Author Topic: Quickeys  (Read 4816 times)

Offline Syntho

  • Platinum Member
  • *****
  • Posts: 1325
Quickeys
« on: November 27, 2018, 09:04:03 PM »
Any of you have any experience using Quickeys? I'm working my way around it but haven't quite figured out how to control an application when it isn't active. Can quickeys control an application when it's not active/in the foreground? I'm trying to set up a simple play/pause shortcut kind of like how we have on the OSX Magic Keyboards. There's play/pause, previous and next track, volume up and down etc and it controls my player no matter which application is active. I'd like to set up the same thing in Quickeys if possible, using the F keys on my Apple Extended keyboard.

Offline GaryN

  • Platinum Member
  • *****
  • Posts: 1566
  • active member
Re: Quickeys
« Reply #1 on: November 27, 2018, 10:05:48 PM »
You should go back and actually read what I spent time trying to explain to you about cooperative vs. preemptive multitasking. You can't control an app that's not active because in the Mac OS it's really NOT ACTIVE. This means you can't control iTunes (which is an app) or any other player app unless you bring it to the foreground first. There are (as far I have ever been able to find) only TWO low-level System multimedia functions you can control in the background.

1)  Apple Audio CD Player
This applies only to actual CDs. Find it under Quickeys Editor / Keysets / Create / Multimedia Tools
Here you can assign keystrokes to do all the things you want.

2) Speakerchanger
This will give you volume up and down and mute (by selecting 0%) of any system audio

Other than these, for iTunes or other player, you will have to create a Universal Sequence (Universal so that your selected keystrokes work in any app) that triggers the Application Switcher, bringing the player to the front then triggering whatever function (play, pause etc.) then immediately back to Application switcher to Return to Previous app. This sounds long and complex but it will actually execute very quickly.

Offline Syntho

  • Platinum Member
  • *****
  • Posts: 1325
Re: Quickeys
« Reply #2 on: November 27, 2018, 10:21:32 PM »
Got it. One little problem is who knows what the previous application will be.

Offline GaryN

  • Platinum Member
  • *****
  • Posts: 1566
  • active member
Re: Quickeys
« Reply #3 on: November 28, 2018, 01:59:27 PM »
Got it. One little problem is who knows what the previous application will be.
The "previous application" is the one you're using. There are only three steps in this macro:
1. Switch to the player
2. Operate the desired player control
3. Return

