OSXOperatingSystem.st
author Claus Gittinger <cg@exept.de>
Tue, 09 Jul 2019 20:55:17 +0200
changeset 24417 03b083548da2
parent 24378 c46855b3748f
child 24464 63a55c7a8813
permissions -rw-r--r--
#REFACTORING by exept class: Smalltalk class changed: #recursiveInstallAutoloadedClassesFrom:rememberIn:maxLevels:noAutoload:packageTop:showSplashInLevels: Transcript showCR:(... bindWith:...) -> Transcript showCR:... with:...

"
 COPYRIGHT (c) 2013 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libbasic' }"

"{ NameSpace: Smalltalk }"

UnixOperatingSystem subclass:#OSXOperatingSystem
	instanceVariableNames:''
	classVariableNames:'NSApp'
	poolDictionaries:''
	category:'OS-Unix'
!

!OSXOperatingSystem primitiveDefinitions!
%{

#ifdef __osx__
// mhm - how could they forget this????
# ifndef __XPC_IOS_SIMULATOR_AVAILABLE_STARTING
#  define __XPC_IOS_SIMULATOR_AVAILABLE_STARTING(version)
# endif
#endif

#ifndef __osx__
# define NO_QUARTZ
# define NO_COCOA
# define NO_CARBON
# define NO_COREGRAPHICS
#endif

// #include <mach/clock.h>
// #include <mach/mach.h>
// #include <sys/time.h>
// #include <mach/clock.h>

#ifndef NO_COCOA

#   undef Array
#   undef Number
#   undef Method
#   undef Point
#   undef Rectangle
#   undef Block
#   undef String
#   undef Character
#   undef Message
#   undef Object
#   undef Context
#   undef Time
#   undef Date
#   undef Set
#   undef Signal
#   undef Delay
#   undef NameSpace
#   undef Process
#   undef Processor
#   undef Class
#   undef FixedPoint
#   undef true
#   undef false

#   undef INT
#   undef UINT

# include <ApplicationServices/ApplicationServices.h>
# include <CoreFoundation/CoreFoundation.h>
// #include <Foundation/Foundation.h>
# include <CoreGraphics/CGGeometry.h>

// #include <CoreGraphics/CGEventTypes.h>

# include <stdlib.h>
# include <stdio.h>
# include <IOKit/graphics/IOGraphicsLib.h>

# include <objc/runtime.h>
# include <objc/message.h>

#  ifdef __DEF_Array
#   define Array __DEF_Array
#  endif
#  ifdef __DEF_Number
#   define Number __DEF_Number
#  endif
#  ifdef __DEF_Method
#   define Method __DEF_Method
#  endif
#  ifdef __DEF_Point
#   define Point __DEF_Point
#  endif
#  ifdef __DEF_Rectangle
#   define Rectangle __DEF_Rectangle
#  endif
#  ifdef __DEF_Block
#   define Block __DEF_Block
#  endif
#  ifdef __DEF_String
#   define String __DEF_String
#  endif
#  ifdef __DEF_Character
#   define Character __DEF_Character
#  endif
#  ifdef __DEF_Message
#   define Message __DEF_Message
#  endif
#  ifdef __DEF_Object
#   define Object __DEF_Object
#  endif
#  ifdef __DEF_Context
#   define Context __DEF_Context
#  endif
#  ifdef __DEF_Date
#   define Date __DEF_Date
#  endif
#  ifdef __DEF_Time
#   define Time __DEF_Time
#  endif
#  ifdef __DEF_Set
#   define Set __DEF_Set
#  endif
#  ifdef __DEF_Signal
#   define Signal __DEF_Signal
#  endif
#  ifdef __DEF_Delay
#   define Delay __DEF_Delay
#  endif
#  ifdef __DEF_NameSpace
#   define NameSpace __DEF_NameSpace
#  endif
#  ifdef __DEF_Process
#   define Process __DEF_Process
#  endif
#  ifdef __DEF_Processor
#   define Processor __DEF_Processor
#  endif
#  ifdef __DEF_Class
#   define STX_Class __DEF_Class
#  endif
#  ifdef __DEF_FixedPoint
#   define FixedPoint __DEF_FixedPoint
#  endif
#  ifdef __DEF_true
#   undef true
#   define true __DEF_true
#  endif
#  ifdef __DEF_false
#   undef false
#   define false __DEF_false
#  endif

#  define INT STX_INT
#  define UINT STX_UINT

typedef struct CMPoint {
	double x;
	double y;
} CMPoint;

typedef struct CMSize {
	double width;
	double height;
} CMSize;

typedef struct CMRect {
	CMPoint origin;
	CMSize size;
} CMRect;

typedef struct AppDel {
	Class isa;

	// Will be an NSWindow later.
	id window;
} AppDelegate;


enum {
	NSBorderlessWindowMask          = 0,
	NSTitledWindowMask                      = 1 << 0,
	NSClosableWindowMask            = 1 << 1,
	NSMiniaturizableWindowMask      = 1 << 2,
	NSResizableWindowMask           = 1 << 3,
};

typedef id(*CMacsSimpleMessage)(id, SEL);
typedef void(*CMacsVoidMessage)(id, SEL);
typedef void(*CMacsVoidMessage1)(id, SEL, void *);
typedef id(*CMacsRectMessage1)(id, SEL, CMRect);
typedef id(*CMacsWindowInitMessage)(id, SEL, CMRect, int, int, bool);

extern CMacsSimpleMessage cmacs_simple_msgSend;
extern CMacsVoidMessage cmacs_void_msgSend;
extern CMacsVoidMessage1 cmacs_void_msgSend1;
extern CMacsRectMessage1 cmacs_rect_msgSend1;
extern CMacsWindowInitMessage cmacs_window_init_msgSend;

#endif // NO_COCOA

%}
! !

