AppleScript Application Still Open On Save and Quit

StefanK, I copied your script into Script Debugger, then turned it into an app, and when double clicked the window comes up on the screen and the countdown begins, perfect.

But, I have a script (turned into an app as well) that does some additional housekeeping prior to either a shut down or restart. I’ve included that script below. Your script is on my Desktop named Shut Down.scpt. Problem is, that when that my script is run (with your Shut Down.scpt included), the window no longer comes up on the screen. Any ideas?

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
delay 1
run script "Users:homer:Documents:MacBook Pro Specific:AppleScript Files:Clear Clipy.scpt" as alias
delay 1
run script "Users:homer:Documents:MacBook Pro Specific:AppleScript Files:Clear Recent Menus.scpt" as alias
delay 1
run script "Users:homer:Desktop:Shut Down.scpt" as alias

Would love to see that script. But, see my response to StafanK below, as whatever your script is, it would need to be that last item in my housekeeping shut down or restart script.

is not an app, it’s a compiled script.

The following is an extended version of the script by @KniazidisR that doesn’t put up multiple windows and includes a small cancel button in the toolbar. To prevent the UI from being blocked while in the repeat loop, a handler is used to manually process events (the cancel button works, no beachball of death, etc). Since the script also takes care of its own window, it works the same as an application and via run script and osascript.

use framework "Foundation"
use scripting additions

property notificationTitle : "The computer will shut down in 15 seconds...             " -- pad as desired for alignment
property aWidth : 360
property aHeight : 30

global cancelled -- a flag for the cancel button, since `performSelectorOnMainThread` doesn't return anything

on run
   set cancelled to false
   my performSelectorOnMainThread:"displayNotification:" withObject:{aWidth, aHeight, notificationTitle} waitUntilDone:true
   activate me
   if cancelled then -- indicate cancellation
      display alert "Shutdown cancelled" message "The system shutown has been cancelled." giving up after 3
   else -- do whatever
      display dialog "tell application \"System Events\" to shut down with state saving preference" with title "Dry Run" buttons {"OK"} default button 1
   end if
end run

