I also gave Keyboard Maestro a quick look, and these scripts seem to work OK on my system (Sonoma 14.7). If you are just pasting the script into an Execute an AppleScript
action, another idea would be to try the Execute a Shell Script
action and use osascript
to run the script file from there. You could also try running from a new user account to see if there is a conflict with something else that has been installed.
Hello Jonas (@ionah)
Maybe you did not read carefully enough what I mentioned above…
Dialogs created via AppleScript and the Objective-C API‘s are not supported in Keyboard Maestro‘s Execute an AppleScript Action. It’s something that you will always have to write some code to call from a Macro when the Dialog related code is in its own file until Peter Lewis has integrated the support for such dialogs.
Another thing I would try is create a script object with the dialog related code that is compiled to the temp folder and called from the same AppleScript. This is less code to write because it’s only one Script instead of two … but it’s still the same way such dialogs have to be called from Keyboard Maestro.
If this doesn’t work on newer OS‘ses (speaking of the dialog is not rendered at all), then there might be a big issue related to osascript. On the particular OS.
But since I am still running Monterey this is only a guess from my side.
Greetings from Germany
Tobias
Then why does it work here on Monterey 12.7.5?
Triggered by a “hot key” as they call it in KM…
It’s not the first time I encounter this kind of issue. Most of time it’s the application that’s not following macOS novelties. In these cases, a fix is possible. In the other case…
Maybe the dialog is rendered and showing … that’s not what I mean by isn’t supported… it’s the fact that Keyboard Maestro doesn’t execute the script the way like the System’s Script Menu or FastScripts or if you just Safe the script as a File elsewhere and call it from another Script where the Dialog is running on the main thread like it is supposed to be.
Compared to the older CocoaDialog or Pashua or the newer swiftDialog where this job is done by the CLI Tool or the Application which renders the dialog and puts it on the main thread automatically - with Keyboard Maestro you always have to ensure that this will happen thru the code you write and how it’s executed on your own.
Greetings from Germany
Tobias
Hi @red_menace. I don’t understand how I missed your post.
The fact that it’s running on your Sonoma config is encouraging.
Your suggestions are very clever (as always) and @Lucky_Magician.should follow them.
Thank you for taking the time to install KM.
Hi, I’m new here but saw your earlier post that said:
In fact, if I can make a dialog with only one popup button, which will open immediately after running the script, without firstly clicking on it, then it could actually work for my case perfectly well.
There is a way to make a button automatically select after a certain amount of time with the property “giving up after”:
set userResponse to display dialog “Hello” buttons {“ok”, “cancel”} default button 1 giving up after 2
This pops up a dialog box with the text “hello” and puts up two buttons. Button 1 is automatically “clicked” after 2 seconds.
I hope that helps.
Hello Greg (@gxwalsh)
What you’re describing here is the optional feature of the standard AppleScript display dialog command.
The code above you’ll referred to is based on AppleScript with Apple‘s Objective-C API and therefor something like timeout of the Dialog would have to be written quite differently.
My Skills to write that aren’t quite on that level so I won’t post any code. Maybe any other person see‘s this and can show you the difference to your suggestion.
Greetings from Germany
Tobias
Greetings from Baltimore!
Yes, I was just trying to answer OP’s request to “open immediately without first clicking on it”
I may have missed the OP’s actual intention.
Hello guys
I was on a trip, just arrived back home. I’m going to read all posts, and retry all examples. Maybe I’m missing something along the lines, don’t know.
I’ll report back how it goes, whether it works or not.
Thank you all for all suggestions and will to help, I greatly appreciate it.
I also came up with a variation that uses NSMenu (for submenus, etc), but I’m not that great at using VoiceOver to test it. Let me know if you want to take a look at it.
I want to try it absolutely.
If it would work then I would save lots of time, I planned to go through all examples you guys shared, got one more macbook with sonoma on it so planned to try it there as well.
Here is a variation that uses NSMenu instead of NSPopUpButton. It works more-or-less the same, but instead of the panel containing a control, it is just used to position a contextual menu (if you are using your own windows, they can be used).
use framework "Foundation"
use scripting additions
# UI item outlets
property mainWindow : missing value
property customMenu : missing value
# script properties
property outcome : missing value -- selection result
property failure : missing value -- error record with keys {handlerName, errorMessage, errorNumber}
property testing : true -- whether to provide feedback for the result being returned
on run -- example
set contextualMenu to {"Lorem Ipsum", "Donec Laoreet", {"Single Submenu", "", "Suspendisse Tempus", "Mauris Iaculis"}, "Quisque Convallis", {"Multiple Submenus", "", "Vivamus Consectetur", "Aenean Pulvinar", {"Aliquam Dignissim", "Try Not To Do This", {"No, Really, Don't Do This!"}}, "Nullam Sollicitudin"}, "Curabitur Mollis"}
return (getSelection from contextualMenu)
end run
# Get a menu selection - performSelector doesn't return anything and actions are asynchronous, so properties are used for results.
to getSelection from menuList
try
if current application's NSThread's isMainThread() as boolean then
my doStuff:menuList
else -- UI stuff needs to be done on the main thread
my performSelectorOnMainThread:"doStuff:" withObject:menuList waitUntilDone:true
end if
if failure is not missing value then error
if testing then
set output to item (((outcome is missing value) as integer) + 1) of {outcome, "missing value"}
activate me
-- display dialog output with title "Result" buttons {"OK"} giving up after 10
say output
end if
return outcome
on error errmess number errnum
if failure is missing value then
-- display alert "NSMenu Script Error " & errnum message errmess
say "NSMenu Script Error," & errnum & "," & first paragraph of errmess
else -- use keys from the failure record
-- display alert "NSMenu Script Error " & failure's errorNumber message quoted form of failure's errorMessage & " from handler " & failure's handlerName
say "NSMenu Script Error," & failure's errorNumber & "," & first paragraph of failure's errorMessage & " from handler " & failure's handlerName
end if
return missing value
end try
end getSelection
to doStuff:(menuList as list) -- do the menu stuff
try
set my customMenu to makeMenu for menuList without usingTags
# create a view for the menu
set mainWindow to makeWindow at {} with panel and floats given contentSize:{0, 0}, styleMask:3
mainWindow's makeKeyAndOrderFront:me -- show the panel
# create a right-click mouse event positioned at the view with the location offset
set uptime to current application's NSProcessInfo's processInfo's systemUptime
set theEvent to current application's NSEvent's mouseEventWithType:(current application's NSEventTypeRightMouseDown) location:{0, 0} modifierFlags:0 timestamp:uptime windowNumber:(mainWindow's windowNumber) context:(missing value) eventNumber:1 clickCount:1 pressure:0.0
current application's NSMenu's popUpContextMenu:customMenu withEvent:theEvent forView:(mainWindow's contentView)
# mainWindow's performClose:(missing value) -- uncomment to dismiss the positioning panel
on error errmess number errnum
set my failure to {handlerName:"doStuff", errorMessage:errmess, errorNumber:errnum}
log result
end try
end doStuff:
# Make and return a NSWindow or NSPanel.
# Default styleMask includes a title, close and minimize buttons, and is not resizeable.
# If no origin is given the window will be centered.
to makeWindow at (origin as list) given contentSize:contentSize as list : {400, 200}, styleMask:styleMask as integer : 15, title:title as text : "", panel:panel as boolean : false, floats:floats as boolean : false, aShadow:aShadow as boolean : true, minimumSize:minimumSize as list : {}, maximumSize:maximumSize as list : {}, backgroundColor:backgroundColor : missing value
tell current application to set theClass to item ((panel as integer) + 1) of {its NSWindow, its NSPanel}
tell (theClass's alloc()'s initWithContentRect:{{0, 0}, contentSize} styleMask:styleMask backing:2 defer:true)
if origin is {} then
tell it to |center|()
else
its setFrameOrigin:origin
end if
if title is not "" then its setTitle:title
if panel and floats then its setFloatingPanel:true
its setHasShadow:aShadow
if minimumSize is not {} then its setContentMinSize:minimumSize
if maximumSize is not {} then its setContentMaxSize:maximumSize
if backgroundColor is not missing value then its setBackgroundColor:backgroundColor
its setAutorecalculatesKeyViewLoop:true -- include added items in the key loop
return it
end tell
end makeWindow
# Make and return a menu from a (possibly nested) list of menu item names.
to makeMenu for menuItems given title:title : "Menu", usingTags:usingTags : true, baseTag:baseTag : 1
set theMenu to current application's NSMenu's alloc()'s initWithTitle:(title as text)
addMenuList to theMenu given itemList:menuItems, usingTags:usingTags, baseTag:baseTag
return theMenu
end makeMenu
# Add a list of menu items to a menu (recursive).
# The itemList can contain nested lists, with any given list of items being the submenu for the previous item.
# If using tags, the menu items (excluding separators) are tagged in the order they are created.
# Menu items use a common action handler and have no key equivalent by default, but individual
# items can be changed as needed by using NSMenu's itemWithTitle: method to get desired items.
to addMenuList to theMenu given itemList:itemList : {}, previousItem:previousItem : missing value, usingTags:usingTags : true, baseTag:baseTag : 1
repeat with anItem in (itemList as list)
if (contents of anItem) is in {"", {}, missing value} then
(theMenu's addItem:(current application's NSMenuItem's separatorItem()))
else if (class of anItem) is list then -- submenu items
if previousItem is not missing value then -- set menu for the submenu as needed
if not (previousItem's hasSubmenu) as boolean then -- create submenu
set submenu to (current application's NSMenu's alloc's initWithTitle:(previousItem's title))
(previousItem's setSubmenu:submenu) -- for any following lists
end if
set baseTag to (addMenuList to submenu given itemList:anItem, previousItem:previousItem, usingTags:usingTags, baseTag:baseTag)
end if
else -- treat as a menu item title
set menuItem to (theMenu's addItemWithTitle:(anItem as text) action:"menuAction:" keyEquivalent:"")
(menuItem's setTarget:me) -- for autoenable
set previousItem to menuItem -- potential submenu
if usingTags then
(menuItem's setTag:baseTag)
# log "Tag " & baseTag & " is menuItem '" & anItem & "' of menu '" & theMenu's title & "'" -- for reference
end if
set baseTag to baseTag + 1
end if
end repeat
return baseTag
end addMenuList
# Common menu action - can use sender's title or tag for comparisons.
on menuAction:sender
try -- do something with the selected menu item - example just sets the outcome property to info about the menu item
set {tagText, submenuText, parentText} to {", with no tag value set.", "", ", of the main menu "}
if ((sender's tag) as integer) is not 0 then set tagText to ", with a tag set to " & sender's tag & "."
if sender's hasSubmenu then set submenuText to ", (which is also a menu)"
try
set parentText to ", of menu '" & ((sender's parentItem's title) as text) & "'" -- immediate parent menu
end try
set my outcome to "Menu item '" & (sender's title as text) & "'" & submenuText & parentText -- & tagText
-- whatever (remember that properties are being used for results)
on error errmess number errnum
set my failure to {handlerName:"menuAction", errorMessage:errmess, errorNumber:errnum}
end try
end menuAction:
Ok, I tried this code too. In script editor it works, but not from keyboard maestro.
When I run it from keyboard maestro, something get openned, like application, and after few seconds I get missing value spoken by voiceOver.
In any case, menu doesn’t open as it certainly does from script editor.
I didn’t understand when you said if I use my own window. What did you mean?
The popup button is a control that needs to be added to a view, while NSMenu just needs to be given a view to use as a reference for positioning. If you happen to already be using a window, you can use that, otherwise the example creates a panel.
To try and isolate the script from the Keyboard Maestro runtime, have you tried running it using osascript
in a shell script, or as an application?
Hello @Lucky_Magician
the NSMenu API is there to create Application Menus. Code based on this API is supposed to be used in Applications only.
If you want to build Windows or dialogs (based on NSWindow or NSAlert) and isolate the Code from KM you can use these in KM Macros.
To isolate it always make sure the code is saved as a Script or Script-Bundle File and use another Script from Keyboard Maestro to call the isolated code. Also remember that you ensure the code runs on the main thread.
Greetings from Germany
Tobias
Here is my displayContextMenu-Function from my ContextMenu Script Library (inspired by Piyomaru):
set menuList to {"Menu Item 1", "Menu Item 2", "Menu item 3", "", "Menu item 4"}
-- show at mouse location
log my ContextMenu's displayContextMenu(menuList, "mouse")
-- show at center screen
log my ContextMenu's displayContextMenu(menuList, "center")
-- show at position
log my ContextMenu's displayContextMenu(menuList, {100, 100})
script ContextMenu
use framework "Foundation"
property parent : a reference to current application
property menuResult : missing value
on displayContextMenu(menuList, atPoint)
if (parent's NSThread's isMainThread) then
my showMenu:{menuList, atPoint}
else
my performSelectorOnMainThread:"showMenu:" withObject:({menuList, atPoint}) waitUntilDone:true
end if
return menuResult
end displayContextMenu
on showMenu:args
set args to args as list
set alist to item 1 of args
set atPoint to item 2 of args
set aMenu to parent's NSMenu's alloc()'s init()
repeat with i in alist
if contents of i ≠ "" then
set aMenuItem to (parent's NSMenuItem's alloc()'s initWithTitle:i action:"actionHandler:" keyEquivalent:"")
else
set aMenuItem to (parent's NSMenuItem's separatorItem())
end if
(aMenuItem's setTarget:me)
(aMenu's addItem:aMenuItem)
end repeat
if (class of atPoint) is text then
if atPoint is "center" then
set currWindow to parent's NSWindow's alloc()'s initWithContentRect:(parent's NSMakeRect(0, 0, 0, 0)) styleMask:0 backing:(parent's NSBackingStoreBuffered) defer:false
set currView to currWindow's contentView
currWindow's |center|()
set currPoint to parent's NSPoint's NSMakePoint(-50, (((length of alist) * 16) / 2) - 50) -- calc center pos
aMenu's popUpMenuPositioningItem:(missing value) atLocation:currPoint inView:(currView)
else
set currPoint to parent's NSEvent's mouseLocation()
aMenu's popUpMenuPositioningItem:(missing value) atLocation:currPoint inView:(missing value)
end if
else if class of atPoint is list then
set currPoint to parent's NSPoint's NSMakePoint(item 1 of atPoint, (parent's NSHeight(parent's NSScreen's screens's firstObject()'s frame)) - (item 2 of atPoint))
aMenu's popUpMenuPositioningItem:(missing value) atLocation:currPoint inView:(missing value)
end if
end showMenu:
on actionHandler:sender
set menuResult to (title of sender as string)
end actionHandler:
end script
Even though designed for NSPopUpButton
, using the popUpContextMenu:
method seems to be a better choice as it is simpler and allows an initial main menu item selection (although the menu will reposition if other than the first item).
Something came to my memories this morning when working on a script for FileMaker Pro: if you run an AppleScriptObjC script from within FMP, you need to migrate cocoa terms to properties (wich is one of the best function from Script Debugger).
Maybe it’s the same for KM?
You should try this:
use framework "Foundation"
use framework "AppKit"
use scripting additions
-- classes, constants, and enums used
property NSPopUpButton : a reference to current application's NSPopUpButton
property NSThread : a reference to current application's NSThread
property NSPanel : a reference to current application's NSPanel
-- UI item outlets
property thePanel : missing value
property thePopup : missing value
-- script properties
property outcome : missing value
property fail : missing value
on run
try
-- make sure LPX is open
if application "Logic Pro X" is not running then error "Logic Pro is not running" number -2700
-- build and show the menu on main thread
if NSThread's isMainThread() as boolean then
displayMenu()
else
my performSelectorOnMainThread:"displayMenu" withObject:(missing value) waitUntilDone:true
end if
-- return result or display alert
if fail is not missing value then error fail's theMess number fail's theNum
return outcome
on error theMess number theNum
tell application "Logic Pro X" to display alert theMess message "Error " & theNum
end try
end run
-- UI stuff needs to be done on the main thread
on displayMenu()
try
-- build the container window
set thePanel to (NSPanel's alloc()'s initWithContentRect:{{0, 0}, {400, 200}} styleMask:15 backing:2 defer:true)
tell thePanel
its |center|()
its setFloatingPanel:true
its setAutorecalculatesKeyViewLoop:true -- include added items in the key loop
end tell
-- get the list of effects in Logic Pro
set menuList to getEffects()
set menuList to menuList
if menuList = {} then error "Menu list is empty" number -2700 2700 is a generic Applescript error
-- build the pop up menu
set thePopup to (NSPopUpButton's alloc()'s initWithFrame:{{20, 20}, {0, 32}} pullsDown:true)
tell thePopup
its addItemsWithTitles:({""} & menuList) -- the menu needs a first empty item because it's a pull down
its setAction:"popupButtonAction:"
set {theW, theH} to its |menu|()'s |size|() as list
its setFrameSize:{theW, 0} -- fit the menu size to its content
its setTarget:me
end tell
-- add the pop up menu to the window
(thePanel's contentView()'s addSubview:thePopup)
-- display the menu only by closing its container window
thePanel's setInitialFirstResponder:(thePanel's contentView())
thePanel's makeKeyAndOrderFront:me
thePopup's performClick:me
thePanel's performClose:(missing value)
on error errMess number errNum
set my fail to {theMess:errMess, theNum:errNum}
end try
end displayMenu
-- Perform an action when a menu item is chosen
on popupButtonAction:sender
set selected to sender's titleOfSelectedItem as text
if (sender's pullsDown as boolean) then -- for pull-down
sender's setTitle:selected -- synchronizeTitleAndSelectedItem doesn't want to work
sender's sizeToFit() -- sized according to the title
end if
set my outcome to selected
end popupButtonAction:
-- Get the list of effects by UI scripting
on getEffects()
activate application "Logic Pro X"
tell application "System Events" to tell process "Logic Pro X"
tell (window 1 whose title ends with "- Pistes")
set theParent to a reference to (UI element 1 of UI element 1 of last group of list 1 of (group 1 whose description is "Inspecteur"))
if not (exists theParent) then error "Parent UI element is missing" number -2700
tell theParent
set theEffects to {}
set theCount to (count of groups)
if theCount < 1 then error "No children found" number -2700
repeat with theItem from 1 to theCount
set counter to count of (value of (attribute "AXChildren" of group theItem))
if counter = 3 then
set beginning of theEffects to (description of group theItem) as string
end if
end repeat
end tell
end tell
end tell
return theEffects
end getEffects
Hello Jonas (@ionah)
No, it’s not trivial … you can use Properties or not within ASObjC Scripts in KM Macros … but I’ve spottet better and more stable and also quicker execution times when Properties in these Scripts were used.
Even though I don’t have Logic - based on the Code you provided which is based on the NSPanel API - it should normally work when saved to a File and called from an AppleScript from within a KM Macro without any issues, since it is simply some sort of floating Panel that your code creates.
This is what I can say about it …
Greetings from Germany
Tobias
There shouldn’t be a need to use that particular shortcut. The standard is to use current application
to refer to the AppleScript runtime/script runner, which is where the API stuff is declared, and is why ASObjC can be used in any script. The first shortcut was to use a property declared as that, the next was to use a property declared as the class, e.g. class "NSView"
, and the current favorite is to use a reference.
My test, which I haven’t heard anything back about, was to try to isolate the environment the script is being run in by using the osascript
shell utility. That runs the script in its own instance and just returns text, so there shouldn’t be any issues with conflicting threads, terminology, or references.