!OSXOperatingSystem primitiveFunctions!
%{

#ifndef NO_COCOA

struct ev {
    struct ev* nextEvent;
    CGEventType type;
    CGKeyCode keycode;
    int x,y;
    int button;
};
static struct ev *nextRecordedEvent = NULL;
static struct ev *lastRecordedEvent = NULL;

CGEventRef
myCGEventCallback(CGEventTapProxy proxy, CGEventType type,
		  CGEventRef event, void *refcon)
{
    if ((type == kCGEventKeyDown) || (type == kCGEventKeyUp)) {
	// The incoming keycode.
	CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
    }
    if (type == kCGEventMouseMoved) {
	// loc = [NSEvent mouseLocation]
	// int x = loc.x;
	// int y = loc.y;
    }
    // We must return the event for it to be useful.
    return event;
}

BOOL
AppDel_didFinishLaunching(AppDelegate *self, SEL _cmd, id notification) {
//    self->window = cmacs_simple_msgSend((id)objc_getClass("NSWindow"), sel_getUid("alloc"));
//
//    /// Create an instance of the window.
//    self->window = cmacs_window_init_msgSend(self->window, sel_getUid("initWithContentRect:styleMask:backing:defer:"), (CMRect){0,0,1024,460}, (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask), 0, false);
//
//    /// Create an instance of our view class.
//    ///
//    /// Relies on the view having declared a constructor that allocates a class pair for it.
//    id view = cmacs_rect_msgSend1(cmacs_simple_msgSend((id)objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (CMRect){ 0, 0, 320, 480 });
//
//    // here we simply add the view to the window.
//    cmacs_void_msgSend1(self->window, sel_getUid("setContentView:"), view);
//    cmacs_simple_msgSend(self->window, sel_getUid("becomeFirstResponder"));
//
//    // Shows our window in the bottom-left hand corner of the screen.
//    cmacs_void_msgSend1(self->window, sel_getUid("makeKeyAndOrderFront:"), self);
    return YES;
}

#endif // NO_COCOA

%}
! !

!OSXOperatingSystem class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2013 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    a small number of OS-X specific redefinitions here.

    [Author:]
	Claus Gittinger
"
! !

!OSXOperatingSystem class methodsFor:'initialization'!

initializeCodeset
    super initializeCodeset.
    Codeset := #'utf8-mac'.
    CodesetEncoder := nil.
! !

!OSXOperatingSystem class methodsFor:'cocoa - events'!

finishLaunching
    "tell the system, that I have finished my startup phase.
     OSX will stop bounding the launch icon then."

    NSApp isNil ifTrue:[
	self getNSApp
    ].
%{
#ifndef NO_COCOA
    id NSApp = __externalAddressVal(@global(NSApp));
    extern int __stxNSAppIsRunning();

    if (!__stxNSAppIsRunning()) {
	objc_msgSend(NSApp, sel_getUid("finishLaunching"));
    }
    RETURN(self);
#endif // NO_COCOA
%}.
    "/ if compiled without COCOA support (which you should not),
    "/ the icon will probably continue to bounce...
    "/ ... but we do not want an error here.

    "/ self primitiveFailed

    "
     self finishLaunching
    "

    "Modified: / 28-02-2017 / 10:59:15 / cg"
!

generateButtonEvent:button down:down x:screenX y:screenY
    "synthesize and send a button event to the screen"
