Centering Display Text Window Depending on That Window Size?

I can't seem to get the right order of things to get the following to work.
The goal is to center the Display Text Window on the screen.
The following does not work.

Not a fast solution, but a quick JS script usually triggers a more strictly Maestronic response : - )

Centered KM Display Text window.kmmacros (23.3 KB)
update

Javascript source

(() => {
    'use strict';

    // Rob Trew (c) 2018

    ObjC.import('AppKit');

    const main = () =>
        centeredWindow(
            'Keyboard Maestro Engine',
            'Keyboard Maestro - Display Text'
        );

    // JXA ------------------------------------------------

    // centeredWindow :: String -> String
    //                      -> Either String IO {W: X: Y: Z}
    const centeredWindow = (strAppName, strWinTitle) =>
        bindLR(
            winXYWHLR(strAppName, strWinTitle),
            xywhWin => {
                const
                    xywhScreen = screenXYWH(true, true),
                    centered = zipWith(
                        plus,
                        map(k => xywhScreen[k], ['X', 'Y']),
                        map(k => (
                            xywhScreen[k] - xywhWin[k]
                        ) / 2, ['W', 'H'])
                    );
                return (
                    xywhWin.win.position = centered,
                    Right(centered)
                );
            }
        );

    // screenXYWH :: Bool -> Bool ->
    //    {X :: Int, Y :: Int, W :: Int, H :: Int}
    const screenXYWH = (blnActiveScreen, blnVisibleOnly) => {
        const
            scr = $.NSScreen,
            // Primary or active ?
            dct = (blnActiveScreen ? (
                scr.mainScreen
            ) : scr.firstObject)[
                blnVisibleOnly ? 'visibleFrame' : 'frame'
            ],
            [xy, wh] = [dct.origin, dct.size];
        return {
            X: xy.x,
            Y: xy.y,
            W: wh.width,
            H: wh.height
        };
    };

    // Application Name -> Window Title -> Either String {X: Y: W: H:}
    // winXYWHLR :: String -> String
    //              -> Either String {X::Int, Y::Int, W::Int, H::Int}
    const winXYWHLR = (strApp, strWin) => {
        const
            ps = standardSEAdditions()
            .applicationProcesses
            .where({
                name: strApp
            });
        return bindLR(
            0 < ps.length ? (() => {
                const ws = ps.at(0).windows.where({
                    name: strWin
                });
                return 0 < ws.length ? (
                    Right(ws.at(0))
                ) : Left(`Window not found: ${strWin}`);
            })() : Left(`Application process not found: ${strApp}`),
            w => {
                const [xy, wh] = map(
                    k => Tuple.apply(
                        null, w[k]()
                    ), ['position', 'size']
                );
                return Right({
                    win: w,
                    X: fst(xy),
                    Y: snd(xy),
                    W: fst(wh),
                    H: snd(wh)
                });
            }
        );
    };

    // standardSEAdditions :: () -> Application
    const standardSEAdditions = () =>
        Object.assign(Application('System Events'), {
            includeStandardAdditions: true
        });

    // GENERIC FUNCTIONS ----------------------------------

    // https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // fst :: (a, b) -> a
    const fst = tpl => tpl[0];

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // min :: Ord a => a -> a -> a
    const min = (a, b) => b < a ? b : a;

    const plus = (a, b) => a + b;

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = (f, xs, ys) =>
        Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => f(xs[i], ys[i], i));

    // MAIN ---
    return main();
})();

@ComplexPoint Nice, thank you, that does the trick.
But I'm always wanting to learn so as you inferred, hoping to see a solution with a token or function and the correct syntax therein.
Thank you as always for your expertise, really appreciated.

1 Like

Hi Troy,

I think the main reason that your original calculation may not have worked is because you never closed the original parenthesis for the first SCREEN() function (easy mistake; happens to the best of us :slightly_smiling_face:)

Here's a sample action with the correct syntax for the SCREEN() function that puts the window comfortably in the center of my 27" display:

image

Calculation

SCREEN(Main,Left,40%), SCREEN(Main,Top,35%)

If you haven't already, try perusing the wiki page for SCREEN() for a little explanation of what each part of the syntax handles. You may also want to try experimenting with adjusting the percentage values and re-running the macro to see what looks best to you.

@gglick thank you for that, I am running off, so don't have time to test, although I'm sure the solution you provided works fine, - but my question would remain how to make it work with a number say 'width - 500' or, do I have to use % all references to the SCREEN() use the % and never a straight number, I guess I could run a calculation to get what percentage of the screen width, 500 is and use that percentage, seems a long way around though. - but would work. -
I want to center windows of differing widths, and not use a percentage.
just wanted to get this question off to you, many thanx.

