unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#44973: Add a macOS global hotkey function
@ 2020-11-30 21:01 j
  2020-12-08 20:39 ` Lars Ingebrigtsen
  0 siblings, 1 reply; 19+ messages in thread
From: j @ 2020-11-30 21:01 UTC (permalink / raw)
  To: 44973

[-- Attachment #1: Type: text/plain, Size: 1120 bytes --]

MacOS makes it very difficult to assign global hotkeys to focus Emacs.
For example, if I'm in another program, and I want to launch/focus Emacs
with "Alt-SPC", it can't just be set in the system preferences; I'd need to
use a 3rd-party program like skhd.

Would it be possible to add a function or hook to Emacs which registers one
or more global hotkeys (using addGlobalMonitorForEventsMatchingMask), and
then when the hotkey is pressed, run an elisp function?

For example, by creating a "ns-register-global hotkey" function, where the
user could choose a keybinding (maybe "ALT-SPC"), and letting the user
provide a hook function (maybe "org-capture-stuff")

I intend this feature to be used for something like an Alfred replacement,
as described here: https://www.mattduck.com/emacs-fuzzy-launcher.html .

Even if it's rare for Emacs to implement an OS-specific feature, there is
some precedent on Windows. If you look at w32-register-hot-keys, it's
similar to, but different from my request.

The implementation in nsterm.m should be simple, but it requires Obj-C and
Emacs-specific development knowledge.

Thanks!

[-- Attachment #2: Type: text/html, Size: 1570 bytes --]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-11-30 21:01 bug#44973: Add a macOS global hotkey function j
@ 2020-12-08 20:39 ` Lars Ingebrigtsen
  2020-12-21  0:40   ` j
  0 siblings, 1 reply; 19+ messages in thread
From: Lars Ingebrigtsen @ 2020-12-08 20:39 UTC (permalink / raw)
  To: j; +Cc: 44973

j@mremus.net writes:

> MacOS makes it very difficult to assign global hotkeys to focus
> Emacs. For example, if I'm in another program, and I want to
> launch/focus Emacs with "Alt-SPC", it can't just be set in the system
> preferences; I'd need to use a 3rd-party program like skhd.
>
> Would it be possible to add a function or hook to Emacs which registers one or
> more global hotkeys (using addGlobalMonitorForEventsMatchingMask), and then
> when the hotkey is pressed, run an elisp function?

[...]

> Even if it's rare for Emacs to implement an OS-specific feature, there
> is some precedent on Windows. If you look at w32-register-hot-keys,
> it's similar to, but different from my request.

Sure, I think that makes sense.  Would it be possible for you to work up
a patch that adds this functionality?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-08 20:39 ` Lars Ingebrigtsen
@ 2020-12-21  0:40   ` j
  2020-12-21  4:34     ` Lars Ingebrigtsen
  2020-12-21  8:12     ` Alan Third
  0 siblings, 2 replies; 19+ messages in thread
From: j @ 2020-12-21  0:40 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 44973

[-- Attachment #1: Type: text/plain, Size: 2026 bytes --]

Hi Lars,

I'm pretty close to having a patch ready, but I'm stuck in one key spot. I
don't know if anyone on the list could help?

After the user registers a hotkey, and when they press the hotkey, MacOS
will run the code below. On the line "RUN_SOME_ELISP_FUNCTION", I would
expect some elisp function to be run (e.g. 'emacs-version').

But no matter what I do, it always crashes the program. I think my first
problem is not knowing how to call elisp (run_hooks, safe_call, etc?)
correctly, but second, I suspect if this is crashing due to a threading
issue.

    handler = [NSEvent
addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown)
handler:^(NSEvent *event){
        if (event.modifierFlags & modifier)
          if([event.charactersIgnoringModifiers characterAtIndex:0] ==
vkey) {
           RUN_SOME_ELISP_FUNCTION
            [[NSApp mainWindow] makeKeyAndOrderFront:NSApp];
          }
        }
      }];

Is there anyone who might know how to fill in this piece?

Thanks

On Tue, Dec 8, 2020 at 12:39 PM Lars Ingebrigtsen <larsi@gnus.org> wrote:

> j@mremus.net writes:
>
> > MacOS makes it very difficult to assign global hotkeys to focus
> > Emacs. For example, if I'm in another program, and I want to
> > launch/focus Emacs with "Alt-SPC", it can't just be set in the system
> > preferences; I'd need to use a 3rd-party program like skhd.
> >
> > Would it be possible to add a function or hook to Emacs which registers
> one or
> > more global hotkeys (using addGlobalMonitorForEventsMatchingMask), and
> then
> > when the hotkey is pressed, run an elisp function?
>
> [...]
>
> > Even if it's rare for Emacs to implement an OS-specific feature, there
> > is some precedent on Windows. If you look at w32-register-hot-keys,
> > it's similar to, but different from my request.
>
> Sure, I think that makes sense.  Would it be possible for you to work up
> a patch that adds this functionality?
>
> --
> (domestic pets only, the antidote for overdose, milk.)
>    bloggy blog: http://lars.ingebrigtsen.no
>

[-- Attachment #2: Type: text/html, Size: 3322 bytes --]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21  0:40   ` j
@ 2020-12-21  4:34     ` Lars Ingebrigtsen
  2020-12-30  4:10       ` j
  2020-12-21  8:12     ` Alan Third
  1 sibling, 1 reply; 19+ messages in thread
From: Lars Ingebrigtsen @ 2020-12-21  4:34 UTC (permalink / raw)
  To: j; +Cc: 44973

j@mremus.net writes:

> But no matter what I do, it always crashes the program. I think my
> first problem is not knowing how to call elisp (run_hooks, safe_call,
> etc?) correctly, but second, I suspect if this is crashing due to a
> threading issue.

I am not at all familiar with the ns functions, but looking at the other
.m files, it looks like you should be able to just say

  call0 (intern ("some-function"));

or something like that?  (Modulo threading stuff.)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21  0:40   ` j
  2020-12-21  4:34     ` Lars Ingebrigtsen
@ 2020-12-21  8:12     ` Alan Third
  2020-12-21 16:18       ` Eli Zaretskii
  1 sibling, 1 reply; 19+ messages in thread
From: Alan Third @ 2020-12-21  8:12 UTC (permalink / raw)
  To: j; +Cc: 44973, Lars Ingebrigtsen

On Sun, Dec 20, 2020 at 04:40:37PM -0800, j@mremus.net wrote:
> Hi Lars,
> 
> I'm pretty close to having a patch ready, but I'm stuck in one key spot. I
> don't know if anyone on the list could help?
> 
> After the user registers a hotkey, and when they press the hotkey, MacOS
> will run the code below. On the line "RUN_SOME_ELISP_FUNCTION", I would
> expect some elisp function to be run (e.g. 'emacs-version').
> 
> But no matter what I do, it always crashes the program. I think my first
> problem is not knowing how to call elisp (run_hooks, safe_call, etc?)
> correctly, but second, I suspect if this is crashing due to a threading
> issue.
> 
>     handler = [NSEvent
> addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown)
> handler:^(NSEvent *event){
>         if (event.modifierFlags & modifier)
>           if([event.charactersIgnoringModifiers characterAtIndex:0] ==
> vkey) {
>            RUN_SOME_ELISP_FUNCTION
>             [[NSApp mainWindow] makeKeyAndOrderFront:NSApp];
>           }
>         }
>       }];

I think you probably want to send an event to Emacs, that way a user
can bind it to anything they like. Look at, say, [EmacsApp openFile:]
for how do that.

One potential problem with your approach is that we don't accept code
using Objective C blocks as it's incompatible with GCC. Unless that
policy has changed?
-- 
Alan Third





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21  8:12     ` Alan Third
@ 2020-12-21 16:18       ` Eli Zaretskii
  2020-12-21 16:34         ` Alan Third
  0 siblings, 1 reply; 19+ messages in thread
