Get the current position of a Keyboard Maestro palette

palettes

#1

For positioning one palette in relation to another, or just moving a mouse to a particular palette.

(for use in conjunction with another custom plugin, which repositions named palettes)

Read the position of a named KM palette.zip (8.2 KB)

Custom Keyboard Maestro Plug-in

NAME

  • Get the position of a named KM palette

VERSION

  • 0.1

SYNOPSIS

  • Returns the coordinates of a named KM palette
  • The coordinates returned:
    • are in an ‘X, Y’ format usable by ‘Use Variable to set [mouse | window] position’ actions
    • may be of any corner of the palette, or of a mid edge or palette centre

REQUIREMENTS

  • Yosemite
    • The core script – readPalettePosn.sh – is mainly written in Javascript for Applications (Yosemite JXA).

OPTIONS

  • Palette name
    • Should be exactly as spelled in the palette title bar
      • (case sensitive)
    • The Global palette can be referred to either as
      • Global or
      • Keyboard Maestro
  • X is position of
    • Popup options:
      • left edge
      • horizontal centre
      • right edge
  • Y is position of
    • Popup options:
      • top edge
      • vertical centre
      • bottom edge

INSTALLATION

  • Drag the .zip file onto the Keyboard Maestro icon in the OS X toolbar.
  • (if updating a previous version of the action, first manually remove the previous copy from the custom actions folder)
    • ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Actions

CONTACT


Uses this script in conjunction with the plugin options panel:

#!/bin/bash

osascript -l JavaScript <<JXA_END 2>/dev/null
(function (strPaletteName, strAcross, strDown) {
	strPaletteName = (strPaletteName.toLowerCase() !== 'global') ?
		strPaletteName : "Keyboard Maestro";
		
	strAcross = strAcross ? strAcross.toLowerCase() : 'left';
	strDown = strDown? strDown.toLowerCase() : 'top';
		
	// strName --> oWin
	function namedPalWin(strName) {
		var lstWins = lngWins ? fnWins.whose({
			name: strName
		}) : [];
		return lstWins.length ? lstWins[0] : null;
	}

	// A corner point a middle point
	// oWindow --> [left|middle|right] --> [top|middle|bottom] --> [lngX, lngY]
	function palPoint(winPal, strX, strY) {
		
		if (['left', 'horizontal', 'right'].indexOf(strX) === -1) strX = 'left';
		if (['top', 'vertical', 'bottom'].indexOf(strY) === -1) strY = 'top';

		var	lstXY = winPal.position(),
			lstWH = winPal.size();

		return [
			lstXY[0] + (strX !== 'left' ?
				(strX === 'horizontal' ? lstWH[0] / 2 : lstWH[0]) : 0),
			lstXY[1] + (strY !== 'top' ?
				(strY === 'vertical' ? lstWH[1] / 2 : lstWH[1]) : 0)
		];
	}

	var appSE = Application("System Events"),
		lstProcs = appSE.applicationProcesses.whose({
			name: "Keyboard Maestro Engine"
		}),
		procKME = (lstProcs && lstProcs.length) ? lstProcs[0] : null;

	var fnWins = procKME ? procKME.windows : null,
		lngWins = fnWins ? fnWins.length : 0,
		palNamed = namedPalWin(strPaletteName),
		rgxSpace = /\s+/,
		strReturn = '',
		lstPosn;

	if (palNamed) {
		lstPosn = palPoint(
			palNamed,
			strAcross.split(rgxSpace)[0],
			strDown.split(rgxSpace)[0]
		);
		// Format usable as KM Variable for Mouse Position etc
		strReturn = lstPosn[0].toString() + ', ' + lstPosn[1].toString();
	} else {
		strReturn = "Palette not open, or not found as spelled: " + strPaletteName;
	}

	return strReturn;
	
})(
	"$KMPARAM_Palette_name",
	"$KMPARAM_X_is_position_of",
	"$KMPARAM_Y_is_position_of"
);
JXA_END

Hovering the cursor over the global palette, to see its contents
#2

So useful, supplemented by your mouse moving macro. Thanks a lot.

Is such access to KM palette data is possible directly through AppleScript?


#3

Skipping past any details of error-checking etc, you could write:

tell application "System Events"
	set procKME to first application process where name = "Keyboard Maestro Engine"
	tell front window of procKME to return {title, position, size}
end tell

or

tell application "System Events"
	set procKME to first application process where name = "Keyboard Maestro Engine"
	set lst to {}
	set lstWins to windows of procKME
	repeat with oWin in lstWins
		set end of lst to {title, position, size} of oWin
	end repeat
	return lst
end tell

#4

@ComplexPoint Hey Rob -

I want to position a palette next to a Finder window, like this:

I’m using your 2 macros - the “Get palette position” and the “Set palette position”. If you just think about it for a minute, you’ll see why I’m posting this.

Right now I have to call “Get palette position” twice, so I can calculate the width of the palette, so I can position it properly.

I can think of several ways you could modify either or both of your plugins to better accommodate this. You with me?

