1 /*
2     Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
3 
4     This file is part of CopyQ.
5 
6     CopyQ is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     CopyQ is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "common/appconfig.h"
21 #include "common/log.h"
22 #include "common/sleeptimer.h"
23 #include "gui/clipboardspy.h"
24 #include "platform/platformcommon.h"
25 #include "x11platformwindow.h"
26 
27 #include <QX11Info>
28 
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31 #include <X11/keysym.h>
32 
33 #ifdef HAS_X11TEST
34 #   include <X11/extensions/XTest.h>
35 #endif
36 
37 namespace {
38 
39 class KeyPressTester final {
40 public:
KeyPressTester(Display * display)41     explicit KeyPressTester(Display *display)
42         : m_display(display)
43     {
44         XQueryKeymap(m_display, m_keyMap);
45     }
46 
isPressed(KeySym key) const47     bool isPressed(KeySym key) const
48     {
49         const KeyCode keyCode = XKeysymToKeycode(m_display, key);
50         return (m_keyMap[keyCode >> 3] >> (keyCode & 7)) & 1;
51     }
52 
53 private:
54     Display *m_display;
55     char m_keyMap[32]{};
56 };
57 
58 #ifdef HAS_X11TEST
fakeKeyEvent(Display * display,unsigned int keyCode,Bool isPress)59 void fakeKeyEvent(Display* display, unsigned int keyCode, Bool isPress)
60 {
61     XTestFakeKeyEvent(display, keyCode, isPress, CurrentTime);
62     XSync(display, False);
63 }
64 
simulateModifierKeyPress(Display * display,const QList<int> & modCodes,Bool keyDown)65 void simulateModifierKeyPress(Display *display, const QList<int> &modCodes, Bool keyDown)
66 {
67     for (int modCode : modCodes) {
68         const auto keySym = static_cast<KeySym>(modCode);
69         KeyCode keyCode = XKeysymToKeycode(display, keySym);
70         fakeKeyEvent(display, keyCode, keyDown);
71     }
72 }
73 
isModifierPressed(Display * display)74 bool isModifierPressed(Display *display)
75 {
76     KeyPressTester tester(display);
77     return tester.isPressed(XK_Shift_L)
78         || tester.isPressed(XK_Shift_R)
79         || tester.isPressed(XK_Control_L)
80         || tester.isPressed(XK_Control_R)
81         || tester.isPressed(XK_Meta_L)
82         || tester.isPressed(XK_Meta_R)
83         || tester.isPressed(XK_Alt_L)
84         || tester.isPressed(XK_Alt_R)
85         || tester.isPressed(XK_Super_L)
86         || tester.isPressed(XK_Super_R)
87         || tester.isPressed(XK_Hyper_L)
88         || tester.isPressed(XK_Hyper_R);
89 }
90 
waitForModifiersReleased(Display * display,const AppConfig & config)91 bool waitForModifiersReleased(Display *display, const AppConfig &config)
92 {
93     const int maxWaitForModsReleaseMs = config.option<Config::window_wait_for_modifier_released_ms>();
94     if (maxWaitForModsReleaseMs >= 0) {
95         SleepTimer t(maxWaitForModsReleaseMs);
96         while (t.sleep()) {
97             if (!isModifierPressed(display))
98                 return true;
99         }
100     }
101 
102     return !isModifierPressed(display);
103 }
104 
simulateKeyPress(Display * display,const QList<int> & modCodes,unsigned int key,const AppConfig & config)105 void simulateKeyPress(Display *display, const QList<int> &modCodes, unsigned int key, const AppConfig &config)
106 {
107     // Wait for user to release modifiers.
108     if (!waitForModifiersReleased(display, config))
109         return;
110 
111     simulateModifierKeyPress(display, modCodes, True);
112 
113     const KeyCode keyCode = XKeysymToKeycode(display, key);
114 
115     fakeKeyEvent(display, keyCode, True);
116     // This is needed to paste into URL bar in Chrome.
117     waitFor(config.option<Config::window_key_press_time_ms>());
118     fakeKeyEvent(display, keyCode, False);
119 
120     simulateModifierKeyPress(display, modCodes, False);
121 
122     XSync(display, False);
123 }
124 #else
125 
simulateKeyPress(Display * display,Window window,unsigned int modifiers,unsigned int key)126 void simulateKeyPress(Display *display, Window window, unsigned int modifiers, unsigned int key)
127 {
128     XKeyEvent event;
129     XEvent *xev = reinterpret_cast<XEvent *>(&event);
130     event.display     = display;
131     event.window      = window;
132     event.root        = DefaultRootWindow(display);
133     event.subwindow   = None;
134     event.time        = CurrentTime;
135     event.x           = 1;
136     event.y           = 1;
137     event.x_root      = 1;
138     event.y_root      = 1;
139     event.same_screen = True;
140     event.keycode     = XKeysymToKeycode(display, key);
141     event.state       = modifiers;
142 
143     event.type = KeyPress;
144     XSendEvent(display, window, True, KeyPressMask, xev);
145     XSync(display, False);
146 
147     event.type = KeyRelease;
148     XSendEvent(display, window, True, KeyPressMask, xev);
149     XSync(display, False);
150 }
151 #endif
152 
153 class X11WindowProperty final {
154 public:
X11WindowProperty(Display * display,Window w,Atom property,long longOffset,long longLength,Atom reqType)155     X11WindowProperty(Display *display, Window w, Atom property, long longOffset,
156                       long longLength, Atom reqType)
157     {
158         if ( XGetWindowProperty(display, w, property, longOffset, longLength, false,
159                                 reqType, &type, &format, &len, &remain, &data) != Success )
160         {
161             data = nullptr;
162         }
163     }
164 
~X11WindowProperty()165     ~X11WindowProperty()
166     {
167         if (data != nullptr)
168             XFree(data);
169     }
170 
isValid() const171     bool isValid() const { return data != nullptr; }
172 
173     X11WindowProperty(const X11WindowProperty &) = delete;
174     X11WindowProperty &operator=(const X11WindowProperty &) = delete;
175 
176     Atom type{};
177     int format{};
178     unsigned long len{};
179     unsigned long remain{};
180     unsigned char *data;
181 };
182 
getCurrentWindow()183 Window getCurrentWindow()
184 {
185     if (!QX11Info::isPlatformX11())
186         return 0L;
187 
188     auto display = QX11Info::display();
189     XSync(display, False);
190 
191     static Atom atomWindow = XInternAtom(display, "_NET_ACTIVE_WINDOW", true);
192 
193     X11WindowProperty property(display, DefaultRootWindow(display), atomWindow, 0l, 1l, XA_WINDOW);
194 
195     if ( property.isValid() && property.type == XA_WINDOW && property.format == 32 && property.len == 1)
196         return *reinterpret_cast<Window *>(property.data);
197 
198     return 0L;
199 }
200 
201 } // namespace
202 
203 
X11PlatformWindow()204 X11PlatformWindow::X11PlatformWindow()
205     : m_window(getCurrentWindow())
206 {
207 }
208 
X11PlatformWindow(Window winId)209 X11PlatformWindow::X11PlatformWindow(Window winId)
210     : m_window(winId)
211 {
212 }
213 
getTitle()214 QString X11PlatformWindow::getTitle()
215 {
216     Q_ASSERT( isValid() );
217 
218     if (!QX11Info::isPlatformX11())
219         return QString();
220 
221     auto display = QX11Info::display();
222     static Atom atomName = XInternAtom(display, "_NET_WM_NAME", false);
223     static Atom atomUTF8 = XInternAtom(display, "UTF8_STRING", false);
224 
225     X11WindowProperty property(display, m_window, atomName, 0, (~0L), atomUTF8);
226     if ( property.isValid() ) {
227         const auto len = static_cast<int>(property.len);
228         QByteArray result(reinterpret_cast<const char *>(property.data), len);
229         return QString::fromUtf8(result);
230     }
231 
232     return QString();
233 }
234 
raise()235 void X11PlatformWindow::raise()
236 {
237     Q_ASSERT( isValid() );
238 
239     if (!QX11Info::isPlatformX11())
240         return;
241 
242     COPYQ_LOG( QString("Raising window \"%1\"").arg(getTitle()) );
243 
244     auto display = QX11Info::display();
245 
246     XEvent e{};
247     memset(&e, 0, sizeof(e));
248     e.type = ClientMessage;
249     e.xclient.display = display;
250     e.xclient.window = m_window;
251     e.xclient.message_type = XInternAtom(display, "_NET_ACTIVE_WINDOW", False);
252     e.xclient.format = 32;
253     e.xclient.data.l[0] = 2;
254     e.xclient.data.l[1] = CurrentTime;
255     e.xclient.data.l[2] = 0;
256     e.xclient.data.l[3] = 0;
257     e.xclient.data.l[4] = 0;
258 
259     XWindowAttributes wattr{};
260     XGetWindowAttributes(display, m_window, &wattr);
261 
262     if (wattr.map_state == IsViewable) {
263         XSendEvent(display, wattr.screen->root, False,
264                    SubstructureNotifyMask | SubstructureRedirectMask,
265                    &e);
266         XSync(display, False);
267         XRaiseWindow(display, m_window);
268         XSetInputFocus(display, m_window, RevertToPointerRoot, CurrentTime);
269         XSync(display, False);
270     }
271 }
272 
pasteClipboard()273 void X11PlatformWindow::pasteClipboard()
274 {
275     const AppConfig config;
276     if ( pasteWithCtrlV(*this, config) )
277         sendKeyPress(XK_Control_L, XK_V, config);
278     else
279         sendKeyPress(XK_Shift_L, XK_Insert, config);
280 }
281 
copy()282 void X11PlatformWindow::copy()
283 {
284     const AppConfig config;
285     ClipboardSpy spy(ClipboardMode::Clipboard, QByteArray());
286     sendKeyPress(XK_Control_L, XK_C, config);
287     spy.wait();
288 }
289 
isValid() const290 bool X11PlatformWindow::isValid() const
291 {
292     return m_window != 0L;
293 }
294 
waitForFocus(int ms)295 bool X11PlatformWindow::waitForFocus(int ms)
296 {
297     Q_ASSERT( isValid() );
298 
299     if (ms >= 0) {
300         SleepTimer t(ms);
301         while (t.sleep()) {
302             const auto currentWindow = getCurrentWindow();
303             if (currentWindow == m_window)
304                 return true;
305         }
306     }
307 
308     return m_window == getCurrentWindow();
309 }
310 
sendKeyPress(int modifier,int key,const AppConfig & config)311 void X11PlatformWindow::sendKeyPress(int modifier, int key, const AppConfig &config)
312 {
313     Q_ASSERT( isValid() );
314 
315     if ( !waitForFocus(config.option<Config::window_wait_before_raise_ms>()) ) {
316         raise();
317         if ( !waitForFocus(config.option<Config::window_wait_raised_ms>()) ) {
318             COPYQ_LOG( QString("Failed to focus window \"%1\"").arg(getTitle()) );
319             return;
320         }
321     }
322 
323     waitMs(config.option<Config::window_wait_after_raised_ms>());
324 
325     if (!QX11Info::isPlatformX11())
326         return;
327 
328     auto display = QX11Info::display();
329 
330 #ifdef HAS_X11TEST
331     simulateKeyPress(display, QList<int>() << modifier, static_cast<uint>(key), config);
332 #else
333     const int modifierMask = (modifier == XK_Control_L) ? ControlMask : ShiftMask;
334     simulateKeyPress(display, m_window, modifierMask, key);
335 #endif
336 }
337