From: Eli Zaretskii @ 2020-12-21 16:18 UTC (permalink / raw)
  To: Alan Third; +Cc: 44973, alan, larsi, j

> Date: Mon, 21 Dec 2020 08:12:04 +0000
> From: Alan Third <alan@idiocy.org>
> Cc: 44973@debbugs.gnu.org, Lars Ingebrigtsen <larsi@gnus.org>
> 
> One potential problem with your approach is that we don't accept code
> using Objective C blocks as it's incompatible with GCC. Unless that
> policy has changed?

It hasn't changed, but does this code really need to use that feature?
The last time we had a much more grave problem: the system header used
for some code itself used ObjC blocks, and thus couldn't be compiled
by GCC.  It looks like this case is not that problematic?






^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21 16:18       ` Eli Zaretskii
@ 2020-12-21 16:34         ` Alan Third
  2020-12-22  5:20           ` Richard Stallman
  0 siblings, 1 reply; 19+ messages in thread
From: Alan Third @ 2020-12-21 16:34 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 44973, larsi, j

On Mon, Dec 21, 2020 at 06:18:58PM +0200, Eli Zaretskii wrote:
> > Date: Mon, 21 Dec 2020 08:12:04 +0000
> > From: Alan Third <alan@idiocy.org>
> > Cc: 44973@debbugs.gnu.org, Lars Ingebrigtsen <larsi@gnus.org>
> > 
> > One potential problem with your approach is that we don't accept code
> > using Objective C blocks as it's incompatible with GCC. Unless that
> > policy has changed?
> 
> It hasn't changed, but does this code really need to use that feature?
> The last time we had a much more grave problem: the system header used
> for some code itself used ObjC blocks, and thus couldn't be compiled
> by GCC.  It looks like this case is not that problematic?

The class method used takes a block as an argument. I don't think
there are any work-arounds.

The GCC work to support blocks appears to be progressing, but slowly:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78352
-- 
Alan Third





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21 16:34         ` Alan Third
@ 2020-12-22  5:20           ` Richard Stallman
  0 siblings, 0 replies; 19+ messages in thread
From: Richard Stallman @ 2020-12-22  5:20 UTC (permalink / raw)
  To: Alan Third; +Cc: 44973, larsi, alan, j

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > The GCC work to support blocks appears to be progressing, but slowly:

  > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78352

I am glad it is making progress.  We will get there eventually.

-- 
Dr Richard Stallman
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)







^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-21  4:34     ` Lars Ingebrigtsen
@ 2020-12-30  4:10       ` j
  2020-12-30 11:01         ` Alan Third
  2020-12-30 17:08         ` Eli Zaretskii
  0 siblings, 2 replies; 19+ messages in thread
From: j @ 2020-12-30  4:10 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 44973


[-- Attachment #1.1: Type: text/plain, Size: 2131 bytes --]

Hi Lars,

Here is the patch to bind a global hotkey in mac. As long as Emacs is set as
"trusted" in macOS preferences, the user can bind a two-key hotkey of the
form [modifier-key] or a single-key modifier [function key], for example
(mac-bind-global-hotkey [f1] 'tetris). Binding a three-key
combo is left to a future patch.

The code is copied from w32-register-hot-key as much as possible.

The routine intentionally does not focus the window after the hotkey is
hit; the
user must call a function like (x-focus-frame nil) to focus the frame.

I also wanted to mention that after discovering x-focus-frame, and testing
my
patch and shkd more, I realized that my patch is in fact very similar to
shkd in
functionality. The main differences are that my patch allows hotkeys to be
declared directly in emacs (which I like), and that this patch acts
directly on
an open window (e.g. making it easier to make a hotkey to yank into an
already-open buffer). On the other hand skhd would call emacsclient, which
interacts with the server, even if no frame is open, offering its own
advantages.

Anyways, I'll leave this patch for your consideration as to whether it's
the right
thing to include in emacs!

P.S. I looked into what other code in emacs might be using blocks. I believe
nsxwidget.m also has a block in nsxwidget_webkit_execute_script. I'm
guessing
it's working in our cases because we're ultimately building with the Xcode C
compiler.

Thanks

On Sun, Dec 20, 2020 at 8:35 PM Lars Ingebrigtsen <larsi@gnus.org> wrote:

> j@mremus.net writes:
>
> > But no matter what I do, it always crashes the program. I think my
> > first problem is not knowing how to call elisp (run_hooks, safe_call,
> > etc?) correctly, but second, I suspect if this is crashing due to a
> > threading issue.
>
> I am not at all familiar with the ns functions, but looking at the other
> .m files, it looks like you should be able to just say
>
>   call0 (intern ("some-function"));
>
> or something like that?  (Modulo threading stuff.)
>
> --
> (domestic pets only, the antidote for overdose, milk.)
>    bloggy blog: http://lars.ingebrigtsen.no
>

[-- Attachment #1.2: Type: text/html, Size: 3049 bytes --]

[-- Attachment #2: mac_bind_global_hotkey.diff --]
[-- Type: application/octet-stream, Size: 14266 bytes --]

diff --git a/lisp/loadup.el b/lisp/loadup.el
index 568b9fe40d..f9be0f3645 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -335,6 +335,7 @@
       (when (featurep 'charprop)
         (load "international/mule-util")
         (load "international/ucs-normalize")
+        (load "ns-fns")
         (load "term/ns-win"))))
 (if (fboundp 'x-create-frame)
     ;; Do it after loading term/foo-win.el since the value of the
diff --git a/lisp/ns-fns.el b/lisp/ns-fns.el
new file mode 100644
index 0000000000..54aefc8cb0
--- /dev/null
+++ b/lisp/ns-fns.el
@@ -0,0 +1,40 @@
+;;; ns-fns.el --- Lisp routines for NeXT/Open/GNUstep/macOS window system  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 1993-1994, 2005-2020 Free Software Foundation, Inc.
+
+;; Keywords: internal
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;
+;;; Commentary:
+
+
+;;; Code:
+(defgroup ns nil
+  "GNUstep/macOS specific features."
+  :group 'environment)
+
+(defun mac-handle-global-hotkey (event)
+  "Handles global hotkey presses, running EVENT.
+Not intended to be called directly"
+  (interactive "e")
+
+  (apply (cdr event)))
+
+(provide 'ns-fns)
+;;; ns-fns.el ends here
diff --git a/src/keyboard.c b/src/keyboard.c
index 2e0143379a..59a17d4e48 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -6010,6 +6010,11 @@ make_lispy_event (struct input_event *event)
 	return list2 (res, list2 (event->frame_or_window, location));
       }
 
+#ifdef NS_IMPL_COCOA
+      case GLOBAL_HOTKEY_EVENT:
+          return list2 (Qmac_global_hotkey, event->arg);
+#endif
+
     case USER_SIGNAL_EVENT:
       /* A user signal.  */
       {
@@ -11759,6 +11764,10 @@ syms_of_keyboard (void)
   DEFSYM (Qcommand_execute, "command-execute");
   DEFSYM (Qinternal_echo_keystrokes_prefix, "internal-echo-keystrokes-prefix");
 
+#ifdef NS_IMPL_COCOA
+  DEFSYM (Qmac_global_hotkey, "mac-global-hotkey");
+#endif
+
   accent_key_syms = Qnil;
   staticpro (&accent_key_syms);
 
@@ -12459,6 +12468,11 @@ keys_of_keyboard (void)
 
   initial_define_lispy_key (Vspecial_event_map, "delete-frame",
 			    "handle-delete-frame");
+
+#ifdef NS_IMPL_COCOA
+  initial_define_lispy_key (Vspecial_event_map, "mac-global-hotkey",
+                            "mac-handle-global-hotkey");
+#endif
 #ifdef HAVE_NTGUI
   initial_define_lispy_key (Vspecial_event_map, "end-session",
 			    "kill-emacs");
diff --git a/src/nsfns.m b/src/nsfns.m
index c7956497c4..7aeb84e880 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -67,6 +67,169 @@ Updated by Christian Limpach (chris@nice.ch)
 
    ========================================================================== */
 
+#ifdef NS_IMPL_COCOA
+const char *const lispy_to_mac_function_keys[] =
+  {
+    "up", "down", "left", "right",          /* NSUpArrowfunctionKey    0xF700 */
+    "f1", "f2", "f3", "f4", "f5",           /* NSF1FunctionKey         0xF704 */
+    "f6", "f7", "f8", "f9", "f10",
+    "f11", "f12", "f13", "f14", "f15",
+    "f16", "f17", "f18", "f19", "f20",
+    "f21", "f22", "f23", "f24", "f25",
+    "f26", "f27", "f28", "f29", "f30",
+    "f31", "f32", "f33", "f34", "f35",      /* NSF35FunctionKey        0xF726 */
+    "insert", "delete", "home",             /* NSInsertfunctionKey     0xF727 */
+    "begin", "end", "prior", "next",
+    "print", "scroll", "pause",
+    0,                                      /* NSSysReqFunctionKey     0xF731 */
+    "break", "reset",
+    0,
+    "menu",
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    "select", "execute", "undo", "redo",
+    "find", "help", "mode-change"           /* NSModeSwitchFunctionKey 0xF747 */
+  };
+
+/* Lookup virtual keycode from string representing the name of a
+   non-ascii keystroke into the corresponding virtual key, using
+   lispy_function_keys.  */
+/* Adapted from w32fns.c */
+static unsigned
+lookup_vk_code (char *key)
+{
+  unsigned i;
+
+  for (i = 0; i < 71; i++)
+    if (lispy_to_mac_function_keys[i]
+	&& strcmp (lispy_to_mac_function_keys[i], key) == 0)
+      return (i | 0xF700);
+
+  /* Alphanumerics map to themselves.  */
+  if (key[1] == 0)
+    {
+      if ((key[0] >= 'A' && key[0] <= 'Z')
+          || (key[0] >= '0' && key[0] <= '9'))
+        return key[0];
+      if (key[0] >= 'a' && key[0] <= 'z')
+        return toupper(key[0]);
+    }
+
+  // tab, enter, and backspace are special cases
+  if (strcmp (key, "tab") == 0)
+    return 9;
+  if (strcmp (key, "enter") == 0)
+    return 13;
+   if (strcmp (key, "escape") == 0)
+    return 27;
+  if (strcmp (key, "backspace") == 0)
+    return 127;
+
+  return -1;
+}
+
+static Lisp_Object
+mac_parse_and_add_hotkey (Lisp_Object key, Lisp_Object lispfn, int hook)
+{
+  /* Copied from Fdefine_key and store_in_keymap.  */
+  register Lisp_Object c;
+  unsigned vk_code = 0;
+  int lisp_modifiers = 0;
+  NSEventModifierFlags mac_modifiers = 0;
+  Lisp_Object res = Qnil;
+  char* vkname;
+
+  CHECK_VECTOR (key);
+
+  if (ASIZE (key) != 1)
+    return Qnil;
+
+  c = AREF (key, 0);
+
+  if (CONSP (c) && lucid_event_type_list_p (c))
+    c = Fevent_convert_list (c);
+
+  if (! FIXNUMP (c) && ! SYMBOLP (c))
+    error ("Key definition is invalid");
+
+  if (! FUNCTIONP(lispfn))
+    error ("HOOK argument is not a function");
+
+  /* Work out the base key and the modifiers.  */
+  if (SYMBOLP (c))
+    {
+
+      c = parse_modifiers (c);
+      lisp_modifiers = XFIXNUM (Fcar (Fcdr (c)));
+      c = Fcar (c);
+      if (!SYMBOLP (c))
+	emacs_abort ();
+
+      vkname = SSDATA (SYMBOL_NAME (c));
+      if (vkname[0] == 0)
+        error("Key definition is invalid");
+      else
+        vk_code = lookup_vk_code (vkname);
+    }
+  else if (FIXNUMP (c))
+    {
+      lisp_modifiers = XFIXNUM (c) & ~CHARACTERBITS;
+      /* Many ascii characters are their own virtual key code.  */
+      vk_code = XFIXNUM (c) & CHARACTERBITS;
+    }
+
+  if (vk_code < 0 || vk_code > 0xF747)
+    return Qnil;
+  else if ((vk_code >= 0xF700) && (vk_code <= 0xF747))
+    mac_modifiers = mac_modifiers | NSEventModifierFlagFunction;
+
+  /* Bind key combinations based on modifier mappings.  */
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_command_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_command_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_command_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_command_modifier, Qcontrol))
+      )
+    {
+      mac_modifiers = mac_modifiers | NSEventModifierFlagCommand;
+    }
+
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_alternate_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_alternate_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_alternate_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_alternate_modifier, Qcontrol))
+      )
+    {
+      mac_modifiers = mac_modifiers | NSEventModifierFlagOption;
+    }
+
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_control_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_control_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_control_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_control_modifier, Qcontrol))
+      )
+    {
+      mac_modifiers = mac_modifiers | NSEventModifierFlagControl;
+    }
+
+  // Bind function key
+  if ((mac_modifiers & NSEventModifierFlagDeviceIndependentFlagsMask) > 0)
+    mac_bind_key(mac_modifiers, vk_code, lispfn);
+
+  return key;
+}
+#endif
+
 /* Let the user specify a Nextstep display with a Lisp object.
    OBJECT may be nil, a frame or a terminal object.
    nil stands for the selected frame--or, if that is not a Nextstep frame,
@@ -2984,6 +3147,56 @@ The position is returned as a cons cell (X . Y) of the
   return Qnil;
 }
 
+#ifdef NS_IMPL_COCOA
+static Lisp_Object mac_registered_hotkeys;
+
+DEFUN ("mac-bind-global-hotkey",
+       Fmac_bind_global_hotkey,
+       Smac_bind_global_hotkey, 2, 2, 0,
+       doc: /* Bind KEY combination as a global hotkey, and run HOOK upon
+invokation. This function assigns a hotkey that will run an elisp function
+from anywhere in MacOS outside Emacs.  The return value is t if registering
+the hotkey was successful, otherwise nil.  */)
+ (Lisp_Object key, Lisp_Object hook)
+{
+  id handler;
+
+  key = mac_parse_and_add_hotkey (key, hook, 1);
+
+  if (!NILP (key) && NILP (Fmemq (Fcons(key,hook), mac_registered_hotkeys)))
+    {
+      Lisp_Object item = Fmemq (Qnil, mac_registered_hotkeys);
+
+      if (NILP (item))
+        mac_registered_hotkeys = Fcons (Fcons(key,hook), mac_registered_hotkeys);
+      else
+        XSETCAR (item, Fcons(key,hook));
+    }
+
+  return key;
+}
+
+DEFUN ("mac-show-bound-hotkeys", Fmac_show_bound_hotkeys,
+       Smac_show_bound_hotkeys, 0, 0, 0,
+       doc: /* Return list of registered hot-key IDs.  */)
+  (void)
+{
+  return mac_registered_hotkeys;
+}
+
+DEFUN ("mac-clear-hotkeys", Fmac_clear_hotkeys,
+       Smac_clear_hotkeys, 0, 0, 0,
+       doc: /* Unbind all global hotkeys */)
+  (void)
+{
+  mac_registered_hotkeys = Qnil;
+
+  mac_clear_hotkeys();
+
+  return Qt;
+}
+#endif
+
 /* ==========================================================================
 
     Class implementations
@@ -3136,6 +3349,11 @@ - (Lisp_Object)lispString
   defsubr (&Sns_set_mouse_absolute_pixel_position);
   defsubr (&Sns_mouse_absolute_pixel_position);
   defsubr (&Sns_show_character_palette);
+#ifdef NS_IMPL_COCOA
+  defsubr (&Smac_bind_global_hotkey);
+  defsubr (&Smac_show_bound_hotkeys);
+  defsubr (&Smac_clear_hotkeys);
+#endif
   defsubr (&Sx_display_mm_width);
   defsubr (&Sx_display_mm_height);
   defsubr (&Sx_display_screens);
@@ -3160,6 +3378,10 @@ - (Lisp_Object)lispString
   defsubr (&Sx_show_tip);
   defsubr (&Sx_hide_tip);
 
+#ifdef NS_IMPL_COCOA
+  staticpro (&mac_registered_hotkeys);
+  mac_registered_hotkeys = Qnil;
+#endif
   as_status = 0;
   as_script = Qnil;
   staticpro (&as_script);
diff --git a/src/nsterm.h b/src/nsterm.h
index f292993d8f..dfade9004b 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -379,6 +379,7 @@ #define NS_DRAW_TO_BUFFER 1
 #ifdef NS_IMPL_COCOA
   BOOL shouldKeepRunning;
   BOOL isFirst;
+  NSStatusItem *theItem;
 #endif
 #ifdef NS_IMPL_GNUSTEP
   BOOL applicationDidFinishLaunchingCalled;
@@ -1230,6 +1231,11 @@ #define NSAPP_DATA2_RUNFILEDIALOG 11
 extern void ns_init_events (struct input_event *);
 extern void ns_finish_events (void);
 
+#ifdef NS_IMPL_COCOA
+typedef enum NSEventModifierFlags NSEventModifierFlags;
+extern void mac_bind_key (enum NSEventModifierFlags modifier, unsigned vkey, Lisp_Object lispfn);
+extern void mac_clear_hotkeys (void);
+#endif
 
 #ifdef NS_IMPL_GNUSTEP
 extern char gnustep_base_version[];  /* version tracking */
diff --git a/src/nsterm.m b/src/nsterm.m
index fa38350a2f..c556eb3a05 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -9721,6 +9721,58 @@ Convert an X font name (XLFD) to an NS font name.
   return ret;
 }
 
+#ifdef NS_IMPL_COCOA
+// Store event handler event ids.
+NSMutableArray *hotkey_ids;
+
+void
+mac_bind_key (NSEventModifierFlags modifier, unsigned vkey,
+                       Lisp_Object lispfn)
+{
+  id handler;
+
+  // Check if Emacs is trusted.
+  NSDictionary *options = @{(__bridge id) kAXTrustedCheckOptionPrompt: @YES};
+  Boolean trusted = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
+
+  if (hotkey_ids == nil)
+    hotkey_ids = [[NSMutableArray alloc] initWithCapacity: 1];
+
+  if (trusted) {
+    handler = [NSEvent
+                addGlobalMonitorForEventsMatchingMask: (NSEventMaskKeyDown)
+                                              handler:^(NSEvent *event) {
+        if ((event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == modifier) {
+          if([[event.charactersIgnoringModifiers capitalizedString]
+               characterAtIndex:0] == vkey) {
+            struct frame *emacsframe = SELECTED_FRAME ();
+            NSEvent *theEvent = [NSApp currentEvent];
+
+            emacs_event->kind = GLOBAL_HOTKEY_EVENT;
+            emacs_event->arg = lispfn;
+            EV_TRAILER(theEvent);
+          }
+        }
+      }];
+
+    [hotkey_ids addObject: handler];
+  }
+  else {
+    error("Emacs app isn't trusted. Enable in system settings.");
+  }
+}
+
+void
+mac_clear_hotkeys (void)
+{
+
+  for (id hotkey in hotkey_ids) {
+    [NSEvent removeMonitor: hotkey];
+  }
+
+  [hotkey_ids removeAllObjects];
+}
+#endif
 
 void
 syms_of_nsterm (void)
@@ -9935,6 +9987,7 @@ Nil means use fullscreen the old (< 10.7) way.  The old way works better with
   DEFSYM (QCmouse, ":mouse");
 
 #ifdef NS_IMPL_COCOA
+  DEFSYM (Qglobal_hotkey_hook, "global-hotkey-hook");
   Fprovide (Qcocoa, Qnil);
   syms_of_macfont ();
 #else
diff --git a/src/termhooks.h b/src/termhooks.h
index d18b750c3a..b9c32a1ccd 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -188,6 +188,11 @@ #define EMACS_TERMHOOKS_H
   USER_SIGNAL_EVENT,		/* A user signal.
                                    code is a number identifying it,
                                    index into lispy_user_signals.  */
+#ifdef NS_IMPL_COCOA
+  GLOBAL_HOTKEY_EVENT,          /* An event for when a hotkey is pressed
+                                   outside of emacs that is setup to execute
+                                   an elisp function.  */
+#endif
 
   /* Help events.  Member `frame_or_window' of the input_event is the
      frame on which the event occurred, and member `arg' contains

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-30  4:10       ` j
@ 2020-12-30 11:01         ` Alan Third
  2020-12-30 17:08         ` Eli Zaretskii
  1 sibling, 0 replies; 19+ messages in thread
