Programmatically shutting down, restarting, sleeping or logging off on OS X

Browsing StackOverflow, I came across a question asking how to programmatically shut down the computer in Delphi when targeting OS X. Mysteriously, the question has been met with four downvotes as I write this, leaving it ‘on hold as off-topic’ until the darstardly questioner stops thinking a programmer’s Q&A site is a proper place for programming questions or something.

Anyhow, with respect to the question, one easy way to do the deed is to use a Cocoa NSAppleScript object to run the following piece of AppleScript:

tell application "Finder" to shut down

As desired, ‘to shut down’ can be replaced with ‘to restart’, ‘to sleep’ or ‘to log out’.

Now in Delphi, NSAppleScript (or more exactly, a wrapper interface type for NSAppleScript) is declared in the Macapi.Foundation unit. Alas, but this is misdeclared, or at least was when I last looked (see here – if someone wants to confirm this is still the case in the latest and greatest, please do in the comments). As such, you need to fix the declaration before using it. On the other hand, fixing it is easy:

uses
  Macapi.ObjectiveC, Macapi.CocoaTypes, Macapi.Foundation;

type
  NSAppleScript = interface(NSObject)
    ['{0AB1D902-25CE-4F0B-A3BE-C4ABEDEB88BC}']
    function compileAndReturnError(errorInfo: Pointer): Boolean; cdecl;
    function executeAndReturnError(errorInfo: Pointer): Pointer; cdecl;
    function executeAppleEvent(event: NSAppleEventDescriptor; error: Pointer): Pointer; cdecl;
    function initWithContentsOfURL(url: NSURL; error: Pointer): Pointer; cdecl;
    function initWithSource(source: NSString): Pointer; cdecl;
    function isCompiled: Boolean; cdecl;
    function source: NSString; cdecl;
  end;
  TNSAppleScript = class(TOCGenericImport<NSAppleScriptClass, NSAppleScript>)  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Script: NSAppleScript;
  Error: Pointer;
begin
  Error := nil;
  Script := TNSAppleScript.Wrap(TNSAppleScript.Alloc.initWithSource(
    NSSTR('tell application "Finder" to shut down')));
  try
    if Script.executeAndReturnError(Error) = nil then
      raise EOSError.Create('AppleScript macro failed');
  finally
    Script.release;
  end;
end;
Advertisements

11 thoughts on “Programmatically shutting down, restarting, sleeping or logging off on OS X

  1. Apple (OSX / ioS) programming is a real piece of shit… Its so difficult to declare that interface at the top!

    • I don’t follow, sorry – in it what way is it difficult? It’s just a regular Delphi interface with methods use the cdecl calling convention.

  2. hi all,
    I’m the one who posted that question. I felt a little upset as my question was put as on hold. This just because I don’t know where to start didn’t mean “Questions must demonstrate a minimal understanding of the problem being solved.”
    I tried to search for solutions but it is difficult because OSX documentation for XE4 is limited on Embarcadero wiki.
    Back to Chris’s answer, I appreciate for your help but I think it’s not a really “native solution”. Firstly, in my opinion, ActiveScript is not really popular on modern OSX version from OSX Lion. Apple removed ActiveScript icon from their application list.
    Few days ago, I thought about calling OSX command line such as “/sbin/shutdown -t now” to shutdown OSX but as I said, this is not an official way to do.
    I wish to translate OSX APIs declaration and use it by calling API as Apple’s sample.

    • Hoang – you are completely wrong if you think using the Cocoa interface for running AppleScript snippets is ‘not a really “native solution”‘. Cocoa is obviously native, and AppleScript remains OS X’s automation language. I also don’t know what you mean about the ‘icon’ (I’ve used OS X since Snow Leopard) – cf. http://www.apple.com/osx/apps/all.html, where the AppleScript Editor is listed as a current stock application for OS X. Ironically, the article you linked to in your StackOverflow question was running AppleScript via the Carbon API, which *is* deprecated!

      • sorry, I was wrong for the AppleScript because they changed the name to Automator. Thanks Chris. I got an API reply below.

        • No, AppleScript is still AppleScript. Automator is something that sits on top, and allows creating ‘scripts’ through drag and drop.

  3. If the API can be called as the following is great:

    procedure NSBeep; cdecl;
    external ‘/System/Library/Frameworks/AppKit.framework/AppKit’ name ‘_NSBeep’;

    • Sorry, but what has that got to do with shutting down the computer? I really wouldn’t bother trying to write the headers for the Carbon API, because the API itself might disappear in future OS X releases – for better or for worse, you need to use Cocoa, or drop down a level and use the POSIX or Mach APIs as applicable.

  4. So, what you you think about Windows API might disappear in the future like OSX? I think they are the same and some APIs are removed in modern Windows version nowadays.

  5. I’ve just moved from Windows to MacOS when Embarcadero supported it in XE4, so, it’s very new to me. I thought that there were something like Windows APIs (as you said, Carbon) to call. Thanks for your useful info.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s