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