summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2026-02-28 15:34:16 +0000
committerSean Whitton <spwhitton@spwhitton.name>2026-02-28 15:34:16 +0000
commit342e002c879329e89bdec3a9d82c28ab06e6fce9 (patch)
tree51bbf420cffecdc0176991f9efe1cc67bfd8fb29
parentfe2fdf7c82e5efc3350061233d3f0aed54915876 (diff)
Show a message in locked frames in single-kboard mode
* src/keyboard.c (kbd_buffer_get_event): Pass the event's frame up to the caller by means of a new 'struct frame **' argument. (read_event_from_main_queue): Show a message in locked frames in single-kboard mode (bug#79892). * src/xdisp.c (log_message): Factor out of message3. (message3): Call it. (message3_nolog): Rename to ... (message3_frame_nolog): ... this. New 'struct frame *' argument which causes temporarily switching to another frame when displaying the message. (message3_frame, message3_nolog): New functions. * src/lisp.h: Declare message3_frame and message3_frame_nolog. * admin/notes/multi-tty: Remove notes on showing a message.
-rw-r--r--admin/notes/multi-tty18
-rw-r--r--src/keyboard.c40
-rw-r--r--src/lisp.h2
-rw-r--r--src/xdisp.c100
4 files changed, 112 insertions, 48 deletions
diff --git a/admin/notes/multi-tty b/admin/notes/multi-tty
index 7783eb395e7..fff20a1769b 100644
--- a/admin/notes/multi-tty
+++ b/admin/notes/multi-tty
@@ -402,29 +402,15 @@ THINGS TO DO
** The single-keyboard mode of MULTI_KBOARD is extremely confusing
sometimes; Emacs does not respond to stimuli from other keyboards.
- At least a beep or a message would be important, if the single-mode
- is still required to prevent interference. (Reported by Dan
- Nicolaescu.)
+ (Reported by Dan Nicolaescu.)
Update: selecting a region with the mouse enables single_kboard
under X. This is very confusing.
Update: After discussions with Richard Stallman, this will be
- resolved by having locked displays warn the user to wait, and
- introducing a complex protocol to remotely bail out of
+ resolved introducing a complex protocol to remotely bail out of
single-kboard mode by pressing C-g.
- Update: Warning the user is not trivial to implement, as Emacs has
- only one echo area, shared by all frames. Ideally the warning
- should not be displayed on the display that is locking the others.
- Perhaps the high probability of user confusion caused by
- single_kboard mode deserves a special case in the display code.
- Alternatively, it might be good enough to signal single_kboard mode
- by changing the modelines or some other frame-local display element
- on the locked out displays.
-
- Update: In fact struct kboard does have an echo_string slot.
-
** The session management module is prone to crashes when the X
connection is closed and then later I try to connect to a new X
session:
diff --git a/src/keyboard.c b/src/keyboard.c
index ffe47460528..648dd81660a 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -2297,6 +2297,7 @@ show_help_echo (Lisp_Object help, Lisp_Object window, Lisp_Object object,
/* Input of single characters from keyboard. */
static Lisp_Object kbd_buffer_get_event (KBOARD **kbp, bool *used_mouse_menu,
+ struct frame **event_frame,
struct timespec *end_time);
static void record_char (Lisp_Object c);
@@ -2342,7 +2343,8 @@ read_event_from_main_queue (struct timespec *end_time,
restore_getcjmp (local_getcjmp);
if (!end_time)
timer_start_idle ();
- c = kbd_buffer_get_event (&kb, used_mouse_menu, end_time);
+ struct frame *frame;
+ c = kbd_buffer_get_event (&kb, used_mouse_menu, &frame, end_time);
unbind_to (count, Qnil);
if (! NILP (c) && (kb != current_kboard))
@@ -2360,9 +2362,26 @@ read_event_from_main_queue (struct timespec *end_time,
else
XSETCDR (last, list1 (c));
kb->kbd_queue_has_data = true;
- c = Qnil;
if (single_kboard)
- goto start;
+ {
+ /* Typing and clicking in a locked frame is confusing because
+ it seems like Emacs has completely locked up (bug#79892).
+ Show a message about what's happening. */
+ /* FIXME: We also display the message in the unlocked frame.
+ Can we avoid that? */
+ if (frame
+ && (FIXNUMP (c) || (EVENT_HAS_PARAMETERS (c)
+ && EQ (EVENT_HEAD_KIND (EVENT_HEAD (c)),
+ Qmouse_click))))
+ {
+ AUTO_STRING (locked, "Frame is locked while another"
+ " waits for input"
+ " or is otherwise in a recursive edit");
+ message3_frame (locked, frame);
+ }
+ c = Qnil;
+ goto start;
+ }
current_kboard = kb;
return make_fixnum (-2);
}
@@ -4005,6 +4024,7 @@ kbd_buffer_get_event_2 (Lisp_Object val)
static Lisp_Object
kbd_buffer_get_event (KBOARD **kbp,
bool *used_mouse_menu,
+ struct frame **event_frame,
struct timespec *end_time)
{
Lisp_Object obj, str;
@@ -4019,6 +4039,8 @@ kbd_buffer_get_event (KBOARD **kbp,
had_pending_conversion_events = false;
#endif
+ *event_frame = NULL;
+
#ifdef subprocesses
if (kbd_on_hold_p () && kbd_buffer_nr_stored () < KBD_BUFFER_SIZE / 4)
{
@@ -4179,6 +4201,9 @@ kbd_buffer_get_event (KBOARD **kbp,
if (*kbp == 0)
*kbp = current_kboard; /* Better than returning null ptr? */
+ if (FRAMEP (event->ie.frame_or_window))
+ *event_frame = XFRAME (event->ie.frame_or_window);
+
obj = Qnil;
/* These two kinds of events get special handling
@@ -4492,13 +4517,14 @@ kbd_buffer_get_event (KBOARD **kbp,
/* Try generating a mouse motion event. */
else if (some_mouse_moved ())
{
- struct frame *f, *movement_frame = some_mouse_moved ();
+ struct frame *f;
Lisp_Object bar_window;
enum scroll_bar_part part;
Lisp_Object x, y;
Time t;
- f = movement_frame;
+ *event_frame = some_mouse_moved ();
+ f = *event_frame;
*kbp = current_kboard;
/* Note that this uses F to determine which terminal to look at.
If there is no valid info, it does not store anything
@@ -4535,8 +4561,8 @@ kbd_buffer_get_event (KBOARD **kbp,
obj = make_lispy_movement (f, bar_window, part, x, y, t);
if (!NILP (obj))
- Vlast_event_device = (STRINGP (movement_frame->last_mouse_device)
- ? movement_frame->last_mouse_device
+ Vlast_event_device = (STRINGP ((*event_frame)->last_mouse_device)
+ ? (*event_frame)->last_mouse_device
: virtual_core_pointer_name);
}
#ifdef HAVE_X_WINDOWS
diff --git a/src/lisp.h b/src/lisp.h
index c7afb07576b..cefca3d6fbb 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4390,6 +4390,8 @@ extern void message1 (const char *);
extern void message1_nolog (const char *);
extern void message3 (Lisp_Object);
extern void message3_nolog (Lisp_Object);
+extern void message3_frame (Lisp_Object, struct frame *);
+extern void message3_frame_nolog (Lisp_Object, struct frame *);
extern void message_dolog (const char *, ptrdiff_t, bool, bool);
extern void message_with_string (const char *, Lisp_Object, bool);
extern void message_log_maybe_newline (void);
diff --git a/src/xdisp.c b/src/xdisp.c
index f5e2b6e2d24..c8e4bf0cf10 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -510,6 +510,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
/* Holds the list (error). */
static Lisp_Object list_of_error;
+/* Forward declarations. */
+static void restore_selected_window (Lisp_Object);
+static void restore_frame_selected_window (Lisp_Object);
+
#ifdef HAVE_WINDOW_SYSTEM
/* Test if overflow newline into fringe. Called with iterator IT
@@ -12481,15 +12485,8 @@ message_log_check_duplicate (ptrdiff_t prev_bol_byte, ptrdiff_t this_bol_byte)
}
-/* Display an echo area message M with a specified length of NBYTES
- bytes. The string may include null characters. If M is not a
- string, clear out any existing message, and let the mini-buffer
- text show through.
-
- This function cancels echoing. */
-
-void
-message3 (Lisp_Object m)
+static void
+log_message (Lisp_Object m)
{
clear_message (true, true);
cancel_echoing ();
@@ -12506,10 +12503,34 @@ message3 (Lisp_Object m)
message_dolog (buffer, nbytes, true, multibyte);
SAFE_FREE ();
}
+}
+
+/* Display an echo area message M with a specified length of NBYTES
+ bytes. The string may include null characters. If M is not a
+ string, clear out any existing message, and let the mini-buffer
+ text show through.
+
+ This function cancels echoing. */
+
+void
+message3 (Lisp_Object m)
+{
+ log_message (m);
if (! inhibit_message)
message3_nolog (m);
}
+/* Display an echo area message M on frame F, which may not be the
+ selected frame. */
+
+void
+message3_frame (Lisp_Object m, struct frame *f)
+{
+ log_message (m);
+ if (! inhibit_message)
+ message3_frame_nolog (m, f);
+}
+
/* Log the message M to stderr. Log an empty line if M is not a string. */
static void
@@ -12538,7 +12559,7 @@ message_to_stderr (Lisp_Object m)
errputc ('\n');
}
-/* The non-logging version of message3.
+/* The non-logging versions of message3 & message3_frame.
This does not cancel echoing, because it is used for echoing.
Perhaps we need to make a separate function for echoing
and make this cancel echoing. */
@@ -12546,28 +12567,55 @@ message_to_stderr (Lisp_Object m)
void
message3_nolog (Lisp_Object m)
{
+ message3_frame_nolog (m, NULL);
+}
+
+void
+message3_frame_nolog (Lisp_Object m, struct frame *f)
+{
struct frame *sf = SELECTED_FRAME ();
+ if (!f) f = sf;
- if (FRAME_INITIAL_P (sf))
+ if (FRAME_INITIAL_P (f))
message_to_stderr (m);
- /* Error messages get reported properly by cmd_error, so this must be just an
- informative message; if the frame hasn't really been initialized yet, just
- toss it. */
- else if (INTERACTIVE && sf->glyphs_initialized_p)
+ /* Error messages get reported properly by cmd_error, so this must be
+ just an informative message; therefore if the frame hasn't really
+ been initialized yet, just toss it. */
+ else if (INTERACTIVE && f->glyphs_initialized_p)
{
- /* Get the frame containing the mini-buffer
- that the selected frame is using. */
- Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (sf);
- Lisp_Object frame = XWINDOW (mini_window)->frame;
- struct frame *f = XFRAME (frame);
+ Lisp_Object frame = Qnil;
+ struct frame *mbf = NULL;
+ specpdl_ref count = SPECPDL_INDEX ();
+
+ if (f == sf)
+ {
+ /* Get the frame containing the mini-buffer that the selected
+ frame is using. */
+ Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (f);
+ frame = XWINDOW (mini_window)->frame;
+ mbf = XFRAME (frame);
- if (FRAME_VISIBLE_P (sf) && !FRAME_VISIBLE_P (f))
- Fmake_frame_visible (frame);
+ if (FRAME_VISIBLE_P (f) && !FRAME_VISIBLE_P (mbf))
+ Fmake_frame_visible (frame);
+ }
+ else
+ {
+ /* We temporarily switch frame, show the message, and then
+ when we unwind the message will normally still be visible
+ in the other frame, at least for a few seconds. */
+ record_unwind_protect
+ (restore_selected_window, selected_window);
+ record_unwind_protect
+ (restore_frame_selected_window, f->selected_window);
+ XSETFRAME (frame, f);
+ selected_frame = frame;
+ selected_window = FRAME_SELECTED_WINDOW (f);
+ }
if (STRINGP (m) && SCHARS (m) > 0)
{
set_message (m);
- if (minibuffer_auto_raise)
+ if (minibuffer_auto_raise && !NILP (frame))
Fraise_frame (frame);
/* Assume we are not echoing.
(If we are, echo_now will override this.) */
@@ -12579,8 +12627,10 @@ message3_nolog (Lisp_Object m)
do_pending_window_change (false);
echo_area_display (true);
do_pending_window_change (false);
- if (FRAME_TERMINAL (f)->frame_up_to_date_hook)
- (*FRAME_TERMINAL (f)->frame_up_to_date_hook) (f);
+ if (mbf && FRAME_TERMINAL (mbf)->frame_up_to_date_hook)
+ (*FRAME_TERMINAL (mbf)->frame_up_to_date_hook) (mbf);
+
+ unbind_to (count, Qnil);
}
}