diff options
Diffstat (limited to 'src/os_macosx.m')
-rw-r--r-- | src/os_macosx.m | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/src/os_macosx.m b/src/os_macosx.m new file mode 100644 index 0000000..153c2f1 --- /dev/null +++ b/src/os_macosx.m @@ -0,0 +1,524 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * os_macosx.m -- Mac specific things for Mac OS X. + */ + +/* Suppress compiler warnings to non-C89 code. */ +#if defined(__clang__) && defined(__STRICT_ANSI__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeclaration-after-statement" +#endif + +/* Avoid a conflict for the definition of Boolean between Mac header files and + * X11 header files. */ +#define NO_X11_INCLUDES + +#include <stdbool.h> +#include <mach/boolean.h> +#include <sys/errno.h> +#include <stdlib.h> + +#ifdef FEAT_RELTIME +#include <dispatch/dispatch.h> +#endif + +#include "vim.h" +#import <AppKit/AppKit.h> + + +/* + * Clipboard support for the console. + */ +#if defined(FEAT_CLIPBOARD) + +/* Used to identify clipboard data copied from Vim. */ + +NSString *VimPboardType = @"VimPboardType"; + + void +clip_mch_lose_selection(Clipboard_T *cbd UNUSED) +{ +} + + + int +clip_mch_own_selection(Clipboard_T *cbd UNUSED) +{ + /* This is called whenever there is a new selection and 'guioptions' + * contains the "a" flag (automatically copy selection). Return TRUE, else + * the "a" flag does nothing. Note that there is no concept of "ownership" + * of the clipboard in Mac OS X. + */ + return TRUE; +} + + + void +clip_mch_request_selection(Clipboard_T *cbd) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSPasteboard *pb = [NSPasteboard generalPasteboard]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType, + NSPasteboardTypeString, nil]; +#else + NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType, + NSStringPboardType, nil]; +#endif + NSString *bestType = [pb availableTypeFromArray:supportedTypes]; + if (!bestType) goto releasepool; + + int motion_type = MAUTO; + NSString *string = nil; + + if ([bestType isEqual:VimPboardType]) + { + /* This type should consist of an array with two objects: + * 1. motion type (NSNumber) + * 2. text (NSString) + * If this is not the case we fall back on using NSPasteboardTypeString. + */ + id plist = [pb propertyListForType:VimPboardType]; + if ([plist isKindOfClass:[NSArray class]] && [plist count] == 2) + { + id obj = [plist objectAtIndex:1]; + if ([obj isKindOfClass:[NSString class]]) + { + motion_type = [[plist objectAtIndex:0] intValue]; + string = obj; + } + } + } + + if (!string) + { + /* Use NSPasteboardTypeString. The motion type is detected automatically. + */ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + NSMutableString *mstring = + [[pb stringForType:NSPasteboardTypeString] mutableCopy]; +#else + NSMutableString *mstring = + [[pb stringForType:NSStringPboardType] mutableCopy]; +#endif + if (!mstring) goto releasepool; + + /* Replace unrecognized end-of-line sequences with \x0a (line feed). */ + NSRange range = { 0, [mstring length] }; + unsigned n = [mstring replaceOccurrencesOfString:@"\x0d\x0a" + withString:@"\x0a" options:0 + range:range]; + if (0 == n) + { + n = [mstring replaceOccurrencesOfString:@"\x0d" withString:@"\x0a" + options:0 range:range]; + } + + string = mstring; + } + + /* Default to MAUTO, uses MCHAR or MLINE depending on trailing NL. */ + if (!(MCHAR == motion_type || MLINE == motion_type || MBLOCK == motion_type + || MAUTO == motion_type)) + motion_type = MAUTO; + + char_u *str = (char_u*)[string UTF8String]; + int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if (input_conv.vc_type != CONV_NONE) + str = string_convert(&input_conv, str, &len); + + if (str) + clip_yank_selection(motion_type, str, len, cbd); + + if (input_conv.vc_type != CONV_NONE) + vim_free(str); + +releasepool: + [pool release]; +} + + +/* + * Send the current selection to the clipboard. + */ + void +clip_mch_set_selection(Clipboard_T *cbd) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + /* If the '*' register isn't already filled in, fill it in now. */ + cbd->owned = TRUE; + clip_get_selection(cbd); + cbd->owned = FALSE; + + /* Get the text to put on the pasteboard. */ + long_u llen = 0; char_u *str = 0; + int motion_type = clip_convert_selection(&str, &llen, cbd); + if (motion_type < 0) + goto releasepool; + + /* TODO: Avoid overflow. */ + int len = (int)llen; + if (output_conv.vc_type != CONV_NONE) + { + char_u *conv_str = string_convert(&output_conv, str, &len); + if (conv_str) + { + vim_free(str); + str = conv_str; + } + } + + if (len > 0) + { + NSString *string = [[NSString alloc] + initWithBytes:str length:len encoding:NSUTF8StringEncoding]; + + /* See clip_mch_request_selection() for info on pasteboard types. */ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType, + NSPasteboardTypeString, nil]; +#else + NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType, + NSStringPboardType, nil]; +#endif + [pb declareTypes:supportedTypes owner:nil]; + + NSNumber *motion = [NSNumber numberWithInt:motion_type]; + NSArray *plist = [NSArray arrayWithObjects:motion, string, nil]; + [pb setPropertyList:plist forType:VimPboardType]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + [pb setString:string forType:NSPasteboardTypeString]; +#else + [pb setString:string forType:NSStringPboardType]; +#endif + + [string release]; + } + + vim_free(str); +releasepool: + [pool release]; +} + +#endif /* FEAT_CLIPBOARD */ + +#ifdef FEAT_RELTIME +/* + * The following timer code is based on a Gist by Jorgen Lundman: + * + * https://gist.github.com/lundman + */ + +typedef struct macos_timer macos_timer_T; + + static void +_timer_cancel(void *arg UNUSED) +{ + // This is not currently used, but it might be useful in the future and + // it is non-trivial enough to provide as usable implementation. +# if 0 + macos_timer_T *timerid = (macos_timer_T *)arg; + + dispatch_release(timerid->tim_timer); + dispatch_release(timerid->tim_queue); + timerid->tim_timer = NULL; + timerid->tim_queue = NULL; + free(timerid); +# endif +} + + static void +_timer_handler(void *arg) +{ + macos_timer_T *timerid = (macos_timer_T *)arg; + union sigval sv; + + sv.sival_ptr = timerid->tim_arg; + + if (timerid->tim_func != NULL) + timerid->tim_func(sv); +} + + static uint64_t +itime_to_ns(const struct timespec *it) +{ + time_t sec = it->tv_sec; + long nsec = it->tv_nsec; + uint64_t ns = NSEC_PER_SEC * sec + nsec; + + return ns == 0 ? DISPATCH_TIME_FOREVER : ns; +} + +/* + * A partial emulation of the POSIX timer_create function. + * + * The limitations and differences include: + * + * - Only CLOCK_REALTIME and CLOCK_MONOTONIC are supported as clockid + * values. + * - Even if CLOCK_REALTIME is specified, internally the mach_absolute_time + * source is used internally. + * - The only notification method supported is SIGEV_THREAD. + */ + inline int +timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid) +{ + macos_timer_T *timer = NULL; + + // We only support real time and monotonic clocks; and SIGEV_THREAD + // notification. In practice, there is no difference between the two + // types of clocks on MacOS - we always use the mach_machine_time + // source. + if ( (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC) + || sevp->sigev_notify != SIGEV_THREAD) + { + semsg("clockid: %d %d", clockid, CLOCK_REALTIME); + semsg("notify: %d %d", sevp->sigev_notify, SIGEV_THREAD); + errno = ENOTSUP; + return -1; + } + + timer = (macos_timer_T *)malloc(sizeof(macos_timer_T)); + if (timer == NULL) + { + errno = ENOMEM; + return -1; + } + *timerid = timer; + + timer->tim_queue = dispatch_queue_create( + "org.vim.timerqueue", NULL); + if (timer->tim_queue == NULL) + { + errno = ENOMEM; + return -1; + } + + timer->tim_timer = dispatch_source_create( + DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timer->tim_queue); + if (timer->tim_timer == NULL) + { + errno = ENOMEM; + return -1; + } + + timer->tim_func = sevp->sigev_notify_function; + timer->tim_arg = sevp->sigev_value.sival_ptr; + + dispatch_set_context(timer->tim_timer, timer); + dispatch_source_set_event_handler_f(timer->tim_timer, _timer_handler); + dispatch_source_set_cancel_handler_f(timer->tim_timer, _timer_cancel); + + dispatch_resume(timer->tim_timer); + + return 0; +} + +/* + * A partial emulation of the POSIX timer_settime function. + * + * The limitations and differences include: + * + * - The flags argument is ignored. The supplied new_value is therefore + * always treated as a relative time. + * - The old_value argument is ignored. + */ + int +timer_settime( + timer_t timerid, + int unused_flags UNUSED, + const struct itimerspec *new_value, + struct itimerspec *old_value UNUSED) +{ + uint64_t first_shot = itime_to_ns(&new_value->it_value); + + if (timerid == NULL) + return 0; + + if (first_shot == DISPATCH_TIME_FOREVER) + { + dispatch_source_set_timer( + timerid->tim_timer, first_shot, first_shot, 0); + } + else + { + uint64_t interval = itime_to_ns(&new_value->it_interval); + + dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, first_shot); + dispatch_source_set_timer(timerid->tim_timer, start, interval, 0); + } + + return 0; +} + +/* + * An emulation of the POSIX timer_delete function. + * + * Disabled because it is not currently used, but an implemented provided + * for completeness and possible future use. + */ + int +timer_delete(timer_t timerid) +{ + /* Calls _timer_cancel() */ + if (timerid != NULL) + dispatch_source_cancel(timerid->tim_timer); + + return 0; +} + +#endif /* FEAT_RELTIME */ + +#ifdef FEAT_SOUND + +static NSMutableDictionary<NSNumber*, NSSound*> *sounds_list = nil; + +/// A delegate for handling when a sound has stopped playing, in +/// order to clean up the sound and to send a callback. +@interface SoundDelegate : NSObject<NSSoundDelegate>; + +- (id) init:(long) sound_id callback:(soundcb_T*) callback; +- (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag; + +@property (readonly) long sound_id; +@property (readonly) soundcb_T *callback; + +@end + +@implementation SoundDelegate +- (id) init:(long) sound_id callback:(soundcb_T*) callback +{ + if ([super init]) + { + _sound_id = sound_id; + _callback = callback; + } + return self; +} + +- (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag +{ + if (sounds_list != nil) + { + if (_callback) + { + call_sound_callback(_callback, _sound_id, flag ? 0 : 1); + delete_sound_callback(_callback); + _callback = NULL; + } + [sounds_list removeObjectForKey:[NSNumber numberWithLong:_sound_id]]; + } + // Release itself. Do that here instead of earlier because NSSound only + // holds weak reference to this object. + [self release]; +} +@end + + void +process_cfrunloop(void) +{ + if (sounds_list != nil && [sounds_list count] > 0) + { + // Continually drain the run loop of events. Currently, this + // is only used for processing sound callbacks, because + // NSSound relies of this runloop to call back to the + // delegate. + @autoreleasepool + { + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) + == kCFRunLoopRunHandledSource) + ; // do nothing + } + } +} + + bool +sound_mch_play(const char_u* sound_name, long sound_id, soundcb_T *callback, bool playfile) +{ + @autoreleasepool + { + NSString *sound_name_ns = [[[NSString alloc] initWithUTF8String:(const char*)sound_name] autorelease]; + NSSound* sound = playfile ? + [[[NSSound alloc] initWithContentsOfFile:sound_name_ns byReference:YES] autorelease] : + [NSSound soundNamed:sound_name_ns]; + if (!sound) + { + return false; + } + + if (sounds_list == nil) + { + sounds_list = [[NSMutableDictionary<NSNumber*, NSSound*> alloc] init]; + } + sounds_list[[NSNumber numberWithLong:sound_id]] = sound; + + // Make a delegate to handle when the sound stops. No need to call + // autorelease because NSSound only holds a weak reference to it. + SoundDelegate *delegate = [[SoundDelegate alloc] init:sound_id callback:callback]; + + [sound setDelegate:delegate]; + [sound play]; + } + return true; +} + + void +sound_mch_stop(long sound_id) +{ + @autoreleasepool + { + NSSound *sound = sounds_list[[NSNumber numberWithLong:sound_id]]; + if (sound != nil) + { + // Stop the sound. No need to release it because the delegate will do + // it for us. + [sound stop]; + } + } +} + + void +sound_mch_clear(void) +{ + if (sounds_list != nil) + { + @autoreleasepool + { + for (NSSound *sound in [sounds_list allValues]) + { + [sound stop]; + } + [sounds_list release]; + sounds_list = nil; + } + } +} + + void +sound_mch_free(void) +{ + sound_mch_clear(); +} + +#endif // FEAT_SOUND + +/* Lift the compiler warning suppression. */ +#if defined(__clang__) && defined(__STRICT_ANSI__) +# pragma clang diagnostic pop +# pragma clang diagnostic pop +#endif |