Subject: bug#76120: closed (Re: bug#76120: This feature is not about "sharing", or a "native" anything.)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 26 Jul 2025 10:39:02 +0000

Your bug report

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

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
76120: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=76120
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#76120: This feature is not about "sharing", or a "native" anything.
From: Eli Zaretskii <[email-masked]>
To: Alvaro Ramirez <[email-masked]>
Cc: [email-masked], [email-masked], [email-masked], [email-masked], [email-masked]
Date: Sat, 26 Jul 2025 13:38:06 +0300
List-Id: <debbugs-submit.debbugs.gnu.org>

> From: Alvaro Ramirez <[email-masked]>
> Date: Sat, 26 Jul 2025 10:23:03 +0100
> Cc: Gerd Möllmann <[email-masked]>,
>  [email-masked], [email-masked], [email-masked],
>  [email-masked]
> 
> 
> > On 26 Jul 2025, at 10:15, Eli Zaretskii <[email-masked]> wrote:
> > 
> > 
> >> 
> >> From: Gerd Möllmann <[email-masked]>
> >> Cc: Eli Zaretskii <[email-masked]>,  [email-masked],  [email-masked],
> >>  [email-masked],  [email-masked]
> >> Date: Sat, 19 Jul 2025 19:54:31 +0200
> >> 
> >> Alvaro Ramirez <[email-masked]> writes:
> >> 
> >>>> I guess I could add a first entry to send-to-handlers that invokes a
> >>>> transient or something that acts as a replacement for the GUI share
> >>>> menu. So I'd aay send-to-handlers solves this. Thanks!
> >>>> 
> >>>> (I was thinking of "open -a Mail FILE" which opens a new message
> >>>> with
> >>>> FILE attached, while "open FILE" opens some application which is
> >>>> associated with the file type, e.g. TextEdit for text files.
> >>>> Sometimes
> >>>> one wants to do that, sometimes the other.)
> >>> 
> >>> Ah, I see what you mean. Closest I got to that on macOS is M-x
> >>> dwim-shell-commands-macos-open-with:
> >>> 
> >>> https://github.com/xenodium/dwim-shell-command/blob/4b077432a94873e5f505c8f569743cfd984eebb1/dwim-shell-commands.el#L1331
> >>> 
> >>> If you do add your own handler, I’d love to see what you come up with.
> >> 
> >> I've added that the my EGTL (Ever Growing Todo List), but I must admit
> >> it's unlikely that I'll do it :-). In similar cases, M-! plus Pcomplete
> >> and Corfu for command, options, file name completions is pretty good, I
> >> must say.
> > 
> > Should we now close this bug?
> 
> Sounds good to me.
> 
> Thanks everyone for all your help.

Closing.

Subject: [PATCH] Expose the native sharing dialog (macOS)
From: Álvaro Ramírez <[email-masked]>
To: [email-masked]
Date: Fri, 07 Feb 2025 14:59:21 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

Hi folks,

Similar to the existing ns-do-show-character-palette command, this 
patch exposes a native macOS context dialog.

The new addition enables users to share (send) text or files to 
other apps.

Examples:

- Send current buffer file.
- Send a list of dired files.
- Send region text.

I'm keen to polish the patch a bit more, but wanted to pre-fly 
things before investing further.

Overall it's a nice/convenient addition. I use it frequently to 
quickly send things to my phone over local network/Bluetooth.

I'm attaching a couple of screenshots showcasing a couple of uses:

share-dired-files.jpg (send files from dired)
share-region-text.jpg (send region text)

Hope you like it.

Álvaro

In GNU Emacs 31.0.50 (build 44, aarch64-apple-darwin24.0.0, NS
 appkit-2566.00 Version 15.0.1 (Build 24A348)) of 2025-02-07 built 
 on
 MacBookPro
Repository revision: 811d575336942b1cd1e1c6fb50620babdf9cc82c
Repository branch: macos-share
Windowing system distributor 'Apple', version 10.3.2566
System Description:  macOS 15.0.1

Configured using:
 'configure --with-ns
 --prefix=/Users/alvaro/stuff/active/code/third_party/emacs/nextstep/Emacs.app/Contents/MacOS
 --enable-locallisppath=/Users/alvaro/stuff/active/code/third_party/emacs/nextstep/Emacs.app/Contents/MacOS'

>From c9b424c60692964a1262e2adef45e77c594e98ab 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-files ns--share-text): Expose native sharing dialogs.

* etc/NEWS: Announce the new feature.
---
 etc/NEWS            |  5 +++
 lisp/term/ns-win.el | 89 +++++++++++++++++++++++++++++++++++++++++++++
 src/nsfns.m         | 82 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 176 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..d1067548aa1 100644
--- a/lisp/term/ns-win.el
+++ b/lisp/term/ns-win.el
@@ -588,6 +588,95 @@ ns-do-show-character-palette
   (interactive)
   (ns-show-character-palette))
 
+(declare-function ns--share-files "nsfns.m" (files))
+
+(declare-function ns--share-text "nsfns.m" (text))
+
+(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"))
+  (cond ((stringp items)
+         (ns--share-text items))
+        ((listp items)
+         (ns--share-files items))
+        (t
+         (error "Invalid items: %s" 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))
+          (active-marks (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 active-marks
+        (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..4f6512fd191 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -3771,6 +3771,86 @@ 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-files",
+       Fns__share_files,
+       Sns__share_files, 1, 1, 0,
+       doc: /* Share a list of files via macOS sharing service. */)
+       (Lisp_Object files)
+{
+  CHECK_LIST (files);
+
+  NSMutableArray *items = [NSMutableArray array];
+  for (Lisp_Object tail = files; CONSP (tail); tail = XCDR (tail))
+  {
+    Lisp_Object elt = XCAR (tail);
+    if (!(STRINGP (elt)))
+    {
+      error ("Element not a string: %s", SDATA (elt));
+    }
+    [items addObject:[NSURL fileURLWithPath:@(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:items];
+
+  [picker showRelativeToRect:displayArea ofView:view preferredEdge:NSRectEdgeMaxY];
+
+  return Qnil;
+}
+
+DEFUN ("ns--share-text",
+       Fns__share_text,
+       Sns__share_text, 1, 1, 0,
+       doc: /* Share text via macOS sharing service. */)
+     (Lisp_Object text)
+{
+  if (!STRINGP (text))
+  {
+    signal_error ("Argument not a string", text);
+  }
+
+  NSString *shareText = @(SSDATA (text));
+  NSArray *items = @[shareText];
+  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:items];
+
+  [picker showRelativeToRect:displayArea ofView:view preferredEdge:NSRectEdgeMaxY];
+
+  return Qnil;
+}
+
 /* ==========================================================================
 
     Class implementations
@@ -4003,6 +4083,8 @@ - (Lisp_Object)lispString
   defsubr (&Sns_set_mouse_absolute_pixel_position);
   defsubr (&Sns_mouse_absolute_pixel_position);
   defsubr (&Sns_show_character_palette);
+  defsubr (&Sns__share_files);
+  defsubr (&Sns__share_text);
   defsubr (&Sx_display_mm_width);
   defsubr (&Sx_display_mm_height);
   defsubr (&Sx_display_screens);
-- 
2.39.5 (Apple Git-154)