Deprecate -t and support -- for args to application.
5 files changed, 106 insertions(+), 34 deletions(-)

M go.mod
M go.sum
M main.go
M +2 -1
@@ 13,10 13,11 @@ and this project adheres to [Semantic Ve
 > - **Fixed**: for any bug fixes.
 > - **Security**: in case of vulnerabilities.
-## [1.1.0] - 
+## [1.1.0] - 2020-04-14
 ### Added
 - Support for marks.  This allows multiple applications to be run.
+- Support for arguments for command.  This deprecates `-t`.
 ### Changed
 - Improved help

M +37 -13
@@ 1,6 1,6 @@ 
 # i3quake
-Quake mode for terminals under the [i3]( window manager.  It's a drop-in replacement for [i3-quake]( -- I've kept the command line arguments cross-compatible.
+Quake mode for applications under the [i3]( window manager.
 i3quake creates, shows, and hides a terminal of your choosing.  Common use for these sorts of tools is to bind a hotkey to the command and thereby create a pop-up-on-demand terminal. Features are:

@@ 20,13 20,23 @@ The program is `go get`-table:
 go get
-Or, you can [download a binary](  Put the executable in your path, and then bind the program to a key chord in i3. For example:
+Or, you can [download a binary](  Put the executable in your path, and then use it to launch your terminal (or other application). For example, to auto-start the app from i3, put this in the `~/.i3/config`:
+exec --no-startup-id i3quake -p right -H 0.6 -t st
+Subsequent runs of i3quake will toggle showing the window, but it turns out that i3quake is not necessary because toggling can be performed with commands that can be bound directly in `~/.i3/config` without the overhead of forking a process. Add this binding to your `~/.i3/config`:
-bindsym $mod+n exec i3quake -p right -H 0.6 -t termite
+bindsym $mod+n [con_mark="i3quake"] scratchpad show
-Arguments are (currently) ignored after the first run.
+You can also perform this same toggle on the command line with i3-msg:
+$ i3-msg '[con_mark="i3quake"], scratchpad show'
 ## Examples

@@ 38,19 48,19 @@ Arguments are (currently) ignored after 
 $ i3quake -p right -H 0.5 -t termite     # Opens termite to right
-$ i3quake -p left -H 0.5 -m gvim -t gvim # Opens gvim to left
+$ i3quake -p left -H 0.5 -m gvim -t gvim # Opens gvim to left, marked as "gvim"
-The last example can be used to bind multiple utility programs to different keys that are popped-up on demand.
-## Why
-I first tried a couple of other projects, but they were all several years old. The one I liked the most had a couple of limitations, bugs, and were out of date with the i3 IPC API. The main bug was that it used the WM_NAME property to identify the quake window, and **everybody** changes that, so it's not reliable. At first I tried to fix the program -- it was only a few dozen lines of Python, after all -- but I encountered three roadblocks:
+The last example can be used to bind multiple utility programs to different keys that are popped-up on demand.  For these "custom marked" applications, the mark is `i3quake<MARK>`, so if you're binding this to a keysym in the config, for the above example you'd use:
-1. I don't know Python.  I don't *like* Python, but I also don't know it, so working on the application was unpleasant and difficult.
-2. There is little useful documentation deeper than surface level for the i3 IPC interface, and even less for the Python i3ipc interface.
-3. What stopped me in the end, though, was that to fix the WM_NAME bug, I needed to use the i3 mark feature.  However, you can't create a window and mark it at the same time, so you need to fork a process, wait for the window to appear, and *then* mark it.  This is when I discovered that writing concurrent code in Python is truly a new level of hell, and I threw in the towel.
+bindsym $mod+g [con_mark="i3quakegvim"] scratchpad show
-Both go-i3ipc libraries I found were also not current with the i3 IPC API, but this was easily addressed, and I spent about a quarter of the time rewriting i3quake from scratch in Go than I spent trying (unsuccessfully) to fix the Python version. This is not because it's faster to write Go; it's because it's faster for **me** to write Go, and because I'd already discovered all of the speed bumps in working on the Python version and knew how I wanted to approach the problem.  The parallel code was certainly nicer in Go.
+## How
+i3quake helps by setting up the windows with the information i3 needs to manage them the way you want.  You can do all of this on the command line using `i3-msg`; i3quake just makes this easy for you.  After that, the fastest and most efficient way of toggling is to bind an i3 command directly to a keysym; all other methods (including calling i3quake) do exactly the same thing, but with more overhead.
+What i3quake is doing is taking your command-line instructions about placement and what to run, launches the application, and sets up the i3 rules for the resulting window. If you run it again, it'll go ahead and use the IPC to toggle the window; however, as mentioned above, this can be more efficiently done by binding the rule to a keysym in the i3 config file.
 ## Credit
 The Python version that I started with was [i3-quake]( That, itself, was inspired by [i3-quickterm]( If we're going all the way back, the idea was popularized by the video game [Quake](, by ID software.

@@ 59,3 69,17 @@ This version uses [i3ipc-go](https://git
 ## Licenses
 **[BSD 3 Clauses](**
+## Supplemental design thoughts
+After I wrote the program, I realized that it might not be entirely necessary -- or, at least, the part that I thought was useful, wasn't. Here's why.
+It turns out that all of the useful work i3quake does is in the set-up of the window: figuring out dimensions, setting up the mark, telling i3 about the scratchpad-ness of the window, that sort of thing.  Once it's done, the toggling is easily performed by i3-msg:
+i3-msg '[con_mark="i3quake"], scratchpad show'
+After set-up, the only thing i3quake does is to make the *same* IPC call that this command would. And, although I dearly love Go, i3-msg is pure C and is certainly lighter than i3quake.  So my instructions changed from using i3quake for the toggling to what they are now.
+The initial window set-up still benefits from being a program. It *could* all be shell scripted, but dealing with math gets a little wonky in bash, and the concurrency requirement of the program set-up still benefits from Go's easy concurrency model.

M go.mod +4 -1
@@ 2,4 2,7 @@ module
 go 1.14
-require v0.0.0-20200401203636-25cff3fb5bf2
+require (
+ v0.0.0-20170811000552-20be20937449
+ v0.0.0-20200401203636-25cff3fb5bf2

M go.sum +2 -0
@@ 1,2 1,4 @@ v0.0.0-20170811000552-20be20937449 h1:X0nEEUOPBAnG5oJFoM0l28BlfL1u57QHsZemNtqi0uk= v0.0.0-20170811000552-20be20937449/go.mod h1:WTfOHN51lH2Zo/uvB/C8p9eHhLlcyolyJVg8bkAjHpQ= v0.0.0-20200401203636-25cff3fb5bf2 h1:7EF029nNcxDXO0ki15KRp/PHKnFEkdrOSBGR1vgvQdI= v0.0.0-20200401203636-25cff3fb5bf2/go.mod h1:mkE9djHoON2xsUBGcuXBzC95l6573bYcLc5X61eOfEU=

M main.go +61 -19
@@ 5,10 5,13 @@ package main
 // BSD-3 Licensed
+// TODO:
+// TODO: Can we dynamically map bindsym using the API?
 import (
+	""

@@ 39,31 42,68 @@ type con struct {
 func main() {
 	// TODO: Option to have per-workspace quake
-	pos := flag.String("p", "top", "Position: top, bottom, left, right (top)")
-	ratio := flag.Float64("H", 0.25, "% height of window, 0-1 (0.25)")
-	// TODO: allow arguments for terminal command
-	cmd := flag.String("t", "i3-sensible-terminal", "Terminal program (default i3-sensible-terminal)")
+	pos := flag.String("p", "top", "Position: top, bottom, left, right")
+	ratio := flag.Float64("H", 0.25, "% height of window, 0-1")
+	cmd := flag.String("t", "i3-sensible-terminal", "Terminal program (DEPRECATED)")
 	version := flag.Bool("v", false, "Print the version and exit")
 	help := flag.Bool("h", false, "Show help and exit")
 	mark := flag.String("m", "", "Additional marking")
-	// TODO: allow shell command as argument (e.g., tmux)
-	flag.Parse()
+	getopt.Parse()
 	if *version {
 		fmt.Printf("i3quake version %s (built %s)", Version, BuildDate)
+	cmds := flag.Args()
+	if *cmd != "i3-sensible-terminal" {
+		example := make([]string, 0, len(os.Args)-1)
+		for i := 0; i < len(os.Args); i++ {
+			if os.Args[i] != "-t" {
+				example = append(example, os.Args[i])
+			} else {
+				i++
+			}
+		}
+		example = append(example, *cmd)
+		fmt.Println("-t has been deprecated; please pass the command to run as a non-flag argument.")
+		fmt.Printf("For example, you should change your command to:\n\t%s\n", strings.Join(example, " "))
+		fmt.Println("Please see the help text for more info.")
+		if len(cmds) > 0 {
+			fmt.Println("\nAdditionally, i3quake has been given conflicting instructions -- args plus -t")
+			fmt.Printf("The `-t %s` argument is being ignored in favor of `%s`\n", *cmd, strings.Join(cmds, " "))
+		}
+	}
+	if len(flag.Args()) == 0 {
+		// Still run sensible-terminal or whatever the user defined with -t if they don't provide another command
+		cmds = []string{*cmd}
+	}
 	if *help {
+		fmt.Printf("USAGE: %s [args] <application>\n", os.Args[0])
-		fmt.Println("Launches a terminal in quake mode, or toggles visibility of an existing")
+		fmt.Println("\nLaunches a terminal in quake mode, or toggles visibility of an existing")
 		fmt.Println("application. The location (top/left/bottom/right) and size (%% of screen")
-		fmt.Println("width/height) is configurable. The application that is run is specificed")
-		fmt.Println("with -t; this can be any GUI program. Multiple applications can be ")
-		fmt.Println("managed by using the -m flag to specify additional marks; only one ")
-		fmt.Println("program can use a given mark.")
+		fmt.Println("width/height) is configurable. i3quake runs the application specified in")
+		fmt.Println("the \"remainder\" arguments.  To pass arguments to *that* program, end")
+		fmt.Println("your arguments with \"--\". The application can be any GUI program. ")
+		fmt.Println("Multiple applications can be managed by using the \"-m\" flag to identify")
+		fmt.Println("the windows; a mark can be used for only one program instance.")
-		fmt.Println("\ti3quake -p left -h 0.5          # open terminal on left, half-screen")
-		fmt.Println("\ti3quake -p right -h 0.5 -m gvim -t gvim   # open gvim on right")
+		fmt.Println("\t# open terminal on left, half-screen")
+		fmt.Println("\ti3quake -p left -H 0.5")
+		fmt.Println("\t#Open gvim on the right, marked as \"gvim\"")
+		fmt.Println("\ti3quake -p right -H 0.5 -m gvim gvim")
+		fmt.Println("\t# Open Conky on the left in \"own-window\" mode:")
+		fmt.Println("\ti3quake -p left -H 0.5 -m conky -- conky -o")
+		fmt.Println("\t# Run nvim in an st window from the top")
+		fmt.Println("\ti3quake -p top -H 0.75 -m nvim -- st -t nvim -w nvim nvim +Project")
+		fmt.Println()
+		fmt.Println("You would then add something like the following to your `~/.i3/config`,")
+		fmt.Println("choosing an appropriate (unused) key:")
+		fmt.Println("\t# For the default mark:")
+		fmt.Println("\tbindsym $mod+n [con_mark=\"i3quake\"] scratchpad show")
+		fmt.Println("\t# For the \"conky\" mark:")
+		fmt.Println("\tbindsym $mod+n [con_mark=\"i3quakeconky\"] scratchpad show")
+		fmt.Println("Note that the bind mark is \"i3quake\" + your mark name specified with `-m`")
 	var p int

@@ 84,10 124,10 @@ func main() {
 	cx := con{c}
-	cx.toggleQuake(p, *mark, *ratio, *cmd)
+	cx.toggleQuake(p, *mark, *ratio, cmds)
-func (c con) toggleQuake(pos int, mark string, ratio float64, cmd string) {
+func (c con) toggleQuake(pos int, mark string, ratio float64, cmds []string) {
 	t, e := c.GetTree()
 	if e != nil {

@@ 95,10 135,12 @@ func (c con) toggleQuake(pos int, mark s
 	w := t.FindMarked(NAME + mark)
 	// quake hasn't been started yet, so fire one off
 	if w == nil {
-		if cmd == "" {
-			cmd = "st"
+		var com *exec.Cmd
+		if len(cmds) > 1 {
+			com = exec.Command(cmds[0], cmds[1:]...)
+		} else {
+			com = exec.Command(cmds[0])
-		com := exec.Command(cmd)
 		evts, err := i3ipc.Subscribe(i3ipc.I3WindowEvent)
 		if err != nil {

@@ 133,7 175,7 @@ func (c con) toggleQuake(pos int, mark s
 	} else {
-		// TODO: Don't ignore argumentas after first run
+		// TODO: Don't ignore argumentas after first run, allowing the window to be moved.
 		cmd := fmt.Sprintf(`[con_mark="%s"], scratchpad show`, NAME+mark)
 		suc, _ := c.Command(cmd)
 		if !suc {