%{
#ifndef NO_COCOA
    if (__bothSmallInteger(screenX, screenY)
     && __isSmallInteger(button)) {
	CGPoint pt;
	pt.x = __intVal(screenX);
	pt.y = __intVal(screenY);

	CGEventType evType;
	CGMouseButton evButton = __intVal(button);

	switch (evButton) {
	    case 1:
		evType = (down == true) ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
		evButton = 0;
		break;
	    case 2:
		evType = (down == true) ? kCGEventRightMouseDown : kCGEventRightMouseUp;
		evButton = 1;
		break;
	    case 3:
		evType = (down == true) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
		evButton = 2;
		break;
	    default:
		evType = (down == true) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
		evButton = __intVal(button) - 1;
		break;
	}

	CGEventRef theEvent = CGEventCreateMouseEvent(NULL, evType, pt, evButton);
	CGEventSetType(theEvent, evType);
	CGEventPost(kCGHIDEventTap, theEvent);
	CFRelease(theEvent);
	RETURN(self);
    }
#endif // NO_COCOA
%}.
    self primitiveFailed

    "
     OperatingSystem generateButtonEvent:1 down:true x:150 y:150.
     OperatingSystem generateButtonEvent:1 down:false x:150 y:150.

     OperatingSystem generateButtonEvent:2 down:true x:150 y:150.
     OperatingSystem generateButtonEvent:2 down:false x:150 y:150.
    "

    "Created: / 28-02-2017 / 00:15:18 / cg"
!

generateKeyboardEvent:keyCode down:down
    "synthesize and send a keyboard event to the screen"
%{
#ifndef NO_COCOA
    if (__isSmallInteger(keyCode)) {
	int evType;

	CGEventRef theEvent = CGEventCreateKeyboardEvent(NULL, __intVal(keyCode), (down==true)?1:0);
	CGEventSetType(theEvent, (down==true) ? kCGEventKeyDown:kCGEventKeyUp);
	CGEventPost(kCGHIDEventTap, theEvent);
	CFRelease(theEvent);
	RETURN(self);
    }
#endif // NO_COCOA
%}.
    self primitiveFailed

    "
     OperatingSystem generateKeyboardEvent:6 down:true.
     OperatingSystem generateKeyboardEvent:6 down:false.
    "

    "Created: / 02-03-2017 / 16:35:04 / cg"
!

generateMouseMoveEventX:screenX y:screenY
    "synthesize and send a mouse move event to the screen"
%{
#ifndef NO_COCOA
    if (__bothSmallInteger(screenX, screenY)) {
	CGPoint pt;
	pt.x = __intVal(screenX);
	pt.y = __intVal(screenY);

	CGEventType evType;
	CGEventRef theEvent = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, pt, 0);
	CGEventSetType(theEvent, kCGEventMouseMoved);
	CGEventPost(kCGHIDEventTap, theEvent);
	CFRelease(theEvent);
	RETURN(self);
    }
#endif // NO_COCOA
%}.
    self primitiveFailed

    "
     OperatingSystem generateMouseMoveEventX:150 y:5.
     Delay waitFor:(1 seconds).
     OperatingSystem generateMouseMoveEventX:1050 y:150.
    "

    "Created: / 02-03-2017 / 20:01:51 / cg"
!

getNSApp
    "get the NSApplication instance and remember in the global named NSApp"

%{
#ifndef NO_COCOA
    Class AppDelClass = NULL;
    id NSApp = NULL;

# if 0 /* not needed */
    Class poolClass = objc_getClass("NSAutoreleasePool");
    if (poolClass == NULL) {
	fprintf(stderr, "OSX: Unable to get NSAutoreleasePool\n");
	RETURN(false);
    }
    id pool = objc_msgSend(poolClass, sel_registerName("alloc"));
    if (pool == NULL) {
	fprintf(stderr, "OSX: Unable to create NSAutoreleasePool\n");
	RETURN(false);
    }
    objc_msgSend(pool, sel_registerName("init"));
# endif

# if 0 /* not needed */
    AppDelClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "AppDelegate", 0);
    class_addMethod(AppDelClass, sel_getUid("applicationDidFinishLaunching:"), (IMP)AppDel_didFinishLaunching, "i@:@");
    objc_registerClassPair(AppDelClass);
# endif

    NSApp = objc_msgSend((id)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));
    if (NSApp == NULL) {
	fprintf(stderr,"OSX: Failed to initialize NSApplication\n");
	RETURN(false);
    }
    @global(NSApp) = __MKEXTERNALADDRESS(NSApp);

