1 /*
2 
3   Copyright (c) 2003-2013 uim Project https://github.com/uim/uim
4 
5   All rights reserved.
6 
7   Redistribution and use in source and binary forms, with or without
8   modification, are permitted provided that the following conditions
9   are met:
10 
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in the
15      documentation and/or other materials provided with the distribution.
16   3. Neither the name of authors nor the names of its contributors
17      may be used to endorse or promote products derived from this software
18      without specific prior written permission.
19 
20   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30   SUCH DAMAGE.
31 */
32 
33 // connection management
34 
35 #ifdef HAVE_CONFIG_H
36 # include <config.h>
37 #endif
38 
39 #include "connection.h"
40 
41 #include <csignal>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <list>
45 #include <map>
46 #include <unistd.h>
47 #include <X11/Xatom.h>
48 #ifdef HAVE_ALLOCA_H
49 # include <alloca.h>
50 #endif
51 
52 #define INIT_BUF_SIZE 1024
53 #define TRANSPORT_UNIT 20
54 #define TRANSPORT_MAX 20	// Emacs's XIM won't work correctly when the value is 100
55 
56 #ifndef HAVE_SIG_T
57 typedef void (*sig_t)(int);
58 #endif
59 
60 extern char *xim_packet_name[];
61 
62 static std::map<Window, XConnection *> gXConnections;
63 static Atom xim_xconnect;
64 static Atom xim_protocol;
65 static Atom xim_moredata;
66 static Atom uim_comm;
67 
68 void
setupNewConnection(XClientMessageEvent * ev)69 XimServer::setupNewConnection(XClientMessageEvent *ev)
70 {
71     XClientMessageEvent r;
72     Window comWin, clientWin;
73 
74     comWin = XCreateSimpleWindow(XimServer::gDpy, ev->window,
75 				 0, 0, 1, 1, 0, 0, 0);
76     clientWin = ev->data.l[0];
77 
78     r.type = ClientMessage;
79     r.window = clientWin;
80     r.message_type = xim_xconnect;
81     r.format = 32;
82     r.data.l[0] = comWin;
83     r.data.l[1] = 0;
84     r.data.l[2] = 2;
85     r.data.l[3] = TRANSPORT_MAX;
86     XSendEvent(XimServer::gDpy, clientWin, False, NoEventMask, (XEvent *)&r);
87 
88     XConnection *xc = new XConnection(this, clientWin, comWin);
89     std::pair<Window, XConnection *> p(comWin, xc);
90     gXConnections.insert(p);
91 }
92 
93 static void
reapOldConnection()94 reapOldConnection()
95 {
96     std::map<Window, XConnection *>::iterator it;
97     for (it = gXConnections.begin(); it != gXConnections.end(); ++it) {
98 	XConnection *xc = (*it).second;
99 	if (!xc->isValid()) {
100 	    delete xc;
101 	    gXConnections.erase(it);
102 	    return;
103 	}
104     }
105 }
106 
107 void
procXClientMessage(XClientMessageEvent * ev)108 procXClientMessage(XClientMessageEvent *ev)
109 {
110     // check connection request
111     XimServer *xs = XimServer::findServer(ev->window);
112     if (xs && ev->message_type == xim_xconnect) {
113 	xs->setupNewConnection(ev);
114 	reapOldConnection();
115 	return;
116     }
117 
118     // process xim packet
119     if (ev->message_type != xim_moredata &&
120 	ev->message_type != xim_protocol) {
121 	printf("non xim message\n");
122 	return;
123     }
124     std::map<Window, XConnection *>::iterator i;
125     i = gXConnections.find(ev->window);
126     if (i == gXConnections.end()) {
127 	printf("packet for unknown window\n");
128 	return;
129     }
130 
131     XConnection *xc = (*i).second;
132     xc->readProc(ev);
133 }
134 
XConnection(XimServer * svr,Window clientWin,Window commWin)135 XConnection::XConnection(XimServer *svr, Window clientWin, Window commWin) :
136     Connection(svr)
137 {
138     mClientWin = clientWin;
139     mCommWin = commWin;
140     mBuf.buf = (char *)malloc(INIT_BUF_SIZE);
141     mBuf.len = 0;
142     mBuf.size = INIT_BUF_SIZE;
143     add_window_watch(mClientWin, this, STRUCTURE_NOTIFY_MASK);
144     mIsValid = true;
145 }
146 
~XConnection()147 XConnection::~XConnection()
148 {
149     if (mIsValid)
150 	remove_window_watch(mClientWin);
151     XDestroyWindow(XimServer::gDpy, mCommWin);
152     free(mBuf.buf);
153 }
154 
destroy(Window)155 void XConnection::destroy(Window /* w */)
156 {
157     if (mIsValid)
158 	OnClose();
159     mIsValid = false;
160 }
161 
readProc(XClientMessageEvent * ev)162 void XConnection::readProc(XClientMessageEvent *ev)
163 {
164     if (!readToBuf(ev))
165 	return;
166 
167     checkByteorder();
168 
169     bool pushed = true;
170     do {
171 	int len = -1;
172 	if (mBuf.len >= 4)
173 	    len = RxPacket::getPacketLength((unsigned char *)mBuf.buf,
174 					    mByteorder);
175 
176 	if ((len > 4 && len <= mBuf.len) ||
177 	    (len == 4 && mBuf.buf[0] == XIM_DISCONNECT)) {
178 	    RxPacket *p =
179 		createRxPacket((unsigned char *)mBuf.buf, mByteorder);
180 	    shiftBuffer(len);
181 	    mRxQ.push_back(p);
182 	} else if (len == 4) {
183 	    // do nothing
184 	    shiftBuffer(4);
185 	} else {
186 	    pushed = false;
187 	}
188     } while (pushed);
189     OnRecv();
190 
191     writeProc();
192 }
193 
shiftBuffer(int len)194 void XConnection::shiftBuffer(int len)
195 {
196     memmove(mBuf.buf, &mBuf.buf[len], mBuf.len - len);
197     mBuf.len -= len;
198 }
199 
checkByteorder()200 bool XConnection::checkByteorder()
201 {
202     if (mByteorder == BYTEORDER_UNKNOWN) {
203 	if (mBuf.buf[0] != XIM_CONNECT) {
204 	    printf("not XIM_CONNECT\n");
205 	    return false;
206 	}
207 	if (mBuf.buf[4] == 0x42)
208 	    mByteorder = MSB_FIRST;
209 	else if (mBuf.buf[4] == 0x6c)
210 	    mByteorder = LSB_FIRST;
211 	else
212 	    return false;
213     }
214     return true;
215 }
216 
readToBuf(XClientMessageEvent * ev)217 bool XConnection::readToBuf(XClientMessageEvent *ev)
218 {
219     if (ev->format == 32 && ev->message_type == xim_protocol) {
220 	// indirect
221 	int offset = 0;
222 	Atom type;
223 	int format;
224 	unsigned long nrItems;
225 	unsigned long remain;
226 	char *data;
227 
228 	while (ev->data.l[0] >= mBuf.size) {
229 	    mBuf.size += 1024;
230 	    mBuf.buf = (char *)realloc(mBuf.buf, mBuf.size);
231 	}
232 	do {
233 	    XGetWindowProperty(XimServer::gDpy, ev->window, ev->data.l[1],
234 			       offset, mBuf.size - mBuf.len, True,
235 			       AnyPropertyType,
236 			       &type, &format, &nrItems, &remain,
237 			       (unsigned char **)(uintptr_t)&data);
238 	    if (!data)
239 		return false;
240 
241 	    if (format == 8) {
242 		memcpy(&mBuf.buf[mBuf.len], data, nrItems);
243 		mBuf.len += static_cast<int>(nrItems);
244 	    } else
245 		return false;
246 	    XFree(data);
247 	} while (remain > 0);
248     } else if (ev->format == 8) {
249 	// direct
250 	if ((mBuf.len + TRANSPORT_UNIT) >= mBuf.size) {
251 	    mBuf.size += TRANSPORT_UNIT;
252 	    mBuf.buf = (char *)realloc(mBuf.buf, mBuf.size);
253 	}
254 	memcpy(&mBuf.buf[mBuf.len], ev->data.b, TRANSPORT_UNIT);
255 	mBuf.len += TRANSPORT_UNIT;
256     } else
257 	return false;
258 
259     return true;
260 }
261 
writePendingPacket()262 void XConnection::writePendingPacket()
263 {
264     std::list<TxPacket *>::iterator i, j;
265     bool sent_preedit_done = false;
266     C8 major;
267 
268     while (!mPendingTxQ.empty()) {
269 	if (hasSyncFlag() || hasPreeditStartSyncFlag() ||
270 			hasPreeditCaretSyncFlag())
271 	    break;
272 
273 	i = mPendingTxQ.begin();
274 	major = (*i)->get_major();
275 
276 	switch (major) {
277 	case XIM_COMMIT:
278 	case XIM_FORWARD_EVENT:
279 	    setSyncFlag();
280 	    break;
281 	case XIM_PREEDIT_START:
282 	    setPreeditStartSyncFlag();
283 	    break;
284 	case XIM_PREEDIT_CARET:
285 	    setPreeditCaretSyncFlag();
286 	    break;
287 	case XIM_PREEDIT_DONE:
288 	    sent_preedit_done = true;
289 	default:
290 	    break;
291 	}
292 
293 	doSend(*i, true);
294 	delete *i;
295 	mPendingTxQ.pop_front();
296 
297  	// XIM_PREEDIT_START/DRAW just after XIM_PREEDIT_DONE maybe
298 	// need to wait XIM_COMMIT and its XIM_SYNC_REPLY
299 	if (sent_preedit_done == true) {
300 	    // push first XIM_COMMIT into the head of the queue
301 	    std::list<TxPacket *> tmp;
302 	    bool first = true;
303 	    while (!mPendingTxQ.empty()) {
304 		j = mPendingTxQ.begin();
305 		major = (*j)->get_major();
306 		if (major == XIM_COMMIT && first == true) {
307 		    tmp.push_front(*j);
308 		    first = false;
309 		} else
310 		    tmp.push_back(*j);
311 
312 		mPendingTxQ.pop_front();
313 	    }
314 	    mPendingTxQ = tmp;
315 	    sent_preedit_done = false;
316 	}
317     }
318 }
319 
writePassivePacket()320 void XConnection::writePassivePacket()
321 {
322     std::list<TxPacket *>::iterator i, j;
323     bool sent_preedit_done = false;
324     C8 major;
325 
326     while (!mPTxQ.empty()) {
327 	if (!mPendingTxQ.empty())
328 	    break;
329 
330 	i = mPTxQ.begin();
331 
332 	if (hasSyncFlag() || hasPreeditStartSyncFlag() ||
333 			hasPreeditCaretSyncFlag()) {
334 	    mPendingTxQ.push_back(*i);
335 	    mPTxQ.pop_front();
336 	    break;
337 	}
338 
339 	major = (*i)->get_major();
340 	switch (major) {
341 	case XIM_COMMIT:
342 	    setSyncFlag();
343 	    break;
344 	case XIM_PREEDIT_START:
345 	    setPreeditStartSyncFlag();
346 	    break;
347 	case XIM_PREEDIT_CARET:
348 	    setPreeditCaretSyncFlag();
349 	    break;
350 	case XIM_PREEDIT_DONE:
351 	    sent_preedit_done = true;
352 	    break;
353 	default:
354 	    break;
355 	}
356 
357 	doSend(*i, true);
358 	delete *i;
359 	mPTxQ.pop_front();
360 
361 	// XIM_PREEDIT_START/DRAW just after XIM_PREEDIT_DONE maybe
362 	// need to wait XIM_COMMIT and its XIM_SYNC_REPLY
363 	if (sent_preedit_done == true) {
364 	    // push first XIM_COMMIT into the head of passive queue
365 	    std::list<TxPacket *> tmp;
366 	    bool first = true;
367 	    while (!mPTxQ.empty()) {
368 		j = mPTxQ.begin();
369 		major = (*j)->get_major();
370 		if (major == XIM_COMMIT && first == true) {
371 		    tmp.push_front(*j);
372 		    first = false;
373 		} else
374 		    tmp.push_back(*j);
375 
376 		mPTxQ.pop_front();
377 	    }
378 	    mPTxQ = tmp;
379 	    sent_preedit_done = false;
380 	}
381     }
382 }
383 
writeNormalPacket()384 void XConnection::writeNormalPacket()
385 {
386     std::list<TxPacket *>::iterator i;
387     C8 major;
388 
389     while (!mTxQ.empty()) {
390 	i = mTxQ.begin();
391 	major = (*i)->get_major();
392 	if (major == XIM_FORWARD_EVENT) {
393 	    if (hasSyncFlag()) {
394 		// move this packet to pending queue
395 		mPendingTxQ.push_back(*i);
396 		mTxQ.pop_front();
397 		continue;
398 	    }
399 	    setSyncFlag();
400 	}
401 	doSend(*i, false);
402 	delete *i;
403 	mTxQ.pop_front();
404     }
405 }
406 
writeProc()407 void XConnection::writeProc()
408 {
409     OnSend(); // add XIM_COMMIT packet to passive queue
410 
411     writePendingPacket();
412     writePassivePacket();
413     writeNormalPacket();
414 
415     // interrupt while _XFlushInt() here will cause lock up of the display.
416     sig_t old_sigusr1 = signal(SIGUSR1, SIG_IGN);
417     XFlush(XimServer::gDpy);
418     signal(SIGUSR1, old_sigusr1);
419 
420     if (mIsCloseWait) {
421 	remove_window_watch(mClientWin);
422 	mClientWin = None;
423 	mIsValid = false;
424 	OnClose();
425     }
426 }
427 
doSend(TxPacket * t,bool passive)428 void XConnection::doSend(TxPacket *t, bool passive)
429 {
430     if (g_option_mask & OPT_TRACE_XIM) {
431 	if (passive)
432 	    printf("(->): %s.\n", xim_packet_name[t->get_major()]);
433 	else
434 	    printf("->: %s.\n", xim_packet_name[t->get_major()]);
435     }
436 
437     XClientMessageEvent r;
438     int buflen;
439     char *buf;
440 
441     buflen = t->get_length();
442     buf = (char *)alloca(buflen);
443     t->write_to_buf((unsigned char *)buf, buflen, mByteorder);
444     if (buflen < TRANSPORT_MAX) {
445 	// via event
446 	int offset = 0;
447 	int length = buflen;
448 	while (length > TRANSPORT_UNIT) {
449 	    r.type = ClientMessage;
450 	    r.window = mClientWin;
451 	    r.format = 8;
452 	    r.message_type = xim_moredata;
453 	    memcpy(r.data.b, &buf[offset], TRANSPORT_UNIT);
454 	    XSendEvent(XimServer::gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
455 	    offset += TRANSPORT_UNIT;
456 	    length -= TRANSPORT_UNIT;
457 	}
458 	r.type = ClientMessage;
459 	r.window = mClientWin;
460 	r.format = 8;
461 	r.message_type = xim_protocol;
462 	memset(r.data.b, 0, TRANSPORT_UNIT);
463 	memcpy(r.data.b, &buf[offset], length);
464 	XSendEvent(XimServer::gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
465     } else {
466 	// via property
467 	r.type = ClientMessage;
468 	r.window = mClientWin;
469 	r.format = 32;
470 	r.message_type = xim_protocol;
471 	r.data.l[0] = buflen;
472 	r.data.l[1] = uim_comm;
473 	XChangeProperty(XimServer::gDpy, mClientWin, uim_comm, XA_STRING,
474 			8, PropModeAppend, (unsigned char *)buf, buflen);
475 	XSendEvent(XimServer::gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
476     }
477 }
478 
connection_setup()479 int connection_setup()
480 {
481     xim_xconnect = XInternAtom(XimServer::gDpy, "_XIM_XCONNECT", False);
482     xim_protocol = XInternAtom(XimServer::gDpy, "_XIM_PROTOCOL", False);
483     xim_moredata = XInternAtom(XimServer::gDpy, "_XIM_MOREDATA", False);
484     uim_comm = XInternAtom(XimServer::gDpy, "UIM_COMM", False);
485     return 0;
486 }
487 /*
488  * Local variables:
489  *  c-indent-level: 4
490  *  c-basic-offset: 4
491  * End:
492  */
493