on displayNotification:parameterList -- main handler
   copy (parameterList as list) to {aWidth, aHeight, aTitle}
   set aColor to current application's NSColor's colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.9
   set aWin to makeWindow(aWidth, aHeight, aTitle, 0.9)
   set aView to makeTextView(aWidth, aHeight, aColor)
   aWin's setContentView:aView
   aWin's makeKeyAndOrderFront:me
   repeat with i from 15 to 1 by -1
      (aView's setString:("" & i & " sec"))
      delay 1
      processEvents for any -- don't block UI
      if cancelled then exit repeat
   end repeat
   aWin's |close|()
end displayNotification:

to makeTextView(width, height, background) -- make a countdown textView for the window content
   tell (current application's NSTextView's alloc()'s initWithFrame:{{0, 0}, {width, height}})
      its setTextColor:(current application's NSColor's cyanColor())
      its setBackgroundColor:background
      its setFont:(current application's NSFont's fontWithName:"Menlo" |size|:18)
      its setAlignment:(current application's NSTextAlignmentCenter)
      repeat 3 times -- tweak vertical alignment
         its lowerBaseline:me
      end repeat
      its setEditable:false -- no insertion point
      return it
   end tell
end makeTextView

to makeWindow(width, height, title, alpha) -- make a window with a titlebar accessory
   tell (current application's NSWindow's alloc()'s initWithContentRect:{{0, 0}, {width, height}} styleMask:1 backing:2 defer:true screen:(current application's NSScreen's mainScreen()))
      its |center|()
      its setDisplaysWhenScreenProfileChanges:true
      its setAlphaValue:alpha
      its setReleasedWhenClosed:true
      its setLevel:(current application's NSNormalWindowLevel) -- NSFloatingWindowLevel
      its setTitle:title
      its addTitlebarAccessoryViewController:(my makeTitleBar())
      return it
   end tell
end makeWindow

to makeTitleBar() -- make the titlebar accessory
   set titlebarView to current application's NSView's alloc()'s initWithFrame:{{0, 0}, {30, 30}}
   titlebarView's addSubview:(makeCancelButton(0, 4)) -- tweak to center vertically
   tell current application's NSTitlebarAccessoryViewController's alloc()'s init()
      its setAutomaticallyAdjustsSize:true
      its setView:titlebarView
      its setLayoutAttribute:(current application's NSLayoutAttributeRight)
      return it
   end tell
end makeTitleBar

to makeCancelButton(x, y) -- make a cancel button for the titlebar accessory
   tell (current application's NSButton's alloc's initWithFrame:{{x, y}, {20, 20}})
      its setButtonType:(current application's NSMomentaryPushInButton)
      its setBezelStyle:(current application's NSCircularBezelStyle)
      its setBordered:false
      its setImageScaling:(current application's NSImageScaleProportionallyUpOrDown)
      its setImage:(current application's NSImage's imageNamed:(current application's NSImageNameStopProgressFreestandingTemplate))
      its setTarget:me
      its setAction:"cancelAction:"
      its setKeyEquivalent:(character id 27) -- escape key
      its setRefusesFirstResponder:true -- no focus ring
      return it
   end tell
end makeCancelButton

on cancelAction:sender -- set a flag indicating the cancel button was pressed
   set cancelled to true
end cancelAction:

to processEvents for eventMask given mode:runLoopMode : (missing value) -- manually handle events to avoid blocking the UI
   if eventMask is any then set eventMask to a reference to current application's NSEventMaskAny
   if runLoopMode is missing value then set runLoopMode to current application's NSDefaultRunLoopMode -- NSModalPanelRunLoopMode
   repeat -- forever
      tell current application's NSApp to set theEvent to its nextEventMatchingMask:eventMask untilDate:(missing value) inMode:runLoopMode dequeue:true
      if theEvent is missing value then exit repeat -- none left
      tell current application's NSApp to sendEvent:theEvent
   end repeat
end processEvents
2 Likes

FWIW, another option is the free swiftDialog app (here). One issue with this script is that the keyboard cancel key does not cancel the dialog. You instead have to press the Enter key or select the Stop button with the mouse.

As written, the dialog only shows a message with the count-down text, but it can be changed with icons, markdown text, and many other enhancements.

set dialogPID to do shell script "/usr/local/bin/dialog --title none --message 'The computer will shut down in 10 seconds' --messagefont 'size=14' --width 325 --height 75 --messageposition bottom --button1text 'Stop' --hideicon &> /dev/null & echo $!"

repeat with i from 9 to 0 by -1 --update dialog content
	delay 1
	if dialogExists(dialogPID) is false then error number -128 --stop script if dialog not found
	do shell script "/bin/echo 'message: The computer will shut down in " & i & " seconds' >> /var/tmp/dialog.log"
end repeat

do shell script "/bin/echo 'quit: true' >> /var/tmp/dialog.log" --quit dialog

--tell application "System Events" to shut down with state saving preference

on dialogExists(dialogPID)
	try
		do shell script "ps -p " & dialogPID & " > /dev/null"
		return true
	end try
	return false
end dialogExists

Another option with swiftDialog is:

set dialogResult to do shell script "/usr/local/bin/dialog --title none --message 'The computer will sleep in 10 seconds' --messagefont 'size=14' --button1text 'Sleep' --button2text 'Cancel' --width 350 --height 90 --hideicon --timer 10 --messageposition bottom; echo $?"
if dialogResult is "2" then error number -128
tell application "System Events" to sleep

The last line of the housekeeping script is in fact correct, unless you were referring to something else.

That is one impressive script. At the very end a window pops up, and I tried to figure out where to put the “tell application “System Events” to shut down with state saving preference”. I must have tried at least a half dozen locations, with no success. I’m sure you have my solution, thank you.

I placed that in the dialog in the run handler to avoid any accidents, so just replace the display dialog statement with your shutdown code.

Works like a charm, thank you very much. My next attempt will be to turn your shut down script into a restart script, I’m sure I’ll manage. Thanks again!

Edit: That didn’t take long, I have both your shut down and restart scripts working well with my housekeeping script.