Problem Toggling Window Size

Toggle Window Size

This macro used to work, and now it doesn't. It should toggle between three different window sizes.

It looks at the frontmost window frame, compares it's size to one of the other two desired sizes. If it finds a known size, it resizes the window to the next size. If it finds no match, it goes to my default preference. I've checked the output, and I get the matches I'm expecting. I've been round, and round on this, and it's making me nuts.

Manipulate a Window.kmactions (965 B)

Hey Carey,

The action you posted couldn’t possibly do the job you describe, so I’m guessing you left part of it out.

-Chris

That was the posting process, I guess.

Anyway... I solved the problem. It was a type issue. I was using the 'is' comparison on a text field, and using a calculation in the switch comparison, which did evaluate to a text string (windows coordinates), but wasn't working. That was a general purpose version of the macro, but hard coding the coordinates solved the issue for me at least.

Main Screen Main Window Size Toggle v5.kmmacros (5.1 KB)

Here is a variant which builds and executes the Manipulate Window action from JS:

Cycle centered front window size.kmmacros (39.8 KB)

JS Source:

(() => {
    'use strict';

    //Rob Trew 2017-12-23

    ObjC.import('AppKit');

    const stages = [
        [1 / 10, 8 / 10],
        [1 / 3, 1 / 3],
        [1 / 5, 3 / 5]
    ];

    // GENERIC ---------------------------------------------------------------

    // findIndex :: (a -> Bool) -> [a] -> Maybe Int
    const findIndex = (p, xs) =>
        xs.reduce((a, x, i) =>
            a.nothing ? (
                p(x) ? just(i) : a
            ) : a, nothing('No match found for: ' + p.toString()));

    // just :: a -> Just a
    const just = x => ({
        nothing: false,
        just: x
    });

    // nothing :: () -> Nothing
    const nothing = (optionalMsg) => ({
        nothing: true,
        msg: optionalMsg
    });

    // readFile :: FilePath -> IO String
    const readFile = strPath => {
        var error = $(),
            str = ObjC.unwrap(
                $.NSString.stringWithContentsOfFileEncodingError(
                    $(strPath)
                    .stringByStandardizingPath,
                    $.NSUTF8StringEncoding,
                    error
                )
            );
        return typeof error.code !== 'string' ? (
            str
        ) : 'Could not read ' + strPath;
    };

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // takeBaseName :: FilePath -> String
    const takeBaseName = strPath =>
        strPath !== '' ? (
            strPath[strPath.length - 1] !== '/' ? (
                strPath.split('/')
                .slice(-1)[0].split('.')[0]
            ) : ''
        ) : '';

    // takeExtension :: FilePath -> String
    const takeExtension = strPath => {
        const
            xs = strPath.split('.'),
            lng = xs.length;
        return lng > 1 ? (
            '.' + xs[lng - 1]
        ) : '';
    };

    // File name template -> temporary path
    // (Random digit sequence inserted between template base and extension)
    // tempFilePath :: String -> IO FilePath
    const tempFilePath = template =>
        ObjC.unwrap($.NSTemporaryDirectory()) +
        takeBaseName(template) + Math.random()
        .toString()
        .substring(3) + takeExtension(template);

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

    // mainScreenXYWH :: () -> {X::Int, Y::Int, W::Int, H::Int}
    const mainScreenXYWH = () => {
        const
            dct = $.NSScreen.mainScreen.visibleFrame,
            xy = dct.origin,
            wh = dct.size;
        return {
            X: xy.x,
            Y: xy.y + 22,
            W: wh.width,
            H: wh.height - 22
        };
    };

    //  KEYBOARD MAESTRO -----------------------------------------------------

    // jsoDoScript :: Object (Dict | Array) -> IO ()
    const jsoDoScript = jso => {
        const strPath = tempFilePath('tmp.plist');
        return (
            Application('Keyboard Maestro Engine')
            .doScript((
                $(Array.isArray(jso) ? jso : [jso])
                .writeToFileAtomically(
                    $(strPath)
                    .stringByStandardizingPath,
                    true
                ),
                readFile(strPath)
            )),
            true
        );
    };

    // MAIN ------------------------------------------------------------------
    const
        canvas = mainScreenXYWH(),
        pixelStages = stages.map(
            ([x, w]) => [
                Math.floor(x * canvas.W),
                Math.floor(w * canvas.W)
            ]
        ),
        w = parseInt(
            Application('Keyboard Maestro Engine')
            .calculate('WINDOW(0,Width)'), 10
        ),
        mbIndex = findIndex(x => w === x[1], pixelStages),
        [xNext, wNext] = pixelStages[
            mbIndex.nothing ? (
                0
            ) : ((mbIndex.just + 1) % stages.length)
        ];

    // KEYBOARD MAESTRO 'MANIPULATE WINDOW' ACTION BUILT AND RUN
    return (
        jsoDoScript([{
            "TargetApplication": {},
            "WindowName": "",
            "MacroActionType": "ManipulateWindow",
            "TargetingType": "Front",
            "Action": "MoveAndResize",
            "HorizontalExpression": xNext.toString(),
            "VerticalExpression": "22",
            "WidthExpression": wNext.toString(),
            "HeightExpression": canvas.H.toString(),
            "Targeting": "FrontWindow",
            "ActionName": "Move and Resize Front Window",
            "WindowIndexExpression": 2,
        }]), [xNext, wNext]
    )
})();

