1 
2 module i3ipc.data;
3 
4 import std.typecons : Nullable, Tuple;
5 import std.algorithm : map, joiner, each;
6 import std.array : array;
7 
8 import std.format : format;
9 import std.json : JSONValue, JSON_TYPE;
10 
11 struct Rectangle
12 {
13 	long x, y, width, height;
14 }
15 
16 T fromJSON(T)(JSONValue v) if (is(T == Rectangle))
17 {
18 	return Rectangle(
19 		v["x"].integer,
20 		v["y"].integer,
21 		v["width"].integer,
22 		v["height"].integer,
23 	);
24 }
25 
26 struct CommandStatus
27 {
28 	bool success;
29 	Nullable!string error;
30 
31 	string toString()
32 	{
33 		return "CommandStatus(%s, \"%s\")".format(success, error);
34 	}
35 }
36 
37 T fromJSON(T)(JSONValue v) if (is(T == CommandStatus))
38 {
39 	return CommandStatus(
40 		JSON_TYPE.TRUE == v["success"].type,
41 		"error" in v ? Nullable!string(v["error"].str) : Nullable!string.init
42 	);
43 }
44 
45 struct Workspace
46 {
47 	long num;
48 	string name;
49 	bool visible, focused, urgent;
50 	Rectangle rect;
51 	string output;
52 }
53 
54 T fromJSON(T)(JSONValue v) if (is(T == Workspace))
55 {
56 	return Workspace(
57 		v["num"].integer,
58 		v["name"].str,
59 		JSON_TYPE.TRUE == v["visible"].type,
60 		JSON_TYPE.TRUE == v["focused"].type,
61 		JSON_TYPE.TRUE == v["urgent"].type,
62 		fromJSON!Rectangle(v["rect"]),
63 		v["output"].str,
64 	);
65 }
66 
67 struct Output
68 {
69 	string name;
70 	bool active;
71 	Nullable!string current_workspace;
72 	Rectangle rect;
73 
74 	string toString()
75 	{
76 		return "Output(\"%s\", %s, \"%s\", %s)".format(name, active, current_workspace, rect);
77 	}
78 }
79 
80 T fromJSON(T)(JSONValue v) if (is(T == Output))
81 {
82 	return Output(
83 		v["name"].str,
84 		JSON_TYPE.TRUE == v["active"].type,
85 		v["current_workspace"].isNull ? Nullable!string.init : Nullable!string(v["current_workspace"].str),
86 		fromJSON!Rectangle(v["rect"])
87 	);
88 }
89 
90 struct Container
91 {
92 	long id;
93 	Type type;
94 	Nullable!string name;
95 	Border border;
96 	long current_border_width;
97 	Layout layout;
98 	Nullable!double percent;
99 	Rectangle rect, window_rect, deco_rect, geometry;
100 	Nullable!long window;
101 	bool urgent, focused;
102 
103 	Container[] nodes;
104 
105 	this(JSONValue json)
106 	{
107 		id = json["id"].integer;
108 		if (!json["name"].isNull) name = json["name"].str;
109 		switch (json["type"].str) {
110 			case "root": type = Type.Root; break;
111 			case "output": type = Type.Output; break;
112 			case "con": type = Type.Normal; break;
113 			case "floating_con": type = Type.Floating; break;
114 			case "workspace": type = Type.Workspace; break;
115 			case "dockarea": type = Type.Dockarea; break;
116 			default: assert(0);
117 		}
118 		switch (json["border"].str) {
119 			case "normal": border = Border.Normal; break;
120 			case "none": border = Border.None; break;
121 			case "pixel": border = Border.Pixel; break;
122 			default: assert(0);
123 		}
124 		current_border_width = json["current_border_width"].integer;
125 		switch (json["layout"].str) {
126 			case "splith": layout = Layout.Columns; break;
127 			case "splitv": layout = Layout.Rows; break;
128 			case "stacked": layout = Layout.Stacked; break;
129 			case "tabbed": layout = Layout.Tabbed; break;
130 			case "dockarea": layout = Layout.Dockarea; break;
131 			case "output": layout = Layout.Output; break;
132 			default: assert(0);
133 		}
134 		if (!json["percent"].isNull) percent = Nullable!double(json["percent"].floating);
135 		rect = fromJSON!Rectangle(json["rect"]);
136 		window_rect = fromJSON!Rectangle(json["window_rect"]);
137 		deco_rect = fromJSON!Rectangle(json["deco_rect"]);
138 		geometry = fromJSON!Rectangle(json["geometry"]);
139 		if (!json["window"].isNull) window = Nullable!long(json["window"].integer);
140 		urgent = JSON_TYPE.TRUE == json["urgent"].type;
141 		focused = JSON_TYPE.TRUE == json["focused"].type;
142 
143 		nodes = map!(json => Container(json))(json["nodes"].array).array;
144 	}
145 
146 	enum Type
147 	{
148 		Root,
149 		Output,
150 		Normal,
151 		Floating,
152 		Workspace,
153 		Dockarea
154 	}
155 
156 	enum Border
157 	{
158 		Normal,
159 		None,
160 		Pixel
161 	}
162 
163 	enum Layout
164 	{
165 		Rows,
166 		Columns,
167 		Stacked,
168 		Tabbed,
169 		Dockarea,
170 		Output
171 	}
172 
173 	string toString()
174 	{
175 		return "Container(%s %s \"%s\" %s %s %s %s %s %s %s %s %s %s %s [%s])".format(
176 			id,
177 			type,
178 			name,
179 
180 			border,
181 			current_border_width,
182 			layout,
183 			percent,
184 			rect, window_rect, deco_rect, geometry,
185 
186 			window,
187 			urgent, focused,
188 
189 			map!(node => node.toString)(nodes).joiner(", "));
190 	}
191 }
192 
193 struct BarConfig
194 {
195 	string id, status_command, font;
196 	Mode mode;
197 	Position position;
198 	bool workspace_buttons, binding_mode_indicator, verbose;
199 
200 	enum Mode
201 	{
202 		Dock,
203 		Hide
204 	}
205 
206 	enum Position
207 	{
208 		Bottom,
209 		Top
210 	}
211 
212 	private enum ConfigurableColors = [
213 		"background",
214 		"statusline",
215 		"separator",
216 		"focused_workspace_text",
217 		"focused_workspace_bg",
218 		"focused_workspace_border",
219 		"active_workspace_text",
220 		"active_workspace_bg",
221 		"active_workspace_border",
222 		"inactive_workspace_text",
223 		"inactive_workspace_bg",
224 		"inactive_workspace_border",
225 		"urgent_workspace_text",
226 		"urgent_workspace_bg",
227 		"urgent_workspace_border",
228 		"binding_mode_text",
229 		"binding_mode_bg",
230 		"binding_mode_border"];
231 
232 	mixin("Tuple!("
233 		~ ConfigurableColors.map!((string x) => "Nullable!string, \"%s\"".format(x)).joiner(",").array
234 		~ ") colors;");
235 
236 	this(JSONValue json)
237 	{
238 		id = json["id"].str;
239 		switch (json["mode"].str) {
240 			case "dock": mode = Mode.Dock; break;
241 			case "hide": mode = Mode.Hide; break;
242 			default: assert(0);
243 		}
244 		switch (json["position"].str) {
245 			case "top": position = Position.Top; break;
246 			case "bottom": position = Position.Bottom; break;
247 			default: assert(0);
248 		}
249 		status_command = json["status_command"].str;
250 		font = json["font"].str;
251 
252 		workspace_buttons = JSON_TYPE.TRUE == json["workspace_buttons"].type;
253 		binding_mode_indicator = JSON_TYPE.TRUE == json["binding_mode_indicator"].type;
254 		verbose = JSON_TYPE.TRUE == json["verbose"].type;
255 
256 		auto colors_json = json["colors"];
257 		mixin(ConfigurableColors.map!(x => "if (\"%1$s\" in colors_json) colors.%1$s = colors_json[\"%1$s\"].str;\n".format(x)).joiner.array);
258 	}
259 
260 	string toString()
261 	{
262 		import std.range : repeat;
263 		mixin(
264 			"return \"BarConfig(" ~ "%s".repeat(26).joiner(" ").array ~ ")\".format(
265 				id, status_command, font,
266 				mode,
267 				position,
268 				workspace_buttons, binding_mode_indicator, verbose,"
269 			~ ConfigurableColors.map!(x => "colors.%s".format(x)).joiner(",\n").array
270 			~ ");");
271 	}
272 }
273 
274 struct Version
275 {
276 	long major, minor, patch;
277 	string human_readable, loaded_config_file_name;
278 }
279 
280 T fromJSON(T)(JSONValue v) if (is(T == Version))
281 {
282 	return Version(
283 		v["major"].integer,
284 		v["minor"].integer,
285 		v["patch"].integer,
286 		v["human_readable"].str,
287 		v["loaded_config_file_name"].str
288 	);
289 }
290 
291 enum WorkspaceChange
292 {
293 	Focus,
294 	Init,
295 	Empty,
296 	Urgent
297 }
298 
299 T fromJSON(T)(JSONValue v) if (is(T == WorkspaceChange))
300 {
301 	switch (v.str) {
302 		case "focus": return WorkspaceChange.Focus;
303 		case "init": return WorkspaceChange.Init;
304 		case "empty": return WorkspaceChange.Empty;
305 		case "urgent": return WorkspaceChange.Urgent;
306 		default: assert(0);
307 	}
308 }
309 
310 enum OutputChange
311 {
312 	Unspecified
313 }
314 
315 T fromJSON(T)(JSONValue v) if (is(T == OutputChange))
316 {
317 	switch (v.str) {
318 		case "unspecified": return OutputChange.Unspecified;
319 		default: assert(0);
320 	}
321 }
322 
323 enum WindowChange
324 {
325 	New,
326 	Close,
327 	Focus,
328 	Title,
329 	Fullscreen,
330 	Move,
331 	Floating,
332 	Urgent
333 }
334 
335 T fromJSON(T)(JSONValue v) if (is(T == WindowChange))
336 {
337 	switch (v.str) {
338 		case "new": return WindowChange.New;
339 		case "close": return WindowChange.Close;
340 		case "focus": return WindowChange.Focus;
341 		case "title": return WindowChange.Title;
342 		case "fullscreen_mode": return WindowChange.Fullscreen;
343 		case "move": return WindowChange.Move;
344 		case "floating": return WindowChange.Floating;
345 		case "urgent": return WindowChange.Urgent;
346 		default: assert(0);
347 	}
348 }
349 
350 enum BindingChange
351 {
352 	Run
353 }
354 
355 T fromJSON(T)(JSONValue v) if (is(T == BindingChange))
356 {
357 	switch (v.str) {
358 		case "run": return BindingChange.Run;
359 		default: assert(0);
360 	}
361 }
362 
363 enum InputType
364 {
365 	Keyboard,
366 	Mouse
367 }
368 
369 T fromJSON(T)(JSONValue v) if (is(T == InputType))
370 {
371 	switch (v.str) {
372 		case "keyboard": return InputType.Keyboard;
373 		case "mouse": return InputType.Mouse;
374 		default: assert(0);
375 	}
376 }
377 
378 struct Binding
379 {
380 	string command;
381 	string[] event_state_mask;
382 	long input_code;
383 	Nullable!string symbol;
384 	InputType input_type;
385 
386 	string toString()
387 	{
388 		return "Binding(\"%s\", %s, %u, \"%s\", %s)".format(command, event_state_mask, input_code, symbol, input_type);
389 	}
390 }
391 
392 T fromJSON(T)(JSONValue v) if (is(T == Binding))
393 {
394 	return Binding(
395 		v["command"].str,
396 		map!(v => v.str)(v["event_state_mask"].array).array,
397 		v["input_code"].integer,
398 		v["symbol"].isNull ? Nullable!string.init : Nullable!string(v["symbol"].str),
399 		fromJSON!InputType(v["input_type"])
400 	);
401 }