X11 I3wm Setup
One of the first additions to my tiling window workflow for my linux desktop was a drop-down terminal; Originally with a dedicated terminal application, then with a shell script with urxvt and then finally my own dedicated program, i3dropdown. Which provides drop-down functionally to in any program and has great performance.
Similar to Evdoublebind discus in my last post, i3dropdown was design to be used for i3wm on X11 and when I migrate to wayland the functionally provided by i3dropdown will have to be duplicated. However, since i3dropdown works entirely by interactions with i3wm's IPC which sway provides backwards compatibility with, migration to sway should be easy for this application.
Most frequently I use terminals, chat-programs like mumble and my password manager as drop downs. For mumble and my password manager is have dedicated keys S-m
and S-p
respectively which when pressed toggle the application, dropping down the application on my current display and starting the application if closed or hiding the window it was already visible. Although, i3dropdown works great with multi monitor setups I find it most useful when I am on the go using my 14 inch laptop, as I have very little screen real estate.
Further I can make any application a drop-down by pressing one of the keys H-TAB
, H-1
, H-2
and H-3
which will convert my focused window into a drop down and hide it assigning it the key used so further presses of that key toggles that window. Usually these auxiliary drop-downs are reference material in a PDF viewer or terminals used for compilation output needing little interaction like LaTeX.
I also have a main terminal where run as Tmux session that I use for many things and a dedicated file-manager terminal currently running nnn
bound to S-n
which hides it self when opening an file with a graphical application. Since I utilize urxvt's daemon feature dropping down a terminal is instant even from a closed state. Further I use 7 frame only drop animation, just long enough to keep it smooth.
My configuration to use I3dropdown for my terminal is shown below, the reset of my configs follow a similar pattern.
#FILE:.config/i3/config
for_window [instance="ddterminal"] floating enable;
bindsym Mod3+Return exec --no-startup-id i3dropdown ddterminal \
"urxvtc -name ddterminal -background rgba:1000/1000/1000/D000 \
-internalBorder 4 -e dash -c 'tmux new -t dropdown'"
At the highest level, I3dropdown detects which state the application window is in: closed, open on active display, or other. Then, if closed the application is started and displayed on the active display, if already on the active display the application is hidden and if hidden it is displayed. To gather all the information needed, I3dropdown uses the I3 IPC to get the JSON tree output of I3. From the tree, the current display, it's resolution and the application state is determined by searching for window matching the instance name provided as a command line argument. When necessary the application is started using the command provide also as a command line argument. Then using the i3IPC the application window is position and resized as necessary to provide the animating drop down effect.
When creating I3dropdown I spent a lot of time optimizing it, more for fun and learning rather than for practical purposes. I3dropdown is definitely pre-maturely optimizated and currently contains many optimizations each of which fall into one of the three following categories in terms of fruitfulness:
i3 --get-socketpath
to get ipc-socket, which can take 10ms.#Note task-clock is not the same as real elapsed time.
> sudo perf stat dash -c "echo hi"
... 0.09 msec task-clock # 0.204 CPUs utilized
# dash vs bash
> sudo perf stat bash -c "echo hi"
... 2.67 msec task-clock # 0.909 CPUs utilized
perf stat
comparing with /bin/echo
.> perf stat i3dropdown ddterminal ~/scripts/dropdown_urxvt.sh
...
7 page-faults:u # 0.010 M/sec
102,520 cycles:u # 0.145 GHz
83,926 instructions:u # 0.82 insn per cycle
17,924 branches:u # 25.302 M/sec
# i3dropdown vs /bin/echo
> perf stat /bin/echo hi
...
57 page-faults:u # 0.132 M/sec
355,561 cycles:u # 0.821 GHz
237,662 instructions:u # 0.67 insn per cycle
50,255 branches:u # 116.021 M/sec
My biggest gripe with all the extreme optimization is it didn't even produce the fastest solution, which would be to make a patch and just implement drop-downs directly in i3. However, having i3dropdown as a separate program has perks too especially if I can adapt it to work on sway which supports i3wm's IPC.
Prediquery was designed for extracting a small amount of data out of a large amount of json. Specifically I3dropdown uses Prediquery to extract the size of the display and position/state of a application matched by instance name from i3 IPC tree output. I'm going to provide just enough detail about prediquery here to understand how i3dropdown uses it, as it deserves a post of it's own.
The basic idea is to, with one pass of the JSON, without mutating the json be able to perform a query that can find objects that match as set of predicates in a possibly recursive json structure and then extract values from the objects fields. The predicates are Boolean expressions of other predicates or functions taking inputs of child or parent objects of the a possible matching object.
Before I explain any more lets consider a very simple example with a code snippet from i3dropdown.
struct rect_box {
long int width, height, x, y;
};
rect_box rect_parse(char * json){
rect_box box;
using namespace prediquery;
QueryExpr query[] = {
/*0*/invoice("x", &box.x),
/*1*/invoice("y", &box.y),
/*2*/invoice("width", &box.width),
/*3*/invoice("height", &box.height)
};
uint64_t res = tquery(json, query, 4 /*length of query[]*/, bitset(0, 1, 2, 3));
if (res != bitset(0,1,2,3)){
printf("rect parse error");
}
return box;
}
In the snippet Prediquery used to perform a basic parse of JSON input in the form {"x":0,"y":1049,"width":1920,"height":31}
. The invoice directive in a query specifies a field value of an object, where to store it's value and a bitset of required predicates to save that value. In the above snippet no required predicates are provided to the invoice so they are always enabled if active. Then tquery(json, query, 4, bitset(0, 1, 2, 3))
provides the json, the query, the number of query directives in the query and the initial query directives active in root of the JSON structure.tquery
returns a bitset of directives fulfilled.
Eventually if I continue work on prediquery I want to avoid having users write query directives by hand, as you'll seen in a moment they become complex and error prone quickly.
The query below is defined recursively. The recurse
directive indicates the set of active query directives to be used when parsing the object or array of objects at a field, if the field neither exists nor is an object or array of objects it is ignored.
i3Query queryi3(const char * inst, const char *reply){
char *output_rect = nullptr;
int64_t instance_workspace = 0;
int64_t current_workspace = 0;
uint64_t instance_id = 0;
using namespace prediquery;
const QueryExpr query[] = {
/*00*/recurse("nodes", bitset(1, 2, 3)), //Output Nodes
/*01*/invoice("rect", &output_rect, bitset(2)),
/*02*/dummy(bitset(1, 6), raise_flag(2)), //FOUND Focus Dummy Flag
/*03*/recurse("nodes", bitset(2, 4, 11)), //{topdock, *content*, bottomdock} Nodes
/*04*/recurse("nodes", bitset(2, 5, 6, 7, 8, 9, 11)), //Workspaces Nodes
/*05*/invoice("num", &instance_workspace, bitset(7)),
/*06*/invoice("num", ¤t_workspace, bitset(2, 11), EXPR_FLAG::OR),
/*07*/dummy(bitset(5), raise_flag(7)), //FOUND Instance Dummy Flag
/*08*/recurse("floating_nodes", bitset(2, 7, 8, 9, 10, 11, 12)), //Container Nodes
/*09*/recurse("nodes", bitset(2, 7, 8, 9, 10, 11, 12)), //Container Nodes
/*10*/invoice("id", &instance_id, bitset(13)),
/*11*/predicate("focused", true, bitset(6), EXPR_FLAG::BOOL | raise_flag(2)),
/*12*/recurse("window_properties", bitset(13)), //Container Nodes window properties
/*13*/predicate("instance", inst, bitset(10, 7),
EXPR_FLAG::STRING_PREFIX | EXPR_FLAG::RAISE_RELATED)
};
uint64_t stub = tquery(reply, query, 14 /*length of query[]*/, bitset(0));
if ((stub & bitset(1, 6)) != bitset(1, 6)){
fprintf(stderr,"Could not find active output/workspace\n");
exit(1);
}
return i3Query{
.instance_workspace = instance_workspace,
.current_workspace = current_workspace,
.instance_found = (stub & bitset(10, 5)) == bitset(10, 5),
.instance_id = instance_id,
.output = rect_parse(output_rect)
};
}
Since when writing the query and checking the results you use the index of directives in the query array it encourages the use of magic numbers, is error prone and hard to make change. Although the performance is quite good for my and similar use cases, large json and small amount of extracted data required, I can't recommend Prediquery yet as really just a prototype.
Ideally a compile-time intermediate language would be written that if expressive enough would make Prediquery both easier and faster than parsing the JSON and walking the tree your self.