Get the Current Position of a Keyboard Maestro Palette

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
3 Likes

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

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

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
3 Likes

@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!

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 ā€¦

Will do. Thanks.

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()
        }
    });

})();

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.

1 Like

( Donā€™t hesitate to message me for clarification of opacities)

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!).

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.

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.

1 Like

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?

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.

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

2 Likes

How do I use this? I find installation instructions but no how to use instructions. The Wiki article on Plug In Actions https://wiki.keyboardmaestro.com/manual/Plug_In_Actions doesn't help either. Any other resources to check?

Thanks in advance for any assistance. :smiley:

Hello!

Does anyone already have a KM macro that "memorizes" the position of a given application's KM palettes and can restore them to their memorized position (in case the user has inadvertently moved them)?

In other words, a KM macro that is something like [Desktop Icon Manager] for KM palettes.

Many thanks in advance!

Hey there, recording and then setting palettes positions is very doable with AppleScript. Iā€™m not at my computer right now but when I am Iā€™ll share something with you to get you started.

HI Chris,

Many thanks for your posting.

Finder is one of the apps for which I want to do this, and I think that AppleScript recording is not supported in Finder (could be mistaken!).

Hi Barry, unless I misunderstood you post, this shouldnā€™t be an issue. Finder does have AppleScript support, but in this case it's irrelevant since what I understand you want to record is a palette's size/position which is part of the Keyboard Maestro Engine app, not Finder.

EDIT: Hey @BF-H, here is a simple macro that uses AppleScript to record the front palette's position, and then compiles the XML to insert a new AppleScript that will position that palette to wherever it was when the macro was triggered. If you're like me, once you have your palettes positioned where you like them, it's unlikely you'll need to change that from time to time. So I have a variety of macros that position my open apps and palettes for me, and this macro can be used to build another macro designed to position your palettes. It is not designed to record a palette's position, and then restore that later on. It's designed to store the position insert that into the macro that is currently being edited.

06)Record Front Palette's Position and Insert Into Macro as Applescript to Restore It (for BF-H).kmmacros (14 KB)

Macro screenshot (click to expand/collapse)