File indexing completed on 2024-04-28 03:40:29
0001 /* 0002 SPDX-FileCopyrightText: 2002-2003 Jeff Roush <jeff@mousetool.com> 0003 SPDX-FileCopyrightText: 2003 Olaf Schmidt <ojschmidt@kde.org> 0004 SPDX-FileCopyrightText: 2003 Gunnar Schmi Dt <gunnar@schmi-dt.de> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kmousetool.h" 0009 0010 #include <X11/Intrinsic.h> /* Intrinsics Definitions*/ 0011 #include <X11/StringDefs.h> /* Standard Name-String definitions*/ 0012 #include <X11/Xmd.h> 0013 #include <X11/extensions/XTest.h> /* Standard Name-String definitions*/ 0014 #include <X11/extensions/xtestext1.h> /* Standard Name-String definitions*/ 0015 #include <fixx11h.h> 0016 0017 #include <QAbstractEventDispatcher> 0018 #include <QApplication> 0019 #include <QAudioOutput> 0020 #include <QFile> 0021 #include <QFileInfo> 0022 #include <QIcon> 0023 #include <QMediaPlayer> 0024 #include <QMenu> 0025 #include <QScreen> 0026 #include <QStandardPaths> 0027 0028 #include <KConfig> 0029 #include <KConfigGroup> 0030 #include <KHelpMenu> 0031 #include <KLocalizedString> 0032 #include <KMessageBox> 0033 #include <KSharedConfig> 0034 #include <KStandardGuiItem> 0035 0036 int currentXPosition; 0037 int currentYPosition; 0038 0039 #undef long 0040 0041 int leftButton; 0042 int rightButton; 0043 0044 /** 0045 * Gnarly X functions 0046 */ 0047 void queryPointer(Window *pidRoot, int *pnRx, int *pnRy, int *pnX, int *pnY, unsigned int *puMask); 0048 int CursorHasMoved(int minMovement); 0049 void getMouseButtons(); 0050 void LeftClick(); 0051 void RightClick(); 0052 void DoubleClick(); 0053 void LeftDn(); 0054 void LeftUp(); 0055 0056 // Declarations 0057 Display *display; 0058 0059 void KMouseTool::init_vars() 0060 { 0061 mContinue_timer = 1; 0062 mTick_count = 0; 0063 mMax_ticks = mDwell_time + 1; 0064 mMouse_is_down = false; 0065 0066 loadOptions(); 0067 0068 // If the ~/.mousetool directory doesn't exist, create it 0069 mSoundFileName = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/mousetool_tap.wav")); 0070 mPlayer = new QMediaPlayer(); 0071 QAudioOutput *output = new QAudioOutput(); 0072 mPlayer->setAudioOutput(output); 0073 mPlayer->setParent(this); 0074 0075 // find application file 0076 mAppfilename = QStandardPaths::findExecutable(QStringLiteral("kmousetool")); 0077 0078 // find the user's autostart directory 0079 mAutostartdirname = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/autostart-scripts/"); 0080 0081 auto screenSize = screen()->availableGeometry(); 0082 int w = screenSize.width(); 0083 int h = screenSize.height(); 0084 MTStroke::setUpperLeft(0, 0); 0085 MTStroke::setUpperRight(w - 1, 0); 0086 MTStroke::setLowerLeft(0, h - 1); 0087 MTStroke::setLowerRight(w - 1, h - 1); 0088 0089 mButtonQuit = buttonBox->addButton(QString(), QDialogButtonBox::RejectRole); 0090 } 0091 0092 void KMouseTool::resetSettings() 0093 { 0094 cbDrag->setChecked(mSmart_drag_on); 0095 cbStart->setChecked(isAutostart()); 0096 cbClick->setChecked(mPlaySound); 0097 cbStroke->setChecked(mStrokesEnabled); 0098 movementEdit->setValue(mMin_movement); 0099 dwellTimeEdit->setValue(mDwell_time); 0100 dragTimeEdit->setValue(mDrag_time); 0101 settingsChanged(); 0102 } 0103 0104 void KMouseTool::setDefaultSettings() 0105 { 0106 cbDrag->setChecked(false); 0107 cbStart->setChecked(false); 0108 cbClick->setChecked(false); 0109 cbStroke->setChecked(false); 0110 movementEdit->setValue(5); 0111 dwellTimeEdit->setValue(5); 0112 dragTimeEdit->setValue(3); 0113 settingsChanged(); 0114 } 0115 0116 void KMouseTool::timerEvent(QTimerEvent *) 0117 { 0118 if (!mMousetool_is_running) 0119 return; 0120 0121 if (!mContinue_timer) { 0122 QAbstractEventDispatcher::instance()->unregisterTimers(this); 0123 return; 0124 } 0125 0126 mMax_ticks = mDwell_time + mDrag_time; 0127 mStroke.addPt(currentXPosition, currentYPosition); 0128 0129 mMoving = mMoving ? CursorHasMoved(1) : CursorHasMoved(mMin_movement); 0130 if (mMoving) { 0131 if (mMousetool_just_started) { 0132 mMousetool_just_started = false; 0133 mTick_count = mMax_ticks; 0134 } else { 0135 mTick_count = 0; 0136 } 0137 return; 0138 } 0139 0140 if (mTick_count < mMax_ticks) 0141 mTick_count++; 0142 0143 // If the mouse has paused ... 0144 if (mTick_count == mDwell_time) { 0145 int strokeType = mStroke.getStrokeType(); 0146 getMouseButtons(); 0147 0148 // if we're dragging the mouse, ignore stroke type 0149 if (mMouse_is_down) { 0150 normalClick(); 0151 return; 0152 } 0153 0154 if (!mStrokesEnabled) { 0155 normalClick(); 0156 return; 0157 } 0158 if (strokeType == MTStroke::DontClick) 0159 return; 0160 if (strokeType == MTStroke::bumped_mouse) 0161 return; 0162 0163 if (strokeType == MTStroke::RightClick || strokeType == MTStroke::UpperRightStroke) 0164 RightClick(); 0165 else if (strokeType == MTStroke::DoubleClick || strokeType == MTStroke::LowerLeftStroke) 0166 DoubleClick(); 0167 else 0168 normalClick(); 0169 } 0170 } 0171 0172 void KMouseTool::normalClick() 0173 { 0174 if (mSmart_drag_on) { 0175 if (!mMouse_is_down) { 0176 LeftDn(); 0177 mMouse_is_down = true; 0178 mTick_count = 0; 0179 playTickSound(); 0180 } else if (mMouse_is_down) { 0181 LeftUp(); 0182 mMouse_is_down = false; 0183 mTick_count = mMax_ticks; 0184 } 0185 } else { 0186 // not smart_drag_on 0187 LeftClick(); 0188 playTickSound(); 0189 } 0190 } 0191 0192 // This function isn't happy yet. 0193 void KMouseTool::playTickSound() 0194 { 0195 if (!mPlaySound) 0196 return; 0197 0198 mPlayer->setSource(QUrl::fromLocalFile(mSoundFileName)); 0199 mPlayer->play(); 0200 } 0201 0202 KMouseTool::KMouseTool(QWidget *parent, const char *name) 0203 : QWidget(parent) 0204 , mHelpMenu(new KHelpMenu(nullptr)) 0205 { 0206 setupUi(this); 0207 setObjectName(QLatin1String(name)); 0208 init_vars(); 0209 resetSettings(); 0210 0211 connect(movementEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged())); 0212 connect(dwellTimeEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged())); 0213 connect(dragTimeEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged())); 0214 connect(cbDrag, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged); 0215 connect(cbStroke, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged); 0216 connect(cbClick, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged); 0217 connect(cbStart, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged); 0218 0219 connect(buttonStartStop, &QAbstractButton::clicked, this, &KMouseTool::startStopSelected); 0220 connect(buttonBoxSettings->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KMouseTool::defaultSelected); 0221 connect(buttonBoxSettings->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &KMouseTool::resetSelected); 0222 connect(buttonBoxSettings->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KMouseTool::applySelected); 0223 connect(buttonBox, &QDialogButtonBox::helpRequested, this, &KMouseTool::helpSelected); 0224 connect(buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &KMouseTool::closeSelected); 0225 KGuiItem::assign(mButtonQuit, KStandardGuiItem::quit()); 0226 connect(mButtonQuit, &QAbstractButton::clicked, this, &KMouseTool::quitSelected); 0227 0228 // When we first start, it's nice to not click immediately. 0229 // So, tell MT we're just starting 0230 mMousetool_just_started = true; 0231 0232 startTimer(100); 0233 mTrayIcon = new KMouseToolTray(this); 0234 updateStartStopText(); 0235 connect(mTrayIcon, &KMouseToolTray::startStopSelected, this, &KMouseTool::startStopSelected); 0236 connect(mTrayIcon, &KMouseToolTray::configureSelected, this, &KMouseTool::configureSelected); 0237 connect(mTrayIcon, &KMouseToolTray::aboutSelected, this, &KMouseTool::aboutSelected); 0238 connect(mTrayIcon, &KMouseToolTray::helpSelected, this, &KMouseTool::helpSelected); 0239 } 0240 0241 KMouseTool::~KMouseTool() 0242 { 0243 } 0244 0245 // ********************************************************* 0246 // Raw X functions 0247 // Begin mouse monitoring and event generation code. 0248 // these should be moved to a separate file. 0249 void getMouseButtons() 0250 { 0251 unsigned char buttonMap[3]; 0252 const int buttonCount = XGetPointerMapping(display, buttonMap, 3); 0253 switch (buttonCount) { 0254 case 0: 0255 case 1: 0256 // ### should not happen 0257 leftButton = 1; 0258 rightButton = 1; 0259 break; 0260 case 2: 0261 leftButton = buttonMap[0]; 0262 rightButton = buttonMap[1]; 0263 break; 0264 default: 0265 leftButton = buttonMap[0]; 0266 rightButton = buttonMap[2]; 0267 break; 0268 } 0269 } 0270 0271 void LeftClick() 0272 { 0273 XTestFakeButtonEvent(display, leftButton, true, 0); 0274 XTestFakeButtonEvent(display, leftButton, false, 0); 0275 } 0276 0277 void DoubleClick() 0278 { 0279 XTestFakeButtonEvent(display, leftButton, true, 0); 0280 XTestFakeButtonEvent(display, leftButton, false, 100); 0281 XTestFakeButtonEvent(display, leftButton, true, 200); 0282 XTestFakeButtonEvent(display, leftButton, false, 300); 0283 } 0284 0285 void RightClick() 0286 { 0287 XTestFakeButtonEvent(display, rightButton, true, 0); 0288 XTestFakeButtonEvent(display, rightButton, false, 0); 0289 } 0290 0291 void LeftDn() 0292 { 0293 XTestFakeButtonEvent(display, leftButton, true, 0); 0294 } 0295 0296 void LeftUp() 0297 { 0298 XTestFakeButtonEvent(display, leftButton, false, 0); 0299 } 0300 0301 void queryPointer(Window *pidRoot, int *pnRx, int *pnRy, int *pnX, int *pnY, unsigned int *puMask) 0302 { 0303 Window id2, idRoot; 0304 int screen_num; 0305 0306 screen_num = DefaultScreen(display); 0307 idRoot = RootWindow(display, screen_num); 0308 XQueryPointer(display, idRoot, &idRoot, &id2, pnRx, pnRy, pnX, pnY, puMask); 0309 0310 *pidRoot = idRoot; 0311 } 0312 0313 // return 1 if cursor has moved, 0 otherwise 0314 int CursorHasMoved(int minMovement) 0315 { 0316 static int nOldRootX = -1; 0317 static int nOldRootY = -1; 0318 0319 // XEvent evButtonEvent; 0320 Window idRootWin; 0321 int nRootX, nRootY, nWinX, nWinY; 0322 unsigned int uintMask; 0323 0324 queryPointer(&idRootWin, &nRootX, &nRootY, &nWinX, &nWinY, &uintMask); 0325 0326 int nRes = 0; 0327 if ((nRootX > nOldRootX ? nRootX - nOldRootX : nOldRootX - nRootX) >= minMovement) 0328 nRes = 1; 0329 if ((nRootY > nOldRootY ? nRootY - nOldRootY : nOldRootY - nRootY) >= minMovement) 0330 nRes = 1; 0331 0332 currentXPosition = nRootX; 0333 currentYPosition = nRootY; 0334 0335 if (nRes) { 0336 nOldRootX = nRootX; 0337 nOldRootY = nRootY; 0338 } 0339 0340 return nRes; 0341 } 0342 // End mouse monitoring and event creation code 0343 0344 bool KMouseTool::isAutostart() 0345 { 0346 QString sym = mAutostartdirname; 0347 sym += QLatin1String("kmousetool"); // sym is now full path to symlink 0348 QFileInfo fi(sym); 0349 0350 return fi.exists(); 0351 } 0352 0353 void KMouseTool::setAutostart(bool start) 0354 { 0355 QString sym = mAutostartdirname; 0356 sym += QLatin1String("kmousetool"); // sym is now full path to symlink 0357 QFileInfo fi(sym); 0358 0359 if (start) { 0360 if (!fi.exists()) // if it doesn't exist, make it 0361 QFile(mAppfilename).link(sym); 0362 } else { 0363 if (fi.exists()) // if it exists, delete it 0364 QFile(sym).remove(); 0365 } 0366 } 0367 0368 bool KMouseTool::applySettings() 0369 { 0370 int drag, dwell; 0371 0372 dwell = dwellTimeEdit->value(); 0373 drag = dragTimeEdit->value(); 0374 0375 // The drag time must be less than the dwell time 0376 if (dwell < drag) { 0377 KMessageBox::error(this, i18n("The drag time must be less than or equal to the dwell time."), i18n("Invalid Value")); 0378 return false; 0379 } 0380 0381 // if we got here, we must be okay. 0382 mMin_movement = movementEdit->value(); 0383 mSmart_drag_on = cbDrag->isChecked(); 0384 mPlaySound = cbClick->isChecked(); 0385 mStrokesEnabled = cbStroke->isChecked(); 0386 setAutostart(cbStart->isChecked()); 0387 0388 mDwell_time = dwell; 0389 mDrag_time = drag; 0390 mTick_count = mMax_ticks; 0391 0392 saveOptions(); 0393 settingsChanged(); 0394 return true; 0395 } 0396 0397 // Save options to kmousetoolrc file 0398 void KMouseTool::loadOptions() 0399 { 0400 KConfigGroup cfg = KSharedConfig::openConfig()->group(QStringLiteral("UserOptions")); 0401 0402 mPlaySound = cfg.readEntry("AudibleClick", false); 0403 mSmart_drag_on = cfg.readEntry("SmartDrag", false); 0404 0405 mDwell_time = cfg.readEntry("DwellTime", 5); 0406 mDrag_time = cfg.readEntry("DragTime", 3); 0407 mMin_movement = cfg.readEntry("Movement", 5); 0408 mStrokesEnabled = cfg.readEntry("strokesEnabled", false); 0409 0410 QPoint p; 0411 int x = cfg.readEntry("x", 0); 0412 int y = cfg.readEntry("y", 0); 0413 p.setX(x); 0414 p.setY(y); 0415 move(p); 0416 0417 mMousetool_is_running = cfg.readEntry("MouseToolIsRunning", false); 0418 display = XOpenDisplay(nullptr); 0419 } 0420 0421 // Save options to kmousetoolrc file 0422 void KMouseTool::saveOptions() 0423 { 0424 QPoint p = pos(); 0425 int x = p.x(); 0426 int y = p.y(); 0427 0428 KConfigGroup cfg = KSharedConfig::openConfig()->group(QStringLiteral("ProgramOptions")); 0429 cfg = KSharedConfig::openConfig()->group(QStringLiteral("UserOptions")); 0430 cfg.writeEntry("x", x); 0431 cfg.writeEntry("y", y); 0432 cfg.writeEntry("strokesEnabled", mStrokesEnabled); 0433 cfg.writeEntry("IsMinimized", isHidden()); 0434 cfg.writeEntry("DwellTime", mDwell_time); 0435 cfg.writeEntry("DragTime", mDrag_time); 0436 cfg.writeEntry("Movement", mMin_movement); 0437 cfg.writeEntry("SmartDrag", mSmart_drag_on); 0438 cfg.writeEntry("AudibleClick", mPlaySound); 0439 cfg.writeEntry("MouseToolIsRunning", mMousetool_is_running); 0440 cfg.sync(); 0441 } 0442 0443 void KMouseTool::updateStartStopText() 0444 { 0445 if (mMousetool_is_running) { 0446 buttonStartStop->setText(i18n("&Stop")); 0447 } else { 0448 buttonStartStop->setText(i18nc("Start tracking the mouse", "&Start")); 0449 } 0450 mTrayIcon->updateStartStopText(mMousetool_is_running); 0451 } 0452 0453 bool KMouseTool::newSettings() 0454 { 0455 return ((mDwell_time != dwellTimeEdit->value()) || (mDrag_time != dragTimeEdit->value()) || (mMin_movement != movementEdit->value()) 0456 || (mSmart_drag_on != cbDrag->isChecked()) || (mPlaySound != cbClick->isChecked()) || (mStrokesEnabled != cbStroke->isChecked()) 0457 || (isAutostart() != cbStart->isChecked())); 0458 } 0459 0460 bool KMouseTool::defaultSettings() 0461 { 0462 return ((5 == dwellTimeEdit->value()) && (3 == dragTimeEdit->value()) && (5 == movementEdit->value()) && !cbDrag->isChecked() && !cbClick->isChecked() 0463 && !cbStroke->isChecked() && !cbStart->isChecked()); 0464 } 0465 0466 /******** SLOTS **********/ 0467 0468 // Value or state changed 0469 void KMouseTool::settingsChanged() 0470 { 0471 buttonBoxSettings->button(QDialogButtonBox::Reset)->setEnabled(newSettings()); 0472 buttonBoxSettings->button(QDialogButtonBox::Apply)->setEnabled(newSettings()); 0473 buttonBoxSettings->button(QDialogButtonBox::RestoreDefaults)->setDisabled(defaultSettings()); 0474 } 0475 0476 // Buttons within the dialog 0477 void KMouseTool::startStopSelected() 0478 { 0479 mMousetool_is_running = !mMousetool_is_running; 0480 updateStartStopText(); 0481 } 0482 0483 void KMouseTool::defaultSelected() 0484 { 0485 setDefaultSettings(); 0486 } 0487 0488 void KMouseTool::resetSelected() 0489 { 0490 resetSettings(); 0491 } 0492 0493 void KMouseTool::applySelected() 0494 { 0495 applySettings(); 0496 } 0497 0498 // Buttons at the bottom of the dialog 0499 void KMouseTool::helpSelected() 0500 { 0501 mHelpMenu->appHelpActivated(); 0502 } 0503 0504 void KMouseTool::closeSelected() 0505 { 0506 if (newSettings()) { 0507 int answer = 0508 KMessageBox::questionTwoActionsCancel(this, 0509 i18n("There are unsaved changes in the active module.\nDo you want to apply the changes before closing " 0510 "the configuration window or discard the changes?"), 0511 i18n("Closing Configuration Window"), 0512 KStandardGuiItem::apply(), 0513 KStandardGuiItem::discard(), 0514 KStandardGuiItem::cancel(), 0515 QStringLiteral("AutomaticSave")); 0516 if (answer == KMessageBox::PrimaryAction) 0517 applySettings(); 0518 else if (answer == KMessageBox::SecondaryAction) 0519 resetSettings(); 0520 if (answer != KMessageBox::Cancel) 0521 hide(); 0522 } else { 0523 hide(); 0524 } 0525 } 0526 0527 void KMouseTool::quitSelected() 0528 { 0529 if (newSettings()) { 0530 int answer = KMessageBox::questionTwoActionsCancel( 0531 this, 0532 i18n("There are unsaved changes in the active module.\nDo you want to apply the changes before quitting KMousetool or discard the changes?"), 0533 i18n("Quitting KMousetool"), 0534 KStandardGuiItem::apply(), 0535 KStandardGuiItem::discard(), 0536 KStandardGuiItem::cancel(), 0537 QStringLiteral("AutomaticSave")); 0538 if (answer == KMessageBox::PrimaryAction) 0539 applySettings(); 0540 if (answer != KMessageBox::Cancel) { 0541 saveOptions(); 0542 qApp->quit(); 0543 } 0544 } else { 0545 saveOptions(); 0546 qApp->quit(); 0547 } 0548 } 0549 0550 // Menu functions 0551 void KMouseTool::configureSelected() 0552 { 0553 show(); 0554 raise(); 0555 activateWindow(); 0556 } 0557 0558 void KMouseTool::aboutSelected() 0559 { 0560 mHelpMenu->aboutApplication(); 0561 } 0562 0563 KMouseToolTray::KMouseToolTray(QWidget *parent) 0564 : KStatusNotifierItem(parent) 0565 { 0566 setStatus(KStatusNotifierItem::Active); 0567 mStartStopAct = contextMenu()->addAction(i18nc("Start tracking the mouse", "&Start"), this, &KMouseToolTray::startStopSelected); 0568 contextMenu()->addSeparator(); 0569 QAction *act = contextMenu()->addAction(i18n("&Configure KMouseTool..."), this, &KMouseToolTray::configureSelected); 0570 act->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0571 contextMenu()->addSeparator(); 0572 act = contextMenu()->addAction(i18n("KMousetool &Handbook"), this, &KMouseToolTray::helpSelected); 0573 act->setIcon(QIcon::fromTheme(QStringLiteral("help-contents"))); 0574 act = contextMenu()->addAction(i18n("&About KMouseTool"), this, &KMouseToolTray::aboutSelected); 0575 act->setIcon(QIcon::fromTheme(QStringLiteral("kmousetool"))); 0576 } 0577 0578 KMouseToolTray::~KMouseToolTray() 0579 { 0580 } 0581 0582 void KMouseToolTray::updateStartStopText(bool mousetool_is_running) 0583 { 0584 QIcon icon; 0585 0586 if (mousetool_is_running) { 0587 mStartStopAct->setText(i18n("&Stop")); 0588 icon = QIcon::fromTheme(QStringLiteral("kmousetool_on")); 0589 } else { 0590 mStartStopAct->setText(i18nc("Start tracking the mouse", "&Start")); 0591 icon = QIcon::fromTheme(QStringLiteral("kmousetool_off")); 0592 } 0593 setIconByPixmap(icon); 0594 } 0595 0596 #include "moc_kmousetool.cpp"