# if 0 /* not needed */
    id req = objc_msgSend(NSApp,sel_getUid("requestUserAttention:"),0);
    objc_msgSend(NSApp,sel_getUid("cancelUserAttentionRequest:"),req);
# endif

# if 0 /* not needed */
    id appDelInst = objc_msgSend((id)objc_getClass("AppDelegate"), sel_getUid("alloc"));
    objc_msgSend(appDelInst, sel_getUid("init"));
    objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelInst);
# endif

# if 0 /* not needed */
    objc_msgSend(pool, sel_registerName("release"));
# endif
    RETURN(true);
#endif // NO_COCOA
%}.
    self primitiveFailed

    "
     self getNSApp
    "

    "Modified: / 28-02-2017 / 13:45:39 / cg"
!

receiveNextEvent
%{
#ifndef NO_COCOA
    CFMachPortRef      eventTap;
    CGEventMask        eventMask;
    CFRunLoopSourceRef runLoopSource;

    // Create an event tap. We are interested in key presses.
    // sigh: a global event tap requires root privileges
    eventMask = ((1 << kCGEventKeyDown)
		| (1 << kCGEventKeyUp)
		| (1 << kCGEventMouseMoved));
    eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0,
				eventMask, NULL, myCGEventCallback);
    if (!eventTap) {
	fprintf(stderr, "failed to create event tap\n");
    } else {
	fprintf(stderr, "ok\n");

	// Create a run loop source.
	runLoopSource = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, eventTap, 0);
	// Enable the event tap.
	CGEventTapEnable(eventTap, true);
    }

#endif // NO_COCOA
%}.
    self primitiveFailed

    "Modified: / 28-02-2017 / 01:05:04 / cg"
! !

!OSXOperatingSystem class methodsFor:'dummy shell operations'!

openApplicationForDocument:aFilenameOrString operation:operationSymbol
    "open a windows-shell/mac finder/desktop application to present the document contained in aFilenameOrString.
     This is typically used to present help-files, html documents, pdf documents etc.
     operationSymbol is one of:
	open
	edit
	explore
    "

    |openCmd|

    (openCmd := self openApplicationHelperCommand) notNil ifTrue:[
	(operationSymbol = 'open') ifTrue:[
	    self executeCommand:'open "',aFilenameOrString asFilename pathName,'"'
	] ifFalse:[
	    (operationSymbol = 'explore') ifTrue:[
		self executeCommand:'open -R "',aFilenameOrString asFilename pathName,'"'
	    ].
	].
    ].
!

openApplicationHelperCommand
    "Return a command line helper to open a default application for file or URL"

    (self canExecuteCommand: 'open') ifTrue:[
	^ 'open'
    ].
    ^ nil

    "Created: / 13-01-2015 / 09:23:18 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

openTerminalWithCommand:shellCommand inBackground:inBackground
    "open a new terminal, which executes a command"

    |cmd|

    cmd := 'osascript -e ''tell application "Terminal" to do script "%1"''' bindWith:shellCommand.

    inBackground ifTrue:[
	^ self
	    startProcess:cmd
	    inputFrom:nil
	    outputTo:nil
	    errorTo:nil
	    auxFrom:nil
	    environment:nil
	    inDirectory:nil
    ] ifFalse:[
	^ self executeCommand:cmd
    ].

    "
     OSXOperatingSystem openTerminalWithCommand:'ls -l' inBackground:true
    "
! !

!OSXOperatingSystem class methodsFor:'file queries'!

caseSensitiveFilenames
    "return true, if the OS has caseSensitive file naming.
     On MSDOS, this will return false;
     on a real OS, we return true.
     Be aware, that OSX can be configured to be either.
     Also, that it actually depends on the mounted volume"

    "/ actually, this is wrong and depends on the mounted volume;
    "/ so we need a query for a particular directory (and/or volume).
    ^ false

    "Modified: / 5.6.1998 / 18:35:18 / cg"
!

