all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: grischka <grishka@gmx.de>
To: Daniel Colascione <dan.colascione@gmail.com>
Cc: emacs-devel@gnu.org
Subject: Re: [PATCH] system-type cygwin with window-system w32
Date: Mon, 18 Jul 2011 23:01:43 +0200	[thread overview]
Message-ID: <4E249F37.3050101@gmx.de> (raw)
In-Reply-To: <4E24855D.5030607@gmail.com>

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

Daniel Colascione wrote:
>> Actually the NT build works quite well with one single thread.
>> Already tested here.
> 
> Do you have a patch handy?

Attached.  Patch was against state at commit:

       Jan D. <jan.h.d@swipnet.se>  2011-02-01 09:53:03
       Use add/delete_read_fd in xsmfns to simplify.  Also ...

Hopefully still applies cleanly.

It is minimal, and maybe not quite obvious.  Basically the two window
message switches (w32fns.c and w32term.c) could be merged into one.
The patch calls the one from the other.

There is an elisp variable 'w32-single-thread' which should return 1
if you want to make sure you're using the right build. :)

Note that you can for example drag the "Open File" dialog across the
main window and it still gets redrawn.

>> * peek for messages in the QUIT macro (say via ELSE_PENDING_SIGNALS)
>>   which is for C-g to interrupt lisp.
> 
> I don't think this approach alone would be sufficient.  I may be wrong,
> but I think the Windows window manager will consider a program to be
> "unresponsive" if it stops actually pumping messages; I don't think
> peeking is sufficient.  Also, [1] says that we shouldn't delay pumping
> messages even if we're able to guarantee we'll get around to them later.
> (Running lisp code is indistinguishable from sleep in this case.)

> Using PeekMessage in lisp code instead of actually pumping messages
> would be like telling your credit card company, "Yeah, I got the bill,
> and I'll get around to responding sometime in the next two years".  I
> don't think it'd go over well.

"Two years" or anyway "indistinguishable from sleep" is never really
acceptable.  Because then emacs can't process key/mouse events
regardless how many threads you have in the backend.

We need to think in terms of UI timings here.  Reference is the kbd
repeat rate, say 30ms, which is also in the range of eye perception
capability and frame repeat rates.  Up to this is smooth.  More can
be tolerable though, even up to 1 or more seconds.  Say for popping
up new windows, frames, etc.

So if your elisp doesn't finish in 30ms, you know ;)

>> * break command_loop_1() such that it can be used to handle just one
>>   event which is to handle scrollbar messages because the widgets
>>   run their own message loop deep in windows.  Otherwise all the
>>   scrolling would happen only after you release the mouse button.
> 
> I doubt that the scrollbar is the only special case we'd need to consider.

AFAICS scrollbar and paint. And maybe clipboard rendering.  Emacs
handles paint directly via expose_frame() though, not via the event
queue.

> I don't understand how this approach helps.  The problem, AIUI, isn't
> that we have Lisp events, but that we read input and wait for processes
> in many places, and it's hard to be confident that each place we pump
> messages is a safe place to process lisp code.  I don't understand how
> draining the lisp event queue would help.

As I see it this is just recursion.   Such things happen all the time
in all GUIs that use callbacks.  Windows, GTK, whatever.  It happens
in emacs already:

       (run-with-idle-timer ... lisp-code)

--- grischka