From: Alan Third @ 2020-12-30 11:01 UTC (permalink / raw)
  To: j; +Cc: 44973, Lars Ingebrigtsen

On Tue, Dec 29, 2020 at 08:10:00PM -0800, j@mremus.net wrote:
> Here is the patch to bind a global hotkey in mac. As long as Emacs is set as
> "trusted" in macOS preferences, the user can bind a two-key hotkey of the
> form [modifier-key] or a single-key modifier [function key], for example
> (mac-bind-global-hotkey [f1] 'tetris). Binding a three-key
> combo is left to a future patch.

Hi, thanks for this.

I'll leave it up to Lars and Eli whether this gets to go in, but I've
got a few comments:

+;; Copyright (C) 1993-1994, 2005-2020 Free Software Foundation, Inc.

Since this is a new file I'm pretty sure we just specify 2020 for
copyright.

+const char *const lispy_to_mac_function_keys[] =
+  {
+    "up", "down", "left", "right",          /* NSUpArrowfunctionKey    0xF700 */
+    "f1", "f2", "f3", "f4", "f5",           /* NSF1FunctionKey         0xF704 */
+    "f6", "f7", "f8", "f9", "f10",

I take it's not possible to use the existing keyboard IO stuff for
this? Like convert_ns_to_X_keysym?

+mac_parse_and_add_hotkey (Lisp_Object key, Lisp_Object lispfn, int hook)
 ^^^

We don't use mac_ in the NS port, we use ns_. I realise this code is
mac specific, but I'd rather not add a new function name prefix, not
least because it's possible it will interfere with the Mac port's
code, but also we may want to extend this functionality to GNUstep in
the future (although that seems unlikely right now).

It also means it's easy to see all the lisp functions that relate to
the NS port by doing something like M-x ns-<TAB>.

If you're copying functionality from the Mac port feel free to set up
an alias, but please use ns for the function names.

+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_command_modifier, Qcontrol))
+      )
      ^^^