defaultPackagePath
    "redefined to add /Application and /Library stuff"
    "called by Smalltalk initSystemPath"
    "self defaultPackagePath"

    |path executablePath executableDir packagesDir
     libDir appDir versionsDir vsnDirName vsnDir|

    path := super defaultPackagePath.

    executablePath := OperatingSystem pathOfSTXExecutable.
    executablePath notNil ifTrue:[
	executableDir := executablePath asFilename directory.
	packagesDir := executableDir directory directory / 'Packages'.
	packagesDir exists ifTrue:[
	    packagesDir := packagesDir pathName.
	    (path includes:packagesDir) ifFalse:[
		path add:packagesDir.
	    ].
	].
	libDir := '/Library/Frameworks/SmalltalkX.framework' asFilename.
	libDir exists ifTrue:[
	    versionsDir := libDir / 'Versions'.
	    versionsDir exists ifTrue:[
		vsnDirName := '%1.%2.%3'
				    bindWith:Smalltalk majorVersionNr
				    with:Smalltalk minorVersionNr
				    with:Smalltalk revisionNr.
		vsnDir := versionsDir / vsnDirName.
		vsnDir exists ifTrue:[
		    vsnDir := vsnDir pathName.
		    (path includes:vsnDir) ifFalse:[
			path add:vsnDir.
		    ].
		].
	    ].
	].
	appDir := '/Applications/SmalltalkX/' asFilename.
	appDir exists ifTrue:[
	    versionsDir := appDir / 'Versions'.
	    versionsDir exists ifTrue:[
		vsnDirName := '%1.%2.%3'
				    bindWith:Smalltalk majorVersionNr
				    with:Smalltalk minorVersionNr
				    with:Smalltalk revisionNr.
		vsnDir := versionsDir / vsnDirName.
		vsnDir exists ifTrue:[
		    vsnDir := vsnDir pathName.
		    (path includes:vsnDir) ifFalse:[
			path add:vsnDir.
		    ].
		].
	    ].
	].
    ].
    ^ path
!

getDocumentsDirectory
    "return your documents directory.
     Under osx, that's the user's 'Documents' folder.
     The fallback for other OS's may return the user's home directory."

    "{ Pragma: +optSpace }"

    |home documentsFolder|

    (home := self getHomeDirectory) notNil ifTrue:[
	(documentsFolder := home asFilename / 'Documents') exists ifTrue:[
	    ^ documentsFolder pathName
	].
    ].
    ^ home

    "
     OperatingSystem getDocumentsDirectory
    "
!

getDriveList
    "return a list of volumes in the system."

    ^ ('/Volumes' asFilename directoryContents)
      , super getDriveList
!

getTrashDirectory
    "get the name of a trash folder (if the OS supports it),
     or nil, if not.
     Must be redefined to return non nil in concrete operating systems"

    ^ '~/.Trash'
!

pathNameForDrive:driveName
    "given a drive name, return the pathname to open it as a directory.
     For Windows, this is the driveName itself.
     For OSX, '/Volumes' is prepended.
     Other OSs might prepent the pount point (i.e. /mnt/)"

    driveName isNil ifTrue:[^ nil].
    driveName asFilename isAbsolute ifTrue:[^ driveName].
    ^ '/Volumes/',driveName
!

supportsVolumes
    "return true if the os support a list of drives/volumes
     (here we can return a list of mounted drives in /Volumes)"

    ^ true
! !

!OSXOperatingSystem class methodsFor:'quartz - screen'!

getFrameBufferImage:displayNr
    "returns the framebuffer as an image object"

    ^ self getFrameBufferImage:displayNr in:nil

    "
     self getFrameBufferImage:0
    "

    "Modified: / 25-02-2017 / 10:57:17 / cg"
!

getFrameBufferImage:displayNr in:aRectangleOrNil
    "returns the frameBuffer (if rect-arg is nil)
     or part of the framebuffer (if non-nil) as an image object"

    |rx ry rwidth rheight bytesPerPixel bytesPerRow address pixels depth pad img ok|

    aRectangleOrNil notNil ifTrue:[
        rx := aRectangleOrNil left.
        ry := aRectangleOrNil top.
        rwidth := aRectangleOrNil width.
        rheight := aRectangleOrNil height.
    ].
