Pause Mouse Movement for Website

Allows toggling pause on website's mousemove listeners, while local cursor still moves.

  1. // ==UserScript==
  2. // @name Pause Mouse Movement for Website
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Allows toggling pause on website's mousemove listeners, while local cursor still moves.
  6. // @author Your Name Here
  7. // @match *://*/*
  8. // @grant none
  9. // @run-at document-start
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // --- Configuration ---
  17. const TOGGLE_KEY = 'm'; // Key to press for toggling
  18. const REQUIRE_CTRL = true; // Require Ctrl key?
  19. const REQUIRE_ALT = true; // Require Alt key?
  20. const REQUIRE_SHIFT = false; // Require Shift key?
  21. // --- End Configuration ---
  22.  
  23. let isPaused = false; // Initial state: website mouse movement is NOT paused
  24. const listenerMap = new Map(); // Stores mapping: originalListener -> wrappedListener
  25. let statusIndicator = null; // Reference to the status indicator element
  26.  
  27. console.log("Pause Mouse Movement Script: Initializing.");
  28.  
  29. // --- Status Indicator ---
  30. function createStatusIndicator() {
  31. const indicator = document.createElement('div');
  32. indicator.style.position = 'fixed';
  33. indicator.style.bottom = '10px';
  34. indicator.style.right = '10px';
  35. indicator.style.padding = '5px 10px';
  36. indicator.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  37. indicator.style.color = 'white';
  38. indicator.style.fontSize = '12px';
  39. indicator.style.borderRadius = '3px';
  40. indicator.style.zIndex = '99999'; // Ensure it's on top
  41. indicator.style.fontFamily = 'sans-serif';
  42. indicator.style.pointerEvents = 'none'; // Don't let it interfere with mouse events
  43. document.body.appendChild(indicator);
  44. return indicator;
  45. }
  46.  
  47. function updateStatusIndicator() {
  48. if (!statusIndicator && document.body) {
  49. statusIndicator = createStatusIndicator();
  50. }
  51. if (statusIndicator) {
  52. statusIndicator.textContent = `Website Mouse: ${isPaused ? 'Paused' : 'Active'} (Toggle: ${REQUIRE_CTRL ? 'Ctrl+' : ''}${REQUIRE_ALT ? 'Alt+' : ''}${REQUIRE_SHIFT ? 'Shift+' : ''}${TOGGLE_KEY.toUpperCase()})`;
  53. statusIndicator.style.backgroundColor = isPaused ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 128, 0, 0.7)'; // Red when paused, Green when active
  54. }
  55. }
  56.  
  57. // --- Toggle Function ---
  58. function togglePause() {
  59. isPaused = !isPaused;
  60. console.log(`Pause Mouse Movement Script: Website mousemove listeners ${isPaused ? 'paused' : 'activated'}.`);
  61. updateStatusIndicator();
  62. }
  63.  
  64. // --- Keyboard Shortcut Listener ---
  65. document.addEventListener('keydown', (event) => {
  66. // Check if the pressed key and modifiers match the configuration
  67. if (event.key.toLowerCase() === TOGGLE_KEY.toLowerCase() &&
  68. event.ctrlKey === REQUIRE_CTRL &&
  69. event.altKey === REQUIRE_ALT &&
  70. event.shiftKey === REQUIRE_SHIFT)
  71. {
  72. // Prevent default action if the key combination might have one
  73. event.preventDefault();
  74. event.stopPropagation();
  75. togglePause();
  76. }
  77. }, true); // Use capture phase to catch before page scripts
  78.  
  79. // --- Intercept addEventListener ---
  80. const originalAddEventListener = EventTarget.prototype.addEventListener;
  81. EventTarget.prototype.addEventListener = function(type, listener, options) {
  82. if (type === 'mousemove' && typeof listener === 'function') {
  83. // Only wrap mousemove listeners
  84. if (!listenerMap.has(listener)) {
  85. const wrappedListener = function(...args) {
  86. if (!isPaused) {
  87. // If not paused, call the original listener
  88. // Use Reflect.apply to correctly handle 'this' context and arguments
  89. try {
  90. Reflect.apply(listener, this, args);
  91. } catch (e) {
  92. console.error("Error executing original mousemove listener:", e);
  93. }
  94. }
  95. // If paused, do nothing, effectively blocking the listener
  96. };
  97. listenerMap.set(listener, wrappedListener);
  98. // Add the wrapped listener instead of the original
  99. originalAddEventListener.call(this, type, wrappedListener, options);
  100. // console.log("Wrapped mousemove listener added for:", this);
  101. } else {
  102. // Listener already wrapped, potentially re-adding? Call original to ensure browser handles it correctly.
  103. originalAddEventListener.call(this, type, listenerMap.get(listener), options);
  104. }
  105. } else {
  106. // For all other event types, call the original function directly
  107. originalAddEventListener.call(this, type, listener, options);
  108. }
  109. };
  110.  
  111. // --- Intercept removeEventListener ---
  112. const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
  113. EventTarget.prototype.removeEventListener = function(type, listener, options) {
  114. if (type === 'mousemove' && typeof listener === 'function') {
  115. // If it's a mousemove listener, try to remove the wrapped version
  116. const wrappedListener = listenerMap.get(listener);
  117. if (wrappedListener) {
  118. // Remove the wrapped listener
  119. originalRemoveEventListener.call(this, type, wrappedListener, options);
  120. listenerMap.delete(listener); // Clean up the map
  121. // console.log("Wrapped mousemove listener removed for:", this);
  122. } else {
  123. // If no wrapper found (maybe added before script ran?), try removing original anyway
  124. originalRemoveEventListener.call(this, type, listener, options);
  125. }
  126. } else {
  127. // For all other event types, call the original function directly
  128. originalRemoveEventListener.call(this, type, listener, options);
  129. }
  130. };
  131.  
  132. console.log("Pause Mouse Movement Script: Event listener interception active.");
  133.  
  134. // Update status indicator once the body is available
  135. if (document.readyState === 'loading') {
  136. document.addEventListener('DOMContentLoaded', updateStatusIndicator);
  137. } else {
  138. // DOMContentLoaded has already fired
  139. updateStatusIndicator();
  140. }
  141.  
  142. })();