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 }