Subject: bug#76120: Acknowledgement ([PATCH] Expose the native sharing dialog (macOS))

From: Alvaro Ramirez <[email-masked]>
Date: Fri, 07 Feb 2025 22:20:29 +0000

Simplifying patch 
(0002-Expose-the-native-sharing-dialog-macOS.patch)

[email-masked] (GNU bug Tracking System) writes:

> Thank you for filing a new bug report with debbugs.gnu.org.
>
> This is an automatically generated reply to let you know your 
> message
> has been received.
>
> Your message is being forwarded to the package maintainers and 
> other
> interested parties for their attention; they will reply in due 
> course.
>
> Your message has been sent to the package maintainer(s):
>  [email-masked]
>
> If you wish to submit further information on this problem, 
> please
> send it to [email-masked].
>
> Please do not send mail to [email-masked] unless you wish
> to report a problem with the Bug-tracking system.
>From 732fa8fa3605c1f93f99193bd153a32b344aea5c Mon Sep 17 00:00:00 2001
From: xenodium <[email-masked]>
Date: Fri, 7 Feb 2025 14:31:25 +0000
Subject: [PATCH] Expose the native sharing dialog (macOS)

* lisp/term/ns-win.el (ns-share): Interactive command to launch the
  native macOS sharing dialog.

* src/nsfns.m (ns--share-items): Exposes native sharing dialog.

* etc/NEWS: Announce the new feature.
---
 etc/NEWS            |  5 +++
 lisp/term/ns-win.el | 82 +++++++++++++++++++++++++++++++++++++++++++++
 src/nsfns.m         | 62 ++++++++++++++++++++++++++++++++++
 3 files changed, 149 insertions(+)

diff --git a/etc/NEWS b/etc/NEWS
index ade635aa924..3d2539204e2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1372,6 +1372,11 @@ allowing Emacs users access to speech recognition utilities.
 Note: Accepting this permission allows the use of system APIs, which may
 send user data to Apple’s speech recognition servers.
 
+---
+** 'ns-share' displays the native sharing dialog (macOS).
+Enables sharing region text, current buffer file, dired marked files,
+or dired region files.
+
 ---
 ** Emacs on MS-Windows now supports GUI dialogs and message boxes better.
 In particular, it is now possible to show text with embedded newlines in
diff --git a/lisp/term/ns-win.el b/lisp/term/ns-win.el
index 46639f2422b..165445bc8d4 100644
--- a/lisp/term/ns-win.el
+++ b/lisp/term/ns-win.el
@@ -588,6 +588,88 @@ ns-do-show-character-palette
   (interactive)
   (ns-show-character-palette))
 
