1 /* NeXT/Open/GNUstep / macOS Cocoa selection processing for emacs.
2 Copyright (C) 1993-1994, 2005-2006, 2008-2018 Free Software
5 This file is part of GNU Emacs.
7 GNU Emacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or (at
10 your option) any later version.
12 GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
21 Originally by Carl Edman
22 Updated by Christian Limpach (chris@nice.ch)
23 OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com)
24 macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net)
25 GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
28 /* This should be the first include, as it may set up #defines affecting
29 interpretation of even the system includes. */
34 #include "termhooks.h"
37 static Lisp_Object Vselection_alist;
39 /* NSGeneralPboard is pretty much analogous to X11 CLIPBOARD */
40 static NSString *NXPrimaryPboard;
41 static NSString *NXSecondaryPboard;
44 static NSMutableDictionary *pasteboard_changecount;
46 /* ==========================================================================
48 Internal utility functions
50 ========================================================================== */
54 symbol_to_nsstring (Lisp_Object sym)
57 if (EQ (sym, QCLIPBOARD)) return NSGeneralPboard;
58 if (EQ (sym, QPRIMARY)) return NXPrimaryPboard;
59 if (EQ (sym, QSECONDARY)) return NXSecondaryPboard;
60 if (EQ (sym, QTEXT)) return NSStringPboardType;
61 return [NSString stringWithUTF8String: SSDATA (SYMBOL_NAME (sym))];
65 ns_symbol_to_pb (Lisp_Object symbol)
67 return [NSPasteboard pasteboardWithName: symbol_to_nsstring (symbol)];
71 ns_string_to_symbol (NSString *t)
73 if ([t isEqualToString: NSGeneralPboard])
75 if ([t isEqualToString: NXPrimaryPboard])
77 if ([t isEqualToString: NXSecondaryPboard])
79 if ([t isEqualToString: NSStringPboardType])
81 if ([t isEqualToString: NSFilenamesPboardType])
83 if ([t isEqualToString: NSTabularTextPboardType])
85 return intern ([t UTF8String]);
90 clean_local_selection_data (Lisp_Object obj)
93 && INTEGERP (XCAR (obj))
95 && INTEGERP (XCAR (XCDR (obj)))
96 && NILP (XCDR (XCDR (obj))))
97 obj = Fcons (XCAR (obj), XCDR (obj));
100 && INTEGERP (XCAR (obj))
101 && INTEGERP (XCDR (obj)))
103 if (XINT (XCAR (obj)) == 0)
105 if (XINT (XCAR (obj)) == -1)
106 return make_number (- XINT (XCDR (obj)));
112 ptrdiff_t size = ASIZE (obj);
116 return clean_local_selection_data (AREF (obj, 0));
117 copy = make_uninit_vector (size);
118 for (i = 0; i < size; i++)
119 ASET (copy, i, clean_local_selection_data (AREF (obj, i)));
128 ns_declare_pasteboard (id pb)
130 [pb declareTypes: ns_send_types owner: NSApp];
135 ns_undeclare_pasteboard (id pb)
137 [pb declareTypes: [NSArray array] owner: nil];
141 ns_store_pb_change_count (id pb)
143 [pasteboard_changecount
144 setObject: [NSNumber numberWithLong: [pb changeCount]]
149 ns_get_pb_change_count (Lisp_Object selection)
151 id pb = ns_symbol_to_pb (selection);
152 return pb != nil ? [pb changeCount] : -1;
156 ns_get_our_change_count_for (Lisp_Object selection)
158 NSNumber *num = [pasteboard_changecount
159 objectForKey: symbol_to_nsstring (selection)];
160 return num != nil ? (NSInteger)[num longValue] : -1;
165 ns_string_to_pasteboard_internal (id pb, Lisp_Object str, NSString *gtype)
169 [pb declareTypes: [NSArray array] owner: nil];
174 NSString *type, *nsStr;
179 utfStr = SSDATA (str);
180 nsStr = [[NSString alloc] initWithBytesNoCopy: utfStr
182 encoding: NSUTF8StringEncoding
184 // FIXME: Why those 2 different code paths?
187 // Used for ns_string_to_pasteboard
188 [pb declareTypes: ns_send_types owner: nil];
189 tenum = [ns_send_types objectEnumerator];
190 while ( (type = [tenum nextObject]) )
191 [pb setString: nsStr forType: type];
195 // Used for ns-own-selection-internal.
196 eassert (gtype == NSStringPboardType);
197 [pb setString: nsStr forType: gtype];
200 ns_store_pb_change_count (pb);
206 ns_get_local_selection (Lisp_Object selection_name,
207 Lisp_Object target_type)
209 Lisp_Object local_value;
210 local_value = assq_no_quit (selection_name, Vselection_alist);
216 ns_get_foreign_selection (Lisp_Object symbol, Lisp_Object target)
219 pb = ns_symbol_to_pb (symbol);
220 return pb != nil ? ns_string_from_pasteboard (pb) : Qnil;
226 /* ==========================================================================
228 Functions used externally
230 ========================================================================== */
234 ns_string_from_pasteboard (id pb)
236 NSString *type, *str;
240 type = [pb availableTypeFromArray: ns_return_types];
247 if (! (str = [pb stringForType: type]))
249 NSData *data = [pb dataForType: type];
251 str = [[NSString alloc] initWithData: data
252 encoding: NSUTF8StringEncoding];
266 /* EOL conversion: PENDING- is this too simple? */
267 NSMutableString *mstr = [[str mutableCopy] autorelease];
268 [mstr replaceOccurrencesOfString: @"\r\n" withString: @"\n"
269 options: NSLiteralSearch range: NSMakeRange (0, [mstr length])];
270 [mstr replaceOccurrencesOfString: @"\r" withString: @"\n"
271 options: NSLiteralSearch range: NSMakeRange (0, [mstr length])];
273 utfStr = [mstr UTF8String];
274 length = [mstr lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
276 #if ! defined (NS_IMPL_COCOA)
279 utfStr = [mstr cString];
280 length = strlen (utfStr);
286 message1 ("ns_string_from_pasteboard: UTF8String failed\n");
287 #if defined (NS_IMPL_COCOA)
288 utfStr = "Conversion failed";
290 utfStr = [str lossyCString];
292 length = strlen (utfStr);
296 return make_string (utfStr, length);
301 ns_string_to_pasteboard (id pb, Lisp_Object str)
303 ns_string_to_pasteboard_internal (pb, str, nil);
308 /* ==========================================================================
312 ========================================================================== */
315 DEFUN ("ns-own-selection-internal", Fns_own_selection_internal,
316 Sns_own_selection_internal, 2, 2, 0,
317 doc: /* Assert an X selection of type SELECTION and value VALUE.
318 SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
319 \(Those are literal upper-case symbol names, since that's what X expects.)
320 VALUE is typically a string, or a cons of two markers, but may be
321 anything that the functions on `selection-converter-alist' know about. */)
322 (Lisp_Object selection, Lisp_Object value)
326 Lisp_Object successful_p = Qnil, rest;
327 Lisp_Object target_symbol;
329 check_window_system (NULL);
330 CHECK_SYMBOL (selection);
332 error ("Selection value may not be nil");
333 pb = ns_symbol_to_pb (selection);
334 if (pb == nil) return Qnil;
336 ns_declare_pasteboard (pb);
338 Lisp_Object old_value = assq_no_quit (selection, Vselection_alist);
339 Lisp_Object new_value = list2 (selection, value);
341 if (NILP (old_value))
342 Vselection_alist = Fcons (new_value, Vselection_alist);
344 Fsetcdr (old_value, Fcdr (new_value));
347 /* We only support copy of text. */
348 type = NSStringPboardType;
349 target_symbol = ns_string_to_symbol (type);
352 ns_string_to_pasteboard_internal (pb, value, type);
356 if (!EQ (Vns_sent_selection_hooks, Qunbound))
358 /* FIXME: Use run-hook-with-args! */
359 for (rest = Vns_sent_selection_hooks; CONSP (rest); rest = Fcdr (rest))
360 call3 (Fcar (rest), selection, target_symbol, successful_p);
367 DEFUN ("ns-disown-selection-internal", Fns_disown_selection_internal,
368 Sns_disown_selection_internal, 1, 1, 0,
369 doc: /* If we own the selection SELECTION, disown it.
370 Disowning it means there is no such selection. */)
371 (Lisp_Object selection)
374 check_window_system (NULL);
375 CHECK_SYMBOL (selection);
377 if (ns_get_pb_change_count (selection)
378 != ns_get_our_change_count_for (selection))
381 pb = ns_symbol_to_pb (selection);
382 if (pb != nil) ns_undeclare_pasteboard (pb);
387 DEFUN ("ns-selection-exists-p", Fns_selection_exists_p, Sns_selection_exists_p,
388 0, 1, 0, doc: /* Whether there is an owner for the given X selection.
389 SELECTION should be the name of the selection in question, typically
390 one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'. (X expects
391 these literal upper-case names.) The symbol nil is the same as
392 `PRIMARY', and t is the same as `SECONDARY'. */)
393 (Lisp_Object selection)
398 if (!window_system_available (NULL))
401 CHECK_SYMBOL (selection);
402 if (EQ (selection, Qnil)) selection = QPRIMARY;
403 if (EQ (selection, Qt)) selection = QSECONDARY;
404 pb = ns_symbol_to_pb (selection);
405 if (pb == nil) return Qnil;
408 return ([types count] == 0) ? Qnil : Qt;
412 DEFUN ("ns-selection-owner-p", Fns_selection_owner_p, Sns_selection_owner_p,
414 doc: /* Whether the current Emacs process owns the given X Selection.
415 The arg should be the name of the selection in question, typically one of
416 the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.
417 \(Those are literal upper-case symbol names, since that's what X expects.)
418 For convenience, the symbol nil is the same as `PRIMARY',
419 and t is the same as `SECONDARY'. */)
420 (Lisp_Object selection)
422 check_window_system (NULL);
423 CHECK_SYMBOL (selection);
424 if (EQ (selection, Qnil)) selection = QPRIMARY;
425 if (EQ (selection, Qt)) selection = QSECONDARY;
426 return ns_get_pb_change_count (selection)
427 == ns_get_our_change_count_for (selection)
432 DEFUN ("ns-get-selection", Fns_get_selection,
433 Sns_get_selection, 2, 2, 0,
434 doc: /* Return text selected from some X window.
435 SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
436 \(Those are literal upper-case symbol names, since that's what X expects.)
437 TARGET-TYPE is the type of data desired, typically `STRING'. */)
438 (Lisp_Object selection_name, Lisp_Object target_type)
440 Lisp_Object val = Qnil;
442 check_window_system (NULL);
443 CHECK_SYMBOL (selection_name);
444 CHECK_SYMBOL (target_type);
446 if (ns_get_pb_change_count (selection_name)
447 == ns_get_our_change_count_for (selection_name))
448 val = ns_get_local_selection (selection_name, target_type);
450 val = ns_get_foreign_selection (selection_name, target_type);
451 if (CONSP (val) && SYMBOLP (Fcar (val)))
454 if (CONSP (val) && NILP (Fcdr (val)))
457 val = clean_local_selection_data (val);
463 nxatoms_of_nsselect (void)
465 NXPrimaryPboard = @"Selection";
466 NXSecondaryPboard = @"Secondary";
468 // This is a memory loss, never released.
469 pasteboard_changecount
470 = [[NSMutableDictionary
471 dictionaryWithObjectsAndKeys:
472 [NSNumber numberWithLong:0], NSGeneralPboard,
473 [NSNumber numberWithLong:0], NXPrimaryPboard,
474 [NSNumber numberWithLong:0], NXSecondaryPboard,
475 [NSNumber numberWithLong:0], NSStringPboardType,
476 [NSNumber numberWithLong:0], NSFilenamesPboardType,
477 [NSNumber numberWithLong:0], NSTabularTextPboardType,
482 syms_of_nsselect (void)
484 DEFSYM (QCLIPBOARD, "CLIPBOARD");
485 DEFSYM (QSECONDARY, "SECONDARY");
486 DEFSYM (QTEXT, "TEXT");
487 DEFSYM (QFILE_NAME, "FILE_NAME");
489 defsubr (&Sns_disown_selection_internal);
490 defsubr (&Sns_get_selection);
491 defsubr (&Sns_own_selection_internal);
492 defsubr (&Sns_selection_exists_p);
493 defsubr (&Sns_selection_owner_p);
495 Vselection_alist = Qnil;
496 staticpro (&Vselection_alist);
498 DEFVAR_LISP ("ns-sent-selection-hooks", Vns_sent_selection_hooks,
499 "A list of functions to be called when Emacs answers a selection request.\n\
500 The functions are called with four arguments:\n\
501 - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');\n\
502 - the selection-type which Emacs was asked to convert the\n\
503 selection into before sending (for example, `STRING' or `LENGTH');\n\
504 - a flag indicating success or failure for responding to the request.\n\
505 We might have failed (and declined the request) for any number of reasons,\n\
506 including being asked for a selection that we no longer own, or being asked\n\
507 to convert into a type that we don't know about or that is inappropriate.\n\
508 This hook doesn't let you change the behavior of Emacs's selection replies,\n\
509 it merely informs you that they have happened.");
510 Vns_sent_selection_hooks = Qnil;