I don't think we leave closing parens hanging like that, just stick it
on the end of the previous line, please.

       doc: /* Bind KEY combination as a global hotkey, and run HOOK upon
+invokation. This function assigns a hotkey that will run an elisp function
 ^^^^^^^^^^

invocation

+mac_bind_key (NSEventModifierFlags modifier, unsigned vkey,

You might want to call this something like ns_bind_global_hotkey so
it's clearer what it does, and a short descriptive comment wouldn't go
amiss.

+    hotkey_ids = [[NSMutableArray alloc] initWithCapacity: 1];

You're allocing this array, but not releasing it anywhere.

+  if (trusted) {
               ^^^

Opening brace should be on a new line. This function (mac_bind_key)
needs this fix in a few places.

+          if([[event.charactersIgnoringModifiers capitalizedString]
            ^^^

Missing space.

We could also do with some documentation.

One thing I'm unsure about is that we allow users to map the left and
right modifier keys separately but I think your code only allows for
matching with left key settings. Is that intentional? It would seem
unlikely that macOS would let us map global hotkeys to the left and
right keys separately, so I understand if it's not possible.
-- 
Alan Third





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-30  4:10       ` j
  2020-12-30 11:01         ` Alan Third
@ 2020-12-30 17:08         ` Eli Zaretskii
  2021-01-04  5:13           ` j
  1 sibling, 1 reply; 19+ messages in thread
From: Eli Zaretskii @ 2020-12-30 17:08 UTC (permalink / raw)
  To: j; +Cc: 44973, larsi

> From: j@mremus.net
> Date: Tue, 29 Dec 2020 20:10:00 -0800
> Cc: 44973@debbugs.gnu.org
> 
> Here is the patch to bind a global hotkey in mac. As long as Emacs is set as
> "trusted" in macOS preferences, the user can bind a two-key hotkey of the
> form [modifier-key] or a single-key modifier [function key], for example
> (mac-bind-global-hotkey [f1] 'tetris). Binding a three-key
> combo is left to a future patch. 
> 
> The code is copied from w32-register-hot-key as much as possible.

Hmm...  w32-register-hot-key is not for binding Emacs commands to
platform-specific keys, it is so that the OS doesn't catch some key
combinations ahead of Emacs.  That is, by using w32-register-hot-key
you make the key combination available for binding to a command using
global-set-key and the likes.

By contrast, it sounds like your patch is for letting users bind
platform-specific key sequences to Emacs commands, which is something
quite different.  What is the rationale for adding this functionality
to Emacs?





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2020-12-30 17:08         ` Eli Zaretskii
@ 2021-01-04  5:13           ` j
  2021-01-04 17:09             ` Eli Zaretskii
  0 siblings, 1 reply; 19+ messages in thread
From: j @ 2021-01-04  5:13 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 44973, Lars Ingebrigtsen


[-- Attachment #1.1: Type: text/plain, Size: 2472 bytes --]

Hi Eli, Alan,

Here's a new patch incorporating all of Alan's feedback, with two notes:
1. This implementation is unable to differentiate between left and right
   modifier keys, because it depends upon NSEventModifierFlags, which
doesn't
   differentiate between left and right.
2. hotkey_ids is a global variable, so it should not be released.

To address Eli's feedback, this patch is inspired by
https://www.mattduck.com/emacs-fuzzy-launcher.html , where Matt uses emacs
to
create a launcher like Spotlight or Alfred. Since he's using GNU/Linux and
i3 wm, he
simply assigned a global hotkey in his window manager config to run
emacsclient
and run the launcher. I believe this is also easy to do in Windows.

In macOS this is much more difficult, because assigning a global hotkey can
only
be done in code. So a user would have to rely on a utility like skhd or
Hammerspoon.

My proposal is to add this patch to emacs, so that a user can assign a
global
hotkey in emacs without relying on a 3rd-party program.

I used w32-register-hot-key as an example of another instance where emacs
added
an OS-specific feature in order to provide a consistent user experience
across
platforms. Also, even though the behavior is totally different, the actual
implementation of that routine and mine are very similar.

Thanks for all of your time and feedback.

On Wed, Dec 30, 2020 at 9:08 AM Eli Zaretskii <eliz@gnu.org> wrote:

> > From: j@mremus.net
> > Date: Tue, 29 Dec 2020 20:10:00 -0800
> > Cc: 44973@debbugs.gnu.org
> >
> > Here is the patch to bind a global hotkey in mac. As long as Emacs is
> set as
> > "trusted" in macOS preferences, the user can bind a two-key hotkey of the
> > form [modifier-key] or a single-key modifier [function key], for example
> > (mac-bind-global-hotkey [f1] 'tetris). Binding a three-key
> > combo is left to a future patch.
> >
> > The code is copied from w32-register-hot-key as much as possible.
>
> Hmm...  w32-register-hot-key is not for binding Emacs commands to
> platform-specific keys, it is so that the OS doesn't catch some key
> combinations ahead of Emacs.  That is, by using w32-register-hot-key
> you make the key combination available for binding to a command using
> global-set-key and the likes.
>
> By contrast, it sounds like your patch is for letting users bind
> platform-specific key sequences to Emacs commands, which is something
> quite different.  What is the rationale for adding this functionality
> to Emacs?
>

[-- Attachment #1.2: Type: text/html, Size: 3421 bytes --]

[-- Attachment #2: ns_bind_global_hotkey.diff --]
[-- Type: application/octet-stream, Size: 17101 bytes --]

diff --git a/lisp/loadup.el b/lisp/loadup.el
index 568b9fe40d..a83b5b9bae 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -1,6 +1,6 @@
 ;;; loadup.el --- load up standardly loaded Lisp files for Emacs
 
-;; Copyright (C) 1985-1986, 1992, 1994, 2001-2020 Free Software
+;; Copyright (C) 1985-1986, 1992, 1994, 2001-2021 Free Software
 ;; Foundation, Inc.
 
 ;; Maintainer: emacs-devel@gnu.org
@@ -335,6 +335,7 @@
       (when (featurep 'charprop)
         (load "international/mule-util")
         (load "international/ucs-normalize")
+        (load "ns-fns")
         (load "term/ns-win"))))
 (if (fboundp 'x-create-frame)
     ;; Do it after loading term/foo-win.el since the value of the
diff --git a/lisp/ns-fns.el b/lisp/ns-fns.el
new file mode 100644
index 0000000000..cf7358a083
--- /dev/null
+++ b/lisp/ns-fns.el
@@ -0,0 +1,40 @@
+;;; ns-fns.el --- Lisp routines for NeXT/Open/GNUstep/macOS window system  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+
+;; Keywords: internal
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;
+;;; Commentary:
+
+
+;;; Code:
+(defgroup ns nil
+  "GNUstep/macOS specific features."
+  :group 'environment)
+
+(defun ns-handle-global-hotkey (event)
+  "Handles global hotkey presses, running EVENT.
+Not intended to be called directly"
+  (interactive "e")
+
+  (apply (cdr event)))
+
+(provide 'ns-fns)
+;;; ns-fns.el ends here
diff --git a/src/keyboard.c b/src/keyboard.c
index 2e0143379a..30691552b6 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1,6 +1,6 @@
 /* Keyboard and mouse input; editor command loop.
 
-Copyright (C) 1985-1989, 1993-1997, 1999-2020 Free Software Foundation,
+Copyright (C) 1985-1989, 1993-1997, 1999-2021 Free Software Foundation,
 Inc.
 
 This file is part of GNU Emacs.
@@ -4868,7 +4868,7 @@ #define FUNCTION_KEY_OFFSET 0xff00
 
 /* You'll notice that this table is arranged to be conveniently
    indexed by X Windows keysym values.  */