[-- Attachment #2: gr-w32-singlethread.patch --]
[-- Type: text/plain, Size: 13052 bytes --]

diff --git a/nt/config.nt b/nt/config.nt
index d612a41..dd4cd38 100644
--- a/nt/config.nt
+++ b/nt/config.nt
@@ -467,6 +467,8 @@ extern char *getenv ();
 #endif
 #endif
 
+#define SINGLE_THREAD
+
 /* Redefine abort.  */
 #ifdef HAVE_NTGUI
 #define abort	w32_abort
diff --git a/src/keyboard.c b/src/keyboard.c
index d533213..e824682 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1290,7 +1290,7 @@ cancel_hourglass_unwind (Lisp_Object arg)
 EXFUN (Fwindow_system, 1);
 
 Lisp_Object
-command_loop_1 (void)
+command_loop_1a (int repeat)
 {
   Lisp_Object cmd;
   Lisp_Object keybuf[30];
@@ -1336,7 +1336,7 @@ command_loop_1 (void)
   if (!CONSP (last_command_event))
     current_kboard->Vlast_repeatable_command = real_this_command;
 
-  while (1)
+  while (repeat || detect_input_pending())
     {
       if (! FRAME_LIVE_P (XFRAME (selected_frame)))
 	Fkill_emacs (Qnil);
@@ -1659,6 +1659,12 @@ command_loop_1 (void)
     }
 }
 
+Lisp_Object
+command_loop_1 ()
+{
+    return command_loop_1a(1);
+}
+
 /* Adjust point to a boundary of a region that has such a property
    that should be treated intangible.  For the moment, we check
    `composition', `display' and `invisible' properties.
diff --git a/src/lisp.h b/src/lisp.h
index cfff42a..efe76f6 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -2013,6 +2013,10 @@ extern char *stack_bottom;
    This is a good thing to do around a loop that has no side effects
    and (in particular) cannot call arbitrary Lisp code.  */
 
+#if defined HAVE_NTGUI && defined SINGLE_THREAD
+#define ELSE_PENDING_SIGNALS else w32_peek_messages();
+void w32_peek_messages();
+#else
 #ifdef SYNC_INPUT
 extern void process_pending_signals (void);
 extern int pending_signals;
@@ -2022,6 +2026,7 @@ extern int pending_signals;
 #else  /* not SYNC_INPUT */
 #define ELSE_PENDING_SIGNALS
 #endif	/* not SYNC_INPUT */
+#endif	/* not HAVE_NTGUI */
 
 #define QUIT						\
   do {							\
diff --git a/src/w32fns.c b/src/w32fns.c
index b09bb0b..93f80f9 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -87,6 +87,10 @@ extern const char *const lispy_function_keys[];
 static unsigned hourglass_timer = 0;
 static HWND hourglass_hwnd = NULL;
 
+#ifdef SINGLE_THREAD
+int w32_single_thread;
+#endif
+
 #ifndef IDC_HAND
 #define IDC_HAND MAKEINTRESOURCE(32649)
 #endif
@@ -2242,17 +2246,15 @@ unregister_hot_keys (HWND hwnd)
 
 /* Main message dispatch loop. */
 
-static void
-w32_msg_pump (deferred_msg * msg_buf)
+unsigned dispatch_msg (MSG *pmsg)
 {
   MSG msg;
   int result;
   HWND focus_window;
 
-  msh_mousewheel = RegisterWindowMessage (MSH_MOUSEWHEEL);
+  result = 0;
+  msg = *pmsg;
 
-  while (GetMessage (&msg, NULL, 0, 0))
-    {
       if (msg.hwnd == NULL)
 	{
 	  switch (msg.message)
@@ -2269,8 +2271,10 @@ w32_msg_pump (deferred_msg * msg_buf)
                  and older versions will never be patched.  */
               CoInitialize (NULL);
 	      w32_createwindow ((struct frame *) msg.wParam);
+#ifndef SINGLE_THREAD
 	      if (!PostThreadMessage (dwMainThreadId, WM_EMACS_DONE, 0, 0))
 		abort ();
+#endif
 	      break;
 	    case WM_EMACS_SETLOCALE:
 	      SetThreadLocale (msg.wParam);
@@ -2278,9 +2282,11 @@ w32_msg_pump (deferred_msg * msg_buf)
 	      break;
 	    case WM_EMACS_SETKEYBOARDLAYOUT:
 	      result = (int) ActivateKeyboardLayout ((HKL) msg.wParam, 0);
+#ifndef SINGLE_THREAD
 	      if (!PostThreadMessage (dwMainThreadId, WM_EMACS_DONE,
 				      result, 0))
 		abort ();
+#endif
 	      break;
 	    case WM_EMACS_REGISTER_HOT_KEY:
 	      focus_window = GetFocus ();
@@ -2300,8 +2306,10 @@ w32_msg_pump (deferred_msg * msg_buf)
                  cell is never made into garbage and is not relocated by
                  GC.  */
 	      XSETCAR ((Lisp_Object) ((EMACS_INT) msg.lParam), Qnil);
+#ifndef SINGLE_THREAD
 	      if (!PostThreadMessage (dwMainThreadId, WM_EMACS_DONE, 0, 0))
 		abort ();
+#endif
 	      break;
 	    case WM_EMACS_TOGGLE_LOCK_KEY:
 	      {
@@ -2331,9 +2339,12 @@ w32_msg_pump (deferred_msg * msg_buf)
 				 KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
 		    cur_state = !cur_state;
 		  }
+#ifndef SINGLE_THREAD
 		if (!PostThreadMessage (dwMainThreadId, WM_EMACS_DONE,
 					cur_state, 0))
 		  abort ();
+#endif
+		result = cur_state;
 	      }
 	      break;
 #ifdef MSG_DEBUG
@@ -2349,11 +2360,37 @@ w32_msg_pump (deferred_msg * msg_buf)
 	  DispatchMessage (&msg);
 	}
 
+    return result;
+}
+
+static void
+w32_msg_pump (deferred_msg * msg_buf)
+{
+#ifndef SINGLE_THREAD
+  MSG msg;
+  msh_mousewheel = RegisterWindowMessage (MSH_MOUSEWHEEL);
+  while (GetMessage (&msg, NULL, 0, 0))
+    {
+      dispatch_msg(&msg);
       /* Exit nested loop when our deferred message has completed.  */
       if (msg_buf->completed)
 	break;
     }
+#endif
+}
+
+#ifdef SINGLE_THREAD
+unsigned do_thread_msg(unsigned threadid, unsigned umsg, WPARAM wParam, LPARAM lParam)
+{
+    MSG msg;
+    msg.hwnd = NULL;
+    msg.message = umsg;
+    msg.wParam = wParam;
+    msg.lParam = lParam;
+    return dispatch_msg(&msg);
 }
+#endif
+
 
 deferred_msg * deferred_msg_head;
 
@@ -2427,7 +2464,9 @@ complete_deferred_msg (HWND hwnd, UINT msg, LRESULT result)
   msg_buf->completed = 1;
 
   /* Ensure input thread is woken so it notices the completion.  */
+#ifndef SINGLE_THREAD
   PostThreadMessage (dwWindowsThreadId, WM_NULL, 0, 0);
+#endif
 }
 
 static void
@@ -2448,7 +2487,9 @@ cancel_all_deferred_msgs (void)
   /* leave_crit (); */
 
   /* Ensure input thread is woken so it notices the completion.  */
+#ifndef SINGLE_THREAD
   PostThreadMessage (dwWindowsThreadId, WM_NULL, 0, 0);
+#endif
 }
 
 DWORD WINAPI
