M CHANGELOG.md +0 -8
@@ 13,14 13,6 @@ and this project adheres to [Semantic Ve
> - **Fixed**: for any bug fixes.
> - **Security**: in case of vulnerabilities.
-## [1.1.0] -
-
-### Added
-- Support for marks. This allows multiple applications to be run.
-
-### Changed
-- Improved help
-
## [1.0.0] - 2020-04-03
First release.
A README.md +55 -0
@@ 0,0 1,55 @@
+# kbplug
+
+A userspace tool for executing commands based on udev events. This allows users to do things like set keymaps when keyboards are hot-plugged, without forcing users to make system-level configuration changes.
+
+## Usage
+
+**kbplug** behavior is entirely defined by a rules file. The rules file declares:
+
+- Which udev events to match -- and this largely uses udev matching rules
+- What programs to run when a rule matches
+
+To write rules, use an existing udev monitoring tool and connect in your device. A great tool for this comes with one of **kbplug**'s dependencies, [go-udev](https://github.com/pilebones/go-udev). You can install that with `go get https://github.com/pilebones/go-udev`. If you don't have go, you can also use `udevadm monitor` from the `systemd` package. What you need are:
+
+- The udev ACTION. This is usually one of `add`, `remove`, or `change`.
+- Sufficient ENV parameters to uniquely identify your rule. You may need only two or three ENV variables.
+- The commands you want to run.
+
+The first two things are udev concepts; the third is how you tell **kbplug** what to do when it sees an event. A simple rules file looks like this:
+
+```
+{
+ "rules": [
+ {
+ "action": "add",
+ "env": {
+ "ACTION": "add",
+ "SUBSYSTEM": "input",
+ "NAME": ".*Goldtouch.*Keyboard.*System Control"
+ },
+ "run": [
+ ["setxkbmap", "-layout","us","-variant","dvorak","-geometry","kinesis","-option","caps:super","-option","compose:lwin"]
+ ]
+ }
+ ]
+}
+```
+
+You can specify as many rules for as many devices, and run as many commands as you want. Commands are executed sequentially in the order they're defined.
+
+The `-d` debug flag can be useful for testing rules without executing the commands. It can be tricky finding a single event (and, usually, you want the *last* event) in the series of events udev will report for a given device; generally, you want to try to find a rule that will execute exactly once per plug event. For example, when I connect that Goldtouch keyboard above, udev spews out a dozen `add` events that seem to correspond with each function the keyboard supports, so it took a bit to find a rule that triggered only once.
+
+## Installation
+
+kbplug is `go get`table:
+
+```
+go get code.ser1.net/kbplug
+```
+
+Or, you can [download a binary](https://downloads.ser1.net/software). Put the executable where you want it to live and then start the journey of creating your rules file. I recommend copying the sample from this readme and modifying it from there. Once you have it working the way you like, set it up to start automatically in whichever way you're most comfortable -- `~/.config/autostart/...`, `systemctl --user ...`, `exec kbplug ~/.rules.json` in `.i3/config`, or however you're managing your autostarts.
+
+## Credits
+
+- `kbplug` is made possible by [go-udev](https://github.com/pilebones/go-udev). There are no other direct dependencies.
+- Thanks, as always, to the wonderful Go creators and maintainers.
M main.go +20 -8
@@ 20,21 20,26 @@ var Version string
func main() {
help := flag.Bool("h", false, "Print help and exit")
version := flag.Bool("v", false, "Print version and exit")
+ debug := flag.Bool("d", false, "Print matches, but don't execute commands")
flag.Parse()
+ usage := fmt.Sprintf("USAGE: %s <rules-file>\n", os.Args[0])
if *version {
+ fmt.Printf(usage)
fmt.Println(Version)
os.Exit(0)
}
+ if *help {
+ fmt.Printf(usage)
+ flag.PrintDefaults()
+ fmt.Printf("\nThe only *required* argument is the path to the rules file.\n")
+ os.Exit(0)
+ }
if flag.NArg() < 1 {
+ fmt.Printf("The only *required* argument is the path to the rules file; this was missing.\n\n")
+ fmt.Printf(usage)
flag.PrintDefaults()
- fmt.Printf("The only argument is the path to the rules file.")
os.Exit(1)
}
- if *help {
- flag.PrintDefaults()
- fmt.Printf("The only argument is the path to the rules file.")
- os.Exit(0)
- }
conn := new(netlink.UEventConn)
if err := conn.Connect(netlink.UdevEvent); err != nil {
log.Fatalln("unable to connect to udev")
@@ 67,7 72,11 @@ func main() {
log.Fatalf("Wrong rule syntax in \"%s\", err: %s", flag.Arg(0), err.Error())
}
// Handling message from queue
- log.Printf("monitoring for connection events for %d rules", len(ruleset.Rules))
+ plural := ""
+ if len(ruleset.Rules) > 1 {
+ plural = "s"
+ }
+ log.Printf("monitoring for connection events for %d rule%s", len(ruleset.Rules), plural)
for {
select {
case evt := <-queue:
@@ 75,12 84,15 @@ func main() {
if rule != nil {
var cmd *exec.Cmd
for _, cx := range rule.Run {
+ log.Printf("%s: %v\n", evt.Env["NAME"], cx)
+ if *debug {
+ continue
+ }
if flag.NArg() > 1 {
cmd = exec.Command(cx[0], cx[1:]...)
} else {
cmd = exec.Command(cx[0])
}
- log.Printf("%s: %v\n", evt.Env["NAME"], cx)
err := cmd.Run()
if err != nil {
panic(err)