-static const char *const lispy_function_keys[] =
+const char *const lispy_function_keys[] =
   {
     /* X Keysym value */
 
@@ -6010,6 +6010,11 @@ make_lispy_event (struct input_event *event)
 	return list2 (res, list2 (event->frame_or_window, location));
       }
 
+#ifdef NS_IMPL_COCOA
+      case GLOBAL_HOTKEY_EVENT:
+          return list2 (Qns_global_hotkey, event->arg);
+#endif
+
     case USER_SIGNAL_EVENT:
       /* A user signal.  */
       {
@@ -11759,6 +11764,10 @@ syms_of_keyboard (void)
   DEFSYM (Qcommand_execute, "command-execute");
   DEFSYM (Qinternal_echo_keystrokes_prefix, "internal-echo-keystrokes-prefix");
 
+#ifdef NS_IMPL_COCOA
+  DEFSYM (Qns_global_hotkey, "ns-global-hotkey");
+#endif
+
   accent_key_syms = Qnil;
   staticpro (&accent_key_syms);
 
@@ -12459,6 +12468,11 @@ keys_of_keyboard (void)
 
   initial_define_lispy_key (Vspecial_event_map, "delete-frame",
 			    "handle-delete-frame");
+
+#ifdef NS_IMPL_COCOA
+  initial_define_lispy_key (Vspecial_event_map, "ns-global-hotkey",
+                            "ns-handle-global-hotkey");
+#endif
 #ifdef HAVE_NTGUI
   initial_define_lispy_key (Vspecial_event_map, "end-session",
 			    "kill-emacs");
diff --git a/src/keyboard.h b/src/keyboard.h
index 41da3a6bf4..b859eb34c8 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -1,5 +1,5 @@
 /* Declarations useful when processing input.
-   Copyright (C) 1985-1987, 1993, 2001-2020 Free Software Foundation,
+   Copyright (C) 1985-1987, 1993, 2001-2021 Free Software Foundation,
    Inc.
 
 This file is part of GNU Emacs.
@@ -495,6 +495,9 @@ kbd_buffer_store_event_hold (struct input_event *event,
 #ifdef HAVE_NTGUI
 extern const char *const lispy_function_keys[];
 #endif
+#ifdef NS_IMPL_COCOA
+extern const char *const lispy_function_keys[];
+#endif
 
 extern char const DEV_TTY[];
 
diff --git a/src/nsfns.m b/src/nsfns.m
index c7956497c4..d361e76f07 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -1,6 +1,6 @@
 /* Functions for the NeXT/Open/GNUstep and macOS window system.
 
-Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2020 Free Software
+Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2021 Free Software
 Foundation, Inc.
 
 This file is part of GNU Emacs.
@@ -67,6 +67,112 @@ Updated by Christian Limpach (chris@nice.ch)
 
    ========================================================================== */
 
+#ifdef NS_IMPL_COCOA
+
+/* Convert a hotkey of the form [M-key] into a form that the OS can recognize.
+   If successful, call ns_bind_global_hotkey to register the key combination
+   with the OS, and set the callback to trigger an emacs event.
+   */
+static Lisp_Object
+ns_parse_and_add_hotkey (Lisp_Object key, Lisp_Object lispfn)
+{
+  register Lisp_Object c;
+  unsigned vk_code = 0;
+  int lisp_modifiers = 0;
+  NSEventModifierFlags ns_modifiers = 0;
+  Lisp_Object res = Qnil;
+  char* vkname;
+
+  CHECK_VECTOR (key);
+
+  if (ASIZE (key) != 1)
+    return Qnil;
+
+  c = AREF (key, 0);
+
+  if (CONSP (c) && lucid_event_type_list_p (c))
+    c = Fevent_convert_list (c);
+
+  if (! FIXNUMP (c) && ! SYMBOLP (c))
+    error ("Key definition is invalid");
+
+  if (! FUNCTIONP(lispfn))
+    error ("HOOK argument is not a function");
+
+  /* Work out the base key and the modifiers.  */
+  if (SYMBOLP (c))
+    {
+      c = parse_modifiers (c);
+      lisp_modifiers = XFIXNUM (Fcar (Fcdr (c)));
+      c = Fcar (c);
+      if (!SYMBOLP (c))
+        emacs_abort ();
+
+      vkname = SSDATA (SYMBOL_NAME (c));
+      if (vkname[0] == 0)
+        error("Key definition is invalid");
+      else
+        vk_code = ns_lookup_vk_code (vkname);
+    }
+  else if (FIXNUMP (c))
+    {
+      lisp_modifiers = XFIXNUM (c) & ~CHARACTERBITS;
+      /* Many ascii characters are their own virtual key code.  */
+      vk_code = XFIXNUM (c) & CHARACTERBITS;
+    }
+
+  if (vk_code < 0 || vk_code > 0xF747)
+    return Qnil;
+  else if ((vk_code >= 0xF700) && (vk_code <= 0xF747))
+    ns_modifiers = ns_modifiers | NSEventModifierFlagFunction;
+
+  /* Bind key combinations based on modifier mappings.  */
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_command_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_command_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_command_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_command_modifier, Qcontrol)))
+    {
+      ns_modifiers = ns_modifiers | NSEventModifierFlagCommand;
+    }
+
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_alternate_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_alternate_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_alternate_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_alternate_modifier, Qcontrol))
+      )
+    {
+      ns_modifiers = ns_modifiers | NSEventModifierFlagOption;
+    }
+
+  if (((lisp_modifiers & hyper_modifier)
+       && EQ (ns_control_modifier, Qhyper))
+      || ((lisp_modifiers & super_modifier)
+          && EQ (ns_control_modifier, Qsuper))
+      || ((lisp_modifiers & meta_modifier)
+          && EQ (ns_control_modifier, Qmeta))
+      || ((lisp_modifiers & ctrl_modifier)
+          && EQ (ns_control_modifier, Qcontrol))
+      )
+    {
+      ns_modifiers = ns_modifiers | NSEventModifierFlagControl;
+    }
+
+  // Bind function key
+  if ((ns_modifiers & NSEventModifierFlagDeviceIndependentFlagsMask) > 0)
+    ns_bind_global_hotkey(ns_modifiers, vk_code, lispfn);
+
+  return key;
+}
+#endif
+
 /* Let the user specify a Nextstep display with a Lisp object.
    OBJECT may be nil, a frame or a terminal object.
    nil stands for the selected frame--or, if that is not a Nextstep frame,
@@ -2984,6 +3090,66 @@ The position is returned as a cons cell (X . Y) of the
   return Qnil;
 }
 
+#ifdef NS_IMPL_COCOA
+static Lisp_Object ns_registered_hotkeys;
+
+DEFUN ("ns-bind-global-hotkey",
+       Fns_bind_global_hotkey,
+       Sns_bind_global_hotkey, 2, 2, 0,
+       doc: /* Bind KEY combination as a global hotkey, and run HOOK upon invocation.
+Assigns a hotkey that will run an elisp function from anywhere in MacOS
+outside Emacs.
+
+KEY must be either a two-key vector of the form [mod-key] or a single
+function key vector [fn]. For Example:
+
+  (ns-bind-global-hotkey [M-1] 'tetris)
+  (ns-bind-global-hotkey [f1]  'make-frame)
+
+HOOK must be a function.
+
+The return value is t if registering the hotkey was successful, otherwise nil.
+*/)
+ (Lisp_Object key, Lisp_Object hook)
+{
+  id handler;
+
+  key = ns_parse_and_add_hotkey (key, hook);
+
+  if (!NILP (key) && NILP (Fmemq (Fcons(key,hook), ns_registered_hotkeys)))
+    {
+      Lisp_Object item = Fmemq (Qnil, ns_registered_hotkeys);
+
+      if (NILP (item))
+        ns_registered_hotkeys = Fcons (Fcons(key,hook), ns_registered_hotkeys);
+      else
+        XSETCAR (item, Fcons(key,hook));
+    }
+
+  return key;
+}
+
+DEFUN ("ns-show-bound-hotkeys", Fns_show_bound_hotkeys,
+       Sns_show_bound_hotkeys, 0, 0, 0,
+       doc: /* Return list of registered hotkey vectors and functions  */)
+  (void)
+{
+  return ns_registered_hotkeys;
+}
+
+DEFUN ("ns-clear-hotkeys", Fns_clear_hotkeys,
+       Sns_clear_hotkeys, 0, 0, 0,
+       doc: /* Unbind all global hotkeys */)
+  (void)
+{
+  ns_registered_hotkeys = Qnil;
+
+  ns_clear_hotkeys();
+
+  return Qt;
+}
+#endif
+
 /* ==========================================================================
 
     Class implementations
@@ -3136,6 +3302,11 @@ - (Lisp_Object)lispString
   defsubr (&Sns_set_mouse_absolute_pixel_position);
   defsubr (&Sns_mouse_absolute_pixel_position);
   defsubr (&Sns_show_character_palette);
+#ifdef NS_IMPL_COCOA
+  defsubr (&Sns_bind_global_hotkey);
+  defsubr (&Sns_show_bound_hotkeys);
+  defsubr (&Sns_clear_hotkeys);
+#endif
   defsubr (&Sx_display_mm_width);
   defsubr (&Sx_display_mm_height);
   defsubr (&Sx_display_screens);
@@ -3160,6 +3331,10 @@ - (Lisp_Object)lispString
   defsubr (&Sx_show_tip);
   defsubr (&Sx_hide_tip);
 
+#ifdef NS_IMPL_COCOA
+  staticpro (&ns_registered_hotkeys);
+  ns_registered_hotkeys = Qnil;
+#endif
   as_status = 0;
   as_script = Qnil;
   staticpro (&as_script);
diff --git a/src/nsterm.h b/src/nsterm.h
index f292993d8f..2f9198753c 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -1,5 +1,5 @@
 /* Definitions and headers for communication with NeXT/Open/GNUstep API.
-   Copyright (C) 1989, 1993, 2005, 2008-2020 Free Software Foundation,
+   Copyright (C) 1989, 1993, 2005, 2008-2021 Free Software Foundation,
    Inc.
 
 This file is part of GNU Emacs.
@@ -379,6 +379,7 @@ #define NS_DRAW_TO_BUFFER 1
 #ifdef NS_IMPL_COCOA
   BOOL shouldKeepRunning;
   BOOL isFirst;
+  NSStatusItem *theItem;
 #endif
 #ifdef NS_IMPL_GNUSTEP
   BOOL applicationDidFinishLaunchingCalled;
@@ -1230,6 +1231,12 @@ #define NSAPP_DATA2_RUNFILEDIALOG 11
 extern void ns_init_events (struct input_event *);
 extern void ns_finish_events (void);
 
+#ifdef NS_IMPL_COCOA
+extern unsigned ns_lookup_vk_code (char *key);
+typedef enum NSEventModifierFlags NSEventModifierFlags;
+extern void ns_bind_global_hotkey (enum NSEventModifierFlags modifier, unsigned vkey, Lisp_Object lispfn);
+extern void ns_clear_hotkeys (void);
+#endif
 
 #ifdef NS_IMPL_GNUSTEP
 extern char gnustep_base_version[];  /* version tracking */