%{
#ifndef NO_COCOA
    CGImageRef image_ref;
    CGDirectDisplayID displayID = CGMainDisplayID();

    ok = false;
    if (rx == nil) {
        image_ref  = CGDisplayCreateImage(displayID);
    } else {
        CGRect rect;
        rect.origin.x = (CGFloat)__intVal(rx);
        rect.origin.y = (CGFloat)__intVal(ry);
        rect.size.width = (CGFloat)__intVal(rwidth);
        rect.size.height = (CGFloat)__intVal(rheight);
        image_ref  = CGDisplayCreateImageForRect(displayID, rect);
    }

    if (image_ref != NULL) {
        CGDataProviderRef provider = CGImageGetDataProvider(image_ref);
        CFDataRef dataref = CGDataProviderCopyData(provider);
        size_t c_width = CGImageGetWidth(image_ref);
        size_t c_height = CGImageGetHeight(image_ref);
        size_t c_bytesPerRow = CGImageGetBytesPerRow(image_ref);
        size_t c_bytesPerPixel = CGImageGetBitsPerPixel(image_ref) / 8;
        unsigned char *c_pixels = (unsigned char *)CFDataGetBytePtr(dataref);

        if (c_bytesPerPixel == 4) {
            int row;
            pixels = __BYTEARRAY_UNINITIALIZED_NEW_INT(c_width * c_height * 3);
            unsigned char *srcRowPtr = c_pixels;
            unsigned char *dstRowPtr = __byteArrayVal(pixels);

            for (row=c_height; row>0; row--) {
                int col = c_width;
                unsigned int *pixSrcPtr = (unsigned int*)srcRowPtr;
                unsigned char *pixDstPtr = dstRowPtr;

                // convert abgr to rgb
#if (__POINTER_SIZE__ == 8) && defined(__LSBFIRST__)
                if (((INT)pixSrcPtr & 7) == 0) {
                    // is aligned
                    for (; col > 4; col -= 4) {
                        // pick 4 pixels (16byte with 2 INT fetches) store as 12byte (4ints)
                        unsigned INT pix12 = ((unsigned INT*)pixSrcPtr)[0];
                        unsigned INT pix34 = ((unsigned INT*)pixSrcPtr)[1];
                        unsigned int w;
                        
                        pixSrcPtr += 4;
                        // a2 r2 g2 b2 a1 r1 g1 b1 => r1..g1..b1..r2..g2..b2
                        w = (pix12 >> 16) & 0xFF;         // r1
                        w |= ((pix12 >> 8) & 0xFF) << 8;  // g1
                        w |= (pix12 & 0xFF) << 16;        // b1
                        w |= ((pix12 >> 48) & 0xFF)<<24;  // r2 
                        ((int*)pixDstPtr)[0] = w;
                        w = (pix12 >> 40) & 0xFF;         // g2 
                        w |= ((pix12 >> 32) & 0xFF)<<8;   // b2 
                        w |= ((pix34 >> 16) & 0xFF)<<16;  // r3
                        w |= ((pix34 >> 8) & 0xFF) <<24;  // g3
                        ((int*)pixDstPtr)[1] = w;
                        w = (pix34 & 0xFF);               // b3
                        w |= ((pix34 >> 48) & 0xFF)<<8;   // r4 
                        w |= ((pix34 >> 40) & 0xFF)<<16;  // g2 
                        w |= ((pix12 >> 32) & 0xFF)<<24;  // b2 
                        ((int*)pixDstPtr)[2] = w;
                        pixDstPtr += 12;
                    }
                }
                for (; col > 2; col -= 2) {
                    // pick 2 pixels (8byte with one fetch) store as 6byte
                    unsigned INT pix12 = ((unsigned INT*)pixSrcPtr)[0];
                    pixSrcPtr += 2;
                    // a2 r2 g2 b2 a1 r1 g1 b1 => r1..g1..b1..r2..g2..b2
                    pixDstPtr[0] = (pix12 >> 16) & 0xFF;
                    pixDstPtr[1] = (pix12 >> 8) & 0xFF;
                    pixDstPtr[2] = pix12 & 0xFF;
                    pixDstPtr[3] = (pix12 >> 48) & 0xFF;
                    pixDstPtr[4] = (pix12 >> 40) & 0xFF;
                    pixDstPtr[5] = (pix12 >> 32) & 0xFF;
                    pixDstPtr += 6;
                }
#endif

                for (; col>0;col--) {
                    unsigned int pix = *pixSrcPtr++;
#if 0
                    pix = ((pix >> 16) & 0x0000FF)|((pix << 16) & 0xFF0000)|(pix & 0x00FF00);
                    pixDstPtr[0] = (pix >> 0);
                    pixDstPtr[1] = (pix >> 8);
                    pixDstPtr[2] = (pix >> 16);
#else
                    pixDstPtr[0] = (pix >> 16) & 0xFF;
                    pixDstPtr[1] = (pix >> 8) & 0xFF;
                    pixDstPtr[2] = pix & 0xFF;
#endif
                    pixDstPtr += 3;
                }
                dstRowPtr += c_width * 3;
                srcRowPtr += c_bytesPerRow;
            }
            bytesPerPixel = __MKSMALLINT(3);
            bytesPerRow = __MKSMALLINT( c_width*3 );
            ok = true;
        } else {
            // to be determined what we get...
            ok = false;
        }
        CFRelease(dataref);
        CGImageRelease(image_ref);

        rwidth = __MKUINT( c_width );
        rheight = __MKUINT( c_height );
    }

    // the following is no longer supported by apple
    // CGDisplayBaseAddress is deprecated
    //
    //    uint32_t rowBytes, rowUInt32s, *screen;
    //
    //    CGDirectDisplayID targetDisplay = 0;
    //
    //    screen = (uint32_t *)CGDisplayBaseAddress(targetDisplay);
    //    rowBytes = CGDisplayBytesPerRow(targetDisplay);
    //    rowUInt32s = rowBytes / 4;
    //    rwidth = __MKUINT( CGDisplayPixelsWide(targetDisplay) );
    //    rheight = __MKUINT( CGDisplayPixelsHigh(targetDisplay) );
    //

#endif // NO_COCOA

%}.
    (ok not or:[rwidth isNil]) ifTrue:[
        ^ self primitiveFailed
    ].
    depth := bytesPerPixel * 8.

    "/ Transcript printf:'w:%d h:%d bpp:%d depth:%d bpr: %d\n'
    "/            withAll:{ rwidth . rheight . bytesPerPixel . depth . bytesPerRow}.

    ( #(24) includes:depth) ifFalse:[
        "/ check what we get here...
        ^ self primitiveFailed:'unsupported depth'
    ].

    img := Image extent:(rwidth @ rheight) depth:depth bits:pixels.
    depth == 24 ifTrue:[
        img bitsPerSample:#[8 8 8].
        img samplesPerPixel:3.
        img photometric:#rgb.
    ] ifFalse:[
        depth == 16 ifTrue:[
            "/ to be determined...
            img bitsPerSample:#[5 5 5].
            img samplesPerPixel:3.
            img photometric:#rgb.
        ].
    ].
    ^ img

    "
     self getFrameBufferImage:0 in:nil
     self getFrameBufferImage:0 in:(100@100 corner:301@303)

     Delay waitFor:3 seconds. self getFrameBufferImage:0 in:nil
    "

    "Created: / 25-02-2017 / 09:49:07 / cg"
    "Modified (comment): / 28-02-2017 / 15:22:17 / cg"
    "Modified: / 27-06-2019 / 15:21:51 / Claus Gittinger"