@@ -2654,6 +2695,10 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 	       dedicated to one frame and does not bother checking
 	       that hwnd matches before combining them.  */
             my_post_msg (&wmsg, hwnd, WM_EMACS_PAINT, wParam, lParam);
+#ifdef SINGLE_THREAD
+            w32_read_socket(0, 0, NULL);
+            //command_loop_1a(0);
+#endif
 
             return 0;
           }
@@ -3205,6 +3250,10 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
   	{
 	  wmsg.dwModifiers = w32_get_modifiers ();
 	  my_post_msg (&wmsg, hwnd, msg, wParam, lParam);
+#ifdef SINGLE_THREAD
+          if (msg == WM_VSCROLL)
+            command_loop_1a(0);
+#endif
 	  return 0;
   	}
 
@@ -3808,11 +3857,15 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 static void
 my_create_window (struct frame * f)
 {
+#ifdef SINGLE_THREAD
+  do_thread_msg (dwWindowsThreadId, WM_EMACS_CREATEWINDOW, (WPARAM)f, 0);
+#else
   MSG msg;
 
   if (!PostThreadMessage (dwWindowsThreadId, WM_EMACS_CREATEWINDOW, (WPARAM)f, 0))
     abort ();
   GetMessage (&msg, NULL, WM_EMACS_DONE, WM_EMACS_DONE);
+#endif
 }
 
 