diff --git a/src/nsterm.m b/src/nsterm.m
index fa38350a2f..03a81ecb0a 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1,6 +1,6 @@
 /* NeXT/Open/GNUstep / macOS communication module.      -*- coding: utf-8 -*-
 
-Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2020 Free Software
+Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2021 Free Software
 Foundation, Inc.
 
 This file is part of GNU Emacs.
@@ -9721,6 +9721,106 @@ Convert an X font name (XLFD) to an NS font name.
   return ret;
 }
 
+#ifdef NS_IMPL_COCOA
+
+/* Lookup virtual keycode from string representing the name of a
+   non-ascii keystroke into the corresponding virtual key, using
+   lispy_function_keys.  */
+/* Adapted from w32fns.c */
+unsigned
+ns_lookup_vk_code (char *key)
+{
+  unsigned i;
+  const unsigned last_keysym = ARRAYELTS (convert_ns_to_X_keysym);
+  unsigned keysym;
+
+  // adapted from ns_convert_key
+  for (i = 0; i < 256; i++)
+    if (lispy_function_keys[i]
+        && strcmp (lispy_function_keys[i], key) == 0)
+      for (keysym = 1; keysym < last_keysym; keysym += 2)
+        if (i == convert_ns_to_X_keysym[keysym])
+          return convert_ns_to_X_keysym[keysym-1];
+
+  /* Alphanumerics map to themselves.  */
+  if (key[1] == 0)
+    {
+      if ((key[0] >= 'A' && key[0] <= 'Z')
+          || (key[0] >= '0' && key[0] <= '9'))
+        return key[0];
+      if (key[0] >= 'a' && key[0] <= 'z')
+        return toupper(key[0]);
+    }
+
+  return -1;
+}
+
+// Store event handler event ids.
+NSMutableArray *hotkey_ids;
+
+/* ns_bind_global_hotkey registers the key combo with the OS, and when the
+   key combination is pressed, sends a GLOBAL_HOTKEY_EVENT.
+   After registering the hotkey, the hotkey and function are added to
+   hotkey_ids.
+   The key combo either be a two-key combo, beginining with one of
+   NSEventModifierFlags, or a single function key.
+   If Emacs is not set as "trusted" in Privacy settings, return an error.
+   */
+void
+ns_bind_global_hotkey (NSEventModifierFlags modifier, unsigned vkey,
+                       Lisp_Object lispfn)
+{
+  id handler;
+
+  // Check if Emacs is trusted.
+  NSDictionary *options = @{(__bridge id) kAXTrustedCheckOptionPrompt: @YES};
+  Boolean trusted = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
+
+  if (hotkey_ids == nil)
+    hotkey_ids = [[NSMutableArray alloc] initWithCapacity: 1];
+
+  if (trusted)
+    {
+      handler = [NSEvent
+                  addGlobalMonitorForEventsMatchingMask: (NSEventMaskKeyDown)
+                                                handler:^(NSEvent *event)
+                  {
+                    if ((event.modifierFlags &
+                         NSEventModifierFlagDeviceIndependentFlagsMask) == modifier)
+                      {
+                        if ([[event.charactersIgnoringModifiers
+                                   capitalizedString]
+                              characterAtIndex:0] == vkey)
+                          {
+                            struct frame *emacsframe = SELECTED_FRAME ();
+                            NSEvent *theEvent = [NSApp currentEvent];
+
+                            emacs_event->kind = GLOBAL_HOTKEY_EVENT;
+                            emacs_event->arg = lispfn;
+                            EV_TRAILER(theEvent);
+                          }
+                      }
+                  }];
+
+      [hotkey_ids addObject: handler];
+    }
+  else
+    error("Emacs app isn't trusted. Enable in system settings.");
+}
+
+/* Clear list of global hotkeys. Remove them with the OS and clear them
+   within Emacs. */
+void
+ns_clear_hotkeys (void)
+{
+
+  for (id hotkey in hotkey_ids) {
+    [NSEvent removeMonitor: hotkey];
+  }
+
+  [hotkey_ids removeAllObjects];
+}
+#endif
 
 void
 syms_of_nsterm (void)
