# HG changeset patch # User Sean E. Russell # Date 1585942400 18000 # Fri Apr 03 14:33:20 2020 -0500 # Node ID 0a9ed05ff3fbe19d70986666de6fc417c1312c26 # Parent f7d1d63dac8998bc4f2f85b8db4b24898225e8f4 Better flags; debug mode; documentation diff --git a/CHANGELOG.md b/CHANGELOG.md --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,6 @@ > - **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. diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -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. diff --git a/main.go b/main.go --- a/main.go +++ b/main.go @@ -20,21 +20,26 @@ 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 \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 @@ 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 @@ 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)