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"