inputplug-0.4.0/.cargo_vcs_info.json0000644000000001120000000000000130550ustar { "git": { "sha1": "a8f6cb1cdbd9dfbeeaec66f50b9c9ac4305d147d" } } inputplug-0.4.0/.gitignore000064400000000000000000000000370000000000000136210ustar 00000000000000/target Cargo.toml *.iml .idea inputplug-0.4.0/Cargo.lock0000644000000200000000000000000110260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "anyhow" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "gethostname" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" dependencies = [ "libc", "winapi", ] [[package]] name = "heck" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] [[package]] name = "inputplug" version = "0.4.0" dependencies = [ "anyhow", "nix 0.22.0", "pidfile-rs", "structopt", "x11rb", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "memoffset" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ "bitflags", "cc", "cfg-if", "libc", ] [[package]] name = "nix" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" dependencies = [ "bitflags", "cc", "cfg-if", "libc", "memoffset", ] [[package]] name = "pidfile-rs" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf50ad3f0e69aab8d669bb31a314baea3f35e4e441c0b07af883c06eacd3496" dependencies = [ "libc", "log", "pkg-config", "thiserror", ] [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] [[package]] name = "structopt" version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71" dependencies = [ "clap", "lazy_static", "structopt-derive", ] [[package]] name = "structopt-derive" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "thiserror" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-segmentation" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-wsapoll" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "x11rb" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ffb080b3f2f616242a4eb8e7d325035312127901025b0052bc3154a282d0f19" dependencies = [ "gethostname", "nix 0.20.0", "winapi", "winapi-wsapoll", ] inputplug-0.4.0/Cargo.toml0000644000000020670000000000000110660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "inputplug" version = "0.4.0" authors = ["Andrej Shadura "] description = "XInput monitor daemon" license = "MIT" repository = "https://github.com/andrewshadura/inputplug" [dependencies.anyhow] version = "1.0" [dependencies.nix] version = ">= 0.19, <1.0" [dependencies.pidfile-rs] version = "0.1" optional = true [dependencies.structopt] version = "0.3" default-features = false [dependencies.x11rb] version = "0.8" features = ["xinput"] [features] default = ["pidfile"] pidfile = ["pidfile-rs"] inputplug-0.4.0/Cargo.toml.orig000064400000000000000000000010020000000000000145110ustar 00000000000000[package] name = "inputplug" version = "0.4.0" authors = ["Andrej Shadura "] edition = "2018" description = "XInput monitor daemon" license = "MIT" repository = "https://github.com/andrewshadura/inputplug" [dependencies] structopt = { version = "0.3", default-features = false } nix = ">= 0.19, <1.0" anyhow = "1.0" [dependencies.pidfile-rs] optional = true version = "0.1" [features] default = ["pidfile"] pidfile = ["pidfile-rs"] [dependencies.x11rb] version = "0.8" features = ["xinput"] inputplug-0.4.0/GNUmakefile000064400000000000000000000010110000000000000136740ustar 00000000000000build: inputplug.1 inputplug.md cargo build --release install: build ifeq ($(DESTDIR),) cargo install --path . else cargo install --root "$(DESTDIR)" --path . endif inputplug.1: inputplug.pod pod2man -r "" -c "" -n $(shell echo $(@:%.1=%) | tr a-z A-Z) $< > $@ inputplug.md: inputplug.pod pod2markdown < $< | sed -e 's, - , — ,g' \ -e 's,^- ,* ,g' \ -e 's,man.he.net/man./,manpages.debian.org/,g' \ -e 's,\[\(<.*@.*>\)\](.*),\1,' > $@ inputplug-0.4.0/LICENSE000064400000000000000000000017770000000000000126520ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. inputplug-0.4.0/README.md000064400000000000000000000047370000000000000131230ustar 00000000000000inputplug ========= inputplug is a very simple daemon which monitors XInput events and runs arbitrary scripts on hierarchy change events (such as a device being attached, removed, enabled or disabled). To build the project, run `cargo build`. * * * # NAME inputplug — XInput event monitor # SYNOPSIS **inputplug** \[**-v**\] \[**-n**\] \[**-d**\] \[**-0**\] **-c** _command-prefix_ **inputplug** \[**-h**|**--help**\] # DESCRIPTION **inputplug** is a daemon which connects to a running X server and monitors its XInput hierarchy change events. Such events arrive when a device is being attached or removed, enabled or disabled etc. When a hierarchy change happens, **inputplug** parses the event notification structure, and calls the command specified by _command-prefix_. The command receives four arguments: * _command-prefix_ _event-type_ _device-id_ _device-type_ _device-name_ Event type may be one of the following: * _XIMasterAdded_ * _XIMasterRemoved_ * _XISlaveAdded_ * _XISlaveRemoved_ * _XISlaveAttached_ * _XISlaveDetached_ * _XIDeviceEnabled_ * _XIDeviceDisabled_ Device type may be any of those: * _XIMasterPointer_ * _XIMasterKeyboard_ * _XISlavePointer_ * _XISlaveKeyboard_ * _XIFloatingSlave_ Device identifier is an integer. The device name may have embedded spaces. # OPTIONS A summary of options is included below. * **-h**, **--help** Show help (**--help** shows more details). * **-v** Be a bit more verbose. * **-n** Start up, monitor events, but don't actually run anything. With verbose more enabled, would print the actual command it'd run. This implies **-d**. * **-d** Don't daemonise. Run in the foreground. * **-0** On start, trigger added and enabled events for each plugged devices. A master device will trigger the "added" event while a slave device will trigger both the "added" and the "enabled" device. * **-c** _command-prefix_ Command prefix to run. Unfortunately, currently this is passed to [execvp(3)](http://manpages.debian.org/execvp) directly, so spaces aren't allowed. This is subject to change in future. * **-p** _pidfile_ Write the process ID of the running daemon to the file _pidfile_ # ENVIRONMENT * _DISPLAY_ X11 display to connect to. # SEE ALSO [xinput(1)](http://manpages.debian.org/xinput) # COPYRIGHT Copyright (C) 2013, 2014, 2018, 2020, 2021 Andrej Shadura. Copyright (C) 2014, 2020 Vincent Bernat. Licensed as MIT/X11. # AUTHOR Andrej Shadura inputplug-0.4.0/inputplug.1000064400000000000000000000154300000000000000137450ustar 00000000000000.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "INPUTPLUG 1" .TH INPUTPLUG 1 "2020-11-01" "" "" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" inputplug \- XInput event monitor .SH "SYNOPSIS" .IX Header "SYNOPSIS" \&\fBinputplug\fR [\fB\-v\fR] [\fB\-n\fR] [\fB\-d\fR] [\fB\-0\fR] \fB\-c\fR \fIcommand-prefix\fR .PP \&\fBinputplug\fR [\fB\-h\fR|\fB\-\-help\fR] .SH "DESCRIPTION" .IX Header "DESCRIPTION" \&\fBinputplug\fR is a daemon which connects to a running X server and monitors its XInput hierarchy change events. Such events arrive when a device is being attached or removed, enabled or disabled etc. .PP When a hierarchy change happens, \fBinputplug\fR parses the event notification structure, and calls the command specified by \fIcommand-prefix\fR. The command receives four arguments: .IP "\fIcommand-prefix\fR \fIevent-type\fR \fIdevice-id\fR \fIdevice-type\fR \fIdevice-name\fR" 4 .IX Item "command-prefix event-type device-id device-type device-name" .PP Event type may be one of the following: .IP "\(bu" 4 \&\fIXIMasterAdded\fR .IP "\(bu" 4 \&\fIXIMasterRemoved\fR .IP "\(bu" 4 \&\fIXISlaveAdded\fR .IP "\(bu" 4 \&\fIXISlaveRemoved\fR .IP "\(bu" 4 \&\fIXISlaveAttached\fR .IP "\(bu" 4 \&\fIXISlaveDetached\fR .IP "\(bu" 4 \&\fIXIDeviceEnabled\fR .IP "\(bu" 4 \&\fIXIDeviceDisabled\fR .PP Device type may be any of those: .IP "\(bu" 4 \&\fIXIMasterPointer\fR .IP "\(bu" 4 \&\fIXIMasterKeyboard\fR .IP "\(bu" 4 \&\fIXISlavePointer\fR .IP "\(bu" 4 \&\fIXISlaveKeyboard\fR .IP "\(bu" 4 \&\fIXIFloatingSlave\fR .PP Device identifier is an integer. The device name may have embedded spaces. .SH "OPTIONS" .IX Header "OPTIONS" A summary of options is included below. .IP "\fB\-h\fR, \fB\-\-help\fR" 4 .IX Item "-h, --help" Show help (\fB\-\-help\fR shows more details). .IP "\fB\-v\fR" 4 .IX Item "-v" Be a bit more verbose. .IP "\fB\-n\fR" 4 .IX Item "-n" Start up, monitor events, but don't actually run anything. With verbose more enabled, would print the actual command it'd run. This implies \fB\-d\fR. .IP "\fB\-d\fR" 4 .IX Item "-d" Don't daemonise. Run in the foreground. .IP "\fB\-0\fR" 4 .IX Item "-0" On start, trigger added and enabled events for each plugged devices. A master device will trigger the \*(L"added\*(R" event while a slave device will trigger both the \*(L"added\*(R" and the \*(L"enabled\*(R" device. .IP "\fB\-c\fR \fIcommand-prefix\fR" 4 .IX Item "-c command-prefix" Command prefix to run. Unfortunately, currently this is passed to \&\fBexecvp\fR\|(3) directly, so spaces aren't allowed. This is subject to change in future. .IP "\fB\-p\fR \fIpidfile\fR" 4 .IX Item "-p pidfile" Write the process \s-1ID\s0 of the running daemon to the file \fIpidfile\fR .SH "ENVIRONMENT" .IX Header "ENVIRONMENT" .IP "\fI\s-1DISPLAY\s0\fR" 4 .IX Item "DISPLAY" X11 display to connect to. .SH "SEE ALSO" .IX Header "SEE ALSO" \&\fBxinput\fR\|(1) .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright (C) 2013, 2014, 2018, 2020, 2021 Andrej Shadura. .PP Copyright (C) 2014, 2020 Vincent Bernat. .PP Licensed as \s-1MIT/X11.\s0 .SH "AUTHOR" .IX Header "AUTHOR" Andrej Shadura inputplug-0.4.0/inputplug.pod000064400000000000000000000045170000000000000143730ustar 00000000000000=head1 NAME inputplug - XInput event monitor =head1 SYNOPSIS B [B<-v>] [B<-n>] [B<-d>] [B<-0>] B<-c> I B [B<-h>|B<--help>] =head1 DESCRIPTION B is a daemon which connects to a running X server and monitors its XInput hierarchy change events. Such events arrive when a device is being attached or removed, enabled or disabled etc. When a hierarchy change happens, B parses the event notification structure, and calls the command specified by I. The command receives four arguments: =over =item I I I I I =back Event type may be one of the following: =over =item * I =item * I =item * I =item * I =item * I =item * I =item * I =item * I =back Device type may be any of those: =over =item * I =item * I =item * I =item * I =item * I =back Device identifier is an integer. The device name may have embedded spaces. =head1 OPTIONS A summary of options is included below. =over =item B<-h>, B<--help> Show help (B<--help> shows more details). =item B<-v> Be a bit more verbose. =item B<-n> Start up, monitor events, but don't actually run anything. With verbose more enabled, would print the actual command it'd run. This implies B<-d>. =item B<-d> Don't daemonise. Run in the foreground. =item B<-0> On start, trigger added and enabled events for each plugged devices. A master device will trigger the "added" event while a slave device will trigger both the "added" and the "enabled" device. =item B<-c> I Command prefix to run. Unfortunately, currently this is passed to L directly, so spaces aren't allowed. This is subject to change in future. =item B<-p> I Write the process ID of the running daemon to the file I =back =head1 ENVIRONMENT =over =item I X11 display to connect to. =back =head1 SEE ALSO L =head1 COPYRIGHT Copyright (C) 2013, 2014, 2018, 2020, 2021 Andrej Shadura. Copyright (C) 2014, 2020 Vincent Bernat. Licensed as MIT/X11. =head1 AUTHOR Andrej Shadura L<< >> inputplug-0.4.0/src/main.rs000064400000000000000000000153500000000000000137160ustar 00000000000000// Copyright (C) 2020—2021 Andrej Shadura // SPDX-License-Identifier: MIT mod mask_iter; use mask_iter::IterableMask; use nix::unistd::daemon; #[cfg(feature = "pidfile")] use pidfile_rs::Pidfile; use std::convert::{From, TryFrom}; use structopt::StructOpt; #[cfg(feature = "pidfile")] use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::PathBuf}; use std::process::Command; use anyhow::{anyhow, Context, Result}; use x11rb::connection::{ Connection as _, RequestConnection }; use x11rb::protocol::Event; use x11rb::protocol::xinput::{ self, ConnectionExt as _, Device, DeviceId, DeviceType, EventMask, HierarchyInfo, HierarchyMask, XIDeviceInfo, XIEventMask }; use x11rb::protocol::xproto::GE_GENERIC_EVENT; #[derive(Debug, StructOpt)] #[structopt(name = "inputplug", about = "XInput event monitor")] struct Opt { /// Enable debug mode. #[structopt(long)] debug: bool, /// Be a bit more verbose. #[structopt(short, long)] verbose: bool, /// Don't daemonize, run in the foreground. #[structopt(short = "d", long)] foreground: bool, /// Start up, monitor events, but don't actually run anything. /// /// With verbose more enabled, would print the actual command it'd run. #[structopt(short, long)] no_act: bool, /// On start, trigger added and enabled events for each plugged devices. /// /// A master device will trigger the "added" event while a slave /// device will trigger both the "added" and the "enabled" device. #[structopt(short = "0", long)] bootstrap: bool, /// Command prefix to run. #[structopt(short = "c", long)] command: String, /// PID file #[cfg(feature = "pidfile")] #[structopt(short = "p", long, parse(from_os_str))] pidfile: Option, } trait HierarchyChangeEvent { fn to_cmdline(&self, conn: &impl RequestConnection) -> Vec; } fn device_name(conn: &impl RequestConnection, deviceid: DeviceId) -> Option { if let Ok(r) = conn.xinput_xi_query_device(deviceid) { if let Ok(reply) = r.reply() { reply.infos.iter() .find(|info| info.deviceid == deviceid) .map(|info| String::from_utf8_lossy(&info.name).to_string()) } else { None } } else { None } } fn format_device_type(device_type: DeviceType) -> String { if device_type == DeviceType::from(0u8) { "".into() } else { format!("XI{:#?}", device_type) } } impl HierarchyChangeEvent for XIDeviceInfo { fn to_cmdline(&self, conn: &impl RequestConnection) -> Vec { vec![ self.deviceid.to_string(), format_device_type(self.type_), String::from_utf8_lossy(&self.name).to_string(), ] } } impl HierarchyChangeEvent for HierarchyInfo { fn to_cmdline(&self, conn: &impl RequestConnection) -> Vec { vec![ self.deviceid.to_string(), format_device_type(self.type_), device_name(conn, self.deviceid).unwrap_or("".to_string()), ] } } fn handle_device>( opt: &Opt, conn: &impl RequestConnection, device_info: &T, change: HierarchyMask ) { let mut command = Command::new(&opt.command); command.arg(format!("XI{:#?}", change)) .args(device_info.to_cmdline(conn)); if opt.verbose { println!("{:?}", &command); } if !opt.no_act { if let Err(e) = command.status() { eprintln!("Command failed: {}", e); } } } fn main() -> Result<()> { let opt = Opt::from_args(); let (conn, _) = x11rb::connect(None).context("Can't open X display")?; let xinput_info = conn .extension_information(xinput::X11_EXTENSION_NAME) .context("X Input extension cannot be detected.")? .ok_or(anyhow!("X Input extension not available."))?; if opt.debug { println!("X Input extension opcode: {}", xinput_info.major_opcode); } // We don’t want to inherit an open connection into the daemon drop(conn); #[cfg(feature = "pidfile")] let pidfile = if opt.pidfile.is_some() { Some(Pidfile::new( &opt.pidfile.as_ref().unwrap(), Permissions::from_mode(0o600) )?) } else { None }; if !opt.foreground { daemon(false, opt.verbose).context("Cannot daemonize")?; println!("Daemonized."); #[cfg(feature = "pidfile")] if pidfile.is_some() { if let Err(error) = pidfile.unwrap().write() { eprintln!("Failed to write to the PID file: {:?}", error); } } } // Now that we’re in the daemon, reconnect to the X server let (conn, screen_num) = x11rb::connect(None) .context("Can't reconnect to the X display")?; let screen = &conn.setup().roots[screen_num]; if opt.bootstrap { if opt.debug { println!("Bootstrapping events"); } if let Ok(reply) = conn.xinput_xi_query_device(bool::from(Device::ALL)) { let reply = reply.reply()?; for info in reply.infos { match DeviceType::try_from(info.type_).unwrap() { DeviceType::MASTER_POINTER | DeviceType::MASTER_KEYBOARD => { handle_device(&opt, &conn, &info, HierarchyMask::MASTER_ADDED) } DeviceType::SLAVE_POINTER | DeviceType::SLAVE_KEYBOARD | DeviceType::FLOATING_SLAVE => { handle_device(&opt, &conn, &info, HierarchyMask::SLAVE_ADDED); handle_device(&opt, &conn, &info, HierarchyMask::DEVICE_ENABLED) } _ => {} } } } } conn.xinput_xi_select_events( screen.root, &[EventMask { deviceid: bool::from(Device::ALL).into(), mask: vec![XIEventMask::HIERARCHY.into()], }] )?; conn.flush()?; loop { let event = conn.wait_for_event() .context("Failed to get an event")?; if event.response_type() != GE_GENERIC_EVENT { continue; } if let Event::XinputHierarchy(hier_event) = event { if hier_event.extension != xinput_info.major_opcode { continue; } for info in hier_event.infos { let flags = IterableMask::from(info.flags) .map(|x| HierarchyMask::from(x as u8)) .collect::>(); for flag in flags { handle_device(&opt, &conn, &info, flag); } } } } } inputplug-0.4.0/src/mask_iter.rs000064400000000000000000000035700000000000000147510ustar 00000000000000// Copyright (C) 2020 Andrej Shadura // SPDX-License-Identifier: MIT use std::iter::Iterator; /// Iterate over the bits of a binary value /// /// `IterableMask` is an iterator producing only the set bits /// of a binary value it is created from: /// ```rust /// IterableMask::from(0x81_u8).collect::>() /// # [0x80, 1] /// ``` pub struct IterableMask { value: T, curr_mask: T } macro_rules! implement_iterable_mask { ($t: ty) => { impl Iterator for IterableMask<$t> { type Item = $t; fn next(&mut self) -> Option<$t> { loop { let bit = self.curr_mask & self.value; self.curr_mask >>= 1; if bit != 0 { return Some(bit); } if self.curr_mask == 0 { break; } } None } } impl From<$t> for IterableMask<$t> { fn from(value: $t) -> Self { IterableMask { value: value, curr_mask: <$t>::from(1u8).rotate_right(1) } } } }; } implement_iterable_mask!(u8); implement_iterable_mask!(u16); implement_iterable_mask!(u32); #[cfg(test)] mod tests { use super::*; #[test] fn basic() { assert_eq!( IterableMask::from(0x123ce_u32).collect::>(), [0x10000, 0x2000, 0x200, 0x100, 0x80, 0x40, 8, 4, 2] ); assert_eq!( IterableMask::from(0x23c5_u16).collect::>(), [0x2000, 0x200, 0x100, 0x80, 0x40, 4, 1] ); assert_eq!( IterableMask::from(0xce_u8).collect::>(), [0x80, 0x40, 8, 4, 2] ); assert_eq!(IterableMask::from(0_u8).collect::>(), []); } }