@@ -6308,6 +6361,15 @@ The return value is the hotkey-id if registered, otherwise nil.  */)
 
       /* Notify input thread about new hot-key definition, so that it
 	 takes effect without needing to switch focus.  */
+#ifdef SINGLE_THREAD
+#ifdef USE_LISP_UNION_TYPE
+      do_thread_msg (dwWindowsThreadId, WM_EMACS_REGISTER_HOT_KEY,
+                         (WPARAM) key.i, 0);
+#else
+      do_thread_msg (dwWindowsThreadId, WM_EMACS_REGISTER_HOT_KEY,
+                         (WPARAM) key, 0);
+#endif
+#else
 #ifdef USE_LISP_UNION_TYPE
       PostThreadMessage (dwWindowsThreadId, WM_EMACS_REGISTER_HOT_KEY,
 			 (WPARAM) key.i, 0);
@@ -6315,6 +6377,7 @@ The return value is the hotkey-id if registered, otherwise nil.  */)
       PostThreadMessage (dwWindowsThreadId, WM_EMACS_REGISTER_HOT_KEY,
 			 (WPARAM) key, 0);
 #endif
+#endif
     }
 
   return key;
@@ -6336,6 +6399,16 @@ DEFUN ("w32-unregister-hot-key", Fw32_unregister_hot_key,
     {
       /* Notify input thread about hot-key definition being removed, so
 	 that it takes effect without needing focus switch.  */
+#ifdef SINGLE_THREAD
+#ifdef USE_LISP_UNION_TYPE
+      do_thread_msg (dwWindowsThreadId, WM_EMACS_UNREGISTER_HOT_KEY,
+                             (WPARAM) XINT (XCAR (item)), (LPARAM) item.i);
+#else
+      do_thread_msg (dwWindowsThreadId, WM_EMACS_UNREGISTER_HOT_KEY,
+                             (WPARAM) XINT (XCAR (item)), (LPARAM) item);
+
+#endif
+#else
 #ifdef USE_LISP_UNION_TYPE
       if (PostThreadMessage (dwWindowsThreadId, WM_EMACS_UNREGISTER_HOT_KEY,
 			     (WPARAM) XINT (XCAR (item)), (LPARAM) item.i))
@@ -6347,6 +6420,7 @@ DEFUN ("w32-unregister-hot-key", Fw32_unregister_hot_key,
 	  MSG msg;
 	  GetMessage (&msg, NULL, WM_EMACS_DONE, WM_EMACS_DONE);
 	}
+#endif
       return Qt;
     }
   return Qnil;
@@ -6401,6 +6475,9 @@ is set to off if the low bit of NEW-STATE is zero, otherwise on.  */)
   (Lisp_Object key, Lisp_Object new_state)
 {
   int vk_code;
+#ifdef SINGLE_THREAD
+  unsigned ret;
+#endif
 
   if (EQ (key, intern ("capslock")))
     vk_code = VK_CAPITAL;
@@ -6414,6 +6491,16 @@ is set to off if the low bit of NEW-STATE is zero, otherwise on.  */)
   if (!dwWindowsThreadId)
     return make_number (w32_console_toggle_lock_key (vk_code, new_state));
 
+#ifdef SINGLE_THREAD
+#ifdef USE_LISP_UNION_TYPE
+  ret = do_thread_msg (dwWindowsThreadId, WM_EMACS_TOGGLE_LOCK_KEY,
+                         (WPARAM) vk_code, (LPARAM) new_state.i);
+#else
+  ret = do_thread_msg (dwWindowsThreadId, WM_EMACS_TOGGLE_LOCK_KEY,
+                         (WPARAM) vk_code, (LPARAM) new_state);
+#endif
+  return make_number (ret);
+#else
 #ifdef USE_LISP_UNION_TYPE
   if (PostThreadMessage (dwWindowsThreadId, WM_EMACS_TOGGLE_LOCK_KEY,
 			 (WPARAM) vk_code, (LPARAM) new_state.i))