No problem. I'm afraid I don't understand exactly what it is you're trying to do with these numbers, but at any rate, if you want to easily center windows of varying size, I'm not sure it's possible to do so easily in KM, at least with a single unified calculation, since KM moves windows based on the coordinates of the window's top left corner rather than the window's center. Personally, if I were to attempt this, I don't think I would turn to KM's built-in window moving capabilities (never mind that this is a plug-in action :slightly_smiling_face:); rather, I would use AppleScript to take advantage of another window-manipulator app I use, Moom, that has a command just for this purpose:

image

Of course, if there is a way to do this natively in KM, I would be glad to see how it's done myself.

Moom

Thank you – much faster, and finds a use for something that was gathering dust in a folder ...

JS version:

Application('Moom').centerFrontmostWindow()
1 Like

I ended up not getting anywhere with trying to get the dimensions of the Display text window (seems you cannot with a token) and then running some sort of calculation with that window size and the screen size. It's no biggie, was just trying to make it more programatic and learn at the same time. I ended up with the following which works just fine.
Cheers

1 Like

Just skimmed this thread, so I hope I'm not missing the point or rehashing a previous solution.

1. Macro-licious

2. AppleScriptive

use sys : application "System Events"
property KME : process "Keyboard Maestro Engine"
property _W : a reference to (KME's windows whose name contains "Display Text")
property D : size of scroll area 1 of process "Finder"
property W : item 1 of _W


set |Screen| to {width:item 1, height:item 2} of D
set |Window| to {width:item 1, height:item 2} of (W's size as list)

set X to ((|Screen|'s width) - (|Window|'s width)) / 2
set Y to ((|Screen|'s height) - (|Window|'s height)) / 2

set W's position to [X, Y]
3 Likes

much appreciate you taking a look, at first when I saw your macro suggestion I thought OMG, I missed the most obvious. But (whew) upon running those two actions, it does not work. If the editor is open, it centers the editor. If the editor is not open it does not center the window.

Added: The AppleScript above does work nicely though! thank you

I think the trick to center it is to add "Keyboard Maestro Engine" to the application menu.

To get it into the menu, you need to choose other and then press G.
Paste the path "/Applications/Keyboard Maestro.app/Contents/MacOS/Keyboard Maestro Engine.app/" into the field and then click open.

1 Like

@JimmyHartington yikes! =), nice Sir.
Perfect, absolutely perfect, all the other solutions have been great as well.
Best forum ever - ever.

1 Like

I think, by now, you've realised it does work if you reproduce the macro in exact fashion, targetting Keyboard Maestro Engine and not Keyboard Maestro. Apologies for not bringing your attention to this at the time, as I can see it is a detail that's easily missed.

@CJK arg, you know I did not even 'see' that. I apologize, your instructions were perfect, it was my ability that was lacking! Thank you for pointing that out, it will help me to be a better.... everything, by paying closer attention to things, both technical and not.
Cheers.

Hey @JimmyHartington,

I am trying to add the "Keyboard Maestro Engine" to the application menu but fail, after hitting "other" a Finder window opens, CMD G does nothing, if i do a regular search the system finds nothing. Could you please clarify what i am doing wrong?

Thanks in advance.

@CJK, thanks for sharing this. :+1:

Wanna know what is so amazing about KM?
It is NOT how much I know, but how LITTLE I know, and continue to learn every day from folks like you in this great forum! I don't know how I overlooked this powerful Action for so long.

Inserting the KM Action "Center Front Window"

For other readers, you may have trouble finding a KM Action labeled "Center Front Window".
You can find it under the name Manipulate a Window action, but it is easiest to just search the Actions for the common keyword "window":

Press ⌘⌃A to show the Select/Insert Action by Name dialog

Note that even though the Action name in the left panel is "Manipulate a Window", it shown in the right pane as "Minimize Front Window". This can be very confusing.

"Minimize" and "Center" are just two of the many different actions this KM Action can actually perform. See the above Wiki link for details.

Also, to be perfectly clear, the way to select "Keyboard Maestro Engine.app" as the app to use in this action, after you

  1. Click the popup "Front Application"
  2. Select "other"
  3. Press ⌘⇧G to display the "Go to Folder" dialog
  4. Enter "/Applications/Keyboard Maestro.app/Contents/MacOS/"
  5. Then select "Keyboard Maestro Engine.app"
3 Likes

Try following @JMichaelTXs instructions at the end of his post.

2 Likes

That one day a while ago when I casually decided to plonk an alias in my Applications folder probably ended up saving me as much time as all of my Keyboard Maestro macros put together:

3 Likes