1 2 module i3ipc.connection; 3 4 struct QueryConnection 5 { 6 private: 7 struct State 8 { 9 Socket socket; 10 11 ~this() 12 { 13 socket.close; 14 } 15 16 @disable this(this); 17 void opAssign(State) { assert(false); } 18 } 19 alias StateRef = RefCounted!(State, RefCountedAutoInitialize.no); 20 StateRef state; 21 22 JSONValue query(RequestType type, immutable(void)[] message = []) 23 { 24 state.socket.sendMessage(type, message); 25 return state.socket.receiveMessage(cast(ResponseType) type); 26 } 27 28 public: 29 this(UnixAddress address) 30 { 31 auto socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); 32 socket.connect(address); 33 state = StateRef(socket); 34 } 35 36 auto execute(string command) 37 { 38 return map!(v => fromJSON!CommandStatus(v))(query(RequestType.Command, command).array); 39 } 40 41 auto workspaces() 42 { 43 return map!(v => fromJSON!Workspace(v))(query(RequestType.GetWorkspaces).array); 44 } 45 46 auto outputs() 47 { 48 return map!(v => fromJSON!Output(v))(query(RequestType.GetOutputs).array); 49 } 50 51 auto tree() 52 { 53 return Container(query(RequestType.GetTree)); 54 } 55 56 auto marks() 57 { 58 return map!(v => v.str)(query(RequestType.GetMarks).array); 59 } 60 61 auto configuredBars() 62 { 63 return map!(v => v.str)(query(RequestType.GetBarConfig).array); 64 } 65 66 auto getBarConfig(string id) 67 { 68 return BarConfig(query(RequestType.GetBarConfig, id)); 69 } 70 71 auto version_() 72 { 73 return fromJSON!Version(query(RequestType.GetVersion)); 74 } 75 } 76 77 alias FiberedConnection = EventConnection!Fiber; 78 alias ThreadedConnection = EventConnection!Thread; 79 private struct EventConnection(Listener) 80 if (is(Listener == Thread) || is(Listener == Fiber)) 81 { 82 private: 83 Listener listener; 84 85 struct State 86 { 87 Socket socket; 88 Mutex mutex; 89 90 UnixAddress address; 91 92 EventCallback!(EventType.Workspace)[] WorkspaceCallbacks; 93 EventCallback!(EventType.Output)[] OutputCallbacks; 94 EventCallback!(EventType.Mode)[] ModeCallbacks; 95 EventCallback!(EventType.Window)[] WindowCallbacks; 96 EventCallback!(EventType.BarConfigUpdate)[] BarConfigUpdateCallbacks; 97 EventCallback!(EventType.Binding)[] BindingCallbacks; 98 99 ~this() 100 { socket.close; } 101 102 @disable this(this); 103 void opAssign(State) { assert(false); } 104 } 105 alias StateRef = RefCounted!(State, RefCountedAutoInitialize.no); 106 StateRef state; 107 108 void connect(UnixAddress address) 109 { 110 if (state.address != address) state.address = address; 111 state.socket.connect(address); 112 113 if (state.WorkspaceCallbacks.length > 0) { 114 state.socket.sendMessage(RequestType.Subscribe, JSONValue("Workspace").toString); 115 } 116 if (state.OutputCallbacks.length > 0) { 117 state.socket.sendMessage(RequestType.Subscribe, JSONValue("Output").toString); 118 } 119 if (state.ModeCallbacks.length > 0) { 120 state.socket.sendMessage(RequestType.Subscribe, JSONValue("Mode").toString); 121 } 122 if (state.WindowCallbacks.length > 0) { 123 state.socket.sendMessage(RequestType.Subscribe, JSONValue("Window").toString); 124 } 125 if (state.BarConfigUpdateCallbacks.length > 0) { 126 state.socket.sendMessage(RequestType.Subscribe, JSONValue("BarConfigUpdate").toString); 127 } 128 if (state.BindingCallbacks.length > 0) { 129 state.socket.sendMessage(RequestType.Subscribe, JSONValue("Binding").toString); 130 } 131 } 132 133 public: 134 this(UnixAddress address) 135 { 136 auto socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); 137 static if (is(Listener == Fiber)) socket.blocking = false; 138 state = StateRef(socket, new Mutex, address); 139 connect(state.address); 140 listener = new ListenerImpl(this); 141 static if (is(Listener == Thread)) listener.start; 142 } 143 144 static if (is(Listener == Fiber)) void dispatch() 145 { listener.call; } 146 147 template subscribe(string event) 148 { 149 enum Event = to!EventType(event); 150 void subscribe(EventCallback!Event cb) 151 { subscribe!Event(cb); } 152 } 153 154 void subscribe(EventType E)(EventCallback!E cb) 155 { 156 synchronized (state.mutex) { 157 if (0 == mixin("state.%sCallbacks.length".format(E))) { 158 state.socket.sendMessage(RequestType.Subscribe, JSONValue([E.toString]).toString); 159 } 160 mixin("state.%sCallbacks".format(E)) ~= cb; 161 } 162 } 163 164 static class ListenerImpl : Listener 165 { 166 alias Connection = EventConnection!Listener; 167 private Connection connection; 168 169 this(Connection connection) 170 { 171 this.connection = connection; 172 super(&listen); 173 } 174 175 void listen() 176 { 177 static if (is(Listener == Thread)) isDaemon = true; 178 179 try while (true) { 180 try 181 { 182 auto header = connection.state.socket.receiveExactly!Header; 183 auto payload = parseJSON(connection.state.socket.receiveExactly(new ubyte[header.payloadSize])); 184 185 if (EventMask & header.rawType) handle(header, payload); 186 else if (header.responseType == ResponseType.Subscribe) {} 187 else assert(0); 188 } 189 catch (SocketException e) { 190 info(e); 191 warning("Lost connection to i3, trying to reestablish in approximately 10 milliseconds"); 192 Thread.getThis.sleep(10.msecs); 193 connection.connect(connection.state.address); 194 } 195 } 196 catch (Exception e) { 197 error(e); 198 } 199 } 200 201 void handle(Header header, JSONValue payload) 202 { 203 switch (header.eventType) with(EventType) 204 { 205 case Workspace: 206 auto change = fromJSON!WorkspaceChange(payload["change"]); 207 auto current = Container(payload["current"]); 208 Nullable!Container old; 209 if (!payload["old"].isNull) old = Container(payload["old"]); 210 211 foreach (cb; connection.state.WorkspaceCallbacks) cb(change, current, old); 212 break; 213 case Output: 214 auto change = fromJSON!OutputChange(payload["change"]); 215 216 foreach (cb; connection.state.OutputCallbacks) cb(change); 217 break; 218 case Mode: 219 auto change = payload["change"].str; 220 auto pango_markup = JSON_TYPE.TRUE == payload["pango_markup"].type; 221 222 foreach (cb; connection.state.ModeCallbacks) cb(change, pango_markup); 223 break; 224 case Window: 225 auto change = fromJSON!WindowChange(payload["change"]); 226 auto container = Container(payload["container"]); 227 228 foreach (cb; connection.state.WindowCallbacks) cb(change, container); 229 break; 230 case BarConfigUpdate: 231 auto barConfig = BarConfig(payload); 232 233 foreach (cb; connection.state.BarConfigUpdateCallbacks) cb(barConfig); 234 break; 235 case Binding: 236 auto change = fromJSON!BindingChange(payload["change"]); 237 auto binding = fromJSON!(i3ipc.data.Binding)(payload["binding"]); 238 239 foreach (cb; connection.state.BindingCallbacks) cb(change, binding); 240 break; 241 default: assert(0); 242 } 243 } 244 } 245 } 246 247 UnixAddress getSessionIPCAddress() 248 { 249 auto result = execute(["i3", "--get-socketpath"]); 250 enforce(0 == result.status); 251 return new UnixAddress(result.output[0 .. $-1]); 252 } 253 254 template EventCallback(EventType T) if (T == EventType.Workspace) 255 { alias EventCallback = void delegate(WorkspaceChange, Container, Nullable!Container); } 256 template EventCallback(alias T) if (T == EventType.Output) 257 { alias EventCallback = void delegate(OutputChange); } 258 template EventCallback(alias T) if (T == EventType.Mode) 259 { alias EventCallback = void delegate(string, bool); } 260 template EventCallback(alias T) if (T == EventType.Window) 261 { alias EventCallback = void delegate(WindowChange, Container); } 262 template EventCallback(alias T) if (T == EventType.BarConfigUpdate) 263 { alias EventCallback = void delegate(BarConfig); } 264 template EventCallback(alias T) if (T == EventType.Binding) 265 { alias EventCallback = void delegate(BindingChange, Binding); } 266 267 268 import core.sync.mutex; 269 import core.thread; 270 271 import std.traits; 272 import std.exception; 273 274 import std.variant; 275 import std.typecons; 276 import std.range; 277 import std.algorithm; 278 import std.array; 279 280 import std.conv; 281 import std.format; 282 import std.json; 283 import std.experimental.logger; 284 285 import std.process; 286 import std.socket; 287 import std.stdio; 288 289 import i3ipc.protocol; 290 import i3ipc.socket; 291 import i3ipc.data;