Add basic index page
841
Cargo.lock
generated
|
@ -1,841 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
[[package]]
|
|
||||||
name = "adler32"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ansi_term"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayref"
|
|
||||||
version = "0.3.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.4.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atty"
|
|
||||||
version = "0.2.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backtrace"
|
|
||||||
version = "0.3.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backtrace-sys"
|
|
||||||
version = "0.1.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake2b_simd"
|
|
||||||
version = "0.5.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bzip2"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bzip2-sys"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "c2-chacha"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.0.41"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "2.33.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cloudabi"
|
|
||||||
version = "0.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cmake"
|
|
||||||
version = "0.1.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "constant_time_eq"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc32fast"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.6.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs"
|
|
||||||
version = "2.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-sys"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dtoa"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "espanso"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"simplelog 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "failure"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "failure_derive"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flate2"
|
|
||||||
version = "1.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fs2"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-cprng"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.62"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked-hash-map"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log-panics"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miniz_oxide"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodrop"
|
|
||||||
version = "0.1.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.41"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "podio"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "0.4.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "0.6.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_hc"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_os"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rdrand"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.1.56"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_users"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "remove_dir_all"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust-argon2"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yaml"
|
|
||||||
version = "0.8.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "simplelog"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "0.15.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "synstructure"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "term"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread_local"
|
|
||||||
version = "0.3.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.1.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-width"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vec_map"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "widestring"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yaml-rust"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zip"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
|
|
||||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
|
||||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
|
||||||
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
|
|
||||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
|
||||||
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
|
|
||||||
"checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2"
|
|
||||||
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
|
|
||||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
|
||||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
|
||||||
"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499"
|
|
||||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
|
||||||
"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
|
|
||||||
"checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f"
|
|
||||||
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
|
|
||||||
"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
|
|
||||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
|
||||||
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
|
|
||||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
|
||||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
|
||||||
"checksum cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62"
|
|
||||||
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
|
|
||||||
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
|
||||||
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
|
|
||||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
|
||||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
|
||||||
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
|
||||||
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
|
|
||||||
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
|
|
||||||
"checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682"
|
|
||||||
"checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
|
||||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
|
||||||
"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
|
|
||||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
|
||||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|
||||||
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
|
|
||||||
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
|
||||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
|
||||||
"checksum log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ae0136257df209261daa18d6c16394757c63e032e27aafd8b07788b051082bef"
|
|
||||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
|
||||||
"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10"
|
|
||||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
|
||||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
|
||||||
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
|
|
||||||
"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd"
|
|
||||||
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
|
||||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
|
||||||
"checksum proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "175a40b9cf564ce9bf050654633dbf339978706b8ead1a907bb970b63185dd95"
|
|
||||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
|
||||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
|
||||||
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
|
|
||||||
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
|
||||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
|
||||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
|
||||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
|
||||||
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
|
||||||
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
|
||||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
|
||||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
|
||||||
"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
|
|
||||||
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
|
|
||||||
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
|
|
||||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
|
||||||
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
|
|
||||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
|
||||||
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
|
|
||||||
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
|
|
||||||
"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
|
|
||||||
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
|
||||||
"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582"
|
|
||||||
"checksum simplelog 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe8c881061cce7ee205784634eda7a61922925e7cc2833188467d3a560e027"
|
|
||||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|
||||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
|
||||||
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
|
||||||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
|
||||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
|
||||||
"checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5"
|
|
||||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
|
||||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
|
||||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
|
||||||
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
|
|
||||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
|
||||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
|
||||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
|
||||||
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
|
|
||||||
"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
|
|
||||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
|
|
||||||
"checksum zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3c21bb410afa2bd823a047f5bda3adb62f51074ac7e06263b2c97ecdd47e9fc6"
|
|
36
Cargo.toml
|
@ -1,36 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "espanso"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
|
|
||||||
license = "GPL-3.0"
|
|
||||||
description = "Cross-platform Text Expander written in Rust"
|
|
||||||
readme = "README.md"
|
|
||||||
homepage = "https://github.com/federico-terzi/espanso"
|
|
||||||
edition = "2018"
|
|
||||||
build="build.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
widestring = "0.4.0"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_yaml = "0.8"
|
|
||||||
dirs = "2.0.2"
|
|
||||||
clap = "2.33.0"
|
|
||||||
regex = "1.3.1"
|
|
||||||
log = "0.4.8"
|
|
||||||
simplelog = "0.7.1"
|
|
||||||
zip = "0.5.3"
|
|
||||||
fs2 = "0.4.3"
|
|
||||||
serde_json = "1.0.40"
|
|
||||||
log-panics = {version = "2.0.0", features = ["with-backtrace"]}
|
|
||||||
backtrace = "0.3.37"
|
|
||||||
chrono = "0.4.9"
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
|
||||||
libc = "0.2.62"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3.1.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
cmake = "0.1.31"
|
|
|
@ -1,37 +0,0 @@
|
||||||
# Starter pipeline
|
|
||||||
# Start with a minimal pipeline that you can customize to build and deploy your code.
|
|
||||||
# Add steps that build, run tests, deploy, and more:
|
|
||||||
# https://aka.ms/yaml
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: Linux
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
steps:
|
|
||||||
- script: |
|
|
||||||
sudo apt -y update
|
|
||||||
sudo apt install -y libxtst-dev libx11-dev libxdo3 libxdo-dev
|
|
||||||
displayName: Install library dependencies
|
|
||||||
- template: ci/test.yml
|
|
||||||
- template: ci/build-linux.yml
|
|
||||||
- template: ci/deploy.yml
|
|
||||||
|
|
||||||
- job: macOS
|
|
||||||
pool:
|
|
||||||
vmImage: 'macOS-10.14'
|
|
||||||
steps:
|
|
||||||
- template: ci/test.yml
|
|
||||||
- template: ci/build-macos.yml
|
|
||||||
- template: ci/deploy.yml
|
|
||||||
- template: ci/publish-homebrew.yml
|
|
||||||
|
|
||||||
- job: Windows
|
|
||||||
pool:
|
|
||||||
vmImage: 'windows-2019'
|
|
||||||
steps:
|
|
||||||
- template: ci/test.yml
|
|
||||||
- template: ci/build-win.yml
|
|
||||||
- template: ci/deploy.yml
|
|
55
build.rs
|
@ -1,55 +0,0 @@
|
||||||
extern crate cmake;
|
|
||||||
use cmake::Config;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/* OS SPECIFIC CONFIGS */
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn get_config() -> PathBuf {
|
|
||||||
Config::new("native/libwinbridge").build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn get_config() -> PathBuf {
|
|
||||||
Config::new("native/liblinuxbridge").build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn get_config() -> PathBuf {
|
|
||||||
Config::new("native/libmacbridge").build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
OS CUSTOM CARGO CONFIG LINES
|
|
||||||
Note: this is where linked libraries should be specified.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn print_config() {
|
|
||||||
println!("cargo:rustc-link-lib=static=winbridge");
|
|
||||||
println!("cargo:rustc-link-lib=dylib=user32");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn print_config() {
|
|
||||||
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
|
|
||||||
println!("cargo:rustc-link-lib=static=linuxbridge");
|
|
||||||
println!("cargo:rustc-link-lib=dylib=X11");
|
|
||||||
println!("cargo:rustc-link-lib=dylib=Xtst");
|
|
||||||
println!("cargo:rustc-link-lib=dylib=xdo");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn print_config() {
|
|
||||||
println!("cargo:rustc-link-lib=dylib=c++");
|
|
||||||
println!("cargo:rustc-link-lib=static=macbridge");
|
|
||||||
println!("cargo:rustc-link-lib=framework=Cocoa");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main()
|
|
||||||
{
|
|
||||||
let dst = get_config();
|
|
||||||
|
|
||||||
println!("cargo:rustc-link-search=native={}", dst.display());
|
|
||||||
print_config();
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
steps:
|
|
||||||
- script: |
|
|
||||||
cargo build --release
|
|
||||||
cd target/release/
|
|
||||||
tar czf "espanso-linux.tar.gz" espanso
|
|
||||||
cd ../..
|
|
||||||
cp target/release/espanso-*.gz .
|
|
||||||
ls -la
|
|
||||||
displayName: "Cargo build and packaging for Linux"
|
|
|
@ -1,18 +0,0 @@
|
||||||
steps:
|
|
||||||
- task: UsePythonVersion@0
|
|
||||||
inputs:
|
|
||||||
versionSpec: '3.7.4'
|
|
||||||
addToPath: true
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
python --version
|
|
||||||
python -m pip install toml click
|
|
||||||
displayName: Installing python dependencies
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
set -e
|
|
||||||
python packager.py build
|
|
||||||
cp target/packager/mac/espanso-*.gz .
|
|
||||||
cp target/packager/mac/espanso.rb .
|
|
||||||
ls -la
|
|
||||||
displayName: "Cargo build and packaging for MacOS"
|
|
|
@ -1,17 +0,0 @@
|
||||||
steps:
|
|
||||||
- task: UsePythonVersion@0
|
|
||||||
inputs:
|
|
||||||
versionSpec: '3.7.4'
|
|
||||||
addToPath: true
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
python --version
|
|
||||||
python -m pip install toml click
|
|
||||||
displayName: Installing python dependencies
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
python packager.py build
|
|
||||||
copy "target\\packager\\win\\espanso-win-installer.exe" "espanso-win-installer.exe"
|
|
||||||
dir
|
|
||||||
displayName: "Build and packaging for Windows"
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
parameters:
|
|
||||||
github:
|
|
||||||
isPreRelease: false
|
|
||||||
repositoryName: '$(Build.Repository.Name)'
|
|
||||||
gitHubConnection: "MyGithubConnection"
|
|
||||||
dependsOn: []
|
|
||||||
displayName: "Release to github"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- script: |
|
|
||||||
VER=$(cat Cargo.toml| grep version -m 1 | awk -F '"' '{ print $2 }')
|
|
||||||
echo '##vso[task.setvariable variable=vers]'v$VER
|
|
||||||
condition: not(eq(variables['Agent.OS'], 'Windows_NT'))
|
|
||||||
displayName: Obtain version from Cargo.toml on Unix
|
|
||||||
|
|
||||||
- powershell: |
|
|
||||||
Select-String -Path "Cargo.toml" -Pattern "version" | Select-Object -First 1 -outvariable v
|
|
||||||
$vv = [regex]::match($v, '"([^"]+)"').Groups[1].Value
|
|
||||||
echo "##vso[task.setvariable variable=vers]v$vv"
|
|
||||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
|
||||||
displayName: Obtain version from Cargo.toml on Windows
|
|
||||||
|
|
||||||
- task: GitHubRelease@0
|
|
||||||
displayName: Create GitHub release
|
|
||||||
inputs:
|
|
||||||
gitHubConnection: ${{ parameters.github.gitHubConnection }}
|
|
||||||
tagSource: manual
|
|
||||||
title: '$(vers)'
|
|
||||||
tag: '$(vers)'
|
|
||||||
assetUploadMode: replace
|
|
||||||
action: edit
|
|
||||||
assets: 'espanso-*'
|
|
||||||
addChangeLog: false
|
|
||||||
repositoryName: ${{ parameters.github.repositoryName }}
|
|
||||||
isPreRelease: ${{ parameters.github.isPreRelease }}
|
|
|
@ -1,35 +0,0 @@
|
||||||
# defaults for any parameters that aren't specified
|
|
||||||
parameters:
|
|
||||||
rust_version: stable
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# Linux and macOS.
|
|
||||||
- script: |
|
|
||||||
set -e
|
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN
|
|
||||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin"
|
|
||||||
env:
|
|
||||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
|
||||||
displayName: "Install rust (*nix)"
|
|
||||||
condition: not(eq(variables['Agent.OS'], 'Windows_NT'))
|
|
||||||
# Windows.
|
|
||||||
- script: |
|
|
||||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
|
||||||
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN%
|
|
||||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
|
||||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
|
||||||
env:
|
|
||||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
|
||||||
displayName: "Install rust (windows)"
|
|
||||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
|
||||||
# Install additional components:
|
|
||||||
- ${{ each component in parameters.components }}:
|
|
||||||
- script: rustup component add ${{ component }}
|
|
||||||
|
|
||||||
# All platforms.
|
|
||||||
- script: |
|
|
||||||
rustup -V
|
|
||||||
rustup component list --installed
|
|
||||||
rustc -Vv
|
|
||||||
cargo -V
|
|
||||||
displayName: Query rust and cargo versions
|
|
|
@ -1,21 +0,0 @@
|
||||||
steps:
|
|
||||||
- task: InstallSSHKey@0
|
|
||||||
inputs:
|
|
||||||
knownHostsEntry: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="
|
|
||||||
sshPublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsB9zcHN84/T5URAsfIpb52HnJl2kUK7WWXyV9pFXaO6yz722JxzVq56J3TTrcUCDhM3DKSGKivB6n/tmLw4mefcY3t7kh8puAtaNrNnB4TWqVPFHZtnpYuYslp1rM92r7Bz1FHfVfsDZxqSWlGU/lp0gNEEgXbr2PCExbCh3TGTsKePARhMAtPEvyEZk1+8uA/HvUTjhuDp7P+BbejAsqtgVF0QoEvqDE5af8DZY6+i1cHRgwBYgSnOus8FHsZUGMyAJQtb+dD7imGw/nzokPJzbmQJwQetyhp52CfThpAm12EFtIU43imb8nndlVAmsIHF6czbmI5LP3U0UcTLct freddy@freddy-Z97M-DS3H"
|
|
||||||
sshKeySecureFile: "azuressh"
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
set -ex
|
|
||||||
cat ~/.ssh/known_hosts
|
|
||||||
git config --global user.email "federicoterzi96@gmail.com"
|
|
||||||
git config --global user.email "Federico Terzi"
|
|
||||||
VER=$(cat Cargo.toml| grep version -m 1 | awk -F '"' '{ print $2 }')
|
|
||||||
git clone git@github.com:federico-terzi/homebrew-espanso.git
|
|
||||||
rm homebrew-espanso/Formula/espanso.rb
|
|
||||||
cp espanso.rb homebrew-espanso/Formula/espanso.rb
|
|
||||||
cd homebrew-espanso
|
|
||||||
git add -A
|
|
||||||
git commit -m "Update to version: $VER"
|
|
||||||
git push
|
|
||||||
displayName: "Publishing to Homebrew"
|
|
16
ci/test.yml
|
@ -1,16 +0,0 @@
|
||||||
parameters:
|
|
||||||
rust_version: stable
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- template: install-rust.yml
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
set -e
|
|
||||||
cargo test --release
|
|
||||||
displayName: Cargo tests on Unix
|
|
||||||
condition: not(eq(variables['Agent.OS'], 'Windows_NT'))
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
cargo test --release
|
|
||||||
displayName: Cargo tests on Windows
|
|
||||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
|
Before Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 24 KiB |
13
index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>espanso</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Coming soon</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,9 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
project(liblinuxbridge)
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
set(CMAKE_REQUIRED_INCLUDES "/usr/local/include" "/usr/include")
|
|
||||||
|
|
||||||
add_library(linuxbridge STATIC bridge.cpp bridge.h)
|
|
||||||
|
|
||||||
install(TARGETS linuxbridge DESTINATION .)
|
|
|
@ -1,383 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "bridge.h"
|
|
||||||
|
|
||||||
#include <locale.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <array>
|
|
||||||
#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>
|
|
||||||
extern "C" { // Needed to avoid C++ compiler name mangling
|
|
||||||
#include <xdo.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This code uses the X11 Record Extension to receive keyboard
|
|
||||||
events. Documentation of this library can be found here:
|
|
||||||
https://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.html
|
|
||||||
|
|
||||||
We will refer to this extension as RE from now on.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
This struct is needed to receive events from the RE.
|
|
||||||
The funny thing is: it's not defined there, it should though.
|
|
||||||
The only place this is mentioned is the libxnee library,
|
|
||||||
so check that out if you need a reference.
|
|
||||||
*/
|
|
||||||
typedef union {
|
|
||||||
unsigned char type ;
|
|
||||||
xEvent event ;
|
|
||||||
xResourceReq req ;
|
|
||||||
xGenericReply reply ;
|
|
||||||
xError error ;
|
|
||||||
xConnSetupPrefix setup;
|
|
||||||
} XRecordDatum;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Connections to the X server, RE recommends 2 connections:
|
|
||||||
one for recording control and one for reading the recorded data.
|
|
||||||
*/
|
|
||||||
Display *data_disp = NULL;
|
|
||||||
Display *ctrl_disp = NULL;
|
|
||||||
|
|
||||||
XRecordRange *record_range;
|
|
||||||
XRecordContext context;
|
|
||||||
|
|
||||||
xdo_t * xdo_context;
|
|
||||||
|
|
||||||
// Callback invoked when a new key event occur.
|
|
||||||
void event_callback (XPointer, XRecordInterceptData*);
|
|
||||||
|
|
||||||
KeypressCallback keypress_callback;
|
|
||||||
void * context_instance;
|
|
||||||
|
|
||||||
void register_keypress_callback(KeypressCallback callback) {
|
|
||||||
keypress_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int32_t initialize(void * _context_instance) {
|
|
||||||
setlocale(LC_ALL, "");
|
|
||||||
|
|
||||||
context_instance = _context_instance;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Open the connections to the X server.
|
|
||||||
RE recommends to open 2 connections to the X server:
|
|
||||||
one for the recording control and one to read the protocol
|
|
||||||
data.
|
|
||||||
*/
|
|
||||||
ctrl_disp = XOpenDisplay(NULL);
|
|
||||||
data_disp = XOpenDisplay(NULL);
|
|
||||||
|
|
||||||
if (!ctrl_disp || !data_disp) { // Display error
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
We must set the ctrl_disp to sync mode, or, when we the enable
|
|
||||||
context in data_disp, there will be a fatal X error.
|
|
||||||
*/
|
|
||||||
XSynchronize(ctrl_disp, True);
|
|
||||||
|
|
||||||
int dummy;
|
|
||||||
|
|
||||||
// Make sure the X RE is installed in this system.
|
|
||||||
if (!XRecordQueryVersion(ctrl_disp, &dummy, &dummy)) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the X Keyboard Extension is installed
|
|
||||||
if (!XkbQueryExtension(ctrl_disp, &dummy, &dummy, &dummy, &dummy, &dummy)) {
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the record range, that is the kind of events we want to track.
|
|
||||||
record_range = XRecordAllocRange ();
|
|
||||||
if (!record_range) {
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
record_range->device_events.first = KeyPress;
|
|
||||||
record_range->device_events.last = KeyRelease;
|
|
||||||
|
|
||||||
// We want to get the keys from all clients
|
|
||||||
XRecordClientSpec client_spec;
|
|
||||||
client_spec = XRecordAllClients;
|
|
||||||
|
|
||||||
// Initialize the context
|
|
||||||
context = XRecordCreateContext(ctrl_disp, 0, &client_spec, 1, &record_range, 1);
|
|
||||||
if (!context) {
|
|
||||||
return -5;
|
|
||||||
}
|
|
||||||
|
|
||||||
xdo_context = xdo_new(NULL);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t eventloop() {
|
|
||||||
if (!XRecordEnableContext (data_disp, context, event_callback, NULL)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup() {
|
|
||||||
XRecordDisableContext(ctrl_disp, context);
|
|
||||||
XRecordFreeContext(ctrl_disp, context);
|
|
||||||
XFree (record_range);
|
|
||||||
XCloseDisplay(data_disp);
|
|
||||||
XCloseDisplay(ctrl_disp);
|
|
||||||
xdo_free(xdo_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_callback(XPointer p, XRecordInterceptData *hook)
|
|
||||||
{
|
|
||||||
// Make sure the event comes from the X11 server
|
|
||||||
if (hook->category != XRecordFromServer) {
|
|
||||||
XRecordFreeData(hook);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast the event payload to a XRecordDatum, needed later to access the fields
|
|
||||||
// This struct was hard to find and understand. Turn's out that all the
|
|
||||||
// required data are included in the "event" field of this structure.
|
|
||||||
// The funny thing is that it's not a XEvent as one might expect,
|
|
||||||
// but a xEvent, a very different beast defined in the Xproto.h header.
|
|
||||||
// I suggest you to look at that header if you want to understand where the
|
|
||||||
// upcoming field where taken from.
|
|
||||||
XRecordDatum *data = (XRecordDatum*) hook->data;
|
|
||||||
|
|
||||||
int event_type = data->type;
|
|
||||||
int key_code = data->event.u.u.detail;
|
|
||||||
|
|
||||||
// In order to convert the key_code into the corresponding string,
|
|
||||||
// we need to synthesize an artificial XKeyEvent, to feed later to the
|
|
||||||
// XLookupString function.
|
|
||||||
XKeyEvent event;
|
|
||||||
event.display = ctrl_disp;
|
|
||||||
event.window = data->event.u.focus.window;
|
|
||||||
event.root = XDefaultRootWindow(ctrl_disp);
|
|
||||||
event.subwindow = None;
|
|
||||||
event.time = data->event.u.keyButtonPointer.time;
|
|
||||||
event.x = 1;
|
|
||||||
event.y = 1;
|
|
||||||
event.x_root = 1;
|
|
||||||
event.y_root = 1;
|
|
||||||
event.same_screen = True;
|
|
||||||
event.keycode = key_code;
|
|
||||||
event.state = data->event.u.keyButtonPointer.state;
|
|
||||||
event.type = KeyPress;
|
|
||||||
|
|
||||||
// Extract the corresponding chars.
|
|
||||||
std::array<char, 10> buffer;
|
|
||||||
int res = XLookupString(&event, buffer.data(), buffer.size(), NULL, NULL);
|
|
||||||
|
|
||||||
switch (event_type) {
|
|
||||||
case KeyPress:
|
|
||||||
//printf ("%d %d %s\n", key_code, res, buffer.data());
|
|
||||||
if (res > 0 && key_code != 22) { // Printable character, but not backspace
|
|
||||||
keypress_callback(context_instance, buffer.data(), buffer.size(), 0, key_code);
|
|
||||||
}else{ // Modifier key
|
|
||||||
keypress_callback(context_instance, NULL, 0, 1, key_code);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
XRecordFreeData(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_string(const char * string) {
|
|
||||||
xdo_enter_text_window(xdo_context, CURRENTWINDOW, string, 12000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void delete_string(int32_t count) {
|
|
||||||
for (int i = 0; i<count; i++) {
|
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 8000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void trigger_paste() {
|
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+v", 8000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void trigger_terminal_paste() {
|
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+Shift+v", 8000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SYSTEM MODULE
|
|
||||||
|
|
||||||
// Function taken from the wmlib tool source code
|
|
||||||
char *get_property(Display *disp, Window win,
|
|
||||||
Atom xa_prop_type, char *prop_name, unsigned long *size)
|
|
||||||
{
|
|
||||||
unsigned long ret_nitems, ret_bytes_after, tmp_size;
|
|
||||||
Atom xa_prop_name, xa_ret_type;
|
|
||||||
unsigned char *ret_prop;
|
|
||||||
int ret_format;
|
|
||||||
char *ret;
|
|
||||||
int size_in_byte;
|
|
||||||
|
|
||||||
xa_prop_name = XInternAtom(disp, prop_name, False);
|
|
||||||
|
|
||||||
if (XGetWindowProperty(disp, win, xa_prop_name, 0, 4096 / 4, False,
|
|
||||||
xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems,
|
|
||||||
&ret_bytes_after, &ret_prop) != Success)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (xa_ret_type != xa_prop_type)
|
|
||||||
{
|
|
||||||
XFree(ret_prop);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(ret_format) {
|
|
||||||
case 8: size_in_byte = sizeof(char); break;
|
|
||||||
case 16: size_in_byte = sizeof(short); break;
|
|
||||||
case 32: size_in_byte = sizeof(long); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp_size = size_in_byte * ret_nitems;
|
|
||||||
ret = (char*) malloc(tmp_size + 1);
|
|
||||||
memcpy(ret, ret_prop, tmp_size);
|
|
||||||
ret[tmp_size] = '\0';
|
|
||||||
|
|
||||||
if (size) *size = tmp_size;
|
|
||||||
|
|
||||||
XFree(ret_prop);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function taken from Window Management Library for Ruby
|
|
||||||
char *xwm_get_win_title(Display *disp, Window win)
|
|
||||||
{
|
|
||||||
char *wname = (char*)get_property(disp,win, XA_STRING, "WM_NAME", NULL);
|
|
||||||
char *nwname = (char*)get_property(disp,win, XInternAtom(disp,
|
|
||||||
"UTF8_STRING", False), "_NET_WM_NAME", NULL);
|
|
||||||
|
|
||||||
return nwname ? nwname : (wname ? wname : NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_window_name(char * buffer, int32_t size) {
|
|
||||||
Display *disp = XOpenDisplay(NULL);
|
|
||||||
|
|
||||||
if (!disp) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the active window
|
|
||||||
Window win;
|
|
||||||
int revert_to_return;
|
|
||||||
XGetInputFocus(disp, &win, &revert_to_return);
|
|
||||||
|
|
||||||
char * title = xwm_get_win_title(disp, win);
|
|
||||||
|
|
||||||
snprintf(buffer, size, "%s", title);
|
|
||||||
|
|
||||||
XFree(title);
|
|
||||||
|
|
||||||
XCloseDisplay(disp);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_window_class(char * buffer, int32_t size) {
|
|
||||||
Display *disp = XOpenDisplay(NULL);
|
|
||||||
|
|
||||||
if (!disp) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the active window
|
|
||||||
Window win;
|
|
||||||
int revert_to_return;
|
|
||||||
XGetInputFocus(disp, &win, &revert_to_return);
|
|
||||||
|
|
||||||
XClassHint hint;
|
|
||||||
|
|
||||||
if (XGetClassHint(disp, win, &hint)) {
|
|
||||||
snprintf(buffer, size, "%s", hint.res_class);
|
|
||||||
XFree(hint.res_name);
|
|
||||||
XFree(hint.res_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
XCloseDisplay(disp);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_window_executable(char *buffer, int32_t size) {
|
|
||||||
Display *disp = XOpenDisplay(NULL);
|
|
||||||
|
|
||||||
if (!disp) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the active window
|
|
||||||
Window win;
|
|
||||||
int revert_to_return;
|
|
||||||
XGetInputFocus(disp, &win, &revert_to_return);
|
|
||||||
|
|
||||||
// Get the window process PID
|
|
||||||
char *pid_raw = (char*)get_property(disp,win, XA_CARDINAL, "_NET_WM_PID", NULL);
|
|
||||||
if (pid_raw == NULL) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pid = pid_raw[0] | pid_raw[1] << 8 | pid_raw[2] << 16 | pid_raw[3] << 24;
|
|
||||||
|
|
||||||
// Get the executable path from it
|
|
||||||
char proc_path[250];
|
|
||||||
snprintf(proc_path, 250, "/proc/%d/exe", pid);
|
|
||||||
|
|
||||||
readlink(proc_path, buffer, size);
|
|
||||||
|
|
||||||
XFree(pid_raw);
|
|
||||||
XCloseDisplay(disp);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t is_current_window_terminal() {
|
|
||||||
char class_buffer[250];
|
|
||||||
int res = get_active_window_class(class_buffer, 250);
|
|
||||||
if (res > 0) {
|
|
||||||
if (strstr(class_buffer, "terminal") != NULL) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ESPANSO_BRIDGE_H
|
|
||||||
#define ESPANSO_BRIDGE_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
extern void * context_instance;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the X11 context and parameters
|
|
||||||
*/
|
|
||||||
extern "C" int32_t initialize(void * context_instance);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Start the event loop indefinitely. Blocking call.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t eventloop();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Clean all the X11 resources allocated during the initialization.
|
|
||||||
*/
|
|
||||||
extern "C" void cleanup();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when a new keypress is made, the first argument is an char array,
|
|
||||||
* while the second is the size of the array.
|
|
||||||
*/
|
|
||||||
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
|
|
||||||
|
|
||||||
extern KeypressCallback keypress_callback;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Register the callback that will be called when a keypress was made
|
|
||||||
*/
|
|
||||||
extern "C" void register_keypress_callback(KeypressCallback callback);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Type the given string by simulating Key Presses
|
|
||||||
*/
|
|
||||||
extern "C" void send_string(const char * string);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the backspace keypress, *count* times.
|
|
||||||
*/
|
|
||||||
extern "C" void delete_string(int32_t count);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Trigger normal paste ( Pressing CTRL+V )
|
|
||||||
*/
|
|
||||||
extern "C" void trigger_paste();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Trigger terminal paste ( Pressing CTRL+SHIFT+V )
|
|
||||||
*/
|
|
||||||
extern "C" void trigger_terminal_paste();
|
|
||||||
|
|
||||||
|
|
||||||
// SYSTEM MODULE
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active windows's WM_NAME
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_active_window_name(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active windows's WM_CLASS
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_active_window_class(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active windows's executable path
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_active_window_executable(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return 1 if the current window is a terminal window, 0 otherwise.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t is_current_window_terminal();
|
|
||||||
|
|
||||||
#endif //ESPANSO_BRIDGE_H
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <AppKit/AppKit.h>
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
#include "bridge.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : NSObject <NSApplicationDelegate> {
|
|
||||||
@public NSStatusItem *myStatusItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
|
||||||
- (IBAction) statusIconClick: (id) sender;
|
|
||||||
- (IBAction) contextMenuClick: (id) sender;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
// Setup status icon
|
|
||||||
myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
|
||||||
|
|
||||||
NSString *nsIconPath = [NSString stringWithUTF8String:icon_path];
|
|
||||||
NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath];
|
|
||||||
[statusImage setTemplate:YES];
|
|
||||||
|
|
||||||
[myStatusItem.button setImage:statusImage];
|
|
||||||
[myStatusItem setHighlightMode:YES];
|
|
||||||
[myStatusItem.button setAction:@selector(statusIconClick:)];
|
|
||||||
[myStatusItem.button setTarget:self];
|
|
||||||
|
|
||||||
// Setup key listener
|
|
||||||
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged)
|
|
||||||
handler:^(NSEvent *event){
|
|
||||||
if (event.type == NSEventTypeKeyDown
|
|
||||||
&& event.keyCode != 0x33) { // Send backspace as a modifier
|
|
||||||
|
|
||||||
const char * chars = [event.characters UTF8String];
|
|
||||||
int len = event.characters.length;
|
|
||||||
|
|
||||||
keypress_callback(context_instance, chars, len, 0, event.keyCode);
|
|
||||||
//NSLog(@"keydown: %@, %d", event.characters, event.keyCode);
|
|
||||||
}else{
|
|
||||||
// Because this event is triggered for both the press and release of a modifier, trigger the callback
|
|
||||||
// only on release
|
|
||||||
if (([event modifierFlags] & (NSEventModifierFlagShift | NSEventModifierFlagCommand |
|
|
||||||
NSEventModifierFlagControl | NSEventModifierFlagOption)) == 0) {
|
|
||||||
|
|
||||||
keypress_callback(context_instance, NULL, 0, 1, event.keyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
//NSLog(@"keydown: %d", event.keyCode);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction) statusIconClick: (id) sender {
|
|
||||||
icon_click_callback(context_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction) contextMenuClick: (id) sender {
|
|
||||||
NSInteger item_id = [[sender valueForKey:@"tag"] integerValue];
|
|
||||||
|
|
||||||
context_menu_click_callback(context_instance, static_cast<int32_t>(item_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,9 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
project(libmacbridge)
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_C_FLAGS "-x objective-c")
|
|
||||||
|
|
||||||
add_library(macbridge STATIC bridge.mm bridge.h AppDelegate.h AppDelegate.mm)
|
|
||||||
|
|
||||||
install(TARGETS macbridge DESTINATION .)
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ESPANSO_BRIDGE_H
|
|
||||||
#define ESPANSO_BRIDGE_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
extern void * context_instance;
|
|
||||||
extern char * icon_path;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the AppDelegate and check for accessibility permissions
|
|
||||||
*/
|
|
||||||
int32_t initialize(void * context, const char * icon_path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Start the event loop indefinitely. Blocking call.
|
|
||||||
*/
|
|
||||||
int32_t eventloop();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when a new keypress is made, the first argument is an char array,
|
|
||||||
* while the second is the size of the array.
|
|
||||||
*/
|
|
||||||
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
|
|
||||||
|
|
||||||
extern KeypressCallback keypress_callback;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Register the callback that will be called when a keypress was made
|
|
||||||
*/
|
|
||||||
void register_keypress_callback(KeypressCallback callback);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Type the given string by using the CGEventKeyboardSetUnicodeString call
|
|
||||||
*/
|
|
||||||
void send_string(const char * string);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the Virtual Key press
|
|
||||||
*/
|
|
||||||
void send_vkey(int32_t vk);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the backspace keypress, *count* times.
|
|
||||||
*/
|
|
||||||
void delete_string(int32_t count);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Trigger normal paste ( Pressing CMD+V )
|
|
||||||
*/
|
|
||||||
void trigger_paste();
|
|
||||||
|
|
||||||
// UI
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the tray icon is clicked
|
|
||||||
*/
|
|
||||||
typedef void (*IconClickCallback)(void * self);
|
|
||||||
extern IconClickCallback icon_click_callback;
|
|
||||||
void register_icon_click_callback(IconClickCallback callback);
|
|
||||||
|
|
||||||
// CONTEXT MENU
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t id;
|
|
||||||
int32_t type;
|
|
||||||
char name[100];
|
|
||||||
} MenuItem;
|
|
||||||
|
|
||||||
int32_t show_context_menu(MenuItem * items, int32_t count);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the context menu is clicked
|
|
||||||
*/
|
|
||||||
typedef void (*ContextMenuClickCallback)(void * self, int32_t id);
|
|
||||||
extern ContextMenuClickCallback context_menu_click_callback;
|
|
||||||
extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback);
|
|
||||||
|
|
||||||
// SYSTEM
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if espanso is authorized to control accessibility features, needed to detect key presses.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
int32_t check_accessibility();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prompt to authorize the accessibility features.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
int32_t prompt_accessibility();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Open Security & Privacy settings panel
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
void open_settings_panel();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active NSRunningApplication path
|
|
||||||
*/
|
|
||||||
int32_t get_active_app_bundle(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active NSRunningApplication bundle identifier
|
|
||||||
*/
|
|
||||||
int32_t get_active_app_identifier(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
// CLIPBOARD
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the clipboard text
|
|
||||||
*/
|
|
||||||
int32_t get_clipboard(char * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the clipboard text
|
|
||||||
*/
|
|
||||||
int32_t set_clipboard(char * text);
|
|
||||||
|
|
||||||
};
|
|
||||||
#endif //ESPANSO_BRIDGE_H
|
|
|
@ -1,266 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "bridge.h"
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#include "AppDelegate.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
void * context_instance;
|
|
||||||
char * icon_path;
|
|
||||||
AppDelegate * delegate_ptr;
|
|
||||||
|
|
||||||
KeypressCallback keypress_callback;
|
|
||||||
IconClickCallback icon_click_callback;
|
|
||||||
ContextMenuClickCallback context_menu_click_callback;
|
|
||||||
|
|
||||||
int32_t initialize(void * context, const char * _icon_path) {
|
|
||||||
context_instance = context;
|
|
||||||
icon_path = strdup(_icon_path);
|
|
||||||
|
|
||||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
|
||||||
delegate_ptr = delegate;
|
|
||||||
NSApplication * application = [NSApplication sharedApplication];
|
|
||||||
[application setDelegate:delegate];
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_keypress_callback(KeypressCallback callback) {
|
|
||||||
keypress_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_icon_click_callback(IconClickCallback callback) {
|
|
||||||
icon_click_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_context_menu_click_callback(ContextMenuClickCallback callback) {
|
|
||||||
context_menu_click_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int32_t eventloop() {
|
|
||||||
[NSApp run];
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_string(const char * string) {
|
|
||||||
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
|
|
||||||
NSString *nsString = [NSString stringWithUTF8String:stringCopy];
|
|
||||||
CFStringRef cfString = (__bridge CFStringRef) nsString;
|
|
||||||
std::vector <UniChar> buffer(nsString.length);
|
|
||||||
CFStringGetCharacters(cfString, CFRangeMake(0, nsString.length), buffer.data());
|
|
||||||
|
|
||||||
free(stringCopy);
|
|
||||||
|
|
||||||
// Send the event
|
|
||||||
|
|
||||||
// Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method
|
|
||||||
// the string gets truncated after 20 characters, so we need to send multiple events.
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < buffer.size()) {
|
|
||||||
int chunk_size = 20;
|
|
||||||
if ((i+chunk_size) > buffer.size()) {
|
|
||||||
chunk_size = buffer.size() - i;
|
|
||||||
}
|
|
||||||
|
|
||||||
UniChar * offset_buffer = buffer.data() + i;
|
|
||||||
CGEventRef e = CGEventCreateKeyboardEvent(NULL, 0x31, true);
|
|
||||||
CGEventKeyboardSetUnicodeString(e, chunk_size, offset_buffer);
|
|
||||||
CGEventPost(kCGHIDEventTap, e);
|
|
||||||
CFRelease(e);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
i += chunk_size;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void delete_string(int32_t count) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
CGEventRef keydown;
|
|
||||||
keydown = CGEventCreateKeyboardEvent(NULL, 0x33, true);
|
|
||||||
CGEventPost(kCGHIDEventTap, keydown);
|
|
||||||
CFRelease(keydown);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
CGEventRef keyup;
|
|
||||||
keyup = CGEventCreateKeyboardEvent(NULL, 0x33, false);
|
|
||||||
CGEventPost(kCGHIDEventTap, keyup);
|
|
||||||
CFRelease(keyup);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_vkey(int32_t vk) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
||||||
CGEventRef keydown;
|
|
||||||
keydown = CGEventCreateKeyboardEvent(NULL, vk, true);
|
|
||||||
CGEventPost(kCGHIDEventTap, keydown);
|
|
||||||
CFRelease(keydown);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
CGEventRef keyup;
|
|
||||||
keyup = CGEventCreateKeyboardEvent(NULL, vk, false);
|
|
||||||
CGEventPost(kCGHIDEventTap, keyup);
|
|
||||||
CFRelease(keyup);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void trigger_paste() {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
||||||
CGEventRef keydown;
|
|
||||||
keydown = CGEventCreateKeyboardEvent(NULL, 0x37, true); // CMD
|
|
||||||
CGEventPost(kCGHIDEventTap, keydown);
|
|
||||||
CFRelease(keydown);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
CGEventRef keydown2;
|
|
||||||
keydown2 = CGEventCreateKeyboardEvent(NULL, 0x09, true); // V key
|
|
||||||
CGEventPost(kCGHIDEventTap, keydown2);
|
|
||||||
CFRelease(keydown2);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
CGEventRef keyup;
|
|
||||||
keyup = CGEventCreateKeyboardEvent(NULL, 0x09, false);
|
|
||||||
CGEventPost(kCGHIDEventTap, keyup);
|
|
||||||
CFRelease(keyup);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
|
|
||||||
CGEventRef keyup2;
|
|
||||||
keyup2 = CGEventCreateKeyboardEvent(NULL, 0x37, false); // CMD
|
|
||||||
CGEventPost(kCGHIDEventTap, keyup2);
|
|
||||||
CFRelease(keyup2);
|
|
||||||
|
|
||||||
usleep(2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_app_bundle(char * buffer, int32_t size) {
|
|
||||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
|
||||||
NSString *bundlePath = [frontApp bundleURL].path;
|
|
||||||
const char * path = [bundlePath UTF8String];
|
|
||||||
|
|
||||||
snprintf(buffer, size, "%s", path);
|
|
||||||
|
|
||||||
[bundlePath release];
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_app_identifier(char * buffer, int32_t size) {
|
|
||||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
|
||||||
NSString *bundleId = frontApp.bundleIdentifier;
|
|
||||||
const char * bundle = [bundleId UTF8String];
|
|
||||||
|
|
||||||
snprintf(buffer, size, "%s", bundle);
|
|
||||||
|
|
||||||
[bundleId release];
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_clipboard(char * buffer, int32_t size) {
|
|
||||||
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
|
||||||
for (id element in pasteboard.pasteboardItems) {
|
|
||||||
NSString *string = [element stringForType: NSPasteboardTypeString];
|
|
||||||
if (string != NULL) {
|
|
||||||
const char * text = [string UTF8String];
|
|
||||||
snprintf(buffer, size, "%s", text);
|
|
||||||
|
|
||||||
[string release];
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t set_clipboard(char * text) {
|
|
||||||
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
|
||||||
NSArray *array = @[NSPasteboardTypeString];
|
|
||||||
[pasteboard declareTypes:array owner:nil];
|
|
||||||
|
|
||||||
NSString *nsText = [NSString stringWithUTF8String:text];
|
|
||||||
[pasteboard setString:nsText forType:NSPasteboardTypeString];
|
|
||||||
}
|
|
||||||
|
|
||||||
// CONTEXT MENU
|
|
||||||
|
|
||||||
int32_t show_context_menu(MenuItem * items, int32_t count) {
|
|
||||||
MenuItem * item_copy = (MenuItem*)malloc(sizeof(MenuItem)*count);
|
|
||||||
memcpy(item_copy, items, sizeof(MenuItem)*count);
|
|
||||||
int32_t count_copy = count;
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
||||||
|
|
||||||
NSMenu *espansoMenu = [[NSMenu alloc] initWithTitle:@"Espanso"];
|
|
||||||
|
|
||||||
for (int i = 0; i<count_copy; i++) {
|
|
||||||
if (item_copy[i].type == 1) {
|
|
||||||
NSString *title = [NSString stringWithUTF8String:item_copy[i].name];
|
|
||||||
NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:title action:@selector(contextMenuClick:) keyEquivalent:@""];
|
|
||||||
[newMenu setTag:(NSInteger)item_copy[i].id];
|
|
||||||
[espansoMenu addItem: newMenu];
|
|
||||||
}else{
|
|
||||||
[espansoMenu addItem: [NSMenuItem separatorItem]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(item_copy);
|
|
||||||
|
|
||||||
[delegate_ptr->myStatusItem popUpStatusItemMenu:espansoMenu];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10.9+ only, see this url for compatibility:
|
|
||||||
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
|
|
||||||
int32_t check_accessibility() {
|
|
||||||
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @NO};
|
|
||||||
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t prompt_accessibility() {
|
|
||||||
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
|
|
||||||
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
void open_settings_panel() {
|
|
||||||
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
project(libwinbridge)
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
add_library(winbridge STATIC bridge.cpp bridge.h)
|
|
||||||
|
|
||||||
install(TARGETS winbridge DESTINATION .)
|
|
|
@ -1,644 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "bridge.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#define UNICODE
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <strsafe.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
|
|
||||||
// How many milliseconds must pass between keystrokes to refresh the keyboard layout
|
|
||||||
const long refreshKeyboardLayoutInterval = 2000;
|
|
||||||
|
|
||||||
void * manager_instance;
|
|
||||||
|
|
||||||
// Keyboard listening
|
|
||||||
|
|
||||||
DWORD lastKeyboardPressTick = 0;
|
|
||||||
HKL currentKeyboardLayout;
|
|
||||||
HWND window;
|
|
||||||
const wchar_t* const winclass = L"Espanso";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// UI
|
|
||||||
|
|
||||||
#define APPWM_ICON_CLICK (WM_APP + 1)
|
|
||||||
#define APPWM_NOTIFICATION_POPUP (WM_APP + 2)
|
|
||||||
#define APPWM_NOTIFICATION_CLOSE (WM_APP + 3)
|
|
||||||
#define APPWM_SHOW_CONTEXT_MENU (WM_APP + 4)
|
|
||||||
|
|
||||||
const wchar_t* const notification_winclass = L"EspansoNotification";
|
|
||||||
HWND nw = NULL;
|
|
||||||
HWND hwnd_st_u = NULL;
|
|
||||||
HBITMAP g_espanso_bmp = NULL;
|
|
||||||
HICON g_espanso_ico = NULL;
|
|
||||||
NOTIFYICONDATA nid = {};
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
|
|
||||||
KeypressCallback keypress_callback = NULL;
|
|
||||||
IconClickCallback icon_click_callback = NULL;
|
|
||||||
ContextMenuClickCallback context_menu_click_callback = NULL;
|
|
||||||
|
|
||||||
void register_keypress_callback(KeypressCallback callback) {
|
|
||||||
keypress_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_icon_click_callback(IconClickCallback callback) {
|
|
||||||
icon_click_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_context_menu_click_callback(ContextMenuClickCallback callback) {
|
|
||||||
context_menu_click_callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Message handler procedure for the windows
|
|
||||||
*/
|
|
||||||
LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
|
|
||||||
{
|
|
||||||
HDC hdcStatic = NULL;
|
|
||||||
|
|
||||||
switch (msg)
|
|
||||||
{
|
|
||||||
case WM_DESTROY:
|
|
||||||
std::cout << "\ndestroying window\n";
|
|
||||||
PostQuitMessage(0);
|
|
||||||
DeleteObject(g_espanso_bmp);
|
|
||||||
DeleteObject(g_espanso_ico);
|
|
||||||
return 0L;
|
|
||||||
case WM_COMMAND: // Click on the tray icon context menu
|
|
||||||
{
|
|
||||||
UINT idItem = (UINT)LOWORD(wp);
|
|
||||||
UINT flags = (UINT)HIWORD(wp);
|
|
||||||
|
|
||||||
if (flags == 0) {
|
|
||||||
context_menu_click_callback(manager_instance, (int32_t)idItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPWM_NOTIFICATION_POPUP: // Request to show a notification
|
|
||||||
{
|
|
||||||
std::unique_ptr<wchar_t[]> ptr(reinterpret_cast<wchar_t*>(wp));
|
|
||||||
|
|
||||||
SetWindowText(hwnd_st_u, L" "); // Clear the previous text
|
|
||||||
SetWindowText(hwnd_st_u, ptr.get());
|
|
||||||
|
|
||||||
// Show the window
|
|
||||||
ShowWindow(nw, SW_SHOWNOACTIVATE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPWM_NOTIFICATION_CLOSE: // Request to close a notification
|
|
||||||
{
|
|
||||||
// Hide the window
|
|
||||||
ShowWindow(nw, SW_HIDE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPWM_SHOW_CONTEXT_MENU: // Request to show context menu
|
|
||||||
{
|
|
||||||
HMENU hPopupMenu = CreatePopupMenu();
|
|
||||||
|
|
||||||
// Create the menu
|
|
||||||
|
|
||||||
int32_t count = static_cast<int32_t>(lp);
|
|
||||||
std::unique_ptr<MenuItem[]> items(reinterpret_cast<MenuItem*>(wp));
|
|
||||||
|
|
||||||
for (int i = 0; i<count; i++) {
|
|
||||||
if (items[i].type == 1) {
|
|
||||||
InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
|
|
||||||
}else{
|
|
||||||
InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_SEPARATOR, items[i].id, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
POINT pt;
|
|
||||||
GetCursorPos(&pt);
|
|
||||||
SetForegroundWindow(nw);
|
|
||||||
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPWM_ICON_CLICK: // Click on the tray icon
|
|
||||||
{
|
|
||||||
switch (lp)
|
|
||||||
{
|
|
||||||
case WM_LBUTTONUP:
|
|
||||||
case WM_RBUTTONUP:
|
|
||||||
icon_click_callback(manager_instance);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case WM_PAINT:
|
|
||||||
{
|
|
||||||
BITMAP bm;
|
|
||||||
PAINTSTRUCT ps;
|
|
||||||
|
|
||||||
HDC hdc = BeginPaint(window, &ps);
|
|
||||||
|
|
||||||
HDC hdcMem = CreateCompatibleDC(hdc);
|
|
||||||
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_bmp);
|
|
||||||
|
|
||||||
GetObject(g_espanso_bmp, sizeof(bm), &bm);
|
|
||||||
|
|
||||||
BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY);
|
|
||||||
|
|
||||||
SelectObject(hdcMem, hbmOld);
|
|
||||||
DeleteDC(hdcMem);
|
|
||||||
|
|
||||||
EndPaint(window, &ps);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case WM_CTLCOLORSTATIC:
|
|
||||||
hdcStatic = (HDC)wp;
|
|
||||||
SetTextColor(hdcStatic, RGB(0, 0, 0));
|
|
||||||
SetBkColor(hdcStatic, RGB(255, 255, 255));
|
|
||||||
//SetBkMode(hdcStatic, OPAQUE);
|
|
||||||
|
|
||||||
return (LRESULT)GetStockObject(NULL_BRUSH);
|
|
||||||
case WM_INPUT: // Message relative to the RAW INPUT events
|
|
||||||
{
|
|
||||||
// Get the input size
|
|
||||||
UINT dwSize;
|
|
||||||
GetRawInputData(
|
|
||||||
(HRAWINPUT)lp,
|
|
||||||
RID_INPUT,
|
|
||||||
NULL,
|
|
||||||
&dwSize,
|
|
||||||
sizeof(RAWINPUTHEADER)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a proper sized structure to hold the data
|
|
||||||
std::vector<BYTE> lpb(dwSize);
|
|
||||||
|
|
||||||
// Request the Raw input data
|
|
||||||
if (GetRawInputData((HRAWINPUT)lp, RID_INPUT, lpb.data(), &dwSize,
|
|
||||||
sizeof(RAWINPUTHEADER)) != dwSize) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the input data
|
|
||||||
RAWINPUT* raw = reinterpret_cast<RAWINPUT*>(lpb.data());
|
|
||||||
|
|
||||||
// Make sure it's a keyboard type event, relative to a key press.
|
|
||||||
if (raw->header.dwType == RIM_TYPEKEYBOARD)
|
|
||||||
{
|
|
||||||
// We only want KEY UP AND KEY DOWN events
|
|
||||||
if (raw->data.keyboard.Message != WM_KEYDOWN && raw->data.keyboard.Message != WM_KEYUP) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int is_key_down = raw->data.keyboard.Message == WM_KEYDOWN;
|
|
||||||
|
|
||||||
DWORD currentTick = GetTickCount();
|
|
||||||
|
|
||||||
// If enough time has passed between the last keypress and now, refresh the keyboard layout
|
|
||||||
if ((currentTick - lastKeyboardPressTick) > refreshKeyboardLayoutInterval) {
|
|
||||||
|
|
||||||
// Because keyboard layouts on windows are Window-specific, to get the current
|
|
||||||
// layout we need to get the foreground window and get its layout.
|
|
||||||
|
|
||||||
HWND hwnd = GetForegroundWindow();
|
|
||||||
if (hwnd) {
|
|
||||||
DWORD threadID = GetWindowThreadProcessId(hwnd, NULL);
|
|
||||||
HKL newKeyboardLayout = GetKeyboardLayout(threadID);
|
|
||||||
|
|
||||||
// It's not always valid, so update the current value only if available.
|
|
||||||
if (newKeyboardLayout != 0) {
|
|
||||||
currentKeyboardLayout = newKeyboardLayout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastKeyboardPressTick = currentTick;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get keyboard state ( necessary to decode the associated Unicode char )
|
|
||||||
std::vector<BYTE> lpKeyState(256);
|
|
||||||
if (GetKeyboardState(lpKeyState.data())) {
|
|
||||||
// Convert the virtual key to an unicode char
|
|
||||||
std::array<WCHAR, 4> buffer;
|
|
||||||
int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), buffer.data(), buffer.size(), 0, currentKeyboardLayout);
|
|
||||||
|
|
||||||
//std::cout << result << " " << buffer[0] << " " << raw->data.keyboard.VKey << std::endl;
|
|
||||||
|
|
||||||
// We need to call the callback in two different ways based on the type of key
|
|
||||||
// The only modifier we use that has a result > 0 is the BACKSPACE, so we have to consider it.
|
|
||||||
if (result >= 1 && raw->data.keyboard.VKey != VK_BACK) {
|
|
||||||
keypress_callback(manager_instance, reinterpret_cast<int32_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, is_key_down);
|
|
||||||
}else{
|
|
||||||
keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey, is_key_down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return DefWindowProc(window, msg, wp, lp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path) {
|
|
||||||
manager_instance = self;
|
|
||||||
|
|
||||||
// Load the images
|
|
||||||
g_espanso_bmp = (HBITMAP)LoadImage(NULL, bmp_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
|
||||||
g_espanso_ico = (HICON)LoadImage(NULL, ico_path, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_SHARED | LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
|
||||||
|
|
||||||
// Make the notification capable of handling different screen definitions
|
|
||||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
|
||||||
|
|
||||||
// Initialize the default keyboard layout
|
|
||||||
currentKeyboardLayout = GetKeyboardLayout(0);
|
|
||||||
|
|
||||||
// Initialize the Worker window
|
|
||||||
|
|
||||||
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
|
|
||||||
WNDCLASSEX wndclass = {
|
|
||||||
sizeof(WNDCLASSEX), // cbSize: Size of this structure
|
|
||||||
0, // style: Class styles
|
|
||||||
window_procedure, // lpfnWndProc: Pointer to the window procedure
|
|
||||||
0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure
|
|
||||||
0, // cbWndExtra: The number of extra bytes to allocate following the window instance.
|
|
||||||
GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class.
|
|
||||||
NULL, // hIcon: A handle to the class icon.
|
|
||||||
LoadCursor(0,IDC_ARROW), // hCursor: A handle to the class cursor.
|
|
||||||
NULL, // hbrBackground: A handle to the class background brush.
|
|
||||||
NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu
|
|
||||||
winclass, // lpszClassName: A pointer to a null-terminated string or is an atom.
|
|
||||||
NULL // hIconSm: A handle to a small icon that is associated with the window class.
|
|
||||||
};
|
|
||||||
|
|
||||||
// Notification Window
|
|
||||||
|
|
||||||
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
|
|
||||||
WNDCLASSEX notificationwndclass = {
|
|
||||||
sizeof(WNDCLASSEX), // cbSize: Size of this structure
|
|
||||||
0, // style: Class styles
|
|
||||||
window_procedure, // lpfnWndProc: Pointer to the window procedure
|
|
||||||
0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure
|
|
||||||
0, // cbWndExtra: The number of extra bytes to allocate following the window instance.
|
|
||||||
GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class.
|
|
||||||
NULL, // hIcon: A handle to the class icon.
|
|
||||||
LoadCursor(0,IDC_ARROW), // hCursor: A handle to the class cursor.
|
|
||||||
NULL, // hbrBackground: A handle to the class background brush.
|
|
||||||
NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu
|
|
||||||
notification_winclass, // lpszClassName: A pointer to a null-terminated string or is an atom.
|
|
||||||
NULL // hIconSm: A handle to a small icon that is associated with the window class.
|
|
||||||
};
|
|
||||||
|
|
||||||
if (RegisterClassEx(&wndclass) && RegisterClassEx(¬ificationwndclass))
|
|
||||||
{
|
|
||||||
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
|
||||||
window = CreateWindowEx(
|
|
||||||
0, // dwExStyle: The extended window style of the window being created.
|
|
||||||
winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
|
||||||
L"Espanso Worker Window", // lpWindowName: The window name.
|
|
||||||
WS_OVERLAPPEDWINDOW, // dwStyle: The style of the window being created.
|
|
||||||
CW_USEDEFAULT, // X: The initial horizontal position of the window.
|
|
||||||
CW_USEDEFAULT, // Y: The initial vertical position of the window.
|
|
||||||
100, // nWidth: The width, in device units, of the window.
|
|
||||||
100, // nHeight: The height, in device units, of the window.
|
|
||||||
NULL, // hWndParent: handle to the parent or owner window of the window being created.
|
|
||||||
NULL, // hMenu: A handle to a menu, or specifies a child-window identifier, depending on the window style.
|
|
||||||
GetModuleHandle(0), // hInstance: A handle to the instance of the module to be associated with the window.
|
|
||||||
NULL // lpParam: Pointer to a value to be passed to the window
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register raw inputs
|
|
||||||
RAWINPUTDEVICE Rid[1];
|
|
||||||
|
|
||||||
Rid[0].usUsagePage = 0x01;
|
|
||||||
Rid[0].usUsage = 0x06;
|
|
||||||
Rid[0].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; // adds HID keyboard and also ignores legacy keyboard messages
|
|
||||||
Rid[0].hwndTarget = window;
|
|
||||||
|
|
||||||
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the notification window
|
|
||||||
nw = CreateWindowEx(
|
|
||||||
WS_EX_TOOLWINDOW | WS_EX_TOPMOST, // dwExStyle: The extended window style of the window being created.
|
|
||||||
notification_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
|
||||||
L"Espanso Notification", // lpWindowName: The window name.
|
|
||||||
WS_POPUPWINDOW, // dwStyle: The style of the window being created.
|
|
||||||
CW_USEDEFAULT, // X: The initial horizontal position of the window.
|
|
||||||
CW_USEDEFAULT, // Y: The initial vertical position of the window.
|
|
||||||
300, // nWidth: The width, in device units, of the window.
|
|
||||||
100, // nHeight: The height, in device units, of the window.
|
|
||||||
NULL, // hWndParent: handle to the parent or owner window of the window being created.
|
|
||||||
NULL, // hMenu: A handle to a menu, or specifies a child-window identifier, depending on the window style.
|
|
||||||
GetModuleHandle(0), // hInstance: A handle to the instance of the module to be associated with the window.
|
|
||||||
NULL // lpParam: Pointer to a value to be passed to the window
|
|
||||||
);
|
|
||||||
|
|
||||||
if (nw)
|
|
||||||
{
|
|
||||||
int x, w, y, h;
|
|
||||||
y = 40; h = 30;
|
|
||||||
x = 100; w = 180;
|
|
||||||
hwnd_st_u = CreateWindowEx(0, L"static", L"ST_U",
|
|
||||||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | SS_CENTER,
|
|
||||||
x, y, w, h,
|
|
||||||
nw, (HMENU)(501),
|
|
||||||
(HINSTANCE)GetWindowLong(nw, GWLP_HINSTANCE), NULL);
|
|
||||||
|
|
||||||
SetWindowText(hwnd_st_u, L"Loading...");
|
|
||||||
|
|
||||||
int posX = GetSystemMetrics(SM_CXSCREEN) - 350;
|
|
||||||
int posY = GetSystemMetrics(SM_CYSCREEN) - 200;
|
|
||||||
|
|
||||||
SetWindowPos(nw, HWND_TOP, posX, posY, 0, 0, SWP_NOSIZE);
|
|
||||||
|
|
||||||
// Hide the window
|
|
||||||
ShowWindow(nw, SW_HIDE);
|
|
||||||
|
|
||||||
// Setup the icon in the notification space
|
|
||||||
|
|
||||||
SendMessage(nw, WM_SETICON, ICON_BIG, (LPARAM)g_espanso_ico);
|
|
||||||
SendMessage(nw, WM_SETICON, ICON_SMALL, (LPARAM)g_espanso_ico);
|
|
||||||
|
|
||||||
//Notification
|
|
||||||
nid.cbSize = sizeof(nid);
|
|
||||||
nid.hWnd = nw;
|
|
||||||
nid.uID = 1;
|
|
||||||
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
|
|
||||||
nid.uCallbackMessage = APPWM_ICON_CLICK;
|
|
||||||
nid.hIcon = g_espanso_ico;
|
|
||||||
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso");
|
|
||||||
|
|
||||||
// Show the notification.
|
|
||||||
Shell_NotifyIcon(NIM_ADD, &nid);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// Something went wrong, error.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void eventloop() {
|
|
||||||
if (window)
|
|
||||||
{
|
|
||||||
// Hide the window
|
|
||||||
ShowWindow(window, SW_HIDE);
|
|
||||||
|
|
||||||
// Enter the Event loop
|
|
||||||
MSG msg;
|
|
||||||
while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Something went wrong, this should have been an infinite loop.
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Type the given string simulating keyboard presses.
|
|
||||||
*/
|
|
||||||
void send_string(const wchar_t * string) {
|
|
||||||
std::wstring msg = string;
|
|
||||||
|
|
||||||
std::vector<INPUT> vec;
|
|
||||||
for (auto ch : msg)
|
|
||||||
{
|
|
||||||
INPUT input = { 0 };
|
|
||||||
input.type = INPUT_KEYBOARD;
|
|
||||||
input.ki.dwFlags = KEYEVENTF_UNICODE;
|
|
||||||
input.ki.wScan = ch;
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
|
||||||
vec.push_back(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
SendInput(vec.size(), vec.data(), sizeof(INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the backspace keypress, *count* times.
|
|
||||||
*/
|
|
||||||
void delete_string(int32_t count) {
|
|
||||||
std::vector<INPUT> vec;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
INPUT input = { 0 };
|
|
||||||
|
|
||||||
input.type = INPUT_KEYBOARD;
|
|
||||||
input.ki.wScan = 0;
|
|
||||||
input.ki.time = 0;
|
|
||||||
input.ki.dwExtraInfo = 0;
|
|
||||||
input.ki.wVk = VK_BACK;
|
|
||||||
input.ki.dwFlags = 0; // 0 for key press
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
|
|
||||||
vec.push_back(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
SendInput(vec.size(), vec.data(), sizeof(INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_vkey(int32_t vk) {
|
|
||||||
std::vector<INPUT> vec;
|
|
||||||
|
|
||||||
INPUT input = { 0 };
|
|
||||||
|
|
||||||
input.type = INPUT_KEYBOARD;
|
|
||||||
input.ki.wScan = 0;
|
|
||||||
input.ki.time = 0;
|
|
||||||
input.ki.dwExtraInfo = 0;
|
|
||||||
input.ki.wVk = vk;
|
|
||||||
input.ki.dwFlags = 0; // 0 for key press
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
SendInput(vec.size(), vec.data(), sizeof(INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
void trigger_paste() {
|
|
||||||
std::vector<INPUT> vec;
|
|
||||||
|
|
||||||
INPUT input = { 0 };
|
|
||||||
|
|
||||||
input.type = INPUT_KEYBOARD;
|
|
||||||
input.ki.wScan = 0;
|
|
||||||
input.ki.time = 0;
|
|
||||||
input.ki.dwExtraInfo = 0;
|
|
||||||
input.ki.wVk = VK_CONTROL;
|
|
||||||
input.ki.dwFlags = 0; // 0 for key press
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.wVk = 0x56; // V KEY
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
input.ki.wVk = VK_CONTROL;
|
|
||||||
vec.push_back(input);
|
|
||||||
|
|
||||||
SendInput(vec.size(), vec.data(), sizeof(INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// SYSTEM
|
|
||||||
|
|
||||||
int32_t get_active_window_name(wchar_t * buffer, int32_t size) {
|
|
||||||
HWND hwnd = GetForegroundWindow();
|
|
||||||
|
|
||||||
return GetWindowText(hwnd, buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_active_window_executable(wchar_t * buffer, int32_t size) {
|
|
||||||
HWND hwnd = GetForegroundWindow();
|
|
||||||
|
|
||||||
// Extract the window PID
|
|
||||||
DWORD windowPid;
|
|
||||||
GetWindowThreadProcessId(hwnd, &windowPid);
|
|
||||||
|
|
||||||
DWORD dsize = (DWORD) size;
|
|
||||||
|
|
||||||
// Extract the process executable file path
|
|
||||||
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, windowPid);
|
|
||||||
int res = QueryFullProcessImageNameW(process, 0, buffer, &dsize);
|
|
||||||
CloseHandle(process);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notifications
|
|
||||||
|
|
||||||
int32_t show_notification(wchar_t * message) {
|
|
||||||
if (nw != NULL) {
|
|
||||||
wchar_t * buffer = new wchar_t[100];
|
|
||||||
swprintf(buffer, 100, L"%ls", message);
|
|
||||||
|
|
||||||
PostMessage(nw, APPWM_NOTIFICATION_POPUP, reinterpret_cast<WPARAM>(buffer), 0);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void close_notification() {
|
|
||||||
if (nw != NULL) {
|
|
||||||
PostMessage(nw, APPWM_NOTIFICATION_CLOSE, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t show_context_menu(MenuItem * items, int32_t count) {
|
|
||||||
if (nw != NULL) {
|
|
||||||
MenuItem * items_buffer = new MenuItem[count];
|
|
||||||
memcpy(items_buffer, items, sizeof(MenuItem)*count);
|
|
||||||
|
|
||||||
PostMessage(nw, APPWM_SHOW_CONTEXT_MENU, reinterpret_cast<WPARAM>(items_buffer), static_cast<LPARAM>(count));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup_ui() {
|
|
||||||
Shell_NotifyIcon(NIM_DELETE, &nid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SYSTEM
|
|
||||||
|
|
||||||
int32_t start_daemon_process() {
|
|
||||||
wchar_t cmd[MAX_PATH];
|
|
||||||
swprintf(cmd, MAX_PATH, L"espanso.exe daemon");
|
|
||||||
|
|
||||||
// Get current espanso directory
|
|
||||||
TCHAR espansoFilePath[MAX_PATH];
|
|
||||||
GetModuleFileName(NULL, espansoFilePath, MAX_PATH);
|
|
||||||
|
|
||||||
STARTUPINFO si = { sizeof(si) };
|
|
||||||
PROCESS_INFORMATION pi;
|
|
||||||
|
|
||||||
// Documentation: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
|
|
||||||
BOOL res = CreateProcess(
|
|
||||||
espansoFilePath,
|
|
||||||
cmd,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
FALSE,
|
|
||||||
DETACHED_PROCESS,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
&si,
|
|
||||||
&pi
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLIPBOARD
|
|
||||||
|
|
||||||
int32_t set_clipboard(wchar_t *text) {
|
|
||||||
const size_t len = wcslen(text) + 1;
|
|
||||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(wchar_t));
|
|
||||||
memcpy(GlobalLock(hMem), text, len * sizeof(wchar_t));
|
|
||||||
GlobalUnlock(hMem);
|
|
||||||
if (!OpenClipboard(NULL)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
EmptyClipboard();
|
|
||||||
if (!SetClipboardData(CF_UNICODETEXT, hMem)) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
CloseClipboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t get_clipboard(wchar_t *buffer, int32_t size) {
|
|
||||||
if (!OpenClipboard(NULL)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get handle of clipboard object for ANSI text
|
|
||||||
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
|
|
||||||
if (!hData) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
HGLOBAL hMem = GlobalLock(hData);
|
|
||||||
if (!hMem) {
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalUnlock(hMem);
|
|
||||||
|
|
||||||
swprintf(buffer, size, L"%s", hMem);
|
|
||||||
|
|
||||||
CloseClipboard();
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ESPANSO_BRIDGE_H
|
|
||||||
#define ESPANSO_BRIDGE_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// SYSTEM
|
|
||||||
|
|
||||||
extern "C" int32_t start_daemon_process();
|
|
||||||
|
|
||||||
extern void * manager_instance;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the Windows parameters
|
|
||||||
* return: 1 if OK, -1 otherwise.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when a new keypress is made, the first argument is an int array,
|
|
||||||
* while the second is the size of the array.
|
|
||||||
*/
|
|
||||||
typedef void (*KeypressCallback)(void * self, int32_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code, int32_t is_key_down);
|
|
||||||
extern KeypressCallback keypress_callback;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Register the callback that will be called when a keypress was made
|
|
||||||
*/
|
|
||||||
extern "C" void register_keypress_callback(KeypressCallback callback);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Start the event loop indefinitely. Blocking call.
|
|
||||||
*/
|
|
||||||
extern "C" void eventloop();
|
|
||||||
|
|
||||||
// Keyboard Manager
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Type the given string by simulating Key Presses
|
|
||||||
*/
|
|
||||||
extern "C" void send_string(const wchar_t * string);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the given Virtual Key press
|
|
||||||
*/
|
|
||||||
extern "C" void send_vkey(int32_t vk);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the backspace keypress, *count* times.
|
|
||||||
*/
|
|
||||||
extern "C" void delete_string(int32_t count);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the Paste keyboard shortcut (CTRL+V)
|
|
||||||
*/
|
|
||||||
extern "C" void trigger_paste();
|
|
||||||
|
|
||||||
// Detect current application commands
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active windows's title
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_active_window_name(wchar_t * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the active windows's executable path
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size);
|
|
||||||
|
|
||||||
// UI
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the tray icon is clicked
|
|
||||||
*/
|
|
||||||
typedef void (*IconClickCallback)(void * self);
|
|
||||||
extern IconClickCallback icon_click_callback;
|
|
||||||
extern "C" void register_icon_click_callback(IconClickCallback callback);
|
|
||||||
|
|
||||||
// CONTEXT MENU
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t id;
|
|
||||||
int32_t type;
|
|
||||||
wchar_t name[100];
|
|
||||||
} MenuItem;
|
|
||||||
|
|
||||||
extern "C" int32_t show_context_menu(MenuItem * items, int32_t count);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the context menu is clicked
|
|
||||||
*/
|
|
||||||
typedef void (*ContextMenuClickCallback)(void * self, int32_t id);
|
|
||||||
extern ContextMenuClickCallback context_menu_click_callback;
|
|
||||||
extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hide the tray icon
|
|
||||||
*/
|
|
||||||
extern "C" void cleanup_ui();
|
|
||||||
|
|
||||||
// NOTIFICATION
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Show a window containing the notification.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t show_notification(wchar_t * message);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Close the notification if present
|
|
||||||
*/
|
|
||||||
extern "C" void close_notification();
|
|
||||||
|
|
||||||
// CLIPBOARD
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the clipboard text
|
|
||||||
*/
|
|
||||||
extern "C" int32_t get_clipboard(wchar_t * buffer, int32_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the clipboard text
|
|
||||||
*/
|
|
||||||
extern "C" int32_t set_clipboard(wchar_t * text);
|
|
||||||
|
|
||||||
#endif //ESPANSO_BRIDGE_H
|
|
|
@ -1,312 +0,0 @@
|
||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 50;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
B6F9DF16232283F8005233EB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B6F9DF15232283F8005233EB /* AppDelegate.m */; };
|
|
||||||
B6F9DF18232283F8005233EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B6F9DF17232283F8005233EB /* Assets.xcassets */; };
|
|
||||||
B6F9DF1E232283F8005233EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B6F9DF1D232283F8005233EB /* main.m */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
B6F9DF11232283F8005233EB /* EspansoNotifyHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EspansoNotifyHelper.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
B6F9DF14232283F8005233EB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
|
||||||
B6F9DF15232283F8005233EB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
|
||||||
B6F9DF17232283F8005233EB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
|
||||||
B6F9DF1C232283F8005233EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
B6F9DF1D232283F8005233EB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
|
||||||
B6F9DF1F232283F8005233EB /* EspansoNotifyHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EspansoNotifyHelper.entitlements; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
B6F9DF0E232283F8005233EB /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
B6F9DF08232283F8005233EB = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
B6F9DF13232283F8005233EB /* EspansoNotifyHelper */,
|
|
||||||
B6F9DF12232283F8005233EB /* Products */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B6F9DF12232283F8005233EB /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
B6F9DF11232283F8005233EB /* EspansoNotifyHelper.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B6F9DF13232283F8005233EB /* EspansoNotifyHelper */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
B6F9DF14232283F8005233EB /* AppDelegate.h */,
|
|
||||||
B6F9DF15232283F8005233EB /* AppDelegate.m */,
|
|
||||||
B6F9DF17232283F8005233EB /* Assets.xcassets */,
|
|
||||||
B6F9DF1C232283F8005233EB /* Info.plist */,
|
|
||||||
B6F9DF1D232283F8005233EB /* main.m */,
|
|
||||||
B6F9DF1F232283F8005233EB /* EspansoNotifyHelper.entitlements */,
|
|
||||||
);
|
|
||||||
path = EspansoNotifyHelper;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
B6F9DF10232283F8005233EB /* EspansoNotifyHelper */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = B6F9DF22232283F8005233EB /* Build configuration list for PBXNativeTarget "EspansoNotifyHelper" */;
|
|
||||||
buildPhases = (
|
|
||||||
B6F9DF0D232283F8005233EB /* Sources */,
|
|
||||||
B6F9DF0E232283F8005233EB /* Frameworks */,
|
|
||||||
B6F9DF0F232283F8005233EB /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = EspansoNotifyHelper;
|
|
||||||
productName = EspansoNotifyHelper;
|
|
||||||
productReference = B6F9DF11232283F8005233EB /* EspansoNotifyHelper.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
B6F9DF09232283F8005233EB /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
LastUpgradeCheck = 1010;
|
|
||||||
ORGANIZATIONNAME = "Federico Terzi";
|
|
||||||
TargetAttributes = {
|
|
||||||
B6F9DF10232283F8005233EB = {
|
|
||||||
CreatedOnToolsVersion = 10.1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = B6F9DF0C232283F8005233EB /* Build configuration list for PBXProject "EspansoNotifyHelper" */;
|
|
||||||
compatibilityVersion = "Xcode 9.3";
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
);
|
|
||||||
mainGroup = B6F9DF08232283F8005233EB;
|
|
||||||
productRefGroup = B6F9DF12232283F8005233EB /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
B6F9DF10232283F8005233EB /* EspansoNotifyHelper */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
B6F9DF0F232283F8005233EB /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
B6F9DF18232283F8005233EB /* Assets.xcassets in Resources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
B6F9DF0D232283F8005233EB /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
B6F9DF1E232283F8005233EB /* main.m in Sources */,
|
|
||||||
B6F9DF16232283F8005233EB /* AppDelegate.m in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
B6F9DF20232283F8005233EB /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
SDKROOT = macosx;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
B6F9DF21232283F8005233EB /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
SDKROOT = macosx;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
B6F9DF23232283F8005233EB /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = EspansoNotifyHelper/EspansoNotifyHelper.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
|
||||||
DEVELOPMENT_TEAM = N69XJWRM3X;
|
|
||||||
INFOPLIST_FILE = EspansoNotifyHelper/Info.plist;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/../Frameworks",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.federicoterzi.EspansoNotifyHelper;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
B6F9DF24232283F8005233EB /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = EspansoNotifyHelper/EspansoNotifyHelper.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
|
||||||
DEVELOPMENT_TEAM = N69XJWRM3X;
|
|
||||||
INFOPLIST_FILE = EspansoNotifyHelper/Info.plist;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/../Frameworks",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.federicoterzi.EspansoNotifyHelper;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
B6F9DF0C232283F8005233EB /* Build configuration list for PBXProject "EspansoNotifyHelper" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
B6F9DF20232283F8005233EB /* Debug */,
|
|
||||||
B6F9DF21232283F8005233EB /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
B6F9DF22232283F8005233EB /* Build configuration list for PBXNativeTarget "EspansoNotifyHelper" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
B6F9DF23232283F8005233EB /* Debug */,
|
|
||||||
B6F9DF24232283F8005233EB /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = B6F9DF09232283F8005233EB /* Project object */;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:EspansoNotifyHelper.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
|
@ -1,91 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1010"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "B6F9DF10232283F8005233EB"
|
|
||||||
BuildableName = "EspansoNotifyHelper.app"
|
|
||||||
BlueprintName = "EspansoNotifyHelper"
|
|
||||||
ReferencedContainer = "container:EspansoNotifyHelper.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
</Testables>
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "B6F9DF10232283F8005233EB"
|
|
||||||
BuildableName = "EspansoNotifyHelper.app"
|
|
||||||
BlueprintName = "EspansoNotifyHelper"
|
|
||||||
ReferencedContainer = "container:EspansoNotifyHelper.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "B6F9DF10232283F8005233EB"
|
|
||||||
BuildableName = "EspansoNotifyHelper.app"
|
|
||||||
BlueprintName = "EspansoNotifyHelper"
|
|
||||||
ReferencedContainer = "container:EspansoNotifyHelper.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "B6F9DF10232283F8005233EB"
|
|
||||||
BuildableName = "EspansoNotifyHelper.app"
|
|
||||||
BlueprintName = "EspansoNotifyHelper"
|
|
||||||
ReferencedContainer = "container:EspansoNotifyHelper.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>SchemeUserState</key>
|
|
||||||
<dict>
|
|
||||||
<key>EspansoNotifyHelper.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>SuppressBuildableAutocreation</key>
|
|
||||||
<dict>
|
|
||||||
<key>B6F9DF10232283F8005233EB</key>
|
|
||||||
<dict>
|
|
||||||
<key>primary</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate>
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
@interface AppDelegate ()
|
|
||||||
|
|
||||||
@property (weak) IBOutlet NSWindow *window;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
|
||||||
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
|
|
||||||
|
|
||||||
NSArray *args = [[NSProcessInfo processInfo] arguments];
|
|
||||||
|
|
||||||
NSString *title = @"Title";
|
|
||||||
NSString *desc = @"Description";
|
|
||||||
double delay = 1.5;
|
|
||||||
|
|
||||||
if ([args count] > 3) {
|
|
||||||
title = args[1];
|
|
||||||
desc = args[2];
|
|
||||||
delay = [args[3] doubleValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSUserNotification *notification = [[NSUserNotification alloc] init];
|
|
||||||
notification.title = title;
|
|
||||||
notification.informativeText = desc;
|
|
||||||
notification.soundName = nil;
|
|
||||||
|
|
||||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
|
||||||
|
|
||||||
[[NSUserNotificationCenter defaultUserNotificationCenter] performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:delay];
|
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
|
||||||
NSRunningApplication *app = [NSRunningApplication currentApplication];
|
|
||||||
[app terminate];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
|
||||||
// Insert code here to tear down your application
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "16x16",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-16.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "16x16",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-32.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "32x32",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-32.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "32x32",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-64.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "128x128",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-128.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "128x128",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-256.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-256.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-512.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-512.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "icongreen-1024.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 4.4 KiB |
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.files.user-selected.read-only</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
|
@ -1,36 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string></string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>LSApplicationCategoryType</key>
|
|
||||||
<string>public.app-category.utilities</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>Copyright © 2019 Federico Terzi. All rights reserved.</string>
|
|
||||||
<key>NSMainNibFile</key>
|
|
||||||
<string>MainMenu</string>
|
|
||||||
<key>LSUIElement</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
int main(int argc, const char * argv[]) {
|
|
||||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
|
||||||
NSApplication * application = [NSApplication sharedApplication];
|
|
||||||
[application setDelegate:delegate];
|
|
||||||
[NSApp run];
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
156
packager.py
|
@ -1,156 +0,0 @@
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import hashlib
|
|
||||||
import click
|
|
||||||
import shutil
|
|
||||||
import toml
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
PACKAGER_TARGET_DIR = "target/packager"
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PackageInfo:
|
|
||||||
name: str
|
|
||||||
version: str
|
|
||||||
description: str
|
|
||||||
publisher: str
|
|
||||||
url: str
|
|
||||||
|
|
||||||
@click.group()
|
|
||||||
def cli():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.option('--skipcargo', default=False, is_flag=True, help="Skip cargo release build")
|
|
||||||
def build(skipcargo):
|
|
||||||
"""Build espanso distribution"""
|
|
||||||
# Check operating system
|
|
||||||
TARGET_OS = "macos"
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
TARGET_OS = "windows"
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
TARGET_OS = "linux"
|
|
||||||
|
|
||||||
print("Detected OS:", TARGET_OS)
|
|
||||||
|
|
||||||
print("Loading info from Cargo.toml")
|
|
||||||
cargo_info = toml.load("Cargo.toml")
|
|
||||||
package_info = PackageInfo(cargo_info["package"]["name"],
|
|
||||||
cargo_info["package"]["version"],
|
|
||||||
cargo_info["package"]["description"],
|
|
||||||
cargo_info["package"]["authors"][0],
|
|
||||||
cargo_info["package"]["homepage"])
|
|
||||||
print(package_info)
|
|
||||||
|
|
||||||
if not skipcargo:
|
|
||||||
print("Building release version...")
|
|
||||||
subprocess.run(["cargo", "build", "--release"])
|
|
||||||
else:
|
|
||||||
print("Skipping build")
|
|
||||||
|
|
||||||
if TARGET_OS == "windows":
|
|
||||||
build_windows(package_info)
|
|
||||||
elif TARGET_OS == "macos":
|
|
||||||
build_mac(package_info)
|
|
||||||
|
|
||||||
|
|
||||||
def build_windows(package_info):
|
|
||||||
print("Starting packaging process for Windows...")
|
|
||||||
|
|
||||||
# Check Inno Setup
|
|
||||||
try:
|
|
||||||
subprocess.run(["iscc"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise Exception("Could not find Inno Setup compiler. Please install it from here: http://www.jrsoftware.org/isdl.php")
|
|
||||||
|
|
||||||
print("Clearing target dirs")
|
|
||||||
|
|
||||||
# Clearing previous build directory
|
|
||||||
if os.path.isdir(PACKAGER_TARGET_DIR):
|
|
||||||
print("Cleaning packager temp directory...")
|
|
||||||
shutil.rmtree(PACKAGER_TARGET_DIR)
|
|
||||||
|
|
||||||
TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "win")
|
|
||||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
INSTALLER_NAME = f"espanso-win-installer"
|
|
||||||
|
|
||||||
# Inno setup
|
|
||||||
shutil.copy("packager/win/modpath.iss", os.path.join(TARGET_DIR, "modpath.iss"))
|
|
||||||
|
|
||||||
print("Processing inno setup template")
|
|
||||||
with open("packager/win/setupscript.iss", "r") as iss_script:
|
|
||||||
content = iss_script.read()
|
|
||||||
|
|
||||||
# Replace variables
|
|
||||||
content = content.replace("{{{app_name}}}", package_info.name)
|
|
||||||
content = content.replace("{{{app_version}}}", package_info.version)
|
|
||||||
content = content.replace("{{{app_publisher}}}", package_info.publisher)
|
|
||||||
content = content.replace("{{{app_url}}}", package_info.url)
|
|
||||||
content = content.replace("{{{app_license}}}", os.path.abspath("LICENSE"))
|
|
||||||
content = content.replace("{{{app_icon}}}", os.path.abspath("packager/win/icon.ico"))
|
|
||||||
content = content.replace("{{{executable_path}}}", os.path.abspath("target/release/espanso.exe"))
|
|
||||||
content = content.replace("{{{output_dir}}}", os.path.abspath(TARGET_DIR))
|
|
||||||
content = content.replace("{{{output_name}}}", INSTALLER_NAME)
|
|
||||||
|
|
||||||
with open(os.path.join(TARGET_DIR, "setupscript.iss"), "w") as output_script:
|
|
||||||
output_script.write(content)
|
|
||||||
|
|
||||||
print("Compiling installer with Inno setup")
|
|
||||||
subprocess.run(["iscc", os.path.abspath(os.path.join(TARGET_DIR, "setupscript.iss"))])
|
|
||||||
|
|
||||||
|
|
||||||
def build_mac(package_info):
|
|
||||||
print("Starting packaging process for MacOS...")
|
|
||||||
|
|
||||||
print("Clearing target dirs")
|
|
||||||
|
|
||||||
# Clearing previous build directory
|
|
||||||
if os.path.isdir(PACKAGER_TARGET_DIR):
|
|
||||||
print("Cleaning packager temp directory...")
|
|
||||||
shutil.rmtree(PACKAGER_TARGET_DIR)
|
|
||||||
|
|
||||||
TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "mac")
|
|
||||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
print("Compressing release to archive...")
|
|
||||||
target_name = f"espanso-mac.tar.gz"
|
|
||||||
archive_target = os.path.abspath(os.path.join(TARGET_DIR, target_name))
|
|
||||||
subprocess.run(["tar",
|
|
||||||
"-C", os.path.abspath("target/release"),
|
|
||||||
"-cvf",
|
|
||||||
archive_target,
|
|
||||||
"espanso",
|
|
||||||
])
|
|
||||||
print(f"Created archive: {archive_target}")
|
|
||||||
|
|
||||||
print("Processing Homebrew formula template")
|
|
||||||
with open("packager/mac/espanso.rb", "r") as formula_template:
|
|
||||||
content = formula_template.read()
|
|
||||||
|
|
||||||
# Replace variables
|
|
||||||
content = content.replace("{{{app_desc}}}", package_info.description)
|
|
||||||
content = content.replace("{{{app_url}}}", package_info.url)
|
|
||||||
content = content.replace("{{{app_version}}}", package_info.version)
|
|
||||||
|
|
||||||
# Calculate hash
|
|
||||||
with open(archive_target, "rb") as f:
|
|
||||||
bytes = f.read()
|
|
||||||
readable_hash = hashlib.sha256(bytes).hexdigest()
|
|
||||||
content = content.replace("{{{release_hash}}}", readable_hash)
|
|
||||||
|
|
||||||
with open(os.path.join(TARGET_DIR, "espanso.rb"), "w") as output_script:
|
|
||||||
output_script.write(content)
|
|
||||||
|
|
||||||
print("Done!")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("[[ espanso packager ]]")
|
|
||||||
|
|
||||||
# Check python version 3
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
raise Exception("Must be using Python 3")
|
|
||||||
|
|
||||||
cli()
|
|
|
@ -1,14 +0,0 @@
|
||||||
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
|
||||||
# https://rubydoc.brew.sh/Formula
|
|
||||||
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
|
|
||||||
class Espanso < Formula
|
|
||||||
desc "{{{app_desc}}}"
|
|
||||||
homepage "{{{app_url}}}"
|
|
||||||
url "https://github.com/federico-terzi/espanso/releases/latest/download/espanso-mac.tar.gz"
|
|
||||||
sha256 "{{{release_hash}}}"
|
|
||||||
version "{{{app_version}}}"
|
|
||||||
|
|
||||||
def install
|
|
||||||
bin.install "espanso"
|
|
||||||
end
|
|
||||||
end
|
|
Before Width: | Height: | Size: 28 KiB |
|
@ -1,219 +0,0 @@
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Inno Setup Ver: 5.4.2
|
|
||||||
// Script Version: 1.4.2
|
|
||||||
// Author: Jared Breland <jbreland@legroom.net>
|
|
||||||
// Homepage: http://www.legroom.net/software
|
|
||||||
// License: GNU Lesser General Public License (LGPL), version 3
|
|
||||||
// http://www.gnu.org/licenses/lgpl.html
|
|
||||||
//
|
|
||||||
// Script Function:
|
|
||||||
// Allow modification of environmental path directly from Inno Setup installers
|
|
||||||
//
|
|
||||||
// Instructions:
|
|
||||||
// Copy modpath.iss to the same directory as your setup script
|
|
||||||
//
|
|
||||||
// Add this statement to your [Setup] section
|
|
||||||
// ChangesEnvironment=true
|
|
||||||
//
|
|
||||||
// Add this statement to your [Tasks] section
|
|
||||||
// You can change the Description or Flags
|
|
||||||
// You can change the Name, but it must match the ModPathName setting below
|
|
||||||
// Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked
|
|
||||||
//
|
|
||||||
// Add the following to the end of your [Code] section
|
|
||||||
// ModPathName defines the name of the task defined above
|
|
||||||
// ModPathType defines whether the 'user' or 'system' path will be modified;
|
|
||||||
// this will default to user if anything other than system is set
|
|
||||||
// setArrayLength must specify the total number of dirs to be added
|
|
||||||
// Result[0] contains first directory, Result[1] contains second, etc.
|
|
||||||
// const
|
|
||||||
// ModPathName = 'modifypath';
|
|
||||||
// ModPathType = 'user';
|
|
||||||
//
|
|
||||||
// function ModPathDir(): TArrayOfString;
|
|
||||||
// begin
|
|
||||||
// setArrayLength(Result, 1);
|
|
||||||
// Result[0] := ExpandConstant('{app}');
|
|
||||||
// end;
|
|
||||||
// #include "modpath.iss"
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
procedure ModPath();
|
|
||||||
var
|
|
||||||
oldpath: String;
|
|
||||||
newpath: String;
|
|
||||||
updatepath: Boolean;
|
|
||||||
pathArr: TArrayOfString;
|
|
||||||
aExecFile: String;
|
|
||||||
aExecArr: TArrayOfString;
|
|
||||||
i, d: Integer;
|
|
||||||
pathdir: TArrayOfString;
|
|
||||||
regroot: Integer;
|
|
||||||
regpath: String;
|
|
||||||
|
|
||||||
begin
|
|
||||||
// Get constants from main script and adjust behavior accordingly
|
|
||||||
// ModPathType MUST be 'system' or 'user'; force 'user' if invalid
|
|
||||||
if ModPathType = 'system' then begin
|
|
||||||
regroot := HKEY_LOCAL_MACHINE;
|
|
||||||
regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
|
|
||||||
end else begin
|
|
||||||
regroot := HKEY_CURRENT_USER;
|
|
||||||
regpath := 'Environment';
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Get array of new directories and act on each individually
|
|
||||||
pathdir := ModPathDir();
|
|
||||||
for d := 0 to GetArrayLength(pathdir)-1 do begin
|
|
||||||
updatepath := true;
|
|
||||||
|
|
||||||
// Modify WinNT path
|
|
||||||
if UsingWinNT() = true then begin
|
|
||||||
|
|
||||||
// Get current path, split into an array
|
|
||||||
RegQueryStringValue(regroot, regpath, 'Path', oldpath);
|
|
||||||
oldpath := oldpath + ';';
|
|
||||||
i := 0;
|
|
||||||
|
|
||||||
while (Pos(';', oldpath) > 0) do begin
|
|
||||||
SetArrayLength(pathArr, i+1);
|
|
||||||
pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1);
|
|
||||||
oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath));
|
|
||||||
i := i + 1;
|
|
||||||
|
|
||||||
// Check if current directory matches app dir
|
|
||||||
if pathdir[d] = pathArr[i-1] then begin
|
|
||||||
// if uninstalling, remove dir from path
|
|
||||||
if IsUninstaller() = true then begin
|
|
||||||
continue;
|
|
||||||
// if installing, flag that dir already exists in path
|
|
||||||
end else begin
|
|
||||||
updatepath := false;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Add current directory to new path
|
|
||||||
if i = 1 then begin
|
|
||||||
newpath := pathArr[i-1];
|
|
||||||
end else begin
|
|
||||||
newpath := newpath + ';' + pathArr[i-1];
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Append app dir to path if not already included
|
|
||||||
if (IsUninstaller() = false) AND (updatepath = true) then
|
|
||||||
newpath := newpath + ';' + pathdir[d];
|
|
||||||
|
|
||||||
// Write new path
|
|
||||||
RegWriteStringValue(regroot, regpath, 'Path', newpath);
|
|
||||||
|
|
||||||
// Modify Win9x path
|
|
||||||
end else begin
|
|
||||||
|
|
||||||
// Convert to shortened dirname
|
|
||||||
pathdir[d] := GetShortName(pathdir[d]);
|
|
||||||
|
|
||||||
// If autoexec.bat exists, check if app dir already exists in path
|
|
||||||
aExecFile := 'C:\AUTOEXEC.BAT';
|
|
||||||
if FileExists(aExecFile) then begin
|
|
||||||
LoadStringsFromFile(aExecFile, aExecArr);
|
|
||||||
for i := 0 to GetArrayLength(aExecArr)-1 do begin
|
|
||||||
if IsUninstaller() = false then begin
|
|
||||||
// If app dir already exists while installing, skip add
|
|
||||||
if (Pos(pathdir[d], aExecArr[i]) > 0) then
|
|
||||||
updatepath := false;
|
|
||||||
break;
|
|
||||||
end else begin
|
|
||||||
// If app dir exists and = what we originally set, then delete at uninstall
|
|
||||||
if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then
|
|
||||||
aExecArr[i] := '';
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path
|
|
||||||
if (IsUninstaller() = false) AND (updatepath = true) then begin
|
|
||||||
SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True);
|
|
||||||
|
|
||||||
// If uninstalling, write the full autoexec out
|
|
||||||
end else begin
|
|
||||||
SaveStringsToFile(aExecFile, aExecArr, False);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Split a string into an array using passed delimeter
|
|
||||||
procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String);
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
i := 0;
|
|
||||||
repeat
|
|
||||||
SetArrayLength(Dest, i+1);
|
|
||||||
if Pos(Separator,Text) > 0 then begin
|
|
||||||
Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1);
|
|
||||||
Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text));
|
|
||||||
i := i + 1;
|
|
||||||
end else begin
|
|
||||||
Dest[i] := Text;
|
|
||||||
Text := '';
|
|
||||||
end;
|
|
||||||
until Length(Text)=0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
|
||||||
var
|
|
||||||
taskname: String;
|
|
||||||
begin
|
|
||||||
taskname := ModPathName;
|
|
||||||
if CurStep = ssPostInstall then
|
|
||||||
if IsTaskSelected(taskname) then
|
|
||||||
ModPath();
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
|
||||||
var
|
|
||||||
aSelectedTasks: TArrayOfString;
|
|
||||||
i: Integer;
|
|
||||||
taskname: String;
|
|
||||||
regpath: String;
|
|
||||||
regstring: String;
|
|
||||||
appid: String;
|
|
||||||
begin
|
|
||||||
// only run during actual uninstall
|
|
||||||
if CurUninstallStep = usUninstall then begin
|
|
||||||
// get list of selected tasks saved in registry at install time
|
|
||||||
appid := '{#emit SetupSetting("AppId")}';
|
|
||||||
if appid = '' then appid := '{#emit SetupSetting("AppName")}';
|
|
||||||
regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1');
|
|
||||||
RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring);
|
|
||||||
if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring);
|
|
||||||
|
|
||||||
// check each task; if matches modpath taskname, trigger patch removal
|
|
||||||
if regstring <> '' then begin
|
|
||||||
taskname := ModPathName;
|
|
||||||
MPExplode(aSelectedTasks, regstring, ',');
|
|
||||||
if GetArrayLength(aSelectedTasks) > 0 then begin
|
|
||||||
for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin
|
|
||||||
if comparetext(aSelectedTasks[i], taskname) = 0 then
|
|
||||||
ModPath();
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function NeedRestart(): Boolean;
|
|
||||||
var
|
|
||||||
taskname: String;
|
|
||||||
begin
|
|
||||||
taskname := ModPathName;
|
|
||||||
if IsTaskSelected(taskname) and not UsingWinNT() then begin
|
|
||||||
Result := True;
|
|
||||||
end else begin
|
|
||||||
Result := False;
|
|
||||||
end;
|
|
||||||
end;
|
|
|
@ -1,66 +0,0 @@
|
||||||
; Script generated by the Inno Setup Script Wizard.
|
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
|
||||||
|
|
||||||
#define MyAppName "{{{app_name}}}"
|
|
||||||
#define MyAppVersion "{{{app_version}}}"
|
|
||||||
#define MyAppPublisher "{{{app_publisher}}}"
|
|
||||||
#define MyAppURL "{{{app_url}}}"
|
|
||||||
#define MyAppExeName "espanso.exe"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
|
||||||
AppId={{0E3D83CE-A644-4E0E-8487-657C7ECF6BF9}
|
|
||||||
AppName={#MyAppName}
|
|
||||||
AppVersion={#MyAppVersion}
|
|
||||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
|
||||||
AppPublisher={#MyAppPublisher}
|
|
||||||
AppPublisherURL={#MyAppURL}
|
|
||||||
AppSupportURL={#MyAppURL}
|
|
||||||
AppUpdatesURL={#MyAppURL}
|
|
||||||
DefaultDirName={autopf}\{#MyAppName}
|
|
||||||
DisableProgramGroupPage=yes
|
|
||||||
LicenseFile="{{{app_license}}}"
|
|
||||||
; Remove the following line to run in administrative install mode (install for all users.)
|
|
||||||
PrivilegesRequired=lowest
|
|
||||||
OutputDir="{{{output_dir}}}"
|
|
||||||
OutputBaseFilename={{{output_name}}}
|
|
||||||
SetupIconFile="{{{app_icon}}}"
|
|
||||||
Compression=lzma
|
|
||||||
SolidCompression=yes
|
|
||||||
WizardStyle=modern
|
|
||||||
ChangesEnvironment=yes
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
Source: "{{{executable_path}}}"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
Source: "{{{app_icon}}}"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\icon.ico"
|
|
||||||
Name: "{userstartup}\espanso"; Filename: "{app}\espanso.exe"; Tasks:StartMenuEntry;
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: modifypath; Description: Add espanso to PATH ( recommended );
|
|
||||||
Name: "StartMenuEntry" ; Description: "Start espanso at Windows startup" ;
|
|
||||||
|
|
||||||
[Code]
|
|
||||||
const
|
|
||||||
ModPathName = 'modifypath';
|
|
||||||
ModPathType = 'user';
|
|
||||||
|
|
||||||
function ModPathDir(): TArrayOfString;
|
|
||||||
begin
|
|
||||||
setArrayLength(Result, 1)
|
|
||||||
Result[0] := ExpandConstant('{app}');
|
|
||||||
end;
|
|
||||||
#include "modpath.iss"
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
||||||
|
|
||||||
[UninstallRun]
|
|
||||||
Filename: "{cmd}"; Parameters: "/C ""taskkill /im espanso.exe /f /t"
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::{c_void, c_char};
|
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
|
||||||
#[link(name="linuxbridge", kind="static")]
|
|
||||||
extern {
|
|
||||||
pub fn initialize(s: *const c_void) -> i32;
|
|
||||||
pub fn eventloop();
|
|
||||||
pub fn cleanup();
|
|
||||||
|
|
||||||
// System
|
|
||||||
pub fn get_active_window_name(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
pub fn get_active_window_class(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
pub fn get_active_window_executable(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
pub fn is_current_window_terminal() -> i32;
|
|
||||||
|
|
||||||
// Keyboard
|
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
|
|
||||||
i32, i32, i32));
|
|
||||||
|
|
||||||
pub fn send_string(string: *const c_char);
|
|
||||||
pub fn delete_string(count: i32);
|
|
||||||
pub fn trigger_paste();
|
|
||||||
pub fn trigger_terminal_paste();
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::{c_void, c_char};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct MacMenuItem {
|
|
||||||
pub item_id: i32,
|
|
||||||
pub item_type: i32,
|
|
||||||
pub item_name: [c_char; 100],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
|
||||||
#[link(name="macbridge", kind="static")]
|
|
||||||
extern {
|
|
||||||
pub fn initialize(s: *const c_void, icon_path: *const c_char);
|
|
||||||
pub fn eventloop();
|
|
||||||
|
|
||||||
// System
|
|
||||||
pub fn check_accessibility() -> i32;
|
|
||||||
pub fn prompt_accessibility() -> i32;
|
|
||||||
pub fn open_settings_panel();
|
|
||||||
pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
|
|
||||||
// Clipboard
|
|
||||||
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
|
|
||||||
pub fn set_clipboard(text: *const c_char) -> i32;
|
|
||||||
|
|
||||||
// UI
|
|
||||||
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
|
||||||
pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32;
|
|
||||||
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32));
|
|
||||||
|
|
||||||
// Keyboard
|
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
|
|
||||||
i32, i32, i32));
|
|
||||||
|
|
||||||
pub fn send_string(string: *const c_char);
|
|
||||||
pub fn send_vkey(vk: i32);
|
|
||||||
pub fn delete_string(count: i32);
|
|
||||||
pub fn trigger_paste();
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub(crate) mod windows;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub(crate) mod linux;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub(crate) mod macos;
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::{c_void};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct WindowsMenuItem {
|
|
||||||
pub item_id: i32,
|
|
||||||
pub item_type: i32,
|
|
||||||
pub item_name: [u16; 100],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
|
||||||
#[link(name="winbridge", kind="static")]
|
|
||||||
extern {
|
|
||||||
pub fn start_daemon_process() -> i32;
|
|
||||||
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32;
|
|
||||||
|
|
||||||
// SYSTEM
|
|
||||||
pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32;
|
|
||||||
pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32;
|
|
||||||
|
|
||||||
// UI
|
|
||||||
pub fn show_notification(message: *const u16) -> i32;
|
|
||||||
pub fn close_notification();
|
|
||||||
pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32;
|
|
||||||
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
|
||||||
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32));
|
|
||||||
pub fn cleanup_ui();
|
|
||||||
|
|
||||||
// CLIPBOARD
|
|
||||||
pub fn get_clipboard(buffer: *mut u16, size: i32) -> i32;
|
|
||||||
pub fn set_clipboard(payload: *const u16) -> i32;
|
|
||||||
|
|
||||||
// KEYBOARD
|
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32,
|
|
||||||
i32, i32, i32, i32));
|
|
||||||
|
|
||||||
pub fn eventloop();
|
|
||||||
pub fn send_string(string: *const u16);
|
|
||||||
pub fn send_vkey(vk: i32);
|
|
||||||
pub fn delete_string(count: i32);
|
|
||||||
pub fn trigger_paste();
|
|
||||||
}
|
|
60
src/check.rs
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This functions are used to check if the required dependencies are satisfied
|
|
||||||
// before starting espanso
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn check_dependencies() -> bool {
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
// Make sure notify-send is installed
|
|
||||||
let status = Command::new("notify-send")
|
|
||||||
.arg("-v")
|
|
||||||
.output();
|
|
||||||
if let Err(_) = status {
|
|
||||||
println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it.");
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure xclip is installed
|
|
||||||
let status = Command::new("xclip")
|
|
||||||
.arg("-version")
|
|
||||||
.output();
|
|
||||||
if let Err(_) = status {
|
|
||||||
println!("Error: 'xclip' command is needed for espanso to work correctly, please install it.");
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn check_dependencies() -> bool {
|
|
||||||
// Nothing to do here
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn check_dependencies() -> bool {
|
|
||||||
// Nothing needed on windows
|
|
||||||
true
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::process::{Command, Stdio};
|
|
||||||
use std::io::{Write};
|
|
||||||
use log::error;
|
|
||||||
|
|
||||||
pub struct LinuxClipboardManager {}
|
|
||||||
|
|
||||||
impl super::ClipboardManager for LinuxClipboardManager {
|
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
|
||||||
let res = Command::new("xclip")
|
|
||||||
.args(&["-o", "-sel", "clip"])
|
|
||||||
.output();
|
|
||||||
|
|
||||||
if let Ok(output) = res {
|
|
||||||
if output.status.success() {
|
|
||||||
let s = String::from_utf8_lossy(&output.stdout);
|
|
||||||
return Some((*s).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_clipboard(&self, payload: &str) {
|
|
||||||
let res = Command::new("xclip")
|
|
||||||
.args(&["-sel", "clip"])
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
if let Ok(mut child) = res {
|
|
||||||
let stdin = child.stdin.as_mut();
|
|
||||||
|
|
||||||
if let Some(output) = stdin {
|
|
||||||
let res = output.write_all(payload.as_bytes());
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
error!("Could not set clipboard: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinuxClipboardManager {
|
|
||||||
pub fn new() -> LinuxClipboardManager {
|
|
||||||
LinuxClipboardManager{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::c_char;
|
|
||||||
use crate::bridge::macos::{get_clipboard, set_clipboard};
|
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
|
|
||||||
pub struct MacClipboardManager {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::ClipboardManager for MacClipboardManager {
|
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 2000] = [0; 2000];
|
|
||||||
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_clipboard(&self, payload: &str) {
|
|
||||||
let res = CString::new(payload);
|
|
||||||
if let Ok(cstr) = res {
|
|
||||||
unsafe {
|
|
||||||
set_clipboard(cstr.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MacClipboardManager {
|
|
||||||
pub fn new() -> MacClipboardManager {
|
|
||||||
MacClipboardManager{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
mod linux;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod macos;
|
|
||||||
|
|
||||||
pub trait ClipboardManager {
|
|
||||||
fn get_clipboard(&self) -> Option<String>;
|
|
||||||
fn set_clipboard(&self, payload: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn get_manager() -> impl ClipboardManager {
|
|
||||||
linux::LinuxClipboardManager::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_manager() -> impl ClipboardManager {
|
|
||||||
windows::WindowsClipboardManager::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn get_manager() -> impl ClipboardManager {
|
|
||||||
macos::MacClipboardManager::new()
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 widestring::U16CString;
|
|
||||||
use crate::bridge::windows::{set_clipboard, get_clipboard};
|
|
||||||
|
|
||||||
pub struct WindowsClipboardManager {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsClipboardManager {
|
|
||||||
pub fn new() -> WindowsClipboardManager {
|
|
||||||
WindowsClipboardManager{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::ClipboardManager for WindowsClipboardManager {
|
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [u16; 2000] = [0; 2000];
|
|
||||||
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = U16CString::from_ptr_str(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_string_lossy();
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_clipboard(&self, payload: &str) {
|
|
||||||
unsafe {
|
|
||||||
let payload_c = U16CString::from_str(payload).unwrap();
|
|
||||||
set_clipboard(payload_c.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,699 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
extern crate dirs;
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{fs};
|
|
||||||
use crate::matcher::Match;
|
|
||||||
use std::fs::{File, create_dir_all};
|
|
||||||
use std::io::Read;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use crate::event::KeyModifier;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use log::{error};
|
|
||||||
use std::fmt;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub(crate) mod runtime;
|
|
||||||
|
|
||||||
// TODO: add documentation link
|
|
||||||
const DEFAULT_CONFIG_FILE_CONTENT : &str = include_str!("../res/config.yaml");
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG_FILE_NAME : &str = "default.yaml";
|
|
||||||
|
|
||||||
// Default values for primitives
|
|
||||||
fn default_name() -> String{ "default".to_owned() }
|
|
||||||
fn default_filter_title() -> String{ "".to_owned() }
|
|
||||||
fn default_filter_class() -> String{ "".to_owned() }
|
|
||||||
fn default_filter_exec() -> String{ "".to_owned() }
|
|
||||||
fn default_disabled() -> bool{ false }
|
|
||||||
fn default_log_level() -> i32 { 0 }
|
|
||||||
fn default_ipc_server_port() -> i32 { 34982 }
|
|
||||||
fn default_use_system_agent() -> bool { true }
|
|
||||||
fn default_config_caching_interval() -> i32 { 800 }
|
|
||||||
fn default_toggle_interval() -> u32 { 230 }
|
|
||||||
fn default_backspace_limit() -> i32 { 3 }
|
|
||||||
fn default_exclude_parent_matches() -> bool {false}
|
|
||||||
fn default_matches() -> Vec<Match> { Vec::new() }
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Configs {
|
|
||||||
#[serde(default = "default_name")]
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
#[serde(default = "default_filter_title")]
|
|
||||||
pub filter_title: String,
|
|
||||||
|
|
||||||
#[serde(default = "default_filter_class")]
|
|
||||||
pub filter_class: String,
|
|
||||||
|
|
||||||
#[serde(default = "default_filter_exec")]
|
|
||||||
pub filter_exec: String,
|
|
||||||
|
|
||||||
#[serde(default = "default_disabled")]
|
|
||||||
pub disabled: bool,
|
|
||||||
|
|
||||||
#[serde(default = "default_log_level")]
|
|
||||||
pub log_level: i32,
|
|
||||||
|
|
||||||
#[serde(default = "default_ipc_server_port")]
|
|
||||||
pub ipc_server_port: i32,
|
|
||||||
|
|
||||||
#[serde(default = "default_use_system_agent")]
|
|
||||||
pub use_system_agent: bool,
|
|
||||||
|
|
||||||
#[serde(default = "default_config_caching_interval")]
|
|
||||||
pub config_caching_interval: i32,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub toggle_key: KeyModifier,
|
|
||||||
|
|
||||||
#[serde(default = "default_toggle_interval")]
|
|
||||||
pub toggle_interval: u32,
|
|
||||||
|
|
||||||
#[serde(default = "default_backspace_limit")]
|
|
||||||
pub backspace_limit: i32,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub backend: BackendType,
|
|
||||||
|
|
||||||
#[serde(default = "default_exclude_parent_matches")]
|
|
||||||
pub exclude_parent_matches: bool,
|
|
||||||
|
|
||||||
#[serde(default = "default_matches")]
|
|
||||||
pub matches: Vec<Match>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Macro used to validate config fields
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! validate_field {
|
|
||||||
($result:expr, $field:expr, $def_value:expr) => {
|
|
||||||
if $field != $def_value {
|
|
||||||
let mut field_name = stringify!($field);
|
|
||||||
if field_name.starts_with("self.") {
|
|
||||||
field_name = &field_name[5..]; // Remove the 'self.' prefix
|
|
||||||
}
|
|
||||||
error!("Validation error, parameter '{}' is reserved and can be only used in the default.yaml config file", field_name);
|
|
||||||
$result = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Configs {
|
|
||||||
/*
|
|
||||||
* Validate the Config instance.
|
|
||||||
* It makes sure that app-specific config instances do not define
|
|
||||||
* attributes reserved to the default config.
|
|
||||||
*/
|
|
||||||
fn validate_specific_config(&self) -> bool {
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
validate_field!(result, self.config_caching_interval, default_config_caching_interval());
|
|
||||||
validate_field!(result, self.log_level, default_log_level());
|
|
||||||
validate_field!(result, self.toggle_key, KeyModifier::default());
|
|
||||||
validate_field!(result, self.toggle_interval, default_toggle_interval());
|
|
||||||
validate_field!(result, self.backspace_limit, default_backspace_limit());
|
|
||||||
validate_field!(result, self.ipc_server_port, default_ipc_server_port());
|
|
||||||
validate_field!(result, self.use_system_agent, default_use_system_agent());
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum BackendType {
|
|
||||||
Inject,
|
|
||||||
Clipboard
|
|
||||||
}
|
|
||||||
impl Default for BackendType {
|
|
||||||
fn default() -> Self {
|
|
||||||
BackendType::Inject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Configs {
|
|
||||||
fn load_config(path: &Path) -> Result<Configs, ConfigLoadError> {
|
|
||||||
let file_res = File::open(path);
|
|
||||||
if let Ok(mut file) = file_res {
|
|
||||||
let mut contents = String::new();
|
|
||||||
let res = file.read_to_string(&mut contents);
|
|
||||||
|
|
||||||
if let Err(_) = res {
|
|
||||||
return Err(ConfigLoadError::UnableToReadFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_res = serde_yaml::from_str(&contents);
|
|
||||||
|
|
||||||
match config_res {
|
|
||||||
Ok(config) => Ok(config),
|
|
||||||
Err(e) => {
|
|
||||||
Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
Err(ConfigLoadError::FileNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ConfigSet {
|
|
||||||
pub default: Configs,
|
|
||||||
pub specific: Vec<Configs>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigSet {
|
|
||||||
pub fn load(dir_path: &Path) -> Result<ConfigSet, ConfigLoadError> {
|
|
||||||
if !dir_path.is_dir() {
|
|
||||||
return Err(ConfigLoadError::InvalidConfigDirectory)
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_file = dir_path.join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
let default = Configs::load_config(default_file.as_path())?;
|
|
||||||
|
|
||||||
let mut specific = Vec::new();
|
|
||||||
|
|
||||||
// Used to make sure no duplicates are present
|
|
||||||
let mut name_set = HashSet::new();
|
|
||||||
|
|
||||||
let dir_entry = fs::read_dir(dir_path);
|
|
||||||
if dir_entry.is_err() {
|
|
||||||
return Err(ConfigLoadError::UnableToReadFile)
|
|
||||||
}
|
|
||||||
let dir_entry = dir_entry.unwrap();
|
|
||||||
|
|
||||||
for entry in dir_entry {
|
|
||||||
let entry = entry;
|
|
||||||
if let Ok(entry) = entry {
|
|
||||||
let path = entry.path();
|
|
||||||
|
|
||||||
// Skip the default one, already loaded
|
|
||||||
if path.file_name().unwrap_or("".as_ref()) == "default.yaml" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip non-yaml config files
|
|
||||||
if path.extension().unwrap_or_default().to_str().unwrap_or_default() != "yaml" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut config = Configs::load_config(path.as_path())?;
|
|
||||||
|
|
||||||
if !config.validate_specific_config() {
|
|
||||||
return Err(ConfigLoadError::InvalidParameter(path.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.name == "default" {
|
|
||||||
return Err(ConfigLoadError::MissingName(path.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if name_set.contains(&config.name) {
|
|
||||||
return Err(ConfigLoadError::NameDuplicate(path.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute new match set, merging the parent's matches.
|
|
||||||
// Note: if an app-specific redefines a trigger already present in the
|
|
||||||
// default config, the latter gets overwritten.
|
|
||||||
if !config.exclude_parent_matches {
|
|
||||||
let mut merged_matches = config.matches.clone();
|
|
||||||
let mut trigger_set = HashSet::new();
|
|
||||||
merged_matches.iter().for_each(|m| {
|
|
||||||
trigger_set.insert(m.trigger.clone());
|
|
||||||
});
|
|
||||||
let parent_matches : Vec<Match> = default.matches.iter().filter(|&m| {
|
|
||||||
!trigger_set.contains(&m.trigger)
|
|
||||||
}).map(|m| m.clone()).collect();
|
|
||||||
|
|
||||||
merged_matches.extend(parent_matches);
|
|
||||||
config.matches = merged_matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if it contains at least a filter, and warn the user about the problem
|
|
||||||
|
|
||||||
name_set.insert(config.name.clone());
|
|
||||||
specific.push(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ConfigSet {
|
|
||||||
default,
|
|
||||||
specific
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_default() -> Result<ConfigSet, ConfigLoadError> {
|
|
||||||
let res = dirs::home_dir();
|
|
||||||
if let Some(home_dir) = res {
|
|
||||||
let espanso_dir = home_dir.join(".espanso");
|
|
||||||
|
|
||||||
// Create the espanso dir if id doesn't exist
|
|
||||||
let res = create_dir_all(espanso_dir.as_path());
|
|
||||||
|
|
||||||
if let Ok(_) = res {
|
|
||||||
let default_file = espanso_dir.join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
|
|
||||||
// If config file does not exist, create one from template
|
|
||||||
if !default_file.exists() {
|
|
||||||
let result = fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT);
|
|
||||||
if result.is_err() {
|
|
||||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConfigSet::load(espanso_dir.as_path())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ConfigManager<'a> {
|
|
||||||
fn active_config(&'a self) -> &'a Configs;
|
|
||||||
fn default_config(&'a self) -> &'a Configs;
|
|
||||||
fn matches(&'a self) -> &'a Vec<Match>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error handling
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum ConfigLoadError {
|
|
||||||
FileNotFound,
|
|
||||||
UnableToReadFile,
|
|
||||||
InvalidYAML(PathBuf, String),
|
|
||||||
InvalidConfigDirectory,
|
|
||||||
InvalidParameter(PathBuf),
|
|
||||||
MissingName(PathBuf),
|
|
||||||
NameDuplicate(PathBuf),
|
|
||||||
UnableToCreateDefaultConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ConfigLoadError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ConfigLoadError::FileNotFound => write!(f, "File not found"),
|
|
||||||
ConfigLoadError::UnableToReadFile => write!(f, "Unable to read config file"),
|
|
||||||
ConfigLoadError::InvalidYAML(path, e) => write!(f, "Error parsing YAML file '{}', invalid syntax: {}", path.to_str().unwrap_or_default(), e),
|
|
||||||
ConfigLoadError::InvalidConfigDirectory => write!(f, "Invalid config directory"),
|
|
||||||
ConfigLoadError::InvalidParameter(path) => write!(f, "Invalid parameter in '{}', use of reserved parameters in app-specific configs is not permitted", path.to_str().unwrap_or_default()),
|
|
||||||
ConfigLoadError::MissingName(path) => write!(f, "The 'name' field is required in app-specific configurations, but it's missing in '{}'", path.to_str().unwrap_or_default()),
|
|
||||||
ConfigLoadError::NameDuplicate(path) => write!(f, "Found duplicate 'name' in '{}', please use different names", path.to_str().unwrap_or_default()),
|
|
||||||
ConfigLoadError::UnableToCreateDefaultConfig => write!(f, "Could not generate default config file"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ConfigLoadError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
ConfigLoadError::FileNotFound => "File not found",
|
|
||||||
ConfigLoadError::UnableToReadFile => "Unable to read config file",
|
|
||||||
ConfigLoadError::InvalidYAML(_, _) => "Error parsing YAML file, invalid syntax",
|
|
||||||
ConfigLoadError::InvalidConfigDirectory => "Invalid config directory",
|
|
||||||
ConfigLoadError::InvalidParameter(_) => "Invalid parameter, use of reserved parameters in app-specific configs is not permitted",
|
|
||||||
ConfigLoadError::MissingName(_) => "The 'name' field is required in app-specific configurations, but it's missing",
|
|
||||||
ConfigLoadError::NameDuplicate(_) => "Found duplicate 'name' in some configurations, please use different names",
|
|
||||||
ConfigLoadError::UnableToCreateDefaultConfig => "Could not generate default config file",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::io::Write;
|
|
||||||
use tempfile::{NamedTempFile, TempDir};
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.yaml");
|
|
||||||
const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yaml");
|
|
||||||
|
|
||||||
// Test Configs
|
|
||||||
|
|
||||||
fn create_tmp_file(string: &str) -> NamedTempFile {
|
|
||||||
let file = NamedTempFile::new().unwrap();
|
|
||||||
file.as_file().write_all(string.as_bytes());
|
|
||||||
file
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variant_eq<T>(a: &T, b: &T) -> bool {
|
|
||||||
std::mem::discriminant(a) == std::mem::discriminant(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_file_not_found() {
|
|
||||||
let config = Configs::load_config(Path::new("invalid/path"));
|
|
||||||
assert_eq!(config.is_err(), true);
|
|
||||||
assert_eq!(config.unwrap_err(), ConfigLoadError::FileNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_file_with_bad_yaml_syntax() {
|
|
||||||
let broken_config_file = create_tmp_file(TEST_CONFIG_FILE_WITH_BAD_YAML);
|
|
||||||
let config = Configs::load_config(broken_config_file.path());
|
|
||||||
match config {
|
|
||||||
Ok(_) => {assert!(false)},
|
|
||||||
Err(e) => {
|
|
||||||
match e {
|
|
||||||
ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, broken_config_file.path().to_owned()),
|
|
||||||
_ => assert!(false),
|
|
||||||
}
|
|
||||||
assert!(true);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_validate_field_macro() {
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
validate_field!(result, 3, 3);
|
|
||||||
assert_eq!(result, true);
|
|
||||||
|
|
||||||
validate_field!(result, 10, 3);
|
|
||||||
assert_eq!(result, false);
|
|
||||||
|
|
||||||
validate_field!(result, 3, 3);
|
|
||||||
assert_eq!(result, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_does_not_have_reserved_fields() {
|
|
||||||
let working_config_file = create_tmp_file(r###"
|
|
||||||
|
|
||||||
backend: Clipboard
|
|
||||||
|
|
||||||
"###);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.unwrap().validate_specific_config(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_has_reserved_fields_config_caching_interval() {
|
|
||||||
let working_config_file = create_tmp_file(r###"
|
|
||||||
|
|
||||||
# This should not happen in an app-specific config
|
|
||||||
config_caching_interval: 100
|
|
||||||
|
|
||||||
"###);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.unwrap().validate_specific_config(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_has_reserved_fields_toggle_key() {
|
|
||||||
let working_config_file = create_tmp_file(r###"
|
|
||||||
|
|
||||||
# This should not happen in an app-specific config
|
|
||||||
toggle_key: CTRL
|
|
||||||
|
|
||||||
"###);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.unwrap().validate_specific_config(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_has_reserved_fields_toggle_interval() {
|
|
||||||
let working_config_file = create_tmp_file(r###"
|
|
||||||
|
|
||||||
# This should not happen in an app-specific config
|
|
||||||
toggle_interval: 1000
|
|
||||||
|
|
||||||
"###);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.unwrap().validate_specific_config(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_has_reserved_fields_backspace_limit() {
|
|
||||||
let working_config_file = create_tmp_file(r###"
|
|
||||||
|
|
||||||
# This should not happen in an app-specific config
|
|
||||||
backspace_limit: 10
|
|
||||||
|
|
||||||
"###);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.unwrap().validate_specific_config(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_loaded_correctly() {
|
|
||||||
let working_config_file = create_tmp_file(TEST_WORKING_CONFIG_FILE);
|
|
||||||
let config = Configs::load_config(working_config_file.path());
|
|
||||||
assert_eq!(config.is_ok(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ConfigSet
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_default_content_should_work_correctly() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_load_fail_bad_directory() {
|
|
||||||
let config_set = ConfigSet::load(Path::new("invalid/path"));
|
|
||||||
assert_eq!(config_set.is_err(), true);
|
|
||||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidConfigDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_missing_default_file() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert_eq!(config_set.is_err(), true);
|
|
||||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::FileNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_invalid_yaml_syntax() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
let default_path_copy = default_path.clone();
|
|
||||||
fs::write(default_path, TEST_CONFIG_FILE_WITH_BAD_YAML);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
match config_set {
|
|
||||||
Ok(_) => {assert!(false)},
|
|
||||||
Err(e) => {
|
|
||||||
match e {
|
|
||||||
ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path_copy),
|
|
||||||
_ => assert!(false),
|
|
||||||
}
|
|
||||||
assert!(true);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_specific_file_with_reserved_fields() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.yaml");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
config_caching_interval: 10000
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_err());
|
|
||||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidParameter(specific_path_copy))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_specific_file_missing_name() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.yaml");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
backend: Clipboard
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_err());
|
|
||||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::MissingName(specific_path_copy))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_temp_espanso_directory() -> TempDir {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
|
||||||
|
|
||||||
tmp_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_temp_file_in_dir(tmp_dir: &TempDir, name: &str, content: &str) -> PathBuf {
|
|
||||||
let specific_path = tmp_dir.path().join(name);
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, content);
|
|
||||||
|
|
||||||
specific_path_copy
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_set_specific_file_duplicate_name() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: specific1
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path2 = create_temp_file_in_dir(&tmp_dir, "specific2.yaml", r###"
|
|
||||||
name: specific1
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_err());
|
|
||||||
assert!(variant_eq(&config_set.unwrap_err(), &ConfigLoadError::NameDuplicate(PathBuf::new())))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_set_merge_with_parent_matches() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, r###"
|
|
||||||
matches:
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "LOL"
|
|
||||||
- trigger: ":yess"
|
|
||||||
replace: "Bob"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.yaml");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
name: specific1
|
|
||||||
|
|
||||||
matches:
|
|
||||||
- trigger: "hello"
|
|
||||||
replace: "newstring"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
|
||||||
assert_eq!(config_set.default.matches.len(), 2);
|
|
||||||
assert_eq!(config_set.specific[0].matches.len(), 3);
|
|
||||||
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == "hello").is_some());
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":lol").is_some());
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":yess").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_set_merge_with_parent_matches_child_priority() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, r###"
|
|
||||||
matches:
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "LOL"
|
|
||||||
- trigger: ":yess"
|
|
||||||
replace: "Bob"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.yaml");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
name: specific1
|
|
||||||
|
|
||||||
matches:
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "newstring"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
|
||||||
assert_eq!(config_set.default.matches.len(), 2);
|
|
||||||
assert_eq!(config_set.specific[0].matches.len(), 2);
|
|
||||||
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":lol" && x.replace == "newstring").is_some());
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":yess").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_specific_config_set_exclude_merge_with_parent_matches() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, r###"
|
|
||||||
matches:
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "LOL"
|
|
||||||
- trigger: ":yess"
|
|
||||||
replace: "Bob"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.yaml");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
name: specific1
|
|
||||||
|
|
||||||
exclude_parent_matches: true
|
|
||||||
|
|
||||||
matches:
|
|
||||||
- trigger: "hello"
|
|
||||||
replace: "newstring"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
|
||||||
assert_eq!(config_set.default.matches.len(), 2);
|
|
||||||
assert_eq!(config_set.specific[0].matches.len(), 1);
|
|
||||||
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == "hello" && x.replace == "newstring").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_only_yaml_files_are_loaded_from_config() {
|
|
||||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
|
||||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
|
||||||
fs::write(default_path, r###"
|
|
||||||
matches:
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "LOL"
|
|
||||||
- trigger: ":yess"
|
|
||||||
replace: "Bob"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path = tmp_dir.path().join("specific.zzz");
|
|
||||||
let specific_path_copy = specific_path.clone();
|
|
||||||
fs::write(specific_path, r###"
|
|
||||||
name: specific1
|
|
||||||
|
|
||||||
exclude_parent_matches: true
|
|
||||||
|
|
||||||
matches:
|
|
||||||
- trigger: "hello"
|
|
||||||
replace: "newstring"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
|
||||||
assert_eq!(config_set.specific.len(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,471 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 regex::Regex;
|
|
||||||
use crate::system::SystemManager;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use log::{debug, warn};
|
|
||||||
use super::{Configs, ConfigSet};
|
|
||||||
use crate::matcher::Match;
|
|
||||||
|
|
||||||
pub struct RuntimeConfigManager<'a, S: SystemManager> {
|
|
||||||
set: ConfigSet,
|
|
||||||
|
|
||||||
// Filter regexps
|
|
||||||
title_regexps: Vec<Option<Regex>>,
|
|
||||||
class_regexps: Vec<Option<Regex>>,
|
|
||||||
exec_regexps: Vec<Option<Regex>>,
|
|
||||||
|
|
||||||
system_manager: S,
|
|
||||||
|
|
||||||
// Cache
|
|
||||||
last_config_update: RefCell<SystemTime>,
|
|
||||||
last_config: RefCell<Option<&'a Configs>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
|
||||||
pub fn new<'b>(set: ConfigSet, system_manager: S) -> RuntimeConfigManager<'b, S> {
|
|
||||||
// Compile all the regexps
|
|
||||||
let title_regexps = set.specific.iter().map(
|
|
||||||
|config| {
|
|
||||||
if config.filter_title.is_empty() {
|
|
||||||
None
|
|
||||||
}else{
|
|
||||||
let res = Regex::new(&config.filter_title);
|
|
||||||
if let Ok(regex) = res {
|
|
||||||
Some(regex)
|
|
||||||
}else{
|
|
||||||
warn!("Invalid regex in 'filter_title' field of configuration {}, ignoring it...", config.name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).collect();
|
|
||||||
|
|
||||||
let class_regexps = set.specific.iter().map(
|
|
||||||
|config| {
|
|
||||||
if config.filter_class.is_empty() {
|
|
||||||
None
|
|
||||||
}else{
|
|
||||||
let res = Regex::new(&config.filter_class);
|
|
||||||
if let Ok(regex) = res {
|
|
||||||
Some(regex)
|
|
||||||
}else{
|
|
||||||
warn!("Invalid regex in 'filter_class' field of configuration {}, ignoring it...", config.name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).collect();
|
|
||||||
|
|
||||||
let exec_regexps = set.specific.iter().map(
|
|
||||||
|config| {
|
|
||||||
if config.filter_exec.is_empty() {
|
|
||||||
None
|
|
||||||
}else{
|
|
||||||
let res = Regex::new(&config.filter_exec);
|
|
||||||
if let Ok(regex) = res {
|
|
||||||
Some(regex)
|
|
||||||
}else{
|
|
||||||
warn!("Invalid regex in 'filter_exec' field of configuration {}, ignoring it...", config.name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).collect();
|
|
||||||
|
|
||||||
let last_config_update = RefCell::new(SystemTime::now());
|
|
||||||
let last_config = RefCell::new(None);
|
|
||||||
|
|
||||||
RuntimeConfigManager {
|
|
||||||
set,
|
|
||||||
title_regexps,
|
|
||||||
class_regexps,
|
|
||||||
exec_regexps,
|
|
||||||
system_manager,
|
|
||||||
last_config_update,
|
|
||||||
last_config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_active_config(&'a self) -> &'a Configs {
|
|
||||||
// TODO: optimize performance by avoiding some of these checks if no Configs use the filters
|
|
||||||
|
|
||||||
debug!("Requested config for window:");
|
|
||||||
|
|
||||||
let active_title = self.system_manager.get_current_window_title();
|
|
||||||
|
|
||||||
if let Some(title) = active_title {
|
|
||||||
debug!("=> Title: '{}'", title);
|
|
||||||
|
|
||||||
for (i, regex) in self.title_regexps.iter().enumerate() {
|
|
||||||
if let Some(regex) = regex {
|
|
||||||
if regex.is_match(&title) {
|
|
||||||
debug!("Matched 'filter_title' for '{}' config, using custom settings.",
|
|
||||||
self.set.specific[i].name);
|
|
||||||
|
|
||||||
return &self.set.specific[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let active_executable = self.system_manager.get_current_window_executable();
|
|
||||||
|
|
||||||
if let Some(executable) = active_executable {
|
|
||||||
debug!("=> Executable: '{}'", executable);
|
|
||||||
|
|
||||||
for (i, regex) in self.exec_regexps.iter().enumerate() {
|
|
||||||
if let Some(regex) = regex {
|
|
||||||
if regex.is_match(&executable) {
|
|
||||||
debug!("Matched 'filter_exec' for '{}' config, using custom settings.",
|
|
||||||
self.set.specific[i].name);
|
|
||||||
|
|
||||||
return &self.set.specific[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let active_class = self.system_manager.get_current_window_class();
|
|
||||||
|
|
||||||
if let Some(class) = active_class {
|
|
||||||
debug!("=> Class: '{}'", class);
|
|
||||||
|
|
||||||
for (i, regex) in self.class_regexps.iter().enumerate() {
|
|
||||||
if let Some(regex) = regex {
|
|
||||||
if regex.is_match(&class) {
|
|
||||||
debug!("Matched 'filter_class' for '{}' config, using custom settings.",
|
|
||||||
self.set.specific[i].name);
|
|
||||||
|
|
||||||
return &self.set.specific[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No matches, return the default mapping
|
|
||||||
debug!("No matches for custom configs, using default settings.");
|
|
||||||
&self.set.default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a, S> {
|
|
||||||
fn active_config(&'a self) -> &'a Configs {
|
|
||||||
let mut last_config_update = self.last_config_update.borrow_mut();
|
|
||||||
if let Ok(elapsed) = (*last_config_update).elapsed() {
|
|
||||||
*last_config_update = SystemTime::now();
|
|
||||||
|
|
||||||
if elapsed.as_millis() < self.set.default.config_caching_interval as u128 {
|
|
||||||
let last_config = self.last_config.borrow();
|
|
||||||
if let Some(cached_config) = *last_config {
|
|
||||||
debug!("Using cached config");
|
|
||||||
return cached_config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = self.calculate_active_config();
|
|
||||||
|
|
||||||
let mut last_config = self.last_config.borrow_mut();
|
|
||||||
*last_config = Some(config);
|
|
||||||
|
|
||||||
config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_config(&'a self) -> &'a Configs {
|
|
||||||
&self.set.default
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&'a self) -> &'a Vec<Match> {
|
|
||||||
&self.active_config().matches
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TESTS
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::io::Write;
|
|
||||||
use tempfile::{NamedTempFile, TempDir};
|
|
||||||
use crate::config::{DEFAULT_CONFIG_FILE_NAME, DEFAULT_CONFIG_FILE_CONTENT};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use crate::config::ConfigManager;
|
|
||||||
use crate::config::tests::{create_temp_espanso_directory, create_temp_file_in_dir};
|
|
||||||
|
|
||||||
struct DummySystemManager {
|
|
||||||
title: RefCell<String>,
|
|
||||||
class: RefCell<String>,
|
|
||||||
exec: RefCell<String>,
|
|
||||||
}
|
|
||||||
impl SystemManager for DummySystemManager {
|
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
|
||||||
Some(self.title.borrow().clone())
|
|
||||||
}
|
|
||||||
fn get_current_window_class(&self) -> Option<String> {
|
|
||||||
Some(self.class.borrow().clone())
|
|
||||||
}
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
|
||||||
Some(self.exec.borrow().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DummySystemManager {
|
|
||||||
pub fn new_custom(title: &str, class: &str, exec: &str) -> DummySystemManager {
|
|
||||||
DummySystemManager{
|
|
||||||
title: RefCell::new(title.to_owned()),
|
|
||||||
class: RefCell::new(class.to_owned()),
|
|
||||||
exec: RefCell::new(exec.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new() -> DummySystemManager {
|
|
||||||
DummySystemManager::new_custom("title", "class", "exec")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change(&self, title: &str, class: &str, exec: &str) {
|
|
||||||
*self.title.borrow_mut() = title.to_owned();
|
|
||||||
*self.class.borrow_mut() = class.to_owned();
|
|
||||||
*self.exec.borrow_mut() = exec.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runtime_constructor_regex_load_correctly() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: myname1
|
|
||||||
filter_exec: "Title"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path2 = create_temp_file_in_dir(&tmp_dir, "specific2.yaml", r###"
|
|
||||||
name: myname2
|
|
||||||
filter_title: "Yeah"
|
|
||||||
filter_class: "Car"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path3 = create_temp_file_in_dir(&tmp_dir, "specific3.yaml", r###"
|
|
||||||
name: myname3
|
|
||||||
filter_title: "Nice"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new();
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
let sp1index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname1").unwrap();
|
|
||||||
let sp2index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname2").unwrap();
|
|
||||||
let sp3index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname3").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(config_manager.exec_regexps.len(), 3);
|
|
||||||
assert_eq!(config_manager.title_regexps.len(), 3);
|
|
||||||
assert_eq!(config_manager.class_regexps.len(), 3);
|
|
||||||
|
|
||||||
assert!(config_manager.class_regexps[sp1index].is_none());
|
|
||||||
assert!(config_manager.class_regexps[sp2index].is_some());
|
|
||||||
assert!(config_manager.class_regexps[sp3index].is_none());
|
|
||||||
|
|
||||||
assert!(config_manager.title_regexps[sp1index].is_none());
|
|
||||||
assert!(config_manager.title_regexps[sp2index].is_some());
|
|
||||||
assert!(config_manager.title_regexps[sp3index].is_some());
|
|
||||||
|
|
||||||
assert!(config_manager.exec_regexps[sp1index].is_some());
|
|
||||||
assert!(config_manager.exec_regexps[sp2index].is_none());
|
|
||||||
assert!(config_manager.exec_regexps[sp3index].is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runtime_constructor_malformed_regexes_are_ignored() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: myname1
|
|
||||||
filter_exec: "[`-_]"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path2 = create_temp_file_in_dir(&tmp_dir, "specific2.yaml", r###"
|
|
||||||
name: myname2
|
|
||||||
filter_title: "[`-_]"
|
|
||||||
filter_class: "Car"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let specific_path3 = create_temp_file_in_dir(&tmp_dir, "specific3.yaml", r###"
|
|
||||||
name: myname3
|
|
||||||
filter_title: "Nice"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new();
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
let sp1index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname1").unwrap();
|
|
||||||
let sp2index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname2").unwrap();
|
|
||||||
let sp3index = config_manager.set.specific
|
|
||||||
.iter().position(|x| x.name == "myname3").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(config_manager.exec_regexps.len(), 3);
|
|
||||||
assert_eq!(config_manager.title_regexps.len(), 3);
|
|
||||||
assert_eq!(config_manager.class_regexps.len(), 3);
|
|
||||||
|
|
||||||
assert!(config_manager.class_regexps[sp1index].is_none());
|
|
||||||
assert!(config_manager.class_regexps[sp2index].is_some());
|
|
||||||
assert!(config_manager.class_regexps[sp3index].is_none());
|
|
||||||
|
|
||||||
assert!(config_manager.title_regexps[sp1index].is_none());
|
|
||||||
assert!(config_manager.title_regexps[sp2index].is_none());
|
|
||||||
assert!(config_manager.title_regexps[sp3index].is_some());
|
|
||||||
|
|
||||||
assert!(config_manager.exec_regexps[sp1index].is_none());
|
|
||||||
assert!(config_manager.exec_regexps[sp2index].is_none());
|
|
||||||
assert!(config_manager.exec_regexps[sp3index].is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runtime_calculate_active_config_specific_title_match() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: chrome
|
|
||||||
filter_title: "Chrome"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "chrome");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_runtime_calculate_active_config_specific_class_match() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: chrome
|
|
||||||
filter_class: "Chrome"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "chrome");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_runtime_calculate_active_config_specific_exec_match() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: chrome
|
|
||||||
filter_exec: "chrome.exe"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "chrome");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_runtime_calculate_active_config_specific_multi_filter_match() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: chrome
|
|
||||||
filter_class: Browser
|
|
||||||
filter_exec: "firefox.exe"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "chrome");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runtime_calculate_active_config_no_match() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: firefox
|
|
||||||
filter_title: "Firefox"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "default");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runtime_active_config_cache() {
|
|
||||||
let tmp_dir = create_temp_espanso_directory();
|
|
||||||
|
|
||||||
let specific_path = create_temp_file_in_dir(&tmp_dir, "specific.yaml", r###"
|
|
||||||
name: firefox
|
|
||||||
filter_title: "Firefox"
|
|
||||||
"###);
|
|
||||||
|
|
||||||
let config_set = ConfigSet::load(tmp_dir.path());
|
|
||||||
assert!(config_set.is_ok());
|
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
|
||||||
|
|
||||||
assert_eq!(config_manager.active_config().name, "default");
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "default");
|
|
||||||
|
|
||||||
config_manager.system_manager.change("Firefox", "Browser", "C\\Path\\firefox.exe");
|
|
||||||
|
|
||||||
// Active config should have changed, but not cached one
|
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "firefox");
|
|
||||||
assert_eq!(config_manager.active_config().name, "default");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::sync::mpsc::Sender;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use crate::event::*;
|
|
||||||
use crate::event::KeyModifier::*;
|
|
||||||
use crate::bridge::linux::*;
|
|
||||||
use std::process::exit;
|
|
||||||
use log::error;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct LinuxContext {
|
|
||||||
pub send_channel: Sender<Event>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinuxContext {
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> {
|
|
||||||
let context = Box::new(LinuxContext {
|
|
||||||
send_channel,
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let context_ptr = &*context as *const LinuxContext as *const c_void;
|
|
||||||
|
|
||||||
register_keypress_callback(keypress_callback);
|
|
||||||
|
|
||||||
let res = initialize(context_ptr);
|
|
||||||
if res <= 0 {
|
|
||||||
error!("Could not initialize linux context, error: {}", res);
|
|
||||||
exit(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Context for LinuxContext {
|
|
||||||
fn eventloop(&self) {
|
|
||||||
unsafe {
|
|
||||||
eventloop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for LinuxContext {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { cleanup(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native bridge code
|
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
|
||||||
is_modifier: i32, key_code: i32) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut LinuxContext;
|
|
||||||
|
|
||||||
if is_modifier == 0 { // Char event
|
|
||||||
// Convert the received buffer to a character
|
|
||||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
|
||||||
let r = String::from_utf8_lossy(buffer).chars().nth(0);
|
|
||||||
|
|
||||||
// Send the char through the channel
|
|
||||||
if let Some(c) = r {
|
|
||||||
let event = Event::Key(KeyEvent::Char(c));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}else{ // Modifier event
|
|
||||||
let modifier: Option<KeyModifier> = match key_code {
|
|
||||||
133 => Some(META),
|
|
||||||
50 => Some(SHIFT),
|
|
||||||
64 => Some(ALT),
|
|
||||||
37 => Some(CTRL),
|
|
||||||
22 => Some(BACKSPACE),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(modifier) = modifier {
|
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::sync::mpsc::Sender;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use crate::bridge::macos::*;
|
|
||||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
|
||||||
use crate::event::KeyModifier::*;
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::fs;
|
|
||||||
use log::{info, error};
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
const STATUS_ICON_BINARY : &'static [u8] = include_bytes!("../res/mac/icon.png");
|
|
||||||
|
|
||||||
pub struct MacContext {
|
|
||||||
pub send_channel: Sender<Event>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MacContext {
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<MacContext> {
|
|
||||||
// Check accessibility
|
|
||||||
unsafe {
|
|
||||||
let res = prompt_accessibility();
|
|
||||||
|
|
||||||
if res == 0 {
|
|
||||||
error!("Accessibility must be enabled to make espanso work on MacOS.");
|
|
||||||
error!("Please allow espanso in the Security & Privacy panel, then restart espanso.");
|
|
||||||
error!("For more information: "); // TODO: add documentation link
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = Box::new(MacContext {
|
|
||||||
send_channel
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize the status icon path
|
|
||||||
let espanso_dir = super::get_data_dir();
|
|
||||||
let status_icon_target = espanso_dir.join("icon.png");
|
|
||||||
|
|
||||||
if status_icon_target.exists() {
|
|
||||||
info!("Status icon already initialized, skipping.");
|
|
||||||
}else {
|
|
||||||
fs::write(&status_icon_target, STATUS_ICON_BINARY).unwrap_or_else(|e| {
|
|
||||||
error!("Error copying the Status Icon to the espanso data directory: {}", e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let context_ptr = &*context as *const MacContext as *const c_void;
|
|
||||||
|
|
||||||
register_keypress_callback(keypress_callback);
|
|
||||||
register_icon_click_callback(icon_click_callback);
|
|
||||||
register_context_menu_click_callback(context_menu_click_callback);
|
|
||||||
|
|
||||||
let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
|
|
||||||
initialize(context_ptr, status_icon_path.as_ptr());
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Context for MacContext {
|
|
||||||
fn eventloop(&self) {
|
|
||||||
unsafe {
|
|
||||||
eventloop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native bridge code
|
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
|
||||||
is_modifier: i32, key_code: i32) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut MacContext;
|
|
||||||
|
|
||||||
if is_modifier == 0 { // Char event
|
|
||||||
// Convert the received buffer to a character
|
|
||||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
|
||||||
let r = String::from_utf8_lossy(buffer).chars().nth(0);
|
|
||||||
|
|
||||||
// Send the char through the channel
|
|
||||||
if let Some(c) = r {
|
|
||||||
let event = Event::Key(KeyEvent::Char(c));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}else{ // Modifier event
|
|
||||||
let modifier: Option<KeyModifier> = match key_code {
|
|
||||||
0x37 => Some(META),
|
|
||||||
0x38 => Some(SHIFT),
|
|
||||||
0x3A => Some(ALT),
|
|
||||||
0x3B => Some(CTRL),
|
|
||||||
0x33 => Some(BACKSPACE),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(modifier) = modifier {
|
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fn icon_click_callback(_self: *mut c_void) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut MacContext;
|
|
||||||
|
|
||||||
let event = Event::Action(ActionType::IconClick);
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut MacContext;
|
|
||||||
|
|
||||||
let event = Event::Action(ActionType::from(id));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
mod linux;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub(crate) mod macos;
|
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use crate::event::Event;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::create_dir_all;
|
|
||||||
|
|
||||||
pub trait Context {
|
|
||||||
fn eventloop(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_data_dir() -> PathBuf {
|
|
||||||
let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating.");
|
|
||||||
let espanso_dir = data_dir.join("espanso");
|
|
||||||
create_dir_all(&espanso_dir).expect("Error creating espanso data directory");
|
|
||||||
espanso_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
|
||||||
macos::MacContext::new(send_channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
|
||||||
linux::LinuxContext::new(send_channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
|
||||||
windows::WindowsContext::new(send_channel)
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::sync::mpsc::Sender;
|
|
||||||
use crate::bridge::windows::*;
|
|
||||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
|
||||||
use crate::event::KeyModifier::*;
|
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::{fs};
|
|
||||||
use widestring::U16CString;
|
|
||||||
use log::{info};
|
|
||||||
|
|
||||||
const BMP_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp");
|
|
||||||
const ICO_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.ico");
|
|
||||||
|
|
||||||
pub struct WindowsContext {
|
|
||||||
send_channel: Sender<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsContext {
|
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<WindowsContext> {
|
|
||||||
// Initialize image resources
|
|
||||||
|
|
||||||
let espanso_dir = super::get_data_dir();
|
|
||||||
|
|
||||||
info!("Initializing Espanso resources in {}", espanso_dir.as_path().display());
|
|
||||||
|
|
||||||
let espanso_bmp_image = espanso_dir.join("espansoicon.bmp");
|
|
||||||
if espanso_bmp_image.exists() {
|
|
||||||
info!("BMP already initialized, skipping.");
|
|
||||||
}else {
|
|
||||||
fs::write(&espanso_bmp_image, BMP_BINARY)
|
|
||||||
.expect("Unable to write windows bmp file");
|
|
||||||
|
|
||||||
info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let espanso_ico_image = espanso_dir.join("espanso.ico");
|
|
||||||
if espanso_ico_image.exists() {
|
|
||||||
info!("ICO already initialized, skipping.");
|
|
||||||
}else {
|
|
||||||
fs::write(&espanso_ico_image, ICO_BINARY)
|
|
||||||
.expect("Unable to write windows ico file");
|
|
||||||
|
|
||||||
info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default();
|
|
||||||
let ico_icon = espanso_ico_image.to_str().unwrap_or_default();
|
|
||||||
|
|
||||||
let send_channel = send_channel;
|
|
||||||
|
|
||||||
let context = Box::new(WindowsContext{
|
|
||||||
send_channel,
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let context_ptr = &*context as *const WindowsContext as *const c_void;
|
|
||||||
|
|
||||||
// Register callbacks
|
|
||||||
register_keypress_callback(keypress_callback);
|
|
||||||
register_icon_click_callback(icon_click_callback);
|
|
||||||
register_context_menu_click_callback(context_menu_click_callback);
|
|
||||||
|
|
||||||
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
|
||||||
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
|
||||||
|
|
||||||
// Initialize the windows
|
|
||||||
let res = initialize(context_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr());
|
|
||||||
if res != 1 {
|
|
||||||
panic!("Can't initialize Windows context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Context for WindowsContext {
|
|
||||||
fn eventloop(&self) {
|
|
||||||
unsafe {
|
|
||||||
eventloop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native bridge code
|
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const i32, len: i32,
|
|
||||||
is_modifier: i32, key_code: i32, is_key_down: i32) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut WindowsContext;
|
|
||||||
if is_key_down != 0 { // KEY DOWN EVENT
|
|
||||||
if is_modifier == 0 { // Char event
|
|
||||||
// Convert the received buffer to a character
|
|
||||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
|
||||||
let r = std::char::from_u32(buffer[0] as u32);
|
|
||||||
|
|
||||||
// Send the char through the channel
|
|
||||||
if let Some(c) = r {
|
|
||||||
let event = Event::Key(KeyEvent::Char(c));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{ // KEY UP event
|
|
||||||
if is_modifier != 0 { // Modifier event
|
|
||||||
let modifier: Option<KeyModifier> = match key_code {
|
|
||||||
0x5B | 0x5C => Some(META),
|
|
||||||
0x10 => Some(SHIFT),
|
|
||||||
0x12 => Some(ALT),
|
|
||||||
0x11 => Some(CTRL),
|
|
||||||
0x08 => Some(BACKSPACE),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(modifier) = modifier {
|
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fn icon_click_callback(_self: *mut c_void) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut WindowsContext;
|
|
||||||
|
|
||||||
let event = Event::Action(ActionType::IconClick);
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
|
||||||
unsafe {
|
|
||||||
let _self = _self as *mut WindowsContext;
|
|
||||||
|
|
||||||
let event = Event::Action(ActionType::from(id));
|
|
||||||
(*_self).send_channel.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
204
src/engine.rs
|
@ -1,204 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::matcher::{Match, MatchReceiver};
|
|
||||||
use crate::keyboard::KeyboardManager;
|
|
||||||
use crate::config::ConfigManager;
|
|
||||||
use crate::config::BackendType;
|
|
||||||
use crate::clipboard::ClipboardManager;
|
|
||||||
use log::{info, warn, error};
|
|
||||||
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
|
||||||
use crate::event::{ActionEventReceiver, ActionType};
|
|
||||||
use crate::extension::Extension;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::process::exit;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use regex::{Regex, Captures};
|
|
||||||
|
|
||||||
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
|
||||||
U: UIManager> {
|
|
||||||
keyboard_manager: &'a S,
|
|
||||||
clipboard_manager: &'a C,
|
|
||||||
config_manager: &'a M,
|
|
||||||
ui_manager: &'a U,
|
|
||||||
|
|
||||||
extension_map: HashMap<String, Box<dyn Extension>>,
|
|
||||||
|
|
||||||
enabled: RefCell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
|
||||||
Engine<'a, S, C, M, U> {
|
|
||||||
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
|
|
||||||
config_manager: &'a M, ui_manager: &'a U,
|
|
||||||
extensions: Vec<Box<dyn Extension>>) -> Engine<'a, S, C, M, U> {
|
|
||||||
// Register all the extensions
|
|
||||||
let mut extension_map = HashMap::new();
|
|
||||||
for extension in extensions.into_iter() {
|
|
||||||
extension_map.insert(extension.name(), extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
let enabled = RefCell::new(true);
|
|
||||||
|
|
||||||
Engine{keyboard_manager,
|
|
||||||
clipboard_manager,
|
|
||||||
config_manager,
|
|
||||||
ui_manager,
|
|
||||||
extension_map,
|
|
||||||
enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_menu(&self) -> Vec<MenuItem> {
|
|
||||||
let mut menu = Vec::new();
|
|
||||||
|
|
||||||
let enabled = self.enabled.borrow();
|
|
||||||
let toggle_text = if *enabled {
|
|
||||||
"Disable"
|
|
||||||
}else{
|
|
||||||
"Enable"
|
|
||||||
}.to_owned();
|
|
||||||
menu.push(MenuItem{
|
|
||||||
item_type: MenuItemType::Button,
|
|
||||||
item_name: toggle_text,
|
|
||||||
item_id: ActionType::Toggle as i32,
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.push(MenuItem{
|
|
||||||
item_type: MenuItemType::Separator,
|
|
||||||
item_name: "".to_owned(),
|
|
||||||
item_id: 999,
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.push(MenuItem{
|
|
||||||
item_type: MenuItemType::Button,
|
|
||||||
item_name: "Exit".to_owned(),
|
|
||||||
item_id: ActionType::Exit as i32,
|
|
||||||
});
|
|
||||||
|
|
||||||
menu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
|
||||||
MatchReceiver for Engine<'a, S, C, M, U>{
|
|
||||||
|
|
||||||
fn on_match(&self, m: &Match) {
|
|
||||||
let config = self.config_manager.active_config();
|
|
||||||
|
|
||||||
if config.disabled {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.keyboard_manager.delete_string(m.trigger.len() as i32);
|
|
||||||
|
|
||||||
let target_string = if m._has_vars {
|
|
||||||
let mut output_map = HashMap::new();
|
|
||||||
|
|
||||||
for variable in m.vars.iter() {
|
|
||||||
let extension = self.extension_map.get(&variable.var_type);
|
|
||||||
if let Some(extension) = extension {
|
|
||||||
let ext_out = extension.calculate(&variable.params);
|
|
||||||
if let Some(output) = ext_out {
|
|
||||||
output_map.insert(variable.name.clone(), output);
|
|
||||||
}else{
|
|
||||||
output_map.insert(variable.name.clone(), "".to_owned());
|
|
||||||
warn!("Could not generate output for variable: {}", variable.name);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
error!("No extension found for variable type: {}", variable.var_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the variables
|
|
||||||
let result = VAR_REGEX.replace_all(&m.replace, |caps: &Captures| {
|
|
||||||
let var_name = caps.name("name").unwrap().as_str();
|
|
||||||
let output = output_map.get(var_name);
|
|
||||||
output.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_string()
|
|
||||||
}else{ // No variables, simple text substitution
|
|
||||||
m.replace.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
match config.backend {
|
|
||||||
BackendType::Inject => {
|
|
||||||
// Send the expected string. On linux, newlines are managed automatically
|
|
||||||
// while on windows and macos, we need to emulate a Enter key press.
|
|
||||||
|
|
||||||
if cfg!(target_os = "linux") {
|
|
||||||
self.keyboard_manager.send_string(&target_string);
|
|
||||||
}else{
|
|
||||||
// To handle newlines, substitute each "\n" char with an Enter key press.
|
|
||||||
let splits = target_string.lines();
|
|
||||||
|
|
||||||
for (i, split) in splits.enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
self.keyboard_manager.send_enter();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.keyboard_manager.send_string(split);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
BackendType::Clipboard => {
|
|
||||||
self.clipboard_manager.set_clipboard(&target_string);
|
|
||||||
self.keyboard_manager.trigger_paste();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_enable_update(&self, status: bool) {
|
|
||||||
let message = if status {
|
|
||||||
"espanso enabled"
|
|
||||||
}else{
|
|
||||||
"espanso disabled"
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("Toggled: {}", message);
|
|
||||||
|
|
||||||
let mut enabled_ref = self.enabled.borrow_mut();
|
|
||||||
*enabled_ref = status;
|
|
||||||
|
|
||||||
self.ui_manager.notify(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager,
|
|
||||||
M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{
|
|
||||||
|
|
||||||
fn on_action_event(&self, e: ActionType) {
|
|
||||||
match e {
|
|
||||||
ActionType::IconClick => {
|
|
||||||
self.ui_manager.show_menu(self.build_menu());
|
|
||||||
},
|
|
||||||
ActionType::Exit => {
|
|
||||||
info!("Terminating espanso.");
|
|
||||||
self.ui_manager.cleanup();
|
|
||||||
exit(0);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::{KeyEventReceiver, ActionEventReceiver, Event};
|
|
||||||
use std::sync::mpsc::Receiver;
|
|
||||||
|
|
||||||
pub trait EventManager {
|
|
||||||
fn eventloop(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DefaultEventManager<'a> {
|
|
||||||
receive_channel: Receiver<Event>,
|
|
||||||
key_receivers: Vec<&'a dyn KeyEventReceiver>,
|
|
||||||
action_receivers: Vec<&'a dyn ActionEventReceiver>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DefaultEventManager<'a> {
|
|
||||||
pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>,
|
|
||||||
action_receivers: Vec<&'a dyn ActionEventReceiver>) -> DefaultEventManager<'a> {
|
|
||||||
DefaultEventManager {
|
|
||||||
receive_channel,
|
|
||||||
key_receivers,
|
|
||||||
action_receivers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a> EventManager for DefaultEventManager<'a> {
|
|
||||||
fn eventloop(&self) {
|
|
||||||
loop {
|
|
||||||
match self.receive_channel.recv() {
|
|
||||||
Ok(event) => {
|
|
||||||
match event {
|
|
||||||
Event::Key(key_event) => {
|
|
||||||
self.key_receivers.iter().for_each(move |&receiver| receiver.on_key_event(key_event.clone()));
|
|
||||||
},
|
|
||||||
Event::Action(action_event) => {
|
|
||||||
self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => panic!("Broken event channel"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub(crate) mod manager;
|
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
Action(ActionType),
|
|
||||||
Key(KeyEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ActionType {
|
|
||||||
Noop = 0,
|
|
||||||
Toggle = 1,
|
|
||||||
Exit = 2,
|
|
||||||
IconClick = 3,
|
|
||||||
Enable = 4,
|
|
||||||
Disable = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for ActionType {
|
|
||||||
fn from(id: i32) -> Self {
|
|
||||||
match id {
|
|
||||||
1 => ActionType::Toggle,
|
|
||||||
2 => ActionType::Exit,
|
|
||||||
3 => ActionType::IconClick,
|
|
||||||
4 => ActionType::Enable,
|
|
||||||
5 => ActionType::Disable,
|
|
||||||
_ => ActionType::Noop,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum KeyEvent {
|
|
||||||
Char(char),
|
|
||||||
Modifier(KeyModifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum KeyModifier {
|
|
||||||
CTRL,
|
|
||||||
SHIFT,
|
|
||||||
ALT,
|
|
||||||
META,
|
|
||||||
BACKSPACE,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for KeyModifier {
|
|
||||||
fn default() -> Self {
|
|
||||||
KeyModifier::ALT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receivers
|
|
||||||
|
|
||||||
pub trait KeyEventReceiver {
|
|
||||||
fn on_key_event(&self, e: KeyEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ActionEventReceiver {
|
|
||||||
fn on_action_event(&self, e: ActionType);
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde_yaml::{Mapping, Value};
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
|
|
||||||
pub struct DateExtension {}
|
|
||||||
|
|
||||||
impl DateExtension {
|
|
||||||
pub fn new() -> DateExtension {
|
|
||||||
DateExtension{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Extension for DateExtension {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
String::from("date")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate(&self, params: &Mapping) -> Option<String> {
|
|
||||||
let now: DateTime<Utc> = Utc::now();
|
|
||||||
|
|
||||||
let format = params.get(&Value::from("format"));
|
|
||||||
|
|
||||||
let date = if let Some(format) = format {
|
|
||||||
now.format(format.as_str().unwrap()).to_string()
|
|
||||||
}else{
|
|
||||||
now.to_rfc2822()
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(date)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde_yaml::Mapping;
|
|
||||||
|
|
||||||
mod date;
|
|
||||||
mod shell;
|
|
||||||
mod script;
|
|
||||||
|
|
||||||
pub trait Extension {
|
|
||||||
fn name(&self) -> String;
|
|
||||||
fn calculate(&self, params: &Mapping) -> Option<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_extensions() -> Vec<Box<dyn Extension>> {
|
|
||||||
vec![
|
|
||||||
Box::new(date::DateExtension::new()),
|
|
||||||
Box::new(shell::ShellExtension::new()),
|
|
||||||
Box::new(script::ScriptExtension::new()),
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde_yaml::{Mapping, Value};
|
|
||||||
use std::process::Command;
|
|
||||||
use log::{warn, error};
|
|
||||||
|
|
||||||
pub struct ScriptExtension {}
|
|
||||||
|
|
||||||
impl ScriptExtension {
|
|
||||||
pub fn new() -> ScriptExtension {
|
|
||||||
ScriptExtension{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Extension for ScriptExtension {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
String::from("script")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate(&self, params: &Mapping) -> Option<String> {
|
|
||||||
let args = params.get(&Value::from("args"));
|
|
||||||
if args.is_none() {
|
|
||||||
warn!("No 'args' parameter specified for script variable");
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
let args = args.unwrap().as_sequence();
|
|
||||||
if let Some(args) = args {
|
|
||||||
let str_args = args.iter().map(|arg| {
|
|
||||||
arg.as_str().unwrap_or_default().to_string()
|
|
||||||
}).collect::<Vec<String>>();
|
|
||||||
|
|
||||||
let output = if str_args.len() > 1 {
|
|
||||||
Command::new(&str_args[0])
|
|
||||||
.args(&str_args[1..])
|
|
||||||
.output()
|
|
||||||
}else{
|
|
||||||
Command::new(&str_args[0])
|
|
||||||
.output()
|
|
||||||
};
|
|
||||||
|
|
||||||
match output {
|
|
||||||
Ok(output) => {
|
|
||||||
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
|
|
||||||
|
|
||||||
return Some(output_str.into_owned())
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not execute script '{:?}', error: {}", args, e);
|
|
||||||
return None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error!("Could not execute script with args '{:?}'", args);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde_yaml::{Mapping, Value};
|
|
||||||
use std::process::Command;
|
|
||||||
use log::{warn, error};
|
|
||||||
|
|
||||||
pub struct ShellExtension {}
|
|
||||||
|
|
||||||
impl ShellExtension {
|
|
||||||
pub fn new() -> ShellExtension {
|
|
||||||
ShellExtension{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Extension for ShellExtension {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
String::from("shell")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate(&self, params: &Mapping) -> Option<String> {
|
|
||||||
let cmd = params.get(&Value::from("cmd"));
|
|
||||||
if cmd.is_none() {
|
|
||||||
warn!("No 'cmd' parameter specified for shell variable");
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
let cmd = cmd.unwrap().as_str().unwrap();
|
|
||||||
|
|
||||||
let output = if cfg!(target_os = "windows") {
|
|
||||||
Command::new("cmd")
|
|
||||||
.args(&["/C", cmd])
|
|
||||||
.output()
|
|
||||||
} else {
|
|
||||||
Command::new("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(cmd)
|
|
||||||
.output()
|
|
||||||
};
|
|
||||||
|
|
||||||
match output {
|
|
||||||
Ok(output) => {
|
|
||||||
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
|
|
||||||
|
|
||||||
Some(output_str.into_owned())
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not execute cmd '{}', error: {}", cmd, e);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::ffi::CString;
|
|
||||||
use crate::bridge::linux::*;
|
|
||||||
|
|
||||||
pub struct LinuxKeyboardManager {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for LinuxKeyboardManager {
|
|
||||||
fn send_string(&self, s: &str) {
|
|
||||||
let res = CString::new(s);
|
|
||||||
match res {
|
|
||||||
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
|
||||||
Err(e) => panic!(e.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_enter(&self) {
|
|
||||||
// On linux this is not needed, so NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trigger_paste(&self) {
|
|
||||||
unsafe {
|
|
||||||
let is_terminal = is_current_window_terminal();
|
|
||||||
|
|
||||||
// Terminals use a different keyboard combination to paste from clipboard,
|
|
||||||
// so we need to check the correct situation.
|
|
||||||
if is_terminal == 0 {
|
|
||||||
trigger_paste();
|
|
||||||
}else{
|
|
||||||
trigger_terminal_paste();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
|
||||||
unsafe {delete_string(count)}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::ffi::CString;
|
|
||||||
use crate::bridge::macos::*;
|
|
||||||
|
|
||||||
pub struct MacKeyboardManager {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for MacKeyboardManager {
|
|
||||||
fn send_string(&self, s: &str) {
|
|
||||||
let res = CString::new(s);
|
|
||||||
match res {
|
|
||||||
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
|
||||||
Err(e) => panic!(e.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_enter(&self) {
|
|
||||||
unsafe {
|
|
||||||
// Send the kVK_Return key press
|
|
||||||
send_vkey(0x24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trigger_paste(&self) {
|
|
||||||
unsafe {
|
|
||||||
trigger_paste();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
|
||||||
unsafe {delete_string(count)}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
mod linux;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod macos;
|
|
||||||
|
|
||||||
pub trait KeyboardManager {
|
|
||||||
fn send_string(&self, s: &str);
|
|
||||||
fn send_enter(&self);
|
|
||||||
fn trigger_paste(&self);
|
|
||||||
fn delete_string(&self, count: i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
|
||||||
windows::WindowsKeyboardManager{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
|
||||||
linux::LinuxKeyboardManager{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
|
||||||
macos::MacKeyboardManager{}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 widestring::{U16CString};
|
|
||||||
use crate::bridge::windows::*;
|
|
||||||
|
|
||||||
pub struct WindowsKeyboardManager {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for WindowsKeyboardManager {
|
|
||||||
fn send_string(&self, s: &str) {
|
|
||||||
let res = U16CString::from_str(s);
|
|
||||||
match res {
|
|
||||||
Ok(s) => {
|
|
||||||
unsafe {
|
|
||||||
send_string(s.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("Error while sending string: {}", e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_enter(&self) {
|
|
||||||
unsafe {
|
|
||||||
// Send the VK_RETURN key press
|
|
||||||
send_vkey(0x0D);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trigger_paste(&self) {
|
|
||||||
unsafe {
|
|
||||||
trigger_paste();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
|
||||||
unsafe {
|
|
||||||
delete_string(count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
585
src/main.rs
|
@ -1,585 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use std::thread;
|
|
||||||
use std::fs::{File, OpenOptions};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::exit;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::sync::mpsc::Receiver;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use clap::{App, Arg, SubCommand, ArgMatches};
|
|
||||||
use fs2::FileExt;
|
|
||||||
use log::{info, warn, LevelFilter};
|
|
||||||
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger};
|
|
||||||
|
|
||||||
use crate::config::ConfigSet;
|
|
||||||
use crate::config::runtime::RuntimeConfigManager;
|
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::event::*;
|
|
||||||
use crate::event::manager::{DefaultEventManager, EventManager};
|
|
||||||
use crate::matcher::scrolling::ScrollingMatcher;
|
|
||||||
use crate::system::SystemManager;
|
|
||||||
use crate::ui::UIManager;
|
|
||||||
use crate::protocol::*;
|
|
||||||
use std::io::{BufReader, BufRead};
|
|
||||||
|
|
||||||
mod ui;
|
|
||||||
mod event;
|
|
||||||
mod check;
|
|
||||||
mod bridge;
|
|
||||||
mod engine;
|
|
||||||
mod config;
|
|
||||||
mod system;
|
|
||||||
mod sysdaemon;
|
|
||||||
mod context;
|
|
||||||
mod matcher;
|
|
||||||
mod keyboard;
|
|
||||||
mod protocol;
|
|
||||||
mod clipboard;
|
|
||||||
mod extension;
|
|
||||||
|
|
||||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
|
||||||
const LOG_FILE: &str = "espanso.log";
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let matches = App::new("espanso")
|
|
||||||
.version(VERSION)
|
|
||||||
.author("Federico Terzi")
|
|
||||||
.about("Cross-platform Text Expander written in Rust")
|
|
||||||
.arg(Arg::with_name("config")
|
|
||||||
.short("c")
|
|
||||||
.long("config")
|
|
||||||
.value_name("FILE")
|
|
||||||
.help("Sets a custom config directory. If not specified, reads the default $HOME/.espanso/default.yaml file, creating it if not present.")
|
|
||||||
.takes_value(true))
|
|
||||||
.arg(Arg::with_name("v")
|
|
||||||
.short("v")
|
|
||||||
.multiple(true)
|
|
||||||
.help("Sets the level of verbosity"))
|
|
||||||
.subcommand(SubCommand::with_name("cmd")
|
|
||||||
.about("Send a command to the espanso daemon.")
|
|
||||||
.subcommand(SubCommand::with_name("exit")
|
|
||||||
.about("Terminate the daemon."))
|
|
||||||
.subcommand(SubCommand::with_name("enable")
|
|
||||||
.about("Enable the espanso replacement engine."))
|
|
||||||
.subcommand(SubCommand::with_name("disable")
|
|
||||||
.about("Disable the espanso replacement engine."))
|
|
||||||
.subcommand(SubCommand::with_name("toggle")
|
|
||||||
.about("Toggle the status of the espanso replacement engine."))
|
|
||||||
)
|
|
||||||
.subcommand(SubCommand::with_name("dump")
|
|
||||||
.about("Prints all current configuration options."))
|
|
||||||
.subcommand(SubCommand::with_name("detect")
|
|
||||||
.about("Tool to detect current window properties, to simplify filters creation."))
|
|
||||||
.subcommand(SubCommand::with_name("daemon")
|
|
||||||
.about("Start the daemon without spawning a new process."))
|
|
||||||
.subcommand(SubCommand::with_name("register")
|
|
||||||
.about("MacOS only. Register espanso in the system daemon manager."))
|
|
||||||
.subcommand(SubCommand::with_name("unregister")
|
|
||||||
.about("MacOS only. Unregister espanso from the system daemon manager."))
|
|
||||||
.subcommand(SubCommand::with_name("log")
|
|
||||||
.about("Print the latest daemon logs."))
|
|
||||||
.subcommand(SubCommand::with_name("start")
|
|
||||||
.about("Start the daemon spawning a new process in the background."))
|
|
||||||
.subcommand(SubCommand::with_name("stop")
|
|
||||||
.about("Stop the espanso daemon."))
|
|
||||||
.subcommand(SubCommand::with_name("restart")
|
|
||||||
.about("Restart the espanso daemon."))
|
|
||||||
.subcommand(SubCommand::with_name("status")
|
|
||||||
.about("Check if the espanso daemon is running or not."))
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let log_level = matches.occurrences_of("v") as i32;
|
|
||||||
|
|
||||||
// Load the configuration
|
|
||||||
let mut config_set = match matches.value_of("config") {
|
|
||||||
None => {
|
|
||||||
if log_level > 1 {
|
|
||||||
println!("loading configuration from default location...");
|
|
||||||
}
|
|
||||||
ConfigSet::load_default()
|
|
||||||
},
|
|
||||||
Some(path) => {
|
|
||||||
if log_level > 1 {
|
|
||||||
println!("loading configuration from custom location: {}", path);
|
|
||||||
}
|
|
||||||
ConfigSet::load(Path::new(path))
|
|
||||||
},
|
|
||||||
}.unwrap_or_else(|e| {
|
|
||||||
println!("{}", e);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
config_set.default.log_level = log_level;
|
|
||||||
|
|
||||||
// Match the correct subcommand
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("cmd") {
|
|
||||||
cmd_main(config_set, matches);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("dump") {
|
|
||||||
println!("{:#?}", config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("detect") {
|
|
||||||
detect_main();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("daemon") {
|
|
||||||
daemon_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("register") {
|
|
||||||
register_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("unregister") {
|
|
||||||
unregister_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("log") {
|
|
||||||
log_main();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("start") {
|
|
||||||
start_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("status") {
|
|
||||||
status_main();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("stop") {
|
|
||||||
stop_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("restart") {
|
|
||||||
restart_main(config_set);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults to start subcommand
|
|
||||||
start_main(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Daemon subcommand, start the event loop and spawn a background thread worker
|
|
||||||
fn daemon_main(config_set: ConfigSet) {
|
|
||||||
// Try to acquire lock file
|
|
||||||
let lock_file = acquire_lock();
|
|
||||||
if lock_file.is_none() {
|
|
||||||
println!("espanso is already running.");
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
precheck_guard();
|
|
||||||
|
|
||||||
// Initialize log
|
|
||||||
let log_level = match config_set.default.log_level {
|
|
||||||
0 => LevelFilter::Warn,
|
|
||||||
1 => LevelFilter::Info,
|
|
||||||
2 | _ => LevelFilter::Debug,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new();
|
|
||||||
|
|
||||||
// Initialize terminal output
|
|
||||||
let terminal_out = TermLogger::new(log_level,
|
|
||||||
simplelog::Config::default(), TerminalMode::Mixed);
|
|
||||||
if let Some(terminal_out) = terminal_out {
|
|
||||||
log_outputs.push(terminal_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize log file output
|
|
||||||
let espanso_dir = context::get_data_dir();
|
|
||||||
let log_file_path = espanso_dir.join(LOG_FILE);
|
|
||||||
let log_file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(log_file_path)
|
|
||||||
.expect("Cannot create log file.");
|
|
||||||
let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file);
|
|
||||||
log_outputs.push(file_out);
|
|
||||||
|
|
||||||
CombinedLogger::init(
|
|
||||||
log_outputs
|
|
||||||
).expect("Error opening log destination");
|
|
||||||
|
|
||||||
// Activate logging for panics
|
|
||||||
log_panics::init();
|
|
||||||
|
|
||||||
info!("espanso version {}", VERSION);
|
|
||||||
info!("starting daemon...");
|
|
||||||
|
|
||||||
let (send_channel, receive_channel) = mpsc::channel();
|
|
||||||
|
|
||||||
let context = context::new(send_channel.clone());
|
|
||||||
|
|
||||||
let config_set_copy = config_set.clone();
|
|
||||||
thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
|
|
||||||
daemon_background(receive_channel, config_set_copy);
|
|
||||||
}).expect("Unable to spawn daemon background thread");
|
|
||||||
|
|
||||||
let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone());
|
|
||||||
ipc_server.start();
|
|
||||||
|
|
||||||
context.eventloop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Background thread worker for the daemon
|
|
||||||
fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
|
|
||||||
let system_manager = system::get_manager();
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
|
||||||
|
|
||||||
let ui_manager = ui::get_uimanager();
|
|
||||||
ui_manager.notify("espanso is running!");
|
|
||||||
|
|
||||||
let clipboard_manager = clipboard::get_manager();
|
|
||||||
|
|
||||||
let keyboard_manager = keyboard::get_manager();
|
|
||||||
|
|
||||||
let extensions = extension::get_extensions();
|
|
||||||
|
|
||||||
let engine = Engine::new(&keyboard_manager,
|
|
||||||
&clipboard_manager,
|
|
||||||
&config_manager,
|
|
||||||
&ui_manager,
|
|
||||||
extensions,
|
|
||||||
);
|
|
||||||
|
|
||||||
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
|
||||||
|
|
||||||
let event_manager = DefaultEventManager::new(
|
|
||||||
receive_channel,
|
|
||||||
vec!(&matcher),
|
|
||||||
vec!(&engine, &matcher),
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("espanso is running!");
|
|
||||||
|
|
||||||
event_manager.eventloop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// start subcommand, spawn a background espanso process.
|
|
||||||
fn start_main(config_set: ConfigSet) {
|
|
||||||
// Try to acquire lock file
|
|
||||||
let lock_file = acquire_lock();
|
|
||||||
if lock_file.is_none() {
|
|
||||||
println!("espanso is already running.");
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
release_lock(lock_file.unwrap());
|
|
||||||
|
|
||||||
precheck_guard();
|
|
||||||
|
|
||||||
start_daemon(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn start_daemon(_: ConfigSet) {
|
|
||||||
unsafe {
|
|
||||||
let res = bridge::windows::start_daemon_process();
|
|
||||||
if res < 0 {
|
|
||||||
println!("Error starting daemon process");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn start_daemon(config_set: ConfigSet) {
|
|
||||||
if config_set.default.use_system_agent {
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
let res = Command::new("launchctl")
|
|
||||||
.args(&["start", "com.federicoterzi.espanso"])
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if let Ok(status) = res {
|
|
||||||
if status.success() {
|
|
||||||
println!("Daemon started correctly!")
|
|
||||||
}else{
|
|
||||||
println!("Error starting launchd daemon with status: {}", status);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
println!("Error starting launchd daemon: {}", res.unwrap_err());
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
fork_daemon(config_set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn start_daemon(config_set: ConfigSet) {
|
|
||||||
if config_set.default.use_system_agent {
|
|
||||||
// TODO: systemd
|
|
||||||
}else{
|
|
||||||
fork_daemon(config_set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn fork_daemon(config_set: ConfigSet) {
|
|
||||||
unsafe {
|
|
||||||
let pid = libc::fork();
|
|
||||||
if pid < 0 {
|
|
||||||
println!("Unable to fork.");
|
|
||||||
exit(4);
|
|
||||||
}
|
|
||||||
if pid > 0 { // Parent process exit
|
|
||||||
println!("daemon started!");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawned process
|
|
||||||
|
|
||||||
// Create a new SID for the child process
|
|
||||||
let sid = libc::setsid();
|
|
||||||
if sid < 0 {
|
|
||||||
exit(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detach stdout and stderr
|
|
||||||
let null_path = std::ffi::CString::new("/dev/null").expect("CString unwrap failed");
|
|
||||||
let fd = libc::open(null_path.as_ptr(), libc::O_RDWR, 0);
|
|
||||||
if fd != -1 {
|
|
||||||
libc::dup2(fd, libc::STDIN_FILENO);
|
|
||||||
libc::dup2(fd, libc::STDOUT_FILENO);
|
|
||||||
libc::dup2(fd, libc::STDERR_FILENO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon_main(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// status subcommand, print the current espanso status
|
|
||||||
fn status_main() {
|
|
||||||
let lock_file = acquire_lock();
|
|
||||||
if let Some(lock_file) = lock_file {
|
|
||||||
println!("espanso is not running");
|
|
||||||
|
|
||||||
release_lock(lock_file);
|
|
||||||
}else{
|
|
||||||
println!("espanso is running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Stop subcommand, used to stop the daemon.
|
|
||||||
fn stop_main(config_set: ConfigSet) {
|
|
||||||
// Try to acquire lock file
|
|
||||||
let lock_file = acquire_lock();
|
|
||||||
if lock_file.is_some() {
|
|
||||||
println!("espanso daemon is not running.");
|
|
||||||
release_lock(lock_file.unwrap());
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = send_command(config_set, IPCCommand{
|
|
||||||
id: "exit".to_owned(),
|
|
||||||
payload: "".to_owned(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
println!("{}", e);
|
|
||||||
exit(1);
|
|
||||||
}else{
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kill the daemon if running and start it again
|
|
||||||
fn restart_main(config_set: ConfigSet) {
|
|
||||||
// Kill the daemon if running
|
|
||||||
let lock_file = acquire_lock();
|
|
||||||
if lock_file.is_none() {
|
|
||||||
// Terminate the current espanso daemon
|
|
||||||
send_command(config_set.clone(), IPCCommand{
|
|
||||||
id: "exit".to_owned(),
|
|
||||||
payload: "".to_owned(),
|
|
||||||
}).unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e));
|
|
||||||
}else{
|
|
||||||
release_lock(lock_file.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(300));
|
|
||||||
|
|
||||||
// Restart the daemon
|
|
||||||
start_main(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cli tool used to analyze active windows to extract useful information
|
|
||||||
/// to create configuration filters.
|
|
||||||
fn detect_main() {
|
|
||||||
let system_manager = system::get_manager();
|
|
||||||
|
|
||||||
println!("Listening for changes, now focus the window you want to analyze.");
|
|
||||||
println!("You can terminate with CTRL+C\n");
|
|
||||||
|
|
||||||
let mut last_title : String = "".to_owned();
|
|
||||||
let mut last_class : String = "".to_owned();
|
|
||||||
let mut last_exec : String = "".to_owned();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let curr_title = system_manager.get_current_window_title().unwrap_or_default();
|
|
||||||
let curr_class = system_manager.get_current_window_class().unwrap_or_default();
|
|
||||||
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default();
|
|
||||||
|
|
||||||
// Check if a change occurred
|
|
||||||
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
|
||||||
println!("Detected change, current window has properties:");
|
|
||||||
println!("==> Title: '{}'", curr_title);
|
|
||||||
println!("==> Class: '{}'", curr_class);
|
|
||||||
println!("==> Executable: '{}'", curr_exec);
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
last_title = curr_title;
|
|
||||||
last_class = curr_class;
|
|
||||||
last_exec = curr_exec;
|
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send the given command to the espanso daemon
|
|
||||||
fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) {
|
|
||||||
let command = if let Some(_) = matches.subcommand_matches("exit") {
|
|
||||||
Some(IPCCommand {
|
|
||||||
id: String::from("exit"),
|
|
||||||
payload: String::from(""),
|
|
||||||
})
|
|
||||||
}else if let Some(_) = matches.subcommand_matches("toggle") {
|
|
||||||
Some(IPCCommand {
|
|
||||||
id: String::from("toggle"),
|
|
||||||
payload: String::from(""),
|
|
||||||
})
|
|
||||||
}else if let Some(_) = matches.subcommand_matches("enable") {
|
|
||||||
Some(IPCCommand {
|
|
||||||
id: String::from("enable"),
|
|
||||||
payload: String::from(""),
|
|
||||||
})
|
|
||||||
}else if let Some(_) = matches.subcommand_matches("disable") {
|
|
||||||
Some(IPCCommand {
|
|
||||||
id: String::from("disable"),
|
|
||||||
payload: String::from(""),
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(command) = command {
|
|
||||||
let res = send_command(config_set, command);
|
|
||||||
|
|
||||||
if res.is_ok() {
|
|
||||||
exit(0);
|
|
||||||
}else{
|
|
||||||
println!("{}", res.unwrap_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_command(config_set: ConfigSet, command: IPCCommand) -> Result<(), String> {
|
|
||||||
let ipc_client = protocol::get_ipc_client(config_set);
|
|
||||||
ipc_client.send_command(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_main() {
|
|
||||||
let espanso_dir = context::get_data_dir();
|
|
||||||
let log_file_path = espanso_dir.join(LOG_FILE);
|
|
||||||
|
|
||||||
if !log_file_path.exists() {
|
|
||||||
println!("No log file found.");
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
let log_file = File::open(log_file_path);
|
|
||||||
if let Ok(log_file) = log_file {
|
|
||||||
let reader = BufReader::new(log_file);
|
|
||||||
for line in reader.lines() {
|
|
||||||
if let Ok(line) = line {
|
|
||||||
println!("{}", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(0);
|
|
||||||
}else{
|
|
||||||
println!("Error reading log file");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_main(config_set: ConfigSet) {
|
|
||||||
sysdaemon::register(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unregister_main(config_set: ConfigSet) {
|
|
||||||
sysdaemon::unregister(config_set);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acquire_lock() -> Option<File> {
|
|
||||||
let espanso_dir = context::get_data_dir();
|
|
||||||
let lock_file_path = espanso_dir.join("espanso.lock");
|
|
||||||
let file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(lock_file_path)
|
|
||||||
.expect("Cannot create reference to lock file.");
|
|
||||||
|
|
||||||
let res = file.try_lock_exclusive();
|
|
||||||
|
|
||||||
if let Ok(_) = res {
|
|
||||||
return Some(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release_lock(lock_file: File) {
|
|
||||||
lock_file.unlock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to make sure all the required dependencies are present before starting espanso.
|
|
||||||
fn precheck_guard() {
|
|
||||||
let satisfied = check::check_dependencies();
|
|
||||||
if !satisfied {
|
|
||||||
println!();
|
|
||||||
println!("Pre-check was not successful, espanso could not be started.");
|
|
||||||
exit(5);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde::{Serialize, Deserialize, Deserializer};
|
|
||||||
use crate::event::{KeyEvent, KeyModifier};
|
|
||||||
use crate::event::KeyEventReceiver;
|
|
||||||
use serde_yaml::Mapping;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
pub(crate) mod scrolling;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
|
||||||
pub struct Match {
|
|
||||||
pub trigger: String,
|
|
||||||
pub replace: String,
|
|
||||||
pub vars: Vec<MatchVariable>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub _has_vars: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'de> serde::Deserialize<'de> for Match {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
|
|
||||||
D: Deserializer<'de> {
|
|
||||||
|
|
||||||
let auto_match = AutoMatch::deserialize(deserializer)?;
|
|
||||||
Ok(Match::from(&auto_match))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a AutoMatch> for Match{
|
|
||||||
fn from(other: &'a AutoMatch) -> Self {
|
|
||||||
lazy_static! {
|
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the match contains variables
|
|
||||||
let has_vars = VAR_REGEX.is_match(&other.replace);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
trigger: other.trigger.clone(),
|
|
||||||
replace: other.replace.clone(),
|
|
||||||
vars: other.vars.clone(),
|
|
||||||
_has_vars: has_vars,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to deserialize the Match struct before applying some custom elaboration.
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
struct AutoMatch {
|
|
||||||
pub trigger: String,
|
|
||||||
pub replace: String,
|
|
||||||
|
|
||||||
#[serde(default = "default_vars")]
|
|
||||||
pub vars: Vec<MatchVariable>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_vars() -> Vec<MatchVariable> {Vec::new()}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MatchVariable {
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub var_type: String,
|
|
||||||
|
|
||||||
pub params: Mapping,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MatchReceiver {
|
|
||||||
fn on_match(&self, m: &Match);
|
|
||||||
fn on_enable_update(&self, status: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Matcher : KeyEventReceiver {
|
|
||||||
fn handle_char(&self, c: char);
|
|
||||||
fn handle_modifier(&self, m: KeyModifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <M: Matcher> KeyEventReceiver for M {
|
|
||||||
fn on_key_event(&self, e: KeyEvent) {
|
|
||||||
match e {
|
|
||||||
KeyEvent::Char(c) => {
|
|
||||||
self.handle_char(c);
|
|
||||||
},
|
|
||||||
KeyEvent::Modifier(m) => {
|
|
||||||
self.handle_modifier(m);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TESTS
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_match_has_vars_should_be_false() {
|
|
||||||
let match_str = r###"
|
|
||||||
trigger: ":test"
|
|
||||||
replace: "There are no variables"
|
|
||||||
"###;
|
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_match_has_vars_should_be_true() {
|
|
||||||
let match_str = r###"
|
|
||||||
trigger: ":test"
|
|
||||||
replace: "There are {{one}} and {{two}} variables"
|
|
||||||
"###;
|
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_match_has_vars_with_spaces_should_be_true() {
|
|
||||||
let match_str = r###"
|
|
||||||
trigger: ":test"
|
|
||||||
replace: "There is {{ one }} variable"
|
|
||||||
"###;
|
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::matcher::{Match, MatchReceiver};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use crate::event::{KeyModifier, ActionEventReceiver, ActionType};
|
|
||||||
use crate::config::ConfigManager;
|
|
||||||
use crate::event::KeyModifier::BACKSPACE;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
|
||||||
config_manager: &'a M,
|
|
||||||
receiver: &'a R,
|
|
||||||
current_set_queue: RefCell<VecDeque<Vec<MatchEntry<'a>>>>,
|
|
||||||
toggle_press_time: RefCell<SystemTime>,
|
|
||||||
is_enabled: RefCell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MatchEntry<'a> {
|
|
||||||
start: usize,
|
|
||||||
_match: &'a Match
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
|
||||||
pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> {
|
|
||||||
let current_set_queue = RefCell::new(VecDeque::new());
|
|
||||||
let toggle_press_time = RefCell::new(SystemTime::now());
|
|
||||||
|
|
||||||
ScrollingMatcher{
|
|
||||||
config_manager,
|
|
||||||
receiver,
|
|
||||||
current_set_queue,
|
|
||||||
toggle_press_time,
|
|
||||||
is_enabled: RefCell::new(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle(&self) {
|
|
||||||
let mut is_enabled = self.is_enabled.borrow_mut();
|
|
||||||
*is_enabled = !(*is_enabled);
|
|
||||||
|
|
||||||
self.receiver.on_enable_update(*is_enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_enabled(&self, enabled: bool) {
|
|
||||||
let mut is_enabled = self.is_enabled.borrow_mut();
|
|
||||||
*is_enabled = enabled;
|
|
||||||
|
|
||||||
self.receiver.on_enable_update(*is_enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> {
|
|
||||||
fn handle_char(&self, c: char) {
|
|
||||||
// if not enabled, avoid any processing
|
|
||||||
if !*(self.is_enabled.borrow()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current_set_queue = self.current_set_queue.borrow_mut();
|
|
||||||
|
|
||||||
let new_matches: Vec<MatchEntry> = self.config_manager.matches().iter()
|
|
||||||
.filter(|&x| x.trigger.chars().nth(0).unwrap() == c)
|
|
||||||
.map(|x | MatchEntry{start: 1, _match: &x})
|
|
||||||
.collect();
|
|
||||||
// TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup.
|
|
||||||
|
|
||||||
let combined_matches: Vec<MatchEntry> = match current_set_queue.back() {
|
|
||||||
Some(last_matches) => {
|
|
||||||
let mut updated: Vec<MatchEntry> = last_matches.iter()
|
|
||||||
.filter(|&x| {
|
|
||||||
x._match.trigger[x.start..].chars().nth(0).unwrap() == c
|
|
||||||
})
|
|
||||||
.map(|x | MatchEntry{start: x.start+1, _match: &x._match})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
updated.extend(new_matches);
|
|
||||||
updated
|
|
||||||
},
|
|
||||||
None => {new_matches},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut found_match = None;
|
|
||||||
|
|
||||||
for entry in combined_matches.iter() {
|
|
||||||
if entry.start == entry._match.trigger.len() {
|
|
||||||
found_match = Some(entry._match);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_set_queue.push_back(combined_matches);
|
|
||||||
|
|
||||||
if current_set_queue.len() as i32 > (self.config_manager.default_config().backspace_limit + 1) {
|
|
||||||
current_set_queue.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_match) = found_match {
|
|
||||||
if let Some(last) = current_set_queue.back_mut() {
|
|
||||||
last.clear();
|
|
||||||
}
|
|
||||||
self.receiver.on_match(_match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_modifier(&self, m: KeyModifier) {
|
|
||||||
let config = self.config_manager.default_config();
|
|
||||||
|
|
||||||
if m == config.toggle_key {
|
|
||||||
let mut toggle_press_time = self.toggle_press_time.borrow_mut();
|
|
||||||
if let Ok(elapsed) = toggle_press_time.elapsed() {
|
|
||||||
if elapsed.as_millis() < config.toggle_interval as u128 {
|
|
||||||
self.toggle();
|
|
||||||
|
|
||||||
let is_enabled = self.is_enabled.borrow();
|
|
||||||
|
|
||||||
if !*is_enabled {
|
|
||||||
self.current_set_queue.borrow_mut().clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*toggle_press_time) = SystemTime::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backspace handling, basically "rewinding history"
|
|
||||||
if m == BACKSPACE {
|
|
||||||
let mut current_set_queue = self.current_set_queue.borrow_mut();
|
|
||||||
current_set_queue.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for ScrollingMatcher<'a, R, M> {
|
|
||||||
fn on_action_event(&self, e: ActionType) {
|
|
||||||
match e {
|
|
||||||
ActionType::Toggle => {
|
|
||||||
self.toggle();
|
|
||||||
},
|
|
||||||
ActionType::Enable => {
|
|
||||||
self.set_enabled(true);
|
|
||||||
},
|
|
||||||
ActionType::Disable => {
|
|
||||||
self.set_enabled(false);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use crate::event::Event;
|
|
||||||
use crate::event::ActionType;
|
|
||||||
use std::io::{BufReader, Read, Write};
|
|
||||||
use std::error::Error;
|
|
||||||
use log::error;
|
|
||||||
use crate::config::ConfigSet;
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
mod unix;
|
|
||||||
|
|
||||||
pub trait IPCServer {
|
|
||||||
fn start(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IPCClient {
|
|
||||||
fn send_command(&self, command: IPCCommand) -> Result<(), String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct IPCCommand {
|
|
||||||
pub id: String,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub payload: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IPCCommand {
|
|
||||||
fn to_event(&self) -> Option<Event> {
|
|
||||||
match self.id.as_ref() {
|
|
||||||
"exit" => {
|
|
||||||
Some(Event::Action(ActionType::Exit))
|
|
||||||
},
|
|
||||||
"toggle" => {
|
|
||||||
Some(Event::Action(ActionType::Toggle))
|
|
||||||
},
|
|
||||||
"enable" => {
|
|
||||||
Some(Event::Action(ActionType::Enable))
|
|
||||||
},
|
|
||||||
"disable" => {
|
|
||||||
Some(Event::Action(ActionType::Disable))
|
|
||||||
},
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
|
|
||||||
match stream {
|
|
||||||
Ok(stream) => {
|
|
||||||
let mut json_str= String::new();
|
|
||||||
let mut buf_reader = BufReader::new(stream);
|
|
||||||
let res = buf_reader.read_to_string(&mut json_str);
|
|
||||||
|
|
||||||
if res.is_ok() {
|
|
||||||
let command : Result<IPCCommand, serde_json::Error> = serde_json::from_str(&json_str);
|
|
||||||
match command {
|
|
||||||
Ok(command) => {
|
|
||||||
let event = command.to_event();
|
|
||||||
if let Some(event) = event {
|
|
||||||
event_channel.send(event).expect("Broken event channel");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!("Error deserializing JSON command: {}", e);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
println!("Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -> Result<(), String>{
|
|
||||||
match stream {
|
|
||||||
Ok(mut stream) => {
|
|
||||||
let json_str = serde_json::to_string(&command);
|
|
||||||
if let Ok(json_str) = json_str {
|
|
||||||
stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| {
|
|
||||||
println!("Can't write to IPC socket: {}", e);
|
|
||||||
});
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Can't connect to daemon: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err("Can't send command".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UNIX IMPLEMENTATION
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn get_ipc_server(_: ConfigSet, event_channel: Sender<Event>) -> impl IPCServer {
|
|
||||||
unix::UnixIPCServer::new(event_channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn get_ipc_client(_: ConfigSet) -> impl IPCClient {
|
|
||||||
unix::UnixIPCClient::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_ipc_server(config_set: ConfigSet, event_channel: Sender<Event>) -> impl IPCServer {
|
|
||||||
windows::WindowsIPCServer::new(config_set, event_channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_ipc_client(config_set: ConfigSet) -> impl IPCClient {
|
|
||||||
windows::WindowsIPCClient::new(config_set)
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::unix::net::{UnixStream,UnixListener};
|
|
||||||
use log::{info, warn};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use super::IPCCommand;
|
|
||||||
|
|
||||||
use crate::context;
|
|
||||||
use crate::event::*;
|
|
||||||
use crate::protocol::{process_event, send_command};
|
|
||||||
|
|
||||||
const UNIX_SOCKET_NAME : &str = "espanso.sock";
|
|
||||||
|
|
||||||
pub struct UnixIPCServer {
|
|
||||||
event_channel: Sender<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnixIPCServer {
|
|
||||||
pub fn new(event_channel: Sender<Event>) -> UnixIPCServer {
|
|
||||||
UnixIPCServer {event_channel}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::IPCServer for UnixIPCServer {
|
|
||||||
fn start(&self) {
|
|
||||||
let event_channel = self.event_channel.clone();
|
|
||||||
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || {
|
|
||||||
let espanso_dir = context::get_data_dir();
|
|
||||||
let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME);
|
|
||||||
|
|
||||||
std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| {
|
|
||||||
warn!("Unable to delete Unix socket: {}", e);
|
|
||||||
});
|
|
||||||
let listener = UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket");
|
|
||||||
|
|
||||||
info!("Binded to IPC unix socket: {}", unix_socket.as_path().display());
|
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
|
||||||
process_event(&event_channel, stream);
|
|
||||||
}
|
|
||||||
}).expect("Unable to spawn IPC server thread");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UnixIPCClient {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnixIPCClient {
|
|
||||||
pub fn new() -> UnixIPCClient {
|
|
||||||
UnixIPCClient{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::IPCClient for UnixIPCClient {
|
|
||||||
fn send_command(&self, command: IPCCommand) -> Result<(), String> {
|
|
||||||
let espanso_dir = context::get_data_dir();
|
|
||||||
let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME);
|
|
||||||
|
|
||||||
// Open the stream
|
|
||||||
let stream = UnixStream::connect(unix_socket);
|
|
||||||
|
|
||||||
send_command(command, stream)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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 log::{info};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use std::net::{TcpListener, TcpStream};
|
|
||||||
use super::IPCCommand;
|
|
||||||
|
|
||||||
use crate::event::*;
|
|
||||||
use crate::protocol::{process_event, send_command};
|
|
||||||
use crate::config::ConfigSet;
|
|
||||||
|
|
||||||
pub struct WindowsIPCServer {
|
|
||||||
config_set: ConfigSet,
|
|
||||||
event_channel: Sender<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsIPCServer {
|
|
||||||
pub fn new(config_set: ConfigSet, event_channel: Sender<Event>) -> WindowsIPCServer {
|
|
||||||
WindowsIPCServer {config_set, event_channel}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::IPCServer for WindowsIPCServer {
|
|
||||||
fn start(&self) {
|
|
||||||
let event_channel = self.event_channel.clone();
|
|
||||||
let server_port = self.config_set.default.ipc_server_port;
|
|
||||||
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || {
|
|
||||||
let listener = TcpListener::bind(
|
|
||||||
format!("127.0.0.1:{}", server_port)
|
|
||||||
).expect("Error binding to IPC server port");
|
|
||||||
|
|
||||||
info!("Binded to IPC tcp socket: {}", listener.local_addr().unwrap().to_string());
|
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
|
||||||
process_event(&event_channel, stream);
|
|
||||||
}
|
|
||||||
}).expect("Unable to spawn IPC server thread");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WindowsIPCClient {
|
|
||||||
config_set: ConfigSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsIPCClient {
|
|
||||||
pub fn new(config_set: ConfigSet) -> WindowsIPCClient {
|
|
||||||
WindowsIPCClient{config_set}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::IPCClient for WindowsIPCClient {
|
|
||||||
fn send_command(&self, command: IPCCommand) -> Result<(), String> {
|
|
||||||
let stream = TcpStream::connect(
|
|
||||||
("127.0.0.1", self.config_set.default.ipc_server_port as u16)
|
|
||||||
);
|
|
||||||
|
|
||||||
send_command(command, stream)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
# espanso configuration file
|
|
||||||
|
|
||||||
# This is the default configuration file, change it as you like it
|
|
||||||
# You can refer to the official documentation:
|
|
||||||
# https://github.com/federico-terzi/espanso
|
|
||||||
|
|
||||||
# Matches are the substitution rules, when you type the "trigger" string
|
|
||||||
# it gets replaced by the "replace" string.
|
|
||||||
matches:
|
|
||||||
# Simple text replacement
|
|
||||||
- trigger: ":espanso"
|
|
||||||
replace: "Hi there!"
|
|
||||||
|
|
||||||
# Dates
|
|
||||||
- trigger: ":date"
|
|
||||||
replace: "{{mydate}}"
|
|
||||||
vars:
|
|
||||||
- name: mydate
|
|
||||||
type: date
|
|
||||||
params:
|
|
||||||
format: "%m/%d/%Y"
|
|
||||||
|
|
||||||
# Shell commands
|
|
||||||
- trigger: ":shell"
|
|
||||||
replace: "{{output}}"
|
|
||||||
vars:
|
|
||||||
- name: output
|
|
||||||
type: shell
|
|
||||||
params:
|
|
||||||
cmd: "echo Hello from you shell"
|
|
||||||
|
|
||||||
# Emojis
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "😂"
|
|
||||||
- trigger: ":llol"
|
|
||||||
replace: "😂😂😂😂"
|
|
||||||
- trigger: ":sad"
|
|
||||||
replace: "☹"
|
|
||||||
- trigger: ":ssad"
|
|
||||||
replace: "☹☹☹☹"
|
|
||||||
|
|
||||||
# Accented letters
|
|
||||||
- trigger: "e''"
|
|
||||||
replace: "è"
|
|
||||||
- trigger: "e//"
|
|
||||||
replace: "é"
|
|
||||||
- trigger: "a''"
|
|
||||||
replace: "à"
|
|
||||||
- trigger: "i''"
|
|
||||||
replace: "ì"
|
|
||||||
- trigger: "o''"
|
|
||||||
replace: "ò"
|
|
||||||
- trigger: "u''"
|
|
||||||
replace: "ù"
|
|
||||||
|
|
||||||
# Capital accented letters
|
|
||||||
- trigger: "E''"
|
|
||||||
replace: "È"
|
|
||||||
- trigger: "E//"
|
|
||||||
replace: "É"
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>Label</key>
|
|
||||||
<string>com.federicoterzi.espanso</string>
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>{{{espanso_path}}}</string>
|
|
||||||
<string>daemon</string>
|
|
||||||
</array>
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
<key>StandardErrorPath</key>
|
|
||||||
<string>/tmp/espanso.err</string>
|
|
||||||
<key>StandardOutPath</key>
|
|
||||||
<string>/tmp/espanso.out</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Before Width: | Height: | Size: 239 B |
|
@ -1,12 +0,0 @@
|
||||||
backend: Clipboard
|
|
||||||
|
|
||||||
definitely a bad yaml
|
|
||||||
|
|
||||||
matches:
|
|
||||||
# Default
|
|
||||||
- trigger: ":espanso"
|
|
||||||
replace: "Hi there!"
|
|
||||||
|
|
||||||
# Emojis
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "😂"
|
|
|
@ -1,10 +0,0 @@
|
||||||
backend: Clipboard
|
|
||||||
|
|
||||||
matches:
|
|
||||||
# Default
|
|
||||||
- trigger: ":espanso"
|
|
||||||
replace: "Hi there!"
|
|
||||||
|
|
||||||
# Emojis
|
|
||||||
- trigger: ":lol"
|
|
||||||
replace: "😂"
|
|
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 28 KiB |
124
src/sysdaemon.rs
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This functions are used to register/unregister espanso from the system daemon manager.
|
|
||||||
|
|
||||||
use crate::config::ConfigSet;
|
|
||||||
|
|
||||||
// INSTALLATION
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
const MAC_PLIST_CONTENT : &str = include_str!("res/mac/com.federicoterzi.espanso.plist");
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
const MAC_PLIST_FILENAME : &str = "com.federicoterzi.espanso.plist";
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn register(_config_set: ConfigSet) {
|
|
||||||
use std::fs::create_dir_all;
|
|
||||||
use std::process::{Command, ExitStatus};
|
|
||||||
|
|
||||||
let home_dir = dirs::home_dir().expect("Could not get user home directory");
|
|
||||||
let library_dir = home_dir.join("Library");
|
|
||||||
let agents_dir = library_dir.join("LaunchAgents");
|
|
||||||
|
|
||||||
// Make sure agents directory exists
|
|
||||||
if !agents_dir.exists() {
|
|
||||||
create_dir_all(agents_dir.clone()).expect("Could not create LaunchAgents directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
let plist_file = agents_dir.join(MAC_PLIST_FILENAME);
|
|
||||||
if !plist_file.exists() {
|
|
||||||
println!("Creating LaunchAgents entry: {}", plist_file.to_str().unwrap_or_default());
|
|
||||||
|
|
||||||
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
|
||||||
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default());
|
|
||||||
|
|
||||||
let plist_content = String::from(MAC_PLIST_CONTENT)
|
|
||||||
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default());
|
|
||||||
|
|
||||||
std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file");
|
|
||||||
|
|
||||||
println!("Entry created correctly!")
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Reloading entry...");
|
|
||||||
|
|
||||||
let res = Command::new("launchctl")
|
|
||||||
.args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
|
|
||||||
.output();
|
|
||||||
|
|
||||||
let res = Command::new("launchctl")
|
|
||||||
.args(&["load", "-w", plist_file.to_str().unwrap_or_default()])
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if let Ok(status) = res {
|
|
||||||
if status.success() {
|
|
||||||
println!("Entry loaded correctly!")
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
println!("Error loading new entry");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn unregister(_config_set: ConfigSet) {
|
|
||||||
use std::fs::create_dir_all;
|
|
||||||
use std::process::{Command, ExitStatus};
|
|
||||||
|
|
||||||
let home_dir = dirs::home_dir().expect("Could not get user home directory");
|
|
||||||
let library_dir = home_dir.join("Library");
|
|
||||||
let agents_dir = library_dir.join("LaunchAgents");
|
|
||||||
|
|
||||||
let plist_file = agents_dir.join(MAC_PLIST_FILENAME);
|
|
||||||
if plist_file.exists() {
|
|
||||||
let _res = Command::new("launchctl")
|
|
||||||
.args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
|
|
||||||
.output();
|
|
||||||
|
|
||||||
std::fs::remove_file(&plist_file).expect("Could not remove espanso entry");
|
|
||||||
|
|
||||||
println!("Entry removed correctly!")
|
|
||||||
}else{
|
|
||||||
println!("espanso is not installed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINUX
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn register(config_set: ConfigSet) {
|
|
||||||
println!("Linux does not support automatic system daemon integration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn unregister(config_set: ConfigSet) {
|
|
||||||
println!("Linux does not support automatic system daemon integration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn register(_config_set: ConfigSet) {
|
|
||||||
println!("Windows does not support automatic system daemon integration.")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn unregister(_config_set: ConfigSet) {
|
|
||||||
println!("Windows does not support automatic system daemon integration.")
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::c_char;
|
|
||||||
|
|
||||||
use crate::bridge::linux::{get_active_window_name, get_active_window_class, get_active_window_executable};
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
pub struct LinuxSystemManager {}
|
|
||||||
|
|
||||||
impl super::SystemManager for LinuxSystemManager {
|
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
|
||||||
let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_window_class(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
|
||||||
let res = get_active_window_class(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
|
||||||
let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinuxSystemManager {
|
|
||||||
pub fn new() -> LinuxSystemManager {
|
|
||||||
LinuxSystemManager{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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::os::raw::c_char;
|
|
||||||
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier};
|
|
||||||
|
|
||||||
pub struct MacSystemManager {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::SystemManager for MacSystemManager {
|
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
|
||||||
self.get_current_window_class()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_window_class(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 250] = [0; 250];
|
|
||||||
let res = get_active_app_identifier(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer : [c_char; 250] = [0; 250];
|
|
||||||
let res = get_active_app_bundle(buffer.as_mut_ptr(), buffer.len() as i32);
|
|
||||||
|
|
||||||
if res > 0 {
|
|
||||||
let c_string = CStr::from_ptr(buffer.as_ptr());
|
|
||||||
|
|
||||||
let string = c_string.to_str();
|
|
||||||
if let Ok(string) = string {
|
|
||||||
return Some((*string).to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MacSystemManager {
|
|
||||||
pub fn new() -> MacSystemManager {
|
|
||||||
MacSystemManager{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of espanso.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
mod linux;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod macos;
|
|
||||||
|
|
||||||
pub trait SystemManager {
|
|
||||||
fn get_current_window_title(&self) -> Option<String>;
|
|
||||||
fn get_current_window_class(&self) -> Option<String>;
|
|
||||||
fn get_current_window_executable(&self) -> Option<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn get_manager() -> impl SystemManager {
|
|
||||||
linux::LinuxSystemManager::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_manager() -> impl SystemManager {
|
|
||||||
windows::WindowsSystemManager::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn get_manager() -> impl SystemManager {
|
|
||||||
macos::MacSystemManager::new()
|
|
||||||
}
|
|