So now you're right back where you were…in the "previous app"

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Quickeys
« Reply #4 on: November 30, 2018, 07:47:21 AM »
It's possible to control an application when it's in the background. This requires an extension which patches GetNextEvent and WaitNextEvent. When it receives a particular key event, for example F1, and you want to replace this with a key combination that is understood by your target program, for example command-r, then it looks at LMGetCurApName to be sure that your target program is running (foreground or background doesn't make a difference.) Then you change the event record.
Code: [Select]
TYPE  EventRecord =
      RECORD
         what:       Integer;       {event code}
         message:    LongInt;       {event message}
         when:       LongInt;       {ticks since startup}
         where:      Point;         {mouse location}
         modifiers:  Integer;       {modifier flags}
      END;
Here you have the chance to change modifiers to command. 'r' has to be stuffed into message as well as the key code for 'r'.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Quickeys
« Reply #5 on: December 05, 2018, 04:31:24 AM »
Solution code:
Code: [Select]
//========================================
// Author:   Cliff Huylebroeck
// ---------------------------
// Category: 68K code resources
// Project:  BackeysINIT
// File:     BackeysINIT.h
// Implemen: BackeysINIT.cp
//========================================

#pragma once

int main();

bool Run();

void InstallPatches();

pascal Boolean New_GetNextEvent(EventMask mask,
                                EventRecord *event);

pascal Boolean New_WaitNextEvent(EventMask mask,
                                 EventRecord *event,
                                 UInt32 sleep,
                                 RgnHandle mouseRgn);

pascal void New_DrawMenuBar();

void Dispatch(Boolean hasEvent,
              EventRecord &event);

void Process_keyDown(EventRecord &event);

void Process_app1Evt(EventRecord &event);

bool IsTargetApName();

bool IsTargetPSN();

void PostApp1Event();
Code: [Select]
//========================================
// Author:   Cliff Huylebroeck
// ---------------------------
// Category: 68K code resources
// Project:  BackeysINIT
// File:     BackeysINIT.cp
//========================================

#include "BackeysINIT.h"

// Rem: The next includes may not be precompiled
// because they contain inline assembler.
#include <A4Stuff.h>
#include <SetupA4.h>

const UInt32 mySignature='1234';

// Rem: If I type F8 then the target program must receive Command-A.

// F8.
const UInt8 bad_charCode=16;
const UInt8 bad_keyCode=100;
const UInt16 bad_modifiers=128;

// Command-A.
const UInt8 good_charCode=97;
const UInt8 good_keyCode=12; // Rem: on Azerty keyboard. (0 on Qwerty keyboard.)
const UInt16 good_modifiers=384;

typedef pascal Boolean (*GetNextEventProcPtr)(EventMask mask,
                                              EventRecord *event);

typedef pascal Boolean (*WaitNextEventProcPtr)(EventMask mask,
                                               EventRecord *event,
                                               UInt32 sleep,
                                               RgnHandle mouseRgn);

typedef pascal void (*DrawMenuBarProcPtr)();

GetNextEventProcPtr old_GetNextEvent;
WaitNextEventProcPtr old_WaitNextEvent;
DrawMenuBarProcPtr old_DrawMenuBar;

UInt8 osEvtMessage;
UInt8 adbAddress;
bool targetIsIdentified;
ProcessSerialNumber targetPSN;
ProcessSerialNumber oldFrontPSN;
bool targetMustPostApp1Event;

int main()
{
    EnterCodeResource();
    PrepareCallback();

    const SInt16 iconID=Run() ? goodIconID : badIconID;
    DrawINITIcon(iconID);

    ExitCodeResource();
    return 0;
}

bool Run()
{
    // 1. Me.
    Handle me;
    if (!GetINITHandle(me))
        {
        return false;
        }

    // 2. Init.
    old_GetNextEvent=nil;
    old_WaitNextEvent=nil;
    old_DrawMenuBar=nil;
    osEvtMessage=0;
    adbAddress=0;
    targetIsIdentified=false;
    targetPSN.highLongOfPSN=0;
    targetPSN.lowLongOfPSN=0;
    targetMustPostApp1Event=false;
    oldFrontPSN.highLongOfPSN=0;
    oldFrontPSN.lowLongOfPSN=0;

    // 3. Patches.
    InstallPatches();

    return true;
}

void InstallPatches()
{
    ProcPtr old_address;
    ProcPtr new_address;

    old_address=::GetToolTrapAddress(_GetNextEvent);
    old_GetNextEvent=reinterpret_cast<GetNextEventProcPtr>(old_address);
    new_address=reinterpret_cast<UniversalProcPtr>(New_GetNextEvent);
    ::SetToolTrapAddress(new_address,
                         _GetNextEvent);

    old_address=::GetToolTrapAddress(_WaitNextEvent);
    old_WaitNextEvent=reinterpret_cast<WaitNextEventProcPtr>(old_address);
    new_address=reinterpret_cast<UniversalProcPtr>(New_WaitNextEvent);
    ::SetToolTrapAddress(new_address,
                         _WaitNextEvent);

    old_address=::GetToolTrapAddress(_DrawMenuBar);
    old_DrawMenuBar=reinterpret_cast<DrawMenuBarProcPtr>(old_address);
    new_address=reinterpret_cast<UniversalProcPtr>(New_DrawMenuBar);
    ::SetToolTrapAddress(new_address,
                         _DrawMenuBar);
}

pascal Boolean New_GetNextEvent(const EventMask mask,
                                EventRecord* const event)
{
    Boolean hasEvent;

    const SInt32 oldA4=SetUpA4();

    hasEvent=old_GetNextEvent(mask,
                              event);
    Dispatch(hasEvent,
             *event);

    RestoreA4(oldA4);

    return hasEvent;
}

pascal Boolean New_WaitNextEvent(const EventMask mask,
                                 EventRecord* const event,
                                 const UInt32 sleep,
                                 RgnHandle const mouseRgn)
{
    Boolean hasEvent;

    const SInt32 oldA4=SetUpA4();

    hasEvent=old_WaitNextEvent(mask,
                               event,
                               sleep,
                               mouseRgn);
    Dispatch(hasEvent,
             *event);

    RestoreA4(oldA4);

    return hasEvent;
}

pascal void New_DrawMenuBar()
{
    const SInt32 oldA4=SetUpA4();

    old_DrawMenuBar();
    if (!targetIsIdentified)
        {
        if (IsTargetApName())
            {
            const OSErr err=::GetCurrentProcess(&targetPSN);
            if (err)
                {
                // Rem: Not much that we can do here.
                }
            else
                {
                targetIsIdentified=true;
                ::SysBeep(0);
                }
            }
        }

    RestoreA4(oldA4);
}

void Dispatch(const Boolean hasEvent,
              EventRecord &event)
{
    if (targetMustPostApp1Event)
        {
        if (IsTargetPSN())
            {
            targetMustPostApp1Event=false;
            PostApp1Event();
            }
        }

    if (!hasEvent)
        {
        return;
        }

    switch (event.what)
        {
        case keyDown:
            {
            Process_keyDown(event);
            break;
            }
        case app1Evt:
            {
            Process_app1Evt(event);
            break;
            }
        }
}

void Process_keyDown(EventRecord &event)
{
    if (!targetIsIdentified)
        {
        return;
        }
   
    if (event.modifiers!=bad_modifiers)
        {
        return;
        }

    StringPtr const keyMap=reinterpret_cast<StringPtr>(&event.message);
    osEvtMessage=keyMap[0];
    adbAddress=keyMap[1];

    UInt8 &keyCode=keyMap[2];
    if (keyCode!=bad_keyCode)
        {
        return;
        }

    UInt8 &charCode=keyMap[3];
    if (charCode!=bad_charCode)
        {
        return;
        }

    // Rem: Background applications can receive only null events,
    // update events and high level events.
    // If we receive a key down event, then the current process
    // is certainly the front process.

    ProcessSerialNumber frontPSN;
    OSErr err=::GetFrontProcess(&frontPSN);
    if (err)
        {
        return;
        }

    Boolean sameProcess;
    err=::SameProcess(&frontPSN,
                      &targetPSN,
                      &sameProcess);
    if (err)
        {
        return;
        }

    if (sameProcess)
        {
        keyCode=good_keyCode;
        charCode=good_charCode;
        event.modifiers=good_modifiers;
        }
    else
        {
        // Rem: Ask to bring the target to the front
        // to be able to receive an app1 event.
        // It will be front after the next WaitNextEvent call.
        err=::SetFrontProcess(&targetPSN);
        if (err)
            {
            // Rem: Not much that we can do here.
            return;
            }
        targetMustPostApp1Event=true;
        oldFrontPSN.highLongOfPSN=frontPSN.highLongOfPSN;
        oldFrontPSN.lowLongOfPSN=frontPSN.lowLongOfPSN;
        }
}

void Process_app1Evt(EventRecord &event)
{
    if (event.message!=mySignature)
        {
        return;
        }

    event.what=keyDown;

    StringPtr const keyMap=reinterpret_cast<StringPtr>(&event.message);
    keyMap[0]=osEvtMessage;
    keyMap[1]=adbAddress;
    keyMap[2]=good_keyCode;
    keyMap[3]=good_charCode;
    event.modifiers=good_modifiers;

    // Rem: Ask to return to the old front process.
    // It will be front after the next WaitNextEvent call.
    const OSErr err=::SetFrontProcess(&oldFrontPSN);
    if (err)
        {
        // Rem: Not much that we can do here.
        return;
        }

    ::SysBeep(0);
}

bool IsTargetApName()
{
    static ConstStringPtr const targetApName="\pSimpleText";
    ConstStringPtr x=targetApName;
    ConstStringPtr const last=x+*x;
    ConstStringPtr y=LMGetCurApName();
    while (x<=last)
        {
        if (*x!=*y)
            {
            return false;
            }
        x++;
        y++;
        }
    return true;
}

bool IsTargetPSN()
{
    ProcessSerialNumber currentPSN;
    OSErr err=::GetCurrentProcess(&currentPSN);
    if (err)
        {
        return false;
        }

    Boolean sameProcess;
    err=::SameProcess(&currentPSN,
                      &targetPSN,
                      &sameProcess);
    if (err)
        {
        return false;
        }

    return sameProcess;
}

void PostApp1Event()
{
    // Rem: PPostEvent gives you a pointer to your event record,
    // so that you can change the modifiers,
    // but the event goes so quickly through the queue,
    // that it's gone before you can change it.
    // So I post an app1Evt and when I receive it,
    // I have all the time I need to change it
    // into a key event.
    ::SysBeep(0);
    const OSErr err=::PostEvent(app1Evt,
                                mySignature);
    if (err)
        {
        // Rem: Not much that we can do here.
        }
}

Offline Syntho

  • Platinum Member
  • *****
  • Posts: 1325
Re: Quickeys
« Reply #6 on: December 06, 2018, 04:39:47 AM »
Did you program this stuff yourself? Seems like a lot of time invested, thanks so much!

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Quickeys
« Reply #7 on: December 07, 2018, 06:53:48 AM »
If the target application is scriptable, then my solution can be simplified, because you don't need a process switch. I'll include both solutions in the OS 9.3 SDK.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Quickeys
« Reply #8 on: December 11, 2018, 06:53:22 AM »
Solution code for sending high level events when the target application is scriptable:
Code: [Select]
//========================================
// Author:   Cliff Huylebroeck
// ---------------------------
// Category: 68K code resources
// Project:  BackEventsINIT
// File:     BackEventsINIT.h
// Implemen: BackEventsINIT.cp
//========================================

#pragma once

int main();

bool Run();

void InstallPatches();

pascal Boolean New_GetNextEvent(EventMask mask,
                                EventRecord *event);

pascal Boolean New_WaitNextEvent(EventMask mask,
                                 EventRecord *event,
                                 UInt32 sleep,
                                 RgnHandle mouseRgn);

void Dispatch(Boolean hasEvent,
              EventRecord &event);

void Process_keyDown(EventRecord &event);

bool MakeAndSendAppleEvent(const AEAddressDesc &target);
Code: [Select]
//========================================
// Author:   Cliff Huylebroeck
// ---------------------------
// Category: 68K code resources
// Project:  BackEventsINIT
// File:     BackEventsINIT.cp
//========================================

#include "BackEventsINIT.h"

// Rem: This extension stops working when you quit the target application.

// Rem: The next includes may not be precompiled
// because they contain inline assembler.
#include <A4Stuff.h>
#include <SetupA4.h>

// Rem: If I type F8 then the Finder must show the about window.

// F8.
const UInt8 bad_charCode=16;
const UInt8 bad_keyCode=100;
const UInt16 bad_modifiers=128;

typedef pascal Boolean (*GetNextEventProcPtr)(EventMask mask,
                                              EventRecord *event);

typedef pascal Boolean (*WaitNextEventProcPtr)(EventMask mask,
                                               EventRecord *event,
                                               UInt32 sleep,
                                               RgnHandle mouseRgn);

GetNextEventProcPtr old_GetNextEvent;
WaitNextEventProcPtr old_WaitNextEvent;

int main()
{
    EnterCodeResource();
    PrepareCallback();

    const SInt16 iconID=Run() ? goodIconID : badIconID;
    DrawINITIcon(iconID);

    ExitCodeResource();
    return 0;
}

bool Run()
{
    // 1. Me.
    Handle me;
    if (!GetINITHandle(me))
        {
        return false;
        }

    // 2. Init.
    old_GetNextEvent=nil;
    old_WaitNextEvent=nil;

    // 3. Patches.
    InstallPatches();

    return true;
}

void InstallPatches()
{
    ProcPtr old_address;
    ProcPtr new_address;

    old_address=::GetToolTrapAddress(_GetNextEvent);
    old_GetNextEvent=reinterpret_cast<GetNextEventProcPtr>(old_address);
    new_address=reinterpret_cast<UniversalProcPtr>(New_GetNextEvent);
    ::SetToolTrapAddress(new_address,
                         _GetNextEvent);

    old_address=::GetToolTrapAddress(_WaitNextEvent);
    old_WaitNextEvent=reinterpret_cast<WaitNextEventProcPtr>(old_address);
    new_address=reinterpret_cast<UniversalProcPtr>(New_WaitNextEvent);
    ::SetToolTrapAddress(new_address,
                         _WaitNextEvent);
}

pascal Boolean New_GetNextEvent(const EventMask mask,
                                EventRecord* const event)
{
    Boolean hasEvent;

    const SInt32 oldA4=SetUpA4();

    hasEvent=old_GetNextEvent(mask,
                              event);
    Dispatch(hasEvent,
             *event);

    RestoreA4(oldA4);

    return hasEvent;
}

pascal Boolean New_WaitNextEvent(const EventMask mask,
                                 EventRecord* const event,
                                 const UInt32 sleep,
                                 RgnHandle const mouseRgn)
{
    Boolean hasEvent;

    const SInt32 oldA4=SetUpA4();

    hasEvent=old_WaitNextEvent(mask,
                               event,
                               sleep,
                               mouseRgn);
    Dispatch(hasEvent,
             *event);

    RestoreA4(oldA4);

    return hasEvent;
}

void Dispatch(const Boolean hasEvent,
              EventRecord &event)
{
    if (!hasEvent)
        {
        return;
        }

    switch (event.what)
        {
        case keyDown:
            {
            Process_keyDown(event);
            break;
            }
        }
}

void Process_keyDown(EventRecord &event)
{
    if (event.modifiers!=bad_modifiers)
        {
        return;
        }

    StringPtr const keyMap=reinterpret_cast<StringPtr>(&event.message);

    UInt8 &keyCode=keyMap[2];
    if (keyCode!=bad_keyCode)
        {
        return;
        }

    UInt8 &charCode=keyMap[3];
    if (charCode!=bad_charCode)
        {
        return;
        }

    event.what=nullEvent;

    const OSType finderCreator='MACS';
    AEAddressDesc target;
    OSErr err=::AECreateDesc(typeApplSignature,
                             &finderCreator,
                             sizeof(OSType),
                             &target);
    if (err)
        {
        // Rem: Not much that we can do here.
        return;
        }

    const bool ok=MakeAndSendAppleEvent(target);

    err=::AEDisposeDesc(&target);
    if (err)
        {
        // Rem: Not much that we can do here.
        return;
        }

    if (!ok)
        {
        // Rem: Not much that we can do here.
        return;
        }
}

bool MakeAndSendAppleEvent(const AEAddressDesc &target)
{
    AppleEvent appleEvent;
    appleEvent.descriptorType=typeNull;
    appleEvent.dataHandle=nil;
    OSErr err=::AECreateAppleEvent(kCoreEventClass,
                                   kAEAbout,
                                   &target,
                                   kAutoGenerateReturnID,
                                   kAnyTransactionID,
                                   &appleEvent);
    if (err)
        {
        return false;
        }

    const OSErr err_send=::AESend(&appleEvent,
                                  nil,
                                  kAENoReply bitor kAEAlwaysInteract bitor kAECanSwitchLayer,
                                  kAENormalPriority,
                                  kAEDefaultTimeout,
                                  nil,
                                  nil);

    err=::AEDisposeDesc(&appleEvent);
    if (err)
        {
        return false;
        }

    if (err_send)
        {
        return false;
        }

    return true;
}
If you want to use this then I recommend to make a PPC version. Be aware that the extension stops working when you quit the target application.