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/1788475/KMLibrary.js
// ==UserScript==
// @name KMLibrary
// @namespace https://greasyfork.org/users/28298
// @version 1.4
// @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 ─────────────────────────────────────────────────────
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() : ''
};
}
// 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=false] - 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.
*
* @example
* // Trigger anywhere, no copy
* triggerKM('mod+d', '97649BB0-C8D9-43BB-89DF-420DD20B3DFE');
*
* @example
* // Trigger inside editbox only, copy selection first
* triggerKM('mod+shift+r', 'SOME-UUID-HERE', 2, '', true);
*
* @example
* // Pass extra text to KM macro
* triggerKM('mod+k', 'SOME-UUID-HERE', 3, 'my-context');
*/
function triggerKM(trigger, macroID, mode = 3, extraText = '', copySelection = false) {
Mousetrap.bindGlobal(trigger, function () {
const inBox = isInEditbox();
if (mode === 1 && inBox) return true;
if (mode === 2 && !inBox) return false;
if (copySelection) document.execCommand('copy');
requestKM(macroID, extraText);
return false;
});
}
function isWindows() {
return navigator.platform.startsWith('Win');
}
function isAppleSilicon() {
try {
const canvas = document.createElement('canvas');
const gl =
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
if (!gl) return false;
const renderer = gl.getParameter(gl.RENDERER) || '';
const r = renderer.toLowerCase();
// "Apple M1, or similar"
if (r.includes('apple m1')) {
return true;
}
// Fallback: not confidently Apple Silicon
return false;
} catch (e) {
return false;
}
}