@@ -6427,6 +6514,7 @@ is set to off if the low bit of NEW-STATE is zero, otherwise on.  */)
       return make_number (msg.wParam);
     }
   return Qnil;
+#endif
 }
 
 DEFUN ("w32-window-exists-p", Fw32_window_exists_p, Sw32_window_exists_p,
@@ -7053,6 +7141,14 @@ Set this to nil to get the old behavior for repainting; this should
 only be necessary if the default setting causes problems.  */);
   w32_strict_painting = 1;
 
+#ifdef SINGLE_THREAD
+  DEFVAR_BOOL ("w32-single-thread",
+	       &w32_single_thread,
+	       doc: /* using single thread */);
+
+  w32_single_thread = 1;
+#endif
+
 #if 0 /* TODO: Port to W32 */
   defsubr (&Sx_change_window_property);
   defsubr (&Sx_delete_window_property);
diff --git a/src/w32proc.c b/src/w32proc.c
index 1c009c7..49eab27 100644
--- a/src/w32proc.c
+++ b/src/w32proc.c
@@ -2028,7 +2028,11 @@ If successful, the new locale id is returned, otherwise nil.  */)
   /* Need to set input thread locale if present.  */
   if (dwWindowsThreadId)
     /* Reply is not needed.  */
+#ifdef SINGLE_THREAD
+    do_thread_msg (dwWindowsThreadId, WM_EMACS_SETLOCALE, XINT (lcid), 0);
+#else
     PostThreadMessage (dwWindowsThreadId, WM_EMACS_SETLOCALE, XINT (lcid), 0);
+#endif
 
   return make_number (GetThreadLocale ());
 }
@@ -2194,6 +2198,10 @@ If successful, the new layout id is returned, otherwise nil.  */)
   /* Synchronize layout with input thread.  */
   if (dwWindowsThreadId)
     {
+#ifdef SINGLE_THREAD
+      if (0 == do_thread_msg (dwWindowsThreadId, WM_EMACS_SETKEYBOARDLAYOUT, (WPARAM) kl, 0))
+        return Qnil;
+#else
       if (PostThreadMessage (dwWindowsThreadId, WM_EMACS_SETKEYBOARDLAYOUT,
 			     (WPARAM) kl, 0))
 	{
@@ -2203,6 +2211,7 @@ If successful, the new layout id is returned, otherwise nil.  */)
 	  if (msg.wParam == 0)
 	    return Qnil;
 	}
+#endif
     }
   else if (!ActivateKeyboardLayout ((HKL) kl, 0))
     return Qnil;
diff --git a/src/w32term.c b/src/w32term.c
index cd4ee54..83eed4a 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -4018,7 +4018,7 @@ static char dbcs_lead = 0;
    recursively with different messages by the system.
 */
 
-static int
+int
 w32_read_socket (struct terminal *terminal, int expected,
 		 struct input_event *hold_quit)
 {
@@ -6294,6 +6294,12 @@ w32_initialize (void)
 		   GetCurrentProcess (), &hMainThread, 0, TRUE, DUPLICATE_SAME_ACCESS);
 
   /* Wait for thread to start */
+#ifdef SINGLE_THREAD
+  DuplicateHandle (GetCurrentProcess (), GetCurrentThread (),
+                   GetCurrentProcess (), &hWindowsThread, 0, TRUE, DUPLICATE_SAME_ACCESS);
+  dwWindowsThreadId = GetCurrentThreadId ();
+  msh_mousewheel = RegisterWindowMessage (MSH_MOUSEWHEEL);
+#else
   {
     MSG msg;
 
@@ -6305,6 +6311,7 @@ w32_initialize (void)
 
     GetMessage (&msg, NULL, WM_EMACS_DONE, WM_EMACS_DONE);
   }
