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;