1 Like

and of course, if you always want the window centered, then it can be a little simpler:

Cycle centered front window size (ver 2 - centered only).kmmacros (25.1 KB)

JS Source:

(() => {
    'use strict';

    // Rob Trew 2017-12-23

    // Ver 2 (Assumes window always centered)

    ObjC.import('AppKit');

    const stages = [
        8 / 10,
        1 / 3,
        3 / 5
    ];

    // GENERIC ---------------------------------------------------------------

    // findIndex :: (a -> Bool) -> [a] -> Maybe Int
    const findIndex = (p, xs) =>
        xs.reduce((a, x, i) =>
            a.nothing ? (
                p(x) ? just(i) : a
            ) : a, nothing('No match found for: ' + p.toString()));

    // just :: a -> Just a
    const just = x => ({
        nothing: false,
        just: x
    });

    // nothing :: () -> Nothing
    const nothing = (optionalMsg) => ({
        nothing: true,
        msg: optionalMsg
    });

    // readFile :: FilePath -> IO String
    const readFile = strPath => {
        var error = $(),
            str = ObjC.unwrap(
                $.NSString.stringWithContentsOfFileEncodingError(
                    $(strPath)
                    .stringByStandardizingPath,
                    $.NSUTF8StringEncoding,
                    error
                )
            );
        return typeof error.code !== 'string' ? (
            str
        ) : 'Could not read ' + strPath;
    };

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // takeBaseName :: FilePath -> String
    const takeBaseName = strPath =>
        strPath !== '' ? (
            strPath[strPath.length - 1] !== '/' ? (
                strPath.split('/')
                .slice(-1)[0].split('.')[0]
            ) : ''
        ) : '';

    // takeExtension :: FilePath -> String
    const takeExtension = strPath => {
        const
            xs = strPath.split('.'),
            lng = xs.length;
        return lng > 1 ? (
            '.' + xs[lng - 1]
        ) : '';
    };

    // File name template -> temporary path
    // (Random digit sequence inserted between template base and extension)
    // tempFilePath :: String -> IO FilePath
    const tempFilePath = template =>
        ObjC.unwrap($.NSTemporaryDirectory()) +
        takeBaseName(template) + Math.random()
        .toString()
        .substring(3) + takeExtension(template);

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

    // mainScreenXYWH :: () -> {X::Int, Y::Int, W::Int, H::Int}
    const mainScreenXYWH = () => {
        const
            dct = $.NSScreen.mainScreen.visibleFrame,
            xy = dct.origin,
            wh = dct.size;
        return {
            X: xy.x,
            Y: xy.y + 22,
            W: wh.width,
            H: wh.height - 22
        };
    };

    //  KEYBOARD MAESTRO -----------------------------------------------------

    // jsoDoScript :: Object (Dict | Array) -> IO ()
    const jsoDoScript = jso => {
        const strPath = tempFilePath('tmp.plist');
        return (
            Application('Keyboard Maestro Engine')
            .doScript((
                $(Array.isArray(jso) ? jso : [jso])
                .writeToFileAtomically(
                    $(strPath)
                    .stringByStandardizingPath,
                    true
                ),
                readFile(strPath)
            )),
            true
        );
    };

    // MAIN ------------------------------------------------------------------
    const
        canvas = mainScreenXYWH(),
        pixelStages = stages.map(
            w => Math.floor(w * canvas.W)
        ),
        w = parseInt(
            Application('Keyboard Maestro Engine')
            .calculate('WINDOW(0,Width)'), 10
        ),
        mbIndex = findIndex(x => w === x, pixelStages),
        wNext = pixelStages[
            mbIndex.nothing ? (
                0
            ) : ((mbIndex.just + 1) % stages.length)
        ],
        xNext = Math.floor((canvas.W - wNext) / 2);

    // KEYBOARD MAESTRO 'MANIPULATE WINDOW' ACTION BUILT AND RUN
    return (
        jsoDoScript([{
            "TargetApplication": {},
            "WindowName": "",
            "MacroActionType": "ManipulateWindow",
            "TargetingType": "Front",
            "Action": "MoveAndResize",
            "HorizontalExpression": xNext.toString(),
            "VerticalExpression": "22",
            "WidthExpression": wNext.toString(),
            "HeightExpression": canvas.H.toString(),
            "Targeting": "FrontWindow",
            "ActionName": "Move and Resize Front Window",
            "WindowIndexExpression": 2,
        }]), [xNext, wNext]
    )
})();

Thanks, but now it’s JS, and could be tied in as a service.

I did find that if I hard code the four window coordinates, I can compare that result. What I’d like is the general case, so I can use a line like this as a condition in a switch:

%Calculate%SCREENVISIBLE(Front,Width).2,SCREENVISIBLE(Front,Top),SCREENVISIBLE(Front,Width).6,SCREENVISIBLE(Front,Height)%

That does not seem to return a result I can compare to a switch of text %WindowFrame%1%