1 /*
2 SPDX-FileCopyrightText: 2002-2003 Jeff Roush <jeff@mousetool.com>
3 SPDX-FileCopyrightText: 2003 Olaf Schmidt <ojschmidt@kde.org>
4 SPDX-FileCopyrightText: 2003 Gunnar Schmi Dt <gunnar@schmi-dt.de>
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "kmousetool.h"
9
10 // Needs to be be before X11/Intrinsic.h because of qtextstream.h
11 #include <phonon/MediaObject>
12
13 #include <X11/Xmd.h>
14 #include <X11/Intrinsic.h> /* Intrinsics Definitions*/
15 #include <X11/StringDefs.h> /* Standard Name-String definitions*/
16 #include <X11/extensions/xtestext1.h> /* Standard Name-String definitions*/
17 #include <X11/extensions/XTest.h> /* Standard Name-String definitions*/
18 #include <fixx11h.h>
19
20 #include <QAbstractEventDispatcher>
21 #include <QApplication>
22 #include <QDesktopWidget>
23 #include <QIcon>
24 #include <QMenu>
25 #include <QStandardPaths>
26 #include <QFile>
27 #include <QFileInfo>
28
29 #include <KConfig>
30 #include <KConfigGroup>
31 #include <KHelpMenu>
32 #include <KIconLoader>
33 #include <KLocalizedString>
34 #include <KMessageBox>
35 #include <KSharedConfig>
36 #include <KStandardGuiItem>
37
38 int currentXPosition;
39 int currentYPosition;
40
41 #undef long
42
43 int leftButton;
44 int rightButton;
45
46 /**
47 * Gnarly X functions
48 */
49 void queryPointer(Window *pidRoot, int *pnRx, int *pnRy, int *pnX, int *pnY, unsigned int *puMask);
50 int CursorHasMoved(int minMovement);
51 void getMouseButtons();
52 void LeftClick();
53 void RightClick();
54 void DoubleClick();
55 void LeftDn();
56 void LeftUp();
57
58 // Declarations
59 Display *display;
60
61
init_vars()62 void KMouseTool::init_vars()
63 {
64 continue_timer = 1;
65 tick_count = 0;
66 max_ticks = dwell_time+1;
67 mouse_is_down = false;
68
69 loadOptions();
70
71 // If the ~/.mousetool directory doesn't exist, create it
72 mSoundFileName = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral( "sounds/mousetool_tap.wav" ));
73 mplayer = Phonon::createPlayer(Phonon::AccessibilityCategory);
74 mplayer->setParent(this);
75
76 // find application file
77 appfilename = QStandardPaths::findExecutable(QStringLiteral( "kmousetool" ));
78
79 // find the user's autostart directory
80 autostartdirname = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/autostart-scripts/");
81
82 QDesktopWidget *d = QApplication::desktop();
83 int w = d->width();
84 int h = d->height();
85 MTStroke::setUpperLeft(0,0);
86 MTStroke::setUpperRight(w-1,0);
87 MTStroke::setLowerLeft(0,h-1);
88 MTStroke::setLowerRight(w-1,h-1);
89
90 buttonQuit = buttonBox->addButton(QString(), QDialogButtonBox::RejectRole);
91 }
92
resetSettings()93 void KMouseTool::resetSettings()
94 {
95 cbDrag ->setChecked(smart_drag_on);
96 cbStart->setChecked(isAutostart());
97 cbClick->setChecked(playSound);
98 cbStroke->setChecked(strokesEnabled);
99 movementEdit->setValue(min_movement);
100 dwellTimeEdit->setValue(dwell_time);
101 dragTimeEdit->setValue(drag_time);
102 settingsChanged();
103 }
104
setDefaultSettings()105 void KMouseTool::setDefaultSettings()
106 {
107 cbDrag ->setChecked(false);
108 cbStart->setChecked(false);
109 cbClick->setChecked(false);
110 cbStroke->setChecked(false);
111 movementEdit->setValue(5);
112 dwellTimeEdit->setValue(5);
113 dragTimeEdit->setValue(3);
114 settingsChanged();
115 }
116
117
timerEvent(QTimerEvent *)118 void KMouseTool::timerEvent( QTimerEvent * )
119 {
120 if (!mousetool_is_running)
121 return;
122
123 if (!continue_timer) {
124 QAbstractEventDispatcher::instance()->unregisterTimers(this);
125 return;
126 }
127
128 max_ticks = dwell_time + drag_time;
129 stroke.addPt(currentXPosition, currentYPosition);
130
131 moving = moving?CursorHasMoved(1):CursorHasMoved(min_movement);
132 if (moving) {
133 if (mousetool_just_started) {
134 mousetool_just_started = false;
135 tick_count = max_ticks;
136 } else {
137 tick_count = 0;
138 }
139 return;
140 }
141
142 if (tick_count<max_ticks)
143 tick_count++;
144
145 // If the mouse has paused ...
146 if (tick_count==dwell_time) {
147 int strokeType = stroke.getStrokeType();
148 getMouseButtons();
149
150 // if we're dragging the mouse, ignore stroke type
151 if (mouse_is_down) {
152 normalClick();
153 return;
154 }
155
156 if (!strokesEnabled) {
157 normalClick();
158 return;
159 }
160 if (strokeType == MTStroke::DontClick)
161 return;
162 if (strokeType==MTStroke::bumped_mouse)
163 return;
164
165 if (strokeType==MTStroke::RightClick || strokeType==MTStroke::UpperRightStroke)
166 RightClick();
167 else if (strokeType==MTStroke::DoubleClick || strokeType==MTStroke::LowerLeftStroke)
168 DoubleClick();
169 else
170 normalClick();
171 }
172 }
173
normalClick()174 void KMouseTool::normalClick()
175 {
176 if (smart_drag_on) {
177 if (!mouse_is_down) {
178 LeftDn();
179 mouse_is_down = true;
180 tick_count = 0;
181 playTickSound();
182 } else if (mouse_is_down) {
183 LeftUp();
184 mouse_is_down = false;
185 tick_count = max_ticks;
186 }
187 } else {
188 // not smart_drag_on
189 LeftClick();
190 playTickSound();
191 }
192 }
193
194 // This function isn't happy yet.
playTickSound()195 void KMouseTool::playTickSound()
196 {
197 if (!playSound)
198 return;
199
200 mplayer->setCurrentSource(QUrl::fromLocalFile(mSoundFileName));
201 mplayer->play();
202 }
203
KMouseTool(QWidget * parent,const char * name)204 KMouseTool::KMouseTool(QWidget *parent, const char *name)
205 : QWidget(parent), helpMenu(new KHelpMenu(nullptr))
206 {
207 setupUi(this);
208 setObjectName( QLatin1String( name ));
209 init_vars();
210 resetSettings();
211
212 connect(movementEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
213 connect(dwellTimeEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
214 connect(dragTimeEdit, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
215 connect(cbDrag, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged);
216 connect(cbStroke, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged);
217 connect(cbClick, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged);
218 connect(cbStart, &QCheckBox::stateChanged, this, &KMouseTool::settingsChanged);
219
220 connect(buttonStartStop, &QAbstractButton::clicked, this, &KMouseTool::startStopSelected);
221 connect(buttonBoxSettings->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KMouseTool::defaultSelected);
222 connect(buttonBoxSettings->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &KMouseTool::resetSelected);
223 connect(buttonBoxSettings->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KMouseTool::applySelected);
224 connect(buttonBox, &QDialogButtonBox::helpRequested, this, &KMouseTool::helpSelected);
225 connect(buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &KMouseTool::closeSelected);
226 KGuiItem::assign(buttonQuit, KStandardGuiItem::quit());
227 connect(buttonQuit, &QAbstractButton::clicked, this, &KMouseTool::quitSelected);
228
229 // When we first start, it's nice to not click immediately.
230 // So, tell MT we're just starting
231 mousetool_just_started = true;
232
233 startTimer(100);
234 trayIcon = new KMouseToolTray (this);
235 updateStartStopText ();
236 connect(trayIcon, &KMouseToolTray::startStopSelected, this, &KMouseTool::startStopSelected);
237 connect(trayIcon, &KMouseToolTray::configureSelected, this, &KMouseTool::configureSelected);
238 connect(trayIcon, &KMouseToolTray::aboutSelected, this, &KMouseTool::aboutSelected);
239 connect(trayIcon, &KMouseToolTray::helpSelected, this, &KMouseTool::helpSelected);
240 connect(trayIcon, SIGNAL(quitSelected()), this, SLOT(quitSelected()));
241 }
242
~KMouseTool()243 KMouseTool::~KMouseTool()
244 {
245 }
246
247 // *********************************************************
248 // Raw X functions
249 // Begin mouse monitoring and event generation code.
250 // these should be moved to a separate file.
getMouseButtons()251 void getMouseButtons()
252 {
253 unsigned char buttonMap[3];
254 const int buttonCount = XGetPointerMapping(display, buttonMap, 3);
255 switch (buttonCount)
256 {
257 case 0:
258 case 1:
259 // ### should not happen
260 leftButton = 1;
261 rightButton = 1;
262 break;
263 case 2:
264 leftButton = buttonMap[0];
265 rightButton = buttonMap[1];
266 break;
267 default:
268 leftButton = buttonMap[0];
269 rightButton = buttonMap[2];
270 break;
271 }
272 }
273
LeftClick()274 void LeftClick()
275 {
276 XTestFakeButtonEvent(display, leftButton, true, 0);
277 XTestFakeButtonEvent(display, leftButton, false, 0);
278 }
279
DoubleClick()280 void DoubleClick()
281 {
282 XTestFakeButtonEvent(display, leftButton, true, 0);
283 XTestFakeButtonEvent(display, leftButton, false, 100);
284 XTestFakeButtonEvent(display, leftButton, true, 200);
285 XTestFakeButtonEvent(display, leftButton, false, 300);
286 }
287
RightClick()288 void RightClick()
289 {
290 XTestFakeButtonEvent(display, rightButton, true, 0);
291 XTestFakeButtonEvent(display, rightButton, false, 0);
292 }
293
LeftDn()294 void LeftDn()
295 {
296 XTestFakeButtonEvent(display, leftButton, true, 0);
297 }
298
LeftUp()299 void LeftUp()
300 {
301 XTestFakeButtonEvent(display, leftButton, false, 0);
302 }
303
304
queryPointer(Window * pidRoot,int * pnRx,int * pnRy,int * pnX,int * pnY,unsigned int * puMask)305 void queryPointer(Window *pidRoot, int *pnRx, int *pnRy, int *pnX, int *pnY, unsigned int *puMask)
306 {
307 Window id2, idRoot;
308 int screen_num;
309
310 screen_num = DefaultScreen(display);
311 idRoot = RootWindow(display,screen_num);
312 XQueryPointer(display, idRoot, &idRoot, &id2, pnRx, pnRy, pnX, pnY, puMask);
313
314 *pidRoot = idRoot;
315 }
316
317 // return 1 if cursor has moved, 0 otherwise
CursorHasMoved(int minMovement)318 int CursorHasMoved (int minMovement)
319 {
320 static int nOldRootX = -1;
321 static int nOldRootY = -1;
322
323 // XEvent evButtonEvent;
324 Window idRootWin;
325 int nRootX, nRootY, nWinX, nWinY;
326 unsigned int uintMask;
327
328 queryPointer(&idRootWin, &nRootX, &nRootY, &nWinX, &nWinY, &uintMask);
329
330 int nRes = 0;
331 if ((nRootX>nOldRootX?nRootX-nOldRootX:nOldRootX-nRootX) >= minMovement)
332 nRes = 1;
333 if ((nRootY>nOldRootY?nRootY-nOldRootY:nOldRootY-nRootY) >= minMovement)
334 nRes = 1;
335
336 currentXPosition = nRootX;
337 currentYPosition = nRootY;
338
339 if (nRes) {
340 nOldRootX = nRootX;
341 nOldRootY = nRootY;
342 }
343
344 return nRes;
345 }
346 // End mouse monitoring and event creation code
347
348
isAutostart()349 bool KMouseTool::isAutostart()
350 {
351 QString sym = autostartdirname;
352 sym += QLatin1String( "kmousetool" ); // sym is now full path to symlink
353 QFileInfo fi(sym);
354
355 return fi.exists();
356 }
357
setAutostart(bool start)358 void KMouseTool::setAutostart (bool start)
359 {
360 QString sym = autostartdirname;
361 sym += QLatin1String( "kmousetool" ); // sym is now full path to symlink
362 QFileInfo fi(sym);
363
364 if (start) {
365 if (!fi.exists()) // if it doesn't exist, make it
366 QFile(appfilename).link(sym);
367 } else {
368 if (fi.exists()) // if it exists, delete it
369 QFile(sym).remove();
370 }
371 }
372
applySettings()373 bool KMouseTool::applySettings()
374 {
375 int drag, dwell;
376
377 dwell = dwellTimeEdit->value();
378 drag = dragTimeEdit->value() ;
379
380 // The drag time must be less than the dwell time
381 if ( dwell < drag) {
382 KMessageBox::sorry(this, i18n("The drag time must be less than or equal to the dwell time."), i18n("Invalid Value"));
383 return false;
384 }
385
386 // if we got here, we must be okay.
387 min_movement = movementEdit->value();
388 smart_drag_on = cbDrag->isChecked();
389 playSound = cbClick->isChecked();
390 strokesEnabled = cbStroke->isChecked();
391 setAutostart (cbStart->isChecked());
392
393 dwell_time = dwell;
394 drag_time = drag;
395 tick_count = max_ticks;
396
397 saveOptions();
398 settingsChanged();
399 return true;
400 }
401
402 // Save options to kmousetoolrc file
loadOptions()403 void KMouseTool::loadOptions()
404 {
405 KConfigGroup cfg = KSharedConfig::openConfig()->group("UserOptions");
406
407 playSound = cfg.readEntry("AudibleClick", false);
408 smart_drag_on = cfg.readEntry("SmartDrag", false);
409
410 dwell_time = cfg.readEntry("DwellTime",5);
411 drag_time = cfg.readEntry("DragTime",3);
412 min_movement = cfg.readEntry("Movement",5);
413 strokesEnabled = cfg.readEntry("strokesEnabled", false);
414
415 QPoint p;
416 int x = cfg.readEntry("x",0);
417 int y = cfg.readEntry("y",0);
418 p.setX(x);
419 p.setY(y);
420 move(p);
421
422 mousetool_is_running = cfg.readEntry("MouseToolIsRunning", false);
423 display = XOpenDisplay(nullptr);
424 }
425
426 // Save options to kmousetoolrc file
saveOptions()427 void KMouseTool::saveOptions()
428 {
429 QPoint p = pos();
430 int x = p.x();
431 int y = p.y();
432
433 KConfigGroup cfg = KSharedConfig::openConfig()->group("ProgramOptions");
434 cfg = KSharedConfig::openConfig()->group("UserOptions");
435 cfg.writeEntry("x", x);
436 cfg.writeEntry("y", y);
437 cfg.writeEntry("strokesEnabled", strokesEnabled);
438 cfg.writeEntry("IsMinimized", isHidden());
439 cfg.writeEntry("DwellTime", dwell_time);
440 cfg.writeEntry("DragTime", drag_time);
441 cfg.writeEntry("Movement", min_movement);
442 cfg.writeEntry("SmartDrag", smart_drag_on);
443 cfg.writeEntry("AudibleClick", playSound);
444 cfg.writeEntry("MouseToolIsRunning", mousetool_is_running);
445 cfg.sync();
446 }
447
updateStartStopText()448 void KMouseTool::updateStartStopText()
449 {
450 if (mousetool_is_running) {
451 buttonStartStop->setText(i18n("&Stop"));
452 } else {
453 buttonStartStop->setText(i18nc("Start tracking the mouse", "&Start"));
454 }
455 trayIcon->updateStartStopText(mousetool_is_running);
456 }
457
newSettings()458 bool KMouseTool::newSettings()
459 {
460 return ((dwell_time != dwellTimeEdit->value()) ||
461 (drag_time != dragTimeEdit->value()) ||
462 (min_movement != movementEdit->value()) ||
463 (smart_drag_on != cbDrag->isChecked()) ||
464 (playSound != cbClick->isChecked()) ||
465 (strokesEnabled != cbStroke->isChecked()) ||
466 (isAutostart() != cbStart->isChecked()));
467 }
468
defaultSettings()469 bool KMouseTool::defaultSettings()
470 {
471 return ((5 == dwellTimeEdit->value()) &&
472 (3 == dragTimeEdit->value()) &&
473 (5 == movementEdit->value()) &&
474 !cbDrag->isChecked() &&
475 !cbClick->isChecked() &&
476 !cbStroke->isChecked() &&
477 !cbStart->isChecked());
478 }
479
480 /******** SLOTS **********/
481
482 // Value or state changed
settingsChanged()483 void KMouseTool::settingsChanged ()
484 {
485 buttonBoxSettings->button(QDialogButtonBox::Reset)->setEnabled (newSettings());
486 buttonBoxSettings->button(QDialogButtonBox::Apply)->setEnabled (newSettings());
487 buttonBoxSettings->button(QDialogButtonBox::RestoreDefaults)->setDisabled (defaultSettings());
488 }
489
490 // Buttons within the dialog
startStopSelected()491 void KMouseTool::startStopSelected()
492 {
493 mousetool_is_running = !mousetool_is_running;
494 updateStartStopText();
495 }
496
defaultSelected()497 void KMouseTool::defaultSelected()
498 {
499 setDefaultSettings();
500 }
501
resetSelected()502 void KMouseTool::resetSelected()
503 {
504 resetSettings();
505 }
506
applySelected()507 void KMouseTool::applySelected()
508 {
509 applySettings();
510 }
511
512 // Buttons at the bottom of the dialog
helpSelected()513 void KMouseTool::helpSelected()
514 {
515 helpMenu->appHelpActivated();
516 }
517
closeSelected()518 void KMouseTool::closeSelected()
519 {
520 if (newSettings())
521 {
522 int answer = KMessageBox::questionYesNoCancel (this,
523 i18n("There are unsaved changes in the active module.\nDo you want to apply the changes before closing the configuration window or discard the changes?"),
524 i18n("Closing Configuration Window"),
525 KStandardGuiItem::apply(), KStandardGuiItem::discard(),
526 KStandardGuiItem::cancel(), QStringLiteral( "AutomaticSave" ));
527 if (answer == KMessageBox::Yes)
528 applySettings();
529 else if (answer == KMessageBox::No)
530 resetSettings();
531 if (answer != KMessageBox::Cancel)
532 hide();
533 } else {
534 hide();
535 }
536 }
537
quitSelected()538 void KMouseTool::quitSelected()
539 {
540 if (newSettings())
541 {
542 int answer = KMessageBox::questionYesNoCancel (this,
543 i18n("There are unsaved changes in the active module.\nDo you want to apply the changes before quitting KMousetool or discard the changes?"),
544 i18n("Quitting KMousetool"),
545 KStandardGuiItem::apply(), KStandardGuiItem::discard(),
546 KStandardGuiItem::cancel(), QStringLiteral( "AutomaticSave" ));
547 if (answer == KMessageBox::Yes)
548 applySettings();
549 if (answer != KMessageBox::Cancel)
550 {
551 saveOptions();
552 qApp->quit();
553 }
554 } else {
555 saveOptions();
556 qApp->quit();
557 }
558 }
559
560 // Menu functions
configureSelected()561 void KMouseTool::configureSelected()
562 {
563 show();
564 raise();
565 activateWindow();
566 }
567
aboutSelected()568 void KMouseTool::aboutSelected()
569 {
570 helpMenu->aboutApplication();
571 }
572
573
574
KMouseToolTray(QWidget * parent)575 KMouseToolTray::KMouseToolTray (QWidget *parent) : KStatusNotifierItem(parent)
576 {
577 setStatus(KStatusNotifierItem::Active);
578 startStopAct = contextMenu()->addAction (i18nc("Start tracking the mouse", "&Start"), this, &KMouseToolTray::startStopSelected);
579 contextMenu()->addSeparator();
580 QAction* act;
581 act = contextMenu()->addAction (i18n("&Configure KMouseTool..."), this, &KMouseToolTray::configureSelected);
582 act->setIcon(QIcon::fromTheme(QStringLiteral( "configure" )));
583 contextMenu()->addSeparator();
584 act = contextMenu()->addAction (i18n("KMousetool &Handbook"), this, &KMouseToolTray::helpSelected);
585 act->setIcon(QIcon::fromTheme(QStringLiteral( "help-contents" )));
586 act = contextMenu()->addAction (i18n("&About KMouseTool"), this, &KMouseToolTray::aboutSelected);
587 act->setIcon(QIcon::fromTheme(QStringLiteral( "kmousetool" )));
588 }
589
~KMouseToolTray()590 KMouseToolTray::~KMouseToolTray() {
591 }
592
updateStartStopText(bool mousetool_is_running)593 void KMouseToolTray::updateStartStopText(bool mousetool_is_running)
594 {
595 QIcon icon;
596
597 if (mousetool_is_running) {
598 startStopAct->setText(i18n("&Stop"));
599 icon = KIconLoader::global()->loadIcon(QStringLiteral( "kmousetool_on" ), KIconLoader::Small);
600 } else {
601 startStopAct->setText(i18nc("Start tracking the mouse", "&Start"));
602 icon = KIconLoader::global()->loadIcon(QStringLiteral( "kmousetool_off" ), KIconLoader::Small);
603 }
604 setIconByPixmap (icon);
605 }
606