Let me know if this is something you want to tackle. It’s OK if not - I just wanted to check with you. Thanks!


#5

Probably won’t have time to do it myself in the next week or two,
but do feel free to adjust it and amend the attribution …


#6

Will do. Thanks.


#7

Can’t remember whether the internals are in AS or something like this:

(function () {
    'use strict';

    var se = Application('com.apple.systemevents'),
        procs = se.applicationProcesses.whose({
            name: 'Keyboard Maestro Engine'
        }),
        procKME = procs.length ? procs[0] : undefined,
        ws = procKME ? procKME.windows() : [];

    return ws.map(function (w) {
        return {
            title: w.title(),
            position: w.position(),
            size: w.size()
        }
    });

})();

#8

Yeah, they’re in JXA, and it’s taxing my brain to figure it out. I can do it, but it’ll take time. There’s so many syntactical elements I’m unfamiliar with (they’re not incomprehensible, I just have to think a little…). I’ve seen them all before, but it’s not a native language for me, so…

Couldn’t you just do it in C#? Then I’d understand it in no time!! :stuck_out_tongue:

I’ve got an AS version working, so I may just go with that. We’ll see.


#9

( Don’t hesitate to message me for clarification of opacities)


#10

Thanks.

This works for me for now:

tell application "System Events"
	set _kme to first application process where name = "Keyboard Maestro Engine"
	set _palette to first window of _kme where name = "_Sandbox"
	set _size to the size of _palette
	return item 1 of _size
end tell

F’ing AS. Took me forever to find the correct syntax for each property. Yeah, just “tell” me these things - only I have to guess the exact wording because God forbid you understand things like Window.Title, Window.Position and Window.Size. And code completion? Naw, that’s too 1990’s.

And I refuse to think about error checking. Honestly, with no defined mechanism for returning exit statuses to KM, it doesn’t really matter. If it doesn’t work, display the results in a window, and Bob’s your uncle.

Not exactly rock solid, but if I wanted that, I should have stayed with Windows (HA!).


#11

OK, let’s start at the top and work our way down, little by little, a few questions at a time.

Please remember - nothing I say is intended as criticism - this is just how I learn.

  1. I notice that you start the function with some statements, then you have some embedded functions, then some more statements following them. Am I reading that correctly - that JS lets you stick embedded functions anywhere? And assuming I’m correct, why did you structure it that way?

Thanks.


#12

The whole thing is wrapped as an anonymous function (you could also call it function run() { …}) because that avoids pollution of the (overpopulated) global namespace, and, above all, means that your own local variables appear grouped in a separate panel of the the Safari JavaScript debugger.

(Wrapping in an anonymous function to get a local namespace sometimes gets called ‘the module pattern’ in discussions of Javascript)

The name rebindings at the start are a fairly traditional way of ensuring default fall-back values for the arguments of the main function. Have to say that I’m moving away from that now – prefer to avoid mutation and use fresh names, lower down, that take values from the arguments if they are provided.

JavaScript functions are nestable objects like any other object (you can convert them to strings, use them as values in records, etc etc). Given the ‘module pattern’ of an outermost anonymous function, you will tend to see some function definitions at the top of the module. (The compiler lifts them there anyway if you place them further down).

One use of the module pattern in OS X scripting is that the arguments to the outer (anonymous) function can be an interface to some broader context. If you are running the JavaScript in a shell script, they might be bash variables bound to KM variable values.


#13

Wanting to play with AppleScript myself I tried to use your code. I get an error when I try…

activate "Mail"
tell application "System Events"
	set _kme to first application process where name = "Keyboard Maestro Engine"
	set _palette to first window of _kme where name = "Email Palette"
	set _size to the size of _palette
	return item 1 of _size
end tell

Error:
System Events got an error: Can’t get window 1 of application process “Keyboard Maestro Engine” whose name = “Email Palette”. Invalid index.

I took it that your _Sandbox what the name of your palette, is that correct?


#14

Yes it is.

My guess is your palette hasn’t been displayed yet, after activating Mail. Try putting a delay in, although I’m not sure how to do that in AppleScript. You could activate it in KM and use a KM pause, then run your AppleScript, and see if that’s what’s wrong.

Ultimately you can trigger your macro when Mail becomes active - the palette will have been displayed by then (I think).

Also, have you seen:

If you download the action (you don’t need to install it unless you want to), unzip it and look in the script file, it has all sorts of AppleScript window position stuff in it.


#15

Hey Guys,

Here’s how I do diagnostics on those sorts of things.

This script is in document 1 of the Script Editor.app (or Script Debugger).

tell application "System Events"
  tell application process "Keyboard Maestro Engine"
    return properties of windows
  end tell
end tell

Then I switch to the app where the palette appears and run this from a Keyboard Maestro macro:

tell application "Script Editor"
  tell document 1 to execute
  delay 0.25
  activate
end tell

That way I get a properly formatted AppleScript record in an AppleScript editor.

I can write any arbitrary code in my AppleScript editor and execute it remotely, and that comes in handy when I need another application to be frontmost for context.

-Chris