OSU navigator

Use key "z" and "x" as mouse left and right, and displays your mouse cursor as osu yellow mouse cursor

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

/* eslint-disable no-multi-spaces */

// ==UserScript==
// @name               OSU navigator
// @name:zh-CN         鼠标OSU化
// @name:zh-TW         滑鼠OSU化
// @name:ko            마우스 OSU화
// @namespace          OSU_NAVIGATOR
// @version            0.2
// @description        Use key "z" and "x" as mouse left and right, and displays your mouse cursor as osu yellow mouse cursor
// @description:zh-CN  使用"z"和"x"键作为鼠标左右键,并将鼠标样式显示为圆形亮黄osu光标
// @description:zh-TW  使用“z”和“x”鍵作為滑鼠左右鍵,並將滑鼠樣式顯示為圓形亮黃osu光標
// @description:ko     "z" 및 "x" 키를 마우스 좌우 키로 사용하고 마우스 스타일을 둥근 밝은 노란색 osu 커서로 표시합니다
// @author             PY-DNG
// @license            MIT
// @match              http*://*/*
// @icon               https://api.iowen.cn/favicon/get.php?url=osu.ppy.sh
// @grant              none
// ==/UserScript==

(function() {
    'use strict';

    // Arguments: level=LogLevel.Info, logContent, asObject=false
    // Needs one call "DoLog();" to get it initialized before using it!
    function DoLog() {
        // Global log levels set
        window.LogLevel = {
            None: 0,
            Error: 1,
            Success: 2,
            Warning: 3,
            Info: 4,
        }
        window.LogLevelMap = {};
        window.LogLevelMap[LogLevel.None]     = {prefix: ''          , color: 'color:#ffffff'}
        window.LogLevelMap[LogLevel.Error]    = {prefix: '[Error]'   , color: 'color:#ff0000'}
        window.LogLevelMap[LogLevel.Success]  = {prefix: '[Success]' , color: 'color:#00aa00'}
        window.LogLevelMap[LogLevel.Warning]  = {prefix: '[Warning]' , color: 'color:#ffa500'}
        window.LogLevelMap[LogLevel.Info]     = {prefix: '[Info]'    , color: 'color:#888888'}
        window.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}

        // Current log level
        DoLog.logLevel = LogLevel.Info; // Info Warning Success Error

        // Log counter
        DoLog.logCount === undefined && (DoLog.logCount = 0);
        if (++DoLog.logCount > 512) {
            console.clear();
            DoLog.logCount = 0;
        }

        // Get args
        let level, logContent, asObject;
        switch (arguments.length) {
            case 1:
                level = LogLevel.Info;
                logContent = arguments[0];
                asObject = false;
                break;
            case 2:
                level = arguments[0];
                logContent = arguments[1];
                asObject = false;
                break;
            case 3:
                level = arguments[0];
                logContent = arguments[1];
                asObject = arguments[2];
                break;
            default:
                level = LogLevel.Info;
                logContent = 'DoLog initialized.';
                asObject = false;
                break;
        }

        // Log when log level permits
        if (level <= DoLog.logLevel) {
            let msg = '%c' + LogLevelMap[level].prefix;
            let subst = LogLevelMap[level].color;

            if (asObject) {
                msg += ' %o';
            } else {
                switch(typeof(logContent)) {
                    case 'string': msg += ' %s'; break;
                    case 'number': msg += ' %d'; break;
                    case 'object': msg += ' %o'; break;
                }
            }

            console.log(msg, subst, logContent);
        }
    }
    DoLog();

	main();
	function main() {
		// Terminal element event listeners
		/*
		for (const elm of document.querySelectorAll('*')) {
			dealElement(elm);
		}
		document.addEventListener('DOMNodeInserted', (e) => {if(!e.target){debugger;}dealElement(e.target);});
		*/
		document.addEventListener('mousemove', function(e) {
			const elm = document.elementFromPoint(e.x, e.y);
			removeListeners(window.OSUMouse.target);
			addListeners(elm);
			window.OSUMouse.target = elm;
		}, {
			capture: true,
			passive: true
		})

		// Global event listeners
		document.body.onkeydown = keyDownListener;
		document.body.onkeyup = keyUpListener;

		// Global status recorder
		window.OSUMouse = {
			ctrlKey: false,
			altKey: false,
			shiftKey: false,
			metaKey: false,
			target: document.body
		};

		// Change cursor
		osuMouseCursor();
	}

	function addListeners(elm) {
		elm.addEventListener('mousemove', recordMouseStatus);
	}

	function removeListeners(elm) {
		// Record mouse status
		elm.removeEventListener('mousemove', recordMouseStatus);
	}

	function recordMouseStatus(e) {
		const props = ['screenX', 'screenY', 'clientX', 'clientY', 'relatedTarget', 'region']
		for (const prop of props) {
			window.OSUMouse[prop] = e[prop];
		}
	}

	function keyDownListener(e) {
		switch (e.key) {
			case 'Control':
				window.OSUMouse.ctrlKey = true;
				//DoLog(window.OSUMouse);
				break;
			case 'Shift':
				window.OSUMouse.shiftKey = true;
				//DoLog(window.OSUMouse);
				break;
			case 'Alt':
				window.OSUMouse.altKey = true;
				//DoLog(window.OSUMouse);
				break;
			case 'Meta':
				window.OSUMouse.metaKey = true;
				//DoLog(window.OSUMouse);
				break;
			case 'z':
			case 'Z':
			case 'x':
			case 'X':
				dispatchMouseDown(e.target);
				break;
		}
	}

	function keyUpListener(e) {
		switch (e.key) {
			case 'Control':
				window.OSUMouse.ctrlKey = false;
				//DoLog(window.OSUMouse);
				break;
			case 'Shift':
				window.OSUMouse.shiftKey = false;
				//DoLog(window.OSUMouse);
				break;
			case 'Alt':
				window.OSUMouse.altKey = false;
				//DoLog(window.OSUMouse);
				break;
			case 'Meta':
				window.OSUMouse.metaKey = false;
				//DoLog(window.OSUMouse);
				break;
			case 'z':
			case 'Z':
				!inputing() && dispatchMouseLeftUp();
				break;
			case 'x':
			case 'X':
				!inputing() && dispatchMouseRightUp();
				break;
		}
	}

	function dispatchMouseDown() {
		const mouseEventInit = {};
		for (const [key, value] of Object.entries(window.OSUMouse)) {
			mouseEventInit[key] = value;
		}
		mouseEventInit.bubbles = true;
		const focusEventInit = {relatedTarget: window.OSUMouse.relatedTarget, bubbles: true};
		const mouseLeft = new MouseEvent('mousedown', mouseEventInit);
		const focus = new FocusEvent('focus', focusEventInit);
		window.OSUMouse.target.dispatchEvent(focus);
		window.OSUMouse.target.dispatchEvent(mouseLeft);
	}

	function dispatchMouseLeftUp() {
		const mouseEventInit = {};
		for (const [key, value] of Object.entries(window.OSUMouse)) {
			mouseEventInit[key] = value;
		}
		mouseEventInit.bubbles = true;
		const mouseRight = new MouseEvent('mouseup', mouseEventInit);
		const mouseclick = new MouseEvent('click', mouseEventInit);
		window.OSUMouse.target.dispatchEvent(mouseRight);
		window.OSUMouse.target.dispatchEvent(mouseclick);
	}

	function dispatchMouseRightUp() {
		const mouseEventInit = {};
		for (const [key, value] of Object.entries(window.OSUMouse)) {
			mouseEventInit[key] = value;
		}
		mouseEventInit.bubbles = true;
		const mousecontextmenu = new MouseEvent('contentmenu', mouseEventInit);
		window.OSUMouse.target.dispatchEvent(mousecontextmenu);
	}

	function inputing() {
		return document.activeElement && [HTMLInputElement, HTMLTextAreaElement].some((o) => (document.activeElement instanceof o));
	}

	function osuMouseCursor() {
		// Cursor
		const OSUCursor = 'data:image/x-icon;base64,AAACAAEAICAAAA8ADwCoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvXAQDW9gEA1vYBANb2AQDW9gEAu9cBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADW9gEA1vYDANb2BADW9gYA1vYIANb2CgDW9gsA1vYLANb2CgDW9ggA1vYGANb2BADW9gIA1vYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADW9gEA1vYDANb2BwDW9gsA1vYQANb2FQDW9hoA1vYdANb2HwDW9h8A1vYdANb2GQDW9hUA1vYPANb2CwDW9gYA1vYDANb2AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1vYCANb2BgDW9gsA1vYSANb2GwDW9iQA1vYtANb2NADW9joA1vY9ANb2PQDW9joA1vY0ANb2LQDW9iQA1vYbANb2EgDW9gsA1vYGANb2AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANb2AwDW9ggA1vYPANb2GgDW9icA1vY0ANb2QwDW9lAA1vZaANb2YQDW9mQA1vZkANb2YQDW9loA1vZPANb2QgDW9jQA1vYnANb2GgDW9g8A1vYHANb2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADW9gMA1vYIANb2EgDW9h8A1vYvANb2QwDW9lYA1vZqANb2egDW9oYA1vaOANb2kwDW9pMA1vaOANb2hgDW9noA1vZqANb2VgDW9kIA1vYvANb2HgDW9hEA1vYIANb2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1vYCANb2CADW9hIA1vYhANb2NQDW9k4A1vZmANb2fgDW9pMA1vakANb2sADW9rcA1va7ANb2uwDW9rcA1vawANb2pADW9pMA1vZ+ANb2ZQDW9k0A1vY1ANb2IQDW9hEA1vYHANb2AgAAAAAAAAAAAAAAAAAAAAAAAAAAANb2AQDW9gYA1vYPANb2HwDW9jUA1vZQANb2bgDW9osA1vakANb2uQDW9sgA1vbSANb22ADW9tsA1vbbANb22ADW9tIA1vbIANb2uQDW9qQA1vaLANb2bQDW9lAA1vY1ANb2HgDW9g8A1vYGANb2AQAAAAAAAAAAAAAAAAAAAAAA1vYDANb2CwDW9hoA1vYvANb2TgDW9m4A1vaOANb2rADW9sUA1vbWANb24wDW9uoA1vbuANb28ADW9vAA1vbvANb26gDW9uMA1vbXANb2xQDW9qwA1vaOANb2bQDW9k0A1vYvANb2GgDW9gsA1vYDAAAAAAAAAAAAAAAAANb2AQDW9gcA1vYSANb2JwDW9kMA1vZmANb2iwDW9qwA1vbIANb23gDW9usA1vb0ANL2+Tza+PtQ4vj8UOL4/Dza+PsA0vb5ANb29ADW9uwA1vbeANb2yQDW9qwA1vaLANb2ZQDW9kIA1vYnANb2EgDW9gYA1vYBAAAAAAAAAAAA1vYDANb2CwDW9hsA1vY0ANb2VgDW9n4A1vakANb2xQDW9t4A1vbuANb290XQ/P3m+/7+/P///////////////P///+b7/v5F0Pz9ANb29wDW9u4A1vbeANb2xQDW9qQA1vZ+ANb2VgDW9jQA1vYbANb2CwDW9gIAAAAAAAAAAADW9gQA1vYQANb2JADW9kMA1vZqANb2kwDW9rkA1vbWANb26wDW9vdx5fz9//////////////////////////////////////////9x5fz9ANb29wDW9uwA1vbXANb2uQDW9pMA1vZqANb2QgDW9iQA1vYPANb2BAAAAAAAAAAAANb2BgDW9hUA1vYtANb2UADW9noA1vakANb2yADW9uMA1vb0RdD8/f////////////////////////////////////////////////////9F0Pz9ANb29ADW9uMA1vbIANb2pADW9noA1vZPANb2LQDW9hUA1vYGAAAAAAC71wEA1vYIANb2GgDW9jQA1vZaANb2hgDW9rAA1vbSANb26gDS9vnm+/7+/////////////////////////////////////////////////////+b7/v4A0vb5ANb26gDW9tIA1vawANb2hgDW9loA1vY0ANb2GQDW9ggAu9cBANb2AQDW9goA1vYdANb2OgDW9mEA1vaOANb2twDW9tgA1vbuPNr4+/z//////////////////////////////////////////////////////////P///zza+PsA1vbvANb22ADW9rcA1vaOANb2YQDW9joA1vYdANb2CgDW9gEA1vYBANb2CwDW9h8A1vY9ANb2ZADW9pMA1va7ANb22wDW9vBQ4vj8////////////////////////////////////////////////////////////////UOL4/ADW9vAA1vbbANb2uwDW9pMA1vZkANb2PQDW9h8A1vYLANb2AQDW9gEA1vYLANb2HwDW9j0A1vZkANb2kwDW9rsA1vbbANb28FDi+Pz///////////////////////////////////////////////////////////////9Q4vj8ANb28ADW9tsA1va7ANb2kwDW9mQA1vY9ANb2HwDW9gsA1vYBANb2AQDW9goA1vYdANb2OgDW9mEA1vaOANb2twDW9tgA1vbvPNr4+/z//////////////////////////////////////////////////////////P///zza+PsA1vbuANb22ADW9rcA1vaOANb2YQDW9joA1vYdANb2CgDW9gEAu9cBANb2CADW9hkA1vY0ANb2WgDW9oYA1vawANb20gDW9uoA0vb55vv+/v/////////////////////////////////////////////////////m+/7+ANL2+QDW9uoA1vbSANb2sADW9oYA1vZaANb2NADW9hoA1vYIALvXAQAAAAAA1vYGANb2FQDW9i0A1vZPANb2egDW9qQA1vbIANb24wDW9vRF0Pz9/////////////////////////////////////////////////////0XQ/P0A1vb0ANb24wDW9sgA1vakANb2egDW9lAA1vYtANb2FQDW9gYAAAAAAAAAAADW9gQA1vYPANb2JADW9kIA1vZqANb2kwDW9rkA1vbXANb27ADW9vdx5fz9////////////////////////////////////////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';
		const CSSCursor = 'body {cursor: url("{C}"), auto !important;}'.replace('{C}', OSUCursor);
		addStyle(CSSCursor, 'osu_cursor');

		/*
		// Canvas
		const canvas = document.createElement('canvas');
		const CSSCanvas = '#osu_cursor_canvas {position: fixed; pointer-events: none; z-index: 99999999}';
		const img = new Image();
		img.onload = function() {
			const ctx = canvas.getContext('2d');
			const half = img.width / 2;
			canvas.width = img.width;
			canvas.height = img.height;
			ctx.drawImage(img, 0, 0);
			canvas.id = 'osu_cursor_canvas';
			document.body.addEventListener('mousemove', (e) => {
				canvas.style.top  = (e.clientY - half).toString() + 'px';
				canvas.style.left = (e.clientX - half).toString() + 'px';
			});
			document.body.appendChild(canvas);
		};
		img.src = OSUCursor;
		addStyle(CSSCanvas);
		*/
	}

	// Just stopPropagation and preventDefault
	function destroyEvent(e) {
		if (!e) {return false;};
		if (!e instanceof Event) {return false;};
		e.stopPropagation();
		e.preventDefault();
	}

	// Append a style text to document(<head>) with a <style> element
	function addStyle(css, id) {
		const style = document.createElement("style");
		id && (style.id = id);
		style.textContent = css;
		for (const elm of document.querySelectorAll('#'+id)) {
			elm.parentElement && elm.parentElement.removeChild(elm);
		}
        document.head.appendChild(style);
    }
})();