+#endif
 
   /* It is desirable that mainThread should have the same notion of
      focus window and active window as windowsThread.  Unfortunately, the
diff --git a/src/w32xfns.c b/src/w32xfns.c
index df9acca..a3c1af4 100644
--- a/src/w32xfns.c
+++ b/src/w32xfns.c
@@ -299,11 +299,19 @@ drain_message_queue (void)
   MSG msg;
   while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
     {
+#ifdef SINGLE_THREAD
+      dispatch_msg(&msg);
+#else
       TranslateMessage (&msg);
       DispatchMessage (&msg);
+#endif
     }
 }
 
+void w32_peek_messages()
+{
+    drain_message_queue ();
+}
 
 /*
  *    XParseGeometry parses strings of the form

  reply	other threads:[~2011-07-18 21:01 UTC|newest]

Thread overview: 51+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-07-18 17:33 [PATCH] system-type cygwin with window-system w32 grischka
2011-07-18 17:50 ` Daniel Colascione
2011-07-18 18:08   ` Daniel Colascione
2011-07-18 18:52     ` grischka
2011-07-18 19:11       ` Daniel Colascione
2011-07-18 21:01         ` grischka [this message]
2011-07-19  2:58           ` Eli Zaretskii
2011-07-19  2:59             ` Daniel Colascione
2011-07-21 17:44           ` Lennart Borgman
2011-07-22  7:30             ` Daniel Colascione
2011-07-22  7:41               ` Lennart Borgman
2011-07-22 21:24                 ` chad
2011-07-22 21:57                   ` Lennart Borgman
2011-07-18 18:38   ` grischka
  -- strict thread matches above, loose matches on Subject: below --
2011-07-18  0:01 Daniel Colascione
2011-07-18  0:06 ` Daniel Colascione
2011-07-18  6:13 ` Eli Zaretskii
2011-07-18  6:29   ` Daniel Colascione
2011-07-18  8:53     ` Eli Zaretskii
2011-07-18 10:10       ` Daniel Colascione
2011-07-18 16:04         ` Paul Eggert
2011-07-18 16:19         ` Eli Zaretskii
2011-07-18 13:55     ` Jason Rumney
2011-07-18 16:13     ` Paul Eggert
2011-07-18 17:34       ` Andreas Schwab
2011-07-18  6:53 ` Eli Zaretskii
2011-07-18  7:01   ` Daniel Colascione
2011-07-18  9:04     ` Eli Zaretskii
2011-07-18  9:41       ` Daniel Colascione
2011-07-18 10:10         ` Eli Zaretskii
2011-07-18 10:49           ` Daniel Colascione
2011-07-18 11:22             ` Juanma Barranquero
2011-07-18 16:41             ` Eli Zaretskii
2011-07-18 16:48               ` Daniel Colascione
2011-07-18 17:08                 ` Eli Zaretskii
2011-07-18 22:08             ` Richard Stallman
2011-07-18 22:24               ` Daniel Colascione
2011-07-18 22:45                 ` David Kastrup
2011-07-18 22:56                   ` Daniel Colascione
2011-07-19 16:49                     ` Richard Stallman
2011-07-21  1:44               ` Lennart Borgman
2011-07-18 22:08             ` Richard Stallman
2011-07-18 13:31       ` Jason Rumney
2011-07-18 13:46         ` Richard Riley
2011-07-18  8:42 ` Eli Zaretskii
2011-07-18 10:33   ` Daniel Colascione
2011-07-18 16:29     ` Eli Zaretskii
2011-07-18 17:04       ` Daniel Colascione
2011-07-18 15:54 ` Stefan Monnier
2011-07-18 15:55 ` Stefan Monnier
2011-07-18 17:37 ` Andreas Schwab

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4E249F37.3050101@gmx.de \
    --to=grishka@gmx.de \
    --cc=dan.colascione@gmail.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.