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