summaryrefslogtreecommitdiffstats
path: root/src/os_macosx.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/os_macosx.m')
-rw-r--r--src/os_macosx.m524
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