blob: 84a0def9e0973a6a82fd10561c11daed3fd1590d [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
Bram Moolenaard0573012017-10-28 21:11:06 +020011 * os_macosx.m -- Mac specific things for Mac OS X.
Bram Moolenaar071d4272004-06-13 20:20:40 +000012 */
13
Bram Moolenaarf536bf62018-03-06 17:55:01 +010014/* Suppress compiler warnings to non-C89 code. */
15#if defined(__clang__) && defined(__STRICT_ANSI__)
16# pragma clang diagnostic push
17# pragma clang diagnostic ignored "-Wc99-extensions"
18# pragma clang diagnostic push
19# pragma clang diagnostic ignored "-Wdeclaration-after-statement"
20#endif
21
Bram Moolenaar423f9722010-10-10 17:08:43 +020022/* Avoid a conflict for the definition of Boolean between Mac header files and
23 * X11 header files. */
24#define NO_X11_INCLUDES
25
Paul Ollis65745772022-06-05 16:55:54 +010026#include <stdbool.h>
27#include <mach/boolean.h>
28#include <sys/errno.h>
29#include <stdlib.h>
30
Evan Millere24a1412022-08-21 17:24:00 +010031#ifdef FEAT_RELTIME
Paul Ollis65745772022-06-05 16:55:54 +010032#include <dispatch/dispatch.h>
Evan Millere24a1412022-08-21 17:24:00 +010033#endif
Paul Ollis65745772022-06-05 16:55:54 +010034
Bram Moolenaar164fca32010-07-14 13:58:07 +020035#include "vim.h"
Bram Moolenaard0573012017-10-28 21:11:06 +020036#import <AppKit/AppKit.h>
Bram Moolenaar164fca32010-07-14 13:58:07 +020037
38
Bram Moolenaare00289d2010-08-14 21:56:42 +020039/*
40 * Clipboard support for the console.
Bram Moolenaare00289d2010-08-14 21:56:42 +020041 */
Bram Moolenaarbe7529e2020-08-13 21:05:39 +020042#if defined(FEAT_CLIPBOARD)
Bram Moolenaar164fca32010-07-14 13:58:07 +020043
Bram Moolenaar66bd1c92010-07-14 20:31:44 +020044/* Used to identify clipboard data copied from Vim. */
45
46NSString *VimPboardType = @"VimPboardType";
47
Bram Moolenaar164fca32010-07-14 13:58:07 +020048 void
Bram Moolenaar2fc39ae2019-06-14 23:27:29 +020049clip_mch_lose_selection(Clipboard_T *cbd UNUSED)
Bram Moolenaar164fca32010-07-14 13:58:07 +020050{
51}
52
53
54 int
Bram Moolenaar2fc39ae2019-06-14 23:27:29 +020055clip_mch_own_selection(Clipboard_T *cbd UNUSED)
Bram Moolenaar164fca32010-07-14 13:58:07 +020056{
57 /* This is called whenever there is a new selection and 'guioptions'
58 * contains the "a" flag (automatically copy selection). Return TRUE, else
59 * the "a" flag does nothing. Note that there is no concept of "ownership"
60 * of the clipboard in Mac OS X.
61 */
62 return TRUE;
63}
64
65
66 void
Bram Moolenaar2fc39ae2019-06-14 23:27:29 +020067clip_mch_request_selection(Clipboard_T *cbd)
Bram Moolenaar164fca32010-07-14 13:58:07 +020068{
69 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
70
71 NSPasteboard *pb = [NSPasteboard generalPasteboard];
Bram Moolenaar1d3dbcf2018-10-08 20:07:39 +020072#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
Bram Moolenaard595a192018-06-17 15:01:04 +020073 NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType,
74 NSPasteboardTypeString, nil];
75#else
Bram Moolenaar164fca32010-07-14 13:58:07 +020076 NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType,
77 NSStringPboardType, nil];
Bram Moolenaard595a192018-06-17 15:01:04 +020078#endif
Bram Moolenaar164fca32010-07-14 13:58:07 +020079 NSString *bestType = [pb availableTypeFromArray:supportedTypes];
80 if (!bestType) goto releasepool;
81
Bram Moolenaar54b08a52011-06-20 00:25:44 +020082 int motion_type = MAUTO;
Bram Moolenaar164fca32010-07-14 13:58:07 +020083 NSString *string = nil;
84
85 if ([bestType isEqual:VimPboardType])
86 {
87 /* This type should consist of an array with two objects:
88 * 1. motion type (NSNumber)
89 * 2. text (NSString)
Bram Moolenaard595a192018-06-17 15:01:04 +020090 * If this is not the case we fall back on using NSPasteboardTypeString.
Bram Moolenaar164fca32010-07-14 13:58:07 +020091 */
92 id plist = [pb propertyListForType:VimPboardType];
93 if ([plist isKindOfClass:[NSArray class]] && [plist count] == 2)
94 {
95 id obj = [plist objectAtIndex:1];
96 if ([obj isKindOfClass:[NSString class]])
97 {
98 motion_type = [[plist objectAtIndex:0] intValue];
99 string = obj;
100 }
101 }
102 }
103
104 if (!string)
105 {
Bram Moolenaard595a192018-06-17 15:01:04 +0200106 /* Use NSPasteboardTypeString. The motion type is detected automatically.
Bram Moolenaar164fca32010-07-14 13:58:07 +0200107 */
Bram Moolenaar1d3dbcf2018-10-08 20:07:39 +0200108#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
Bram Moolenaard595a192018-06-17 15:01:04 +0200109 NSMutableString *mstring =
110 [[pb stringForType:NSPasteboardTypeString] mutableCopy];
111#else
Bram Moolenaar164fca32010-07-14 13:58:07 +0200112 NSMutableString *mstring =
113 [[pb stringForType:NSStringPboardType] mutableCopy];
Bram Moolenaard595a192018-06-17 15:01:04 +0200114#endif
Bram Moolenaar164fca32010-07-14 13:58:07 +0200115 if (!mstring) goto releasepool;
116
117 /* Replace unrecognized end-of-line sequences with \x0a (line feed). */
118 NSRange range = { 0, [mstring length] };
119 unsigned n = [mstring replaceOccurrencesOfString:@"\x0d\x0a"
120 withString:@"\x0a" options:0
121 range:range];
122 if (0 == n)
123 {
124 n = [mstring replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
125 options:0 range:range];
126 }
Bram Moolenaard43848c2010-07-14 14:28:26 +0200127
Bram Moolenaar164fca32010-07-14 13:58:07 +0200128 string = mstring;
129 }
130
Bram Moolenaar54b08a52011-06-20 00:25:44 +0200131 /* Default to MAUTO, uses MCHAR or MLINE depending on trailing NL. */
Bram Moolenaar164fca32010-07-14 13:58:07 +0200132 if (!(MCHAR == motion_type || MLINE == motion_type || MBLOCK == motion_type
133 || MAUTO == motion_type))
Bram Moolenaar54b08a52011-06-20 00:25:44 +0200134 motion_type = MAUTO;
Bram Moolenaar164fca32010-07-14 13:58:07 +0200135
136 char_u *str = (char_u*)[string UTF8String];
137 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
138
Bram Moolenaar164fca32010-07-14 13:58:07 +0200139 if (input_conv.vc_type != CONV_NONE)
140 str = string_convert(&input_conv, str, &len);
Bram Moolenaar164fca32010-07-14 13:58:07 +0200141
142 if (str)
143 clip_yank_selection(motion_type, str, len, cbd);
144
Bram Moolenaar164fca32010-07-14 13:58:07 +0200145 if (input_conv.vc_type != CONV_NONE)
146 vim_free(str);
Bram Moolenaar164fca32010-07-14 13:58:07 +0200147
148releasepool:
149 [pool release];
150}
151
152
153/*
154 * Send the current selection to the clipboard.
155 */
156 void
Bram Moolenaar2fc39ae2019-06-14 23:27:29 +0200157clip_mch_set_selection(Clipboard_T *cbd)
Bram Moolenaar164fca32010-07-14 13:58:07 +0200158{
159 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
160
161 /* If the '*' register isn't already filled in, fill it in now. */
162 cbd->owned = TRUE;
163 clip_get_selection(cbd);
164 cbd->owned = FALSE;
Bram Moolenaard43848c2010-07-14 14:28:26 +0200165
Bram Moolenaar164fca32010-07-14 13:58:07 +0200166 /* Get the text to put on the pasteboard. */
167 long_u llen = 0; char_u *str = 0;
168 int motion_type = clip_convert_selection(&str, &llen, cbd);
169 if (motion_type < 0)
170 goto releasepool;
171
172 /* TODO: Avoid overflow. */
173 int len = (int)llen;
Bram Moolenaar164fca32010-07-14 13:58:07 +0200174 if (output_conv.vc_type != CONV_NONE)
175 {
176 char_u *conv_str = string_convert(&output_conv, str, &len);
177 if (conv_str)
178 {
179 vim_free(str);
180 str = conv_str;
181 }
182 }
Bram Moolenaar164fca32010-07-14 13:58:07 +0200183
184 if (len > 0)
185 {
186 NSString *string = [[NSString alloc]
187 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
188
189 /* See clip_mch_request_selection() for info on pasteboard types. */
190 NSPasteboard *pb = [NSPasteboard generalPasteboard];
Bram Moolenaar1d3dbcf2018-10-08 20:07:39 +0200191#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
Bram Moolenaard595a192018-06-17 15:01:04 +0200192 NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType,
193 NSPasteboardTypeString, nil];
194#else
Bram Moolenaar164fca32010-07-14 13:58:07 +0200195 NSArray *supportedTypes = [NSArray arrayWithObjects:VimPboardType,
196 NSStringPboardType, nil];
Bram Moolenaard595a192018-06-17 15:01:04 +0200197#endif
Bram Moolenaar164fca32010-07-14 13:58:07 +0200198 [pb declareTypes:supportedTypes owner:nil];
199
200 NSNumber *motion = [NSNumber numberWithInt:motion_type];
201 NSArray *plist = [NSArray arrayWithObjects:motion, string, nil];
202 [pb setPropertyList:plist forType:VimPboardType];
203
Bram Moolenaar1d3dbcf2018-10-08 20:07:39 +0200204#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
Bram Moolenaard595a192018-06-17 15:01:04 +0200205 [pb setString:string forType:NSPasteboardTypeString];
206#else
Bram Moolenaar164fca32010-07-14 13:58:07 +0200207 [pb setString:string forType:NSStringPboardType];
Bram Moolenaard595a192018-06-17 15:01:04 +0200208#endif
Bram Moolenaard43848c2010-07-14 14:28:26 +0200209
Bram Moolenaar164fca32010-07-14 13:58:07 +0200210 [string release];
211 }
212
213 vim_free(str);
214releasepool:
215 [pool release];
216}
217
218#endif /* FEAT_CLIPBOARD */
Bram Moolenaarf536bf62018-03-06 17:55:01 +0100219
Paul Ollis65745772022-06-05 16:55:54 +0100220#ifdef FEAT_RELTIME
221/*
222 * The following timer code is based on a Gist by Jorgen Lundman:
223 *
224 * https://gist.github.com/lundman
225 */
226
227typedef struct macos_timer macos_timer_T;
228
229 static void
230_timer_cancel(void *arg UNUSED)
231{
232 // This is not currently used, but it might be useful in the future and
233 // it is non-trivial enough to provide as usable implementation.
234# if 0
235 macos_timer_T *timerid = (macos_timer_T *)arg;
236
237 dispatch_release(timerid->tim_timer);
238 dispatch_release(timerid->tim_queue);
239 timerid->tim_timer = NULL;
240 timerid->tim_queue = NULL;
241 free(timerid);
242# endif
243}
244
245 static void
246_timer_handler(void *arg)
247{
248 macos_timer_T *timerid = (macos_timer_T *)arg;
249 union sigval sv;
250
251 sv.sival_ptr = timerid->tim_arg;
252
253 if (timerid->tim_func != NULL)
254 timerid->tim_func(sv);
255}
256
257 static uint64_t
258itime_to_ns(const struct timespec *it)
259{
260 time_t sec = it->tv_sec;
261 long nsec = it->tv_nsec;
262 uint64_t ns = NSEC_PER_SEC * sec + nsec;
263
264 return ns == 0 ? DISPATCH_TIME_FOREVER : ns;
265}
266
267/*
268 * A partial emulation of the POSIX timer_create function.
269 *
270 * The limitations and differences include:
271 *
272 * - Only CLOCK_REALTIME and CLOCK_MONOTONIC are supported as clockid
273 * values.
274 * - Even if CLOCK_REALTIME is specified, internally the mach_absolute_time
275 * source is used internally.
276 * - The only notification method supported is SIGEV_THREAD.
277 */
278 inline int
279timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid)
280{
281 macos_timer_T *timer = NULL;
282
283 // We only support real time and monotonic clocks; and SIGEV_THREAD
284 // notification. In practice, there is no difference between the two
285 // types of clocks on MacOS - we always use the mach_machine_time
286 // source.
287 if ( (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC)
288 || sevp->sigev_notify != SIGEV_THREAD)
289 {
290 semsg("clockid: %d %d", clockid, CLOCK_REALTIME);
291 semsg("notify: %d %d", sevp->sigev_notify, SIGEV_THREAD);
292 errno = ENOTSUP;
293 return -1;
294 }
295
296 timer = (macos_timer_T *)malloc(sizeof(macos_timer_T));
297 if (timer == NULL)
298 {
299 errno = ENOMEM;
300 return -1;
301 }
302 *timerid = timer;
303
304 timer->tim_queue = dispatch_queue_create(
305 "org.vim.timerqueue", NULL);
306 if (timer->tim_queue == NULL)
307 {
308 errno = ENOMEM;
309 return -1;
310 }
311
312 timer->tim_timer = dispatch_source_create(
313 DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timer->tim_queue);
314 if (timer->tim_timer == NULL)
315 {
316 errno = ENOMEM;
317 return -1;
318 }
319
320 timer->tim_func = sevp->sigev_notify_function;
321 timer->tim_arg = sevp->sigev_value.sival_ptr;
322
323 dispatch_set_context(timer->tim_timer, timer);
324 dispatch_source_set_event_handler_f(timer->tim_timer, _timer_handler);
325 dispatch_source_set_cancel_handler_f(timer->tim_timer, _timer_cancel);
326
327 dispatch_resume(timer->tim_timer);
328
329 return 0;
330}
331
332/*
333 * A partial emulation of the POSIX timer_settime function.
334 *
335 * The limitations and differences include:
336 *
337 * - The flags argument is ignored. The supplied new_value is therfore
338 * always treated as a relative time.
339 * - The old_value argument is ignored.
340 */
341 int
342timer_settime(
343 timer_t timerid,
344 int unused_flags UNUSED,
345 const struct itimerspec *new_value,
346 struct itimerspec *old_value UNUSED)
347{
348 uint64_t first_shot = itime_to_ns(&new_value->it_value);
349
350 if (timerid == NULL)
351 return 0;
352
353 if (first_shot == DISPATCH_TIME_FOREVER)
354 {
355 dispatch_source_set_timer(
356 timerid->tim_timer, first_shot, first_shot, 0);
357 }
358 else
359 {
360 uint64_t interval = itime_to_ns(&new_value->it_interval);
361
362 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, first_shot);
363 dispatch_source_set_timer(timerid->tim_timer, start, interval, 0);
364 }
365
366 return 0;
367}
368
369/*
370 * An emulation of the POSIX timer_delete function.
371 *
372 * Disabled because it is not currently used, but an implemented provided
373 * for completeness and possible future use.
374 */
Paul Ollis65745772022-06-05 16:55:54 +0100375 int
376timer_delete(timer_t timerid)
377{
378 /* Calls _timer_cancel() */
379 if (timerid != NULL)
380 dispatch_source_cancel(timerid->tim_timer);
381
382 return 0;
383}
Paul Ollis65745772022-06-05 16:55:54 +0100384
385#endif /* FEAT_RELTIME */
386
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100387#ifdef FEAT_SOUND
388
389static NSMutableDictionary<NSNumber*, NSSound*> *sounds_list = nil;
390
391/// A delegate for handling when a sound has stopped playing, in
392/// order to clean up the sound and to send a callback.
393@interface SoundDelegate : NSObject<NSSoundDelegate>;
394
395- (id) init:(long) sound_id callback:(soundcb_T*) callback;
396- (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag;
397
398@property (readonly) long sound_id;
399@property (readonly) soundcb_T *callback;
400
401@end
402
403@implementation SoundDelegate
404- (id) init:(long) sound_id callback:(soundcb_T*) callback
405{
406 if ([super init])
407 {
408 _sound_id = sound_id;
409 _callback = callback;
410 }
411 return self;
412}
413
414- (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag
415{
416 if (sounds_list != nil)
417 {
418 if (_callback)
419 {
420 call_sound_callback(_callback, _sound_id, flag ? 0 : 1);
421 delete_sound_callback(_callback);
422 _callback = NULL;
423 }
424 [sounds_list removeObjectForKey:[NSNumber numberWithLong:_sound_id]];
425 }
426 // Release itself. Do that here instead of earlier because NSSound only
427 // holds weak reference to this object.
428 [self release];
429}
430@end
431
432 void
433process_cfrunloop()
434{
435 if (sounds_list != nil && [sounds_list count] > 0)
436 {
437 // Continually drain the run loop of events. Currently, this
438 // is only used for processing sound callbacks, because
439 // NSSound relies of this runloop to call back to the
440 // delegate.
441 @autoreleasepool
442 {
443 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
444 == kCFRunLoopRunHandledSource)
445 ; // do nothing
446 }
447 }
448}
449
450 bool
451sound_mch_play(const char_u* sound_name, long sound_id, soundcb_T *callback, bool playfile)
452{
453 @autoreleasepool
454 {
455 NSString *sound_name_ns = [[[NSString alloc] initWithUTF8String:(const char*)sound_name] autorelease];
456 NSSound* sound = playfile ?
457 [[[NSSound alloc] initWithContentsOfFile:sound_name_ns byReference:YES] autorelease] :
458 [NSSound soundNamed:sound_name_ns];
459 if (!sound)
460 {
461 return false;
462 }
463
464 if (sounds_list == nil)
465 {
466 sounds_list = [[NSMutableDictionary<NSNumber*, NSSound*> alloc] init];
467 }
468 sounds_list[[NSNumber numberWithLong:sound_id]] = sound;
469
470 // Make a delegate to handle when the sound stops. No need to call
471 // autorelease because NSSound only holds a weak reference to it.
472 SoundDelegate *delegate = [[SoundDelegate alloc] init:sound_id callback:callback];
473
474 [sound setDelegate:delegate];
475 [sound play];
476 }
477 return true;
478}
479
480 void
481sound_mch_stop(long sound_id)
482{
483 @autoreleasepool
484 {
485 NSSound *sound = sounds_list[[NSNumber numberWithLong:sound_id]];
486 if (sound != nil)
487 {
488 // Stop the sound. No need to release it because the delegate will do
489 // it for us.
490 [sound stop];
491 }
492 }
493}
494
495 void
496sound_mch_clear()
497{
498 if (sounds_list != nil)
499 {
500 @autoreleasepool
501 {
502 for (NSSound *sound in [sounds_list allValues])
503 {
504 [sound stop];
505 }
506 [sounds_list release];
507 sounds_list = nil;
508 }
509 }
510}
511
512 void
513sound_mch_free()
514{
515 sound_mch_clear();
516}
517
518#endif // FEAT_SOUND
519
Bram Moolenaarf536bf62018-03-06 17:55:01 +0100520/* Lift the compiler warning suppression. */
521#if defined(__clang__) && defined(__STRICT_ANSI__)
522# pragma clang diagnostic pop
523# pragma clang diagnostic pop
524#endif