Status output in JSON
Would it be possible to print status data in JSON format? Querying data would be much simpler than having to manually parse output which is error prone should status output change .
Comments (45)
-
repo owner -
repo owner -
repo owner - changed status to open
-
reporter It’s not worth the effort if I’m the only one asking. Only worried about future proofing. However, if others think the JSON flag is a good idea then I could take a stab at it. I'm using Go to parse the eye readable output.
Is there any guarantee that windows in the status output are ordered by stack order? Otherwise, I’ll have to order them by coordinates based on the layout.
-
repo owner Currently no, they’re looped over in order of initialization (with some caveats) but this is easily changed and might be a better idea.
I can say that I’ll be doing my best to make the status output as reliable and stable and possible, so you shouldn’t have to worry about changes, if there is more interest in this I can update the status and use JSON.
Very willing to take PRs on this
-
repo owner Hey this is the only outstanding issue currently so I’m working on it right now, haven’t decided if I’m gonna full swap or just add a flag as described above. I also wanted to correct myself and say that yes window order of printing will always be in the tiling order, it’s the same loop used for layouts (there is the floating windows but they can just be jumped over). This will also be the same situation in JSON, since it’s just a linked list and some numbers (nmaster, nstack) used as points to create “stacks“. A lot of the structures in dk are linked lists because otherwise we’d be doing a ton of memory moving, so order is guaranteed on clients, workspaces, monitors, rules, panels, and desktop windows. The order can change or be modified but the status output will always match the current order.
Just wanted to clarify the ordering is consistent.
-
repo owner Check out the latest commit, I’ve added support for JSON and a new
type=json
flag.dkcmd status type=json num=1 | jq
Would love your opinion on the setup and whether some of it should be tweaked, currently it looks like this (I prettied it up with jq) https://pastebin.com/aRgFF2G2
Cheers,
Nate
-
reporter Nice. I can massage the JSON for the shape I need. Will change my scripts to use the JSON output and provide feedback later this week.
-
reporter There are inconsistencies in use of snake_case in keys. Not a big deal, can be fixed when mapping JSON to an object in my script.
globals.smart_gap workspace.smartgap border.width window.w
Misspelling
client.tans_id => client.trans_id
Missing
workspace.layout
-
repo owner Ahh my bad I’ll get on those, I knew there had to be some and I’ve been messing with it myself (mostly in shell using jq) and I’m having issues pulling out say the first object from say
"clients"
I want to get"0"
but no luck so I tried changing the naming to"_0"
which has worked. Just wondering if you have issues with that or what would be your suggestion?
I’ll push a fix for the above mentioned issues shortly.
-
repo owner Ok I’ve fixed the mistakes you mentioned, changed any object names that were just a number to be
_num
, tried to align the value names with what you would use in dkcmd *(this is also the smartgap vs smart_gap)* so there have been a few more changesglobals -> global # these would all be under workspaces._N gappx -> gap nmaster -> master nstack -> stack smartgap -> smart_gap # applies to workspaces, monitors, and clients (its far more clear what it means) active -> focused # open to suggestions on this, would a list of the clients on the workspace be better? # with the focused client always as the first entry? Is this even useful? workspaces._N.active_window -> workspaces._N.window # debatably confusing but its everything currently focused (monitor->workspace->client) global.active_monitor -> global.focused
I think that covers most of my changes for now, gonna keep playing with it and see what you think.
-
reporter Can you include last focused window address?
-
repo owner Yea absolutely, I'm at work now so I'll add it this evening.
-
reporter Now that I’m using this, I think the extra flag is more flexible.
status type=full format=json status type=bar format=json
You have done the grunt work on how to do it. I should be able to add PRs for the other types I use.
-
repo owner Yea that’s not a bad idea at all, should be a fairly easy change. I’m gonna add the last focused window and go through and change any numbers that are really just booleans to be so.
eg. global.smart_gap – 0/1 → true/false
-
repo owner I have a question with the last focused client, it’s actually a bit more problematic than I originally expected, should this be the last focused window globally across workspace switches or just the last focused on the active workspace?
-
reporter Last focused in active workspace
-
repo owner Would it be beneficial to just have both of the lists within each workspace clients and stack (focus stack)?
Then the order of both would be clear or would this be cumbersome? It’s very easy to do in the code.
-
reporter Maybe
global.focused.workspace.last_window_address
This is a separate issue. If you’re adding functionality for tracking last window, it might be better to have a MRU stack across workspaces. That would allow building a super+tab script that behaves like ctrl+tab in editors, navigating through recently used windows efficiently.
-
repo owner I just pushed it, few changes
- workspace keys are now 0 indexed
- workspaces now have
clients
(tiling order) andfocus_stack
(0 is focused, 1 is last focused, and so on..) - everything that is treated as a boolean has been output as such
So to get what you want (last focused window on current workspace) do something like
global.focused.workspace.focus_stack._1.id
Let me know what you think and if you notice anything wrong.
-
reporter Working well. It might be better ergonomics if
focused
,clients
,focus_stack
,workspaces
were arrays instead of dictionaries. Again, not a big deal.I have to do this in Node to keep rest of code simple
/** Convert a dk indexed dictionary to array */ function toArray(obj) { return ( Object.entries(obj) // sort by keys .sort( (a, b) => parseInt(a[0].substring(1), 10) < parseInt(b[0].substring(1), 10) ) .map(([k, v]) => v) ) }
Full script
https://gist.github.com/mgutz/5c43c43f0f65d6b7b594c31785315a74
-
repo owner That’s a good suggestion, I thought about it but wasn’t sure so I went with objects, I’ll change it
-
repo owner Ok I just pushed the commit changing as much as possible to be arrays rather than just indexed keys (idk wtf I was thinking lol)
-
repo owner Just as a side note while reading your script - the ws command accepts some additional arguments to view/send/follow the nextne and prevne (ne = non-empty).
This might not be useful for your case just wanted to make sure.
-
reporter I’m a Mac to Linux convert. I’m emulating Mac dynamic workspace where if the current workspace is at the head or tail, prev/next moves to an empty workspace. I use super+1, super+2 to swap windows at tile index to master, as opposed to focusing absolute workspaces.
-
reporter Can you add pid to client data? I want to select a window from all windows across all workspaces in rofi. Need the pid to guess the icon.
-
repo owner Hmm yea I probably can fairly easily, right now we don’t store or care about the pid but I can implement NET_WM_PID
-
Thanks for the addition, doing some tests, I've run into an issue, which very well may be an edge case.
issue: empty client list
replicate: two monitors, workspace 1 on monitor 1 is empty, workspace 6 on monitor 2 has an open terminal
Unexpected character ',' on line 1. cratch":false,"callback":"","trans_id":"0x00000000"}]}}],"clients":[, ....................................................................^
I believe the issue would also come up if ran from a script via something like dmenu or rofi if there was nothing open as well.
-
reporter I ran into similar issue. Was browsing a URL with double quotes in the title. Malformed JSON.
Unexpected character 'M' in object on line 1. :"0x00000000"},{"id":"0x03c0002c","title":"python - Why do I get a "M ....................................................................^
-
repo owner Okay I just pushed the fix for titles containing double quotes
-
repo owner As for @Michael and the empty client list with a trailing comma, that shouldn’t be possible but can you repeat it and post the full json?
-
repo owner I also just added the client PID, totally forgot we’re already tracking it it wasn’t hard at all :D
-
reporter Thaks, verified the pid and escaped quotes. I noticed that when a window swallows/absorbs the terminal, the client JSON is still terminal metadata. For example, when I run obsidian from terminal
{ "id": "0x02600003", "pid": 132892, "title": "Authentication - notes - Obsidian v1.5.11", "class": "Alacritty", "instance": "Alacritty", "workspace": 1, "focused": false, "x": 1156, "y": 24, "w": 750, "h": 378, "bw": 3, "hoff": 0, "float": false, "full": false, "fakefull": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "callback": "", "trans_id": "0x00000000" }
xprop gives
M_STATE(WM_STATE): window state: Normal icon window: 0x0 _NET_WM_DESKTOP(CARDINAL) = 0 _NET_WM_USER_TIME(CARDINAL) = 98397223 WM_NORMAL_HINTS(WM_SIZE_HINTS): program specified location: 1156, 612 program specified minimum size: 200 by 150 program specified maximum size: 2147483647 by 2147483647 _MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x2, 0x0, 0x0, 0x0, 0x0 XdndAware(ATOM) = BITMAP _NET_WM_BYPASS_COMPOSITOR(CARDINAL) = 2 _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED(CARDINAL) = 1 WM_NAME(UTF8_STRING) = "CDrama - notes - Obsidian v1.5.11" _NET_WM_NAME(UTF8_STRING) = "CDrama - notes - Obsidian v1.5.11" WM_WINDOW_ROLE(STRING) = "browser-window" WM_CLASS(STRING) = "obsidian", "obsidian" _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL _NET_WM_PID(CARDINAL) = 133011 WM_CLIENT_MACHINE(STRING) = "endeavour" WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW, _NET_WM_PING, _NET_WM_SYNC_REQUEST
Is there a way to get the medata for the swallower (obsidian in this case)? Prefer to see obsidian icon in rofi window list.
-
repo owner Yea I can do it this evening, the way absorbing works is strange as we just take over the window but I should be able to update the class and instance like we do for the title. I can also include the client->absorbed which is another client.
Another good edge case find thank you.
-
repo owner Hey sorry I wasn’t able to get to this last night.
Pretty sure I’ve got it cleaned up and the clients now look like this (this one is absorbing)
{ "id": "0x05a00002", "pid": 12744, "title": "Fri-29-33.mp4 - mpv", "class": "mpv", "instance": "gl", "workspace": 1, "focused": false, "x": 1920, "y": 1440, "w": 1912, "h": 712, "bw": 4, "hoff": 0, "float": false, "full": false, "fakefull": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "callback": "", "trans_id": "0x00000000", "absorbed": { "id": "0x05800005", "pid": 17391, "title": "st - f", "class": "st-256color", "instance": "st-256color", "workspace": 1, "focused": false, "x": 0, "y": 0, "w": 3840, "h": 2160, "bw": 4, "hoff": 0, "float": false, "full": false, "fakefull": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "callback": "", "trans_id": "0x00000000", "absorbed": {} } }
-
reporter Pids seem to be switched
{ "id": "0x01c00003", "pid": 104936, "title": "Boxing - notes - Obsidian v1.5.11", "class": "obsidian", "instance": "obsidian", "[skipped]": "...", "absorbed": { "id": "0x01200003", "pid": 105043, "title": "~", "class": "Alacritty", "instance": "Alacritty", "[skipped]": "...", } } # process tree 104936 'alacritty' /usr/bin/alacritty 104951 'zsh' /usr/bin/zsh 105043 'electron' /usr/lib/electron28/electron 105046 'electron' /usr/lib/electron28/electron 105167 'electron' /usr/lib/electron28/electron 105047 'electron' /usr/lib/electron28/electron 105049 'electron' /usr/lib/electron28/electron 105169 'electron' /usr/lib/electron28/electron 105173 'electron' /usr/lib/electron28/electron
-
repo owner Yea we take over the entire client, I’ll update the pid quickly
-
repo owner Ok, pid should be fixed now as well
-
reporter working well, you can try the utility dkgo
# this is the command that required all the pid and absorbing meta dkgo rofi-master
-
repo owner Very nice, not gonna lie it’s great to see you taking advantage of the scriptability.
I’m gonna convert all of the status to JSON and rewrite the example scripts, just seems a lot better now. Once that’s done I think I’ll close this and tag v2.2
-
Sorry, have been terribly busy. Here is the full json output, the issue is in line 486. I prettied the output instead of just the json dump.
{ "global": { "numws": 9, "static_ws": true, "focus_mouse": true, "focus_open": true, "focus_urgent": true, "win_minwh": 50, "win_minxy": 10, "smart_border": false, "smart_gap": true, "tile_hints": true, "tile_tohead": false, "obey_motif": true, "layouts": [ "tile", "rtile", "mono", "grid", "spiral", "dwindle", "none", "tstack" ], "callbacks": [ "albumart", "popfull" ], "border": { "width": 5, "outer_width": 3, "focus": "0xff8aadf4", "urgent": "0xff2e3440", "unfocus": "0xff2e3440", "outer_focus": "0xff6c7086", "outer_urgent": "0xff2e3440", "outer_unfocus": "0xff2e3440" }, "focused": { "name": "HDMI-2", "number": 2, "focused": true, "x": 0, "y": 0, "w": 1360, "h": 768, "wx": 0, "wy": 0, "ww": 1360, "wh": 768, "workspace": { "name": "6", "number": 6, "focused": true, "monitor": "HDMI-2", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": true, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ], "focus_stack": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ] } } }, "workspaces": [ { "name": "1", "number": 1, "focused": false, "monitor": "eDP-1", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "2", "number": 2, "focused": false, "monitor": "HDMI-2", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "3", "number": 3, "focused": false, "monitor": "eDP-1", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "4", "number": 4, "focused": false, "monitor": "HDMI-2", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "5", "number": 5, "focused": false, "monitor": "eDP-1", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "6", "number": 6, "focused": true, "monitor": "HDMI-2", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": true, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ], "focus_stack": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ] }, { "name": "7", "number": 7, "focused": false, "monitor": "eDP-1", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "8", "number": 8, "focused": false, "monitor": "HDMI-2", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] }, { "name": "9", "number": 9, "focused": false, "monitor": "eDP-1", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] } ], "monitors": [ { "name": "eDP-1", "number": 1, "focused": false, "x": 1360, "y": 0, "w": 1920, "h": 1080, "wx": 1360, "wy": 41, "ww": 1920, "wh": 1039, "workspace": { "name": "1", "number": 1, "focused": false, "monitor": "eDP-1", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] } }, { "name": "HDMI-2", "number": 2, "focused": true, "x": 0, "y": 0, "w": 1360, "h": 768, "wx": 0, "wy": 0, "ww": 1360, "wh": 768, "workspace": { "name": "6", "number": 6, "focused": true, "monitor": "HDMI-2", "layout": "spiral", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": true, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ], "focus_stack": [ { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ] } } ], "clients": [ , { "id": "0x01e00005", "pid": 320939, "title": "dkcmd status type=full num=1 | tee ~/Documents/output.json", "class": "Alacritty", "instance": "Alacritty", "workspace": 6, "focused": true, "x": 5, "y": 5, "w": 1340, "h": 748, "bw": 5, "hoff": 0, "float": false, "full": false, "fakefull": false, "fixed": false, "sticky": false, "urgent": false, "above": false, "hidden": false, "scratch": false, "no_absorb": false, "callback": "", "transient": {}, "absorbed": {} } ], "rules": [ { "title": "", "class": "^(nitrogen|lxappearance|pavucontrol|headset|blueman|nm-applet|blueman-manager|blueman)$", "instance": "", "workspace": 0, "monitor": "", "x": -1, "y": -1, "w": -1, "h": -1, "float": true, "full": false, "fakefull": false, "sticky": false, "scratch": false, "focus": false, "ignore_cfg": false, "ignore_msg": false, "no_absorb": false, "callback": "", "xgrav": "", "ygrav": "" } ], "panels": [ { "id": "0x01600001", "class": "Polybar", "instance": "polybar", "x": 1370, "y": 5, "w": 1900, "h": 36, "l": 0, "r": 0, "t": 41, "b": 0, "monitor": { "name": "eDP-1", "number": 1, "focused": false, "x": 1360, "y": 0, "w": 1920, "h": 1080, "wx": 1360, "wy": 41, "ww": 1920, "wh": 1039, "workspace": { "name": "1", "number": 1, "focused": false, "monitor": "eDP-1", "layout": "tile", "master": 1, "stack": 3, "msplit": 0.50, "ssplit": 0.50, "gap": 5, "smart_gap": false, "pad_l": 5, "pad_r": 5, "pad_t": 5, "pad_b": 5, "clients": [], "focus_stack": [] } } } ], "desks": [] }
-
repo owner Hmmm that’s not great, I’m not even sure where this would happen. I think I can see the setup from the status but is there anything specific you did to create this?
-
repo owner nvm, I see what’s happening and I’m working on fixing it.
-
repo owner Should be fixed with the latest commit
-
repo owner - changed status to resolved
Please open a new issue if you experience issue with the new status
- Log in to comment
Yea it's definitely possible and it did cross my mind when implementing the status, is there a specific use case you have that would benefit from using JSON? As it stands it's a decent amount of work and additional code with little gain. I disagree with it being more simple than the current implementation, JSON is notoriously slow to parse and everything we're doing is text manipulation (inherently prone to change and failure) so the error bit is moot, many applications have worse interfaces even using JSON.
If anything I'd be willing to add an extra flag to status if we should output in JSON but so far you are the only person to bring it up. Do you have any experience with C and/or JSON? Collaboration would be great for something like this.
Cheers,
Nathaniel