@@ -9935,6 +10035,7 @@ Nil means use fullscreen the old (< 10.7) way.  The old way works better with
   DEFSYM (QCmouse, ":mouse");
 
 #ifdef NS_IMPL_COCOA
+  DEFSYM (Qglobal_hotkey_hook, "global-hotkey-hook");
   Fprovide (Qcocoa, Qnil);
   syms_of_macfont ();
 #else
diff --git a/src/termhooks.h b/src/termhooks.h
index d18b750c3a..db50a89f6d 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -1,6 +1,6 @@
 /* Parameters and display hooks for terminal devices.
 
-Copyright (C) 1985-1986, 1993-1994, 2001-2020 Free Software Foundation,
+Copyright (C) 1985-1986, 1993-1994, 2001-2021 Free Software Foundation,
 Inc.
 
 This file is part of GNU Emacs.
@@ -188,6 +188,11 @@ #define EMACS_TERMHOOKS_H
   USER_SIGNAL_EVENT,		/* A user signal.
                                    code is a number identifying it,
                                    index into lispy_user_signals.  */
+#ifdef NS_IMPL_COCOA
+  GLOBAL_HOTKEY_EVENT,          /* An event for when a hotkey is pressed
+                                   outside of emacs that is setup to execute
+                                   an elisp function.  */
+#endif
 
   /* Help events.  Member `frame_or_window' of the input_event is the
      frame on which the event occurred, and member `arg' contains

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-04  5:13           ` j
@ 2021-01-04 17:09             ` Eli Zaretskii
  2021-01-05  8:25               ` Lars Ingebrigtsen
  2021-01-06  5:02               ` Richard Stallman
  0 siblings, 2 replies; 19+ messages in thread
From: Eli Zaretskii @ 2021-01-04 17:09 UTC (permalink / raw)
  To: j; +Cc: 44973, larsi

> From: j@mremus.net
> Date: Sun, 3 Jan 2021 21:13:47 -0800
> Cc: Lars Ingebrigtsen <larsi@gnus.org>, 44973@debbugs.gnu.org
> 
> I used w32-register-hot-key as an example of another instance where emacs added
> an OS-specific feature in order to provide a consistent user experience across
> platforms.

w32-register-hot-key allows a consistent UX across platforms
_inside_Emacs_.  Your changes, AFAIU, aims at using Emacs as a way to
work around problems in the OS WM, not in Emacs running on that OS.

So I'm still not sure we should install this.

Lars, do you have an opinion on this?





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-04 17:09             ` Eli Zaretskii
@ 2021-01-05  8:25               ` Lars Ingebrigtsen
  2021-01-05  8:26                 ` Lars Ingebrigtsen
                                   ` (2 more replies)
  2021-01-06  5:02               ` Richard Stallman
  1 sibling, 3 replies; 19+ messages in thread
From: Lars Ingebrigtsen @ 2021-01-05  8:25 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 44973, j

Eli Zaretskii <eliz@gnu.org> writes:

> w32-register-hot-key allows a consistent UX across platforms
> _inside_Emacs_.  Your changes, AFAIU, aims at using Emacs as a way to
> work around problems in the OS WM, not in Emacs running on that OS.
>
> So I'm still not sure we should install this.
>
> Lars, do you have an opinion on this?

Since this brings up the Macos version of Emacs into feature parity with
what you can do in GNU/Linux and Windows, I don't have any objections to
including it.  

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-05  8:25               ` Lars Ingebrigtsen
@ 2021-01-05  8:26                 ` Lars Ingebrigtsen
  2021-01-05 15:04                   ` Eli Zaretskii
  2021-01-05 15:04                 ` Eli Zaretskii
  2021-01-06  5:15                 ` Richard Stallman
  2 siblings, 1 reply; 19+ messages in thread
From: Lars Ingebrigtsen @ 2021-01-05  8:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 44973, j

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Since this brings up the Macos version of Emacs into feature parity with
> what you can do in GNU/Linux and Windows, I don't have any objections to
> including it.  

(There's still the issue of code blocks, though -- we're not accepting
those until gcc supports those, I think?)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-05  8:25               ` Lars Ingebrigtsen
  2021-01-05  8:26                 ` Lars Ingebrigtsen
@ 2021-01-05 15:04                 ` Eli Zaretskii
  2021-01-06  5:15                 ` Richard Stallman
  2 siblings, 0 replies; 19+ messages in thread
From: Eli Zaretskii @ 2021-01-05 15:04 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 44973, j

> From: Lars Ingebrigtsen <larsi@gnus.org>
> Cc: j@mremus.net,  44973@debbugs.gnu.org
> Date: Tue, 05 Jan 2021 09:25:24 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > w32-register-hot-key allows a consistent UX across platforms
> > _inside_Emacs_.  Your changes, AFAIU, aims at using Emacs as a way to
> > work around problems in the OS WM, not in Emacs running on that OS.
> >
> > So I'm still not sure we should install this.
> >
> > Lars, do you have an opinion on this?
> 
> Since this brings up the Macos version of Emacs into feature parity with
> what you can do in GNU/Linux and Windows, I don't have any objections to
> including it.  

Then I guess we can install it (once the other problem is solved).





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-05  8:26                 ` Lars Ingebrigtsen
@ 2021-01-05 15:04                   ` Eli Zaretskii
  0 siblings, 0 replies; 19+ messages in thread
From: Eli Zaretskii @ 2021-01-05 15:04 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 44973, j

> From: Lars Ingebrigtsen <larsi@gnus.org>
> Cc: j@mremus.net,  44973@debbugs.gnu.org
> Date: Tue, 05 Jan 2021 09:26:56 +0100
> 
> Lars Ingebrigtsen <larsi@gnus.org> writes:
> 
> > Since this brings up the Macos version of Emacs into feature parity with
> > what you can do in GNU/Linux and Windows, I don't have any objections to
> > including it.  
> 
> (There's still the issue of code blocks, though -- we're not accepting
> those until gcc supports those, I think?)

Right.





^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-04 17:09             ` Eli Zaretskii
  2021-01-05  8:25               ` Lars Ingebrigtsen
@ 2021-01-06  5:02               ` Richard Stallman
  1 sibling, 0 replies; 19+ messages in thread
From: Richard Stallman @ 2021-01-06  5:02 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 44973, larsi, j

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > w32-register-hot-key allows a consistent UX across platforms
  > _inside_Emacs_.  Your changes, AFAIU, aims at using Emacs as a way to
  > work around problems in the OS WM, not in Emacs running on that OS.

That sure sounds like a MacOS-specific feature.  If so, we should
not include it in a GNU program.

-- 
Dr Richard Stallman
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)







^ permalink raw reply	[flat|nested] 19+ messages in thread

* bug#44973: Add a macOS global hotkey function
  2021-01-05  8:25               ` Lars Ingebrigtsen
  2021-01-05  8:26                 ` Lars Ingebrigtsen
  2021-01-05 15:04                 ` Eli Zaretskii
@ 2021-01-06  5:15                 ` Richard Stallman
  2 siblings, 0 replies; 19+ messages in thread
From: Richard Stallman @ 2021-01-06  5:15 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 44973, j

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

You and Eli gave two analyses of the nature of this change:

Eli:

  > w32-register-hot-key allows a consistent UX across platforms
  > _inside_Emacs_.  Your changes, AFAIU, aims at using Emacs as a way to
  > work around problems in the OS WM, not in Emacs running on that OS.

Lars:

  > Since this brings up the Macos version of Emacs into feature parity with
  > what you can do in GNU/Linux and Windows,

If Lars is right, it is ok to install.  If Eli is right, it should not
be installed.

So it's a matter of which of those two is right.  I have no idea.
But you can compare notes and figure out.

Is it possible that, in some way, both are right?  If so, this is a
strange situation and worth deeper study.


-- 
Dr Richard Stallman
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)







^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2021-01-06  5:15 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-30 21:01 bug#44973: Add a macOS global hotkey function j
2020-12-08 20:39 ` Lars Ingebrigtsen
2020-12-21  0:40   ` j
2020-12-21  4:34     ` Lars Ingebrigtsen
2020-12-30  4:10       ` j
2020-12-30 11:01         ` Alan Third
2020-12-30 17:08         ` Eli Zaretskii
2021-01-04  5:13           ` j
2021-01-04 17:09             ` Eli Zaretskii
2021-01-05  8:25               ` Lars Ingebrigtsen
2021-01-05  8:26                 ` Lars Ingebrigtsen
2021-01-05 15:04                   ` Eli Zaretskii
2021-01-05 15:04                 ` Eli Zaretskii
2021-01-06  5:15                 ` Richard Stallman
2021-01-06  5:02               ` Richard Stallman
2020-12-21  8:12     ` Alan Third
2020-12-21 16:18       ` Eli Zaretskii
2020-12-21 16:34         ` Alan Third
2020-12-22  5:20           ` Richard Stallman

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).