+(declare-function ns--share-items "nsfns.m" (items))
+
+(defun ns-share (&optional items)
+  "Share ITEMS (list of files or string) via macOS service.
+
+When ITEMS are missing, derive as follows.
+
+From `dired', ITEMS are based on either of these being active:
+
+  - Marked files (list).
+  - Files in region (list).
+  - File at point (list).
+
+Anywhere else, these being active:
+
+  - Region (string).
+  - Current buffer file (list)."
+  (interactive)
+  (unless items
+    (setq items (ns--shareable-items)))
+  (unless items
+    (user-error "Nothing to share"))
+  (ns--share-items items))
+
+(defun ns--shareable-items ()
+  "Figure out what user wants to share based on context.
+
+From `dired', shared items are based on either of these being active:
+
+  - Marked files
+  - Files in region.
+  - File at point.
+
+Anywhere else, these being active:
+
+  - Region (sent as text).
+  - Current buffer file (if one is available)."
+  (cond ((derived-mode-p 'dired-mode)
+         (or
+          (ns--dired-filenames-in-region)
+          (dired-get-marked-files)))
+        ((use-region-p)
+         (buffer-substring-no-properties
+          (region-beginning)
+          (region-end)))
+        ((buffer-file-name)
+         (list (buffer-file-name)))))
+
+(defun ns--dired-filenames-in-region ()
+  "If `dired' buffer, return region files.  nil otherwise."
+  (when (and (equal major-mode 'dired-mode)
+             (use-region-p))
+    (let ((start (region-beginning))
+          (end (region-end))
+          (marked-files (let ((files (dired-get-marked-files)))
+                          (cond ((null (cdr files))
+                                 nil)
+                                ((and (= (length files) 2)
+                                      (eq (car files) t))
+                                 t)
+                                (t
+                                 (not (seq-empty-p files))))))
+          (filenames))
+      (when marked-files
+        (user-error "Mark dired files or select region.  Not both."))
+      (save-excursion
+        (save-restriction
+          (goto-char start)
+          (while (< (point) end)
+            ;; Skip non-file lines.
+            (while (and (< (point) end) (dired-between-files))
+              (forward-line 1))
+            (when (and (dired-get-filename nil t)
+                       ;; Ensure filename is fully selected.
+                       (< (save-excursion
+                            (forward-line 0)
+                            (dired-move-to-filename))
+                          end))
+              (setq filenames (append filenames (list (dired-get-filename nil t)))))
+            (forward-line 1))))
+      filenames)))
+
 (defun ns-next-frame ()
   "Switch to next visible frame."
   (interactive)
diff --git a/src/nsfns.m b/src/nsfns.m
index a2c50468cd1..d697a8c1737 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -3771,6 +3771,67 @@ The position is returned as a cons cell (X . Y) of the
   return Qnil;
 }
 
+static NSPoint
+ns_get_point_relative_coordinates (NSView* view)
+{
+  Lisp_Object coordinate = Fnth (make_fixnum (2), Fposn_at_point (Fpoint (), Qnil));
+
+  int x = ^{
+    Lisp_Object value = Fcar (coordinate);
+    if (NILP (value))
+      {
+        signal_error ("No x coordinate found", coordinate);
+      }
+    return XFIXNUM (value);
+  }();
+
+  int y = ^{
+    Lisp_Object value = Fcdr (coordinate);
+    if (NILP (value))
+    {
+      signal_error ("No y coordinate found", coordinate);
+    }
+    return XFIXNUM (value);
+  }();
+
+  return [view convertPoint:NSMakePoint (x, y) toView:nil];
+}
+
+DEFUN ("ns--share-items",
+       Fns__share_items,
+       Sns__share_items, 1, 1, 0,
+       doc: /* Share a list of items via macOS sharing service. */)
+       (Lisp_Object items)
+{
+  CHECK_LIST (items);
+
+  NSMutableArray *shared = [NSMutableArray array];
+  for (Lisp_Object tail = items; CONSP (tail); tail = XCDR (tail))
+  {
+    Lisp_Object elt = XCAR (tail);
+    if (!(STRINGP (elt)))
+    {
+      signal_error ("Element not a string", elt);
+    }
+    BOOL isDir;
+    if ([[NSFileManager defaultManager] fileExistsAtPath:@(SSDATA(elt)) isDirectory:&isDir]) {
+      [shared addObject:[NSURL fileURLWithPath:@(SSDATA(elt))]];
+      continue;
+    }
+    [shared addObject:@(SSDATA(elt))];
+  }
+
+  struct frame *frame = SELECTED_FRAME ();
+  EmacsView *view = FRAME_NS_VIEW (frame);
+  NSPoint point = ns_get_point_relative_coordinates (view);
+  NSRect displayArea = NSMakeRect (point.x + 15, view.bounds.size.height - point.y + 25, 1, 1);
+  NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:shared];
+
+  [picker showRelativeToRect:displayArea ofView:view preferredEdge:NSRectEdgeMaxY];
+
+  return Qnil;
+}
+
 /* ==========================================================================
 
     Class implementations
@@ -4003,6 +4064,7 @@ - (Lisp_Object)lispString
   defsubr (&Sns_set_mouse_absolute_pixel_position);
   defsubr (&Sns_mouse_absolute_pixel_position);
   defsubr (&Sns_show_character_palette);
+  defsubr (&Sns__share_items);
   defsubr (&Sx_display_mm_width);
   defsubr (&Sx_display_mm_height);
   defsubr (&Sx_display_screens);
-- 
2.39.5 (Apple Git-154)