KMLibrary

KMLibrary for interacting with Mac KM

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/571968/1788810/KMLibrary.js

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         KMLibrary
// @namespace    https://greasyfork.org/users/28298
// @version      1.6
// @description  KMLibrary for interacting with Mac KM
// @author       Jerry
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      GNU GPLv3
// @noframes
// @require      https://greasyfork.org/scripts/456410-gmlibrary/code/GMLibrary.js
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @grant        GM_setClipboard
// @connect      localhost
// @run-at       document-start
// ==/UserScript==

// KM -> Hotkey (Mousetrap.bindGlobal) -> UserScript -> KM (requestKM webserver)
// ── Shared KM helpers ─────────────────────────────────────────────────────
// Possible return values: "windows", "macos-arm", "macos-intel", "ios", "android", "linux", "unknown"
function getOS() {
    const platform = navigator.platform?.toLowerCase() ?? '';
    const ua = navigator.userAgent?.toLowerCase() ?? '';

    if (platform.startsWith('win')) return 'windows';
    if (platform.startsWith('iphone') || platform.startsWith('ipad') || ua.includes('iphone') || ua.includes('ipad')) return 'ios';
    if (platform.startsWith('mac')) {
        // navigator.platform is 'MacIntel' for both Intel and Apple Silicon —
        // WebGL renderer is the most reliable client-side signal
        try {
            const canvas = document.createElement('canvas');
            const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
            if (gl) {
                const renderer = (gl.getParameter(gl.RENDERER) ?? '').toLowerCase();
                if (renderer.includes('apple m')) return 'macos-arm';
            }
        } catch (_) {}
        return 'macos-intel';
    }
    if (platform.startsWith('linux') || ua.includes('linux')) {
        return ua.includes('android') ? 'android' : 'linux';
    }

    return 'unknown';
}
const OS = getOS();
const isWindows   = OS === 'windows';
const isMacARM    = OS === 'macos-arm';
const isMacIntel  = OS === 'macos-intel';
const isMac       = OS === 'macos-arm' || OS === 'macos-intel';
const isIOS       = OS === 'ios';
const isAndroid   = OS === 'android';
const isLinux     = OS === 'linux';

function isInEditbox() {
    const activeEl = unsafeWindow.document.activeElement;
    if (!activeEl) return false;
    const tagName = activeEl.tagName.toLowerCase();
    if (tagName === 'textarea') return true;
    if (tagName === 'input') {
        const type = (activeEl.type || '').toLowerCase();
        return ['text', 'search', 'email', 'url', 'tel', 'password', 'number'].includes(type);
    }
    if (activeEl.isContentEditable) return true;
    const contentEditable = activeEl.getAttribute('contenteditable');
    return contentEditable === 'true' || contentEditable === '';
}

function getPageContext() {
    const sel = unsafeWindow.getSelection();
    return {
        url: unsafeWindow.location.href,
        title: unsafeWindow.document.title,
        isInEditbox: isInEditbox(),
        selectedText: sel ? sel.toString() : '',
        os: OS
    };
}

// function requestKM(macroId, extraText = '') {
//     GM_xmlhttpRequest({
//         method: 'GET',
//         url: 'https://localhost:3011/action.html?macro=' + macroId +
//              '&value=' + encodeURIComponent(JSON.stringify({ ...getPageContext(), extraText })),
//         timeout: 2000,
//         onerror: function() {},
//         ontimeout: function() {}
//     });
// }

function requestKM(macroId, extraText = '') {
    GM_xmlhttpRequest({
        method: 'GET',
        url: (isWindows ? 'http://localhost:3010' : 'https://localhost:3011') +
             '/action.html?macro=' + macroId +
             '&value=' + encodeURIComponent(JSON.stringify({ ...getPageContext(), extraText })),
        timeout: 60000,
        onload: isWindows ? function(res) {
            const text = res.responseText;
            if (!text) return;
            const lines = text.split('\n');
            lines.forEach((line, i) => {
                document.execCommand('insertText', false, line);
                if (i < lines.length - 1) document.execCommand('insertParagraph', false);
            });
        } : function() {},
        onerror: function() {},
        ontimeout: function() {}
    });
}

/**
* triggerKM(trigger, macroID, mode, extraText, copySelection)
*
* Binds a hotkey or hotstring to a Keyboard Maestro macro via Mousetrap.
*
* @param {string|string[]} trigger        - Hotkey (e.g. 'mod+d', ['mod+d','ctrl+d']) or
*                                           hotstring (e.g. 'hello'). 'mod' resolves to
*                                           Cmd on Mac, Ctrl on Windows/Linux.
* @param {string}          macroID        - KM macro UUID
*                                           (e.g. '97649BB0-C8D9-43BB-89DF-420DD20B3DFE')
* @param {number}          [mode=3]       - Focus context filter:
*                                             1 = outside editbox only
*                                             2 = inside editbox only
*                                             3 = anywhere (default)
* @param {string}          [extraText=''] - Optional text passed along in the KM payload.
*                                           Accessible in KM via the %TriggerValue% token.
* @param {boolean}         [copySelection=true] - If true, copies the current selection to
*                                           the clipboard (via execCommand('copy')) before
*                                           firing the KM macro. Useful when the macro needs
*                                           the selected text available on the clipboard (for history purpose).
*
*/
function triggerKM(trigger, macroID, mode = 3, extraText = '', copySelection = true) {
    Mousetrap.bindGlobal(trigger, function () {
        const inBox = isInEditbox();
        if (mode === 1 && inBox)  return true;
        if (mode === 2 && !inBox) return true;
        if (copySelection) document.execCommand('copy');
        requestKM(macroID, extraText);
        return false;
    });
}