File indexing completed on 2024-12-01 13:34:29
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-only 0003 SPDX-FileCopyrightText: 1999-2002 Lubos Lunak <l.lunak@kde.org> 0004 */ 0005 0006 #include "gestures.h" 0007 0008 #include <math.h> 0009 0010 #include "khotkeysglobal.h" 0011 0012 #include "action_data/action_data.h" 0013 #include "input.h" 0014 0015 #include <QApplication> 0016 #include <QDebug> 0017 0018 #include <kkeyserver.h> 0019 #include <kxerrorhandler.h> 0020 0021 #include "windows_helper/window_selection_list.h" 0022 0023 // only necessary for circumventing bug #173606, see below 0024 #include <QX11Info> 0025 #include <X11/Xlib.h> 0026 0027 namespace KHotKeys 0028 { 0029 QPointer<Gesture> gesture_handler = nullptr; 0030 0031 Gesture::Gesture(bool enabled_P, QObject *parent_P) 0032 : QObject(parent_P) 0033 , _enabled(false) 0034 , recording(false) 0035 , button(0) 0036 , exclude(nullptr) 0037 { 0038 Q_UNUSED(enabled_P); 0039 // qDebug() << enabled_P; 0040 nostroke_timer.setSingleShot(true); 0041 connect(&nostroke_timer, SIGNAL(timeout()), SLOT(stroke_timeout())); 0042 connect(windows_handler, SIGNAL(active_window_changed(WId)), SLOT(active_window_changed(WId))); 0043 } 0044 0045 Gesture::~Gesture() 0046 { 0047 enable(false); 0048 } 0049 0050 void Gesture::enable(bool enabled_P) 0051 { 0052 // qDebug() << enabled_P; 0053 if (_enabled == enabled_P) 0054 return; 0055 _enabled = enabled_P; 0056 update_grab(); 0057 } 0058 0059 void Gesture::set_exclude(Windowdef_list *windows_P) 0060 { 0061 delete exclude; 0062 // check for count() > 0 - empty exclude list means no window is excluded, 0063 // but empty Windowdef_list matches everything 0064 if (windows_P != nullptr && windows_P->count() > 0) 0065 exclude = windows_P->copy(); 0066 else 0067 exclude = nullptr; 0068 update_grab(); 0069 } 0070 0071 void Gesture::update_grab() 0072 { 0073 // qDebug() << "Enabled:" << _enabled; 0074 // qDebug() << "Handler:" << handlers.count(); 0075 // qDebug() << "Exclude:" << exclude << " Match? " << (exclude && exclude->match( Window_data( windows_handler->active_window()))); 0076 0077 if (!QX11Info::isPlatformX11()) { 0078 return; 0079 } 0080 0081 if (_enabled && handlers.count() > 0 && (exclude == nullptr || !exclude->match(Window_data(windows_handler->active_window())))) { 0082 qApp->removeNativeEventFilter(this); // avoid being installed twice 0083 qApp->installNativeEventFilter(this); 0084 // CHECKME grab only when there's at least one gesture? 0085 grab_mouse(true); 0086 } else { 0087 grab_mouse(false); 0088 qApp->removeNativeEventFilter(this); 0089 } 0090 } 0091 0092 void Gesture::active_window_changed(WId) 0093 { 0094 update_grab(); 0095 } 0096 0097 void Gesture::handleScore(ActionData *const data, const qreal score) 0098 { 0099 if (score > maxScore) { 0100 maxScore = score; 0101 bestFit = data; 0102 } 0103 } 0104 0105 void Gesture::register_handler(QObject *receiver_P, const char *slot_P) 0106 { 0107 if (handlers.contains(receiver_P)) 0108 return; 0109 handlers[receiver_P] = true; 0110 // connect directly because we want to be sure that all triggers submitted 0111 // their scores back to this object before executing the best match we 0112 // could find. 0113 connect(this, SIGNAL(handle_gesture(StrokePoints)), receiver_P, slot_P, Qt::DirectConnection); 0114 // clang-format off 0115 connect(receiver_P, SIGNAL(gotScore(ActionData*const,qreal)), this, SLOT(handleScore(ActionData*const,qreal)), Qt::DirectConnection); 0116 // clang-format on 0117 if (handlers.count() == 1) 0118 update_grab(); 0119 } 0120 0121 void Gesture::unregister_handler(QObject *receiver_P, const char *slot_P) 0122 { 0123 if (!handlers.contains(receiver_P)) 0124 return; 0125 handlers.remove(receiver_P); 0126 0127 disconnect(this, SIGNAL(handle_gesture(StrokePoints)), receiver_P, slot_P); 0128 // clang-format off 0129 disconnect(receiver_P, SIGNAL(gotScore(ActionData*const,qreal)), this, SLOT(handleScore(ActionData*const,qreal))); 0130 // clang-format on 0131 if (handlers.count() == 0) 0132 update_grab(); 0133 } 0134 0135 bool Gesture::nativeEventFilter(const QByteArray &eventType, void *message, long *) 0136 { 0137 if (eventType != "xcb_generic_event_t") { 0138 return false; 0139 } 0140 0141 xcb_generic_event_t *generic_event = static_cast<xcb_generic_event_t *>(message); 0142 const uint8_t type = generic_event->response_type & ~0x80; 0143 0144 if (type == XCB_BUTTON_PRESS) { 0145 xcb_button_press_event_t *event = static_cast<xcb_button_press_event_t *>(message); 0146 0147 if (event->detail != button) 0148 return false; 0149 0150 qDebug() << "GESTURE: mouse press"; 0151 stroke.reset(); 0152 stroke.record(event->event_x, event->event_y); 0153 nostroke_timer.start(timeout); 0154 recording = true; 0155 start_x = event->event_x; 0156 start_y = event->event_y; 0157 return true; 0158 } 0159 // if stroke is finished... postprocess the data and send a signal. 0160 // then wait for incoming matching scores and execute the best fit. 0161 else if (type == XCB_BUTTON_RELEASE && recording) { 0162 xcb_button_release_event_t *event = static_cast<xcb_button_release_event_t *>(message); 0163 0164 if (event->detail != button) 0165 return false; 0166 0167 recording = false; 0168 nostroke_timer.stop(); 0169 stroke.record(event->event_x, event->event_y); 0170 StrokePoints gesture(stroke.processData()); 0171 if (gesture.isEmpty()) { 0172 qDebug() << "GESTURE: replay"; 0173 XAllowEvents(QX11Info::display(), AsyncPointer, CurrentTime); 0174 XUngrabPointer(QX11Info::display(), CurrentTime); 0175 mouse_replay(true); 0176 return true; 0177 } 0178 0179 // prepare for the incoming scores from different triggers 0180 maxScore = 0.0; 0181 bestFit = nullptr; 0182 0183 emit handle_gesture(gesture); 0184 // the signal is emitted directly, so we get all trigger scores before 0185 // the next lines are executed. bestFit should now contain 0186 // a pointer to the ActionData with the best-matching gesture. 0187 0188 if (bestFit != nullptr) { 0189 // set up the windows_handler 0190 WId window = windows_handler->window_at_position(start_x, start_y); 0191 windows_handler->set_action_window(window); 0192 // then execute the action associated with the best match. 0193 bestFit->execute(); 0194 } 0195 0196 return true; 0197 } else if (type == XCB_MOTION_NOTIFY && recording) { 0198 xcb_motion_notify_event_t *event = static_cast<xcb_motion_notify_event_t *>(message); 0199 0200 // ignore small initial movement 0201 if (nostroke_timer.isActive() && abs(start_x - event->event_x) < 10 && abs(start_y - event->event_y) < 10) 0202 return true; 0203 nostroke_timer.stop(); 0204 0205 stroke.record(event->event_x, event->event_y); 0206 } 0207 return false; 0208 } 0209 0210 void Gesture::stroke_timeout() 0211 { 0212 // qDebug() << "GESTURE: timeout"; 0213 XAllowEvents(QX11Info::display(), AsyncPointer, CurrentTime); 0214 XUngrabPointer(QX11Info::display(), CurrentTime); 0215 mouse_replay(false); 0216 0217 // for xorg-server 1.7 to 1.9 RC4: disable drag'n'drop support to evade bug #173606 0218 if (VendorRelease(QX11Info::display()) < 10899905 && VendorRelease(QX11Info::display()) >= 10700000) 0219 mouse_replay(true); 0220 0221 recording = false; 0222 } 0223 0224 void Gesture::mouse_replay(bool release_P) 0225 { 0226 bool was_enabled = _enabled; 0227 enable(false); 0228 Mouse::send_mouse_button(button, release_P); 0229 enable(was_enabled); 0230 } 0231 0232 void Gesture::grab_mouse(bool grab_P) 0233 { 0234 // qDebug() << grab_P; 0235 0236 if (grab_P) { 0237 // qDebug() << "gesture grab"; 0238 Q_ASSERT(button != 0); 0239 KXErrorHandler handler; 0240 static int mask[] = {0, 0241 Button1MotionMask, 0242 Button2MotionMask, 0243 Button3MotionMask, 0244 Button4MotionMask, 0245 Button5MotionMask, 0246 ButtonMotionMask, 0247 ButtonMotionMask, 0248 ButtonMotionMask, 0249 ButtonMotionMask}; 0250 #define XCapL KKeyServer::modXLock() 0251 #define XNumL KKeyServer::modXNumLock() 0252 #define XScrL KKeyServer::modXScrollLock() 0253 unsigned int mods[8] = {0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL}; 0254 #undef XCapL 0255 #undef XNumL 0256 #undef XScrL 0257 for (int i = 0; i < 8; ++i) 0258 XGrabButton(QX11Info::display(), 0259 button, 0260 mods[i], 0261 QX11Info::appRootWindow(), 0262 False, 0263 ButtonPressMask | ButtonReleaseMask | mask[button], 0264 GrabModeAsync, 0265 GrabModeAsync, 0266 None, 0267 None); 0268 bool err = handler.error(true); 0269 Q_UNUSED(err); 0270 // qDebug() << "Gesture grab:" << err; 0271 } else { 0272 // qDebug() << "Gesture ungrab"; 0273 XUngrabButton(QX11Info::display(), button, AnyModifier, QX11Info::appRootWindow()); 0274 } 0275 } 0276 0277 void Gesture::set_mouse_button(unsigned int button_P) 0278 { 0279 if (button == button_P) 0280 return; 0281 if (!_enabled) { 0282 button = button_P; 0283 return; 0284 } 0285 grab_mouse(false); 0286 button = button_P; 0287 grab_mouse(true); 0288 } 0289 0290 void Gesture::set_timeout(int timeout_P) 0291 { 0292 timeout = timeout_P; 0293 } 0294 0295 // Definitions for Gesture end here, Definitions for Stroke following. 0296 0297 Stroke::Stroke() 0298 { 0299 reset(); 0300 points = new point[MAX_POINTS]; // CHECKME 0301 } 0302 0303 Stroke::~Stroke() 0304 { 0305 delete[] points; 0306 } 0307 0308 void Stroke::reset() 0309 { 0310 min_x = 10000; 0311 min_y = 10000; 0312 max_x = -1; 0313 max_y = -1; 0314 point_count = -1; 0315 } 0316 0317 bool Stroke::record(int x, int y) 0318 { 0319 if (point_count == -1) { 0320 ++point_count; 0321 points[point_count].x = x; 0322 points[point_count].y = y; 0323 0324 // start metrics 0325 min_x = max_x = x; 0326 min_y = max_y = y; 0327 } else { 0328 ++point_count; 0329 if (point_count >= MAX_POINTS) 0330 return false; 0331 points[point_count].x = x; 0332 points[point_count].y = y; 0333 0334 // update metrics 0335 if (x < min_x) 0336 min_x = x; 0337 if (x > max_x) 0338 max_x = x; 0339 if (y < min_y) 0340 min_y = y; 0341 if (y > max_y) 0342 max_y = y; 0343 } 0344 return true; 0345 } 0346 0347 // Compute some additional data from the raw point coordinates and store 0348 // it all in a new data structure to be passed on and saved. 0349 0350 StrokePoints Stroke::processData() 0351 { 0352 if (point_count < 2) 0353 return StrokePoints(); // empty vector 0354 0355 int n = point_count - 1; 0356 0357 StrokePoints results(n); 0358 0359 // calculate s, where s is the length of a stroke up to the current point 0360 // (first loop) divided by the total stroke length (second loop) 0361 qreal strokelength = 0.0; 0362 results[0].s = 0.0; 0363 0364 for (int i = 0; i < n - 1; i++) { 0365 strokelength += hypot(points[i + 1].x - points[i].x, points[i + 1].y - points[i].y); 0366 results[i + 1].s = strokelength; 0367 } 0368 0369 for (int i = 0; i < n; i++) 0370 results[i].s /= strokelength; 0371 0372 // check which axis is longer... 0373 int scaleX = max_x - min_x; 0374 int scaleY = max_y - min_y; 0375 qreal scale = (scaleX > scaleY) ? scaleX : scaleY; 0376 0377 // ...and scale the stroke coordinates to a new size depending on this axis 0378 // (saving into the new data structure for higher precision) 0379 for (int i = 0; i < n; i++) { 0380 results[i].x = (points[i].x - (min_x + max_x) / 2.0) / scale + 0.5; 0381 results[i].y = (points[i].y - (min_y + max_y) / 2.0) / scale + 0.5; 0382 } 0383 0384 // calculate values of delta_s and angle for the points by simple comparison 0385 // with the respective successor. 0386 // delta_s is the distance to the successor in the same units as s. 0387 // angle is the angle to the successor in units of pi. 0388 for (int i = 0; i < n - 1; i++) { 0389 results[i].delta_s = results[i + 1].s - results[i].s; 0390 results[i].angle = atan2(results[i + 1].y - results[i].y, results[i + 1].x - results[i].x) / M_PI; 0391 } 0392 0393 // last point of result would need special logic, so we simply discard it - 0394 // there's enough points anyway 0395 results.pop_back(); 0396 0397 return results; 0398 } 0399 0400 } // namespace KHotKeys 0401 0402 #include "moc_gestures.cpp"