!

getMousePosition
    |xLoc yLoc|

%{
#ifndef NO_COREGRAPHICS
    CGEventRef event = CGEventCreate(NULL);
    CGPoint loc = CGEventGetLocation(event);
    CFRelease(event);

    xLoc = __MKFLOAT(loc.x);
    yLoc = __MKFLOAT(loc.y);
#endif // NO_COREGRAPHICS
%}.
    ^ xLoc @ yLoc
!

getScreenBounds:displayNr
    "returns the bounds of the screen.
     This is needed, because the dimension of the XQuartz screen does not include the dock."

    |rx ry rwidth rheight|

%{
#ifndef NO_COCOA
    CGDirectDisplayID targetDisplay = 0;

    rx = ry = __MKSMALLINT(0);
    rwidth = __MKUINT( CGDisplayPixelsWide(targetDisplay) );
    rheight = __MKUINT( CGDisplayPixelsHigh(targetDisplay) );
#endif // NO_COCOA
%}.
    (rwidth isNil) ifTrue:[
	^ self primitiveFailed
    ].
    ^ rx@ry extent:(rwidth@rheight)

    "
     self getScreenBounds:0
    "

    "Created: / 05-03-2017 / 11:10:08 / cg"
! !

!OSXOperatingSystem class methodsFor:'sound & voice'!

canPlaySound
    ^ true

    "Created: / 29-08-2018 / 09:35:52 / Claus Gittinger"
!

canSpeak
    ^ true

    "Created: / 29-08-2018 / 09:45:16 / Claus Gittinger"
!

playSound:soundFile
    "use 'say ...'"

    ^ self executeCommand:('afplay "%1"' bindWith:soundFile asFilename pathName)

    "
     self speak:'hello world'
    "
!

voiceCommandSpec
    ^ #(
	"/ triples are:
	"/      -command
	"/      -commandline for default voice
	"/      -commandline for specific voice
	(
	    'say'
		'say "%2"'
		'say -v "%1" "%2"'
	)
    )
!

voiceInfo
    "return a list of available voices plus info:
     for each available voice, a triple is returned, containing:
	voiceName language_territory comment/description
    "

    ^ (PipeStream outputFromCommand:'say -v ?')
	asCollectionOfLines
	    collect:[:l |
		|s name lang comment|

		s := l readStream.
		"/ skip forward for two separators
		[
		    [s next isLetterOrDigit] whileTrue:[].
		    s peek isLetterOrDigit
		] whileTrue.
		name := l copyFrom:1 to:s position - 1.
		s skipSeparators.
		lang := s upToSeparator.
		s skipSeparators.
		s next == $# ifTrue:[
		    s skipSeparators.
		    comment := s upToEnd utf8Decoded.
		].
		{ name . lang . comment }
	    ]

    "
     OperatingSystem voiceInfo
    "
