1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "unix/ibus/selection_monitor.h"
31
32 #include <xcb/xcb.h>
33 #include <xcb/xfixes.h>
34
35 #include <cstdlib>
36 #include <memory>
37 #include <string>
38
39 #include "base/logging.h"
40 #include "base/mutex.h"
41 #include "base/thread.h"
42 #include "base/port.h"
43 #include "base/util.h"
44
45 namespace mozc {
46 namespace ibus {
47
48 namespace {
49
50 using std::unique_ptr;
51
52 class ScopedXcbGenericError {
53 public:
ScopedXcbGenericError()54 ScopedXcbGenericError()
55 : error_(NULL) {
56 }
~ScopedXcbGenericError()57 ~ScopedXcbGenericError() {
58 free(error_);
59 error_ = NULL;
60 }
get() const61 const xcb_generic_error_t *get() const {
62 return error_;
63 }
mutable_get()64 xcb_generic_error_t **mutable_get() {
65 return &error_;
66 }
67
68 private:
69 xcb_generic_error_t *error_;
70 };
71
72 template <typename T>
73 struct FreeDeleter {
operator ()mozc::ibus::__anone74343f50111::FreeDeleter74 void operator()(T *ptr) const {
75 free(ptr);
76 }
77 };
78
79 // TODO(yukawa): Use template aliases when GCC 4.6 is retired.
80 typedef unique_ptr<xcb_get_property_reply_t,
81 FreeDeleter<xcb_get_property_reply_t>>
82 ScopedXcbGetPropertyReply;
83 typedef unique_ptr<xcb_get_atom_name_reply_t,
84 FreeDeleter<xcb_get_atom_name_reply_t>>
85 ScopedXcbGetAtomNameReply;
86 typedef unique_ptr<xcb_intern_atom_reply_t,
87 FreeDeleter<xcb_intern_atom_reply_t>>
88 ScopedXcbInternAtomReply;
89 typedef unique_ptr<xcb_xfixes_query_version_reply_t,
90 FreeDeleter<xcb_xfixes_query_version_reply_t>>
91 ScopedXcbXFixesQueqyVersionReply;
92
93 struct XcbAtoms {
94 xcb_atom_t mozc_selection_monitor;
95 xcb_atom_t net_wm_name;
96 xcb_atom_t net_wm_pid;
97 xcb_atom_t utf8_string;
98 xcb_atom_t wm_client_machine;
XcbAtomsmozc::ibus::__anone74343f50111::XcbAtoms99 XcbAtoms()
100 : mozc_selection_monitor(XCB_NONE),
101 net_wm_name(XCB_NONE),
102 net_wm_pid(XCB_NONE),
103 utf8_string(XCB_NONE),
104 wm_client_machine(XCB_NONE) {
105 }
106 };
107
108 class SelectionMonitorServer {
109 public:
SelectionMonitorServer()110 SelectionMonitorServer()
111 : connection_(NULL),
112 requestor_window_(0),
113 root_window_(0),
114 xfixes_first_event_(0),
115 xcb_maximum_request_len_(0) {
116 }
117
~SelectionMonitorServer()118 ~SelectionMonitorServer() {
119 Release();
120 }
121
Init()122 bool Init() {
123 connection_ = ::xcb_connect(NULL, NULL);
124 if (connection_ == NULL) {
125 return false;
126 }
127
128 if (!InitXFixes()) {
129 Release();
130 return false;
131 }
132
133 if (!InitAtoms()) {
134 Release();
135 return false;
136 }
137
138 const xcb_screen_t *screen =
139 ::xcb_setup_roots_iterator(::xcb_get_setup(connection_)).data;
140
141 requestor_window_ = ::xcb_generate_id(connection_);
142 const uint32 mask = XCB_CW_EVENT_MASK;
143 const uint32 values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
144 root_window_ = screen->root;
145 ::xcb_create_window(connection_, screen->root_depth,
146 requestor_window_, root_window_,
147 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
148 screen->root_visual, mask, values);
149 const uint32 xfixes_mask =
150 XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
151 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
152 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
153 ::xcb_xfixes_select_selection_input_checked(
154 connection_, requestor_window_, XCB_ATOM_PRIMARY, xfixes_mask);
155 ::xcb_flush(connection_);
156 return true;
157 }
158
checkConnection()159 bool checkConnection() {
160 if (!connection_) {
161 return false;
162 }
163 if (::xcb_connection_has_error(connection_)) {
164 LOG(ERROR) << "XCB connection has error.";
165 connection_ = NULL;
166 return false;
167 }
168 return true;
169 }
170
WaitForNextSelectionEvent(size_t max_bytes,SelectionInfo * next_info)171 bool WaitForNextSelectionEvent(size_t max_bytes, SelectionInfo *next_info) {
172 DCHECK(next_info);
173 if (!connection_) {
174 return false;
175 }
176
177 ::xcb_flush(connection_);
178 unique_ptr<xcb_generic_event_t, void (*)(void*)> event(
179 ::xcb_wait_for_event(connection_), &std::free);
180
181 if (event.get() == NULL) {
182 LOG(ERROR) << "NULL event returned.";
183 return false;
184 }
185
186 const uint32_t response_type = (event->response_type & ~0x80);
187
188 if (response_type ==
189 (xfixes_first_event_ + XCB_XFIXES_SELECTION_NOTIFY)) {
190 return OnXFixesSelectionNotify(event.get(), max_bytes, next_info);
191 }
192
193 if (response_type != XCB_SELECTION_NOTIFY) {
194 VLOG(2) << "Ignored a message. response_type: " << response_type;
195 return false;
196 }
197
198 return OnSelectionNotify(event.get(), max_bytes, next_info);
199 }
200
201 // Sends a harmless message to the |requestor_window_|. You can call this
202 // method to awake a message pump thread which is waiting for the next
203 // X11 message for |requestor_window_|.
SendNoopEventMessage()204 void SendNoopEventMessage() {
205 if (!connection_ || !requestor_window_) {
206 return;
207 }
208
209 // Send a dummy event so that the event pump can wake up.
210 xcb_client_message_event_t event = {};
211 event.response_type = XCB_CLIENT_MESSAGE;
212 event.window = requestor_window_;
213 event.format = 32;
214 event.type = XCB_NONE;
215 ::xcb_send_event(connection_, false, requestor_window_,
216 XCB_EVENT_MASK_NO_EVENT,
217 reinterpret_cast<const char *>(&event));
218 ::xcb_flush(connection_);
219 }
220
221 private:
Release()222 void Release() {
223 if (connection_) {
224 ::xcb_disconnect(connection_);
225 connection_ = NULL;
226 }
227 }
228
CreateAtom(const string name,xcb_atom_t * atom) const229 bool CreateAtom(const string name, xcb_atom_t *atom) const {
230 DCHECK(atom);
231 *atom = XCB_NONE;
232 xcb_intern_atom_cookie_t cookie =
233 ::xcb_intern_atom(connection_, false, name.size(), name.c_str());
234 ScopedXcbInternAtomReply reply(
235 ::xcb_intern_atom_reply(connection_, cookie, 0));
236 if (reply.get() == NULL) {
237 LOG(ERROR) << "xcb_intern_atom_reply returned NULL reply.";
238 return false;
239 }
240 if (reply->atom == XCB_NONE) {
241 return false;
242 }
243 *atom = reply->atom;
244 return true;
245 }
246
InitAtoms()247 bool InitAtoms() {
248 return
249 CreateAtom("MOZC_SEL_MON", &atoms_.mozc_selection_monitor) &&
250 CreateAtom("UTF8_STRING", &atoms_.utf8_string) &&
251 CreateAtom("_NET_WM_NAME", &atoms_.net_wm_name) &&
252 CreateAtom("_NET_WM_PID", &atoms_.net_wm_pid) &&
253 CreateAtom("WM_CLIENT_MACHINE", &atoms_.wm_client_machine);
254 }
255
InitXFixes()256 bool InitXFixes() {
257 const xcb_query_extension_reply_t
258 *ext_reply = ::xcb_get_extension_data(connection_, &xcb_xfixes_id);
259 if (ext_reply == NULL) {
260 LOG(ERROR) << "xcb_get_extension_data returns NULL.";
261 return false;
262 }
263
264 const xcb_xfixes_query_version_cookie_t xfixes_query_cookie =
265 xcb_xfixes_query_version(
266 connection_,
267 XCB_XFIXES_MAJOR_VERSION,
268 XCB_XFIXES_MINOR_VERSION);
269 ScopedXcbGenericError xcb_error;
270 ScopedXcbXFixesQueqyVersionReply xfixes_query(
271 ::xcb_xfixes_query_version_reply(
272 connection_, xfixes_query_cookie, xcb_error.mutable_get()));
273 if (xcb_error.get() != NULL) {
274 LOG(ERROR) << "xcb_xfixes_query_version_reply failed. error_code: "
275 << static_cast<uint32>(xcb_error.get()->error_code);
276 return false;
277 }
278 if (xfixes_query.get() == NULL) {
279 return false;
280 }
281
282 xfixes_first_event_ = ext_reply->first_event;
283 LOG(INFO) << "XFixes ver: " << xfixes_query->major_version
284 << "." << xfixes_query->major_version
285 << ", first_event: " << xfixes_first_event_;
286
287 xcb_maximum_request_len_ = ::xcb_get_maximum_request_length(connection_);
288 if (xcb_maximum_request_len_ <= 0) {
289 LOG(ERROR) << "Unexpected xcb maximum request length: "
290 << xcb_maximum_request_len_;
291 return false;
292 }
293
294 return true;
295 }
296
GetAtomName(xcb_atom_t atom) const297 string GetAtomName(xcb_atom_t atom) const {
298 const xcb_get_atom_name_cookie_t cookie = ::xcb_get_atom_name(
299 connection_, atom);
300 ScopedXcbGenericError xcb_error;
301 ScopedXcbGetAtomNameReply reply(
302 ::xcb_get_atom_name_reply(
303 connection_, cookie, xcb_error.mutable_get()));
304 if (xcb_error.get() != NULL) {
305 LOG(ERROR) << "xcb_get_atom_name_reply failed. error_code: "
306 << static_cast<uint32>(xcb_error.get()->error_code);
307 return "";
308 }
309 if (reply.get() == NULL) {
310 VLOG(2) << "reply is NULL";
311 return "";
312 }
313
314 const char *ptr = ::xcb_get_atom_name_name(reply.get());
315 const size_t len = ::xcb_get_atom_name_name_length(reply.get());
316 return string(ptr, len);
317 }
318
GetByteArrayProperty(xcb_window_t window,xcb_atom_t property_atom,xcb_atom_t property_type_atom,size_t max_bytes,string * retval) const319 bool GetByteArrayProperty(xcb_window_t window, xcb_atom_t property_atom,
320 xcb_atom_t property_type_atom,
321 size_t max_bytes,
322 string *retval) const {
323 DCHECK(retval);
324 retval->clear();
325 size_t bytes_after = 0;
326 int element_bit_size = 0;
327 {
328 const xcb_get_property_cookie_t cookie =
329 ::xcb_get_property(connection_, false,
330 window,
331 property_atom,
332 property_type_atom,
333 0, 0);
334 ScopedXcbGetPropertyReply reply(
335 ::xcb_get_property_reply(connection_, cookie, 0));
336 if (reply.get() == NULL) {
337 VLOG(2) << "reply is NULL";
338 return false;
339 }
340 if (reply->type == XCB_NONE) {
341 LOG(ERROR) << "reply type is XCB_NONE";
342 return false;
343 }
344 if (reply->type != property_type_atom) {
345 LOG(ERROR) << "unexpected atom type: " << GetAtomName(reply->type);
346 return false;
347 }
348 bytes_after = reply->bytes_after;
349 element_bit_size = reply->format;
350 }
351
352 if (max_bytes < bytes_after) {
353 LOG(WARNING) << "Exceeds size limit. Returns an empty string."
354 << " max_bytes: " << max_bytes
355 << ", bytes_after: " << bytes_after;
356 *retval = "";
357 return true;
358 }
359
360 if (element_bit_size == 0) {
361 VLOG(1) << "element_bit_size is 0. Assuming byte-size data.";
362 element_bit_size = 8;
363 }
364
365 if (element_bit_size != 8) {
366 LOG(ERROR) << "Unsupported bit size: " << element_bit_size;
367 return false;
368 }
369
370 int byte_offset = 0;
371
372 while (bytes_after > 0) {
373 const xcb_get_property_cookie_t cookie =
374 ::xcb_get_property(connection_,
375 false,
376 window,
377 property_atom,
378 property_type_atom,
379 byte_offset,
380 max_bytes);
381 ScopedXcbGetPropertyReply reply(
382 ::xcb_get_property_reply(connection_, cookie, 0));
383 if (reply.get() == NULL) {
384 VLOG(2) << "reply is NULL";
385 return false;
386 }
387 if (reply->format != element_bit_size) {
388 LOG(ERROR) << "bit size changed: " << reply->format;
389 return false;
390 }
391 bytes_after = reply->bytes_after;
392 const char *data = reinterpret_cast<const char *>(
393 ::xcb_get_property_value(reply.get()));
394 const int length = ::xcb_get_property_value_length(reply.get());
395 *retval += string(data, length);
396 byte_offset += length;
397 }
398 return true;
399 }
400
401 template <typename T>
GetCardinalProperty(xcb_window_t window,xcb_atom_t property_atom,T * retval) const402 bool GetCardinalProperty(xcb_window_t window, xcb_atom_t property_atom,
403 T *retval) const {
404 *retval = 0;
405 const xcb_get_property_cookie_t cookie =
406 ::xcb_get_property(connection_, false,
407 window,
408 property_atom,
409 XCB_ATOM_CARDINAL,
410 0, sizeof(T) * 8);
411 ScopedXcbGetPropertyReply reply(
412 ::xcb_get_property_reply(connection_, cookie, 0));
413 if (reply.get() == NULL) {
414 VLOG(2) << "reply is NULL";
415 return false;
416 }
417
418 if (reply->type != XCB_ATOM_CARDINAL) {
419 LOG(ERROR) << "unexpected type: " << GetAtomName(reply->type);
420 return false;
421 }
422
423 // All data should be read.
424 if (reply->bytes_after != 0) {
425 LOG(ERROR) << "unexpectedly " << reply->bytes_after
426 << " bytes data remain.";
427 return false;
428 }
429 if (reply->format != 0 && (reply->format != sizeof(T) * 8)) {
430 LOG(ERROR) << "unexpected bit size: " << reply->format;
431 return false;
432 }
433
434 *retval = *reinterpret_cast<const T *>(
435 ::xcb_get_property_value(reply.get()));
436 return true;
437 }
438
OnXFixesSelectionNotify(const xcb_generic_event_t * event,size_t max_bytes,SelectionInfo * next_info)439 bool OnXFixesSelectionNotify(const xcb_generic_event_t *event,
440 size_t max_bytes, SelectionInfo *next_info) {
441 const xcb_xfixes_selection_notify_event_t *event_notify =
442 reinterpret_cast<const xcb_xfixes_selection_notify_event_t *>(
443 event);
444 if (event_notify->selection != XCB_ATOM_PRIMARY) {
445 VLOG(2) << "Ignored :" << GetAtomName(event_notify->selection);
446 return false;
447 }
448
449 // Send request message for selection info.
450 ::xcb_convert_selection(connection_, requestor_window_,
451 XCB_ATOM_PRIMARY, atoms_.utf8_string,
452 atoms_.mozc_selection_monitor,
453 XCB_CURRENT_TIME);
454
455 last_request_info_.timestamp = event_notify->selection_timestamp;
456
457 uint32_t net_wm_pid = 0;
458 if (GetCardinalProperty(event_notify->owner,
459 atoms_.net_wm_pid,
460 &net_wm_pid)) {
461 last_request_info_.process_id = net_wm_pid;
462 }
463
464 string net_wm_name;
465 if (GetByteArrayProperty(event_notify->owner,
466 atoms_.net_wm_name,
467 atoms_.utf8_string,
468 max_bytes,
469 &net_wm_name)) {
470 last_request_info_.window_title = net_wm_name;
471 }
472
473 string wm_client_machine;
474 if (GetByteArrayProperty(event_notify->owner,
475 atoms_.wm_client_machine,
476 XCB_ATOM_STRING,
477 max_bytes,
478 &wm_client_machine)) {
479 last_request_info_.machine_name = wm_client_machine;
480 }
481
482 *next_info = last_request_info_;
483 return true;
484 }
485
OnSelectionNotify(const xcb_generic_event_t * event,size_t max_bytes,SelectionInfo * next_info)486 bool OnSelectionNotify(const xcb_generic_event_t *event, size_t max_bytes,
487 SelectionInfo *next_info) {
488 const xcb_selection_notify_event_t *event_notify =
489 reinterpret_cast<const xcb_selection_notify_event_t *>(event);
490 if (event_notify->selection != XCB_ATOM_PRIMARY) {
491 VLOG(2) << "Ignored a message. selection type:"
492 << event_notify->selection;
493 return false;
494 }
495
496 if (event_notify->property == XCB_NONE) {
497 VLOG(2) << "Ignored a message whose property type is XCB_NONE";
498 return false;
499 }
500
501 string selected_text;
502 if (!GetByteArrayProperty(event_notify->requestor,
503 event_notify->property,
504 atoms_.utf8_string,
505 max_bytes,
506 &selected_text)) {
507 LOG(ERROR) << "Failed to retrieve selection text.";
508 return false;
509 }
510
511 // Update the result.
512 *next_info = last_request_info_;
513 next_info->selected_text = selected_text;
514 return true;
515 }
516
517 xcb_connection_t *connection_;
518 xcb_window_t requestor_window_;
519 xcb_window_t root_window_;
520 uint32 xfixes_first_event_;
521 uint32 xcb_maximum_request_len_;
522 SelectionInfo last_request_info_;
523 XcbAtoms atoms_;
524
525 DISALLOW_COPY_AND_ASSIGN(SelectionMonitorServer);
526 };
527
528 class SelectionMonitorImpl : public SelectionMonitorInterface,
529 public Thread {
530 public:
SelectionMonitorImpl(SelectionMonitorServer * server,size_t max_text_bytes)531 SelectionMonitorImpl(SelectionMonitorServer *server, size_t max_text_bytes)
532 : server_(server),
533 max_text_bytes_(max_text_bytes),
534 quit_(false) {
535 }
536
~SelectionMonitorImpl()537 virtual ~SelectionMonitorImpl() {
538 // Currently mozc::Thread cannot safely detach the attached thread since
539 // the detached thread continues running on a heap allocated to this
540 // object.
541 // TODO(yukawa): Implement safer thread termination.
542 if (Thread::IsRunning()) {
543 QueryQuit();
544 // TODO(yukawa): Add Wait method to mozc::Thread.
545 Util::Sleep(100);
546 }
547 }
548
549 // Implements SelectionMonitorInterface::StartMonitoring.
StartMonitoring()550 virtual void StartMonitoring() {
551 Thread::Start("SelectionMonitor");
552 }
553
554 // Implements SelectionMonitorInterface::QueryQuit.
QueryQuit()555 virtual void QueryQuit() {
556 if (Thread::IsRunning()) {
557 quit_ = true;
558 // Awake the message pump thread so that it can see the updated
559 // |quit_| immediately.
560 server_->SendNoopEventMessage();
561 }
562 }
563
564 // Implements SelectionMonitorInterface::GetSelectionInfo.
GetSelectionInfo()565 virtual SelectionInfo GetSelectionInfo() {
566 SelectionInfo info;
567 {
568 scoped_lock l(&mutex_);
569 info = last_selection_info_;
570 }
571 return info;
572 }
573
574 // Implements Thread::Run.
Run()575 virtual void Run() {
576 while (!quit_) {
577 if (!server_->checkConnection()) {
578 scoped_lock l(&mutex_);
579 last_selection_info_ = SelectionInfo();
580 quit_ = true;
581 break;
582 }
583
584 SelectionInfo next_info;
585 // Note that this is blocking call and will not return until the next
586 // X11 message is received. In order to interrupt, you can call
587 // SendNoopEventMessage() method from other threads.
588 if (server_->WaitForNextSelectionEvent(max_text_bytes_, &next_info)) {
589 scoped_lock l(&mutex_);
590 last_selection_info_ = next_info;
591 }
592 }
593 }
594
595 private:
596 unique_ptr<SelectionMonitorServer> server_;
597 const size_t max_text_bytes_;
598 volatile bool quit_;
599 Mutex mutex_;
600 SelectionInfo last_selection_info_;
601
602 DISALLOW_COPY_AND_ASSIGN(SelectionMonitorImpl);
603 };
604
605 } // namespace
606
~SelectionMonitorInterface()607 SelectionMonitorInterface::~SelectionMonitorInterface() {}
608
SelectionInfo()609 SelectionInfo::SelectionInfo()
610 : timestamp(0), process_id(0) {
611 }
612
Create(size_t max_text_bytes)613 SelectionMonitorInterface *SelectionMonitorFactory::Create(
614 size_t max_text_bytes) {
615 unique_ptr<SelectionMonitorServer> server(new SelectionMonitorServer());
616 if (!server->Init()) {
617 return NULL;
618 }
619 return new SelectionMonitorImpl(server.release(), max_text_bytes);
620 }
621
622 } // namespace ibus
623 } // namespace mozc
624