!

voiceMapping
    "return a mapping from common (OS-independent) voice names
     to OS-specific names or IDs.
     The speak:voiceName interface will recognize both.
     For portable programs, always use the OS-independent name and
     let every OS xlate to its internal name."

    |info map v|

    VoiceMapping notNil ifTrue:[^ VoiceMapping].

    info := self voiceInfo.
    map := OrderedCollection new.
    v := info detect:[:entry | #('Tom' 'Alex' 'Fred' 'Bruce' 'Ralph' 'Daniel') includes:entry first] ifNone:nil.
    map add:( 'male' -> (v ? #(nil)) first ).
    v := info detect:[:entry | #('Samantha' 'Fiona' 'Karen' 'Moira') includes:entry first] ifNone:nil.
    map add:( 'female' -> (v ? #(nil)) first ).
    v := info detect:[:entry | #('Zarvox') includes:entry first] ifNone:nil.
    map add:( 'computer' -> (v ? #(nil)) first ).
    map add:( 'default' -> nil ).
    VoiceMapping := map.
    ^ VoiceMapping

    "on OSX, this could be:
	^ {
	    'male' -> 'Alex' .
	    'female' -> 'Fiona' .
	    'computer' -> 'Zarvox' .
	    'default' -> 'Fiona'
	}
    "

    "
     OperatingSystem voiceMapping
    "

    "Created: / 29-08-2018 / 09:55:00 / Claus Gittinger"
! !

!OSXOperatingSystem class methodsFor:'time and date'!

getOSTimeWithNanos
    "This returns the OS time as a 2-element vector with milliseconds (as before)
     plus nanoseconds.
     The base of the returned value is not consistent across
     different OS's - some return the number of nanoseconds since jan, 1st 1970;
     others since 1900. The Time classes are prepared for this, and
     convert as appropriate (by using my fromOSTime: conversion methods).

     Don't use this method in application code since it is an internal (private)
     interface. For compatibility use instances of Time, Date or Timestamp to work with."

    |seconds millis nanos|
%{
// all of the experiments below only provide 1us resolution on my machine
// Not sure if that has to do with the OSX version (high sierra)
// therefore, it is disabled, and we fall back to the inherited method,
// which returns microsecond resolution nanos anyway.
#if 0
    unsigned long _secs, _millis, _nanos;

# if defined(CLOCK_REALTIME) && !defined(NO_CLOCK_GETTIME)
    struct timespec ts;
    static int has_clock_gettime = 1;
    unsigned long _secs, _millis, _nanos;

    if (has_clock_gettime) {
	if (clock_gettime(CLOCK_REALTIME, &ts) != -1) {
	    _secs = ts.tv_sec;
	    _millis = ts.tv_nsec / 1000000;
	    _nanos = ts.tv_nsec % 1000000;

	    seconds = __MKUINT(_secs);
	    millis = __MKUINT(_millis);
	    nanos = __MKUINT(_nanos);
	} else {
	    /*
	     * clock_gettime is not implemented in the kernel
	     * fall through to alternative implementation
	     */
	    has_clock_gettime = 0;
	}
    }
# else
#  if 0
    // althoug documented, my osx doesn't have it...
    uint32_t secpart;
    uint32_t nsecpart;

    clock_get_calendar_nanotime(&secpart, &nsecpart);

    _secs = secpart;
    _millis = nsecpart / 1000000;
    _nanos = nsecpart % 1000000;
#  else
    // OS X does not have clock_gettime, use host_get_clock_service
    clock_serv_t cclock;
    mach_timespec_t mts;
    host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
    clock_get_time(cclock, &mts);
    mach_port_deallocate(mach_task_self(), cclock);

    _secs = mts.tv_sec;
    _millis = mts.tv_nsec / 1000000;
    _nanos = mts.tv_nsec % 1000000;
#  endif

    seconds = __MKUINT(_secs);
    millis = __MKUINT(_millis);
    nanos = __MKUINT(_nanos);
# endif
#endif
%}.
    seconds notNil ifTrue:[
	^ { ((seconds * 1000) + millis) . nanos }
    ].

    ^ super getOSTimeWithNanos.
! !

!OSXOperatingSystem class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !