diff options
author | Yuan Fu <yuan@debian-BULLSEYE-live-builder-AMD64> | 2022-08-29 11:41:10 -0700 |
---|---|---|
committer | Yuan Fu <yuan@debian-BULLSEYE-live-builder-AMD64> | 2022-08-29 11:41:10 -0700 |
commit | 77d5a0cf9fc4a6dc44f0c6ee5e3295e0eea08273 (patch) | |
tree | 969937ec44ce5ddf9447b074aa15314e0b9e8e95 /src | |
parent | e98b4715bb986524bde9356b62429af9786ae716 (diff) | |
parent | df2f6fb7fc4b79834ae40db8be2ccdc1e4a273f1 (diff) | |
download | emacs-77d5a0cf9fc4a6dc44f0c6ee5e3295e0eea08273.tar.gz |
Merge remote-tracking branch 'origin/master' into feature/tree-sitter
Diffstat (limited to 'src')
110 files changed, 12523 insertions, 5073 deletions
diff --git a/src/.lldbinit b/src/.lldbinit new file mode 100644 index 00000000000..358cea5f8b6 --- /dev/null +++ b/src/.lldbinit @@ -0,0 +1,33 @@ +# -*- mode: shell-script -*- +# Copyright (C) 2022 Free Software Foundation, Inc. +# +# 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, 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/>. +# +# Use 'lldb --local-init' or add to your ~/.lldbinit the line +# +# settings set target.load-cwd-lldbinit true +# +# Emacs-specific commands start with 'x'. Type 'help' to see all +# commands. Type 'help <command>' to see help for a command +# <command>. + +# Make Python find our files +script -- sys.path.append('../etc') + +# Load our Python files +command script import emacs_lldb + +# end. diff --git a/src/Makefile.in b/src/Makefile.in index a21af42c0b9..eb537e21277 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -639,30 +639,23 @@ Emacs.pdmp: $(pdmp) endif ifeq ($(DUMPING),pdumper) -$(pdmp): emacs$(EXEEXT) +$(pdmp): emacs$(EXEEXT) $(lispsource)/loaddefs.el $(lispsource)/loaddefs.elc LC_ALL=C $(RUN_TEMACS) -batch $(BUILD_DETAILS) -l loadup --temacs=pdump \ --bin-dest $(BIN_DESTDIR) --eln-dest $(ELN_DESTDIR) cp -f $@ $(bootstrap_pdmp) endif -## We run make-docfile twice because the command line may get too long -## on some systems. Unfortunately, no-one has any idea -## exactly how long the maximum safe command line length is on all the -## various systems that Emacs supports. -## ## $(SOME_MACHINE_OBJECTS) comes before $(obj) because some files may ## or may not be included in $(obj), but they are always included in ## $(SOME_MACHINE_OBJECTS). Since a file is processed when it is mentioned ## for the first time, this prevents any variation between configurations ## in the contents of the DOC file. ## -$(etc)/DOC: $(libsrc)/make-docfile$(EXEEXT) $(doc_obj) $(lispsource)/loaddefs.el +$(etc)/DOC: $(libsrc)/make-docfile$(EXEEXT) $(doc_obj) $(AM_V_GEN)$(MKDIR_P) $(etc) $(AM_V_at)rm -f $(etc)/DOC $(AM_V_at)$(libsrc)/make-docfile -d $(srcdir) \ $(SOME_MACHINE_OBJECTS) $(doc_obj) > $(etc)/DOC - $(AM_V_at)$(libsrc)/make-docfile -a $(etc)/DOC -d $(lispsource) \ - loaddefs.el $(libsrc)/make-docfile$(EXEEXT) $(libsrc)/make-fingerprint$(EXEEXT): \ $(lib)/libgnu.a @@ -801,11 +794,12 @@ ctagsfiles3 = $(wildcard ${srcdir}/*.cc) ## In out-of-tree builds, TAGS are generated in the build dir, like ## other non-bootstrap build products (see Bug#31744). -## This does not need to depend on ../lisp and ../lwlib TAGS files, +## This does not need to depend on ../lisp, ../lwlib and ../lib TAGS files, ## because etags "--include" only includes a pointer to the file, ## rather than the file contents. TAGS: ${ETAGS} $(ctagsfiles1) $(ctagsfiles2) $(AM_V_GEN)${ETAGS} --include=../lisp/TAGS --include=$(lwlibdir)/TAGS \ + --include=$(lib)/TAGS \ --regex='{c}/[ ]*DEFVAR_[A-Z_ (]+"\([^"]+\)"/\1/' \ --regex='{c}/[ ]*DEFVAR_[A-Z_ (]+"[^"]+",[ ]\([A-Za-z0-9_]+\)/\1/' \ $(ctagsfiles1) \ @@ -814,12 +808,12 @@ TAGS: ${ETAGS} $(ctagsfiles1) $(ctagsfiles2) $(ctagsfiles2) \ $(ctagsfiles3) -## Arrange to make tags tables for ../lisp and ../lwlib, +## Arrange to make tags tables for ../lisp, ../lwlib and ../lib, ## which the above TAGS file for the C files includes by reference. -../lisp/TAGS $(lwlibdir)/TAGS: FORCE +../lisp/TAGS $(lwlibdir)/TAGS $(lib)/TAGS: FORCE $(MAKE) -C $(dir $@) $(notdir $@) ETAGS="$(ETAGS)" -tags: TAGS ../lisp/TAGS $(lwlibdir)/TAGS +tags: TAGS ../lisp/TAGS $(lwlibdir)/TAGS $(lib)/TAGS .PHONY: tags @@ -856,7 +850,6 @@ ifeq ($(HAVE_NATIVE_COMP):$(NATIVE_DISABLED),yes:) ## List of *.eln files we need to produce in addition to the preloaded ## ones in $(lisp). elnlisp := \ - emacs-lisp/autoload.eln \ emacs-lisp/byte-opt.eln \ emacs-lisp/bytecomp.eln \ emacs-lisp/cconv.eln \ @@ -892,13 +885,7 @@ elnlisp := $(addprefix ${lispsource}/,${elnlisp}) $(lisp:.elc=.eln) fi endif -## VCSWITNESS points to the file that holds info about the current checkout. -## We use it as a heuristic to decide when to rebuild loaddefs.el. -## If empty it is ignored; the parent makefile can set it to some other value. -VCSWITNESS = - -$(lispsource)/loaddefs.el: $(VCSWITNESS) | \ - bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp) +$(lispsource)/loaddefs.el: | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp) $(MAKE) -C ../lisp autoloads EMACS="$(bootstrap_exe)" ## Dump an Emacs executable named bootstrap-emacs containing the diff --git a/src/alloc.c b/src/alloc.c index 14c3a4cb8d7..ced7c73cba7 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -479,7 +479,7 @@ enum mem_type static bool deadp (Lisp_Object x) { - return EQ (x, dead_object ()); + return BASE_EQ (x, dead_object ()); } #ifdef GC_MALLOC_CHECK @@ -5334,6 +5334,7 @@ static void * pure_alloc (size_t size, int type) { void *result; + static bool pure_overflow_warned = false; again: if (type >= 0) @@ -5358,6 +5359,12 @@ pure_alloc (size_t size, int type) if (pure_bytes_used <= pure_size) return result; + if (!pure_overflow_warned) + { + message ("Pure Lisp storage overflowed"); + pure_overflow_warned = true; + } + /* Don't allocate a large amount here, because it might get mmap'd and then its address might not be usable. */ @@ -5375,9 +5382,6 @@ pure_alloc (size_t size, int type) goto again; } - -#ifdef HAVE_UNEXEC - /* Print a warning if PURESIZE is too small. */ void @@ -5388,8 +5392,6 @@ check_pure_size (void) " bytes needed)"), pure_bytes_used + pure_bytes_used_before_overflow); } -#endif - /* Find the byte sequence {DATA[0], ..., DATA[NBYTES-1], '\0'} from the non-Lisp data pool of the pure storage, and return its start @@ -6224,6 +6226,10 @@ garbage_collect (void) mark_xterm (); #endif +#ifdef HAVE_NS + mark_nsterm (); +#endif + /* Everything is now marked, except for the data in font caches, undo lists, and finalizers. The first two are compacted by removing an items which aren't reachable otherwise. */ @@ -6282,6 +6288,11 @@ garbage_collect (void) /* GC is complete: now we can run our finalizer callbacks. */ run_finalizers (&doomed_finalizers); +#ifdef HAVE_WINDOW_SYSTEM + /* Eject unused image cache entries. */ + image_prune_animation_caches (false); +#endif + if (!NILP (Vpost_gc_hook)) { specpdl_ref gc_count = inhibit_garbage_collection (); diff --git a/src/bidi.c b/src/bidi.c index 4d2c74b17cd..c4d04136e9e 100644 --- a/src/bidi.c +++ b/src/bidi.c @@ -1277,6 +1277,12 @@ bidi_fetch_char (ptrdiff_t charpos, ptrdiff_t bytepos, ptrdiff_t *disp_pos, SET_TEXT_POS (pos, charpos, bytepos); *disp_pos = compute_display_string_pos (&pos, string, w, frame_window_p, disp_prop); + /* The factor of 100 below is a heuristic that needs to be + tuned. It means we consider 100 buffer positions examined by + the above call roughly equivalent to the display engine + iterating over a single buffer position. */ + if (max_redisplay_ticks > 0 && *disp_pos > charpos) + update_redisplay_ticks ((*disp_pos - charpos) / 100 + 1, w); } /* Fetch the character at BYTEPOS. */ @@ -1385,6 +1391,8 @@ bidi_fetch_char (ptrdiff_t charpos, ptrdiff_t bytepos, ptrdiff_t *disp_pos, SET_TEXT_POS (pos, charpos + *nchars, bytepos + *ch_len); *disp_pos = compute_display_string_pos (&pos, string, w, frame_window_p, disp_prop); + if (max_redisplay_ticks > 0 && *disp_pos > charpos + *nchars) + update_redisplay_ticks ((*disp_pos - charpos - *nchars) / 100 + 1, w); } return ch; @@ -1583,6 +1591,9 @@ bidi_find_paragraph_start (ptrdiff_t pos, ptrdiff_t pos_byte) return pos_byte; } +/* This tracks how far we needed to search for first strong character. */ +static ptrdiff_t nsearch_for_strong; + /* On a 3.4 GHz machine, searching forward for a strong directional character in a long paragraph full of weaks or neutrals takes about 1 ms for each 20K characters. The number below limits each call to @@ -1652,6 +1663,8 @@ find_first_strong_char (ptrdiff_t pos, ptrdiff_t bytepos, ptrdiff_t end, pos += *nchars; bytepos += *ch_len; } + + nsearch_for_strong += pos - pos1; return type; } @@ -1681,6 +1694,9 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, bool no_default_p) calls to BYTE_TO_CHAR and its ilk. */ ptrdiff_t begbyte = string_p ? 0 : BEGV_BYTE; ptrdiff_t end = string_p ? bidi_it->string.schars : ZV; + ptrdiff_t pos = bidi_it->charpos; + + nsearch_for_strong = 0; /* Special case for an empty buffer. */ if (bytepos == begbyte && bidi_it->charpos == end) @@ -1702,7 +1718,7 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, bool no_default_p) else if (dir == NEUTRAL_DIR) /* P2 */ { ptrdiff_t ch_len, nchars; - ptrdiff_t pos, disp_pos = -1; + ptrdiff_t disp_pos = -1; int disp_prop = 0; bidi_type_t type; const unsigned char *s; @@ -1800,6 +1816,14 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, bool no_default_p) bidi_it->level_stack[0].level = 0; bidi_line_init (bidi_it); + + /* The factor of 50 below is a heuristic that needs to be tuned. It + means we consider 50 buffer positions examined by this function + roughly equivalent to the display engine iterating over a single + buffer position. */ + ptrdiff_t nexamined = bidi_it->charpos - pos + nsearch_for_strong; + if (max_redisplay_ticks > 0 && nexamined > 0) + update_redisplay_ticks (nexamined / 50, bidi_it->w); } @@ -2566,6 +2590,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it) bidi_bracket_type_t btype; bidi_type_t type = bidi_it->type; bool retval = false; + ptrdiff_t n = 0; /* When scanning backwards, we don't expect any unresolved bidi bracket characters. */ @@ -2695,6 +2720,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it) } old_sidx = bidi_it->stack_idx; type = bidi_resolve_weak (bidi_it); + n++; /* Skip level runs excluded from this isolating run sequence. */ new_sidx = bidi_it->stack_idx; if (bidi_it->level_stack[new_sidx].level > current_level @@ -2718,6 +2744,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it) goto give_up; } type = bidi_resolve_weak (bidi_it); + n++; } } if (type == NEUTRAL_B @@ -2794,6 +2821,12 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it) } give_up: + /* The factor of 20 below is a heuristic that needs to be tuned. It + means we consider 20 buffer positions examined by this function + roughly equivalent to the display engine iterating over a single + buffer position. */ + if (max_redisplay_ticks > 0 && n > 0) + update_redisplay_ticks (n / 20 + 1, bidi_it->w); return retval; } @@ -3363,6 +3396,7 @@ bidi_find_other_level_edge (struct bidi_it *bidi_it, int level, bool end_flag) else { int new_level; + ptrdiff_t pos0 = bidi_it->charpos; /* If we are at end of level, its edges must be cached. */ if (end_flag) @@ -3398,6 +3432,12 @@ bidi_find_other_level_edge (struct bidi_it *bidi_it, int level, bool end_flag) bidi_cache_iterator_state (bidi_it, 1, 1); } } while (new_level >= level); + /* The factor of 50 below is a heuristic that needs to be + tuned. It means we consider 50 buffer positions examined by + the above call roughly equivalent to the display engine + iterating over a single buffer position. */ + if (max_redisplay_ticks > 0 && bidi_it->charpos > pos0) + update_redisplay_ticks ((bidi_it->charpos - pos0) / 50 + 1, bidi_it->w); } } diff --git a/src/buffer.c b/src/buffer.c index 97e9e227386..be7c2f2161e 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -604,6 +604,7 @@ even if it is dead. The return value is never nil. */) set_buffer_intervals (b, NULL); BUF_UNCHANGED_MODIFIED (b) = 1; BUF_OVERLAY_UNCHANGED_MODIFIED (b) = 1; + BUF_CHARS_UNCHANGED_MODIFIED (b) = 1; BUF_END_UNCHANGED (b) = 0; BUF_BEG_UNCHANGED (b) = 0; *(BUF_GPT_ADDR (b)) = *(BUF_Z_ADDR (b)) = 0; /* Put an anchor '\0'. */ @@ -918,7 +919,8 @@ does not run the hooks `kill-buffer-hook', set_buffer_internal_1 (b); Fset (intern ("buffer-save-without-query"), Qnil); Fset (intern ("buffer-file-number"), Qnil); - Fset (intern ("buffer-stale-function"), Qnil); + if (!NILP (Flocal_variable_p (Qbuffer_stale_function, base_buffer))) + Fkill_local_variable (Qbuffer_stale_function); /* Cloned buffers need extra setup, to do things such as deep variable copies for list variables that might be mangled due to destructive operations in the indirect buffer. */ @@ -991,6 +993,7 @@ reset_buffer (register struct buffer *b) /* It is more conservative to start out "changed" than "unchanged". */ b->clip_changed = 0; b->prevent_redisplay_optimizations_p = 1; + b->long_line_optimizations_p = 0; bset_backed_up (b, Qnil); bset_local_minor_modes (b, Qnil); BUF_AUTOSAVE_MODIFF (b) = 0; @@ -1075,7 +1078,7 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) eassert (XSYMBOL (sym)->u.s.redirect == SYMBOL_LOCALIZED); /* Need not do anything if some other buffer's binding is now cached. */ - if (EQ (SYMBOL_BLV (XSYMBOL (sym))->where, buffer)) + if (BASE_EQ (SYMBOL_BLV (XSYMBOL (sym))->where, buffer)) { /* Symbol is set up for this buffer's old local value: swap it out! */ @@ -1168,7 +1171,8 @@ is first appended to NAME, to speed up finding a non-existent buffer. */) genbase = name; else { - char number[sizeof "-999999"]; + enum { bug_52711 = true }; /* https://bugs.gnu.org/57211 */ + char number[bug_52711 ? INT_BUFSIZE_BOUND (int) + 1 : sizeof "-999999"]; EMACS_INT r = get_random (); eassume (0 <= r); int i = r % 1000000; @@ -1510,7 +1514,7 @@ state of the current buffer. Use with care. */) decrease SAVE_MODIFF and auto_save_modified or increase MODIFF. */ if (SAVE_MODIFF >= MODIFF) - SAVE_MODIFF = modiff_incr (&MODIFF); + SAVE_MODIFF = modiff_incr (&MODIFF, 1); if (EQ (flag, Qautosaved)) BUF_AUTOSAVE_MODIFF (b) = MODIFF; } @@ -1570,6 +1574,7 @@ This does not change the name of the visited file (if any). */) (register Lisp_Object newname, Lisp_Object unique) { register Lisp_Object tem, buf; + Lisp_Object requestedname = newname; CHECK_STRING (newname); @@ -1586,7 +1591,8 @@ This does not change the name of the visited file (if any). */) if (NILP (unique) && XBUFFER (tem) == current_buffer) return BVAR (current_buffer, name); if (!NILP (unique)) - newname = Fgenerate_new_buffer_name (newname, BVAR (current_buffer, name)); + newname = Fgenerate_new_buffer_name (newname, + BVAR (current_buffer, name)); else error ("Buffer name `%s' is in use", SDATA (newname)); } @@ -1606,7 +1612,7 @@ This does not change the name of the visited file (if any). */) run_buffer_list_update_hook (current_buffer); call2 (intern ("uniquify--rename-buffer-advice"), - BVAR (current_buffer, name), unique); + requestedname, unique); /* Refetch since that last call may have done GC. */ return BVAR (current_buffer, name); @@ -1617,7 +1623,7 @@ This does not change the name of the visited file (if any). */) static bool candidate_buffer (Lisp_Object b, Lisp_Object buffer) { - return (BUFFERP (b) && !EQ (b, buffer) + return (BUFFERP (b) && !BASE_EQ (b, buffer) && BUFFER_LIVE_P (XBUFFER (b)) && !BUFFER_HIDDEN_P (XBUFFER (b))); } @@ -1819,10 +1825,12 @@ cleaning up all windows currently displaying the buffer to be killed. */) /* Query if the buffer is still modified. */ if (INTERACTIVE && modified) { - AUTO_STRING (format, "Buffer %s modified; kill anyway? "); - tem = do_yes_or_no_p (CALLN (Fformat, format, BVAR (b, name))); - if (NILP (tem)) + /* Ask whether to kill the buffer, and exit if the user says + "no". */ + if (NILP (call1 (Qkill_buffer__possibly_save, buffer))) return unbind_to (count, Qnil); + /* Recheck modified. */ + modified = BUF_MODIFF (b) > BUF_SAVE_MODIFF (b); } /* Delete the autosave file, if requested. */ @@ -1861,7 +1869,7 @@ cleaning up all windows currently displaying the buffer to be killed. */) since anything can happen within do_yes_or_no_p. */ /* Don't kill the minibuffer now current. */ - if (EQ (buffer, XWINDOW (minibuf_window)->contents)) + if (BASE_EQ (buffer, XWINDOW (minibuf_window)->contents)) return Qnil; /* When we kill an ordinary buffer which shares its buffer text @@ -1905,7 +1913,7 @@ cleaning up all windows currently displaying the buffer to be killed. */) is the sole other buffer give up. */ XSETBUFFER (tem, current_buffer); if (EQ (tem, XWINDOW (minibuf_window)->contents) - && EQ (buffer, Fother_buffer (buffer, Qnil, Qnil))) + && BASE_EQ (buffer, Fother_buffer (buffer, Qnil, Qnil))) return Qnil; /* Now there is no question: we can kill the buffer. */ @@ -2453,6 +2461,7 @@ results, see Info node `(elisp)Swapping Text'. */) swapfield (bidi_paragraph_cache, struct region_cache *); current_buffer->prevent_redisplay_optimizations_p = 1; other_buffer->prevent_redisplay_optimizations_p = 1; + swapfield (long_line_optimizations_p, bool_bf); swapfield (overlays_before, struct Lisp_Overlay *); swapfield (overlays_after, struct Lisp_Overlay *); swapfield (overlay_center, ptrdiff_t); @@ -2472,12 +2481,12 @@ results, see Info node `(elisp)Swapping Text'. */) bset_point_before_scroll (current_buffer, Qnil); bset_point_before_scroll (other_buffer, Qnil); - modiff_incr (¤t_buffer->text->modiff); - modiff_incr (&other_buffer->text->modiff); - modiff_incr (¤t_buffer->text->chars_modiff); - modiff_incr (&other_buffer->text->chars_modiff); - modiff_incr (¤t_buffer->text->overlay_modiff); - modiff_incr (&other_buffer->text->overlay_modiff); + modiff_incr (¤t_buffer->text->modiff, 1); + modiff_incr (&other_buffer->text->modiff, 1); + modiff_incr (¤t_buffer->text->chars_modiff, 1); + modiff_incr (&other_buffer->text->chars_modiff, 1); + modiff_incr (¤t_buffer->text->overlay_modiff, 1); + modiff_incr (&other_buffer->text->overlay_modiff, 1); current_buffer->text->beg_unchanged = current_buffer->text->gpt; current_buffer->text->end_unchanged = current_buffer->text->gpt; other_buffer->text->beg_unchanged = other_buffer->text->gpt; @@ -2511,23 +2520,23 @@ results, see Info node `(elisp)Swapping Text'. */) { ws = Fcons (w, ws); if (MARKERP (XWINDOW (w)->pointm) - && (EQ (XWINDOW (w)->contents, buf1) - || EQ (XWINDOW (w)->contents, buf2))) + && (BASE_EQ (XWINDOW (w)->contents, buf1) + || BASE_EQ (XWINDOW (w)->contents, buf2))) Fset_marker (XWINDOW (w)->pointm, make_fixnum (BUF_BEGV (XBUFFER (XWINDOW (w)->contents))), XWINDOW (w)->contents); /* Blindly copied from pointm part. */ if (MARKERP (XWINDOW (w)->old_pointm) - && (EQ (XWINDOW (w)->contents, buf1) - || EQ (XWINDOW (w)->contents, buf2))) + && (BASE_EQ (XWINDOW (w)->contents, buf1) + || BASE_EQ (XWINDOW (w)->contents, buf2))) Fset_marker (XWINDOW (w)->old_pointm, make_fixnum (BUF_BEGV (XBUFFER (XWINDOW (w)->contents))), XWINDOW (w)->contents); if (MARKERP (XWINDOW (w)->start) - && (EQ (XWINDOW (w)->contents, buf1) - || EQ (XWINDOW (w)->contents, buf2))) + && (BASE_EQ (XWINDOW (w)->contents, buf1) + || BASE_EQ (XWINDOW (w)->contents, buf2))) Fset_marker (XWINDOW (w)->start, make_fixnum (XBUFFER (XWINDOW (w)->contents)->last_window_start), @@ -2537,10 +2546,11 @@ results, see Info node `(elisp)Swapping Text'. */) } if (current_buffer->text->intervals) - (eassert (EQ (current_buffer->text->intervals->up.obj, buffer)), + (eassert (BASE_EQ (current_buffer->text->intervals->up.obj, buffer)), XSETBUFFER (current_buffer->text->intervals->up.obj, current_buffer)); if (other_buffer->text->intervals) - (eassert (EQ (other_buffer->text->intervals->up.obj, Fcurrent_buffer ())), + (eassert (BASE_EQ (other_buffer->text->intervals->up.obj, + Fcurrent_buffer ())), XSETBUFFER (other_buffer->text->intervals->up.obj, other_buffer)); return Qnil; @@ -3950,9 +3960,9 @@ for the rear of the overlay advance when text is inserted there else CHECK_BUFFER (buffer); - if (MARKERP (beg) && !EQ (Fmarker_buffer (beg), buffer)) + if (MARKERP (beg) && !BASE_EQ (Fmarker_buffer (beg), buffer)) signal_error ("Marker points into wrong buffer", beg); - if (MARKERP (end) && !EQ (Fmarker_buffer (end), buffer)) + if (MARKERP (end) && !BASE_EQ (Fmarker_buffer (end), buffer)) signal_error ("Marker points into wrong buffer", end); CHECK_FIXNUM_COERCE_MARKER (beg); @@ -4015,7 +4025,7 @@ modify_overlay (struct buffer *buf, ptrdiff_t start, ptrdiff_t end) bset_redisplay (buf); - modiff_incr (&BUF_OVERLAY_MODIFF (buf)); + modiff_incr (&BUF_OVERLAY_MODIFF (buf), 1); } /* Remove OVERLAY from LIST. */ @@ -4070,9 +4080,9 @@ buffer. */) if (NILP (Fbuffer_live_p (buffer))) error ("Attempt to move overlay to a dead buffer"); - if (MARKERP (beg) && !EQ (Fmarker_buffer (beg), buffer)) + if (MARKERP (beg) && !BASE_EQ (Fmarker_buffer (beg), buffer)) signal_error ("Marker points into wrong buffer", beg); - if (MARKERP (end) && !EQ (Fmarker_buffer (end), buffer)) + if (MARKERP (end) && !BASE_EQ (Fmarker_buffer (end), buffer)) signal_error ("Marker points into wrong buffer", end); CHECK_FIXNUM_COERCE_MARKER (beg); @@ -5622,7 +5632,7 @@ used. */); DEFVAR_PER_BUFFER ("mode-line-format", &BVAR (current_buffer, mode_line_format), Qnil, - doc: /* Template for displaying mode line for current buffer. + doc: /* Template for displaying mode line for a window's buffer. The value may be nil, a string, a symbol or a list. @@ -5635,6 +5645,9 @@ For any symbol other than t or nil, the symbol's value is processed as `risky-local-variable' property, all properties in any strings, as well as all :eval and :propertize forms in the value, are ignored. +When the value is processed, the window's buffer is temporarily the +current buffer. + A list whose car is a string or list is processed by processing each of the list elements recursively, as separate mode line constructs, and concatenating the results. @@ -6436,6 +6449,37 @@ Since `clone-indirect-buffer' calls `make-indirect-buffer', this hook will run for `clone-indirect-buffer' calls as well. */); Vclone_indirect_buffer_hook = Qnil; + DEFVAR_LISP ("long-line-threshold", Vlong_line_threshold, + doc: /* Line length above which to use redisplay shortcuts. + +The value should be a positive integer or nil. +If the value is an integer, shortcuts in the display code intended +to speed up redisplay for long lines will automatically be enabled +in buffers which contain one or more lines whose length is above +this threshold. +If nil, these display shortcuts will always remain disabled. + +There is no reason to change that value except for debugging purposes. */); + XSETFASTINT (Vlong_line_threshold, 10000); + + DEFVAR_INT ("large-hscroll-threshold", large_hscroll_threshold, + doc: /* Horizontal scroll of truncated lines above which to use redisplay shortcuts. + +The value should be a positive integer. + +Shortcuts in the display code intended to speed up redisplay for long +and truncated lines will automatically be enabled when a line's +horizontal scroll amount is or about to become larger than the value +of this variable. + +This variable has effect only in buffers which contain one or more +lines whose length is above `long-line-threshold', which see. +To disable redisplay shortcuts for long truncated line, set this +variable to `most-positive-fixnum'. + +There is no reason to change that value except for debugging purposes. */); + large_hscroll_threshold = 10000; + defsubr (&Sbuffer_live_p); defsubr (&Sbuffer_list); defsubr (&Sget_buffer); @@ -6489,5 +6533,9 @@ will run for `clone-indirect-buffer' calls as well. */); DEFSYM (Qautosaved, "autosaved"); + DEFSYM (Qkill_buffer__possibly_save, "kill-buffer--possibly-save"); + + DEFSYM (Qbuffer_stale_function, "buffer-stale-function"); + Fput (intern_c_string ("erase-buffer"), Qdisabled, Qt); } diff --git a/src/buffer.h b/src/buffer.h index bc07a63b537..04792374cd1 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -149,12 +149,18 @@ enum { BEG = 1, BEG_BYTE = BEG }; #define BUF_BEG_UNCHANGED(buf) ((buf)->text->beg_unchanged) #define BUF_END_UNCHANGED(buf) ((buf)->text->end_unchanged) +#define BUF_CHARS_UNCHANGED_MODIFIED(buf) \ + ((buf)->text->chars_unchanged_modified) + #define UNCHANGED_MODIFIED \ BUF_UNCHANGED_MODIFIED (current_buffer) #define OVERLAY_UNCHANGED_MODIFIED \ BUF_OVERLAY_UNCHANGED_MODIFIED (current_buffer) #define BEG_UNCHANGED BUF_BEG_UNCHANGED (current_buffer) #define END_UNCHANGED BUF_END_UNCHANGED (current_buffer) + +#define CHARS_UNCHANGED_MODIFIED \ + BUF_CHARS_UNCHANGED_MODIFIED (current_buffer) /* Functions to set PT in the current buffer, or another buffer. */ @@ -237,9 +243,10 @@ struct buffer_text ptrdiff_t z_byte; /* Byte pos of end of buffer. */ ptrdiff_t gap_size; /* Size of buffer's gap. */ modiff_count modiff; /* This counts buffer-modification events - for this buffer. It is incremented for - each such event, and never otherwise - changed. */ + for this buffer. It is increased + logarithmically to the extent of the + modification for each such event, + and never otherwise changed. */ modiff_count chars_modiff; /* This is modified with character change events for this buffer. It is set to modiff for each such event, and never @@ -267,6 +274,11 @@ struct buffer_text end_unchanged contain no useful information. */ modiff_count overlay_unchanged_modified; + /* CHARS_MODIFF as of last redisplay that finished. It's used + when we only care about changes in actual buffer text, not in + any other kind of changes, like properties etc. */ + modiff_count chars_unchanged_modified; + /* Properties of this buffer's text. */ INTERVAL intervals; @@ -685,6 +697,10 @@ struct buffer defined, as well as by with-temp-buffer, for example. */ bool_bf inhibit_buffer_hooks : 1; + /* Non-zero when the buffer contains long lines and specific + display optimizations must be used. */ + bool_bf long_line_optimizations_p : 1; + /* List of overlays that end at or before the current center, in order of end-position. */ struct Lisp_Overlay *overlays_before; diff --git a/src/bytecode.c b/src/bytecode.c index fa068e1ec6b..d75767bb0c5 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -176,8 +176,8 @@ DEFINE (Bmin, 0136) \ DEFINE (Bmult, 0137) \ \ DEFINE (Bpoint, 0140) \ -/* Was Bmark in v17. */ \ -DEFINE (Bsave_current_buffer, 0141) /* Obsolete. */ \ +/* 0141 was Bmark in v17, Bsave_current_buffer in 18-19. */ \ +DEFINE (Bsave_current_buffer_OBSOLETE, 0141) /* Obsolete since 20. */ \ DEFINE (Bgoto_char, 0142) \ DEFINE (Binsert, 0143) \ DEFINE (Bpoint_max, 0144) \ @@ -194,7 +194,7 @@ DEFINE (Bbolp, 0156) \ DEFINE (Bbobp, 0157) \ DEFINE (Bcurrent_buffer, 0160) \ DEFINE (Bset_buffer, 0161) \ -DEFINE (Bsave_current_buffer_1, 0162) /* Replacing Bsave_current_buffer. */ \ +DEFINE (Bsave_current_buffer, 0162) \ /* 0163 was Bset_mark in v17. */ \ DEFINE (Binteractive_p, 0164) /* Obsolete since Emacs-24.1. */ \ \ @@ -924,8 +924,8 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, record_unwind_protect_excursion (); NEXT; - CASE (Bsave_current_buffer): /* Obsolete since ??. */ - CASE (Bsave_current_buffer_1): + CASE (Bsave_current_buffer_OBSOLETE): /* Obsolete since 20. */ + CASE (Bsave_current_buffer): record_unwind_current_buffer (); NEXT; @@ -1678,6 +1678,12 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, /* TODO: Perhaps introduce another byte-code for switch when the number of cases is less, which uses a simple vector for linear search as the jump table. */ + + /* TODO: Instead of pushing the table in a separate + Bconstant op, use an immediate argument (maybe separate + switch opcodes for 1-byte and 2-byte constant indices). + This would also get rid of some hacks that assume each + Bswitch to be preceded by a Bconstant. */ Lisp_Object jmp_table = POP; if (BYTE_CODE_SAFE && !HASH_TABLE_P (jmp_table)) emacs_abort (); diff --git a/src/callint.c b/src/callint.c index 8283c61da67..8ef0e5240a5 100644 --- a/src/callint.c +++ b/src/callint.c @@ -161,73 +161,33 @@ check_mark (bool for_region) xsignal0 (Qmark_inactive); } -/* If the list of args INPUT was produced with an explicit call to - `list', look for elements that were computed with - (region-beginning) or (region-end), and put those expressions into - VALUES instead of the present values. +/* If FUNCTION has an `interactive-args' spec, replace relevant + elements in VALUES with those forms instead. This function doesn't return a value because it modifies elements of VALUES to do its job. */ static void -fix_command (Lisp_Object input, Lisp_Object function, Lisp_Object values) +fix_command (Lisp_Object function, Lisp_Object values) { - /* FIXME: Instead of this ugly hack, we should provide a way for an - interactive spec to return an expression/function that will re-build the - args without user intervention. */ - if (CONSP (input)) + /* Quick exit if there's no values to alter. */ + if (!CONSP (values) || !SYMBOLP (function)) + return; + + Lisp_Object reps = Fget (function, Qinteractive_args); + + if (CONSP (reps)) { - Lisp_Object car; + int i = 0; + Lisp_Object vals = values; - car = XCAR (input); - /* Skip through certain special forms. */ - while (EQ (car, Qlet) || EQ (car, Qletx) - || EQ (car, Qsave_excursion) - || EQ (car, Qprogn)) + while (!NILP (vals)) { - while (CONSP (XCDR (input))) - input = XCDR (input); - input = XCAR (input); - if (!CONSP (input)) - break; - car = XCAR (input); - } - if (EQ (car, Qlist)) - { - Lisp_Object intail, valtail; - for (intail = Fcdr (input), valtail = values; - CONSP (valtail); - intail = Fcdr (intail), valtail = XCDR (valtail)) - { - Lisp_Object elt; - elt = Fcar (intail); - if (CONSP (elt)) - { - Lisp_Object presflag, carelt; - carelt = XCAR (elt); - /* If it is (if X Y), look at Y. */ - if (EQ (carelt, Qif) - && NILP (Fnthcdr (make_fixnum (3), elt))) - elt = Fnth (make_fixnum (2), elt); - /* If it is (when ... Y), look at Y. */ - else if (EQ (carelt, Qwhen)) - { - while (CONSP (XCDR (elt))) - elt = XCDR (elt); - elt = Fcar (elt); - } - - /* If the function call we're looking at - is a special preserved one, copy the - whole expression for this argument. */ - if (CONSP (elt)) - { - presflag = Fmemq (Fcar (elt), preserved_fns); - if (!NILP (presflag)) - Fsetcar (valtail, Fcar (intail)); - } - } - } + Lisp_Object rep = Fassq (make_fixnum (i), reps); + if (!NILP (rep)) + Fsetcar (vals, XCDR (rep)); + vals = XCDR (vals); + ++i; } } @@ -235,31 +195,28 @@ fix_command (Lisp_Object input, Lisp_Object function, Lisp_Object values) optional, remove them from the list. This makes navigating the history less confusing, since it doesn't contain a lot of parameters that aren't used. */ - if (CONSP (values)) + Lisp_Object arity = Ffunc_arity (function); + /* We don't want to do this simplification if we have an &rest + function, because (cl-defun foo (a &optional (b 'zot)) ..) + etc. */ + if (FIXNUMP (XCAR (arity)) && FIXNUMP (XCDR (arity))) { - Lisp_Object arity = Ffunc_arity (function); - /* We don't want to do this simplification if we have an &rest - function, because (cl-defun foo (a &optional (b 'zot)) ..) - etc. */ - if (FIXNUMP (XCAR (arity)) && FIXNUMP (XCDR (arity))) + Lisp_Object final = Qnil; + ptrdiff_t final_i = 0, i = 0; + for (Lisp_Object tail = values; + CONSP (tail); + tail = XCDR (tail), ++i) { - Lisp_Object final = Qnil; - ptrdiff_t final_i = 0, i = 0; - for (Lisp_Object tail = values; - CONSP (tail); - tail = XCDR (tail), ++i) + if (!NILP (XCAR (tail))) { - if (!NILP (XCAR (tail))) - { - final = tail; - final_i = i; - } + final = tail; + final_i = i; } - - /* Chop the trailing optional values. */ - if (final_i > 0 && final_i >= XFIXNUM (XCAR (arity)) - 1) - XSETCDR (final, Qnil); } + + /* Chop the trailing optional values. */ + if (final_i > 0 && final_i >= XFIXNUM (XCAR (arity)) - 1) + XSETCDR (final, Qnil); } } @@ -360,7 +317,6 @@ invoke it (via an `interactive' spec that contains, for instance, an { Lisp_Object funval = Findirect_function (function, Qt); uintmax_t events = num_input_events; - Lisp_Object input = specs; /* Compute the arg values using the user's expression. */ specs = Feval (specs, CONSP (funval) && EQ (Qclosure, XCAR (funval)) @@ -371,7 +327,7 @@ invoke it (via an `interactive' spec that contains, for instance, an Make a copy of the list of values, for the command history, and turn them into things we can eval. */ Lisp_Object values = quotify_args (Fcopy_sequence (specs)); - fix_command (input, function, values); + fix_command (function, values); call4 (intern ("add-to-history"), intern ("command-history"), Fcons (function, values), Qnil, Qt); } @@ -439,7 +395,7 @@ invoke it (via an `interactive' spec that contains, for instance, an && (w = XCAR (w), WINDOWP (w))) { if (MINI_WINDOW_P (XWINDOW (w)) - && ! (minibuf_level > 0 && EQ (w, minibuf_window))) + && ! (minibuf_level > 0 && BASE_EQ (w, minibuf_window))) error ("Attempt to select inactive minibuffer window"); /* If the current buffer wants to clean up, let it. */ @@ -509,7 +465,7 @@ invoke it (via an `interactive' spec that contains, for instance, an case 'b': /* Name of existing buffer. */ args[i] = Fcurrent_buffer (); - if (EQ (selected_window, minibuf_window)) + if (BASE_EQ (selected_window, minibuf_window)) args[i] = Fother_buffer (args[i], Qnil, Qnil); args[i] = Fread_buffer (callint_message, args[i], Qt, Qnil); break; @@ -950,4 +906,6 @@ use `event-start', `event-end', and `event-click-count'. */); defsubr (&Scall_interactively); defsubr (&Sfuncall_interactively); defsubr (&Sprefix_numeric_value); + + DEFSYM (Qinteractive_args, "interactive-args"); } diff --git a/src/callproc.c b/src/callproc.c index dd162f36a6c..2d457b3c84c 100644 --- a/src/callproc.c +++ b/src/callproc.c @@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, child_errno = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env, - SSDATA (current_dir), NULL, &oldset); + SSDATA (current_dir), NULL, false, false, &oldset); eassert ((child_errno == 0) == (0 < pid)); if (pid > 0) @@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes, int emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, char **argv, char **envp, const char *cwd, - const char *pty, const sigset_t *oldset) + const char *pty_name, bool pty_in, bool pty_out, + const sigset_t *oldset) { #if USABLE_POSIX_SPAWN /* Prefer the simpler `posix_spawn' if available. `posix_spawn' doesn't yet support setting up pseudoterminals, so we fall back to `vfork' if we're supposed to use a pseudoterminal. */ - bool use_posix_spawn = pty == NULL; + bool use_posix_spawn = pty_name == NULL; posix_spawn_file_actions_t actions; posix_spawnattr_t attributes; @@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, /* vfork, and prevent local vars from being clobbered by the vfork. */ pid_t *volatile newpid_volatile = newpid; const char *volatile cwd_volatile = cwd; - const char *volatile pty_volatile = pty; + const char *volatile ptyname_volatile = pty_name; + bool volatile ptyin_volatile = pty_in; + bool volatile ptyout_volatile = pty_out; char **volatile argv_volatile = argv; int volatile stdin_volatile = std_in; int volatile stdout_volatile = std_out; @@ -1485,7 +1488,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, /* Darwin doesn't let us run setsid after a vfork, so use fork when necessary. Below, we reset SIGCHLD handling after a vfork, as apparently macOS can mistakenly deliver SIGCHLD to the child. */ - if (pty != NULL) + if (pty_in || pty_out) pid = fork (); else pid = VFORK (); @@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, newpid = newpid_volatile; cwd = cwd_volatile; - pty = pty_volatile; + pty_name = ptyname_volatile; + pty_in = ptyin_volatile; + pty_out = ptyout_volatile; argv = argv_volatile; std_in = stdin_volatile; std_out = stdout_volatile; @@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, if (pid == 0) #endif /* not WINDOWSNT */ { - bool pty_flag = pty != NULL; /* Make the pty be the controlling terminal of the process. */ #ifdef HAVE_PTYS dissociate_controlling_tty (); /* Make the pty's terminal the controlling terminal. */ - if (pty_flag && std_in >= 0) + if (pty_in && std_in >= 0) { #ifdef TIOCSCTTY /* We ignore the return value @@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, #endif } #if defined (LDISC1) - if (pty_flag && std_in >= 0) + if (pty_in && std_in >= 0) { struct termios t; tcgetattr (std_in, &t); @@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, } #else #if defined (NTTYDISC) && defined (TIOCSETD) - if (pty_flag && std_in >= 0) + if (pty_in && std_in >= 0) { /* Use new line discipline. */ int ldisc = NTTYDISC; @@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, both TIOCSCTTY is defined. */ /* Now close the pty (if we had it open) and reopen it. This makes the pty the controlling terminal of the subprocess. */ - if (pty_flag) + if (pty_name) { /* I wonder if emacs_close (emacs_open (pty, ...)) would work? */ - if (std_in >= 0) + if (pty_in && std_in >= 0) emacs_close (std_in); - std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0); - + int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0); + if (pty_in) + std_in = ptyfd; + if (pty_out) + std_out = ptyfd; if (std_in < 0) { - emacs_perror (pty); + emacs_perror (pty_name); _exit (EXIT_CANCELED); } @@ -1567,7 +1574,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, #endif /* not DONT_REOPEN_PTY */ #ifdef SETUP_SLAVE_PTY - if (pty_flag) + if (pty_in && std_in >= 0) { SETUP_SLAVE_PTY; } @@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, /* Stop blocking SIGCHLD in the child. */ unblock_child_signal (oldset); - if (pty_flag) + if (pty_out) child_setup_tty (std_out); #endif diff --git a/src/character.c b/src/character.c index c1a1b553891..968daccafa7 100644 --- a/src/character.c +++ b/src/character.c @@ -666,35 +666,26 @@ count_size_as_multibyte (const unsigned char *str, ptrdiff_t len) } -/* Convert unibyte text at STR of BYTES bytes to a multibyte text - that contains the same single-byte characters. It actually - converts all 8-bit characters to multibyte forms. It is assured - that we can use LEN bytes at STR as a work area and that is - enough. */ - +/* Convert unibyte text at SRC of NCHARS chars to a multibyte text + at DST, that contains the same single-byte characters. + Return the number of bytes written at DST. */ ptrdiff_t -str_to_multibyte (unsigned char *str, ptrdiff_t len, ptrdiff_t bytes) +str_to_multibyte (unsigned char *dst, const unsigned char *src, + ptrdiff_t nchars) { - unsigned char *p = str, *endp = str + bytes; - unsigned char *to; - - while (p < endp && *p < 0x80) p++; - if (p == endp) - return bytes; - to = p; - bytes = endp - p; - endp = str + len; - memmove (endp - bytes, p, bytes); - p = endp - bytes; - while (p < endp) + unsigned char *d = dst; + for (ptrdiff_t i = 0; i < nchars; i++) { - int c = *p++; - - if (c >= 0x80) - c = BYTE8_TO_CHAR (c); - to += CHAR_STRING (c, to); + unsigned char c = src[i]; + if (c <= 0x7f) + *d++ = c; + else + { + *d++ = 0xc0 + ((c >> 6) & 1); + *d++ = 0x80 + (c & 0x3f); + } } - return (to - str); + return d - dst; } /* Arrange multibyte text at STR of LEN bytes as a unibyte text. It @@ -734,31 +725,6 @@ str_as_unibyte (unsigned char *str, ptrdiff_t bytes) return (to - str); } -/* Convert eight-bit chars in SRC (in multibyte form) to the - corresponding byte and store in DST. CHARS is the number of - characters in SRC. The value is the number of bytes stored in DST. - Usually, the value is the same as CHARS, but is less than it if SRC - contains a non-ASCII, non-eight-bit character. */ - -ptrdiff_t -str_to_unibyte (const unsigned char *src, unsigned char *dst, ptrdiff_t chars) -{ - ptrdiff_t i; - - for (i = 0; i < chars; i++) - { - int c = string_char_advance (&src); - - if (CHAR_BYTE8_P (c)) - c = CHAR_TO_BYTE8 (c); - else if (! ASCII_CHAR_P (c)) - return i; - *dst++ = c; - } - return i; -} - - static ptrdiff_t string_count_byte8 (Lisp_Object string) { diff --git a/src/character.h b/src/character.h index 6ee6bcab205..6d0f035c2bb 100644 --- a/src/character.h +++ b/src/character.h @@ -567,10 +567,9 @@ extern int translate_char (Lisp_Object, int c); extern ptrdiff_t count_size_as_multibyte (const unsigned char *, ptrdiff_t); extern ptrdiff_t str_as_multibyte (unsigned char *, ptrdiff_t, ptrdiff_t, ptrdiff_t *); -extern ptrdiff_t str_to_multibyte (unsigned char *, ptrdiff_t, ptrdiff_t); +extern ptrdiff_t str_to_multibyte (unsigned char *dst, const unsigned char *src, + ptrdiff_t nchars); extern ptrdiff_t str_as_unibyte (unsigned char *, ptrdiff_t); -extern ptrdiff_t str_to_unibyte (const unsigned char *, unsigned char *, - ptrdiff_t); extern ptrdiff_t strwidth (const char *, ptrdiff_t); extern ptrdiff_t c_string_width (const unsigned char *, ptrdiff_t, int, ptrdiff_t *, ptrdiff_t *); diff --git a/src/charset.c b/src/charset.c index 9edbd4c8c84..bb59262fe98 100644 --- a/src/charset.c +++ b/src/charset.c @@ -1852,9 +1852,8 @@ although this usage is obsolescent. */) DEFUN ("encode-char", Fencode_char, Sencode_char, 2, 2, 0, doc: /* Encode the character CH into a code-point of CHARSET. -Return the encoded code-point, a fixnum if its value is small enough, -otherwise a bignum. -Return nil if CHARSET doesn't support CH. */) +Return the encoded code-point as an integer, +or nil if CHARSET doesn't support CH. */) (Lisp_Object ch, Lisp_Object charset) { int c, id; diff --git a/src/coding.c b/src/coding.c index aa32efc3f61..0ae8eb3282b 100644 --- a/src/coding.c +++ b/src/coding.c @@ -8194,7 +8194,7 @@ decode_coding_object (struct coding_system *coding, if (saved_pt >= 0) { /* This is the case of: - (BUFFERP (src_object) && EQ (src_object, dst_object)) + (BUFFERP (src_object) && BASE_EQ (src_object, dst_object)) As we have moved PT while replacing the original buffer contents, we must recover it now. */ set_buffer_internal (XBUFFER (src_object)); @@ -8283,7 +8283,7 @@ encode_coding_object (struct coding_system *coding, ptrdiff_t chars = to - from; ptrdiff_t bytes = to_byte - from_byte; Lisp_Object attrs; - ptrdiff_t saved_pt = -1, saved_pt_byte; + ptrdiff_t saved_pt = -1, saved_pt_byte UNINIT; bool need_marker_adjustment = 0; bool kill_src_buffer = 0; Lisp_Object old_deactivate_mark; @@ -8298,7 +8298,7 @@ encode_coding_object (struct coding_system *coding, attrs = CODING_ID_ATTRS (coding->id); bool same_buffer = false; - if (EQ (src_object, dst_object) && BUFFERP (src_object)) + if (BASE_EQ (src_object, dst_object) && BUFFERP (src_object)) { struct Lisp_Marker *tail; @@ -8379,7 +8379,7 @@ encode_coding_object (struct coding_system *coding, if (BUFFERP (dst_object)) { coding->dst_object = dst_object; - if (EQ (src_object, dst_object)) + if (BASE_EQ (src_object, dst_object)) { coding->dst_pos = from; coding->dst_pos_byte = from_byte; @@ -8434,7 +8434,7 @@ encode_coding_object (struct coding_system *coding, if (saved_pt >= 0) { /* This is the case of: - (BUFFERP (src_object) && EQ (src_object, dst_object)) + (BUFFERP (src_object) && BASE_EQ (src_object, dst_object)) As we have moved PT while replacing the original buffer contents, we must recover it now. */ set_buffer_internal (XBUFFER (src_object)); @@ -9416,7 +9416,7 @@ code_convert_region (Lisp_Object start, Lisp_Object end, setup_coding_system (coding_system, &coding); coding.mode |= CODING_MODE_LAST_BLOCK; - if (BUFFERP (dst_object) && !EQ (dst_object, src_object)) + if (BUFFERP (dst_object) && !BASE_EQ (dst_object, src_object)) { struct buffer *buf = XBUFFER (dst_object); ptrdiff_t buf_pt = BUF_PT (buf); @@ -10785,7 +10785,7 @@ usage: (find-operation-coding-system OPERATION ARGUMENTS...) */) && ((STRINGP (target) && STRINGP (XCAR (elt)) && fast_string_match (XCAR (elt), target) >= 0) - || (FIXNUMP (target) && EQ (target, XCAR (elt))))) + || (FIXNUMP (target) && BASE_EQ (target, XCAR (elt))))) { val = XCDR (elt); /* Here, if VAL is both a valid coding system and a valid @@ -11499,7 +11499,7 @@ DEFUN ("coding-system-put", Fcoding_system_put, Scoding_system_put, } ASET (attrs, coding_attr_plist, - Fplist_put (CODING_ATTR_PLIST (attrs), prop, val)); + plist_put (CODING_ATTR_PLIST (attrs), prop, val)); return val; } diff --git a/src/comp.c b/src/comp.c index c230536ac59..70e7d5a8bbf 100644 --- a/src/comp.c +++ b/src/comp.c @@ -2626,6 +2626,7 @@ emit_maybe_gc_or_quit (Lisp_Object insn) /* This is in charge of serializing an object and export a function to retrieve it at load time. */ +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" static void emit_static_object (const char *name, Lisp_Object obj) @@ -4397,7 +4398,7 @@ one for the file name and another for its contents, followed by .eln. */) { Lisp_Object match_idx = Fstring_match (XCAR (lds_re_tail), filename, Qnil, Qnil); - if (EQ (match_idx, make_fixnum (0))) + if (BASE_EQ (match_idx, make_fixnum (0))) { filename = Freplace_match (build_string ("//"), Qt, Qt, filename, Qnil); @@ -4681,6 +4682,7 @@ DEFUN ("comp--release-ctxt", Fcomp__release_ctxt, Scomp__release_ctxt, return Qt; } +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" DEFUN ("comp-native-driver-options-effective-p", Fcomp_native_driver_options_effective_p, @@ -4697,6 +4699,7 @@ DEFUN ("comp-native-driver-options-effective-p", } #pragma GCC diagnostic pop +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" DEFUN ("comp-native-compiler-options-effective-p", Fcomp_native_compiler_options_effective_p, @@ -4943,6 +4946,7 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, return filename; } +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" DEFUN ("comp-libgccjit-version", Fcomp_libgccjit_version, Scomp_libgccjit_version, 0, 0, 0, @@ -5013,8 +5017,6 @@ helper_GET_SYMBOL_WITH_POSITION (Lisp_Object a) /* `native-comp-eln-load-path' clean-up support code. */ -static Lisp_Object all_loaded_comp_units_h; - #ifdef WINDOWSNT static Lisp_Object return_nil (Lisp_Object arg) @@ -5056,11 +5058,12 @@ eln_load_path_final_clean_up (void) } /* This function puts the compilation unit in the - `all_loaded_comp_units_h` hashmap. */ + `Vcomp_loaded_comp_units_h` hashmap. */ static void register_native_comp_unit (Lisp_Object comp_u) { - Fputhash (XNATIVE_COMP_UNIT (comp_u)->file, comp_u, all_loaded_comp_units_h); + Fputhash ( + XNATIVE_COMP_UNIT (comp_u)->file, comp_u, Vcomp_loaded_comp_units_h); } @@ -5548,7 +5551,7 @@ LATE_LOAD has to be non-nil when loading for deferred compilation. */) struct Lisp_Native_Comp_Unit *comp_u = allocate_native_comp_unit (); Lisp_Object encoded_filename = ENCODE_FILE (filename); - if (!NILP (Fgethash (filename, all_loaded_comp_units_h, Qnil)) + if (!NILP (Fgethash (filename, Vcomp_loaded_comp_units_h, Qnil)) && !file_in_eln_sys_dir (filename) && !NILP (Ffile_writable_p (filename))) { @@ -5750,10 +5753,6 @@ compiled one. */); staticpro (&loadsearch_re_list); loadsearch_re_list = Qnil; - staticpro (&all_loaded_comp_units_h); - all_loaded_comp_units_h = - CALLN (Fmake_hash_table, QCweakness, Qkey_and_value, QCtest, Qequal); - DEFVAR_LISP ("comp-ctxt", Vcomp_ctxt, doc: /* The compiler context. */); Vcomp_ctxt = Qnil; @@ -5781,7 +5780,7 @@ For internal use. */); DEFVAR_LISP ("native-comp-eln-load-path", Vnative_comp_eln_load_path, doc: /* List of eln cache directories. -If a directory is non absolute is assumed to be relative to +If a directory is non absolute it is assumed to be relative to `invocation-directory'. `comp-native-version-dir' value is used as a sub-folder name inside each eln cache directory. @@ -5813,6 +5812,12 @@ For internal use. */); doc: /* When non-nil assume the file being compiled to be preloaded. */); + DEFVAR_LISP ("comp-loaded-comp-units-h", Vcomp_loaded_comp_units_h, + doc: /* Hash table recording all loaded compilation units. +file -> CU. */); + Vcomp_loaded_comp_units_h = + CALLN (Fmake_hash_table, QCweakness, Qvalue, QCtest, Qequal); + Fprovide (intern_c_string ("native-compile"), Qnil); #endif /* #ifdef HAVE_NATIVE_COMP */ diff --git a/src/composite.c b/src/composite.c index 4d69702171f..22422cca090 100644 --- a/src/composite.c +++ b/src/composite.c @@ -24,6 +24,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <config.h> +#include <stdlib.h> /* for qsort */ + #include "lisp.h" #include "character.h" #include "composite.h" @@ -1021,7 +1023,11 @@ composition_compute_stop_pos (struct composition_it *cmp_it, ptrdiff_t charpos, /* But we don't know where to stop the searching. */ endpos = NILP (string) ? BEGV - 1 : -1; /* Usually we don't reach ENDPOS because we stop searching - at an uncomposable character (NL, LRE, etc). */ + at an uncomposable character (NL, LRE, etc). In buffers + with long lines, however, NL might be far away, so + pretend that the buffer is smaller. */ + if (current_buffer->long_line_optimizations_p) + endpos = get_closer_narrowed_begv (cmp_it->parent_it->w, charpos); } } cmp_it->id = -1; @@ -1513,10 +1519,11 @@ struct position_record /* Similar to find_composition, but find an automatic composition instead. This function looks for automatic composition at or near position - POS of OBJECT (a buffer or a string). OBJECT defaults to the - current buffer. It must be assured that POS is not within a static - composition. Also, the current buffer must be displayed in some - window, otherwise the function will return FALSE. + POS of STRING object, either a buffer or a Lisp string. If STRING + is nil, it defaults to the current buffer. It must be assured that + POS is not within a static composition. Also, the current buffer + must be displayed in some window, otherwise the function will + return FALSE. If LIMIT is negative, and there's no composition that includes POS (i.e. starts at or before POS and ends at or after POS), return @@ -1525,8 +1532,8 @@ struct position_record MAX_AUTO_COMPOSITION_LOOKBACK, the maximum number of look-back for automatic compositions (3) -- this is a limitation imposed by composition rules in composition-function-table, which see. If - BACKLIM is negative, it stands for the beginning of OBJECT: BEGV - for a buffer or position zero for a string. + BACKLIM is negative, it stands for the beginning of STRING object: + BEGV for a buffer or position zero for a string. If LIMIT is positive, search for a composition forward (LIMIT > POS) or backward (LIMIT < POS). In this case, LIMIT bounds the @@ -1535,18 +1542,21 @@ struct position_record function can find a composition that starts after POS. BACKLIM limits how far back is the function allowed to look in - OBJECT while trying to find a position where it is safe to start - searching forward for compositions. Such a safe place is generally - the position after a character that can never be composed. + STRING object while trying to find a position where it is safe to + start searching forward for compositions. Such a safe place is + generally the position after a character that can never be + composed. If BACKLIM is negative, that means the first character position of - OBJECT; this is useful when calling the function for the first time - for a given buffer or string, since it is possible that a - composition begins before POS. However, if POS is very far from - the beginning of OBJECT, a negative value of BACKLIM could make the - function slow. Also, in this case the function may return START - and END that do not include POS, something that is not necessarily - wanted, and needs to be explicitly checked by the caller. + STRING object; this is useful when calling the function for the + first time for a given buffer or string, since it is possible that + a composition begins before POS. However, if POS is very far from + the beginning of STRING object, a negative value of BACKLIM could + make the function slow. For that reason, when STRING is a buffer + or nil, we restrict the search back to the first newline before + POS. Also, in this case the function may return START and END that + do not include POS, something that is not necessarily wanted, and + needs to be explicitly checked by the caller. When calling the function in a loop for the same buffer/string, the caller should generally set BACKLIM equal to POS, to avoid costly @@ -1585,7 +1595,23 @@ find_automatic_composition (ptrdiff_t pos, ptrdiff_t limit, ptrdiff_t backlim, cur.pos = pos; if (NILP (string)) { - head = backlim < 0 ? BEGV : backlim, tail = ZV, stop = GPT; + if (backlim < 0) + { + /* This assumes a newline can never be composed. */ + head = find_newline (pos, -1, 0, -1, -1, NULL, NULL, false); + } + else + head = backlim; + if (current_buffer->long_line_optimizations_p) + { + /* In buffers with very long lines, this function becomes very + slow. Pretend that the buffer is narrowed to make it fast. */ + ptrdiff_t begv = get_closer_narrowed_begv (w, window_point (w)); + if (pos > begv) + head = begv; + } + tail = ZV; + stop = GPT; cur.pos_byte = CHAR_TO_BYTE (cur.pos); cur.p = BYTE_POS_ADDR (cur.pos_byte); } @@ -1871,7 +1897,8 @@ should be ignored. */) else { CHECK_STRING (string); - validate_subarray (string, from, to, SCHARS (string), &frompos, &topos); + ptrdiff_t chars = SCHARS (string); + validate_subarray (string, from, to, chars, &frompos, &topos); if (! STRING_MULTIBYTE (string)) { ptrdiff_t i; @@ -1881,9 +1908,10 @@ should be ignored. */) error ("Attempt to shape unibyte text"); /* STRING is a pure-ASCII string, so we can convert it (or, rather, its copy) to multibyte and use that thereafter. */ - Lisp_Object string_copy = Fconcat (1, &string); - STRING_SET_MULTIBYTE (string_copy); - string = string_copy; + /* FIXME: Not clear why we need to do that: AFAICT the rest of + the code should work on an ASCII-only unibyte string just + as well (bug#56347). */ + string = make_multibyte_string (SSDATA (string), chars, chars); } frombyte = string_char_to_byte (string, frompos); } @@ -2028,6 +2056,54 @@ See `find-composition' for more details. */) return Fcons (make_fixnum (start), Fcons (make_fixnum (end), tail)); } +static int +compare_composition_rules (const void *r1, const void *r2) +{ + Lisp_Object vec1 = *(Lisp_Object *)r1, vec2 = *(Lisp_Object *)r2; + + return XFIXNAT (AREF (vec2, 1)) - XFIXNAT (AREF (vec1, 1)); +} + +DEFUN ("composition-sort-rules", Fcomposition_sort_rules, + Scomposition_sort_rules, 1, 1, 0, + doc: /* Sort composition RULES by their LOOKBACK parameter. + +If RULES include just one rule, return RULES. +Otherwise, return a new list of rules where all the rules are +arranged in decreasing order of the LOOKBACK parameter of the +rules (the second element of the rule's vector). This is required +when combining composition rules from different sources, because +of the way buffer text is examined for matching one of the rules. */) + (Lisp_Object rules) +{ + ptrdiff_t nrules; + USE_SAFE_ALLOCA; + + CHECK_LIST (rules); + nrules = list_length (rules); + if (nrules > 1) + { + ptrdiff_t i; + Lisp_Object *sortvec; + + SAFE_NALLOCA (sortvec, 1, nrules); + for (i = 0; i < nrules; i++) + { + Lisp_Object elt = XCAR (rules); + if (VECTORP (elt) && ASIZE (elt) == 3 && FIXNATP (AREF (elt, 1))) + sortvec[i] = elt; + else + error ("Invalid composition rule in RULES argument"); + rules = XCDR (rules); + } + qsort (sortvec, nrules, sizeof (Lisp_Object), compare_composition_rules); + rules = Flist (nrules, sortvec); + SAFE_FREE (); + } + + return rules; +} + void syms_of_composite (void) @@ -2159,4 +2235,5 @@ This list is auto-generated, you should not need to modify it. */); defsubr (&Sfind_composition_internal); defsubr (&Scomposition_get_gstring); defsubr (&Sclear_composition_cache); + defsubr (&Scomposition_sort_rules); } diff --git a/src/conf_post.h b/src/conf_post.h index 5108e44efbd..6ecebf36ab9 100644 --- a/src/conf_post.h +++ b/src/conf_post.h @@ -267,7 +267,7 @@ extern void _DebPrint (const char *fmt, ...); /* Tell regex.c to use a type compatible with Emacs. */ #define RE_TRANSLATE_TYPE Lisp_Object #define RE_TRANSLATE(TBL, C) char_table_translate (TBL, C) -#define RE_TRANSLATE_P(TBL) (!EQ (TBL, make_fixnum (0))) +#define RE_TRANSLATE_P(TBL) (!BASE_EQ (TBL, make_fixnum (0))) #endif /* Tell time_rz.c to use Emacs's getter and setter for TZ. diff --git a/src/data.c b/src/data.c index 2447ed72e4a..6921232665b 100644 --- a/src/data.c +++ b/src/data.c @@ -880,7 +880,7 @@ add_to_function_history (Lisp_Object symbol, Lisp_Object olddef) if (NILP (XCDR (tail)) && STRINGP (XCAR (tail))) file = XCAR (tail); - Lisp_Object tem = Fplist_member (past, file); + Lisp_Object tem = plist_member (past, file); if (!NILP (tem)) { /* New def from a file used before. Overwrite the previous record associated with this file. */ @@ -1552,8 +1552,13 @@ swap_in_symval_forwarding (struct Lisp_Symbol *symbol, struct Lisp_Buffer_Local_ /* Find the value of a symbol, returning Qunbound if it's not bound. This is helpful for code which just wants to get a variable's value if it has one, without signaling an error. - Note that it must not be possible to quit - within this function. Great care is required for this. */ + + This function is very similar to buffer_local_value, but we have + two separate code paths here since find_symbol_value has to be very + efficient, while buffer_local_value doesn't have to be. + + Note that it must not be possible to quit within this function. + Great care is required for this. */ Lisp_Object find_symbol_value (Lisp_Object symbol) @@ -2336,7 +2341,7 @@ From now on the default value will apply in this buffer. Return VARIABLE. */) forwarded objects won't work right. */ { Lisp_Object buf; XSETBUFFER (buf, current_buffer); - if (EQ (buf, blv->where)) + if (BASE_EQ (buf, blv->where)) swap_in_global_binding (sym); } @@ -3518,9 +3523,16 @@ representation. */) } DEFUN ("ash", Fash, Sash, 2, 2, 0, - doc: /* Return VALUE with its bits shifted left by COUNT. -If COUNT is negative, shifting is actually to the right. -In this case, the sign bit is duplicated. */) + doc: /* Return integer VALUE with its bits shifted left by COUNT bit positions. +If COUNT is negative, shift VALUE to the right instead. +VALUE and COUNT must be integers. +Mathematically, the return value is VALUE multiplied by 2 to the +power of COUNT, rounded down. If the result is non-zero, its sign +is the same as that of VALUE. +In terms of bits, when COUNT is positive, the function moves +the bits of VALUE to the left, adding zero bits on the right; when +COUNT is negative, it moves the bits of VALUE to the right, +discarding bits. */) (Lisp_Object value, Lisp_Object count) { CHECK_INTEGER (value); @@ -3528,7 +3540,7 @@ In this case, the sign bit is duplicated. */) if (! FIXNUMP (count)) { - if (EQ (value, make_fixnum (0))) + if (BASE_EQ (value, make_fixnum (0))) return value; if (mpz_sgn (*xbignum_val (count)) < 0) { @@ -3573,11 +3585,11 @@ Lisp_Object expt_integer (Lisp_Object x, Lisp_Object y) { /* Special cases for -1 <= x <= 1, which never overflow. */ - if (EQ (x, make_fixnum (1))) + if (BASE_EQ (x, make_fixnum (1))) return x; - if (EQ (x, make_fixnum (0))) - return EQ (x, y) ? make_fixnum (1) : x; - if (EQ (x, make_fixnum (-1))) + if (BASE_EQ (x, make_fixnum (0))) + return BASE_EQ (x, y) ? make_fixnum (1) : x; + if (BASE_EQ (x, make_fixnum (-1))) return ((FIXNUMP (y) ? XFIXNUM (y) & 1 : mpz_odd_p (*xbignum_val (y))) ? x : make_fixnum (1)); diff --git a/src/dired.c b/src/dired.c index cd50012ddc7..c2c099f0a5f 100644 --- a/src/dired.c +++ b/src/dired.c @@ -219,6 +219,13 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, } #endif + if (!NILP (full) && !STRING_MULTIBYTE (directory)) + { /* We will be concatenating 'directory' with local file name. + We always decode local file names, so in order to safely concatenate + them we need 'directory' to be decoded as well (bug#56469). */ + directory = DECODE_FILE (directory); + } + ptrdiff_t directory_nbytes = SBYTES (directory); re_match_object = Qt; @@ -263,9 +270,20 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, ptrdiff_t name_nbytes = SBYTES (name); ptrdiff_t nbytes = directory_nbytes + needsep + name_nbytes; ptrdiff_t nchars = SCHARS (directory) + needsep + SCHARS (name); - finalname = make_uninit_multibyte_string (nchars, nbytes); - if (nchars == nbytes) - STRING_SET_UNIBYTE (finalname); + /* DECODE_FILE may return non-ASCII unibyte strings (e.g. when + file-name-coding-system is 'binary'), so we don't know for sure + that the bytes we have follow our internal utf-8 representation + for multibyte strings. If nchars == nbytes we don't need to + care and just return a unibyte string; and if not, that means + one of 'name' or 'directory' is multibyte, in which case we + presume that the other one would also be multibyte if it + contained non-ASCII. + FIXME: This last presumption is broken when 'directory' is + multibyte (with non-ASCII), and 'name' is unibyte with non-ASCII + (because file-name-coding-system is 'binary'). */ + finalname = (nchars == nbytes) + ? make_uninit_string (nbytes) + : make_uninit_multibyte_string (nchars, nbytes); memcpy (SDATA (finalname), SDATA (directory), directory_nbytes); if (needsep) SSET (finalname, directory_nbytes, DIRECTORY_SEP); @@ -482,8 +500,8 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, decoded names in order to filter false positives, such as "a" falsely matching "a-ring". */ if (!NILP (file_encoding) - && !NILP (Fplist_get (Fcoding_system_plist (file_encoding), - Qdecomposed_characters))) + && !NILP (plist_get (Fcoding_system_plist (file_encoding), + Qdecomposed_characters))) { check_decoded = true; if (STRING_MULTIBYTE (file)) @@ -521,9 +539,9 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, name = DECODE_FILE (name); ptrdiff_t name_blen = SBYTES (name), name_len = SCHARS (name); if (completion_ignore_case - && !EQ (Fcompare_strings (name, zero, file_len, file, zero, file_len, - Qt), - Qt)) + && !BASE_EQ (Fcompare_strings (name, zero, file_len, file, zero, + file_len, Qt), + Qt)) continue; switch (dirent_type (dp)) @@ -603,10 +621,12 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, skip = name_len - elt_len; cmp_len = make_fixnum (elt_len); if (skip < 0 - || !EQ (Fcompare_strings (name, make_fixnum (skip), - Qnil, - elt, zero, cmp_len, Qt), - Qt)) + || !BASE_EQ (Fcompare_strings (name, + make_fixnum (skip), + Qnil, + elt, zero, cmp_len, + Qt), + Qt)) continue; } break; @@ -637,10 +657,12 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, skip = name_len - elt_len; cmp_len = make_fixnum (elt_len); if (skip < 0 - || !EQ (Fcompare_strings (name, make_fixnum (skip), - Qnil, - elt, zero, cmp_len, Qt), - Qt)) + || !BASE_EQ (Fcompare_strings (name, + make_fixnum (skip), + Qnil, + elt, zero, cmp_len, + Qt), + Qt)) continue; } break; @@ -699,7 +721,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, = Fcompare_strings (name, zero, make_fixnum (compare), file, zero, make_fixnum (compare), completion_ignore_case ? Qt : Qnil); - if (!EQ (cmp, Qt)) + if (!BASE_EQ (cmp, Qt)) continue; } @@ -722,7 +744,8 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, = Fcompare_strings (bestmatch, zero, make_fixnum (compare), name, zero, make_fixnum (compare), completion_ignore_case ? Qt : Qnil); - ptrdiff_t matchsize = EQ (cmp, Qt) ? compare : eabs (XFIXNUM (cmp)) - 1; + ptrdiff_t matchsize = BASE_EQ (cmp, Qt) + ? compare : eabs (XFIXNUM (cmp)) - 1; if (completion_ignore_case) { @@ -751,13 +774,13 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, file, zero, Qnil, Qnil), - EQ (Qt, cmp)) + BASE_EQ (Qt, cmp)) && (cmp = Fcompare_strings (bestmatch, zero, make_fixnum (SCHARS (file)), file, zero, Qnil, Qnil), - ! EQ (Qt, cmp)))) + ! BASE_EQ (Qt, cmp)))) bestmatch = name; } bestmatchsize = matchsize; diff --git a/src/dispextern.h b/src/dispextern.h index c7399ca2998..2f5f4335fe5 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -2287,6 +2287,8 @@ struct composition_it reverse order, and thus the grapheme clusters must be rendered from the last to the first. */ bool reversed_p; + /* Parent iterator. */ + struct it *parent_it; /** The following members contain information about the current grapheme cluster. */ @@ -2332,6 +2334,14 @@ struct it with which display_string was called. */ ptrdiff_t end_charpos; + /* Alternate begin position of the buffer that may be used to + optimize display (see the SET_WITH_NARROWED_BEGV macro). */ + ptrdiff_t narrowed_begv; + + /* Alternate end position of the buffer that may be used to + optimize display. */ + ptrdiff_t narrowed_zv; + /* C string to iterate over. Non-null means get characters from this string, otherwise characters are read from current_buffer or it->string. */ @@ -2342,9 +2352,6 @@ struct it used for overlay strings and strings from display properties. */ ptrdiff_t string_nchars; - /* Position at which redisplay end trigger functions should be run. */ - ptrdiff_t redisplay_end_trigger_charpos; - /* True means multibyte characters are enabled. */ bool_bf multibyte_p : 1; @@ -2742,11 +2749,11 @@ struct it /* The line number of point's line, or zero if not computed yet. */ ptrdiff_t pt_lnum; - /* Number of pixels to offset tab stops due to width fixup of the - first glyph that crosses first_visible_x. This is only needed on - GUI frames, only when display-line-numbers is in effect, and only - in hscrolled windows. */ - int tab_offset; + /* Number of pixels to adjust tab stops and stretch glyphs due to + width fixup of the first stretch glyph that crosses first_visible_x. + This is only needed on GUI frames, only when display-line-numbers + is in effect, and only in hscrolled windows. */ + int stretch_adjust; /* Left fringe bitmap number (enum fringe_bitmap_type). */ unsigned left_user_fringe_bitmap : FRINGE_ID_BITS; @@ -2867,18 +2874,17 @@ typedef struct { INLINE void reset_mouse_highlight (Mouse_HLInfo *hlinfo) { - - hlinfo->mouse_face_beg_row = hlinfo->mouse_face_beg_col = -1; - hlinfo->mouse_face_end_row = hlinfo->mouse_face_end_col = -1; - hlinfo->mouse_face_mouse_x = hlinfo->mouse_face_mouse_y = 0; - hlinfo->mouse_face_beg_x = hlinfo->mouse_face_end_x = 0; - hlinfo->mouse_face_face_id = DEFAULT_FACE_ID; - hlinfo->mouse_face_mouse_frame = NULL; - hlinfo->mouse_face_window = Qnil; - hlinfo->mouse_face_overlay = Qnil; - hlinfo->mouse_face_past_end = false; - hlinfo->mouse_face_hidden = false; - hlinfo->mouse_face_defer = false; + hlinfo->mouse_face_beg_row = hlinfo->mouse_face_beg_col = -1; + hlinfo->mouse_face_end_row = hlinfo->mouse_face_end_col = -1; + hlinfo->mouse_face_mouse_x = hlinfo->mouse_face_mouse_y = 0; + hlinfo->mouse_face_beg_x = hlinfo->mouse_face_end_x = 0; + hlinfo->mouse_face_face_id = DEFAULT_FACE_ID; + hlinfo->mouse_face_mouse_frame = NULL; + hlinfo->mouse_face_window = Qnil; + hlinfo->mouse_face_overlay = Qnil; + hlinfo->mouse_face_past_end = false; + hlinfo->mouse_face_hidden = false; + hlinfo->mouse_face_defer = false; } /*********************************************************************** @@ -3085,12 +3091,15 @@ struct image XFORM xform; #endif #ifdef HAVE_HAIKU - /* Non-zero if the image has not yet been transformed for display. */ - int have_be_transforms_p; + /* The affine transformation to apply to this image. */ + double transform[3][3]; - double be_rotate; - double be_scale_x; - double be_scale_y; + /* The original width and height of the image. */ + int original_width, original_height; + + /* Whether or not bilinear filtering should be used to "smooth" the + image. */ + bool use_bilinear_filtering; #endif /* Colors allocated for this image, if any. Allocated via xmalloc. */ @@ -3393,6 +3402,9 @@ void mark_window_display_accurate (Lisp_Object, bool); void redisplay_preserve_echo_area (int); void init_iterator (struct it *, struct window *, ptrdiff_t, ptrdiff_t, struct glyph_row *, enum face_id); +ptrdiff_t get_narrowed_begv (struct window *, ptrdiff_t); +ptrdiff_t get_narrowed_zv (struct window *, ptrdiff_t); +ptrdiff_t get_closer_narrowed_begv (struct window *, ptrdiff_t); void init_iterator_to_row_start (struct it *, struct window *, struct glyph_row *); void start_display (struct it *, struct window *, struct text_pos); @@ -3407,6 +3419,8 @@ int partial_line_height (struct it *it_origin); bool in_display_vector_p (struct it *); int frame_mode_line_height (struct frame *); extern bool redisplaying_p; +extern bool display_working_on_window_p; +extern void unwind_display_working_on_window (void); extern bool help_echo_showing_p; extern Lisp_Object help_echo_string, help_echo_window; extern Lisp_Object help_echo_object, previous_help_echo_string; @@ -3505,6 +3519,8 @@ extern unsigned row_hash (struct glyph_row *); extern bool buffer_flipping_blocked_p (void); +extern void update_redisplay_ticks (int, struct window *); + /* Defined in image.c */ #ifdef HAVE_WINDOW_SYSTEM @@ -3533,6 +3549,7 @@ struct image_cache *make_image_cache (void); void free_image_cache (struct frame *); void clear_image_caches (Lisp_Object); void mark_image_cache (struct image_cache *); +void image_prune_animation_caches (bool); bool valid_image_p (Lisp_Object); void prepare_image_for_display (struct frame *, struct image *); ptrdiff_t lookup_image (struct frame *, Lisp_Object, int); diff --git a/src/dispnew.c b/src/dispnew.c index 7a4d9f8710b..53a47c4b2f2 100644 --- a/src/dispnew.c +++ b/src/dispnew.c @@ -2732,12 +2732,25 @@ set_frame_matrix_frame (struct frame *f) operations in window matrices of frame_matrix_frame. */ static void -make_current (struct glyph_matrix *desired_matrix, struct glyph_matrix *current_matrix, int row) +make_current (struct glyph_matrix *desired_matrix, + struct glyph_matrix *current_matrix, int row) { struct glyph_row *current_row = MATRIX_ROW (current_matrix, row); struct glyph_row *desired_row = MATRIX_ROW (desired_matrix, row); bool mouse_face_p = current_row->mouse_face_p; + /* If we aborted redisplay of this window, a row in the desired + matrix might not have its hash computed. But update_window + relies on each row having its correct hash, so do it here if + needed. */ + if (!desired_row->hash + /* A glyph row that is not completely empty is unlikely to have + a zero hash value. */ + && !(!desired_row->used[0] + && !desired_row->used[1] + && !desired_row->used[2])) + desired_row->hash = row_hash (desired_row); + /* Do current_row = desired_row. This exchanges glyph pointers between both rows, and does a structure assignment otherwise. */ assign_row (current_row, desired_row); @@ -4284,11 +4297,11 @@ set_window_cursor_after_update (struct window *w) /* If we are showing a message instead of the mini-buffer, show the cursor for the message instead. */ && XWINDOW (minibuf_window) == w - && EQ (minibuf_window, echo_area_window) + && BASE_EQ (minibuf_window, echo_area_window) /* These cases apply only to the frame that contains the active mini-buffer window. */ && FRAME_HAS_MINIBUF_P (f) - && EQ (FRAME_MINIBUF_WINDOW (f), echo_area_window)) + && BASE_EQ (FRAME_MINIBUF_WINDOW (f), echo_area_window)) { cx = cy = vpos = hpos = 0; @@ -4948,13 +4961,13 @@ update_frame_1 (struct frame *f, bool force_p, bool inhibit_id_p, /* If we are showing a message instead of the mini-buffer, show the cursor for the message instead of for the (now hidden) mini-buffer contents. */ - || (EQ (minibuf_window, selected_window) - && EQ (minibuf_window, echo_area_window) + || (BASE_EQ (minibuf_window, selected_window) + && BASE_EQ (minibuf_window, echo_area_window) && !NILP (echo_area_buffer[0]))) /* These cases apply only to the frame that contains the active mini-buffer window. */ && FRAME_HAS_MINIBUF_P (f) - && EQ (FRAME_MINIBUF_WINDOW (f), echo_area_window)) + && BASE_EQ (FRAME_MINIBUF_WINDOW (f), echo_area_window)) { int top = WINDOW_TOP_EDGE_LINE (XWINDOW (FRAME_MINIBUF_WINDOW (f))); int col; @@ -6306,7 +6319,7 @@ pass nil for VARIABLE. */) { if (idx == ASIZE (state)) goto changed; - if (!EQ (AREF (state, idx++), frame)) + if (!BASE_EQ (AREF (state, idx++), frame)) goto changed; if (idx == ASIZE (state)) goto changed; @@ -6321,7 +6334,7 @@ pass nil for VARIABLE. */) continue; if (idx == ASIZE (state)) goto changed; - if (!EQ (AREF (state, idx++), buf)) + if (!BASE_EQ (AREF (state, idx++), buf)) goto changed; if (idx == ASIZE (state)) goto changed; diff --git a/src/doc.c b/src/doc.c index 14db3189f34..34b80d03aa9 100644 --- a/src/doc.c +++ b/src/doc.c @@ -346,7 +346,7 @@ string is passed through `substitute-command-keys'. */) /* If DOC is 0, it's typically because of a dumped file missing from the DOC file (bug in src/Makefile.in). */ - if (EQ (doc, make_fixnum (0))) + if (BASE_EQ (doc, make_fixnum (0))) doc = Qnil; if (FIXNUMP (doc) || CONSP (doc)) { @@ -400,7 +400,7 @@ aren't strings. */) tem = Fget (indirect, prop); } - if (EQ (tem, make_fixnum (0))) + if (BASE_EQ (tem, make_fixnum (0))) tem = Qnil; /* See if we want to look for the string in the DOC file. */ @@ -637,7 +637,7 @@ default_to_grave_quoting_style (void) Lisp_Object dv = DISP_CHAR_VECTOR (XCHAR_TABLE (Vstandard_display_table), LEFT_SINGLE_QUOTATION_MARK); return (VECTORP (dv) && ASIZE (dv) == 1 - && EQ (AREF (dv, 0), make_fixnum ('`'))); + && BASE_EQ (AREF (dv, 0), make_fixnum ('`'))); } DEFUN ("text-quoting-style", Ftext_quoting_style, diff --git a/src/editfns.c b/src/editfns.c index 17f0252969e..cd5cddee79f 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -161,7 +161,7 @@ DEFUN ("byte-to-string", Fbyte_to_string, Sbyte_to_string, 1, 1, 0, if (XFIXNUM (byte) < 0 || XFIXNUM (byte) > 255) error ("Invalid byte"); b = XFIXNUM (byte); - return make_string_from_bytes ((char *) &b, 1, 1); + return make_unibyte_string ((char *) &b, 1); } DEFUN ("string-to-char", Fstring_to_char, Sstring_to_char, 1, 1, 0, @@ -648,7 +648,7 @@ Field boundaries are not noticed if `inhibit-field-text-motion' is non-nil. */) prev_new = make_fixnum (XFIXNUM (new_pos) - 1); if (NILP (Vinhibit_field_text_motion) - && !EQ (new_pos, old_pos) + && !BASE_EQ (new_pos, old_pos) && (!NILP (Fget_char_property (new_pos, Qfield, Qnil)) || !NILP (Fget_char_property (old_pos, Qfield, Qnil)) /* To recognize field boundaries, we must also look at the @@ -709,9 +709,28 @@ Field boundaries are not noticed if `inhibit-field-text-motion' is non-nil. */) } -DEFUN ("line-beginning-position", - Fline_beginning_position, Sline_beginning_position, 0, 1, 0, - doc: /* Return the character position of the first character on the current line. +static ptrdiff_t +bol (Lisp_Object n, ptrdiff_t *out_count) +{ + ptrdiff_t bytepos, charpos, count; + + if (NILP (n)) + count = 0; + else if (FIXNUMP (n)) + count = clip_to_bounds (-BUF_BYTES_MAX, XFIXNUM (n) - 1, BUF_BYTES_MAX); + else + { + CHECK_INTEGER (n); + count = NILP (Fnatnump (n)) ? -BUF_BYTES_MAX : BUF_BYTES_MAX; + } + if (out_count) + *out_count = count; + scan_newline_from_point (count, &charpos, &bytepos); + return charpos; +} + +DEFUN ("pos-bol", Fpos_bol, Spos_bol, 0, 1, 0, + doc: /* Return the position of the first character on the current line. With optional argument N, scan forward N - 1 lines first. If the scan reaches the end of the buffer, return that position. @@ -720,6 +739,17 @@ position of the first character in logical order, i.e. the smallest character position on the logical line. See `vertical-motion' for movement by screen lines. +This function does not move point. Also see `line-beginning-position'. */) + (Lisp_Object n) +{ + return make_fixnum (bol (n, NULL)); +} + +DEFUN ("line-beginning-position", + Fline_beginning_position, Sline_beginning_position, 0, 1, 0, + doc: /* Return the position of the first character in the current line/field. +This function is like `pos-bol' (which see), but respects fields. + This function constrains the returned position to the current field unless that position would be on a different line from the original, unconstrained result. If N is nil or 1, and a front-sticky field @@ -729,28 +759,33 @@ boundaries, bind `inhibit-field-text-motion' to t. This function does not move point. */) (Lisp_Object n) { - ptrdiff_t charpos, bytepos, count; + ptrdiff_t count, charpos = bol (n, &count); + /* Return END constrained to the current input field. */ + return Fconstrain_to_field (make_fixnum (charpos), make_fixnum (PT), + count != 0 ? Qt : Qnil, + Qt, Qnil); +} + +static ptrdiff_t +eol (Lisp_Object n) +{ + ptrdiff_t count; if (NILP (n)) - count = 0; + count = 1; else if (FIXNUMP (n)) - count = clip_to_bounds (-BUF_BYTES_MAX, XFIXNUM (n) - 1, BUF_BYTES_MAX); + count = clip_to_bounds (-BUF_BYTES_MAX, XFIXNUM (n), BUF_BYTES_MAX); else { CHECK_INTEGER (n); count = NILP (Fnatnump (n)) ? -BUF_BYTES_MAX : BUF_BYTES_MAX; } - - scan_newline_from_point (count, &charpos, &bytepos); - - /* Return END constrained to the current input field. */ - return Fconstrain_to_field (make_fixnum (charpos), make_fixnum (PT), - count != 0 ? Qt : Qnil, - Qt, Qnil); + return find_before_next_newline (PT, 0, count - (count <= 0), + NULL); } -DEFUN ("line-end-position", Fline_end_position, Sline_end_position, 0, 1, 0, - doc: /* Return the character position of the last character on the current line. +DEFUN ("pos-eol", Fpos_eol, Spos_eol, 0, 1, 0, + doc: /* Return the position of the last character on the current line. With argument N not nil or 1, move forward N - 1 lines first. If scan reaches end of buffer, return that position. @@ -758,6 +793,19 @@ This function ignores text display directionality; it returns the position of the last character in logical order, i.e. the largest character position on the line. +This function does not move point. Also see `line-end-position'. */) + (Lisp_Object n) +{ + return make_fixnum (eol (n)); +} + +DEFUN ("line-end-position", Fline_end_position, Sline_end_position, 0, 1, 0, + doc: /* Return the position of the last character in the current line/field. +With argument N not nil or 1, move forward N - 1 lines first. +If scan reaches end of buffer, return that position. + +This function is like `pos-eol' (which see), but respects fields. + This function constrains the returned position to the current field unless that would be on a different line from the original, unconstrained result. If N is nil or 1, and a rear-sticky field ends @@ -767,24 +815,8 @@ boundaries bind `inhibit-field-text-motion' to t. This function does not move point. */) (Lisp_Object n) { - ptrdiff_t clipped_n; - ptrdiff_t end_pos; - ptrdiff_t orig = PT; - - if (NILP (n)) - clipped_n = 1; - else if (FIXNUMP (n)) - clipped_n = clip_to_bounds (-BUF_BYTES_MAX, XFIXNUM (n), BUF_BYTES_MAX); - else - { - CHECK_INTEGER (n); - clipped_n = NILP (Fnatnump (n)) ? -BUF_BYTES_MAX : BUF_BYTES_MAX; - } - end_pos = find_before_next_newline (orig, 0, clipped_n - (clipped_n <= 0), - NULL); - /* Return END_POS constrained to the current input field. */ - return Fconstrain_to_field (make_fixnum (end_pos), make_fixnum (orig), + return Fconstrain_to_field (make_fixnum (eol (n)), make_fixnum (PT), Qnil, Qt, Qnil); } @@ -797,7 +829,7 @@ save_excursion_save (union specbinding *pdl) pdl->unwind_excursion.marker = Fpoint_marker (); /* Selected window if current buffer is shown in it, nil otherwise. */ pdl->unwind_excursion.window - = (EQ (XWINDOW (selected_window)->contents, Fcurrent_buffer ()) + = (BASE_EQ (XWINDOW (selected_window)->contents, Fcurrent_buffer ()) ? selected_window : Qnil); } @@ -821,7 +853,7 @@ save_excursion_restore (Lisp_Object marker, Lisp_Object window) /* If buffer was visible in a window, and a different window was selected, and the old selected window is still showing this buffer, restore point in that window. */ - if (WINDOWP (window) && !EQ (window, selected_window)) + if (WINDOWP (window) && !BASE_EQ (window, selected_window)) { /* Set window point if WINDOW is live and shows the current buffer. */ Lisp_Object contents = XWINDOW (window)->contents; @@ -1172,8 +1204,7 @@ This ignores the environment variables LOGNAME and USER, so it differs from } DEFUN ("user-uid", Fuser_uid, Suser_uid, 0, 0, 0, - doc: /* Return the effective uid of Emacs. -Value is a fixnum, if it's small enough, otherwise a bignum. */) + doc: /* Return the effective uid of Emacs, as an integer. */) (void) { uid_t euid = geteuid (); @@ -1181,8 +1212,7 @@ Value is a fixnum, if it's small enough, otherwise a bignum. */) } DEFUN ("user-real-uid", Fuser_real_uid, Suser_real_uid, 0, 0, 0, - doc: /* Return the real uid of Emacs. -Value is a fixnum, if it's small enough, otherwise a bignum. */) + doc: /* Return the real uid of Emacs, as an integer. */) (void) { uid_t uid = getuid (); @@ -1208,8 +1238,7 @@ Return nil if a group with such GID does not exists or is not known. */) } DEFUN ("group-gid", Fgroup_gid, Sgroup_gid, 0, 0, 0, - doc: /* Return the effective gid of Emacs. -Value is a fixnum, if it's small enough, otherwise a bignum. */) + doc: /* Return the effective gid of Emacs, as an integer. */) (void) { gid_t egid = getegid (); @@ -1217,8 +1246,7 @@ Value is a fixnum, if it's small enough, otherwise a bignum. */) } DEFUN ("group-real-gid", Fgroup_real_gid, Sgroup_real_gid, 0, 0, 0, - doc: /* Return the real gid of Emacs. -Value is a fixnum, if it's small enough, otherwise a bignum. */) + doc: /* Return the real gid of Emacs, as an integer. */) (void) { gid_t gid = getgid (); @@ -1306,8 +1334,7 @@ DEFUN ("system-name", Fsystem_name, Ssystem_name, 0, 0, 0, } DEFUN ("emacs-pid", Femacs_pid, Semacs_pid, 0, 0, 0, - doc: /* Return the process ID of Emacs, as a number. -Value is a fixnum, if it's small enough, otherwise a bignum. */) + doc: /* Return the process ID of Emacs, as an integer. */) (void) { pid_t pid = getpid (); @@ -2658,9 +2685,17 @@ DEFUN ("delete-and-extract-region", Fdelete_and_extract_region, DEFUN ("widen", Fwiden, Swiden, 0, 0, "", doc: /* Remove restrictions (narrowing) from current buffer. -This allows the buffer's full text to be seen and edited. */) +This allows the buffer's full text to be seen and edited. + +Note that, when the current buffer contains one or more lines whose +length is above `long-line-threshold', Emacs may decide to leave, for +performance reasons, the accessible portion of the buffer unchanged +after this function is called from low-level hooks, such as +`jit-lock-functions' or `post-command-hook'. */) (void) { + if (! NILP (Vrestrictions_locked)) + return Qnil; if (BEG != BEGV || Z != ZV) current_buffer->clip_changed = 1; BEGV = BEG; @@ -2671,17 +2706,23 @@ This allows the buffer's full text to be seen and edited. */) return Qnil; } -DEFUN ("narrow-to-region", Fnarrow_to_region, Snarrow_to_region, 2, 2, "r", - doc: /* Restrict editing in this buffer to the current region. -The rest of the text becomes temporarily invisible and untouchable -but is not deleted; if you save the buffer in a file, the invisible -text is included in the file. \\[widen] makes all visible again. -See also `save-restriction'. +static void +unwind_locked_begv (Lisp_Object point_min) +{ + SET_BUF_BEGV (current_buffer, XFIXNUM (point_min)); +} -When calling from Lisp, pass two arguments START and END: -positions (integers or markers) bounding the text that should -remain visible. */) - (Lisp_Object start, Lisp_Object end) +static void +unwind_locked_zv (Lisp_Object point_max) +{ + SET_BUF_ZV (current_buffer, XFIXNUM (point_max)); +} + +/* Internal function for Fnarrow_to_region, meant to be used with a + third argument 'true', in which case it should be followed by "specbind + (Qrestrictions_locked, Qt)". */ +Lisp_Object +narrow_to_region_internal (Lisp_Object start, Lisp_Object end, bool lock) { EMACS_INT s = fix_position (start), e = fix_position (end); @@ -2690,14 +2731,36 @@ remain visible. */) EMACS_INT tem = s; s = e; e = tem; } - if (!(BEG <= s && s <= e && e <= Z)) - args_out_of_range (start, end); + if (lock) + { + if (!(BEGV <= s && s <= e && e <= ZV)) + args_out_of_range (start, end); - if (BEGV != s || ZV != e) - current_buffer->clip_changed = 1; + if (BEGV != s || ZV != e) + current_buffer->clip_changed = 1; + + record_unwind_protect (restore_point_unwind, Fpoint_marker ()); + record_unwind_protect (unwind_locked_begv, Fpoint_min ()); + record_unwind_protect (unwind_locked_zv, Fpoint_max ()); + + SET_BUF_BEGV (current_buffer, s); + SET_BUF_ZV (current_buffer, e); + } + else + { + if (! NILP (Vrestrictions_locked)) + return Qnil; + + if (!(BEG <= s && s <= e && e <= Z)) + args_out_of_range (start, end); + + if (BEGV != s || ZV != e) + current_buffer->clip_changed = 1; + + SET_BUF_BEGV (current_buffer, s); + SET_BUF_ZV (current_buffer, e); + } - SET_BUF_BEGV (current_buffer, s); - SET_BUF_ZV (current_buffer, e); if (PT < s) SET_PT (s); if (e < PT) @@ -2707,6 +2770,27 @@ remain visible. */) return Qnil; } +DEFUN ("narrow-to-region", Fnarrow_to_region, Snarrow_to_region, 2, 2, "r", + doc: /* Restrict editing in this buffer to the current region. +The rest of the text becomes temporarily invisible and untouchable +but is not deleted; if you save the buffer in a file, the invisible +text is included in the file. \\[widen] makes all visible again. +See also `save-restriction'. + +When calling from Lisp, pass two arguments START and END: +positions (integers or markers) bounding the text that should +remain visible. + +Note that, when the current buffer contains one or more lines whose +length is above `long-line-threshold', Emacs may decide to leave, for +performance reasons, the accessible portion of the buffer unchanged +after this function is called from low-level hooks, such as +`jit-lock-functions' or `post-command-hook'. */) + (Lisp_Object start, Lisp_Object end) +{ + return narrow_to_region_internal (start, end, false); +} + Lisp_Object save_restriction_save (void) { @@ -2843,7 +2927,7 @@ otherwise MSGID-PLURAL. */) CHECK_INTEGER (n); /* Placeholder implementation until we get our act together. */ - return EQ (n, make_fixnum (1)) ? msgid : msgid_plural; + return BASE_EQ (n, make_fixnum (1)) ? msgid : msgid_plural; } DEFUN ("message", Fmessage, Smessage, 1, MANY, 0, @@ -4517,6 +4601,15 @@ This variable is experimental; email 32252@debbugs.gnu.org if you need it to be non-nil. */); binary_as_unsigned = false; + DEFSYM (Qrestrictions_locked, "restrictions-locked"); + DEFVAR_LISP ("restrictions-locked", Vrestrictions_locked, + doc: /* If non-nil, restrictions are currently locked. + +This happens when `narrow-to-region', which see, is called from Lisp +with an optional argument LOCK non-nil. */); + Vrestrictions_locked = Qnil; + Funintern (Qrestrictions_locked, Qnil); + defsubr (&Spropertize); defsubr (&Schar_equal); defsubr (&Sgoto_char); @@ -4549,6 +4642,8 @@ it to be non-nil. */); defsubr (&Sline_beginning_position); defsubr (&Sline_end_position); + defsubr (&Spos_bol); + defsubr (&Spos_eol); defsubr (&Ssave_excursion); defsubr (&Ssave_current_buffer); diff --git a/src/emacs.c b/src/emacs.c index 189692a02ea..8f19c486553 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -979,20 +979,24 @@ load_pdump (int argc, char **argv) sprintf (dump_file, "%s%c%s-%s%s", path_exec, DIRECTORY_SEP, argv0_base, hexbuf, suffix); #if !defined (NS_SELF_CONTAINED) - /* Assume the Emacs binary lives in a sibling directory as set up by - the default installation configuration. */ - const char *go_up = "../../../../bin/"; - needed += (strip_suffix ? strlen (strip_suffix) : 0) - - strlen (suffix) + strlen (go_up); - if (exec_bufsize < needed) + if (!(emacs_executable && *emacs_executable)) { - xfree (emacs_executable); - emacs_executable = xpalloc (NULL, &exec_bufsize, needed - exec_bufsize, - -1, 1); + /* If we didn't find the Emacs binary, assume that it lives in a + sibling directory as set up by the default installation + configuration. */ + const char *go_up = "../../../../bin/"; + needed += (strip_suffix ? strlen (strip_suffix) : 0) + - strlen (suffix) + strlen (go_up); + if (exec_bufsize < needed) + { + xfree (emacs_executable); + emacs_executable = xpalloc (NULL, &exec_bufsize, + needed - exec_bufsize, -1, 1); + } + sprintf (emacs_executable, "%s%c%s%s%s", + path_exec, DIRECTORY_SEP, go_up, argv0_base, + strip_suffix ? strip_suffix : ""); } - sprintf (emacs_executable, "%s%c%s%s%s", - path_exec, DIRECTORY_SEP, go_up, argv0_base, - strip_suffix ? strip_suffix : ""); #endif result = pdumper_load (dump_file, emacs_executable); @@ -1930,9 +1934,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem init_bignum (); init_threads (); init_eval (); -#ifdef HAVE_PGTK - init_pgtkterm (); /* Must come before `init_atimer'. */ -#endif running_asynch_code = 0; init_random (); init_xfaces (); @@ -1944,6 +1945,11 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem if (!initialized) syms_of_comp (); + /* Do less garbage collection in batch mode (since these tend to be + more short-lived, and the memory is returned to the OS on exit + anyway). */ + Vgc_cons_percentage = make_float (noninteractive? 1.0: 0.1); + no_loadup = argmatch (argv, argc, "-nl", "--no-loadup", 6, NULL, &skip_args); @@ -2419,11 +2425,11 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #if defined WINDOWSNT || defined HAVE_NTGUI globals_of_w32select (); #endif + } #ifdef HAVE_HAIKU - init_haiku_select (); + init_haiku_select (); #endif - } init_charset (); diff --git a/src/eval.c b/src/eval.c index 45ddbab2a2c..6ea7a473f60 100644 --- a/src/eval.c +++ b/src/eval.c @@ -57,6 +57,12 @@ Lisp_Object Vrun_hooks; /* FIXME: We should probably get rid of this! */ Lisp_Object Vsignaling_function; +/* The handler structure which will catch errors in Lisp hooks called + from redisplay. We do not use it for this; we compare it with the + handler which is about to be used in signal_or_quit, and if it + matches, cause a backtrace to be generated. */ +static struct handler *redisplay_deep_handler; + /* These would ordinarily be static, but they need to be visible to GDB. */ bool backtrace_p (union specbinding *) EXTERNALLY_VISIBLE; Lisp_Object *backtrace_args (union specbinding *) EXTERNALLY_VISIBLE; @@ -246,6 +252,7 @@ init_eval (void) lisp_eval_depth = 0; /* This is less than the initial value of num_nonmacro_input_events. */ when_entered_debugger = -1; + redisplay_deep_handler = NULL; } /* Ensure that *M is at least A + B if possible, or is its maximum @@ -333,7 +340,8 @@ call_debugger (Lisp_Object arg) /* Interrupting redisplay and resuming it later is not safe under all circumstances. So, when the debugger returns, abort the interrupted redisplay by going back to the top-level. */ - if (debug_while_redisplaying) + if (debug_while_redisplaying + && !EQ (Vdebugger, Qdebug_early)) Ftop_level (); return unbind_to (count, val); @@ -593,16 +601,19 @@ The return value is BASE-VARIABLE. */) if (SYMBOL_CONSTANT_P (new_alias)) /* Making it an alias effectively changes its value. */ - error ("Cannot make a constant an alias"); + error ("Cannot make a constant an alias: %s", + SDATA (SYMBOL_NAME (new_alias))); sym = XSYMBOL (new_alias); switch (sym->u.s.redirect) { case SYMBOL_FORWARDED: - error ("Cannot make an internal variable an alias"); + error ("Cannot make a built-in variable an alias: %s", + SDATA (SYMBOL_NAME (new_alias))); case SYMBOL_LOCALIZED: - error ("Don't know how to make a localized variable an alias"); + error ("Don't know how to make a buffer-local variable an alias: %s", + SDATA (SYMBOL_NAME (new_alias))); case SYMBOL_PLAINVAL: case SYMBOL_VARALIAS: break; @@ -633,7 +644,8 @@ The return value is BASE-VARIABLE. */) for (p = specpdl_ptr; p > specpdl; ) if ((--p)->kind >= SPECPDL_LET && (EQ (new_alias, specpdl_symbol (p)))) - error ("Don't know how to make a let-bound variable an alias"); + error ("Don't know how to make a let-bound variable an alias: %s", + SDATA (SYMBOL_NAME (new_alias))); } if (sym->u.s.trapped_write == SYMBOL_TRAPPED_WRITE) @@ -1552,12 +1564,16 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *), ptrdiff_t nargs, Lisp_Object *args)) { + struct handler *old_deep = redisplay_deep_handler; struct handler *c = push_handler (handlers, CONDITION_CASE); + if (redisplaying_p) + redisplay_deep_handler = c; if (sys_setjmp (c->jmp)) { Lisp_Object val = handlerlist->val; clobbered_eassert (handlerlist == c); handlerlist = handlerlist->next; + redisplay_deep_handler = old_deep; return hfun (val, nargs, args); } else @@ -1565,6 +1581,7 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *), Lisp_Object val = bfun (nargs, args); eassert (handlerlist == c); handlerlist = c->next; + redisplay_deep_handler = old_deep; return val; } } @@ -1697,6 +1714,11 @@ quit (void) return signal_or_quit (Qquit, Qnil, true); } +/* Has an error in redisplay giving rise to a backtrace occurred as + yet in the current command? This gets reset in the command + loop. */ +bool backtrace_yet = false; + /* Signal an error, or quit. ERROR_SYMBOL and DATA are as with Fsignal. If KEYBOARD_QUIT, this is a quit; ERROR_SYMBOL should be Qquit and DATA should be Qnil, and this function may return. @@ -1812,6 +1834,40 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool keyboard_quit) unbind_to (count, Qnil); } + /* If an error is signalled during a Lisp hook in redisplay, write a + backtrace into the buffer *Redisplay-trace*. */ + if (!debugger_called && !NILP (error_symbol) + && backtrace_on_redisplay_error + && (NILP (clause) || h == redisplay_deep_handler) + && NILP (Vinhibit_debugger) + && !NILP (Ffboundp (Qdebug_early))) + { + max_ensure_room (&max_lisp_eval_depth, lisp_eval_depth, 100); + specpdl_ref count = SPECPDL_INDEX (); + ptrdiff_t counti = specpdl_ref_to_count (count); + AUTO_STRING (redisplay_trace, "*Redisplay_trace*"); + Lisp_Object redisplay_trace_buffer; + AUTO_STRING (gap, "\n\n\n\n"); /* Separates things in *Redisplay-trace* */ + Lisp_Object delayed_warning; + max_ensure_room (&max_specpdl_size, counti, 200); + redisplay_trace_buffer = Fget_buffer_create (redisplay_trace, Qnil); + current_buffer = XBUFFER (redisplay_trace_buffer); + if (!backtrace_yet) /* Are we on the first backtrace of the command? */ + Ferase_buffer (); + else + Finsert (1, &gap); + backtrace_yet = true; + specbind (Qstandard_output, redisplay_trace_buffer); + specbind (Qdebugger, Qdebug_early); + call_debugger (list2 (Qerror, Fcons (error_symbol, data))); + unbind_to (count, Qnil); + delayed_warning = make_string + ("Error in a redisplay Lisp hook. See buffer *Redisplay_trace*", 61); + + Vdelayed_warnings_list = Fcons (list2 (Qerror, delayed_warning), + Vdelayed_warnings_list); + } + if (!NILP (clause)) { Lisp_Object unwind_data @@ -2208,7 +2264,7 @@ this does nothing and returns nil. */) && !AUTOLOADP (XSYMBOL (function)->u.s.function)) return Qnil; - if (!NILP (Vpurify_flag) && EQ (docstring, make_fixnum (0))) + if (!NILP (Vpurify_flag) && BASE_EQ (docstring, make_fixnum (0))) /* `read1' in lread.c has found the docstring starting with "\ and assumed the docstring will be provided by Snarf-documentation, so it passed us 0 instead. But that leads to accidental sharing in purecopy's @@ -2229,7 +2285,7 @@ un_autoload (Lisp_Object oldqueue) while (CONSP (queue)) { Lisp_Object first = XCAR (queue); - if (CONSP (first) && EQ (XCAR (first), make_fixnum (0))) + if (CONSP (first) && BASE_EQ (XCAR (first), make_fixnum (0))) Vfeatures = XCDR (first); else Ffset (first, Fcar (Fcdr (Fget (first, Qfunction_history)))); @@ -2286,8 +2342,13 @@ it defines a macro. */) /* This is to make sure that loadup.el gives a clear picture of what files are preloaded and when. */ if (will_dump_p () && !will_bootstrap_p ()) - error ("Attempt to autoload %s while preparing to dump", - SDATA (SYMBOL_NAME (funname))); + { + /* Avoid landing here recursively while outputting the + backtrace from the error. */ + gflags.will_dump_ = false; + error ("Attempt to autoload %s while preparing to dump", + SDATA (SYMBOL_NAME (funname))); + } CHECK_SYMBOL (funname); @@ -3464,7 +3525,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.where = Fcurrent_buffer (); eassert (sym->u.s.redirect != SYMBOL_LOCALIZED - || (EQ (SYMBOL_BLV (sym)->where, Fcurrent_buffer ()))); + || (BASE_EQ (SYMBOL_BLV (sym)->where, Fcurrent_buffer ()))); if (sym->u.s.redirect == SYMBOL_LOCALIZED) { @@ -4282,6 +4343,11 @@ Does not apply if quit is handled by a `condition-case'. */); DEFVAR_BOOL ("debug-on-next-call", debug_on_next_call, doc: /* Non-nil means enter debugger before next `eval', `apply' or `funcall'. */); + DEFVAR_BOOL ("backtrace-on-redisplay-error", backtrace_on_redisplay_error, + doc: /* Non-nil means create a backtrace if a lisp error occurs in redisplay. +The backtrace is written to buffer *Redisplay-trace*. */); + backtrace_on_redisplay_error = false; + DEFVAR_BOOL ("debugger-may-continue", debugger_may_continue, doc: /* Non-nil means debugger may continue execution. This is nil when the debugger is called under circumstances where it diff --git a/src/fileio.c b/src/fileio.c index e29685e07bf..9697f6c8cf1 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -708,7 +708,7 @@ This function does not grok magic file names. */) memset (data + prefix_len, 'X', nX); memcpy (data + prefix_len + nX, SSDATA (encoded_suffix), suffix_len); int kind = (NILP (dir_flag) ? GT_FILE - : EQ (dir_flag, make_fixnum (0)) ? GT_NOCREATE + : BASE_EQ (dir_flag, make_fixnum (0)) ? GT_NOCREATE : GT_DIR); int fd = gen_tempname (data, suffix_len, O_BINARY | O_CLOEXEC, kind); bool failed = fd < 0; @@ -2601,9 +2601,9 @@ is case-insensitive. */) if (err <= 0) return err < 0 ? Qt : Qnil; Lisp_Object parent = file_name_directory (filename); - /* Avoid infinite loop if the root has trouble - (impossible?). */ - if (!NILP (Fstring_equal (parent, filename))) + /* Avoid infinite loop if the root has trouble (if that's even possible). + Without a parent, we just don't know and return nil as well. */ + if (!STRINGP (parent) || !NILP (Fstring_equal (parent, filename))) return Qnil; filename = parent; } @@ -3532,8 +3532,9 @@ DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 3, Only the 12 low bits of MODE are used. If optional FLAG is `nofollow', do not follow FILENAME if it is a symbolic link. -Interactively, mode bits are read by `read-file-modes', which accepts -symbolic notation, like the `chmod' command from GNU Coreutils. */) +Interactively, prompt for FILENAME, and read MODE with +`read-file-modes', which accepts symbolic notation, like the `chmod' +command from GNU Coreutils. */) (Lisp_Object filename, Lisp_Object mode, Lisp_Object flag) { CHECK_FIXNUM (mode); @@ -5831,6 +5832,15 @@ See Info node `(elisp)Modification Time' for more details. */) return Qnil; } +Lisp_Object +buffer_visited_file_modtime (struct buffer *buf) +{ + int ns = buf->modtime.tv_nsec; + if (ns < 0) + return make_fixnum (UNKNOWN_MODTIME_NSECS - ns); + return make_lisp_time (buf->modtime); +} + DEFUN ("visited-file-modtime", Fvisited_file_modtime, Svisited_file_modtime, 0, 0, 0, doc: /* Return the current buffer's recorded visited file modification time. @@ -5840,10 +5850,7 @@ visited file doesn't exist. See Info node `(elisp)Modification Time' for more details. */) (void) { - int ns = current_buffer->modtime.tv_nsec; - if (ns < 0) - return make_fixnum (UNKNOWN_MODTIME_NSECS - ns); - return make_lisp_time (current_buffer->modtime); + return buffer_visited_file_modtime (current_buffer); } DEFUN ("set-visited-file-modtime", Fset_visited_file_modtime, @@ -5870,6 +5877,8 @@ in `current-time' or an integer flag as returned by `visited-file-modtime'. */) current_buffer->modtime = mtime; current_buffer->modtime_size = -1; } + else if (current_buffer->base_buffer) + error ("An indirect buffer does not have a visited file"); else { register Lisp_Object filename; diff --git a/src/fns.c b/src/fns.c index 6094c00b27c..7e78bba3a04 100644 --- a/src/fns.c +++ b/src/fns.c @@ -589,20 +589,21 @@ Do NOT use this function to compare file names for equality. */) #endif /* !__STDC_ISO_10646__, !WINDOWSNT */ } -static Lisp_Object concat (ptrdiff_t nargs, Lisp_Object *args, - Lisp_Object last_tail, bool vector_target); -static Lisp_Object concat_strings (ptrdiff_t nargs, Lisp_Object *args); +static Lisp_Object concat_to_list (ptrdiff_t nargs, Lisp_Object *args, + Lisp_Object last_tail); +static Lisp_Object concat_to_vector (ptrdiff_t nargs, Lisp_Object *args); +static Lisp_Object concat_to_string (ptrdiff_t nargs, Lisp_Object *args); Lisp_Object concat2 (Lisp_Object s1, Lisp_Object s2) { - return concat_strings (2, ((Lisp_Object []) {s1, s2})); + return concat_to_string (2, ((Lisp_Object []) {s1, s2})); } Lisp_Object concat3 (Lisp_Object s1, Lisp_Object s2, Lisp_Object s3) { - return concat_strings (3, ((Lisp_Object []) {s1, s2, s3})); + return concat_to_string (3, ((Lisp_Object []) {s1, s2, s3})); } DEFUN ("append", Fappend, Sappend, 0, MANY, 0, @@ -615,7 +616,7 @@ usage: (append &rest SEQUENCES) */) { if (nargs == 0) return Qnil; - return concat (nargs - 1, args, args[nargs - 1], false); + return concat_to_list (nargs - 1, args, args[nargs - 1]); } DEFUN ("concat", Fconcat, Sconcat, 0, MANY, 0, @@ -628,7 +629,7 @@ to be `eq'. usage: (concat &rest SEQUENCES) */) (ptrdiff_t nargs, Lisp_Object *args) { - return concat_strings (nargs, args); + return concat_to_string (nargs, args); } DEFUN ("vconcat", Fvconcat, Svconcat, 0, MANY, 0, @@ -638,7 +639,7 @@ Each argument may be a list, vector or string. usage: (vconcat &rest SEQUENCES) */) (ptrdiff_t nargs, Lisp_Object *args) { - return concat (nargs, args, Qnil, true); + return concat_to_vector (nargs, args); } @@ -706,17 +707,16 @@ the same empty object instead of its copy. */) wrong_type_argument (Qsequencep, arg); } -/* This structure holds information of an argument of `concat_strings' that is - a string and has text properties to be copied. */ +/* This structure holds information of an argument of `concat_to_string' + that is a string and has text properties to be copied. */ struct textprop_rec { ptrdiff_t argnum; /* refer to ARGS (arguments of `concat') */ - ptrdiff_t from; /* refer to ARGS[argnum] (argument string) */ ptrdiff_t to; /* refer to VAL (the target string) */ }; static Lisp_Object -concat_strings (ptrdiff_t nargs, Lisp_Object *args) +concat_to_string (ptrdiff_t nargs, Lisp_Object *args) { USE_SAFE_ALLOCA; @@ -842,7 +842,6 @@ concat_strings (ptrdiff_t nargs, Lisp_Object *args) if (string_intervals (arg)) { textprops[num_textprops].argnum = i; - textprops[num_textprops].from = 0; textprops[num_textprops].to = toindex; num_textprops++; } @@ -857,9 +856,8 @@ concat_strings (ptrdiff_t nargs, Lisp_Object *args) else { /* Copy a single-byte string to a multibyte string. */ - toindex_byte += copy_text (SDATA (arg), - SDATA (result) + toindex_byte, - nchars, 0, 1); + toindex_byte += str_to_multibyte (SDATA (result) + toindex_byte, + SDATA (arg), nchars); } toindex += nchars; } @@ -912,19 +910,100 @@ concat_strings (ptrdiff_t nargs, Lisp_Object *args) return result; } -/* Concatenate sequences into a list or vector. */ +/* Concatenate sequences into a list. */ +Lisp_Object +concat_to_list (ptrdiff_t nargs, Lisp_Object *args, Lisp_Object last_tail) +{ + /* Copy the contents of the args into the result. */ + Lisp_Object result = Qnil; + Lisp_Object last = Qnil; /* Last cons in result if nonempty. */ + + for (ptrdiff_t i = 0; i < nargs; i++) + { + Lisp_Object arg = args[i]; + /* List arguments are treated specially since this is the common case. */ + if (CONSP (arg)) + { + Lisp_Object head = Fcons (XCAR (arg), Qnil); + Lisp_Object prev = head; + arg = XCDR (arg); + FOR_EACH_TAIL (arg) + { + Lisp_Object next = Fcons (XCAR (arg), Qnil); + XSETCDR (prev, next); + prev = next; + } + CHECK_LIST_END (arg, arg); + if (NILP (result)) + result = head; + else + XSETCDR (last, head); + last = prev; + } + else if (NILP (arg)) + ; + else if (VECTORP (arg) || STRINGP (arg) + || BOOL_VECTOR_P (arg) || COMPILEDP (arg)) + { + ptrdiff_t arglen = XFIXNUM (Flength (arg)); + ptrdiff_t argindex_byte = 0; + + /* Copy element by element. */ + for (ptrdiff_t argindex = 0; argindex < arglen; argindex++) + { + /* Fetch next element of `arg' arg into `elt', or break if + `arg' is exhausted. */ + Lisp_Object elt; + if (STRINGP (arg)) + { + int c; + if (STRING_MULTIBYTE (arg)) + { + ptrdiff_t char_idx = argindex; + c = fetch_string_char_advance_no_check (arg, &char_idx, + &argindex_byte); + } + else + c = SREF (arg, argindex); + elt = make_fixed_natnum (c); + } + else if (BOOL_VECTOR_P (arg)) + elt = bool_vector_ref (arg, argindex); + else + elt = AREF (arg, argindex); + + /* Store this element into the result. */ + Lisp_Object node = Fcons (elt, Qnil); + if (NILP (result)) + result = node; + else + XSETCDR (last, node); + last = node; + } + } + else + wrong_type_argument (Qsequencep, arg); + } + + if (NILP (result)) + result = last_tail; + else + XSETCDR (last, last_tail); + return result; +} + +/* Concatenate sequences into a vector. */ Lisp_Object -concat (ptrdiff_t nargs, Lisp_Object *args, Lisp_Object last_tail, - bool vector_target) +concat_to_vector (ptrdiff_t nargs, Lisp_Object *args) { /* Check argument types and compute total length of arguments. */ EMACS_INT result_len = 0; for (ptrdiff_t i = 0; i < nargs; i++) { Lisp_Object arg = args[i]; - if (!(CONSP (arg) || NILP (arg) || VECTORP (arg) || STRINGP (arg) - || COMPILEDP (arg) || BOOL_VECTOR_P (arg))) + if (!(VECTORP (arg) || CONSP (arg) || NILP (arg) || STRINGP (arg) + || BOOL_VECTOR_P (arg) || COMPILEDP (arg))) wrong_type_argument (Qsequencep, arg); EMACS_INT len = XFIXNAT (Flength (arg)); result_len += len; @@ -932,90 +1011,61 @@ concat (ptrdiff_t nargs, Lisp_Object *args, Lisp_Object last_tail, memory_full (SIZE_MAX); } - /* When the target is a list, return the tail directly if all other - arguments are empty. */ - if (!vector_target && result_len == 0) - return last_tail; - - /* Create the output object. */ - Lisp_Object result = vector_target - ? make_nil_vector (result_len) - : Fmake_list (make_fixnum (result_len), Qnil); + /* Create the output vector. */ + Lisp_Object result = make_uninit_vector (result_len); + Lisp_Object *dst = XVECTOR (result)->contents; /* Copy the contents of the args into the result. */ - Lisp_Object tail = Qnil; - ptrdiff_t toindex = 0; - if (CONSP (result)) - { - tail = result; - toindex = -1; /* -1 in toindex is flag we are making a list */ - } - - Lisp_Object prev = Qnil; for (ptrdiff_t i = 0; i < nargs; i++) { - ptrdiff_t arglen = 0; - ptrdiff_t argindex = 0; - ptrdiff_t argindex_byte = 0; - Lisp_Object arg = args[i]; - if (!CONSP (arg)) - arglen = XFIXNUM (Flength (arg)); - - /* Copy element by element. */ - while (1) + if (VECTORP (arg)) { - /* Fetch next element of `arg' arg into `elt', or break if - `arg' is exhausted. */ - Lisp_Object elt; - if (CONSP (arg)) - { - elt = XCAR (arg); - arg = XCDR (arg); - } - else if (NILP (arg) || argindex >= arglen) - break; - else if (STRINGP (arg)) + ptrdiff_t size = ASIZE (arg); + memcpy (dst, XVECTOR (arg)->contents, size * sizeof *dst); + dst += size; + } + else if (CONSP (arg)) + do + { + *dst++ = XCAR (arg); + arg = XCDR (arg); + } + while (!NILP (arg)); + else if (NILP (arg)) + ; + else if (STRINGP (arg)) + { + ptrdiff_t size = SCHARS (arg); + if (STRING_MULTIBYTE (arg)) { - int c; - if (STRING_MULTIBYTE (arg)) - c = fetch_string_char_advance_no_check (arg, &argindex, - &argindex_byte); - else + ptrdiff_t byte = 0; + for (ptrdiff_t i = 0; i < size;) { - c = SREF (arg, argindex); - argindex++; + int c = fetch_string_char_advance_no_check (arg, &i, &byte); + *dst++ = make_fixnum (c); } - XSETFASTINT (elt, c); - } - else if (BOOL_VECTOR_P (arg)) - { - elt = bool_vector_ref (arg, argindex); - argindex++; } else - { - elt = AREF (arg, argindex); - argindex++; - } - - /* Store this element into the result. */ - if (toindex < 0) - { - XSETCAR (tail, elt); - prev = tail; - tail = XCDR (tail); - } - else - { - ASET (result, toindex, elt); - toindex++; - } + for (ptrdiff_t i = 0; i < size; i++) + *dst++ = make_fixnum (SREF (arg, i)); + } + else if (BOOL_VECTOR_P (arg)) + { + ptrdiff_t size = bool_vector_size (arg); + for (ptrdiff_t i = 0; i < size; i++) + *dst++ = bool_vector_ref (arg, i); + } + else + { + eassert (COMPILEDP (arg)); + ptrdiff_t size = PVSIZE (arg); + memcpy (dst, XVECTOR (arg)->contents, size * sizeof *dst); + dst += size; } } - if (!NILP (prev)) - XSETCDR (prev, last_tail); + eassert (dst == XVECTOR (result)->contents + result_len); return result; } @@ -1045,7 +1095,7 @@ string_char_to_byte (Lisp_Object string, ptrdiff_t char_index) if (best_above == best_above_byte) return char_index; - if (EQ (string, string_char_byte_cache_string)) + if (BASE_EQ (string, string_char_byte_cache_string)) { if (string_char_byte_cache_charpos < char_index) { @@ -1105,7 +1155,7 @@ string_byte_to_char (Lisp_Object string, ptrdiff_t byte_index) if (best_above == best_above_byte) return byte_index; - if (EQ (string, string_char_byte_cache_string)) + if (BASE_EQ (string, string_char_byte_cache_string)) { if (string_char_byte_cache_bytepos < byte_index) { @@ -1154,65 +1204,25 @@ string_byte_to_char (Lisp_Object string, ptrdiff_t byte_index) return i; } -/* Convert STRING to a multibyte string. */ - -static Lisp_Object -string_make_multibyte (Lisp_Object string) -{ - unsigned char *buf; - ptrdiff_t nbytes; - Lisp_Object ret; - USE_SAFE_ALLOCA; - - if (STRING_MULTIBYTE (string)) - return string; - - nbytes = count_size_as_multibyte (SDATA (string), - SCHARS (string)); - /* If all the chars are ASCII, they won't need any more bytes - once converted. In that case, we can return STRING itself. */ - if (nbytes == SBYTES (string)) - return string; - - buf = SAFE_ALLOCA (nbytes); - copy_text (SDATA (string), buf, SBYTES (string), - 0, 1); - - ret = make_multibyte_string ((char *) buf, SCHARS (string), nbytes); - SAFE_FREE (); - - return ret; -} - - /* Convert STRING (if unibyte) to a multibyte string without changing - the number of characters. Characters 0200 through 0237 are - converted to eight-bit characters. */ + the number of characters. Characters 0x80..0xff are interpreted as + raw bytes. */ Lisp_Object string_to_multibyte (Lisp_Object string) { - unsigned char *buf; - ptrdiff_t nbytes; - Lisp_Object ret; - USE_SAFE_ALLOCA; - if (STRING_MULTIBYTE (string)) return string; - nbytes = count_size_as_multibyte (SDATA (string), SBYTES (string)); + ptrdiff_t nchars = SCHARS (string); + ptrdiff_t nbytes = count_size_as_multibyte (SDATA (string), nchars); /* If all the chars are ASCII, they won't need any more bytes once converted. */ - if (nbytes == SBYTES (string)) + if (nbytes == nchars) return make_multibyte_string (SSDATA (string), nbytes, nbytes); - buf = SAFE_ALLOCA (nbytes); - memcpy (buf, SDATA (string), SBYTES (string)); - str_to_multibyte (buf, nbytes, SBYTES (string)); - - ret = make_multibyte_string ((char *) buf, SCHARS (string), nbytes); - SAFE_FREE (); - + Lisp_Object ret = make_uninit_multibyte_string (nchars, nbytes); + str_to_multibyte (SDATA (ret), SDATA (string), nchars); return ret; } @@ -1257,7 +1267,17 @@ string the same way whether it is unibyte or multibyte.) */) { CHECK_STRING (string); - return string_make_multibyte (string); + if (STRING_MULTIBYTE (string)) + return string; + + ptrdiff_t nchars = SCHARS (string); + ptrdiff_t nbytes = count_size_as_multibyte (SDATA (string), nchars); + if (nbytes == nchars) + return string; + + Lisp_Object ret = make_uninit_multibyte_string (nchars, nbytes); + str_to_multibyte (SDATA (ret), SDATA (string), nchars); + return ret; } DEFUN ("string-make-unibyte", Fstring_make_unibyte, Sstring_make_unibyte, @@ -1362,19 +1382,24 @@ an error is signaled. */) (Lisp_Object string) { CHECK_STRING (string); + if (!STRING_MULTIBYTE (string)) + return string; - if (STRING_MULTIBYTE (string)) + ptrdiff_t chars = SCHARS (string); + Lisp_Object ret = make_uninit_string (chars); + unsigned char *src = SDATA (string); + unsigned char *dst = SDATA (ret); + for (ptrdiff_t i = 0; i < chars; i++) { - ptrdiff_t chars = SCHARS (string); - unsigned char *str = xmalloc (chars); - ptrdiff_t converted = str_to_unibyte (SDATA (string), str, chars); - - if (converted < chars) - error ("Can't convert the %"pD"dth character to unibyte", converted); - string = make_unibyte_string ((char *) str, chars); - xfree (str); + unsigned char b = *src++; + if (b <= 0x7f) + *dst++ = b; /* ASCII */ + else if (CHAR_BYTE8_HEAD_P (b)) + *dst++ = 0x80 | (b & 1) << 6 | (*src++ & 0x3f); /* raw byte */ + else + error ("Cannot convert character at index %"pD"d to unibyte", i); } - return string; + return ret; } @@ -1532,6 +1557,62 @@ substring_both (Lisp_Object string, ptrdiff_t from, ptrdiff_t from_byte, return res; } +DEFUN ("take", Ftake, Stake, 2, 2, 0, + doc: /* Return the first N elements of LIST. +If N is zero or negative, return nil. +If N is greater or equal to the length of LIST, return LIST (or a copy). */) + (Lisp_Object n, Lisp_Object list) +{ + CHECK_FIXNUM (n); + EMACS_INT m = XFIXNUM (n); + if (m <= 0) + return Qnil; + CHECK_LIST (list); + if (NILP (list)) + return Qnil; + Lisp_Object ret = Fcons (XCAR (list), Qnil); + Lisp_Object prev = ret; + m--; + list = XCDR (list); + while (m > 0 && CONSP (list)) + { + Lisp_Object p = Fcons (XCAR (list), Qnil); + XSETCDR (prev, p); + prev = p; + m--; + list = XCDR (list); + } + if (m > 0 && !NILP (list)) + wrong_type_argument (Qlistp, list); + return ret; +} + +DEFUN ("ntake", Fntake, Sntake, 2, 2, 0, + doc: /* Modify LIST to keep only the first N elements. +If N is zero or negative, return nil. +If N is greater or equal to the length of LIST, return LIST unmodified. +Otherwise, return LIST after truncating it. */) + (Lisp_Object n, Lisp_Object list) +{ + CHECK_FIXNUM (n); + EMACS_INT m = XFIXNUM (n); + if (m <= 0) + return Qnil; + CHECK_LIST (list); + Lisp_Object tail = list; + --m; + while (m > 0 && CONSP (tail)) + { + tail = XCDR (tail); + m--; + } + if (CONSP (tail)) + XSETCDR (tail, Qnil); + else if (!NILP (tail)) + wrong_type_argument (Qlistp, list); + return list; +} + DEFUN ("nthcdr", Fnthcdr, Snthcdr, 2, 2, 0, doc: /* Take cdr N times on LIST, return the result. */) (Lisp_Object n, Lisp_Object list) @@ -1576,7 +1657,7 @@ DEFUN ("nthcdr", Fnthcdr, Snthcdr, 2, 2, 0, { /* If the tortoise just jumped (which is rare), update TORTOISE_NUM accordingly. */ - if (EQ (tail, li.tortoise)) + if (BASE_EQ (tail, li.tortoise)) tortoise_num = num; saved_tail = XCDR (tail); @@ -2014,7 +2095,7 @@ This function may destructively modify SEQ to produce the value. */) next = XCDR (tail); /* If SEQ contains a cycle, attempting to reverse it in-place will inevitably come back to SEQ. */ - if (EQ (next, seq)) + if (BASE_EQ (next, seq)) circular_list (seq); Fsetcdr (tail, prev); prev = tail; @@ -2276,24 +2357,27 @@ merge_c (Lisp_Object org_l1, Lisp_Object org_l2, bool (*less) (Lisp_Object, Lisp /* This does not check for quits. That is safe since it must terminate. */ -DEFUN ("plist-get", Fplist_get, Splist_get, 2, 2, 0, +DEFUN ("plist-get", Fplist_get, Splist_get, 2, 3, 0, doc: /* Extract a value from a property list. PLIST is a property list, which is a list of the form \(PROP1 VALUE1 PROP2 VALUE2...). This function returns the value corresponding to the given PROP, or nil if PROP is not one of the properties on the list. The comparison -with PROP is done using `eq'. +with PROP is done using PREDICATE, which defaults to `eq'. -This function never signals an error. */) - (Lisp_Object plist, Lisp_Object prop) +This function doesn't signal an error if PLIST is invalid. */) + (Lisp_Object plist, Lisp_Object prop, Lisp_Object predicate) { Lisp_Object tail = plist; + if (NILP (predicate)) + return plist_get (plist, prop); + FOR_EACH_TAIL_SAFE (tail) { if (! CONSP (XCDR (tail))) break; - if (EQ (prop, XCAR (tail))) + if (!NILP (call2 (predicate, prop, XCAR (tail)))) return XCAR (XCDR (tail)); tail = XCDR (tail); } @@ -2301,39 +2385,58 @@ This function never signals an error. */) return Qnil; } +/* Faster version of the above that works with EQ only */ +Lisp_Object +plist_get (Lisp_Object plist, Lisp_Object prop) +{ + Lisp_Object tail = plist; + FOR_EACH_TAIL_SAFE (tail) + { + if (! CONSP (XCDR (tail))) + break; + if (EQ (prop, XCAR (tail))) + return XCAR (XCDR (tail)); + tail = XCDR (tail); + } + return Qnil; +} + DEFUN ("get", Fget, Sget, 2, 2, 0, doc: /* Return the value of SYMBOL's PROPNAME property. This is the last value stored with `(put SYMBOL PROPNAME VALUE)'. */) (Lisp_Object symbol, Lisp_Object propname) { CHECK_SYMBOL (symbol); - Lisp_Object propval = Fplist_get (CDR (Fassq (symbol, Voverriding_plist_environment)), - propname); + Lisp_Object propval = plist_get (CDR (Fassq (symbol, + Voverriding_plist_environment)), + propname); if (!NILP (propval)) return propval; - return Fplist_get (XSYMBOL (symbol)->u.s.plist, propname); + return plist_get (XSYMBOL (symbol)->u.s.plist, propname); } -DEFUN ("plist-put", Fplist_put, Splist_put, 3, 3, 0, +DEFUN ("plist-put", Fplist_put, Splist_put, 3, 4, 0, doc: /* Change value in PLIST of PROP to VAL. PLIST is a property list, which is a list of the form \(PROP1 VALUE1 PROP2 VALUE2 ...). -The comparison with PROP is done using `eq'. +The comparison with PROP is done using PREDICATE, which defaults to `eq'. If PROP is already a property on the list, its value is set to VAL, otherwise the new PROP VAL pair is added. The new plist is returned; use `(setq x (plist-put x prop val))' to be sure to use the new value. The PLIST is modified by side effects. */) - (Lisp_Object plist, Lisp_Object prop, Lisp_Object val) + (Lisp_Object plist, Lisp_Object prop, Lisp_Object val, Lisp_Object predicate) { Lisp_Object prev = Qnil, tail = plist; + if (NILP (predicate)) + return plist_put (plist, prop, val); FOR_EACH_TAIL (tail) { if (! CONSP (XCDR (tail))) break; - if (EQ (prop, XCAR (tail))) + if (!NILP (call2 (predicate, prop, XCAR (tail)))) { Fsetcar (XCDR (tail), val); return plist; @@ -2351,47 +2454,8 @@ The PLIST is modified by side effects. */) return plist; } -DEFUN ("put", Fput, Sput, 3, 3, 0, - doc: /* Store SYMBOL's PROPNAME property with value VALUE. -It can be retrieved with `(get SYMBOL PROPNAME)'. */) - (Lisp_Object symbol, Lisp_Object propname, Lisp_Object value) -{ - CHECK_SYMBOL (symbol); - set_symbol_plist - (symbol, Fplist_put (XSYMBOL (symbol)->u.s.plist, propname, value)); - return value; -} - -DEFUN ("lax-plist-get", Flax_plist_get, Slax_plist_get, 2, 2, 0, - doc: /* Extract a value from a property list, comparing with `equal'. -This function is otherwise like `plist-get', but may signal an error -if PLIST isn't a valid plist. */) - (Lisp_Object plist, Lisp_Object prop) -{ - Lisp_Object tail = plist; - FOR_EACH_TAIL (tail) - { - if (! CONSP (XCDR (tail))) - break; - if (! NILP (Fequal (prop, XCAR (tail)))) - return XCAR (XCDR (tail)); - tail = XCDR (tail); - } - - CHECK_TYPE (NILP (tail), Qplistp, plist); - - return Qnil; -} - -DEFUN ("lax-plist-put", Flax_plist_put, Slax_plist_put, 3, 3, 0, - doc: /* Change value in PLIST of PROP to VAL, comparing with `equal'. -PLIST is a property list, which is a list of the form -\(PROP1 VALUE1 PROP2 VALUE2 ...). PROP and VAL are any objects. -If PROP is already a property on the list, its value is set to VAL, -otherwise the new PROP VAL pair is added. The new plist is returned; -use `(setq x (lax-plist-put x prop val))' to be sure to use the new value. -The PLIST is modified by side effects. */) - (Lisp_Object plist, Lisp_Object prop, Lisp_Object val) +Lisp_Object +plist_put (Lisp_Object plist, Lisp_Object prop, Lisp_Object val) { Lisp_Object prev = Qnil, tail = plist; FOR_EACH_TAIL (tail) @@ -2399,7 +2463,7 @@ The PLIST is modified by side effects. */) if (! CONSP (XCDR (tail))) break; - if (! NILP (Fequal (prop, XCAR (tail)))) + if (EQ (prop, XCAR (tail))) { Fsetcar (XCDR (tail), val); return plist; @@ -2409,12 +2473,24 @@ The PLIST is modified by side effects. */) tail = XCDR (tail); } CHECK_TYPE (NILP (tail), Qplistp, plist); - Lisp_Object newcell = list2 (prop, val); + Lisp_Object newcell + = Fcons (prop, Fcons (val, NILP (prev) ? plist : XCDR (XCDR (prev)))); if (NILP (prev)) return newcell; Fsetcdr (XCDR (prev), newcell); return plist; } + +DEFUN ("put", Fput, Sput, 3, 3, 0, + doc: /* Store SYMBOL's PROPNAME property with value VALUE. +It can be retrieved with `(get SYMBOL PROPNAME)'. */) + (Lisp_Object symbol, Lisp_Object propname, Lisp_Object value) +{ + CHECK_SYMBOL (symbol); + set_symbol_plist + (symbol, plist_put (XSYMBOL (symbol)->u.s.plist, propname, value)); + return value; +} DEFUN ("eql", Feql, Seql, 2, 2, 0, doc: /* Return t if the two args are `eq' or are indistinguishable numbers. @@ -2757,20 +2833,26 @@ usage: (nconc &rest LISTS) */) static EMACS_INT mapcar1 (EMACS_INT leni, Lisp_Object *vals, Lisp_Object fn, Lisp_Object seq) { - if (VECTORP (seq) || COMPILEDP (seq)) + if (NILP (seq)) + return 0; + else if (CONSP (seq)) { + Lisp_Object tail = seq; for (ptrdiff_t i = 0; i < leni; i++) { - Lisp_Object dummy = call1 (fn, AREF (seq, i)); + if (! CONSP (tail)) + return i; + Lisp_Object dummy = call1 (fn, XCAR (tail)); if (vals) vals[i] = dummy; + tail = XCDR (tail); } } - else if (BOOL_VECTOR_P (seq)) + else if (VECTORP (seq) || COMPILEDP (seq)) { - for (EMACS_INT i = 0; i < leni; i++) + for (ptrdiff_t i = 0; i < leni; i++) { - Lisp_Object dummy = call1 (fn, bool_vector_ref (seq, i)); + Lisp_Object dummy = call1 (fn, AREF (seq, i)); if (vals) vals[i] = dummy; } @@ -2788,17 +2870,14 @@ mapcar1 (EMACS_INT leni, Lisp_Object *vals, Lisp_Object fn, Lisp_Object seq) vals[i_before] = dummy; } } - else /* Must be a list, since Flength did not get an error */ + else { - Lisp_Object tail = seq; - for (ptrdiff_t i = 0; i < leni; i++) + eassert (BOOL_VECTOR_P (seq)); + for (EMACS_INT i = 0; i < leni; i++) { - if (! CONSP (tail)) - return i; - Lisp_Object dummy = call1 (fn, XCAR (tail)); + Lisp_Object dummy = call1 (fn, bool_vector_ref (seq, i)); if (vals) vals[i] = dummy; - tail = XCDR (tail); } } @@ -2831,12 +2910,18 @@ FUNCTION must be a function of one argument, and must return a value SAFE_ALLOCA_LISP (args, args_alloc); ptrdiff_t nmapped = mapcar1 (leni, args, function, sequence); ptrdiff_t nargs = 2 * nmapped - 1; + eassert (nmapped == leni); - for (ptrdiff_t i = nmapped - 1; i > 0; i--) - args[i + i] = args[i]; + if (NILP (separator) || (STRINGP (separator) && SCHARS (separator) == 0)) + nargs = nmapped; + else + { + for (ptrdiff_t i = nmapped - 1; i > 0; i--) + args[i + i] = args[i]; - for (ptrdiff_t i = 1; i < nargs; i += 2) - args[i] = separator; + for (ptrdiff_t i = 1; i < nargs; i += 2) + args[i] = separator; + } Lisp_Object ret = Fconcat (nargs, args); SAFE_FREE (); @@ -2946,6 +3031,9 @@ if `last-nonmenu-event' is nil, and `use-dialog-box' is non-nil. */) specpdl_ref count = SPECPDL_INDEX (); specbind (Qenable_recursive_minibuffers, Qt); + /* Preserve the actual command that eventually called `yes-or-no-p' + (otherwise `repeat' will be repeating `exit-minibuffer'). */ + specbind (Qreal_this_command, Vreal_this_command); while (1) { @@ -3069,7 +3157,7 @@ dynamic module files, in that order; but the function will not try to load the file without any suffix. See `get-load-suffixes' for the complete list of suffixes. -To find the file, this function searches that directories in `load-path'. +To find the file, this function searches the directories in `load-path'. If the optional third argument NOERROR is non-nil, then, if the file is not found, the function returns nil instead of signaling @@ -3112,8 +3200,13 @@ FILENAME are suppressed. */) /* This is to make sure that loadup.el gives a clear picture of what files are preloaded and when. */ if (will_dump_p () && !will_bootstrap_p ()) - error ("(require %s) while preparing to dump", - SDATA (SYMBOL_NAME (feature))); + { + /* Avoid landing here recursively while outputting the + backtrace from the error. */ + gflags.will_dump_ = false; + error ("(require %s) while preparing to dump", + SDATA (SYMBOL_NAME (feature))); + } /* A certain amount of recursive `require' is legitimate, but if we require the same feature recursively 3 times, @@ -3169,22 +3262,25 @@ FILENAME are suppressed. */) bottleneck of Widget operation. Here is their translation to C, for the sole reason of efficiency. */ -DEFUN ("plist-member", Fplist_member, Splist_member, 2, 2, 0, +DEFUN ("plist-member", Fplist_member, Splist_member, 2, 3, 0, doc: /* Return non-nil if PLIST has the property PROP. PLIST is a property list, which is a list of the form \(PROP1 VALUE1 PROP2 VALUE2 ...). -The comparison with PROP is done using `eq'. +The comparison with PROP is done using PREDICATE, which defaults to +`eq'. Unlike `plist-get', this allows you to distinguish between a missing property and a property with the value nil. The value is actually the tail of PLIST whose car is PROP. */) - (Lisp_Object plist, Lisp_Object prop) + (Lisp_Object plist, Lisp_Object prop, Lisp_Object predicate) { Lisp_Object tail = plist; + if (NILP (predicate)) + predicate = Qeq; FOR_EACH_TAIL (tail) { - if (EQ (XCAR (tail), prop)) + if (!NILP (call2 (predicate, XCAR (tail), prop))) return tail; tail = XCDR (tail); if (! CONSP (tail)) @@ -3194,13 +3290,22 @@ The value is actually the tail of PLIST whose car is PROP. */) return Qnil; } +/* plist_member isn't used much in the Emacs sources, so just provide + a shim so that the function name follows the same pattern as + plist_get/plist_put. */ +Lisp_Object +plist_member (Lisp_Object plist, Lisp_Object prop) +{ + return Fplist_member (plist, prop, Qnil); +} + DEFUN ("widget-put", Fwidget_put, Swidget_put, 3, 3, 0, doc: /* In WIDGET, set PROPERTY to VALUE. The value can later be retrieved with `widget-get'. */) (Lisp_Object widget, Lisp_Object property, Lisp_Object value) { CHECK_CONS (widget); - XSETCDR (widget, Fplist_put (XCDR (widget), property, value)); + XSETCDR (widget, plist_put (XCDR (widget), property, value)); return value; } @@ -3217,7 +3322,7 @@ later with `widget-put'. */) if (NILP (widget)) return Qnil; CHECK_CONS (widget); - tmp = Fplist_member (XCDR (widget), property); + tmp = plist_member (XCDR (widget), property); if (CONSP (tmp)) { tmp = XCDR (tmp); @@ -4902,7 +5007,8 @@ Hash codes are not guaranteed to be preserved across Emacs sessions. */) DEFUN ("sxhash-eql", Fsxhash_eql, Ssxhash_eql, 1, 1, 0, doc: /* Return an integer hash code for OBJ suitable for `eql'. -If (eql A B), then (= (sxhash-eql A) (sxhash-eql B)). +If (eql A B), then (= (sxhash-eql A) (sxhash-eql B)), but the opposite +isn't necessarily true. Hash codes are not guaranteed to be preserved across Emacs sessions. */) (Lisp_Object obj) @@ -4912,7 +5018,8 @@ Hash codes are not guaranteed to be preserved across Emacs sessions. */) DEFUN ("sxhash-equal", Fsxhash_equal, Ssxhash_equal, 1, 1, 0, doc: /* Return an integer hash code for OBJ suitable for `equal'. -If (equal A B), then (= (sxhash-equal A) (sxhash-equal B)). +If (equal A B), then (= (sxhash-equal A) (sxhash-equal B)), but the +opposite isn't necessarily true. Hash codes are not guaranteed to be preserved across Emacs sessions. */) (Lisp_Object obj) @@ -6031,6 +6138,8 @@ The same variable also affects the function `read-answer'. */); defsubr (&Scopy_alist); defsubr (&Ssubstring); defsubr (&Ssubstring_no_properties); + defsubr (&Stake); + defsubr (&Sntake); defsubr (&Snthcdr); defsubr (&Snth); defsubr (&Selt); @@ -6050,8 +6159,6 @@ The same variable also affects the function `read-answer'. */); defsubr (&Sget); defsubr (&Splist_put); defsubr (&Sput); - defsubr (&Slax_plist_get); - defsubr (&Slax_plist_put); defsubr (&Seql); defsubr (&Sequal); defsubr (&Sequal_including_properties); @@ -6083,4 +6190,6 @@ The same variable also affects the function `read-answer'. */); defsubr (&Sbuffer_hash); defsubr (&Slocale_info); defsubr (&Sbuffer_line_statistics); + + DEFSYM (Qreal_this_command, "real-this-command"); } diff --git a/src/font.c b/src/font.c index 702536c1cab..8acedb9bf88 100644 --- a/src/font.c +++ b/src/font.c @@ -3589,8 +3589,8 @@ font_open_by_name (struct frame *f, Lisp_Object name) The second is with frame F NULL. In this case, DRIVER is globally registered in the variable `font_driver_list'. All font-driver - implementations must call this function in its syms_of_XXXX - (e.g. syms_of_xfont). */ + implementations must call this function in its + syms_of_XXXX_for_pdumper (e.g. syms_of_xfont_for_pdumper). */ void register_font_driver (struct font_driver const *driver, struct frame *f) @@ -4691,8 +4691,7 @@ Each element of the value is a cons (VARIATION-SELECTOR . GLYPH-ID), where VARIATION-SELECTOR is a character code of variation selector (#xFE00..#xFE0F or #xE0100..#xE01EF). - GLYPH-ID is a glyph code of the corresponding variation glyph, -a fixnum, if it's small enough, otherwise a bignum. */) + GLYPH-ID is a glyph code of the corresponding variation glyph, an integer. */) (Lisp_Object font_object, Lisp_Object character) { unsigned variations[256]; @@ -4729,8 +4728,7 @@ a fixnum, if it's small enough, otherwise a bignum. */) that apply to POSITION. POSITION may be nil, in which case, FONT-SPEC is the font for displaying the character CH with the default face. GLYPH-CODE is the glyph code in the font to use for - the character, it is a fixnum, if it is small enough, otherwise a - bignum. + the character, as an integer. For a text terminal, return a nonnegative integer glyph code for the character, or a negative integer if the character is not diff --git a/src/frame.c b/src/frame.c index c21461d49fe..25d71e0769f 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1444,10 +1444,6 @@ affects all frames on the same terminal device. */) If FRAME is a switch-frame event `(switch-frame FRAME1)', use FRAME1 as frame. - If TRACK is non-zero and the frame that currently has the focus - redirects its focus to the selected frame, redirect that focused - frame's focus to FRAME instead. - FOR_DELETION non-zero means that the selected frame is being deleted, which includes the possibility that the frame's terminal is dead. @@ -1455,7 +1451,7 @@ affects all frames on the same terminal device. */) The value of NORECORD is passed as argument to Fselect_window. */ Lisp_Object -do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object norecord) +do_switch_frame (Lisp_Object frame, int for_deletion, Lisp_Object norecord) { struct frame *sf = SELECTED_FRAME (), *f; @@ -1477,59 +1473,6 @@ do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object nor else if (f == sf) return frame; - /* If a frame's focus has been redirected toward the currently - selected frame, we should change the redirection to point to the - newly selected frame. This means that if the focus is redirected - from a minibufferless frame to a surrogate minibuffer frame, we - can use `other-window' to switch between all the frames using - that minibuffer frame, and the focus redirection will follow us - around. */ -#if 0 - /* This is too greedy; it causes inappropriate focus redirection - that's hard to get rid of. */ - if (track) - { - Lisp_Object tail; - - for (tail = Vframe_list; CONSP (tail); tail = XCDR (tail)) - { - Lisp_Object focus; - - if (!FRAMEP (XCAR (tail))) - emacs_abort (); - - focus = FRAME_FOCUS_FRAME (XFRAME (XCAR (tail))); - - if (FRAMEP (focus) && XFRAME (focus) == SELECTED_FRAME ()) - Fredirect_frame_focus (XCAR (tail), frame); - } - } -#else /* ! 0 */ - /* Instead, apply it only to the frame we're pointing to. */ -#ifdef HAVE_WINDOW_SYSTEM - if (track && FRAME_WINDOW_P (f) && FRAME_TERMINAL (f)->get_focus_frame) - { - Lisp_Object focus, gfocus; - - gfocus = FRAME_TERMINAL (f)->get_focus_frame (f); - if (FRAMEP (gfocus)) - { - focus = FRAME_FOCUS_FRAME (XFRAME (gfocus)); - if (FRAMEP (focus) && XFRAME (focus) == SELECTED_FRAME ()) - /* Redirect frame focus also when FRAME has its minibuffer - window on the selected frame (see Bug#24500). - - Don't do that: It causes redirection problem with a - separate minibuffer frame (Bug#24803) and problems - when updating the cursor on such frames. - || (NILP (focus) - && EQ (FRAME_MINIBUF_WINDOW (f), sf->selected_window))) */ - Fredirect_frame_focus (gfocus, frame); - } - } -#endif /* HAVE_X_WINDOWS */ -#endif /* ! 0 */ - if (!for_deletion && FRAME_HAS_MINIBUF_P (sf)) resize_mini_window (XWINDOW (FRAME_MINIBUF_WINDOW (sf)), 1); @@ -1627,7 +1570,7 @@ This function returns FRAME, or nil if FRAME has been deleted. */) /* Do not select a tooltip frame (Bug#47207). */ error ("Cannot select a tooltip frame"); else - return do_switch_frame (frame, 1, 0, norecord); + return do_switch_frame (frame, 0, norecord); } DEFUN ("handle-switch-frame", Fhandle_switch_frame, @@ -1643,7 +1586,7 @@ necessarily represent user-visible input focus. */) kset_prefix_arg (current_kboard, Vcurrent_prefix_arg); run_hook (Qmouse_leave_buffer_hook); - return do_switch_frame (event, 0, 0, Qnil); + return do_switch_frame (event, 0, Qnil); } DEFUN ("selected-frame", Fselected_frame, Sselected_frame, 0, 0, 0, @@ -1990,6 +1933,9 @@ delete_frame (Lisp_Object frame, Lisp_Object force) int is_tooltip_frame; bool nochild = !FRAME_PARENT_FRAME (f); Lisp_Object minibuffer_child_frame = Qnil; +#ifdef HAVE_X_WINDOWS + specpdl_ref ref; +#endif if (!FRAME_LIVE_P (f)) return Qnil; @@ -2158,7 +2104,7 @@ delete_frame (Lisp_Object frame, Lisp_Object force) Fraise_frame (frame1); #endif - do_switch_frame (frame1, 0, 1, Qnil); + do_switch_frame (frame1, 1, Qnil); sf = SELECTED_FRAME (); } else @@ -2173,7 +2119,29 @@ delete_frame (Lisp_Object frame, Lisp_Object force) /* Clear any X selections for this frame. */ #ifdef HAVE_X_WINDOWS if (FRAME_X_P (f)) - x_clear_frame_selections (f); + { + /* Don't preserve selections when a display is going away, since + that sends stuff down the wire. */ + + ref = SPECPDL_INDEX (); + + if (EQ (force, Qnoelisp)) + specbind (Qx_auto_preserve_selections, Qnil); + + x_clear_frame_selections (f); + unbind_to (ref, Qnil); + } +#endif + +#ifdef HAVE_PGTK + if (FRAME_PGTK_P (f)) + { + /* Do special selection events now, in case the window gets + destroyed by this deletion. Does this run Lisp code? */ + swallow_events (false); + + pgtk_clear_frame_selections (f); + } #endif /* Free glyphs. @@ -3948,9 +3916,10 @@ static const struct frame_parm_table frame_parms[] = {"z-group", SYMBOL_INDEX (Qz_group)}, {"override-redirect", SYMBOL_INDEX (Qoverride_redirect)}, {"no-special-glyphs", SYMBOL_INDEX (Qno_special_glyphs)}, - {"alpha-background", SYMBOL_INDEX (Qalpha_background)}, + {"alpha-background", SYMBOL_INDEX (Qalpha_background)}, + {"use-frame-synchronization", SYMBOL_INDEX (Quse_frame_synchronization)}, #ifdef HAVE_X_WINDOWS - {"shaded", SYMBOL_INDEX (Qshaded)}, + {"shaded", SYMBOL_INDEX (Qshaded)}, #endif #ifdef NS_IMPL_COCOA {"ns-appearance", SYMBOL_INDEX (Qns_appearance)}, @@ -5119,7 +5088,9 @@ gui_set_no_special_glyphs (struct frame *f, Lisp_Object new_value, Lisp_Object o bool gui_mouse_grabbed (Display_Info *dpyinfo) { - return (dpyinfo->grabbed + return ((dpyinfo->grabbed + || (dpyinfo->terminal->any_grab_hook + && dpyinfo->terminal->any_grab_hook (dpyinfo))) && dpyinfo->last_mouse_frame && FRAME_LIVE_P (dpyinfo->last_mouse_frame)); } @@ -6225,6 +6196,7 @@ syms_of_frame (void) DEFSYM (Qtop_only, "top-only"); DEFSYM (Qiconify_top_level, "iconify-top-level"); DEFSYM (Qmake_invisible, "make-invisible"); + DEFSYM (Quse_frame_synchronization, "use-frame-synchronization"); { int i; diff --git a/src/fringe.c b/src/fringe.c index bf0b5fde761..5d7c8dca998 100644 --- a/src/fringe.c +++ b/src/fringe.c @@ -209,6 +209,20 @@ static unsigned short left_curly_arrow_bits[] = { static unsigned short right_curly_arrow_bits[] = { 0x3c, 0x3e, 0x03, 0x27, 0x3f, 0x3e, 0x3c, 0x3e}; +/* Large circle bitmap. */ +/* + ........ + ..xxxx.. + .xxxxxx. + xxxxxxxx + xxxxxxxx + .xxxxxx. + ..xxxx.. + ........ +*/ +static unsigned short large_circle_bits[] = { + 0x3c, 0x7e, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x3c}; + /* Reverse Overlay arrow bitmap. A triangular arrow. */ /* ......xx @@ -454,6 +468,7 @@ static struct fringe_bitmap standard_bitmaps[] = { FRBITS (down_arrow_bits), 8, 0, ALIGN_BITMAP_BOTTOM, 0 }, { FRBITS (left_curly_arrow_bits), 8, 0, ALIGN_BITMAP_CENTER, 0 }, { FRBITS (right_curly_arrow_bits), 8, 0, ALIGN_BITMAP_CENTER, 0 }, + { FRBITS (large_circle_bits), 8, 0, ALIGN_BITMAP_CENTER, 0 }, { FRBITS (left_triangle_bits), 8, 0, ALIGN_BITMAP_CENTER, 0 }, { FRBITS (right_triangle_bits), 8, 0, ALIGN_BITMAP_CENTER, 0 }, { FRBITS (top_left_angle_bits), 8, 0, ALIGN_BITMAP_TOP, 0 }, diff --git a/src/ftcrfont.c b/src/ftcrfont.c index 6bb41110d5c..e089f9dea85 100644 --- a/src/ftcrfont.c +++ b/src/ftcrfont.c @@ -567,7 +567,7 @@ ftcrfont_draw (struct glyph_string *s, unblock_input (); return 0; } - BView_cr_dump_clipping (FRAME_HAIKU_VIEW (f), cr); + BView_cr_dump_clipping (FRAME_HAIKU_DRAWABLE (f), cr); #endif if (with_background) @@ -677,7 +677,11 @@ ftcrhbfont_begin_hb_font (struct font *font, double *position_unit) ftcrfont_info->ft_size = ft_face->size; hb_font_t *hb_font = fthbfont_begin_hb_font (font, position_unit); - if (ftcrfont_info->bitmap_position_unit) + /* HarfBuzz 5 correctly scales bitmap-only fonts without position + unit adjustment. + (https://github.com/harfbuzz/harfbuzz/issues/489) */ + if (!hb_version_atleast (5, 0, 0) + && ftcrfont_info->bitmap_position_unit) *position_unit = ftcrfont_info->bitmap_position_unit; return hb_font; diff --git a/src/gnutls.c b/src/gnutls.c index 0e1e63e157a..a0de0238c47 100644 --- a/src/gnutls.c +++ b/src/gnutls.c @@ -1635,10 +1635,10 @@ gnutls_verify_boot (Lisp_Object proc, Lisp_Object proplist) char *c_hostname; if (NILP (proplist)) - proplist = Fcdr (Fplist_get (p->childp, QCtls_parameters)); + proplist = Fcdr (plist_get (p->childp, QCtls_parameters)); - verify_error = Fplist_get (proplist, QCverify_error); - hostname = Fplist_get (proplist, QChostname); + verify_error = plist_get (proplist, QCverify_error); + hostname = plist_get (proplist, QChostname); if (EQ (verify_error, Qt)) verify_error_all = true; @@ -1668,7 +1668,7 @@ gnutls_verify_boot (Lisp_Object proc, Lisp_Object proplist) p->gnutls_peer_verification = peer_verification; - warnings = Fplist_get (Fgnutls_peer_status (proc), intern (":warnings")); + warnings = plist_get (Fgnutls_peer_status (proc), intern (":warnings")); if (!NILP (warnings)) { for (Lisp_Object tail = warnings; CONSP (tail); tail = XCDR (tail)) @@ -1870,13 +1870,13 @@ one trustfile (usually a CA bundle). */) return Qnil; } - hostname = Fplist_get (proplist, QChostname); - priority_string = Fplist_get (proplist, QCpriority); - trustfiles = Fplist_get (proplist, QCtrustfiles); - keylist = Fplist_get (proplist, QCkeylist); - crlfiles = Fplist_get (proplist, QCcrlfiles); - loglevel = Fplist_get (proplist, QCloglevel); - prime_bits = Fplist_get (proplist, QCmin_prime_bits); + hostname = plist_get (proplist, QChostname); + priority_string = plist_get (proplist, QCpriority); + trustfiles = plist_get (proplist, QCtrustfiles); + keylist = plist_get (proplist, QCkeylist); + crlfiles = plist_get (proplist, QCcrlfiles); + loglevel = plist_get (proplist, QCloglevel); + prime_bits = plist_get (proplist, QCmin_prime_bits); if (!STRINGP (hostname)) { @@ -1929,7 +1929,7 @@ one trustfile (usually a CA bundle). */) check_memory_full (gnutls_certificate_allocate_credentials (&x509_cred)); XPROCESS (proc)->gnutls_x509_cred = x509_cred; - verify_flags = Fplist_get (proplist, QCverify_flags); + verify_flags = plist_get (proplist, QCverify_flags); if (TYPE_RANGED_FIXNUMP (unsigned int, verify_flags)) { gnutls_verify_flags = XFIXNAT (verify_flags); @@ -2109,7 +2109,7 @@ one trustfile (usually a CA bundle). */) } XPROCESS (proc)->gnutls_complete_negotiation_p = - !NILP (Fplist_get (proplist, QCcomplete_negotiation)); + !NILP (plist_get (proplist, QCcomplete_negotiation)); GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CRED_SET; ret = emacs_gnutls_handshake (XPROCESS (proc)); if (ret < GNUTLS_E_SUCCESS) @@ -2348,7 +2348,7 @@ gnutls_symmetric (bool encrypting, Lisp_Object cipher, if (!NILP (info) && CONSP (info)) { - Lisp_Object v = Fplist_get (info, QCcipher_id); + Lisp_Object v = plist_get (info, QCcipher_id); if (TYPE_RANGED_FIXNUMP (gnutls_cipher_algorithm_t, v)) gca = XFIXNUM (v); } @@ -2625,7 +2625,7 @@ itself. */) if (!NILP (info) && CONSP (info)) { - Lisp_Object v = Fplist_get (info, QCmac_algorithm_id); + Lisp_Object v = plist_get (info, QCmac_algorithm_id); if (TYPE_RANGED_FIXNUMP (gnutls_mac_algorithm_t, v)) gma = XFIXNUM (v); } @@ -2715,7 +2715,7 @@ the number itself. */) if (!NILP (info) && CONSP (info)) { - Lisp_Object v = Fplist_get (info, QCdigest_algorithm_id); + Lisp_Object v = plist_get (info, QCdigest_algorithm_id); if (TYPE_RANGED_FIXNUMP (gnutls_digest_algorithm_t, v)) gda = XFIXNUM (v); } diff --git a/src/gtkutil.c b/src/gtkutil.c index f2018bc01f5..a6bba096a43 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1887,7 +1887,9 @@ xg_free_frame_widgets (struct frame *f) /* x_free_frame_resources should have taken care of it */ #ifndef HAVE_PGTK +#ifdef HAVE_XDBE eassert (!FRAME_X_DOUBLE_BUFFERED_P (f)); +#endif g_object_unref (FRAME_X_OUTPUT (f)->im_context); #endif gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f)); diff --git a/src/haiku_draw_support.cc b/src/haiku_draw_support.cc index f0cc084bb37..8e911dd1843 100644 --- a/src/haiku_draw_support.cc +++ b/src/haiku_draw_support.cc @@ -280,14 +280,19 @@ hsl_color_rgb (double h, double s, double l, uint32_t *rgb) void BView_DrawBitmap (void *view, void *bitmap, int x, int y, int width, int height, int vx, int vy, int vwidth, - int vheight) + int vheight, bool use_bilinear_filtering) { BView *vw = get_view (view); BBitmap *bm = (BBitmap *) bitmap; vw->SetDrawingMode (B_OP_OVER); - vw->DrawBitmap (bm, BRect (x, y, x + width - 1, y + height - 1), - BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1)); + if (!use_bilinear_filtering) + vw->DrawBitmap (bm, BRect (x, y, x + width - 1, y + height - 1), + BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1)); + else + vw->DrawBitmap (bm, BRect (x, y, x + width - 1, y + height - 1), + BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1), + B_FILTER_BITMAP_BILINEAR); vw->SetDrawingMode (B_OP_COPY); } @@ -357,134 +362,64 @@ BView_DrawBitmapWithEraseOp (void *view, void *bitmap, int x, } void -BView_DrawMask (void *src, void *view, - int x, int y, int width, int height, - int vx, int vy, int vwidth, int vheight, - uint32_t color) +be_draw_image_mask (void *src, void *view, int x, int y, int width, + int height, int vx, int vy, int vwidth, int vheight, + uint32_t color) { BBitmap *source = (BBitmap *) src; BBitmap bm (source->Bounds (), B_RGBA32); BRect bounds = bm.Bounds (); + int bx, by, bit; + BView *vw; if (bm.InitCheck () != B_OK) return; - for (int y = 0; y < BE_RECT_HEIGHT (bounds); ++y) + + /* Fill the background color or transparency into the bitmap, + depending on the value of the mask. */ + for (by = 0; by < BE_RECT_HEIGHT (bounds); ++by) { - for (int x = 0; x < BE_RECT_WIDTH (bounds); ++x) + for (bx = 0; bx < BE_RECT_WIDTH (bounds); ++bx) { - int bit = haiku_get_pixel ((void *) source, x, y); + bit = haiku_get_pixel ((void *) source, bx, by); if (!bit) - haiku_put_pixel ((void *) &bm, x, y, ((uint32_t) 255 << 24) | color); + haiku_put_pixel ((void *) &bm, bx, by, + ((uint32_t) 255 << 24) | color); else - haiku_put_pixel ((void *) &bm, x, y, 0); + haiku_put_pixel ((void *) &bm, bx, by, 0); } } - BView *vw = get_view (view); + + vw = get_view (view); vw->SetDrawingMode (B_OP_OVER); vw->DrawBitmap (&bm, BRect (x, y, x + width - 1, y + height - 1), BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1)); vw->SetDrawingMode (B_OP_COPY); } -static BBitmap * -rotate_bitmap_270 (BBitmap *bmp) +void +be_apply_affine_transform (void *view, double m0, double m1, double tx, + double m2, double m3, double ty) { - BRect r = bmp->Bounds (); - BBitmap *bm = new BBitmap (BRect (r.top, r.left, r.bottom, r.right), - bmp->ColorSpace (), true); - if (bm->InitCheck () != B_OK) - gui_abort ("Failed to init bitmap for rotate"); - int w = BE_RECT_WIDTH (r); - int h = BE_RECT_HEIGHT (r); + BAffineTransform transform (m0, m2, m1, m3, tx, ty); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) - haiku_put_pixel ((void *) bm, y, w - x - 1, - haiku_get_pixel ((void *) bmp, x, y)); - - return bm; + get_view (view)->SetTransform (transform); } -static BBitmap * -rotate_bitmap_90 (BBitmap *bmp) +void +be_apply_inverse_transform (double (*matrix3x3)[3], int x, int y, + int *x_out, int *y_out) { - BRect r = bmp->Bounds (); - BBitmap *bm = new BBitmap (BRect (r.top, r.left, r.bottom, r.right), - bmp->ColorSpace (), true); - if (bm->InitCheck () != B_OK) - gui_abort ("Failed to init bitmap for rotate"); - int w = BE_RECT_WIDTH (r); - int h = BE_RECT_HEIGHT (r); + BAffineTransform transform (matrix3x3[0][0], matrix3x3[1][0], + matrix3x3[0][1], matrix3x3[1][1], + matrix3x3[0][2], matrix3x3[1][2]); + BPoint point (x, y); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) - haiku_put_pixel ((void *) bm, h - y - 1, x, - haiku_get_pixel ((void *) bmp, x, y)); - - return bm; -} - -void * -BBitmap_transform_bitmap (void *bitmap, void *mask, uint32_t m_color, - double rot, int desw, int desh) -{ - BBitmap *bm = (BBitmap *) bitmap; - BBitmap *mk = (BBitmap *) mask; - int copied_p = 0; - - if (rot == 90) - { - copied_p = 1; - bm = rotate_bitmap_90 (bm); - if (mk) - mk = rotate_bitmap_90 (mk); - } - - if (rot == 270) - { - copied_p = 1; - bm = rotate_bitmap_270 (bm); - if (mk) - mk = rotate_bitmap_270 (mk); - } - - BRect n = BRect (0, 0, desw - 1, desh - 1); - BView vw (n, NULL, B_FOLLOW_NONE, 0); - BBitmap *dst = new BBitmap (n, bm->ColorSpace (), true); - if (dst->InitCheck () != B_OK) - if (bm->InitCheck () != B_OK) - gui_abort ("Failed to init bitmap for scale"); - dst->AddChild (&vw); - - if (!vw.LockLooper ()) - gui_abort ("Failed to lock offscreen view for scale"); - - if (rot != 90 && rot != 270) - { - BAffineTransform tr; - tr.RotateBy (BPoint (desw / 2, desh / 2), rot * M_PI / 180.0); - vw.SetTransform (tr); - } - - vw.MovePenTo (0, 0); - vw.DrawBitmap (bm, n); - if (mk) - { - BRect k = mk->Bounds (); - BView_DrawMask ((void *) mk, (void *) &vw, - 0, 0, BE_RECT_WIDTH (k), - BE_RECT_HEIGHT (k), - 0, 0, desw, desh, m_color); - } - vw.Sync (); - vw.RemoveSelf (); + transform.ApplyInverse (&point); - if (copied_p) - delete bm; - if (copied_p && mk) - delete mk; - return dst; + *x_out = std::floor (point.x); + *y_out = std::floor (point.y); } void @@ -540,3 +475,62 @@ be_draw_cross_on_pixmap (void *bitmap, int x, int y, int width, be_draw_cross_on_pixmap_1 (target, x, y, width, height, color); } + +void +be_draw_bitmap_with_mask (void *view, void *bitmap, void *mask, + int dx, int dy, int width, int height, + int vx, int vy, int vwidth, int vheight, + bool use_bilinear_filtering) +{ + BBitmap *source ((BBitmap *) bitmap); + BBitmap combined (source->Bounds (), B_RGBA32); + BRect bounds; + int x, y, bit; + BView *vw; + uint32_t source_mask; + unsigned long pixel; + + if (combined.InitCheck () != B_OK) + return; + + if (combined.ImportBits (source) != B_OK) + return; + + bounds = source->Bounds (); + + if (source->ColorSpace () == B_RGB32) + source_mask = 255u << 24; + else + source_mask = 0; + + for (y = 0; y < BE_RECT_HEIGHT (bounds); ++y) + { + for (x = 0; x < BE_RECT_WIDTH (bounds); ++x) + { + bit = haiku_get_pixel (mask, x, y); + + if (bit) + { + pixel = haiku_get_pixel (bitmap, x, y); + haiku_put_pixel ((void *) &combined, x, y, + source_mask | pixel); + } + else + haiku_put_pixel ((void *) &combined, x, y, 0); + } + } + + vw = get_view (view); + + vw->SetDrawingMode (B_OP_OVER); + if (!use_bilinear_filtering) + vw->DrawBitmap (&combined, + BRect (dx, dy, dx + width - 1, dy + height - 1), + BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1)); + else + vw->DrawBitmap (&combined, + BRect (dx, dy, dx + width - 1, dy + height - 1), + BRect (vx, vy, vx + vwidth - 1, vy + vheight - 1), + B_FILTER_BITMAP_BILINEAR); + vw->SetDrawingMode (B_OP_COPY); +} diff --git a/src/haiku_font_support.cc b/src/haiku_font_support.cc index ca6aaf71204..d824cc59ae2 100644 --- a/src/haiku_font_support.cc +++ b/src/haiku_font_support.cc @@ -598,6 +598,12 @@ BFont_find (struct haiku_font_pattern *pt) p->last = NULL; p->next_family = r; r = p; + + if (pt->specified & FSPEC_ANTIALIAS) + { + p->specified |= FSPEC_ANTIALIAS; + p->use_antialiasing = pt->use_antialiasing; + } } else if (sty_count) { @@ -623,6 +629,12 @@ BFont_find (struct haiku_font_pattern *pt) p->family_index = fi; p->style_index = si; + if (pt->specified & FSPEC_ANTIALIAS) + { + p->specified |= FSPEC_ANTIALIAS; + p->use_antialiasing = pt->use_antialiasing; + } + if (p->specified & FSPEC_SLANT && (p->slant == SLANT_OBLIQUE || p->slant == SLANT_ITALIC)) @@ -916,3 +928,14 @@ be_find_font_indices (struct haiku_font_pattern *pattern, return 1; } + +void +be_set_font_antialiasing (void *font, bool antialias_p) +{ + BFont *font_object; + + font_object = (BFont *) font; + font_object->SetFlags (antialias_p + ? B_FORCE_ANTIALIASING + : B_DISABLE_ANTIALIASING); +} diff --git a/src/haiku_io.c b/src/haiku_io.c index d3455276855..5cc70f6f71f 100644 --- a/src/haiku_io.c +++ b/src/haiku_io.c @@ -107,6 +107,8 @@ haiku_len (enum haiku_event_type type) return sizeof (struct haiku_scroll_bar_part_event); case SCREEN_CHANGED_EVENT: return sizeof (struct haiku_screen_changed_event); + case CLIPBOARD_CHANGED_EVENT: + return sizeof (struct haiku_clipboard_changed_event); } emacs_abort (); diff --git a/src/haiku_select.cc b/src/haiku_select.cc index 764001f62b0..872da1d6c44 100644 --- a/src/haiku_select.cc +++ b/src/haiku_select.cc @@ -18,6 +18,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <config.h> +#include <Application.h> #include <Clipboard.h> #include <Message.h> #include <Path.h> @@ -47,6 +48,16 @@ static int64 count_primary = -1; /* The number of times the secondary selection has changed. */ static int64 count_secondary = -1; +/* Whether or not we currently think Emacs owns the primary + selection. */ +static bool owned_primary; + +/* Likewise for the secondary selection. */ +static bool owned_secondary; + +/* And the clipboard. */ +static bool owned_clipboard; + static BClipboard * get_clipboard_object (enum haiku_clipboard clipboard) { @@ -111,53 +122,6 @@ be_find_clipboard_data_1 (BClipboard *cb, const char *type, ssize_t *len) } static void -be_get_clipboard_targets_1 (BClipboard *cb, char **buf, int buf_size) -{ - BMessage *data; - char *name; - int32 count_found; - type_code type; - int32 i; - int index; - - if (!cb->Lock ()) - { - buf[0] = NULL; - return; - } - - data = cb->Data (); - index = 0; - - if (!data) - { - buf[0] = NULL; - cb->Unlock (); - return; - } - - for (i = 0; (data->GetInfo (B_ANY_TYPE, i, &name, - &type, &count_found) - == B_OK); ++i) - { - if (type == B_MIME_TYPE) - { - if (index < (buf_size - 1)) - { - buf[index++] = strdup (name); - - if (!buf[index - 1]) - break; - } - } - } - - buf[index] = NULL; - - cb->Unlock (); -} - -static void be_set_clipboard_data_1 (BClipboard *cb, const char *type, const char *data, ssize_t len, bool clear) { @@ -197,14 +161,17 @@ be_update_clipboard_count (enum haiku_clipboard id) { case CLIPBOARD_CLIPBOARD: count_clipboard = system_clipboard->SystemCount (); + owned_clipboard = true; break; case CLIPBOARD_PRIMARY: count_primary = primary->SystemCount (); + owned_primary = true; break; case CLIPBOARD_SECONDARY: count_secondary = secondary->SystemCount (); + owned_secondary = true; break; } } @@ -227,14 +194,6 @@ be_set_clipboard_data (enum haiku_clipboard id, const char *type, data, len, clear); } -void -be_get_clipboard_targets (enum haiku_clipboard id, char **targets, - int len) -{ - be_get_clipboard_targets_1 (get_clipboard_object (id), targets, - len); -} - static bool clipboard_owner_p (void) { @@ -278,7 +237,7 @@ be_clipboard_owner_p (enum haiku_clipboard clipboard) } void -init_haiku_select (void) +be_clipboard_init (void) { system_clipboard = new BClipboard ("system"); primary = new BClipboard ("primary"); @@ -488,3 +447,73 @@ be_unlock_clipboard (enum haiku_clipboard clipboard, bool discard) board->Unlock (); } + +void +be_handle_clipboard_changed_message (void) +{ + int64 n_clipboard, n_primary, n_secondary; + + n_clipboard = system_clipboard->SystemCount (); + n_primary = primary->SystemCount (); + n_secondary = secondary->SystemCount (); + + if (count_clipboard != -1 + && (n_clipboard > count_clipboard + 1) + && owned_clipboard) + { + owned_clipboard = false; + haiku_selection_disowned (CLIPBOARD_CLIPBOARD, + n_clipboard); + } + + if (count_primary != -1 + && (n_primary > count_primary + 1) + && owned_primary) + { + owned_primary = false; + haiku_selection_disowned (CLIPBOARD_PRIMARY, + n_primary); + } + + if (count_secondary != -1 + && (n_secondary > count_secondary + 1) + && owned_secondary) + { + owned_secondary = false; + haiku_selection_disowned (CLIPBOARD_SECONDARY, + n_secondary); + } +} + +void +be_start_watching_selection (enum haiku_clipboard id) +{ + BClipboard *clipboard; + + clipboard = get_clipboard_object (id); + clipboard->StartWatching (be_app); +} + +bool +be_selection_outdated_p (enum haiku_clipboard id, int64 count) +{ + if (id == CLIPBOARD_CLIPBOARD && count_clipboard > count) + return true; + + if (id == CLIPBOARD_PRIMARY && count_primary > count) + return true; + + if (id == CLIPBOARD_SECONDARY && count_secondary > count) + return true; + + return false; +} + +int64 +be_get_clipboard_count (enum haiku_clipboard id) +{ + BClipboard *clipboard; + + clipboard = get_clipboard_object (id); + return clipboard->SystemCount (); +} diff --git a/src/haiku_support.cc b/src/haiku_support.cc index 182f2128473..983928442a1 100644 --- a/src/haiku_support.cc +++ b/src/haiku_support.cc @@ -21,6 +21,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <app/Application.h> #include <app/Cursor.h> +#include <app/Clipboard.h> #include <app/Messenger.h> #include <app/Roster.h> @@ -92,22 +93,23 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ /* Some messages that Emacs sends to itself. */ enum { - SCROLL_BAR_UPDATE = 3000, - WAIT_FOR_RELEASE = 3001, - RELEASE_NOW = 3002, - CANCEL_DROP = 3003, - SHOW_MENU_BAR = 3004, - BE_MENU_BAR_OPEN = 3005, - QUIT_APPLICATION = 3006, - REPLAY_MENU_BAR = 3007, - FONT_FAMILY_SELECTED = 3008, - FONT_STYLE_SELECTED = 3009, - FILE_PANEL_SELECTION = 3010, - QUIT_PREVIEW_DIALOG = 3011, - SET_FONT_INDICES = 3012, - SET_PREVIEW_DIALOG = 3013, - UPDATE_PREVIEW_DIALOG = 3014, - SEND_MOVE_FRAME_EVENT = 3015, + SCROLL_BAR_UPDATE = 3000, + WAIT_FOR_RELEASE = 3001, + RELEASE_NOW = 3002, + CANCEL_DROP = 3003, + SHOW_MENU_BAR = 3004, + BE_MENU_BAR_OPEN = 3005, + QUIT_APPLICATION = 3006, + REPLAY_MENU_BAR = 3007, + FONT_FAMILY_SELECTED = 3008, + FONT_STYLE_SELECTED = 3009, + FILE_PANEL_SELECTION = 3010, + QUIT_PREVIEW_DIALOG = 3011, + SET_FONT_INDICES = 3012, + SET_PREVIEW_DIALOG = 3013, + UPDATE_PREVIEW_DIALOG = 3014, + SEND_MOVE_FRAME_EVENT = 3015, + SET_DISABLE_ANTIALIASING = 3016, }; /* X11 keysyms that we use. */ @@ -140,12 +142,15 @@ enum struct font_selection_dialog_message { - /* Whether or not font selection was cancelled. */ + /* Whether or not font selection was canceled. */ bool_bf cancel : 1; /* Whether or not a size was explicitly specified. */ bool_bf size_specified : 1; + /* Whether or not antialiasing should be disabled. */ + bool_bf disable_antialias : 1; + /* The index of the selected font family. */ int family_idx; @@ -184,10 +189,6 @@ static BMessage volatile *popup_track_message; number. */ static int32 volatile alert_popup_value; -/* The current window ID. This is increased every time a frame is - created. */ -static int current_window_id; - /* The view that has the passive grab. */ static void *grab_view; @@ -644,8 +645,12 @@ public: void MessageReceived (BMessage *msg) { + struct haiku_clipboard_changed_event rq; + if (msg->what == QUIT_APPLICATION) Quit (); + else if (msg->what == B_CLIPBOARD_CHANGED) + haiku_write (CLIPBOARD_CHANGED_EVENT, &rq); else BApplication::MessageReceived (msg); } @@ -689,7 +694,6 @@ public: was_shown_p (false), menu_bar_active_p (false), override_redirect_p (false), - window_id (current_window_id), menus_begun (NULL), z_group (Z_GROUP_NONE), tooltip_p (false), @@ -932,12 +936,11 @@ public: if (msg->WasDropped ()) { BPoint whereto; - int32 windowid; + int64 threadid; struct haiku_drag_and_drop_event rq; - if (msg->FindInt32 ("emacs:window_id", &windowid) == B_OK - && !msg->IsSourceRemote () - && windowid == this->window_id) + if (msg->FindInt64 ("emacs:thread_id", &threadid) == B_OK + && threadid == find_thread (NULL)) return; whereto = msg->DropPoint (); @@ -1493,7 +1496,6 @@ public: class EmacsView : public BView { public: - uint32_t previous_buttons; int looper_locked_count; BRegion sb_region; BRegion invalid_region; @@ -1508,12 +1510,13 @@ public: BLocker cr_surface_lock; #endif - BPoint tt_absl_pos; BMessage *wait_for_release_message; + int64 grabbed_buttons; + BScreen screen; + bool use_frame_synchronization; EmacsView () : BView (BRect (0, 0, 0, 0), "Emacs", B_FOLLOW_NONE, B_WILL_DRAW), - previous_buttons (0), looper_locked_count (0), offscreen_draw_view (NULL), offscreen_draw_bitmap_1 (NULL), @@ -1522,7 +1525,9 @@ public: cr_surface (NULL), cr_context (NULL), #endif - wait_for_release_message (NULL) + wait_for_release_message (NULL), + grabbed_buttons (0), + use_frame_synchronization (false) { } @@ -1545,6 +1550,16 @@ public: } void + SetFrameSynchronization (bool sync) + { + if (LockLooper ()) + { + use_frame_synchronization = sync; + UnlockLooper (); + } + } + + void MessageReceived (BMessage *msg) { uint32 buttons; @@ -1720,14 +1735,14 @@ public: void FlipBuffers (void) { + EmacsWindow *w; if (!LockLooper ()) gui_abort ("Failed to lock looper during buffer flip"); if (!offscreen_draw_view) gui_abort ("Failed to lock offscreen view during buffer flip"); offscreen_draw_view->Sync (); - - EmacsWindow *w = (EmacsWindow *) Window (); + w = (EmacsWindow *) Window (); w->shown_flag = 0; if (copy_bitmap && @@ -1748,6 +1763,11 @@ public: if (copy_bitmap->InitCheck () != B_OK) gui_abort ("Failed to init copy bitmap during buffer flip"); + /* Wait for VBLANK. If responding to the invalidation or buffer + flipping takes longer than the blanking period, we lose. */ + if (use_frame_synchronization) + screen.WaitForRetrace (); + Invalidate (&invalid_region); invalid_region.MakeEmpty (); UnlockLooper (); @@ -1786,31 +1806,29 @@ public: MouseMoved (BPoint point, uint32 transit, const BMessage *drag_msg) { struct haiku_mouse_motion_event rq; - int32 windowid; + int64 threadid; EmacsWindow *window; - BToolTip *tooltip; window = (EmacsWindow *) Window (); - tooltip = ToolTip (); - rq.just_exited_p = transit == B_EXITED_VIEW; + if (transit == B_EXITED_VIEW) + rq.just_exited_p = true; + else + rq.just_exited_p = false; + rq.x = point.x; rq.y = point.y; rq.window = window; rq.time = system_time (); if (drag_msg && (drag_msg->IsSourceRemote () - || drag_msg->FindInt32 ("emacs:window_id", - &windowid) != B_OK - || windowid != window->window_id)) + || drag_msg->FindInt64 ("emacs:thread_id", + &threadid) != B_OK + || threadid != find_thread (NULL))) rq.dnd_message = true; else rq.dnd_message = false; - if (tooltip) - tooltip->SetMouseRelativeLocation (BPoint (-(point.x - tt_absl_pos.x), - -(point.y - tt_absl_pos.y))); - if (!grab_view_locker.Lock ()) gui_abort ("Couldn't lock grab view locker"); @@ -1826,42 +1844,51 @@ public: } void - BasicMouseDown (BPoint point, BView *scroll_bar) + BasicMouseDown (BPoint point, BView *scroll_bar, BMessage *message) { struct haiku_button_event rq; - uint32 mods, buttons; + int64 when; + int32 mods, buttons, button; - this->GetMouse (&point, &buttons, false); + if (message->FindInt64 ("when", &when) != B_OK + || message->FindInt32 ("modifiers", &mods) != B_OK + || message->FindInt32 ("buttons", &buttons) != B_OK) + return; - if (!grab_view_locker.Lock ()) - gui_abort ("Couldn't lock grab view locker"); - if (buttons) - grab_view = this; - grab_view_locker.Unlock (); + /* Find which button was pressed by comparing the previous button + mask to the current one. This assumes that B_MOUSE_DOWN will + be sent for each button press. */ + button = buttons & ~grabbed_buttons; + grabbed_buttons = buttons; + + if (!scroll_bar) + { + if (!grab_view_locker.Lock ()) + gui_abort ("Couldn't lock grab view locker"); + grab_view = this; + grab_view_locker.Unlock (); + } rq.window = this->Window (); rq.scroll_bar = scroll_bar; - if (!(previous_buttons & B_PRIMARY_MOUSE_BUTTON) - && (buttons & B_PRIMARY_MOUSE_BUTTON)) + if (button == B_PRIMARY_MOUSE_BUTTON) rq.btn_no = 0; - else if (!(previous_buttons & B_SECONDARY_MOUSE_BUTTON) - && (buttons & B_SECONDARY_MOUSE_BUTTON)) + else if (button == B_SECONDARY_MOUSE_BUTTON) rq.btn_no = 2; - else if (!(previous_buttons & B_TERTIARY_MOUSE_BUTTON) - && (buttons & B_TERTIARY_MOUSE_BUTTON)) + else if (button == B_TERTIARY_MOUSE_BUTTON) rq.btn_no = 1; else + /* We don't know which button was pressed. This usually happens + when a B_MOUSE_UP is sent to a view that didn't receive a + corresponding B_MOUSE_DOWN event, so simply ignore the + message. */ return; - previous_buttons = buttons; - rq.x = point.x; rq.y = point.y; - - mods = modifiers (); - rq.modifiers = 0; + if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; @@ -1878,62 +1905,76 @@ public: SetMouseEventMask (B_POINTER_EVENTS, (B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY)); - rq.time = system_time (); + rq.time = when; haiku_write (BUTTON_DOWN, &rq); } void MouseDown (BPoint point) { - BasicMouseDown (point, NULL); + BMessage *msg; + BLooper *looper; + + looper = Looper (); + msg = (looper + ? looper->CurrentMessage () + : NULL); + + if (msg) + BasicMouseDown (point, NULL, msg); } void - BasicMouseUp (BPoint point, BView *scroll_bar) + BasicMouseUp (BPoint point, BView *scroll_bar, BMessage *message) { struct haiku_button_event rq; - uint32 buttons, mods; + int64 when; + int32 mods, button, buttons; - this->GetMouse (&point, &buttons, false); + if (message->FindInt64 ("when", &when) != B_OK + || message->FindInt32 ("modifiers", &mods) != B_OK + || message->FindInt32 ("buttons", &buttons) != B_OK) + return; - if (!grab_view_locker.Lock ()) - gui_abort ("Couldn't lock grab view locker"); - if (!buttons) - grab_view = NULL; - grab_view_locker.Unlock (); + if (!scroll_bar) + { + if (!grab_view_locker.Lock ()) + gui_abort ("Couldn't lock grab view locker"); + if (!buttons) + grab_view = NULL; + grab_view_locker.Unlock (); + } + + button = (grabbed_buttons & ~buttons); + grabbed_buttons = buttons; - if (!buttons && wait_for_release_message) + if (wait_for_release_message) { - wait_for_release_message->SendReply (wait_for_release_message); - delete wait_for_release_message; - wait_for_release_message = NULL; + if (!grabbed_buttons) + { + wait_for_release_message->SendReply (wait_for_release_message); + delete wait_for_release_message; + wait_for_release_message = NULL; + } - previous_buttons = buttons; return; } rq.window = this->Window (); rq.scroll_bar = scroll_bar; - if ((previous_buttons & B_PRIMARY_MOUSE_BUTTON) - && !(buttons & B_PRIMARY_MOUSE_BUTTON)) + if (button == B_PRIMARY_MOUSE_BUTTON) rq.btn_no = 0; - else if ((previous_buttons & B_SECONDARY_MOUSE_BUTTON) - && !(buttons & B_SECONDARY_MOUSE_BUTTON)) + else if (button == B_SECONDARY_MOUSE_BUTTON) rq.btn_no = 2; - else if ((previous_buttons & B_TERTIARY_MOUSE_BUTTON) - && !(buttons & B_TERTIARY_MOUSE_BUTTON)) + else if (button == B_TERTIARY_MOUSE_BUTTON) rq.btn_no = 1; else return; - previous_buttons = buttons; - rq.x = point.x; rq.y = point.y; - mods = modifiers (); - rq.modifiers = 0; if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; @@ -1947,14 +1988,23 @@ public: if (mods & B_OPTION_KEY) rq.modifiers |= HAIKU_MODIFIER_SUPER; - rq.time = system_time (); + rq.time = when; haiku_write (BUTTON_UP, &rq); } void MouseUp (BPoint point) { - BasicMouseUp (point, NULL); + BMessage *msg; + BLooper *looper; + + looper = Looper (); + msg = (looper + ? looper->CurrentMessage () + : NULL); + + if (msg) + BasicMouseUp (point, NULL, msg); } }; @@ -1967,8 +2017,9 @@ public: float old_value; scroll_bar_info info; - /* True if button events should be passed to the parent. */ - bool handle_button; + /* How many button events were passed to the parent without + release. */ + int handle_button_count; bool in_overscroll; bool can_overscroll; bool maybe_overscroll; @@ -1984,7 +2035,7 @@ public: : BScrollBar (BRect (x, y, x1, y1), NULL, NULL, 0, 0, horizontal_p ? B_HORIZONTAL : B_VERTICAL), dragging (0), - handle_button (false), + handle_button_count (0), in_overscroll (false), can_overscroll (false), maybe_overscroll (false), @@ -2208,10 +2259,10 @@ public: && mods & B_CONTROL_KEY) { /* Allow C-mouse-3 to split the window on a scroll bar. */ - handle_button = true; + handle_button_count += 1; SetMouseEventMask (B_POINTER_EVENTS, (B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS)); - parent->BasicMouseDown (ConvertToParent (pt), this); + parent->BasicMouseDown (ConvertToParent (pt), this, message); return; } @@ -2274,14 +2325,23 @@ public: MouseUp (BPoint pt) { struct haiku_scroll_bar_drag_event rq; + BMessage *msg; + BLooper *looper; in_overscroll = false; maybe_overscroll = false; - if (handle_button) + if (handle_button_count) { - handle_button = false; - parent->BasicMouseUp (ConvertToParent (pt), this); + handle_button_count--; + looper = Looper (); + msg = (looper + ? looper->CurrentMessage () + : NULL); + + if (msg) + parent->BasicMouseUp (ConvertToParent (pt), + this, msg); return; } @@ -2574,6 +2634,9 @@ class EmacsFontPreviewDialog : public BWindow current_font = new BFont; current_font->SetFamilyAndStyle (name, sname); + if (message->GetBool ("emacs:disable_antialiasing", false)) + current_font->SetFlags (B_DISABLE_ANTIALIASING); + if (size_name && strlen (size_name)) { size = atoi (size_name); @@ -2615,26 +2678,32 @@ public: } }; -class DualLayoutView : public BView +class TripleLayoutView : public BView { BScrollView *view_1; - BView *view_2; + BView *view_2, *view_3; void FrameResized (float new_width, float new_height) { BRect frame; - float width, height; + float width, height, height_1, width_1; + float basic_height; frame = Frame (); view_2->GetPreferredSize (&width, &height); + view_3->GetPreferredSize (&width_1, &height_1); + + basic_height = height + height_1; view_1->MoveTo (0, 0); view_1->ResizeTo (BE_RECT_WIDTH (frame), - BE_RECT_HEIGHT (frame) - height); - view_2->MoveTo (2, BE_RECT_HEIGHT (frame) - height); + BE_RECT_HEIGHT (frame) - basic_height); + view_2->MoveTo (2, BE_RECT_HEIGHT (frame) - basic_height); view_2->ResizeTo (BE_RECT_WIDTH (frame) - 4, height); + view_3->MoveTo (2, BE_RECT_HEIGHT (frame) - height_1); + view_3->ResizeTo (BE_RECT_WIDTH (frame) - 4, height_1); BView::FrameResized (new_width, new_height); } @@ -2644,19 +2713,24 @@ class DualLayoutView : public BView MinSize (void) { float width, height; + float width_1, height_1; BSize size_1; size_1 = view_1->MinSize (); view_2->GetPreferredSize (&width, &height); + view_3->GetPreferredSize (&width_1, &height_1); - return BSize (std::max (size_1.width, width), - std::max (size_1.height, height)); + return BSize (std::max (size_1.width, + std::max (width_1, width)), + std::max (size_1.height, height + height_1)); } public: - DualLayoutView (BScrollView *first, BView *second) : BView (NULL, B_FRAME_EVENTS), - view_1 (first), - view_2 (second) + TripleLayoutView (BScrollView *first, BView *second, + BView *third) : BView (NULL, B_FRAME_EVENTS), + view_1 (first), + view_2 (second), + view_3 (third) { FrameResized (801, 801); } @@ -2665,13 +2739,14 @@ public: class EmacsFontSelectionDialog : public BWindow { BView basic_view; + BCheckBox antialias_checkbox; BCheckBox preview_checkbox; BSplitView split_view; BListView font_family_pane; BListView font_style_pane; BScrollView font_family_scroller; BScrollView font_style_scroller; - DualLayoutView style_view; + TripleLayoutView style_view; BObjectList<BStringItem> all_families; BObjectList<BStringItem> all_styles; BButton cancel_button, ok_button; @@ -2707,6 +2782,9 @@ class EmacsFontSelectionDialog : public BWindow message.AddInt32 ("emacs:family", family); message.AddInt32 ("emacs:style", style); + if (antialias_checkbox.Value () == B_CONTROL_ON) + message.AddBool ("emacs:disable_antialiasing", true); + message.AddString ("emacs:size", size_entry.Text ()); @@ -2834,6 +2912,11 @@ class EmacsFontSelectionDialog : public BWindow rq.size = atoi (text); rq.size_specified = rq.size > 0 || strlen (text); + if (antialias_checkbox.Value () == B_CONTROL_ON) + rq.disable_antialias = true; + else + rq.disable_antialias = false; + write_port (comm_port, 0, &rq, sizeof rq); } else if (msg->what == B_CANCEL) @@ -2859,6 +2942,11 @@ class EmacsFontSelectionDialog : public BWindow if (preview) UpdatePreview (); } + else if (msg->what == SET_DISABLE_ANTIALIASING) + { + if (preview) + UpdatePreview (); + } BWindow::MessageReceived (msg); } @@ -2881,6 +2969,7 @@ public: font_family_pane.RemoveSelf (); font_style_pane.RemoveSelf (); + antialias_checkbox.RemoveSelf (); preview_checkbox.RemoveSelf (); style_view.RemoveSelf (); font_family_scroller.RemoveSelf (); @@ -2897,12 +2986,15 @@ public: EmacsFontSelectionDialog (bool monospace_only, int initial_family_idx, int initial_style_idx, - int initial_size) + int initial_size, + bool initial_antialias) : BWindow (BRect (0, 0, 500, 500), "Select font from list", B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL, 0), basic_view (NULL, 0), + antialias_checkbox ("Disable antialiasing", "Disable antialiasing", + new BMessage (SET_DISABLE_ANTIALIASING)), preview_checkbox ("Show preview", "Show preview", new BMessage (SET_PREVIEW_DIALOG)), font_family_pane (BRect (0, 0, 0, 0), NULL, @@ -2917,7 +3009,8 @@ public: font_style_scroller (NULL, &font_style_pane, B_FOLLOW_ALL_SIDES, B_SUPPORTS_LAYOUT, false, true), - style_view (&font_style_scroller, &preview_checkbox), + style_view (&font_style_scroller, &antialias_checkbox, + &preview_checkbox), all_families (20, true), all_styles (20, true), cancel_button ("Cancel", "Cancel", @@ -2946,6 +3039,7 @@ public: split_view.AddChild (&font_family_scroller, 0.7); split_view.AddChild (&style_view, 0.3); style_view.AddChild (&font_style_scroller); + style_view.AddChild (&antialias_checkbox); style_view.AddChild (&preview_checkbox); basic_view.SetViewUIColor (B_PANEL_BACKGROUND_COLOR); @@ -3007,6 +3101,9 @@ public: sprintf (format_buffer, "%d", initial_size); size_entry.SetText (format_buffer); } + + if (!initial_antialias) + antialias_checkbox.SetValue (B_CONTROL_ON); } void @@ -3225,6 +3322,41 @@ public: } }; +/* A view that is added as a child of a tooltip's text view, and + prevents motion events from reaching it (thereby moving the + tooltip). */ +class EmacsMotionSuppressionView : public BView +{ + void + AttachedToWindow (void) + { + BView *text_view, *tooltip_view; + + /* We know that this view is a child of the text view, whose + parent is the tooltip view, and that the tooltip view has + already set its mouse event mask. */ + + text_view = Parent (); + + if (!text_view) + return; + + tooltip_view = text_view->Parent (); + + if (!tooltip_view) + return; + + tooltip_view->SetEventMask (B_KEYBOARD_EVENTS, 0); + } + +public: + EmacsMotionSuppressionView (void) : BView (BRect (-1, -1, 1, 1), + NULL, 0, 0) + { + return; + } +}; + static int32 start_running_application (void *data) { @@ -3474,8 +3606,8 @@ be_get_screen_dimensions (int *width, int *height) frame = screen.Frame (); - *width = 1 + frame.right - frame.left; - *height = 1 + frame.bottom - frame.top; + *width = BE_RECT_WIDTH (frame); + *height = BE_RECT_HEIGHT (frame); } /* Resize VIEW to WIDTH, HEIGHT. */ @@ -4263,30 +4395,48 @@ BView_set_tooltip (void *view, const char *tooltip) /* Set VIEW's tooltip to a sticky tooltip at X by Y. */ void -BView_set_and_show_sticky_tooltip (void *view, const char *tooltip, - int x, int y) +be_show_sticky_tooltip (void *view, const char *tooltip_text, + int x, int y) { - BToolTip *tip; - BView *vw = (BView *) view; + BToolTip *tooltip; + BView *vw, *tooltip_view; + BPoint point; + + vw = (BView *) view; + if (!vw->LockLooper ()) gui_abort ("Failed to lock view while showing sticky tooltip"); - vw->SetToolTip (tooltip); - tip = vw->ToolTip (); - BPoint pt; - EmacsView *ev = dynamic_cast<EmacsView *> (vw); - if (ev) - ev->tt_absl_pos = BPoint (x, y); - vw->GetMouse (&pt, NULL, 1); - pt.x -= x; - pt.y -= y; + vw->SetToolTip ((const char *) NULL); + + /* If the tooltip text is empty, then a tooltip object won't be + created by SetToolTip. */ + if (tooltip_text[0] == '\0') + tooltip_text = " "; + + vw->SetToolTip (tooltip_text); + + tooltip = vw->ToolTip (); + + vw->GetMouse (&point, NULL, 1); + point.x -= x; + point.y -= y; + + point.x = -point.x; + point.y = -point.y; + + /* We don't have to make the tooltip sticky since not receiving + mouse movement is enough to prevent it from being hidden. */ + tooltip->SetMouseRelativeLocation (point); - pt.x = -pt.x; - pt.y = -pt.y; + /* Prevent the tooltip from moving in response to mouse + movement. */ + tooltip_view = tooltip->View (); - tip->SetMouseRelativeLocation (pt); - tip->SetSticky (1); - vw->ShowToolTip (tip); + if (tooltip_view) + tooltip_view->AddChild (new EmacsMotionSuppressionView); + + vw->ShowToolTip (tooltip); vw->UnlockLooper (); } @@ -4950,13 +5100,17 @@ be_drag_message (void *view, void *message, bool allow_same_view, BMessage cancel_message (CANCEL_DROP); struct object_wait_info infos[2]; ssize_t stat; + thread_id window_thread; block_input_function (); - if (!allow_same_view && - (msg->ReplaceInt32 ("emacs:window_id", window->window_id) - == B_NAME_NOT_FOUND)) - msg->AddInt32 ("emacs:window_id", window->window_id); + if (!allow_same_view) + window_thread = window->Looper ()->Thread (); + + if (!allow_same_view + && (msg->ReplaceInt64 ("emacs:thread_id", window_thread) + == B_NAME_NOT_FOUND)) + msg->AddInt64 ("emacs:thread_id", window_thread); if (!vw->LockLooper ()) gui_abort ("Failed to lock view looper for drag"); @@ -5096,7 +5250,8 @@ be_select_font (void (*process_pending_signals_function) (void), haiku_font_family_or_style *style, int *size, bool allow_monospace_only, int initial_family, int initial_style, - int initial_size) + int initial_size, bool initial_antialias, + bool *disable_antialias) { EmacsFontSelectionDialog *dialog; struct font_selection_dialog_message msg; @@ -5106,7 +5261,7 @@ be_select_font (void (*process_pending_signals_function) (void), dialog = new EmacsFontSelectionDialog (allow_monospace_only, initial_family, initial_style, - initial_size); + initial_size, initial_antialias); dialog->CenterOnScreen (); if (dialog->InitCheck () < B_OK) @@ -5135,6 +5290,7 @@ be_select_font (void (*process_pending_signals_function) (void), memcpy (family, family_buffer, sizeof *family); memcpy (style, style_buffer, sizeof *style); *size = msg.size_specified ? msg.size : -1; + *disable_antialias = msg.disable_antialias; return true; } @@ -5322,3 +5478,26 @@ be_get_explicit_workarea (int *x, int *y, int *width, int *height) return true; } + +/* Clear the grab view. This has to be called manually from some + places, since we don't get B_MOUSE_UP messages after a popup menu + is run. */ + +void +be_clear_grab_view (void) +{ + if (grab_view_locker.Lock ()) + { + grab_view = NULL; + grab_view_locker.Unlock (); + } +} + +void +be_set_use_frame_synchronization (void *view, bool sync) +{ + EmacsView *vw; + + vw = (EmacsView *) view; + vw->SetFrameSynchronization (sync); +} diff --git a/src/haiku_support.h b/src/haiku_support.h index 7f8d471b650..ca1808556a4 100644 --- a/src/haiku_support.h +++ b/src/haiku_support.h @@ -114,8 +114,14 @@ enum haiku_event_type DUMMY_EVENT, SCREEN_CHANGED_EVENT, MENU_BAR_LEFT, + CLIPBOARD_CHANGED_EVENT, }; +struct haiku_clipboard_changed_event +{ + char dummy; +}; + struct haiku_screen_changed_event { bigtime_t when; @@ -271,6 +277,7 @@ enum haiku_font_specification FSPEC_WIDTH = 1 << 7, FSPEC_LANGUAGE = 1 << 8, FSPEC_INDICES = 1 << 9, + FSPEC_ANTIALIAS = 1 << 10, }; typedef char haiku_font_family_or_style[64]; @@ -390,6 +397,10 @@ struct haiku_font_pattern /* Temporary field used during font enumeration. */ int oblique_seen_p; + + /* Whether or not to enable antialiasing in the font. This field is + special in that it's not handled by `BFont_open_pattern'. */ + int use_antialiasing; }; struct haiku_scroll_bar_value_event @@ -553,10 +564,8 @@ extern void BView_StrokeLine (void *, int, int, int, int); extern void BView_CopyBits (void *, int, int, int, int, int, int, int, int); extern void BView_InvertRect (void *, int, int, int, int); extern void BView_DrawBitmap (void *, void *, int, int, int, int, int, int, - int, int); + int, int, bool); extern void BView_DrawBitmapWithEraseOp (void *, void *, int, int, int, int); -extern void BView_DrawMask (void *, void *, int, int, int, int, int, int, - int, int, uint32_t); extern void BView_DrawBitmapTiled (void *, void *, int, int, int, int, int, int, int, int); @@ -565,8 +574,15 @@ extern void BView_set_view_cursor (void *, void *); extern void BView_move_frame (void *, int, int, int, int); extern void BView_scroll_bar_update (void *, int, int, int, int, bool); -extern void *BBitmap_transform_bitmap (void *, void *, uint32_t, double, - int, int); +extern void *be_transform_bitmap (void *, void *, uint32_t, double, + int, int, bool); +extern void be_apply_affine_transform (void *, double, double, double, + double, double, double); +extern void be_apply_inverse_transform (double (*)[3], int, int, int *, int *); +extern void be_draw_image_mask (void *, void *, int, int, int, int, int, int, + int, int, uint32_t); +extern void be_draw_bitmap_with_mask (void *, void *, void *, int, int, int, + int, int, int, int, int, bool); extern void be_get_display_resolution (double *, double *); extern void be_get_screen_dimensions (int *, int *); @@ -632,8 +648,7 @@ extern int32 BAlert_go (void *, void (*) (void), void (*) (void), extern void BButton_set_enabled (void *, int); extern void BView_set_tooltip (void *, const char *); extern void BView_show_tooltip (void *); -extern void BView_set_and_show_sticky_tooltip (void *, const char *, - int, int); +extern void be_show_sticky_tooltip (void *, const char *, int, int); extern void BAlert_delete (void *); @@ -684,6 +699,7 @@ extern const char *be_find_setting (const char *); extern haiku_font_family_or_style *be_list_font_families (size_t *); extern void be_font_style_to_flags (char *, struct haiku_font_pattern *); extern void *be_open_font_at_index (int, int, float); +extern void be_set_font_antialiasing (void *, bool); extern int be_get_ui_color (const char *, uint32_t *); extern void BMessage_delete (void *); @@ -697,7 +713,8 @@ extern bool be_replay_menu_bar_event (void *, struct haiku_menu_bar_click_event extern bool be_select_font (void (*) (void), bool (*) (void), haiku_font_family_or_style *, haiku_font_family_or_style *, - int *, bool, int, int, int); + int *, bool, int, int, int, + bool, bool *); extern int be_find_font_indices (struct haiku_font_pattern *, int *, int *); extern status_t be_roster_launch (const char *, const char *, char **, @@ -710,6 +727,8 @@ extern void be_set_window_fullscreen_mode (void *, enum haiku_fullscreen_mode); extern void be_lock_window (void *); extern void be_unlock_window (void *); extern bool be_get_explicit_workarea (int *, int *, int *, int *); +extern void be_clear_grab_view (void); +extern void be_set_use_frame_synchronization (void *, bool); #ifdef __cplusplus } diff --git a/src/haikufns.c b/src/haikufns.c index b79443203ff..aaa4e866228 100644 --- a/src/haikufns.c +++ b/src/haikufns.c @@ -949,6 +949,10 @@ haiku_create_frame (Lisp_Object parms) || !FRAME_LIVE_P (XFRAME (KVAR (kb, Vdefault_minibuffer_frame))))) kset_default_minibuffer_frame (kb, frame); + /* Set whether or not frame synchronization is enabled. */ + gui_default_parameter (f, parms, Quse_frame_synchronization, Qt, + NULL, NULL, RES_TYPE_BOOLEAN); + gui_default_parameter (f, parms, Qz_group, Qnil, NULL, NULL, RES_TYPE_SYMBOL); @@ -1290,16 +1294,17 @@ compute_tip_xy (struct frame *f, static Lisp_Object haiku_hide_tip (bool delete) { + Lisp_Object it, frame; + if (!NILP (tip_timer)) { call1 (Qcancel_timer, tip_timer); tip_timer = Qnil; } - Lisp_Object it, frame; FOR_EACH_FRAME (it, frame) - if (FRAME_WINDOW_P (XFRAME (frame)) && - FRAME_HAIKU_VIEW (XFRAME (frame))) + if (FRAME_WINDOW_P (XFRAME (frame)) + && FRAME_HAIKU_VIEW (XFRAME (frame))) BView_set_tooltip (FRAME_HAIKU_VIEW (XFRAME (frame)), NULL); if (NILP (tip_frame) @@ -1500,9 +1505,9 @@ haiku_set_background_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval if (FRAME_HAIKU_VIEW (f)) { - BView_draw_lock (FRAME_HAIKU_VIEW (f), false, 0, 0, 0, 0); - BView_SetViewColor (FRAME_HAIKU_VIEW (f), background); - BView_draw_unlock (FRAME_HAIKU_VIEW (f)); + BView_draw_lock (FRAME_HAIKU_DRAWABLE (f), false, 0, 0, 0, 0); + BView_SetViewColor (FRAME_HAIKU_DRAWABLE (f), background); + BView_draw_unlock (FRAME_HAIKU_DRAWABLE (f)); FRAME_OUTPUT_DATA (f)->cursor_fg = background; update_face_from_frame_parameter (f, Qbackground_color, arg); @@ -2114,6 +2119,13 @@ haiku_set_mouse_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval) update_face_from_frame_parameter (f, Qmouse_color, arg); } +static void +haiku_set_use_frame_synchronization (struct frame *f, Lisp_Object arg, + Lisp_Object oldval) +{ + be_set_use_frame_synchronization (FRAME_HAIKU_VIEW (f), !NILP (arg)); +} + DEFUN ("haiku-set-mouse-absolute-pixel-position", @@ -2329,6 +2341,10 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, int old_windows_or_buffers_changed = windows_or_buffers_changed; specpdl_ref count = SPECPDL_INDEX (); Lisp_Object window, size, tip_buf; + bool displayed; +#ifdef ENABLE_CHECKING + struct glyph_row *row, *end; +#endif AUTO_STRING (tip, " *tip*"); specbind (Qinhibit_redisplay, Qt); @@ -2391,8 +2407,8 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, reliable way to get it. */ compute_tip_xy (f, parms, dx, dy, width, height, &root_x, &root_y); BView_convert_from_screen (FRAME_HAIKU_VIEW (f), &root_x, &root_y); - BView_set_and_show_sticky_tooltip (FRAME_HAIKU_VIEW (f), SSDATA (string), - root_x, root_y); + be_show_sticky_tooltip (FRAME_HAIKU_VIEW (f), SSDATA (string), + root_x, root_y); unblock_input (); goto start_timer; } @@ -2400,7 +2416,6 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame))) { if (FRAME_VISIBLE_P (XFRAME (tip_frame)) - && EQ (frame, tip_last_frame) && !NILP (Fequal_including_properties (tip_last_string, string)) && !NILP (Fequal (tip_last_parms, parms))) { @@ -2557,7 +2572,26 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, clear_glyph_matrix (w->desired_matrix); clear_glyph_matrix (w->current_matrix); SET_TEXT_POS (pos, BEGV, BEGV_BYTE); - try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + row = w->desired_matrix->rows; + end = w->desired_matrix->rows + w->desired_matrix->nrows; + + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + + eassert (row < end && row->ends_at_zv_p); +#endif + } + /* Calculate size of tooltip window. */ size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, make_fixnum (w->pixel_height), Qnil, @@ -3105,6 +3139,7 @@ frame_parm_handler haiku_frame_parm_handlers[] = haiku_set_override_redirect, gui_set_no_special_glyphs, gui_set_alpha_background, + haiku_set_use_frame_synchronization, }; void @@ -3178,7 +3213,7 @@ syms_of_haikufns (void) DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, doc: /* SKIP: real doc in xfns.c. */); - Vx_max_tooltip_size = Fcons (make_fixnum (80), make_fixnum (40)); + Vx_max_tooltip_size = Qnil; DEFVAR_LISP ("x-cursor-fore-pixel", Vx_cursor_fore_pixel, doc: /* SKIP: real doc in xfns.c. */); diff --git a/src/haikufont.c b/src/haikufont.c index 54f11c6e413..3e7f6f86dcb 100644 --- a/src/haikufont.c +++ b/src/haikufont.c @@ -381,54 +381,67 @@ haikufont_maybe_handle_special_family (Lisp_Object family, static Lisp_Object haikufont_pattern_to_entity (struct haiku_font_pattern *ptn) { - Lisp_Object ent; + Lisp_Object entity, extras; + + entity = font_make_entity (); + extras = Qnil; + + ASET (entity, FONT_TYPE_INDEX, Qhaiku); + ASET (entity, FONT_FOUNDRY_INDEX, Qhaiku); + ASET (entity, FONT_FAMILY_INDEX, Qdefault); + ASET (entity, FONT_ADSTYLE_INDEX, Qnil); + ASET (entity, FONT_REGISTRY_INDEX, Qiso10646_1); + ASET (entity, FONT_SIZE_INDEX, make_fixnum (0)); + ASET (entity, FONT_AVGWIDTH_INDEX, make_fixnum (0)); + ASET (entity, FONT_SPACING_INDEX, make_fixnum (FONT_SPACING_MONO)); + + /* FONT_EXTRA_INDEX in a font entity can contain a cons of two + numbers (STYLE . IDX) under the key :indices that tell Emacs how + to open a font. */ + if (ptn->specified & FSPEC_INDICES) + extras = Fcons (Fcons (QCindices, + Fcons (make_fixnum (ptn->family_index), + make_fixnum (ptn->style_index))), + extras); - ent = font_make_entity (); - ASET (ent, FONT_TYPE_INDEX, Qhaiku); - ASET (ent, FONT_FOUNDRY_INDEX, Qhaiku); - ASET (ent, FONT_FAMILY_INDEX, Qdefault); - ASET (ent, FONT_ADSTYLE_INDEX, Qnil); - ASET (ent, FONT_REGISTRY_INDEX, Qiso10646_1); - ASET (ent, FONT_SIZE_INDEX, make_fixnum (0)); - ASET (ent, FONT_AVGWIDTH_INDEX, make_fixnum (0)); - ASET (ent, FONT_SPACING_INDEX, make_fixnum (FONT_SPACING_MONO)); + if (ptn->specified & FSPEC_ANTIALIAS) + extras = Fcons (Fcons (QCantialias, + ptn->use_antialiasing ? Qt : Qnil), + extras); - /* FONT_EXTRA_INDEX in a font entity can be a cons of two numbers - (STYLE . IDX) that tell Emacs how to open a font. */ - if (ptn->specified & FSPEC_INDICES) - ASET (ent, FONT_EXTRA_INDEX, - Fcons (make_fixnum (ptn->family_index), - make_fixnum (ptn->style_index))); + ASET (entity, FONT_EXTRA_INDEX, extras); - FONT_SET_STYLE (ent, FONT_WIDTH_INDEX, Qnormal); - FONT_SET_STYLE (ent, FONT_WEIGHT_INDEX, Qnormal); - FONT_SET_STYLE (ent, FONT_SLANT_INDEX, Qnormal); + FONT_SET_STYLE (entity, FONT_WIDTH_INDEX, Qnormal); + FONT_SET_STYLE (entity, FONT_WEIGHT_INDEX, Qnormal); + FONT_SET_STYLE (entity, FONT_SLANT_INDEX, Qnormal); if (ptn->specified & FSPEC_FAMILY) - ASET (ent, FONT_FAMILY_INDEX, intern (ptn->family)); + ASET (entity, FONT_FAMILY_INDEX, intern (ptn->family)); else - ASET (ent, FONT_FAMILY_INDEX, Qdefault); + ASET (entity, FONT_FAMILY_INDEX, Qdefault); if (ptn->specified & FSPEC_STYLE) - ASET (ent, FONT_ADSTYLE_INDEX, intern (ptn->style)); + ASET (entity, FONT_ADSTYLE_INDEX, intern (ptn->style)); else { if (ptn->specified & FSPEC_WEIGHT) - FONT_SET_STYLE (ent, FONT_WEIGHT_INDEX, + FONT_SET_STYLE (entity, FONT_WEIGHT_INDEX, haikufont_weight_to_lisp (ptn->weight)); if (ptn->specified & FSPEC_SLANT) - FONT_SET_STYLE (ent, FONT_SLANT_INDEX, + FONT_SET_STYLE (entity, FONT_SLANT_INDEX, haikufont_slant_to_lisp (ptn->slant)); if (ptn->specified & FSPEC_WIDTH) - FONT_SET_STYLE (ent, FONT_WIDTH_INDEX, + FONT_SET_STYLE (entity, FONT_WIDTH_INDEX, haikufont_width_to_lisp (ptn->width)); } if (ptn->specified & FSPEC_SPACING) - ASET (ent, FONT_SPACING_INDEX, - make_fixnum (ptn->mono_spacing_p ? - FONT_SPACING_MONO : FONT_SPACING_PROPORTIONAL)); - return ent; + ASET (entity, FONT_SPACING_INDEX, + make_fixnum (ptn->mono_spacing_p + ? FONT_SPACING_MONO + : FONT_SPACING_PROPORTIONAL)); + + return entity; } static void @@ -479,6 +492,14 @@ haikufont_pattern_from_object (struct haiku_font_pattern *pattern, pattern->specified |= FSPEC_WIDTH; pattern->width = haikufont_lisp_to_width (val); } + + val = assq_no_quit (QCantialias, + AREF (font_object, FONT_EXTRA_INDEX)); + if (CONSP (val)) + { + pattern->specified |= FSPEC_ANTIALIAS; + pattern->use_antialiasing = !NILP (XCDR (val)); + } } static void @@ -613,6 +634,13 @@ haikufont_spec_or_entity_to_pattern (Lisp_Object ent, int list_p, } } + tem = assq_no_quit (QCantialias, AREF (ent, FONT_EXTRA_INDEX)); + if (CONSP (tem)) + { + ptn->specified |= FSPEC_ANTIALIAS; + ptn->use_antialiasing = !NILP (XCDR (tem)); + } + tem = AREF (ent, FONT_REGISTRY_INDEX); if (SYMBOLP (tem)) haikufont_apply_registry (ptn, tem); @@ -732,10 +760,10 @@ haikufont_open (struct frame *f, Lisp_Object font_entity, int x) struct haiku_font_pattern ptn; struct font *font; void *be_font; - Lisp_Object font_object, tem, extra; - int px_size, min_width, max_width, - avg_width, height, space_width, ascent, - descent, underline_pos, underline_thickness; + Lisp_Object font_object, tem, extra, indices, antialias; + int px_size, min_width, max_width; + int avg_width, height, space_width, ascent; + int descent, underline_pos, underline_thickness; if (x <= 0) { @@ -746,15 +774,21 @@ haikufont_open (struct frame *f, Lisp_Object font_entity, int x) extra = AREF (font_entity, FONT_EXTRA_INDEX); + indices = assq_no_quit (QCindices, extra); + antialias = assq_no_quit (QCantialias, extra); + + if (CONSP (indices)) + indices = XCDR (indices); + /* If the font's indices is already available, open the font using those instead. */ - if (CONSP (extra) && FIXNUMP (XCAR (extra)) - && FIXNUMP (XCDR (extra))) + if (CONSP (indices) && FIXNUMP (XCAR (indices)) + && FIXNUMP (XCDR (indices))) { block_input (); - be_font = be_open_font_at_index (XFIXNUM (XCAR (extra)), - XFIXNUM (XCDR (extra)), x); + be_font = be_open_font_at_index (XFIXNUM (XCAR (indices)), + XFIXNUM (XCDR (indices)), x); unblock_input (); if (!be_font) @@ -778,13 +812,8 @@ haikufont_open (struct frame *f, Lisp_Object font_entity, int x) block_input (); - /* `font_make_object' tries to treat the extra data as an alist. - There is never any real data here, so clear that field. */ - - ASET (font_entity, FONT_EXTRA_INDEX, Qnil); font_object = font_make_object (VECSIZE (struct haikufont_info), font_entity, x); - ASET (font_entity, FONT_EXTRA_INDEX, extra); ASET (font_object, FONT_TYPE_INDEX, Qhaiku); font_info = (struct haikufont_info *) XFONT_OBJECT (font_object); @@ -799,6 +828,9 @@ haikufont_open (struct frame *f, Lisp_Object font_entity, int x) font_info->be_font = be_font; font_info->glyphs = xzalloc (0x100 * sizeof *font_info->glyphs); + if (CONSP (antialias)) + be_set_font_antialiasing (be_font, !NILP (XCDR (antialias))); + font->pixel_size = 0; font->driver = &haikufont_driver; font->encoding_charset = -1; @@ -1149,6 +1181,24 @@ haikufont_list_family (struct frame *f) return list; } +/* List of boolean properties in font names accepted by this font + driver. */ +static const char *const haikufont_booleans[] = + { + ":antialias", + NULL, + }; + +/* List of non-boolean properties. Currently empty. */ +static const char *const haikufont_non_booleans[1]; + +static void +haikufont_filter_properties (Lisp_Object font, Lisp_Object alist) +{ + font_filter_properties (font, alist, haikufont_booleans, + haikufont_non_booleans); +} + struct font_driver const haikufont_driver = { .type = LISPSYM_INITIALLY (Qhaiku), @@ -1163,7 +1213,8 @@ struct font_driver const haikufont_driver = .encode_char = haikufont_encode_char, .text_extents = haikufont_text_extents, .shape = haikufont_shape, - .list_family = haikufont_list_family + .list_family = haikufont_list_family, + .filter_properties = haikufont_filter_properties, }; static bool @@ -1189,6 +1240,7 @@ in the font selection dialog. */) int rc, size, initial_family, initial_style, initial_size; struct haiku_font_pattern pattern; Lisp_Object lfamily, lweight, lslant, lwidth, ladstyle, lsize; + bool disable_antialiasing, initial_antialias; f = decode_window_system_frame (frame); @@ -1198,6 +1250,7 @@ in the font selection dialog. */) initial_style = -1; initial_family = -1; initial_size = -1; + initial_antialias = true; font = FRAME_FONT (f); @@ -1211,6 +1264,11 @@ in the font selection dialog. */) haikufont_done_with_query_pattern (&pattern); initial_size = font->pixel_size; + + /* This field is safe to access even after + haikufont_done_with_query_pattern. */ + if (pattern.specified & FSPEC_ANTIALIAS) + initial_antialias = pattern.use_antialiasing; } popup_activated_p++; @@ -1220,7 +1278,8 @@ in the font selection dialog. */) &family, &style, &size, !NILP (exclude_proportional), initial_family, initial_style, - initial_size); + initial_size, initial_antialias, + &disable_antialiasing); request_sigio (); popup_activated_p--; @@ -1237,15 +1296,27 @@ in the font selection dialog. */) lwidth = (pattern.specified & FSPEC_WIDTH ? haikufont_width_to_lisp (pattern.width) : Qnil); ladstyle = (pattern.specified & FSPEC_STYLE - ? intern (pattern.style) : Qnil); + ? intern (pattern.style) : Qnil); lsize = (size >= 0 ? make_fixnum (size) : Qnil); + if (disable_antialiasing) + return CALLN (Ffont_spec, QCfamily, lfamily, + QCweight, lweight, QCslant, lslant, + QCwidth, lwidth, QCadstyle, ladstyle, + QCsize, lsize, QCantialias, Qnil); + return CALLN (Ffont_spec, QCfamily, lfamily, QCweight, lweight, QCslant, lslant, QCwidth, lwidth, QCadstyle, ladstyle, QCsize, lsize); } +static void +syms_of_haikufont_for_pdumper (void) +{ + register_font_driver (&haikufont_driver, NULL); +} + void syms_of_haikufont (void) { @@ -1270,9 +1341,12 @@ syms_of_haikufont (void) DEFSYM (Qko, "ko"); DEFSYM (Qjp, "jp"); + DEFSYM (QCindices, ":indices"); + #ifdef USE_BE_CAIRO Fput (Qhaiku, Qfont_driver_superseded_by, Qftcr); #endif + pdumper_do_now_and_after_load (syms_of_haikufont_for_pdumper); font_cache = list (Qnil); staticpro (&font_cache); diff --git a/src/haikumenu.c b/src/haikumenu.c index 5729bed4a9b..69bb56c124e 100644 --- a/src/haikumenu.c +++ b/src/haikumenu.c @@ -422,14 +422,21 @@ haiku_menu_show (struct frame *f, int x, int y, int menuflags, BView_convert_to_screen (view, &x, &y); unblock_input (); + unrequest_sigio (); popup_activated_p++; menu_item_selection = BMenu_run (menu, x, y, haiku_menu_show_help, block_input, unblock_input, haiku_process_pending_signals_for_menu, NULL); popup_activated_p--; + request_sigio (); FRAME_DISPLAY_INFO (f)->grabbed = 0; + /* Clear the grab view manually. There is a race condition here if + the window thread receives a button press between here and the + end of BMenu_run. */ + be_clear_grab_view (); + if (menu_item_selection) { prefix = entry = Qnil; @@ -627,8 +634,6 @@ set_frame_menubar (struct frame *f, bool deep_p) /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); diff --git a/src/haikuselect.c b/src/haikuselect.c index 8a7b6f2e0b1..7eb93a2754d 100644 --- a/src/haikuselect.c +++ b/src/haikuselect.c @@ -24,6 +24,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "haikuselect.h" #include "haikuterm.h" #include "haiku_support.h" +#include "keyboard.h" #include <stdlib.h> @@ -36,6 +37,10 @@ struct frame *haiku_dnd_frame; /* Whether or not to move the tip frame during drag-and-drop. */ bool haiku_dnd_follow_tooltip; +/* Whether or not the current DND frame is able to receive drops from + the current drag-and-drop operation. */ +bool haiku_dnd_allow_same_frame; + static void haiku_lisp_to_message (Lisp_Object, void *); static enum haiku_clipboard @@ -53,6 +58,23 @@ haiku_get_clipboard_name (Lisp_Object clipboard) signal_error ("Invalid clipboard", clipboard); } +DEFUN ("haiku-selection-timestamp", Fhaiku_selection_timestamp, + Shaiku_selection_timestamp, 1, 1, 0, + doc: /* Retrieve the "timestamp" of the clipboard CLIPBOARD. +CLIPBOARD can either be the symbol `PRIMARY', `SECONDARY' or +`CLIPBOARD'. The timestamp is returned as a number describing the +number of times programs have put data into CLIPBOARD. */) + (Lisp_Object clipboard) +{ + enum haiku_clipboard clipboard_name; + int64 timestamp; + + clipboard_name = haiku_get_clipboard_name (clipboard); + timestamp = be_get_clipboard_count (clipboard_name); + + return INT_TO_INTEGER (timestamp); +} + DEFUN ("haiku-selection-data", Fhaiku_selection_data, Shaiku_selection_data, 2, 2, 0, doc: /* Retrieve content typed as NAME from the clipboard @@ -441,10 +463,10 @@ haiku_lisp_to_message (Lisp_Object obj, void *message) int rc; specpdl_ref ref; - CHECK_LIST (obj); - for (tem = obj; CONSP (tem); tem = XCDR (tem)) + tem = obj; + + FOR_EACH_TAIL (tem) { - maybe_quit (); t1 = XCAR (tem); CHECK_CONS (t1); @@ -490,9 +512,9 @@ haiku_lisp_to_message (Lisp_Object obj, void *message) signal_error ("Unknown data type", type_sym); CHECK_LIST (t1); - for (t2 = XCDR (t1); CONSP (t2); t2 = XCDR (t2)) + t2 = XCDR (t1); + FOR_EACH_TAIL (t2) { - maybe_quit (); data = XCAR (t2); if (FIXNUMP (type_sym) || BIGNUMP (type_sym)) @@ -515,7 +537,7 @@ haiku_lisp_to_message (Lisp_Object obj, void *message) unblock_input (); if (rc) - signal_error ("Invalid message", msg_data); + signal_error ("Invalid message", data); unbind_to (ref, Qnil); break; @@ -812,6 +834,8 @@ currently being displayed to move along with the mouse pointer. */) haiku_dnd_frame = f; haiku_dnd_follow_tooltip = !NILP (follow_tooltip); + haiku_dnd_allow_same_frame = !NILP (allow_same_frame); + be_message = be_create_simple_message (); record_unwind_protect_ptr (haiku_unwind_drag_message, be_message); @@ -1012,29 +1036,138 @@ haiku_note_drag_motion (void) internal_catch_all (haiku_note_drag_motion_1, NULL, haiku_note_drag_motion_2); + + /* Redisplay this way to preserve the echo area. Otherwise, the + contents will abruptly disappear when the mouse moves over a + frame. */ + redisplay_preserve_echo_area (34); +} + +void +haiku_note_drag_wheel (struct input_event *ie) +{ + bool horizontal, up; + + up = false; + horizontal = false; + + if (ie->modifiers & up_modifier) + up = true; + + if (ie->kind == HORIZ_WHEEL_EVENT) + horizontal = true; + + ie->kind = NO_EVENT; + + if (!NILP (Vhaiku_drag_wheel_function) + && (haiku_dnd_allow_same_frame + || XFRAME (ie->frame_or_window) != haiku_dnd_frame)) + safe_call (7, Vhaiku_drag_wheel_function, ie->frame_or_window, + ie->x, ie->y, horizontal ? Qt : Qnil, up ? Qt : Qnil, + make_int (ie->modifiers)); + + redisplay_preserve_echo_area (35); +} + +void +init_haiku_select (void) +{ + be_clipboard_init (); +} + +void +haiku_handle_selection_clear (struct input_event *ie) +{ + enum haiku_clipboard id; + + id = haiku_get_clipboard_name (ie->arg); + + if (be_selection_outdated_p (id, ie->timestamp)) + return; + + CALLN (Frun_hook_with_args, + Qhaiku_lost_selection_functions, ie->arg); + + /* This is required for redisplay to happen if something changed the + display inside the selection loss functions. */ + redisplay_preserve_echo_area (20); +} + +void +haiku_selection_disowned (enum haiku_clipboard id, int64 count) +{ + struct input_event ie; + + EVENT_INIT (ie); + ie.kind = SELECTION_CLEAR_EVENT; + + switch (id) + { + case CLIPBOARD_CLIPBOARD: + ie.arg = QCLIPBOARD; + break; + + case CLIPBOARD_PRIMARY: + ie.arg = QPRIMARY; + break; + + case CLIPBOARD_SECONDARY: + ie.arg = QSECONDARY; + break; + } + + ie.timestamp = count; + kbd_buffer_store_event (&ie); +} + +void +haiku_start_watching_selections (void) +{ + be_start_watching_selection (CLIPBOARD_CLIPBOARD); + be_start_watching_selection (CLIPBOARD_PRIMARY); + be_start_watching_selection (CLIPBOARD_SECONDARY); } void syms_of_haikuselect (void) { DEFVAR_BOOL ("haiku-signal-invalid-refs", haiku_signal_invalid_refs, - doc: /* If nil, silently ignore invalid file names in system messages. + doc: /* If nil, silently ignore invalid file names in system messages. Otherwise, an error will be signalled if adding a file reference to a system message failed. */); haiku_signal_invalid_refs = true; DEFVAR_LISP ("haiku-drag-track-function", Vhaiku_drag_track_function, - doc: /* If non-nil, a function to call upon mouse movement while dragging a message. + doc: /* If non-nil, a function to call upon mouse movement while dragging a message. The function is called without any arguments. `mouse-position' can be used to retrieve the current position of the mouse. */); Vhaiku_drag_track_function = Qnil; + DEFVAR_LISP ("haiku-lost-selection-functions", Vhaiku_lost_selection_functions, + doc: /* A list of functions to be called when Emacs loses an X selection. +These are only called if a connection to the Haiku display was opened. */); + Vhaiku_lost_selection_functions = Qnil; + + DEFVAR_LISP ("haiku-drag-wheel-function", Vhaiku_drag_wheel_function, + doc: /* Function called upon wheel movement while dragging a message. +If non-nil, it is called with 6 arguments when the mouse wheel moves +while a drag-and-drop operation is in progress: the frame where the +mouse moved, the frame-relative X and Y positions where the mouse +moved, whether or not the wheel movement was horizontal, whether or +not the wheel moved up (or left, if the movement was horizontal), and +keyboard modifiers currently held down. */); + Vhaiku_drag_wheel_function = Qnil; + DEFSYM (QSECONDARY, "SECONDARY"); DEFSYM (QCLIPBOARD, "CLIPBOARD"); DEFSYM (QSTRING, "STRING"); DEFSYM (QUTF8_STRING, "UTF8_STRING"); DEFSYM (Qforeign_selection, "foreign-selection"); DEFSYM (QTARGETS, "TARGETS"); + + DEFSYM (Qhaiku_lost_selection_functions, + "haiku-lost-selection-functions"); + DEFSYM (Qmessage, "message"); DEFSYM (Qstring, "string"); DEFSYM (Qref, "ref"); @@ -1053,6 +1186,7 @@ used to retrieve the current position of the mouse. */); DEFSYM (Qalready_running, "already-running"); defsubr (&Shaiku_selection_data); + defsubr (&Shaiku_selection_timestamp); defsubr (&Shaiku_selection_put); defsubr (&Shaiku_selection_owner_p); defsubr (&Shaiku_drag_message); diff --git a/src/haikuselect.h b/src/haikuselect.h index e9a2f2dd77d..42e9c93f7e9 100644 --- a/src/haikuselect.h +++ b/src/haikuselect.h @@ -37,15 +37,15 @@ enum haiku_clipboard #ifdef __cplusplus extern "C" { -/* Also declared in haikuterm.h for use in emacs.c. */ -extern void init_haiku_select (void); #endif -/* Whether or not the selection was recently changed. */ +/* Defined in haikuselect.c. */ +extern void haiku_selection_disowned (enum haiku_clipboard, int64); +/* Defined in haiku_select.cc. */ +extern void be_clipboard_init (void); extern char *be_find_clipboard_data (enum haiku_clipboard, const char *, ssize_t *); extern void be_set_clipboard_data (enum haiku_clipboard, const char *, const char *, ssize_t, bool); -extern void be_get_clipboard_targets (enum haiku_clipboard, char **, int); extern bool be_clipboard_owner_p (enum haiku_clipboard); extern void be_update_clipboard_count (enum haiku_clipboard); @@ -64,6 +64,11 @@ extern int be_add_point_data (void *, const char *, float, float); extern int be_add_message_message (void *, const char *, void *); extern int be_lock_clipboard_message (enum haiku_clipboard, void **, bool); extern void be_unlock_clipboard (enum haiku_clipboard, bool); +extern void be_handle_clipboard_changed_message (void); +extern void be_start_watching_selection (enum haiku_clipboard); +extern bool be_selection_outdated_p (enum haiku_clipboard, int64); +extern int64 be_get_clipboard_count (enum haiku_clipboard); + #ifdef __cplusplus }; #endif diff --git a/src/haikuterm.c b/src/haikuterm.c index 365b23cd92c..df1c39974f8 100644 --- a/src/haikuterm.c +++ b/src/haikuterm.c @@ -32,6 +32,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "haiku_support.h" #include "thread.h" #include "window.h" +#include "haikuselect.h" #include <math.h> #include <stdlib.h> @@ -162,15 +163,15 @@ haiku_clip_to_string (struct glyph_string *s) /* If n[FOO].width is 0, it means to not draw at all, so set the clipping to some impossible value. */ if (r[0].width <= 0) - BView_ClipToRect (FRAME_HAIKU_VIEW (s->f), + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (s->f), FRAME_PIXEL_WIDTH (s->f), FRAME_PIXEL_HEIGHT (s->f), 10, 10); else { - BView_ClipToRect (FRAME_HAIKU_VIEW (s->f), r[0].x, + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (s->f), r[0].x, r[0].y, r[0].width, r[0].height); - BView_invalidate_region (FRAME_HAIKU_VIEW (s->f), r[0].x, + BView_invalidate_region (FRAME_HAIKU_DRAWABLE (s->f), r[0].x, r[0].y, r[0].width, r[0].height); } } @@ -180,15 +181,15 @@ haiku_clip_to_string (struct glyph_string *s) /* If n[FOO].width is 0, it means to not draw at all, so set the clipping to some impossible value. */ if (r[1].width <= 0) - BView_ClipToRect (FRAME_HAIKU_VIEW (s->f), + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (s->f), FRAME_PIXEL_WIDTH (s->f), FRAME_PIXEL_HEIGHT (s->f), 10, 10); else { - BView_ClipToRect (FRAME_HAIKU_VIEW (s->f), r[1].x, r[1].y, + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (s->f), r[1].x, r[1].y, r[1].width, r[1].height); - BView_invalidate_region (FRAME_HAIKU_VIEW (s->f), r[1].x, + BView_invalidate_region (FRAME_HAIKU_DRAWABLE (s->f), r[1].x, r[1].y, r[1].width, r[1].height); } } @@ -197,9 +198,9 @@ haiku_clip_to_string (struct glyph_string *s) static void haiku_clip_to_string_exactly (struct glyph_string *s, struct glyph_string *dst) { - BView_ClipToRect (FRAME_HAIKU_VIEW (s->f), s->x, s->y, + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (s->f), s->x, s->y, s->width, s->height); - BView_invalidate_region (FRAME_HAIKU_VIEW (s->f), s->x, + BView_invalidate_region (FRAME_HAIKU_DRAWABLE (s->f), s->x, s->y, s->width, s->height); } @@ -245,7 +246,7 @@ static void haiku_clear_frame_area (struct frame *f, int x, int y, int width, int height) { - void *vw = FRAME_HAIKU_VIEW (f); + void *vw = FRAME_HAIKU_DRAWABLE (f); block_input (); BView_draw_lock (vw, true, x, y, width, height); BView_StartClip (vw); @@ -260,7 +261,7 @@ haiku_clear_frame_area (struct frame *f, int x, int y, static void haiku_clear_frame (struct frame *f) { - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f))); @@ -286,11 +287,16 @@ haiku_clear_frame (struct frame *f) static Lisp_Object haiku_new_font (struct frame *f, Lisp_Object font_object, int fontset) { - struct font *font = XFONT_OBJECT (font_object); + struct font *font; + int ascent, descent, unit; + + font = XFONT_OBJECT (font_object); + if (fontset < 0) fontset = fontset_from_font (font_object); FRAME_FONTSET (f) = fontset; + if (FRAME_FONT (f) == font) return font_object; @@ -298,12 +304,11 @@ haiku_new_font (struct frame *f, Lisp_Object font_object, int fontset) FRAME_BASELINE_OFFSET (f) = font->baseline_offset; FRAME_COLUMN_WIDTH (f) = font->average_width; - int ascent, descent; get_font_ascent_descent (font, &ascent, &descent); FRAME_LINE_HEIGHT (f) = ascent + descent; FRAME_TAB_BAR_HEIGHT (f) = FRAME_TAB_BAR_LINES (f) * FRAME_LINE_HEIGHT (f); - int unit = FRAME_COLUMN_WIDTH (f); + unit = FRAME_COLUMN_WIDTH (f); if (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0) FRAME_CONFIG_SCROLL_BAR_COLS (f) = (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) + unit - 1) / unit; @@ -311,13 +316,10 @@ haiku_new_font (struct frame *f, Lisp_Object font_object, int fontset) FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + unit - 1) / unit; if (FRAME_HAIKU_WINDOW (f) && !FRAME_TOOLTIP_P (f)) - { - adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f), - FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), - 3, false, Qfont); + adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f), + FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), + 3, false, Qfont); - haiku_clear_under_internal_border (f); - } return font_object; } @@ -594,7 +596,7 @@ haiku_draw_box_rect (struct glyph_string *s, int left_x, int top_y, int right_x, int bottom_y, int hwidth, int vwidth, bool left_p, bool right_p, struct haiku_rect *clip_rect) { - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); struct face *face = s->face; BView_SetHighColor (view, face->box_color); @@ -658,7 +660,7 @@ haiku_draw_relief_rect (struct glyph_string *s, int left_x, int top_y, uint32_t color_white, color_black; void *view; - view = FRAME_HAIKU_VIEW (s->f); + view = FRAME_HAIKU_DRAWABLE (s->f); haiku_calculate_relief_colors (s, &color_white, &color_black); BView_SetHighColor (view, raised_p ? color_white : color_black); @@ -767,7 +769,7 @@ haiku_draw_underwave (struct glyph_string *s, int width, int x) dy = wave_height - 1; y = s->ybase - wave_height + 3; xmax = x + width; - view = FRAME_HAIKU_VIEW (s->f); + view = FRAME_HAIKU_DRAWABLE (s->f); BView_StartClip (view); haiku_clip_to_string (s); @@ -809,7 +811,7 @@ haiku_draw_text_decoration (struct glyph_string *s, struct face *face, if (s->hl == DRAW_CURSOR) haiku_merge_cursor_foreground (s, &cursor_color, NULL); - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); if (face->underline) { @@ -1011,7 +1013,7 @@ static void haiku_draw_plain_background (struct glyph_string *s, struct face *face, int x, int y, int width, int height) { - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); unsigned long cursor_color; if (s->hl == DRAW_CURSOR) @@ -1073,7 +1075,7 @@ haiku_draw_stipple_background (struct glyph_string *s, struct face *face, unsigned long foreground, background; void *view; - view = FRAME_HAIKU_VIEW (s->f); + view = FRAME_HAIKU_DRAWABLE (s->f); rec = haiku_get_bitmap_rec (s->f, s->face->stipple); if (explicit_colors_p) @@ -1171,7 +1173,7 @@ haiku_draw_glyph_string_foreground (struct glyph_string *s) else x = s->x; - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); if (s->font_not_found_p) { @@ -1250,6 +1252,8 @@ haiku_draw_glyphless_glyph_string_foreground (struct glyph_string *s) ? CHAR_TABLE_REF (Vglyphless_char_display, glyph->u.glyphless.ch) : XCHAR_TABLE (Vglyphless_char_display)->extras[0]); + if (CONSP (acronym)) + acronym = XCAR (acronym); if (STRINGP (acronym)) str = SSDATA (acronym); } @@ -1287,9 +1291,9 @@ haiku_draw_glyphless_glyph_string_foreground (struct glyph_string *s) else color = s->face->foreground; - BView_SetHighColor (FRAME_HAIKU_VIEW (s->f), color); - BView_SetPenSize (FRAME_HAIKU_VIEW (s->f), 1); - BView_StrokeRectangle (FRAME_HAIKU_VIEW (s->f), + BView_SetHighColor (FRAME_HAIKU_DRAWABLE (s->f), color); + BView_SetPenSize (FRAME_HAIKU_DRAWABLE (s->f), 1); + BView_StrokeRectangle (FRAME_HAIKU_DRAWABLE (s->f), x, s->ybase - glyph->ascent, glyph->pixel_width, glyph->ascent + glyph->descent); @@ -1333,7 +1337,7 @@ haiku_draw_stretch_glyph_string (struct glyph_string *s) if (s->row->reversed_p) x -= width; - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); unsigned long cursor_color; haiku_merge_cursor_foreground (s, NULL, &cursor_color); @@ -1399,14 +1403,14 @@ haiku_draw_stretch_glyph_string (struct glyph_string *s) static void haiku_start_clip (struct glyph_string *s) { - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); BView_StartClip (view); } static void haiku_end_clip (struct glyph_string *s) { - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); BView_EndClip (view); } @@ -1426,7 +1430,7 @@ haiku_clip_to_row (struct window *w, struct glyph_row *row, width = window_width; height = row->visible_height; - BView_ClipToRect (FRAME_HAIKU_VIEW (f), x, y, width, height); + BView_ClipToRect (FRAME_HAIKU_DRAWABLE (f), x, y, width, height); } static void @@ -1446,7 +1450,7 @@ haiku_draw_composite_glyph_string_foreground (struct glyph_string *s) { int i, j, x; struct font *font = s->font; - void *view = FRAME_HAIKU_VIEW (s->f); + void *view = FRAME_HAIKU_DRAWABLE (s->f); struct face *face = s->face; /* If first glyph of S has a left box line, start drawing the text @@ -1629,6 +1633,14 @@ haiku_draw_image_relief (struct glyph_string *s) } static void +haiku_translate_transform (double (*transform)[3], double dx, + double dy) +{ + transform[0][2] += dx; + transform[1][2] += dy; +} + +static void haiku_draw_image_glyph_string (struct glyph_string *s) { struct face *face = s->face; @@ -1639,6 +1651,7 @@ haiku_draw_image_glyph_string (struct glyph_string *s) struct haiku_rect nr; Emacs_Rectangle cr, ir, r; unsigned long background; + double image_transform[3][3]; height = s->height; if (s->slice.y == 0) @@ -1659,10 +1672,9 @@ haiku_draw_image_glyph_string (struct glyph_string *s) if (s->slice.y == 0) y += box_line_vwidth; - view = FRAME_HAIKU_VIEW (s->f); + view = FRAME_HAIKU_DRAWABLE (s->f); bitmap = s->img->pixmap; - /* TODO: implement stipples for images with masks. */ s->stippled_p = face->stipple != 0; if (s->hl == DRAW_CURSOR) @@ -1670,8 +1682,8 @@ haiku_draw_image_glyph_string (struct glyph_string *s) else background = face->background; - BView_SetHighColor (view, background); - BView_FillRectangle (view, x, y, width, height); + haiku_draw_background_rect (s, face, x, y, + width, height); if (bitmap) { @@ -1700,34 +1712,66 @@ haiku_draw_image_glyph_string (struct glyph_string *s) if (gui_intersect_rectangles (&cr, &ir, &r)) { - if (s->img->have_be_transforms_p) + memcpy (&image_transform, &s->img->transform, + sizeof image_transform); + + if (s->slice.x != x || s->slice.y != y + || s->slice.width != s->img->width + || s->slice.height != s->img->height) { - bitmap = BBitmap_transform_bitmap (bitmap, - s->img->mask, - face->background, - s->img->be_rotate, - s->img->width, - s->img->height); - mask = NULL; + BView_StartClip (view); + BView_ClipToRect (view, r.x, r.y, r.width, r.height); } - BView_DrawBitmap (view, bitmap, - s->slice.x + r.x - x, - s->slice.y + r.y - y, - r.width, r.height, - r.x, r.y, r.width, r.height); - if (mask) + haiku_translate_transform (image_transform, + x - s->slice.x, + y - s->slice.y); + + be_apply_affine_transform (view, + image_transform[0][0], + image_transform[0][1], + image_transform[0][2], + image_transform[1][0], + image_transform[1][1], + image_transform[1][2]); + + if (!s->stippled_p || !mask) { - BView_DrawMask (mask, view, - s->slice.x + r.x - x, - s->slice.y + r.y - y, - r.width, r.height, - r.x, r.y, r.width, r.height, - face->background); + BView_DrawBitmap (view, bitmap, 0, 0, + s->img->original_width, + s->img->original_height, + 0, 0, + s->img->original_width, + s->img->original_height, + s->img->use_bilinear_filtering); + + if (mask) + be_draw_image_mask (mask, view, 0, 0, + s->img->original_width, + s->img->original_height, + 0, 0, + s->img->original_width, + s->img->original_height, + background); } - - if (s->img->have_be_transforms_p) - BBitmap_free (bitmap); + else + /* In order to make sure the stipple background remains + visible, use the mask for the alpha channel of BITMAP + and composite it onto the view instead. */ + be_draw_bitmap_with_mask (view, bitmap, mask, 0, 0, + s->img->original_width, + s->img->original_height, + 0, 0, + s->img->original_width, + s->img->original_height, + s->img->use_bilinear_filtering); + + if (s->slice.x != x || s->slice.y != y + || s->slice.width != s->img->width + || s->slice.height != s->img->height) + BView_EndClip (view); + + be_apply_affine_transform (view, 1, 0, 0, 0, 1, 0); } if (!s->img->mask) @@ -1761,7 +1805,7 @@ haiku_draw_image_glyph_string (struct glyph_string *s) static void haiku_draw_glyph_string (struct glyph_string *s) { - void *view = FRAME_HAIKU_VIEW (s->f);; + void *view = FRAME_HAIKU_DRAWABLE (s->f);; struct face *face = s->face; block_input (); @@ -1921,10 +1965,8 @@ haiku_draw_glyph_string (struct glyph_string *s) /* Set the stipple_p flag indicating whether or not a stipple was drawn in s->row. That is the case either when s is a stretch glyph string and s->face->stipple is not NULL, or when - s->face->stipple exists and s->hl is not DRAW_CURSOR, and s is - not an image. This is different from X. */ - if (s->first_glyph->type != IMAGE_GLYPH - && s->face->stipple + s->face->stipple exists and s->hl is not DRAW_CURSOR. */ + if (s->face->stipple && (s->first_glyph->type == STRETCH_GLYPH || s->hl != DRAW_CURSOR)) s->row->stipple_p = true; @@ -1961,7 +2003,7 @@ haiku_after_update_window_line (struct window *w, block_input (); if (face) { - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); BView_draw_lock (view, false, 0, 0, 0, 0); BView_StartClip (view); BView_SetHighColor (view, (face->background_defaulted_p @@ -1970,7 +2012,7 @@ haiku_after_update_window_line (struct window *w, BView_FillRectangle (view, 0, y, width, height); BView_FillRectangle (view, FRAME_PIXEL_WIDTH (f) - width, y, width, height); - BView_invalidate_region (FRAME_HAIKU_VIEW (f), + BView_invalidate_region (FRAME_HAIKU_DRAWABLE (f), 0, y, width, height); BView_invalidate_region (view, FRAME_PIXEL_WIDTH (f) - width, y, width, height); @@ -2035,7 +2077,7 @@ haiku_draw_hollow_cursor (struct window *w, struct glyph_row *row) void *view; f = XFRAME (WINDOW_FRAME (w)); - view = FRAME_HAIKU_VIEW (f); + view = FRAME_HAIKU_DRAWABLE (f); /* Get the glyph the cursor is on. If we can't tell because the current matrix is invalid or such, give up. */ @@ -2108,7 +2150,7 @@ haiku_draw_bar_cursor (struct window *w, struct glyph_row *row, } else { - view = FRAME_HAIKU_VIEW (f); + view = FRAME_HAIKU_DRAWABLE (f); face = FACE_FROM_ID (f, cursor_glyph->face_id); /* If the glyph's background equals the color we normally draw @@ -2294,7 +2336,7 @@ haiku_draw_vertical_window_border (struct window *w, struct face *face; face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID); - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); BView_draw_lock (view, true, x, y_0, 1, y_1); BView_StartClip (view); if (face) @@ -2344,7 +2386,7 @@ haiku_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) unsigned long color_last = (face_last ? face_last->foreground : FRAME_FOREGROUND_PIXEL (f)); - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); BView_draw_lock (view, true, x0, y0, x1 - x0 + 1, y1 - y0 + 1); BView_StartClip (view); @@ -2514,7 +2556,7 @@ haiku_scroll_bar_create (struct window *w, int left, int top, void *view; f = XFRAME (WINDOW_FRAME (w)); - view = FRAME_HAIKU_VIEW (f); + view = FRAME_HAIKU_DRAWABLE (f); block_input (); bar = ALLOCATE_PSEUDOVECTOR (struct scroll_bar, prev, PVEC_OTHER); @@ -2564,7 +2606,7 @@ haiku_set_horizontal_scroll_bar (struct window *w, int portion, int whole, int p width = window_width; top = WINDOW_SCROLL_BAR_AREA_Y (w); height = WINDOW_CONFIG_SCROLL_BAR_HEIGHT (w); - view = FRAME_HAIKU_VIEW (WINDOW_XFRAME (w)); + view = FRAME_HAIKU_DRAWABLE (WINDOW_XFRAME (w)); block_input (); @@ -2623,7 +2665,7 @@ haiku_set_vertical_scroll_bar (struct window *w, int portion, int whole, int pos left = WINDOW_SCROLL_BAR_AREA_X (w); width = WINDOW_SCROLL_BAR_AREA_WIDTH (w); - view = FRAME_HAIKU_VIEW (WINDOW_XFRAME (w)); + view = FRAME_HAIKU_DRAWABLE (WINDOW_XFRAME (w)); block_input (); if (NILP (w->vertical_scroll_bar)) @@ -2672,7 +2714,7 @@ haiku_draw_fringe_bitmap (struct window *w, struct glyph_row *row, uint32 col; f = XFRAME (WINDOW_FRAME (w)); - view = FRAME_HAIKU_VIEW (f); + view = FRAME_HAIKU_DRAWABLE (f); face = p->face; block_input (); @@ -2788,7 +2830,7 @@ static void haiku_scroll_run (struct window *w, struct run *run) { struct frame *f = XFRAME (w->frame); - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); int x, y, width, height, from_y, to_y, bottom_y; window_box (w, ANY_AREA, &x, &y, &width, &height); @@ -3163,12 +3205,17 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) FRAME_PIXEL_HEIGHT (f) = height; haiku_clear_under_internal_border (f); + + /* Flush the frame and flip buffers here. It is + necessary for tooltips displayed inside menus, as + redisplay cannot happen. */ + haiku_flush (f); continue; } - BView_draw_lock (FRAME_HAIKU_VIEW (f), false, 0, 0, 0, 0); - BView_resize_to (FRAME_HAIKU_VIEW (f), width, height); - BView_draw_unlock (FRAME_HAIKU_VIEW (f)); + BView_draw_lock (FRAME_HAIKU_DRAWABLE (f), false, 0, 0, 0, 0); + BView_resize_to (FRAME_HAIKU_DRAWABLE (f), width, height); + BView_draw_unlock (FRAME_HAIKU_DRAWABLE (f)); if (width != FRAME_PIXEL_WIDTH (f) || height != FRAME_PIXEL_HEIGHT (f) @@ -3320,6 +3367,7 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) if (b->just_exited_p) { Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (f); + if (f == hlinfo->mouse_face_mouse_frame) { /* If we move outside the frame, then we're @@ -3330,6 +3378,9 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) haiku_flush_dirty_back_buffer_on (f); } + if (f == x_display_list->last_mouse_glyph_frame) + x_display_list->last_mouse_glyph_frame = NULL; + if (f->auto_lower && !popup_activated_p /* Don't do this if the mouse entered a scroll bar. */ && !BView_inside_scroll_bar (FRAME_HAIKU_VIEW (f), @@ -3822,6 +3873,9 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) : down_modifier); py = 0.0f; px = 0.0f; + + if (be_drag_and_drop_in_progress ()) + haiku_note_drag_wheel (&inev); } break; @@ -3966,6 +4020,9 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) inev.timestamp = b->when / 1000; break; } + case CLIPBOARD_CHANGED_EVENT: + be_handle_clipboard_changed_message (); + break; case APP_QUIT_REQUESTED_EVENT: inev.kind = SAVE_SESSION_EVENT; inev.arg = Qt; @@ -4071,7 +4128,7 @@ haiku_flash (struct frame *f) int flash_left = FRAME_INTERNAL_BORDER_WIDTH (f); int flash_right = FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f); int width = flash_right - flash_left; - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); object_wait_info info; bigtime_t wakeup; @@ -4359,6 +4416,7 @@ haiku_term_init (void) else dpyinfo->default_name = build_string ("GNU Emacs"); + haiku_start_watching_selections (); unblock_input (); return dpyinfo; @@ -4398,7 +4456,8 @@ haiku_clear_under_internal_border (struct frame *f) ? lookup_basic_face (NULL, f, INTERNAL_BORDER_FACE_ID) : INTERNAL_BORDER_FACE_ID)); struct face *face = FACE_FROM_ID_OR_NULL (f, face_id); - void *view = FRAME_HAIKU_VIEW (f); + void *view = FRAME_HAIKU_DRAWABLE (f); + block_input (); BView_draw_lock (view, true, 0, 0, FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f)); @@ -4439,7 +4498,7 @@ haiku_scroll_bar_remove (struct scroll_bar *bar) struct frame *f; f = WINDOW_XFRAME (XWINDOW (bar->window)); - view = FRAME_HAIKU_VIEW (f); + view = FRAME_HAIKU_DRAWABLE (f); block_input (); BView_forget_scroll_bar (view, bar->left, bar->top, diff --git a/src/haikuterm.h b/src/haikuterm.h index ea20289b5d1..b603c0a482f 100644 --- a/src/haikuterm.h +++ b/src/haikuterm.h @@ -275,7 +275,8 @@ struct scroll_bar #define MAKE_FRAME_DIRTY(f) (FRAME_DIRTY_P (f) = 1) #define FRAME_OUTPUT_DATA(f) ((f)->output_data.haiku) #define FRAME_HAIKU_WINDOW(f) (FRAME_OUTPUT_DATA (f)->window) -#define FRAME_HAIKU_VIEW(f) ((MAKE_FRAME_DIRTY (f)), FRAME_OUTPUT_DATA (f)->view) +#define FRAME_HAIKU_VIEW(f) (FRAME_OUTPUT_DATA (f)->view) +#define FRAME_HAIKU_DRAWABLE(f) ((MAKE_FRAME_DIRTY (f)), FRAME_HAIKU_VIEW (f)) #define FRAME_HAIKU_MENU_BAR(f) (FRAME_OUTPUT_DATA (f)->menubar) #define FRAME_DISPLAY_INFO(f) (FRAME_OUTPUT_DATA (f)->display_info) #define FRAME_FONT(f) (FRAME_OUTPUT_DATA (f)->font) @@ -287,7 +288,7 @@ struct scroll_bar #ifdef USE_BE_CAIRO #define FRAME_CR_CONTEXT(f) \ (FRAME_HAIKU_VIEW (f) \ - ? EmacsView_cairo_context (FRAME_HAIKU_VIEW (f)) \ + ? EmacsView_cairo_context (FRAME_HAIKU_DRAWABLE (f)) \ : NULL) #endif @@ -333,6 +334,7 @@ extern Lisp_Object haiku_popup_dialog (struct frame *, Lisp_Object, Lisp_Object) extern void haiku_activate_menubar (struct frame *); extern void haiku_wait_for_event (struct frame *, int); extern void haiku_note_drag_motion (void); +extern void haiku_note_drag_wheel (struct input_event *); extern void initialize_frame_menubar (struct frame *); @@ -357,4 +359,6 @@ extern void haiku_end_cr_clip (cairo_t *); extern void haiku_merge_cursor_foreground (struct glyph_string *, unsigned long *, unsigned long *); +extern void haiku_handle_selection_clear (struct input_event *); +extern void haiku_start_watching_selections (void); #endif /* _HAIKU_TERM_H_ */ diff --git a/src/image.c b/src/image.c index 058c1755704..f5004c2c4c7 100644 --- a/src/image.c +++ b/src/image.c @@ -186,6 +186,10 @@ static void free_color_table (void); static unsigned long *colors_in_color_table (int *n); #endif +#if defined (HAVE_WEBP) || defined (HAVE_GIF) +static void anim_prune_animation_cache (Lisp_Object); +#endif + #ifdef USE_CAIRO static Emacs_Pix_Container @@ -1071,9 +1075,16 @@ struct image_type libraries on Windows), or NULL if none. */ bool (*init) (void); /* An initializer for the init field. */ -# define IMAGE_TYPE_INIT(f) f -#else -# define IMAGE_TYPE_INIT(f) +#endif +#if defined HAVE_RSVG || defined HAVE_PNG || defined HAVE_GIF || \ + defined HAVE_TIFF || defined HAVE_JPEG || defined HAVE_XPM || \ + defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK || \ + defined HAVE_WEBP +# ifdef WINDOWSNT +# define IMAGE_TYPE_INIT(f) f +# else +# define IMAGE_TYPE_INIT(f) +# endif #endif }; @@ -2120,19 +2131,35 @@ clear_image_caches (Lisp_Object filter) } DEFUN ("clear-image-cache", Fclear_image_cache, Sclear_image_cache, - 0, 1, 0, + 0, 2, 0, doc: /* Clear the image cache. FILTER nil or a frame means clear all images in the selected frame. FILTER t means clear the image caches of all frames. Anything else means clear only those images that refer to FILTER, -which is then usually a filename. */) - (Lisp_Object filter) +which is then usually a filename. + +This function also clears the image animation cache. If +ANIMATION-CACHE is non-nil, only the image spec `eq' with +ANIMATION-CACHE is removed, and other image cache entries are not +evicted. */) + (Lisp_Object filter, Lisp_Object animation_cache) { + if (!NILP (animation_cache)) + { +#if defined (HAVE_WEBP) || defined (HAVE_GIF) + anim_prune_animation_cache (XCDR (animation_cache)); +#endif + return Qnil; + } + if (! (NILP (filter) || FRAMEP (filter))) clear_image_caches (filter); else clear_image_cache (decode_window_system_frame (filter), Qt); + /* Also clear the animation caches. */ + image_prune_animation_caches (true); + return Qnil; } @@ -2200,21 +2227,6 @@ image_frame_cache_size (struct frame *f) return total; } -DEFUN ("image-cache-size", Fimage_cache_size, Simage_cache_size, 0, 0, 0, - doc: /* Return the size of the image cache. */) - (void) -{ - Lisp_Object tail, frame; - size_t total = 0; - - FOR_EACH_FRAME (tail, frame) - if (FRAME_WINDOW_P (XFRAME (frame))) - total += image_frame_cache_size (XFRAME (frame)); - - return make_int (total); -} - - DEFUN ("image-flush", Fimage_flush, Simage_flush, 1, 2, 0, doc: /* Flush the image with specification SPEC on frame FRAME. @@ -2309,8 +2321,8 @@ postprocess_image (struct frame *f, struct image *img) tem = XCDR (conversion); if (CONSP (tem)) image_edge_detection (f, img, - Fplist_get (tem, QCmatrix), - Fplist_get (tem, QCcolor_adjustment)); + plist_get (tem, QCmatrix), + plist_get (tem, QCcolor_adjustment)); } } } @@ -2503,17 +2515,17 @@ compute_image_size (double width, double height, finally move the origin back to the top left of the image, which may now be a different corner. - Note that different GUI backends (X, Cairo, w32, NS) want the - transform matrix defined as transform from the original image to - the transformed image, while others want the matrix to describe the - transform of the space, which boils down to inverting the matrix. + Note that different GUI backends (X, Cairo, w32, NS, Haiku) want + the transform matrix defined as transform from the original image + to the transformed image, while others want the matrix to describe + the transform of the space, which boils down to inverting the + matrix. It's possible to pre-calculate the matrix multiplications and just generate one transform matrix that will do everything we need in a single step, but the maths for each element is much more complex and performing the steps separately makes for more readable code. */ -#ifndef HAVE_HAIKU typedef double matrix3x3[3][3]; static void @@ -2528,7 +2540,6 @@ matrix3x3_mult (matrix3x3 a, matrix3x3 b, matrix3x3 result) result[i][j] = sum; } } -#endif /* not HAVE_HAIKU */ static void compute_image_rotation (struct image *img, double *rotation) @@ -2553,6 +2564,22 @@ compute_image_rotation (struct image *img, double *rotation) static void image_set_transform (struct frame *f, struct image *img) { + bool flip; + +#if defined HAVE_HAIKU + matrix3x3 identity = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + + img->original_width = img->width; + img->original_height = img->height; + img->use_bilinear_filtering = false; + + memcpy (&img->transform, identity, sizeof identity); +#endif + # if (defined HAVE_IMAGEMAGICK \ && !defined DONT_CREATE_TRANSFORMED_IMAGEMAGICK_IMAGE) /* ImageMagick images already have the correct transform. */ @@ -2587,8 +2614,10 @@ image_set_transform (struct frame *f, struct image *img) double rotation = 0.0; compute_image_rotation (img, &rotation); -#ifndef HAVE_HAIKU -# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS + /* Determine flipping. */ + flip = !NILP (image_spec_value (img->spec, QCflip, NULL)); + +# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_HAIKU /* We want scale up operations to use a nearest neighbor filter to show real pixels instead of munging them, but scale down operations to use a blended filter, to avoid aliasing and the like. @@ -2602,6 +2631,10 @@ image_set_transform (struct frame *f, struct image *img) smoothing = !NILP (s); # endif +#ifdef HAVE_HAIKU + img->use_bilinear_filtering = smoothing; +#endif + /* Perform scale transformation. */ matrix3x3 matrix @@ -2611,7 +2644,7 @@ image_set_transform (struct frame *f, struct image *img) : img->width / (double) width), [1][1] = (!IEEE_FLOATING_POINT && height == 0 ? DBL_MAX : img->height / (double) height), -# elif defined HAVE_NTGUI || defined HAVE_NS +# elif defined HAVE_NTGUI || defined HAVE_NS || defined HAVE_HAIKU [0][0] = (!IEEE_FLOATING_POINT && img->width == 0 ? DBL_MAX : width / (double) img->width), [1][1] = (!IEEE_FLOATING_POINT && img->height == 0 ? DBL_MAX @@ -2626,26 +2659,65 @@ image_set_transform (struct frame *f, struct image *img) /* Perform rotation transformation. */ int rotate_flag = -1; - if (rotation == 0) + + /* Haiku needs this, since the transformation is done on the basis + of the view, and not the image. */ +#ifdef HAVE_HAIKU + int extra_tx, extra_ty; + + extra_tx = 0; + extra_ty = 0; +#endif + + if (rotation == 0 && !flip) rotate_flag = 0; else { # if (defined USE_CAIRO || defined HAVE_XRENDER \ - || defined HAVE_NTGUI || defined HAVE_NS) + || defined HAVE_NTGUI || defined HAVE_NS \ + || defined HAVE_HAIKU) int cos_r, sin_r; - if (rotation == 90) + if (rotation == 0) + { + /* FLIP is always true here. As this will rotate by 0 + degrees, it has no visible effect. Applying only + translation matrix to the image would be sufficient for + horizontal flipping, but writing special handling for + this case would increase code complexity somewhat. */ + cos_r = 1; + sin_r = 0; + rotate_flag = 1; + +#ifdef HAVE_HAIKU + extra_tx = width; + extra_ty = 0; +#endif + } + else if (rotation == 90) { width = img->height; height = img->width; cos_r = 0; sin_r = 1; rotate_flag = 1; + +#ifdef HAVE_HAIKU + if (!flip) + extra_ty = height; + extra_tx = 0; +#endif } else if (rotation == 180) { cos_r = -1; sin_r = 0; rotate_flag = 1; + +#ifdef HAVE_HAIKU + if (!flip) + extra_tx = width; + extra_ty = height; +#endif } else if (rotation == 270) { @@ -2654,6 +2726,13 @@ image_set_transform (struct frame *f, struct image *img) cos_r = 0; sin_r = -1; rotate_flag = 1; + +#ifdef HAVE_HAIKU + extra_tx = width; + + if (flip) + extra_ty = height; +#endif } if (0 < rotate_flag) @@ -2674,9 +2753,14 @@ image_set_transform (struct frame *f, struct image *img) matrix3x3 v; matrix3x3_mult (rot, u, v); - /* 3. Translate back. */ + /* 3. Translate back. Flip horizontally if requested. */ t[2][0] = width * -.5; t[2][1] = height * -.5; + if (flip) + { + t[0][0] = -t[0][0]; + t[2][0] = -t[2][0]; + } matrix3x3_mult (t, v, matrix); # else /* 1. Translate so (0, 0) is in the center of the image. */ @@ -2694,9 +2778,10 @@ image_set_transform (struct frame *f, struct image *img) matrix3x3 v; matrix3x3_mult (u, rot, v); - /* 3. Translate back. */ + /* 3. Translate back. Flip horizontally if requested. */ t[2][0] = width * .5; t[2][1] = height * .5; + if (flip) t[0][0] = -t[0][0]; matrix3x3_mult (v, t, matrix); # endif img->width = width; @@ -2757,35 +2842,17 @@ image_set_transform (struct frame *f, struct image *img) img->xform.eM22 = matrix[1][1]; img->xform.eDx = matrix[2][0]; img->xform.eDy = matrix[2][1]; -# endif -#else - if (rotation != 0 && - rotation != 90 && - rotation != 180 && - rotation != 270 && - rotation != 360) - { - image_error ("No native support for rotation by %g degrees", - make_float (rotation)); - return; - } - - rotation = fmod (rotation, 360.0); +# elif defined HAVE_HAIKU + /* Store the transform in the struct image for later. */ + memcpy (&img->transform, &matrix, sizeof matrix); - if (rotation == 90 || rotation == 270) + /* Also add the extra translations. */ + if (rotate_flag) { - int w = width; - width = height; - height = w; + img->transform[0][2] = extra_tx; + img->transform[1][2] = extra_ty; } - - img->have_be_transforms_p = rotation != 0 || (img->width != width) || (img->height != height); - img->be_rotate = rotation; - img->be_scale_x = 1.0 / (img->width / (double) width); - img->be_scale_y = 1.0 / (img->height / (double) height); - img->width = width; - img->height = height; -#endif /* not HAVE_HAIKU */ +#endif } #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */ @@ -2972,6 +3039,11 @@ struct anim_cache /* A function to call to free the handle. */ void (*destructor) (void *); int index, width, height, frames; + /* This is used to be able to say something about the cache size. + We don't actually know how much memory the different libraries + actually use here (since these cache structures are opaque), so + this is mostly just the size of the original image file. */ + int byte_size; struct timespec update_time; struct anim_cache *next; }; @@ -2988,12 +3060,16 @@ anim_create_cache (Lisp_Object spec) cache->index = -1; cache->next = NULL; cache->spec = spec; + cache->byte_size = 0; return cache; } -/* Discard cached images that haven't been used for a minute. */ +/* Discard cached images that haven't been used for a minute. If + CLEAR is t, remove all animation cache entries. If CLEAR is + anything other than nil or t, only remove the entries that have a + spec `eq' to CLEAR. */ static void -anim_prune_animation_cache (void) +anim_prune_animation_cache (Lisp_Object clear) { struct anim_cache **pcache = &anim_cache; struct timespec old = timespec_sub (current_timespec (), @@ -3002,9 +3078,9 @@ anim_prune_animation_cache (void) while (*pcache) { struct anim_cache *cache = *pcache; - if (timespec_cmp (old, cache->update_time) <= 0) - pcache = &cache->next; - else + if (EQ (clear, Qt) + || (EQ (clear, Qnil) && timespec_cmp (old, cache->update_time) > 0) + || EQ (clear, cache->spec)) { if (cache->handle) cache->destructor (cache); @@ -3013,6 +3089,8 @@ anim_prune_animation_cache (void) *pcache = cache->next; xfree (cache); } + else + pcache = &cache->next; } } @@ -3022,7 +3100,7 @@ anim_get_animation_cache (Lisp_Object spec) struct anim_cache *cache; struct anim_cache **pcache = &anim_cache; - anim_prune_animation_cache (); + anim_prune_animation_cache (Qnil); while (1) { @@ -3723,6 +3801,8 @@ enum xbm_keyword_index XBM_ALGORITHM, XBM_HEURISTIC_MASK, XBM_MASK, + XBM_DATA_WIDTH, + XBM_DATA_HEIGHT, XBM_LAST }; @@ -3744,7 +3824,9 @@ static const struct image_keyword xbm_format[XBM_LAST] = {":relief", IMAGE_INTEGER_VALUE, 0}, {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, - {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0} + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":data-width", IMAGE_POSITIVE_INTEGER_VALUE, 0}, + {":data-height", IMAGE_POSITIVE_INTEGER_VALUE, 0} }; /* Tokens returned from xbm_scan. */ @@ -3766,8 +3848,8 @@ enum xbm_token an entry `:file FILENAME' where FILENAME is a string. If the specification is for a bitmap loaded from memory it must - contain `:width WIDTH', `:height HEIGHT', and `:data DATA', where - WIDTH and HEIGHT are integers > 0. DATA may be: + contain `:data-width WIDTH', `:data-height HEIGHT', and `:data DATA', + where WIDTH and HEIGHT are integers > 0. DATA may be: 1. a string large enough to hold the bitmap data, i.e. it must have a size >= (WIDTH + 7) / 8 * HEIGHT @@ -3777,9 +3859,7 @@ enum xbm_token 3. a vector of strings or bool-vectors, one for each line of the bitmap. - 4. a string containing an in-memory XBM file. WIDTH and HEIGHT - may not be specified in this case because they are defined in the - XBM file. + 4. a string containing an in-memory XBM file. Both the file and data forms may contain the additional entries `:background COLOR' and `:foreground COLOR'. If not present, @@ -3799,13 +3879,13 @@ xbm_image_p (Lisp_Object object) if (kw[XBM_FILE].count) { - if (kw[XBM_WIDTH].count || kw[XBM_HEIGHT].count || kw[XBM_DATA].count) + if (kw[XBM_DATA].count) return 0; } else if (kw[XBM_DATA].count && xbm_file_p (kw[XBM_DATA].value)) { /* In-memory XBM file. */ - if (kw[XBM_WIDTH].count || kw[XBM_HEIGHT].count || kw[XBM_FILE].count) + if (kw[XBM_FILE].count) return 0; } else @@ -3814,14 +3894,14 @@ xbm_image_p (Lisp_Object object) int width, height, stride; /* Entries for `:width', `:height' and `:data' must be present. */ - if (!kw[XBM_WIDTH].count - || !kw[XBM_HEIGHT].count + if (!kw[XBM_DATA_WIDTH].count + || !kw[XBM_DATA_HEIGHT].count || !kw[XBM_DATA].count) return 0; data = kw[XBM_DATA].value; - width = XFIXNAT (kw[XBM_WIDTH].value); - height = XFIXNAT (kw[XBM_HEIGHT].value); + width = XFIXNAT (kw[XBM_DATA_WIDTH].value); + height = XFIXNAT (kw[XBM_DATA_HEIGHT].value); if (!kw[XBM_STRIDE].count) stride = width; @@ -4445,8 +4525,8 @@ xbm_load (struct frame *f, struct image *img) /* Get specified width, and height. */ if (!in_memory_file_p) { - img->width = XFIXNAT (fmt[XBM_WIDTH].value); - img->height = XFIXNAT (fmt[XBM_HEIGHT].value); + img->width = XFIXNAT (fmt[XBM_DATA_WIDTH].value); + img->height = XFIXNAT (fmt[XBM_DATA_HEIGHT].value); eassert (img->width > 0 && img->height > 0); if (!check_image_size (f, img->width, img->height)) { @@ -8954,13 +9034,14 @@ gif_load (struct frame *f, struct image *img) struct anim_cache* cache = NULL; /* Which sub-image are we to display? */ Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); + int byte_size = 0; idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0; if (!NILP (image_number)) { /* If this is an animated image, create a cache for it. */ - cache = anim_get_animation_cache (img->spec); + cache = anim_get_animation_cache (XCDR (img->spec)); /* We have an old cache entry, so use it. */ if (cache->handle) { @@ -9007,6 +9088,14 @@ gif_load (struct frame *f, struct image *img) image_error ("Cannot open `%s'", file); return false; } + + /* Get the file size so that we can report it in + `image-cache-size'. */ + struct stat st; + FILE *fp = fopen (SSDATA (encoded_file), "rb"); + if (fstat (fileno (fp), &st) == 0) + byte_size = st.st_size; + fclose (fp); } else { @@ -9021,6 +9110,7 @@ gif_load (struct frame *f, struct image *img) memsrc.bytes = SDATA (specified_data); memsrc.len = SBYTES (specified_data); memsrc.index = 0; + byte_size = memsrc.len; #if GIFLIB_MAJOR < 5 gif = DGifOpen (&memsrc, gif_read_from_memory); @@ -9115,6 +9205,7 @@ gif_load (struct frame *f, struct image *img) cache->destructor = (void (*)(void *)) &gif_destroy; cache->width = width; cache->height = height; + cache->byte_size = byte_size; } img->corners[TOP_CORNER] = gif->SavedImages[0].ImageDesc.Top; @@ -9652,7 +9743,7 @@ webp_load (struct frame *f, struct image *img) /* Animated image. */ int timestamp; - struct anim_cache* cache = anim_get_animation_cache (img->spec); + struct anim_cache* cache = anim_get_animation_cache (XCDR (img->spec)); /* Get the next frame from the animation cache. */ if (cache->handle && cache->index == idx - 1) { @@ -9686,6 +9777,9 @@ webp_load (struct frame *f, struct image *img) purge the anim cache. */ webp_data.size = size; + /* This is used just for reporting by `image-cache-size'. */ + cache->byte_size = size; + /* Get the width/height of the total image. */ WebPDemuxer* demux = WebPDemux (&webp_data); cache->width = width = WebPDemuxGetI (demux, WEBP_FF_CANVAS_WIDTH); @@ -10025,9 +10119,10 @@ imagemagick_create_cache (char *signature) return cache; } -/* Discard cached images that haven't been used for a minute. */ +/* Discard cached images that haven't been used for a minute. If + CLEAR, discard all cached animated images. */ static void -imagemagick_prune_animation_cache (void) +imagemagick_prune_animation_cache (bool clear) { struct animation_cache **pcache = &animation_cache; struct timespec old = timespec_sub (current_timespec (), @@ -10036,15 +10131,15 @@ imagemagick_prune_animation_cache (void) while (*pcache) { struct animation_cache *cache = *pcache; - if (timespec_cmp (old, cache->update_time) <= 0) - pcache = &cache->next; - else + if (clear || timespec_cmp (old, cache->update_time) > 0) { if (cache->wand) DestroyMagickWand (cache->wand); *pcache = cache->next; xfree (cache); } + else + pcache = &cache->next; } } @@ -10055,7 +10150,7 @@ imagemagick_get_animation_cache (MagickWand *wand) struct animation_cache *cache; struct animation_cache **pcache = &animation_cache; - imagemagick_prune_animation_cache (); + imagemagick_prune_animation_cache (false); while (1) { @@ -11785,6 +11880,30 @@ The list of capabilities can include one or more of the following: return Qnil; } +DEFUN ("image-cache-size", Fimage_cache_size, Simage_cache_size, 0, 0, 0, + doc: /* Return the size of the image cache. */) + (void) +{ + Lisp_Object tail, frame; + size_t total = 0; + + FOR_EACH_FRAME (tail, frame) + if (FRAME_WINDOW_P (XFRAME (frame))) + total += image_frame_cache_size (XFRAME (frame)); + +#if defined (HAVE_WEBP) || defined (HAVE_GIF) + struct anim_cache *pcache = anim_cache; + while (pcache) + { + total += pcache->byte_size; + pcache = pcache->next; + } +#endif + + return make_int (total); +} + + DEFUN ("init-image-library", Finit_image_library, Sinit_image_library, 1, 1, 0, doc: /* Initialize image library implementing image type TYPE. Return t if TYPE is a supported image type. @@ -11894,6 +12013,18 @@ lookup_image_type (Lisp_Object type) return NULL; } +/* Prune the animation caches. If CLEAR, remove all animation cache + entries. */ +void +image_prune_animation_caches (bool clear) +{ +#if defined (HAVE_WEBP) || defined (HAVE_GIF) + anim_prune_animation_cache (clear? Qt: Qnil); +#endif +#ifdef HAVE_IMAGEMAGICK + imagemagick_prune_animation_cache (clear); +#endif +} void syms_of_image (void) @@ -11938,6 +12069,7 @@ non-numeric, there is no explicit limit on the size of images. */); DEFSYM (QCtransform_smoothing, ":transform-smoothing"); DEFSYM (QCcolor_adjustment, ":color-adjustment"); DEFSYM (QCmask, ":mask"); + DEFSYM (QCflip, ":flip"); /* Other symbols. */ DEFSYM (Qlaplace, "laplace"); diff --git a/src/indent.c b/src/indent.c index 51f6f414de3..aa905f387bb 100644 --- a/src/indent.c +++ b/src/indent.c @@ -306,6 +306,8 @@ and point (e.g., control characters will have a width of 2 or 4, tabs will have a variable width). Ignores finite width of frame, which means that this function may return values greater than (frame-width). +In a buffer with very long lines, the value will be an approximation, +because calculating the exact number is very expensive. Whether the line is visible (if `selective-display' is t) has no effect; however, ^M is treated as end of line when `selective-display' is t. Text that has an invisible property is considered as having width 0, unless @@ -313,6 +315,7 @@ Text that has an invisible property is considered as having width 0, unless (void) { Lisp_Object temp; + XSETFASTINT (temp, current_column ()); return temp; } @@ -341,6 +344,14 @@ current_column (void) && MODIFF == last_known_column_modified) return last_known_column; + ptrdiff_t line_beg = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1, + NULL, NULL, 1); + + /* Avoid becoming abysmally slow for very long lines. */ + if (current_buffer->long_line_optimizations_p + && !NILP (Vlong_line_threshold) + && PT - line_beg > XFIXNUM (Vlong_line_threshold)) + return PT - line_beg; /* this is an approximation! */ /* If the buffer has overlays, text properties, or multibyte characters, use a more general algorithm. */ if (buffer_intervals (current_buffer) @@ -484,15 +495,15 @@ check_display_width (ptrdiff_t pos, ptrdiff_t col, ptrdiff_t *endpos) : MOST_POSITIVE_FIXNUM); plist = XCDR (val); - if ((prop = Fplist_get (plist, QCwidth), + if ((prop = plist_get (plist, QCwidth), RANGED_FIXNUMP (0, prop, INT_MAX)) - || (prop = Fplist_get (plist, QCrelative_width), + || (prop = plist_get (plist, QCrelative_width), RANGED_FIXNUMP (0, prop, INT_MAX))) width = XFIXNUM (prop); else if (FLOATP (prop) && 0 <= XFLOAT_DATA (prop) && XFLOAT_DATA (prop) <= INT_MAX) width = (int)(XFLOAT_DATA (prop) + 0.5); - else if ((prop = Fplist_get (plist, QCalign_to), + else if ((prop = plist_get (plist, QCalign_to), RANGED_FIXNUMP (col, prop, align_to_max))) width = XFIXNUM (prop) - col; else if (FLOATP (prop) && col <= XFLOAT_DATA (prop) @@ -514,7 +525,7 @@ check_display_width (ptrdiff_t pos, ptrdiff_t col, ptrdiff_t *endpos) /* For :relative-width, we need to multiply by the column width of the character at POS, if it is greater than 1. */ if (!NILP (plist) - && !NILP (Fplist_get (plist, QCrelative_width)) + && !NILP (plist_get (plist, QCrelative_width)) && !NILP (BVAR (current_buffer, enable_multibyte_characters))) { int b, wd; @@ -556,13 +567,56 @@ scan_for_column (ptrdiff_t *endpos, EMACS_INT *goalcol, ptrdiff_t scan, scan_byte, next_boundary, prev_pos, prev_bpos; scan = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1, NULL, &scan_byte, 1); - next_boundary = scan; - prev_pos = scan; - prev_bpos = scan_byte; window = Fget_buffer_window (Fcurrent_buffer (), Qnil); w = ! NILP (window) ? XWINDOW (window) : NULL; + if (current_buffer->long_line_optimizations_p) + { + bool lines_truncated = false; + + if (!NILP (BVAR (current_buffer, truncate_lines))) + lines_truncated = true; + else if (!NILP (Vtruncate_partial_width_windows) && w + && w->total_cols < FRAME_COLS (XFRAME (WINDOW_FRAME (w)))) + { + if (FIXNUMP (Vtruncate_partial_width_windows)) + lines_truncated = + w->total_cols < XFIXNAT (Vtruncate_partial_width_windows); + else + lines_truncated = true; + } + /* Special optimization for buffers with long and truncated + lines: assumes that each character is a single column. */ + if (lines_truncated) + { + ptrdiff_t bolpos = scan; + /* The newline which ends this line or ZV. */ + ptrdiff_t eolpos = + find_newline (PT, PT_BYTE, ZV, ZV_BYTE, 1, NULL, NULL, 1); + + scan = bolpos + goal; + if (scan > end) + scan = end; + if (scan > eolpos) + scan = (eolpos == ZV ? ZV : eolpos - 1); + col = scan - bolpos; + if (col > large_hscroll_threshold) + { + prev_col = col - 1; + prev_pos = scan - 1; + prev_bpos = CHAR_TO_BYTE (scan); + goto endloop; + } + /* Restore the values we've overwritten above. */ + scan = bolpos; + col = 0; + } + } + next_boundary = scan; + prev_pos = scan; + prev_bpos = scan_byte; + memset (&cmp_it, 0, sizeof cmp_it); cmp_it.id = -1; composition_compute_stop_pos (&cmp_it, scan, scan_byte, end, Qnil); @@ -877,8 +931,10 @@ The return value is the column where the insertion ends. */) DEFUN ("current-indentation", Fcurrent_indentation, Scurrent_indentation, 0, 0, 0, doc: /* Return the indentation of the current line. -This is the horizontal position of the character -following any initial whitespace. */) +This is the horizontal position of the character following any initial +whitespace. +Text that has an invisible property is considered as having width 0, unless +`buffer-invisibility-spec' specifies that it is replaced by an ellipsis. */) (void) { ptrdiff_t posbyte; @@ -996,6 +1052,9 @@ as displayed of the previous characters in the line. This function ignores line-continuation; there is no upper limit on the column number a character can have and horizontal scrolling has no effect. +Text that has an invisible property is considered as having width 0, +unless `buffer-invisibility-spec' specifies that it is replaced by +an ellipsis. If specified column is within a character, point goes after that character. If it's past end of line, point goes to end of line. @@ -1861,7 +1920,7 @@ vmotion (ptrdiff_t from, ptrdiff_t from_byte, /* If the window contains this buffer, use it for getting text properties. Otherwise use the current buffer as arg for doing that. */ - if (EQ (w->contents, Fcurrent_buffer ())) + if (BASE_EQ (w->contents, Fcurrent_buffer ())) text_prop_object = window; else text_prop_object = Fcurrent_buffer (); @@ -2177,6 +2236,8 @@ whether or not it is currently displayed in some window. */) line_number_display_width (w, &lnum_width, &lnum_pixel_width); SET_TEXT_POS (pt, PT, PT_BYTE); itdata = bidi_shelve_cache (); + record_unwind_protect_void (unwind_display_working_on_window); + display_working_on_window_p = true; start_display (&it, w, pt); it.lnum_width = lnum_width; first_x = it.first_visible_x; @@ -2333,7 +2394,15 @@ whether or not it is currently displayed in some window. */) last line that it occupies. */ if (it_start < ZV) { - while (IT_CHARPOS (it) <= it_start) + if ((it.bidi_it.scan_dir > 0) + ? IT_CHARPOS (it) < it_start + : IT_CHARPOS (it) > it_start) + { + it.vpos = 0; + it.current_y = 0; + move_it_by_lines (&it, 1); + } + while (IT_CHARPOS (it) == it_start) { it.vpos = 0; it.current_y = 0; diff --git a/src/inotify.c b/src/inotify.c index e92ad40abcc..16d20e7e925 100644 --- a/src/inotify.c +++ b/src/inotify.c @@ -217,7 +217,7 @@ add_watch (int wd, Lisp_Object filename, /* Assign a watch ID that is not already in use, by looking for a gap in the existing sorted list. */ for (; ! NILP (XCDR (tail)); tail = XCDR (tail), id++) - if (!EQ (XCAR (XCAR (XCDR (tail))), make_fixnum (id))) + if (!BASE_EQ (XCAR (XCAR (XCDR (tail))), make_fixnum (id))) break; if (MOST_POSITIVE_FIXNUM < id) emacs_abort (); diff --git a/src/insdel.c b/src/insdel.c index 4676330cb79..7cbc88e51d4 100644 --- a/src/insdel.c +++ b/src/insdel.c @@ -913,7 +913,7 @@ insert_1_both (const char *string, the insertion. This, together with recording the insertion, will add up to the right stuff in the undo list. */ record_insert (PT, nchars); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars); CHARS_MODIFF = MODIFF; memcpy (GPT_ADDR, string, nbytes); @@ -1047,7 +1047,7 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte, #endif record_insert (PT, nchars); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars); CHARS_MODIFF = MODIFF; GAP_SIZE -= outgoing_nbytes; @@ -1138,7 +1138,8 @@ insert_from_gap (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail) of this dance. */ invalidate_buffer_caches (current_buffer, GPT, GPT); record_insert (GPT, nchars); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars); + CHARS_MODIFF = MODIFF; insert_from_gap_1 (nchars, nbytes, text_at_gap_tail); @@ -1272,7 +1273,7 @@ insert_from_buffer_1 (struct buffer *buf, #endif record_insert (PT, nchars); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars); CHARS_MODIFF = MODIFF; GAP_SIZE -= outgoing_nbytes; @@ -1379,7 +1380,7 @@ adjust_after_replace (ptrdiff_t from, ptrdiff_t from_byte, if (len == 0) evaporate_overlays (from); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars_del + len); CHARS_MODIFF = MODIFF; } @@ -1581,7 +1582,7 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new, check_markers (); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars_del + inschars); CHARS_MODIFF = MODIFF; if (adjust_match_data) @@ -1719,7 +1720,7 @@ replace_range_2 (ptrdiff_t from, ptrdiff_t from_byte, check_markers (); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars_del + inschars); CHARS_MODIFF = MODIFF; } @@ -1894,7 +1895,7 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte, at the end of the text before the gap. */ adjust_markers_for_delete (from, from_byte, to, to_byte); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, nchars_del); CHARS_MODIFF = MODIFF; /* Relocate point as if it were a marker. */ @@ -1954,7 +1955,7 @@ modify_text (ptrdiff_t start, ptrdiff_t end) BUF_COMPUTE_UNCHANGED (current_buffer, start - 1, end); if (MODIFF <= SAVE_MODIFF) record_first_change (); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, end - start); CHARS_MODIFF = MODIFF; bset_point_before_scroll (current_buffer, Qnil); diff --git a/src/intervals.c b/src/intervals.c index 9e28637d6bc..85152c58a5d 100644 --- a/src/intervals.c +++ b/src/intervals.c @@ -1737,11 +1737,11 @@ lookup_char_property (Lisp_Object plist, Lisp_Object prop, bool textprop) { tail = XCDR (tail); for (; NILP (fallback) && CONSP (tail); tail = XCDR (tail)) - fallback = Fplist_get (plist, XCAR (tail)); + fallback = plist_get (plist, XCAR (tail)); } if (textprop && NILP (fallback) && CONSP (Vdefault_text_properties)) - fallback = Fplist_get (Vdefault_text_properties, prop); + fallback = plist_get (Vdefault_text_properties, prop); return fallback; } diff --git a/src/json.c b/src/json.c index 4b3fabb3eb3..cdcc11358e6 100644 --- a/src/json.c +++ b/src/json.c @@ -975,7 +975,7 @@ usage: (json-parse-string STRING &rest ARGS) */) json_error_t error; json_t *object - = json_loads (SSDATA (encoded), JSON_DECODE_ANY, &error); + = json_loads (SSDATA (encoded), JSON_DECODE_ANY | JSON_ALLOW_NUL, &error); if (object == NULL) json_parse_error (&error); @@ -1071,7 +1071,9 @@ usage: (json-parse-buffer &rest args) */) json_error_t error; json_t *object = json_load_callback (json_read_buffer_callback, &data, - JSON_DECODE_ANY | JSON_DISABLE_EOF_CHECK, + JSON_DECODE_ANY + | JSON_DISABLE_EOF_CHECK + | JSON_ALLOW_NUL, &error); if (object == NULL) diff --git a/src/keyboard.c b/src/keyboard.c index 55d710ed627..1d7125a0a3e 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1295,7 +1295,8 @@ command_loop_1 (void) /* Note that the value cell will never directly contain nil if the symbol is a local variable. */ if (!NILP (Vpost_command_hook) && !NILP (Vrun_hooks)) - safe_run_hooks (Qpost_command_hook); + safe_run_hooks_maybe_narrowed (Qpost_command_hook, + XWINDOW (selected_window)); /* If displaying a message, resize the echo area window to fit that message's size exactly. */ @@ -1305,9 +1306,6 @@ command_loop_1 (void) /* If there are warnings waiting, process them. */ if (!NILP (Vdelayed_warnings_list)) safe_run_hooks (Qdelayed_warnings_hook); - - if (!NILP (Vdeferred_action_list)) - safe_run_hooks (Qdeferred_action_function); } /* Do this after running Vpost_command_hook, for consistency. */ @@ -1333,6 +1331,7 @@ command_loop_1 (void) display_malloc_warning (); Vdeactivate_mark = Qnil; + backtrace_yet = false; /* Don't ignore mouse movements for more than a single command loop. (This flag is set in xdisp.c whenever the tool bar is @@ -1345,7 +1344,7 @@ command_loop_1 (void) if (minibuf_level && !NILP (echo_area_buffer[0]) - && EQ (minibuf_window, echo_area_window) + && BASE_EQ (minibuf_window, echo_area_window) && NUMBERP (Vminibuffer_message_timeout)) { /* Bind inhibit-quit to t so that C-g gets read in @@ -1374,12 +1373,6 @@ command_loop_1 (void) } } - /* If it has changed current-menubar from previous value, - really recompute the menubar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag) - && !NILP (Ffboundp (Qrecompute_lucid_menubar))) - call0 (Qrecompute_lucid_menubar); - Vthis_command = Qnil; Vreal_this_command = Qnil; Vthis_original_command = Qnil; @@ -1470,7 +1463,9 @@ command_loop_1 (void) } Vthis_command = cmd; Vreal_this_command = cmd; - safe_run_hooks (Qpre_command_hook); + + safe_run_hooks_maybe_narrowed (Qpre_command_hook, + XWINDOW (selected_window)); already_adjusted = 0; @@ -1501,7 +1496,14 @@ command_loop_1 (void) point_before_last_command_or_undo = PT; buffer_before_last_command_or_undo = current_buffer; + /* Restart our counting of redisplay ticks before + executing the command, so that we don't blame the new + command for the sins of the previous one. */ + update_redisplay_ticks (0, NULL); + display_working_on_window_p = false; + call1 (Qcommand_execute, Vthis_command); + display_working_on_window_p = false; #ifdef HAVE_WINDOW_SYSTEM /* Do not check display_hourglass_p here, because @@ -1515,7 +1517,8 @@ command_loop_1 (void) } kset_last_prefix_arg (current_kboard, Vcurrent_prefix_arg); - safe_run_hooks (Qpost_command_hook); + safe_run_hooks_maybe_narrowed (Qpost_command_hook, + XWINDOW (selected_window)); /* If displaying a message, resize the echo area window to fit that message's size exactly. Do this only if the echo area @@ -1530,8 +1533,6 @@ command_loop_1 (void) if (!NILP (Vdelayed_warnings_list)) safe_run_hooks (Qdelayed_warnings_hook); - safe_run_hooks (Qdeferred_action_function); - kset_last_command (current_kboard, Vthis_command); kset_real_last_command (current_kboard, Vreal_this_command); if (!CONSP (last_command_event)) @@ -1567,9 +1568,15 @@ command_loop_1 (void) call0 (Qdeactivate_mark); else { + Lisp_Object symval; /* Even if not deactivating the mark, set PRIMARY if `select-active-regions' is non-nil. */ - if (!NILP (Fwindow_system (Qnil)) + if ((!NILP (Fwindow_system (Qnil)) + || ((symval = + find_symbol_value (Qtty_select_active_regions), + (!EQ (symval, Qunbound) && !NILP (symval))) + && !NILP (Fterminal_parameter (Qnil, + Qxterm__set_selection)))) /* Even if mark_active is non-nil, the actual buffer marker may not have been set yet (Bug#7044). */ && XMARKER (BVAR (current_buffer, mark))->buffer @@ -1582,9 +1589,12 @@ command_loop_1 (void) { Lisp_Object txt = call1 (Vregion_extract_function, Qnil); + if (XFIXNUM (Flength (txt)) > 0) /* Don't set empty selections. */ call2 (Qgui_set_selection, QPRIMARY, txt); + + CALLN (Frun_hook_with_args, Qpost_select_region_hook, txt); } if (current_buffer != prev_buffer || MODIFF != prev_modiff) @@ -1822,8 +1832,16 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) static Lisp_Object safe_run_hooks_1 (ptrdiff_t nargs, Lisp_Object *args) { - eassert (nargs == 2); - return call0 (args[1]); + eassert (nargs >= 2 && nargs <= 4); + switch (nargs) + { + case 2: + return call0 (args[1]); + case 3: + return call1 (args[1], args[2]); + default: + return call2 (args[1], args[2], args[3]); + } } /* Subroutine for safe_run_hooks: handle an error by clearing out the function @@ -1832,7 +1850,7 @@ safe_run_hooks_1 (ptrdiff_t nargs, Lisp_Object *args) static Lisp_Object safe_run_hooks_error (Lisp_Object error, ptrdiff_t nargs, Lisp_Object *args) { - eassert (nargs == 2); + eassert (nargs >= 2 && nargs <= 4); AUTO_STRING (format, "Error in %s (%S): %S"); Lisp_Object hook = args[0]; Lisp_Object fun = args[1]; @@ -1868,11 +1886,27 @@ safe_run_hooks_error (Lisp_Object error, ptrdiff_t nargs, Lisp_Object *args) static Lisp_Object safe_run_hook_funcall (ptrdiff_t nargs, Lisp_Object *args) { - eassert (nargs == 2); + eassert (nargs >= 2 && nargs <= 4); /* Yes, run_hook_with_args works with args in the other order. */ - internal_condition_case_n (safe_run_hooks_1, - 2, ((Lisp_Object []) {args[1], args[0]}), - Qt, safe_run_hooks_error); + switch (nargs) + { + case 2: + internal_condition_case_n (safe_run_hooks_1, + 2, ((Lisp_Object []) {args[1], args[0]}), + Qt, safe_run_hooks_error); + break; + case 3: + internal_condition_case_n (safe_run_hooks_1, + 3, ((Lisp_Object []) {args[1], args[0], args[2]}), + Qt, safe_run_hooks_error); + break; + default: + internal_condition_case_n (safe_run_hooks_1, + 4, ((Lisp_Object []) + {args[1], args[0], args[2], args[3]}), + Qt, safe_run_hooks_error); + break; + } return Qnil; } @@ -1890,6 +1924,33 @@ safe_run_hooks (Lisp_Object hook) unbind_to (count, Qnil); } +void +safe_run_hooks_maybe_narrowed (Lisp_Object hook, struct window *w) +{ + specpdl_ref count = SPECPDL_INDEX (); + + specbind (Qinhibit_quit, Qt); + + if (current_buffer->long_line_optimizations_p) + narrow_to_region_internal (make_fixnum (get_narrowed_begv (w, PT)), + make_fixnum (get_narrowed_zv (w, PT)), + true); + + run_hook_with_args (2, ((Lisp_Object []) {hook, hook}), safe_run_hook_funcall); + unbind_to (count, Qnil); +} + +void +safe_run_hooks_2 (Lisp_Object hook, Lisp_Object arg1, Lisp_Object arg2) +{ + specpdl_ref count = SPECPDL_INDEX (); + + specbind (Qinhibit_quit, Qt); + run_hook_with_args (4, ((Lisp_Object []) {hook, hook, arg1, arg2}), + safe_run_hook_funcall); + unbind_to (count, Qnil); +} + /* Nonzero means polling for input is temporarily suppressed. */ @@ -2576,7 +2637,7 @@ read_char (int commandflag, Lisp_Object map, && (input_was_pending || !redisplay_dont_pause))) { input_was_pending = input_pending; - if (help_echo_showing_p && !EQ (selected_window, minibuf_window)) + if (help_echo_showing_p && !BASE_EQ (selected_window, minibuf_window)) redisplay_preserve_echo_area (5); else redisplay (); @@ -2924,7 +2985,7 @@ read_char (int commandflag, Lisp_Object map, goto exit; } - if (EQ (c, make_fixnum (-2))) + if (BASE_EQ (c, make_fixnum (-2))) return c; if (CONSP (c) && EQ (XCAR (c), Qt)) @@ -3249,7 +3310,7 @@ read_char (int commandflag, Lisp_Object map, unbind_to (count, Qnil); redisplay (); - if (EQ (c, make_fixnum (040))) + if (BASE_EQ (c, make_fixnum (040))) { cancel_echoing (); do @@ -3307,6 +3368,11 @@ help_char_p (Lisp_Object c) static void record_char (Lisp_Object c) { + /* subr.el/read-passwd binds inhibit_record_char to avoid recording + passwords. */ + if (!record_all_keys && inhibit_record_char) + return; + int recorded = 0; if (CONSP (c) && (EQ (XCAR (c), Qhelp_echo) || EQ (XCAR (c), Qmouse_movement))) @@ -3720,7 +3786,7 @@ Time_to_position (Time encoded_pos) { if (encoded_pos <= INPUT_EVENT_POS_MAX) return encoded_pos; - Time encoded_pos_min = INPUT_EVENT_POS_MIN; + Time encoded_pos_min = position_to_Time (INPUT_EVENT_POS_MIN); eassert (encoded_pos_min <= encoded_pos); ptrdiff_t notpos = -1 - encoded_pos; return -1 - notpos; @@ -3994,69 +4060,43 @@ kbd_buffer_get_event (KBOARD **kbp, We return nil for them. */ switch (event->kind) { +#ifndef HAVE_HAIKU case SELECTION_REQUEST_EVENT: case SELECTION_CLEAR_EVENT: { -#ifdef HAVE_X11 +#if defined HAVE_X11 || HAVE_PGTK /* Remove it from the buffer before processing it, since otherwise swallow_events will see it and process it again. */ struct selection_input_event copy = event->sie; kbd_fetch_ptr = next_kbd_event (event); input_pending = readable_events (0); + +#ifdef HAVE_X11 x_handle_selection_event (©); #else + pgtk_handle_selection_event (©); +#endif +#else /* We're getting selection request events, but we don't have a window system. */ emacs_abort (); #endif } break; +#else + case SELECTION_REQUEST_EVENT: + emacs_abort (); -#ifdef HAVE_X_WINDOWS - case UNSUPPORTED_DROP_EVENT: + case SELECTION_CLEAR_EVENT: { - struct frame *f; + struct input_event copy = event->ie; kbd_fetch_ptr = next_kbd_event (event); input_pending = readable_events (0); - - /* This means this event was already handled in - `x_dnd_begin_drag_and_drop'. */ - if (event->ie.modifiers < x_dnd_unsupported_event_level) - break; - - f = XFRAME (event->ie.frame_or_window); - - if (!FRAME_LIVE_P (f)) - break; - - if (!NILP (Vx_dnd_unsupported_drop_function)) - { - if (!NILP (call7 (Vx_dnd_unsupported_drop_function, - XCAR (XCDR (event->ie.arg)), event->ie.x, - event->ie.y, XCAR (XCDR (XCDR (event->ie.arg))), - make_uint (event->ie.code), - event->ie.frame_or_window, - make_int (event->ie.timestamp)))) - break; - } - - /* `x-dnd-unsupported-drop-function' could have deleted the - event frame. */ - if (!FRAME_LIVE_P (f)) - break; - - x_dnd_do_unsupported_drop (FRAME_DISPLAY_INFO (f), - event->ie.frame_or_window, - XCAR (event->ie.arg), - XCAR (XCDR (event->ie.arg)), - (Window) event->ie.code, - XFIXNUM (event->ie.x), - XFIXNUM (event->ie.y), - event->ie.timestamp); - break; + haiku_handle_selection_clear (©); } + break; #endif case MONITORS_CHANGED_EVENT: @@ -4368,14 +4408,24 @@ kbd_buffer_get_event (KBOARD **kbp, static void process_special_events (void) { - for (union buffered_input_event *event = kbd_fetch_ptr; - event != kbd_store_ptr; event = next_kbd_event (event)) + union buffered_input_event *event; +#if defined HAVE_X11 || defined HAVE_PGTK || defined HAVE_HAIKU +#ifndef HAVE_HAIKU + struct selection_input_event copy; +#else + struct input_event copy; +#endif + int moved_events; +#endif + + for (event = kbd_fetch_ptr; event != kbd_store_ptr; + event = next_kbd_event (event)) { /* If we find a stored X selection request, handle it now. */ if (event->kind == SELECTION_REQUEST_EVENT || event->kind == SELECTION_CLEAR_EVENT) { -#ifdef HAVE_X11 +#if defined HAVE_X11 || defined HAVE_PGTK /* Remove the event from the fifo buffer before processing; otherwise swallow_events called recursively could see it @@ -4383,8 +4433,7 @@ process_special_events (void) between kbd_fetch_ptr and EVENT one slot to the right, cyclically. */ - struct selection_input_event copy = event->sie; - int moved_events; + copy = event->sie; if (event < kbd_fetch_ptr) { @@ -4400,8 +4449,34 @@ process_special_events (void) moved_events * sizeof *kbd_fetch_ptr); kbd_fetch_ptr = next_kbd_event (kbd_fetch_ptr); input_pending = readable_events (0); + +#ifdef HAVE_X11 x_handle_selection_event (©); #else + pgtk_handle_selection_event (©); +#endif +#elif defined HAVE_HAIKU + if (event->ie.kind != SELECTION_CLEAR_EVENT) + emacs_abort (); + + copy = event->ie; + + if (event < kbd_fetch_ptr) + { + memmove (kbd_buffer + 1, kbd_buffer, + (event - kbd_buffer) * sizeof *kbd_buffer); + kbd_buffer[0] = kbd_buffer[KBD_BUFFER_SIZE - 1]; + moved_events = kbd_buffer + KBD_BUFFER_SIZE - 1 - kbd_fetch_ptr; + } + else + moved_events = event - kbd_fetch_ptr; + + memmove (kbd_fetch_ptr + 1, kbd_fetch_ptr, + moved_events * sizeof *kbd_fetch_ptr); + kbd_fetch_ptr = next_kbd_event (kbd_fetch_ptr); + input_pending = readable_events (0); + haiku_handle_selection_clear (©); +#else /* We're getting selection request events, but we don't have a window system. */ emacs_abort (); @@ -4603,6 +4678,11 @@ timer_check_2 (Lisp_Object timers, Lisp_Object idle_timers) /* If timer is ripe, run it if it hasn't been run. */ if (ripe) { + /* If we got here, presumably `decode_timer` has checked + that this timer has not yet been triggered. */ + eassert (NILP (AREF (chosen_timer, 0))); + /* In a production build, where assertions compile to + nothing, we still want to play it safe here. */ if (NILP (AREF (chosen_timer, 0))) { specpdl_ref count = SPECPDL_INDEX (); @@ -4621,8 +4701,8 @@ timer_check_2 (Lisp_Object timers, Lisp_Object idle_timers) /* Since we have handled the event, we don't need to tell the caller to wake up and do it. */ - /* But the caller must still wait for the next timer, so - return 0 to indicate that. */ + /* But the caller must still wait for the next timer, so + return 0 to indicate that. */ } nexttime = make_timespec (0, 0); @@ -5573,7 +5653,7 @@ make_lispy_position (struct frame *f, Lisp_Object x, Lisp_Object y, if (IMAGEP (object)) { Lisp_Object image_map, hotspot; - if ((image_map = Fplist_get (XCDR (object), QCmap), + if ((image_map = plist_get (XCDR (object), QCmap), !NILP (image_map)) && (hotspot = find_hot_spot (image_map, dx, dy), CONSP (hotspot)) @@ -9498,7 +9578,7 @@ read_char_minibuf_menu_prompt (int commandflag, if (!FIXNUMP (obj) || XFIXNUM (obj) == -2 || (! EQ (obj, menu_prompt_more_char) && (!FIXNUMP (menu_prompt_more_char) - || ! EQ (obj, make_fixnum (Ctl (XFIXNUM (menu_prompt_more_char))))))) + || ! BASE_EQ (obj, make_fixnum (Ctl (XFIXNUM (menu_prompt_more_char))))))) { if (!NILP (KVAR (current_kboard, defining_kbd_macro))) store_kbd_macro_char (obj); @@ -11437,7 +11517,7 @@ quit_throw_to_read_char (bool from_signal) if (FRAMEP (internal_last_event_frame) && !EQ (internal_last_event_frame, selected_frame)) do_switch_frame (make_lispy_switch_frame (internal_last_event_frame), - 0, 0, Qnil); + 0, Qnil); sys_longjmp (getcjmp, 1); } @@ -12058,11 +12138,13 @@ syms_of_keyboard (void) DEFSYM (Qpre_command_hook, "pre-command-hook"); DEFSYM (Qpost_command_hook, "post-command-hook"); + /* Hook run after the region is selected. */ + DEFSYM (Qpost_select_region_hook, "post-select-region-hook"); + DEFSYM (Qundo_auto__add_boundary, "undo-auto--add-boundary"); DEFSYM (Qundo_auto__undoably_changed_buffers, "undo-auto--undoably-changed-buffers"); - DEFSYM (Qdeferred_action_function, "deferred-action-function"); DEFSYM (Qdelayed_warnings_hook, "delayed-warnings-hook"); DEFSYM (Qfunction_key, "function-key"); @@ -12157,12 +12239,13 @@ syms_of_keyboard (void) apply_modifiers. */ DEFSYM (Qmodifier_cache, "modifier-cache"); - DEFSYM (Qrecompute_lucid_menubar, "recompute-lucid-menubar"); DEFSYM (Qactivate_menubar_hook, "activate-menubar-hook"); DEFSYM (Qpolling_period, "polling-period"); DEFSYM (Qgui_set_selection, "gui-set-selection"); + DEFSYM (Qxterm__set_selection, "xterm--set-selection"); + DEFSYM (Qtty_select_active_regions, "tty-select-active-regions"); /* The primary selection. */ DEFSYM (QPRIMARY, "PRIMARY"); @@ -12591,32 +12674,55 @@ cancels any modification. */); DEFSYM (Qdeactivate_mark, "deactivate-mark"); DEFVAR_LISP ("deactivate-mark", Vdeactivate_mark, - doc: /* If an editing command sets this to t, deactivate the mark afterward. + doc: /* Whether to deactivate the mark after an editing command. The command loop sets this to nil before each command, and tests the value when the command returns. -Buffer modification stores t in this variable. */); +If an editing command sets this non-nil, deactivate the mark after +the command returns. + +Buffer modifications store t in this variable. + +By default, deactivating the mark will save the contents of the region +according to `select-active-regions', unless this is set to the symbol +`dont-save'. */); Vdeactivate_mark = Qnil; Fmake_variable_buffer_local (Qdeactivate_mark); DEFVAR_LISP ("pre-command-hook", Vpre_command_hook, doc: /* Normal hook run before each command is executed. -If an unhandled error happens in running this hook, -the function in which the error occurred is unconditionally removed, since -otherwise the error might happen repeatedly and make Emacs nonfunctional. + +If an unhandled error happens in running this hook, the function in +which the error occurred is unconditionally removed, since otherwise +the error might happen repeatedly and make Emacs nonfunctional. + +Note that, when the current buffer contains one or more lines whose +length is above `long-line-threshold', these hook functions are called +with the buffer narrowed to a small portion around point, and the +narrowing is locked (see `narrow-to-region'), so that these hook +functions cannot use `widen' to gain access to other portions of +buffer text. See also `post-command-hook'. */); Vpre_command_hook = Qnil; DEFVAR_LISP ("post-command-hook", Vpost_command_hook, doc: /* Normal hook run after each command is executed. -If an unhandled error happens in running this hook, -the function in which the error occurred is unconditionally removed, since -otherwise the error might happen repeatedly and make Emacs nonfunctional. + +If an unhandled error happens in running this hook, the function in +which the error occurred is unconditionally removed, since otherwise +the error might happen repeatedly and make Emacs nonfunctional. It is a bad idea to use this hook for expensive processing. If unavoidable, wrap your code in `(while-no-input (redisplay) CODE)' to avoid making Emacs unresponsive while the user types. +Note that, when the current buffer contains one or more lines whose +length is above `long-line-threshold', these hook functions are called +with the buffer narrowed to a small portion around point, and the +narrowing is locked (see `narrow-to-region'), so that these hook +functions cannot use `widen' to gain access to other portions of +buffer text. + See also `pre-command-hook'. */); Vpost_command_hook = Qnil; @@ -12636,9 +12742,14 @@ See also `pre-command-hook'. */); Fset (Qecho_area_clear_hook, Qnil); - DEFVAR_LISP ("lucid-menu-bar-dirty-flag", Vlucid_menu_bar_dirty_flag, - doc: /* Non-nil means menu bar, specified Lucid style, needs to be recomputed. */); - Vlucid_menu_bar_dirty_flag = Qnil; +#ifdef USE_LUCID + DEFVAR_BOOL ("lucid--menu-grab-keyboard", + lucid__menu_grab_keyboard, + doc: /* If non-nil, grab keyboard during menu operations. +This is only relevant when using the Lucid X toolkit. It can be +convenient to disable this for debugging purposes. */); + lucid__menu_grab_keyboard = true; +#endif DEFVAR_LISP ("menu-bar-final-items", Vmenu_bar_final_items, doc: /* List of menu bar items to move to the end of the menu bar. @@ -12769,17 +12880,6 @@ This keymap works like `input-decode-map', but comes after `function-key-map'. Another difference is that it is global rather than terminal-local. */); Vkey_translation_map = Fmake_sparse_keymap (Qnil); - DEFVAR_LISP ("deferred-action-list", Vdeferred_action_list, - doc: /* List of deferred actions to be performed at a later time. -The precise format isn't relevant here; we just check whether it is nil. */); - Vdeferred_action_list = Qnil; - - DEFVAR_LISP ("deferred-action-function", Vdeferred_action_function, - doc: /* Function to call to handle deferred actions, after each command. -This function is called with no arguments after each command -whenever `deferred-action-list' is non-nil. */); - Vdeferred_action_function = Qnil; - DEFVAR_LISP ("delayed-warnings-list", Vdelayed_warnings_list, doc: /* List of warnings to be displayed after this command. Each element must be a list (TYPE MESSAGE [LEVEL [BUFFER-NAME]]), @@ -12898,7 +12998,10 @@ This variable only has an effect when Transient Mark mode is enabled. If the value is `only', only temporarily active regions (usually made by mouse-dragging or shift-selection) set the window system's primary -selection. */); +selection. + +If this variable causes the region to be set as the primary selection, +`post-select-region-hook' is then run afterwards. */); Vselect_active_regions = Qt; DEFVAR_LISP ("saved-region-selection", @@ -12966,7 +13069,7 @@ Emacs allows binding both upper and lower case key sequences to commands. However, if there is a lower case key sequence bound to a command, and the user enters an upper case key sequence that is not bound to a command, Emacs will use the lower case binding. Setting -this variable to nil inhibits this behaviour. */); +this variable to nil inhibits this behavior. */); translate_upper_case_key_bindings = true; DEFVAR_BOOL ("input-pending-p-filter-events", @@ -12992,6 +13095,27 @@ argument, which is the terminal on which the monitor configuration changed. */); Vdisplay_monitors_changed_functions = Qnil; + DEFVAR_BOOL ("inhibit--record-char", + inhibit_record_char, + doc: /* If non-nil, don't record input events. +This inhibits recording input events for the purposes of keyboard +macros, dribble file, and `recent-keys'. +Internal use only. */); + inhibit_record_char = false; + + DEFVAR_BOOL ("record-all-keys", record_all_keys, + doc: /* Non-nil means record all keys you type. +When nil, the default, characters typed as part of passwords are +not recorded. The non-nil value countermands `inhibit--record-char', +which see. */); + record_all_keys = false; + + DEFVAR_LISP ("post-select-region-hook", Vpost_select_region_hook, + doc: /* Abnormal hook run after the region is selected. +This usually happens as a result of `select-active-regions'. The hook +is called with one argument, the string that was selected. */);; + Vpost_select_region_hook = Qnil; + pdumper_do_now_and_after_load (syms_of_keyboard_for_pdumper); } @@ -13019,7 +13143,6 @@ syms_of_keyboard_for_pdumper (void) PDUMPER_RESET (num_input_keys, 0); PDUMPER_RESET (num_nonmacro_input_events, 0); PDUMPER_RESET_LV (Vlast_event_frame, Qnil); - PDUMPER_RESET_LV (Vdeferred_action_list, Qnil); PDUMPER_RESET_LV (Vdelayed_warnings_list, Qnil); /* Create the initial keyboard. Qt means 'unset'. */ @@ -13141,7 +13264,10 @@ mark_kboards (void) { /* These two special event types have no Lisp_Objects to mark. */ if (event->kind != SELECTION_REQUEST_EVENT - && event->kind != SELECTION_CLEAR_EVENT) +#ifndef HAVE_HAIKU + && event->kind != SELECTION_CLEAR_EVENT +#endif + ) { mark_object (event->ie.x); mark_object (event->ie.y); diff --git a/src/keyboard.h b/src/keyboard.h index 6ae2dc9c4c6..507d80c2975 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -27,6 +27,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ # include "xterm.h" /* for struct selection_input_event */ #endif +#ifdef HAVE_PGTK +#include "pgtkterm.h" /* for struct selection_input_event */ +#endif + INLINE_HEADER_BEGIN /* Most code should use this macro to access Lisp fields in struct kboard. */ @@ -226,7 +230,7 @@ union buffered_input_event { ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH; struct input_event ie; -#ifdef HAVE_X11 +#if defined HAVE_X11 || defined HAVE_PGTK struct selection_input_event sie; #endif }; diff --git a/src/keymap.c b/src/keymap.c index c8b01eed6fd..506b755e5da 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -1026,8 +1026,14 @@ DEFUN ("copy-keymap", Fcopy_keymap, Scopy_keymap, 1, 1, 0, doc: /* Return a copy of the keymap KEYMAP. Note that this is almost never needed. If you want a keymap that's like -another yet with a few changes, you should use map inheritance rather -than copying. I.e. something like: +another yet with a few changes, you should use keymap inheritance rather +than copying. That is, something like: + + (defvar-keymap foo-map + :parent <theirmap> + ...) + +Or, if you need to support Emacs versions older than 29: (let ((map (make-sparse-keymap))) (set-keymap-parent map <theirmap>) @@ -2596,7 +2602,10 @@ The optional 5th arg NO-REMAP alters how command remapping is handled: - If DEFINITION is remapped to OTHER-COMMAND, normally return the bindings for OTHER-COMMAND. But if NO-REMAP is non-nil, return the - bindings for DEFINITION instead, ignoring its remapping. */) + bindings for DEFINITION instead, ignoring its remapping. + +Keys that are represented as events that have a `non-key-event' non-nil +symbol property are ignored. */) (Lisp_Object definition, Lisp_Object keymap, Lisp_Object firstonly, Lisp_Object noindirect, Lisp_Object no_remap) { /* The keymaps in which to search. */ @@ -2720,7 +2729,12 @@ The optional 5th arg NO-REMAP alters how command remapping is handled: /* It is a true unshadowed match. Record it, unless it's already been seen (as could happen when inheriting keymaps). */ - if (NILP (Fmember (sequence, found))) + if (NILP (Fmember (sequence, found)) + /* Filter out non key events. */ + && !(VECTORP (sequence) + && ASIZE (sequence) == 1 + && SYMBOLP (AREF (sequence, 0)) + && !NILP (Fget (AREF (sequence, 0), Qnon_key_event)))) found = Fcons (sequence, found); /* If firstonly is Qnon_ascii, then we can return the first @@ -3461,4 +3475,6 @@ that describe key bindings. That is why the default is nil. */); DEFSYM (Qkey_parse, "key-parse"); DEFSYM (Qkey_valid_p, "key-valid-p"); + + DEFSYM (Qnon_key_event, "non-key-event"); } diff --git a/src/kqueue.c b/src/kqueue.c index c3c4631784d..99a9434cc2e 100644 --- a/src/kqueue.c +++ b/src/kqueue.c @@ -159,8 +159,8 @@ kqueue_compare_dir_list (Lisp_Object watch_object) (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (old_entry)), Qnil); /* Status change time has been changed, the file attributes have changed. */ - if (NILP (Fequal (Fnth (make_fixnum (3), old_entry), - Fnth (make_fixnum (3), new_entry)))) + if (NILP (Fequal (Fnth (make_fixnum (3), old_entry), + Fnth (make_fixnum (3), new_entry)))) kqueue_generate_event (watch_object, Fcons (Qattrib, Qnil), XCAR (XCDR (old_entry)), Qnil); diff --git a/src/lisp.h b/src/lisp.h index 361a3f63b28..0281c483e32 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -368,6 +368,11 @@ typedef EMACS_INT Lisp_Word; ((ok) ? (void) 0 : wrong_type_argument (predicate, x)) #define lisp_h_CONSP(x) TAGGEDP (x, Lisp_Cons) #define lisp_h_BASE_EQ(x, y) (XLI (x) == XLI (y)) +#define lisp_h_BASE2_EQ(x, y) \ + (BASE_EQ (x, y) \ + || (symbols_with_pos_enabled \ + && SYMBOL_WITH_POS_P (x) \ + && BASE_EQ (XSYMBOL_WITH_POS (x)->sym, y))) /* FIXME: Do we really need to inline the whole thing? * What about keeping the part after `symbols_with_pos_enabled` in @@ -453,6 +458,7 @@ typedef EMACS_INT Lisp_Word; # define CHECK_TYPE(ok, predicate, x) lisp_h_CHECK_TYPE (ok, predicate, x) # define CONSP(x) lisp_h_CONSP (x) # define BASE_EQ(x, y) lisp_h_BASE_EQ (x, y) +# define BASE2_EQ(x, y) lisp_h_BASE2_EQ (x, y) # define FLOATP(x) lisp_h_FLOATP (x) # define FIXNUMP(x) lisp_h_FIXNUMP (x) # define NILP(x) lisp_h_NILP (x) @@ -1323,6 +1329,14 @@ INLINE bool return lisp_h_BASE_EQ (x, y); } +/* Return true if X and Y are the same object, reckoning X to be the + same as a bare symbol Y if X is Y with position. */ +INLINE bool +(BASE2_EQ) (Lisp_Object x, Lisp_Object y) +{ + return lisp_h_BASE2_EQ (x, y); +} + /* Return true if X and Y are the same object, reckoning a symbol with position as being the same as the bare symbol. */ INLINE bool @@ -1631,13 +1645,13 @@ STRING_MULTIBYTE (Lisp_Object str) /* Mark STR as a multibyte string. Assure that STR contains only ASCII characters in advance. */ -#define STRING_SET_MULTIBYTE(STR) \ - do { \ - if (XSTRING (STR)->u.s.size == 0) \ - (STR) = empty_multibyte_string; \ - else \ - XSTRING (STR)->u.s.size_byte = XSTRING (STR)->u.s.size; \ - } while (false) +INLINE void +STRING_SET_MULTIBYTE (Lisp_Object str) +{ + /* The 0-length strings are unique&shared so we can't modify them. */ + eassert (XSTRING (str)->u.s.size > 0); + XSTRING (str)->u.s.size_byte = XSTRING (str)->u.s.size; +} /* Convenience functions for dealing with Lisp strings. */ @@ -3137,7 +3151,7 @@ CHECK_NUMBER (Lisp_Object x) INLINE void CHECK_INTEGER (Lisp_Object x) { - CHECK_TYPE (INTEGERP (x), Qnumberp, x); + CHECK_TYPE (INTEGERP (x), Qintegerp, x); } INLINE void @@ -3442,7 +3456,7 @@ union specbinding #define WRAP_SPECPDL_REF 1 #endif -/* Abstract reference to to a specpdl entry. +/* Abstract reference to a specpdl entry. The number is always a multiple of sizeof (union specbinding). */ #ifdef WRAP_SPECPDL_REF /* Use a proper type for specpdl_ref if it does not make the code slower, @@ -3784,10 +3798,10 @@ make_symbol_constant (Lisp_Object sym) /* Buffer-local variable access functions. */ -INLINE int +INLINE bool blv_found (struct Lisp_Buffer_Local_Value *blv) { - eassert (blv->found == !EQ (blv->defcell, blv->valcell)); + eassert (blv->found == !BASE_EQ (blv->defcell, blv->valcell)); return blv->found; } @@ -3902,10 +3916,14 @@ integer_to_uintmax (Lisp_Object num, uintmax_t *n) typedef intmax_t modiff_count; INLINE modiff_count -modiff_incr (modiff_count *a) -{ - modiff_count a0 = *a; - bool modiff_overflow = INT_ADD_WRAPV (a0, 1, a); +modiff_incr (modiff_count *a, ptrdiff_t len) +{ + modiff_count a0 = *a; int incr = len ? 1 : 0; + /* Increase the counter more for a large modification and less for a + small modification. Increase it logarithmically to avoid + increasing it too much. */ + while (len >>= 1) incr++; + bool modiff_overflow = INT_ADD_WRAPV (a0, incr, a); eassert (!modiff_overflow && *a >> 30 >> 30 == 0); return a0; } @@ -4025,6 +4043,10 @@ extern ptrdiff_t string_char_to_byte (Lisp_Object, ptrdiff_t); extern ptrdiff_t string_byte_to_char (Lisp_Object, ptrdiff_t); extern Lisp_Object string_to_multibyte (Lisp_Object); extern Lisp_Object string_make_unibyte (Lisp_Object); +extern Lisp_Object plist_get (Lisp_Object plist, Lisp_Object prop); +extern Lisp_Object plist_put (Lisp_Object plist, Lisp_Object prop, + Lisp_Object val); +extern Lisp_Object plist_member (Lisp_Object plist, Lisp_Object prop); extern void syms_of_fns (void); /* Defined in sort.c */ @@ -4513,6 +4535,7 @@ extern Lisp_Object Vrun_hooks; extern Lisp_Object Vsignaling_function; extern Lisp_Object inhibit_lisp_code; extern bool signal_quit_p (Lisp_Object); +extern bool backtrace_yet; /* To run a normal hook, use the appropriate function from the list below. The calling convention: @@ -4662,6 +4685,7 @@ extern void save_restriction_restore (Lisp_Object); extern Lisp_Object make_buffer_string (ptrdiff_t, ptrdiff_t, bool); extern Lisp_Object make_buffer_string_both (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool); +extern Lisp_Object narrow_to_region_internal (Lisp_Object, Lisp_Object, bool); extern void init_editfns (void); extern void syms_of_editfns (void); @@ -4720,6 +4744,7 @@ extern bool internal_delete_file (Lisp_Object); extern Lisp_Object check_emacs_readlinkat (int, Lisp_Object, char const *); extern bool file_directory_p (Lisp_Object); extern bool file_accessible_directory_p (Lisp_Object); +extern Lisp_Object buffer_visited_file_modtime (struct buffer *); extern void init_fileio (void); extern void syms_of_fileio (void); @@ -4748,6 +4773,8 @@ extern ptrdiff_t fast_c_string_match_ignore_case (Lisp_Object, const char *, ptrdiff_t); extern ptrdiff_t fast_looking_at (Lisp_Object, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, Lisp_Object); +extern ptrdiff_t find_newline1 (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, + ptrdiff_t, ptrdiff_t *, ptrdiff_t *, bool); extern ptrdiff_t find_newline (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t *, ptrdiff_t *, bool); extern void scan_newline (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, @@ -4809,6 +4836,8 @@ extern bool detect_input_pending (void); extern bool detect_input_pending_ignore_squeezables (void); extern bool detect_input_pending_run_timers (bool); extern void safe_run_hooks (Lisp_Object); +extern void safe_run_hooks_maybe_narrowed (Lisp_Object, struct window *); +extern void safe_run_hooks_2 (Lisp_Object, Lisp_Object, Lisp_Object); extern void cmd_error_internal (Lisp_Object, const char *); extern Lisp_Object command_loop_2 (Lisp_Object); extern Lisp_Object read_menu_command (void); @@ -4828,7 +4857,7 @@ extern void syms_of_indent (void); /* Defined in frame.c. */ extern void store_frame_param (struct frame *, Lisp_Object, Lisp_Object); extern void store_in_alist (Lisp_Object *, Lisp_Object, Lisp_Object); -extern Lisp_Object do_switch_frame (Lisp_Object, int, int, Lisp_Object); +extern Lisp_Object do_switch_frame (Lisp_Object, int, Lisp_Object); extern Lisp_Object get_frame_param (struct frame *, Lisp_Object); extern void frames_discard_buffer (Lisp_Object); extern void init_frame_once (void); @@ -4921,7 +4950,8 @@ extern void setup_process_coding_systems (Lisp_Object); #endif extern int emacs_spawn (pid_t *, int, int, int, char **, char **, - const char *, const char *, const sigset_t *); + const char *, const char *, bool, bool, + const sigset_t *); extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL; extern void init_callproc_1 (void); extern void init_callproc (void); diff --git a/src/lread.c b/src/lread.c index dfabe75113e..06fac7185bb 100644 --- a/src/lread.c +++ b/src/lread.c @@ -131,25 +131,15 @@ static ptrdiff_t read_from_string_limit; /* Position in object from which characters are being read by `readchar'. */ static EMACS_INT readchar_offset; -/* This contains the last string skipped with #@. */ -static char *saved_doc_string; -/* Length of buffer allocated in saved_doc_string. */ -static ptrdiff_t saved_doc_string_size; -/* Length of actual data in saved_doc_string. */ -static ptrdiff_t saved_doc_string_length; -/* This is the file position that string came from. */ -static file_offset saved_doc_string_position; - -/* This contains the previous string skipped with #@. - We copy it from saved_doc_string when a new string - is put in saved_doc_string. */ -static char *prev_saved_doc_string; -/* Length of buffer allocated in prev_saved_doc_string. */ -static ptrdiff_t prev_saved_doc_string_size; -/* Length of actual data in prev_saved_doc_string. */ -static ptrdiff_t prev_saved_doc_string_length; -/* This is the file position that string came from. */ -static file_offset prev_saved_doc_string_position; +struct saved_string { + char *string; /* string in allocated buffer */ + ptrdiff_t size; /* allocated size of buffer */ + ptrdiff_t length; /* length of string in buffer */ + file_offset position; /* position in file the string came from */ +}; + +/* The last two strings skipped with #@ (most recent first). */ +static struct saved_string saved_strings[2]; /* A list of file names for files being loaded in Fload. Used to check for recursive loads. */ @@ -1605,13 +1595,12 @@ Return t if the file exists and loads successfully. */) if (!NILP (Ffboundp (Qdo_after_load_evaluation))) call1 (Qdo_after_load_evaluation, hist_file_name) ; - xfree (saved_doc_string); - saved_doc_string = 0; - saved_doc_string_size = 0; - - xfree (prev_saved_doc_string); - prev_saved_doc_string = 0; - prev_saved_doc_string_size = 0; + for (int i = 0; i < ARRAYELTS (saved_strings); i++) + { + xfree (saved_strings[i].string); + saved_strings[i].string = NULL; + saved_strings[i].size = 0; + } if (!noninteractive && (NILP (nomessage) || force_load_messages)) { @@ -1735,13 +1724,24 @@ maybe_swap_for_eln (bool no_native, Lisp_Object *filename, int *fd, { if (!NILP (find_symbol_value ( Qnative_comp_warning_on_missing_source))) - call2 (intern_c_string ("display-warning"), - Qcomp, - CALLN (Fformat, - build_string ("Cannot look-up eln file as no source " - "file was found for %s"), - *filename)); - return; + { + /* If we have an installation without any .el files, + there's really no point in giving a warning here, + because that will trigger a cascade of warnings. So + just do a sanity check and refuse to do anything if we + can't find even central .el files. */ + if (NILP (Flocate_file_internal (build_string ("simple.el"), + Vload_path, + Qnil, Qnil))) + return; + call2 (intern_c_string ("display-warning"), + Qcomp, + CALLN (Fformat, + build_string ("Cannot look up eln file as " + "no source file was found for %s"), + *filename)); + return; + } } } Lisp_Object eln_rel_name = Fcomp_el_to_eln_rel_filename (src_name); @@ -3045,7 +3045,6 @@ read_string_literal (char stackbuf[VLA_ELEMS (stackbufsize)], /* True if we saw an escape sequence specifying a single-byte character. */ bool force_singlebyte = false; - bool cancel = false; ptrdiff_t nchars = 0; int ch; @@ -3074,8 +3073,6 @@ read_string_literal (char stackbuf[VLA_ELEMS (stackbufsize)], case ' ': case '\n': /* `\SPC' and `\LF' generate no characters at all. */ - if (p == read_buffer) - cancel = true; continue; default: UNREAD (ch); @@ -3141,15 +3138,6 @@ read_string_literal (char stackbuf[VLA_ELEMS (stackbufsize)], if (ch < 0) end_of_file_error (); - /* If purifying, and string starts with \ newline, - return zero instead. This is for doc strings - that we are really going to find in etc/DOC.nn.nn. */ - if (!NILP (Vpurify_flag) && NILP (Vdoc_file_name) && cancel) - { - unbind_to (count, Qnil); - return make_fixnum (0); - } - if (!force_multibyte && force_singlebyte) { /* READ_BUFFER contains raw 8-bit bytes and no multibyte @@ -3175,7 +3163,7 @@ hash_table_from_plist (Lisp_Object plist) /* This is repetitive but fast and simple. */ #define ADDPARAM(name) \ do { \ - Lisp_Object val = Fplist_get (plist, Q ## name); \ + Lisp_Object val = plist_get (plist, Q ## name); \ if (!NILP (val)) \ { \ *par++ = QC ## name; \ @@ -3190,7 +3178,7 @@ hash_table_from_plist (Lisp_Object plist) ADDPARAM (rehash_threshold); ADDPARAM (purecopy); - Lisp_Object data = Fplist_get (plist, Qdata); + Lisp_Object data = plist_get (plist, Qdata); /* Now use params to make a new hash table and fill it. */ Lisp_Object ht = Fmake_hash_table (par - params, params); @@ -3446,57 +3434,95 @@ skip_lazy_string (Lisp_Object readcharfun) record the last string that we skipped, and record where in the file it comes from. */ - /* But first exchange saved_doc_string - with prev_saved_doc_string, so we save two strings. */ - { - char *temp = saved_doc_string; - ptrdiff_t temp_size = saved_doc_string_size; - file_offset temp_pos = saved_doc_string_position; - ptrdiff_t temp_len = saved_doc_string_length; - - saved_doc_string = prev_saved_doc_string; - saved_doc_string_size = prev_saved_doc_string_size; - saved_doc_string_position = prev_saved_doc_string_position; - saved_doc_string_length = prev_saved_doc_string_length; - - prev_saved_doc_string = temp; - prev_saved_doc_string_size = temp_size; - prev_saved_doc_string_position = temp_pos; - prev_saved_doc_string_length = temp_len; - } + /* First exchange the two saved_strings. */ + verify (ARRAYELTS (saved_strings) == 2); + struct saved_string t = saved_strings[0]; + saved_strings[0] = saved_strings[1]; + saved_strings[1] = t; enum { extra = 100 }; - if (saved_doc_string_size == 0) + struct saved_string *ss = &saved_strings[0]; + if (ss->size == 0) { - saved_doc_string = xmalloc (nskip + extra); - saved_doc_string_size = nskip + extra; + ss->size = nskip + extra; + ss->string = xmalloc (ss->size); } - if (nskip > saved_doc_string_size) + else if (nskip > ss->size) { - saved_doc_string = xrealloc (saved_doc_string, nskip + extra); - saved_doc_string_size = nskip + extra; + ss->size = nskip + extra; + ss->string = xrealloc (ss->string, ss->size); } FILE *instream = infile->stream; - saved_doc_string_position = (file_tell (instream) - infile->lookahead); + ss->position = (file_tell (instream) - infile->lookahead); - /* Copy that many bytes into saved_doc_string. */ + /* Copy that many bytes into the saved string. */ ptrdiff_t i = 0; int c = 0; for (int n = min (nskip, infile->lookahead); n > 0; n--) - saved_doc_string[i++] = c = infile->buf[--infile->lookahead]; + ss->string[i++] = c = infile->buf[--infile->lookahead]; block_input (); for (; i < nskip && c >= 0; i++) - saved_doc_string[i] = c = getc (instream); + ss->string[i] = c = getc (instream); unblock_input (); - saved_doc_string_length = i; + ss->length = i; } else /* Skip that many bytes. */ skip_dyn_bytes (readcharfun, nskip); } +/* Given a lazy-loaded string designator VAL, return the actual string. + VAL is (FILENAME . POS). */ +static Lisp_Object +get_lazy_string (Lisp_Object val) +{ + /* Get a doc string from the file we are loading. + If it's in a saved string, get it from there. + + Here, we don't know if the string is a bytecode string or a doc + string. As a bytecode string must be unibyte, we always return a + unibyte string. If it is actually a doc string, caller must make + it multibyte. */ + + /* We used to emit negative positions for 'user variables' (whose doc + strings started with an asterisk); take the absolute value for + compatibility. */ + EMACS_INT pos = eabs (XFIXNUM (XCDR (val))); + struct saved_string *ss = &saved_strings[0]; + struct saved_string *ssend = ss + ARRAYELTS (saved_strings); + while (ss < ssend + && !(pos >= ss->position && pos < ss->position + ss->length)) + ss++; + if (ss >= ssend) + return get_doc_string (val, 1, 0); + + ptrdiff_t start = pos - ss->position; + char *str = ss->string; + ptrdiff_t from = start; + ptrdiff_t to = start; + + /* Process quoting with ^A, and find the end of the string, + which is marked with ^_ (037). */ + while (str[from] != 037) + { + int c = str[from++]; + if (c == 1) + { + c = str[from++]; + str[to++] = (c == 1 ? c + : c == '0' ? 0 + : c == '_' ? 037 + : c); + } + else + str[to++] = c; + } + + return make_unibyte_string (str + start, to - start); +} + /* Length of prefix only consisting of symbol constituent characters. */ static ptrdiff_t @@ -4238,6 +4264,15 @@ read0 (Lisp_Object readcharfun, bool locate_syms) XSETCDR (e->u.list.tail, obj); read_stack_pop (); obj = e->u.list.head; + + /* Hack: immediately convert (#$ . FIXNUM) to the corresponding + string if load-force-doc-strings is set. */ + if (load_force_doc_strings + && BASE_EQ (XCAR (obj), Vload_file_name) + && !NILP (XCAR (obj)) + && FIXNUMP (XCDR (obj))) + obj = get_lazy_string (obj); + break; } @@ -4265,7 +4300,7 @@ read0 (Lisp_Object readcharfun, bool locate_syms) /* Catch silly games like #1=#1# */ invalid_syntax ("nonsensical self-reference", readcharfun); - /* Optimisation: since the placeholder is already + /* Optimization: since the placeholder is already a cons, repurpose it as the actual value. This allows us to skip the substitution below, since the placeholder is already referenced @@ -4855,7 +4890,7 @@ oblookup (Lisp_Object obarray, register const char *ptr, ptrdiff_t size, ptrdiff hash = hash_string (ptr, size_byte) % obsize; bucket = AREF (obarray, hash); oblookup_last_bucket_number = hash; - if (EQ (bucket, make_fixnum (0))) + if (BASE_EQ (bucket, make_fixnum (0))) ; else if (!SYMBOLP (bucket)) /* Like CADR error message. */ @@ -4877,7 +4912,7 @@ oblookup (Lisp_Object obarray, register const char *ptr, ptrdiff_t size, ptrdiff /* Like 'oblookup', but considers 'Vread_symbol_shorthands', potentially recognizing that IN is shorthand for some other - longhand name, which is then then placed in OUT. In that case, + longhand name, which is then placed in OUT. In that case, memory is malloc'ed for OUT (which the caller must free) while SIZE_OUT and SIZE_BYTE_OUT respectively hold the character and byte sizes of the transformed symbol name. If IN is not recognized diff --git a/src/macfont.m b/src/macfont.m index 4dd55e77469..4de056cb361 100644 --- a/src/macfont.m +++ b/src/macfont.m @@ -929,7 +929,7 @@ macfont_descriptor_entity (CTFontDescriptorRef desc, Lisp_Object extra, cfnumber_get_font_symbolic_traits_value (num, &sym_traits); CFRelease (dict); } - if (EQ (AREF (entity, FONT_SIZE_INDEX), make_fixnum (0))) + if (BASE_EQ (AREF (entity, FONT_SIZE_INDEX), make_fixnum (0))) ASET (entity, FONT_AVGWIDTH_INDEX, make_fixnum (0)); ASET (entity, FONT_EXTRA_INDEX, Fcopy_sequence (extra)); name = CTFontDescriptorCopyAttribute (desc, kCTFontNameAttribute); @@ -2645,6 +2645,9 @@ macfont_open (struct frame * f, Lisp_Object entity, int pixel_size) font->pixel_size = size; font->driver = &macfont_driver; font->encoding_charset = font->repertory_charset = -1; + /* Clear font->space_width so macfont_monospace_width_multiplier may + not be confused by an uninitialized value. */ + font->space_width = 0; block_input (); @@ -2653,7 +2656,7 @@ macfont_open (struct frame * f, Lisp_Object entity, int pixel_size) macfont_info->cgfont = CTFontCopyGraphicsFont (macfont, NULL); val = assq_no_quit (QCdestination, AREF (entity, FONT_EXTRA_INDEX)); - if (CONSP (val) && EQ (XCDR (val), make_fixnum (1))) + if (CONSP (val) && BASE_EQ (XCDR (val), make_fixnum (1))) macfont_info->screen_font = mac_screen_font_create_with_name (font_name, size); else diff --git a/src/marker.c b/src/marker.c index 3c8e628762e..9727586f424 100644 --- a/src/marker.c +++ b/src/marker.c @@ -214,11 +214,12 @@ buf_charpos_to_bytepos (struct buffer *b, ptrdiff_t charpos) We have one known above and one known below. Scan, counting characters, from whichever one is closer. */ + eassert (best_below <= charpos && charpos <= best_above); if (charpos - best_below < best_above - charpos) { bool record = charpos - best_below > 5000; - while (best_below != charpos) + while (best_below < charpos) { best_below++; best_below_byte += buf_next_char_len (b, best_below_byte); @@ -243,7 +244,7 @@ buf_charpos_to_bytepos (struct buffer *b, ptrdiff_t charpos) { bool record = best_above - charpos > 5000; - while (best_above != charpos) + while (best_above > charpos) { best_above--; best_above_byte -= buf_prev_char_len (b, best_above_byte); diff --git a/src/minibuf.c b/src/minibuf.c index 1f77a6cdc18..bedc5644807 100644 --- a/src/minibuf.c +++ b/src/minibuf.c @@ -427,8 +427,8 @@ No argument or nil as argument means use the current buffer as BUFFER. */) { if (NILP (buffer)) buffer = Fcurrent_buffer (); - return EQ (buffer, (Fcar (Fnthcdr (make_fixnum (minibuf_level), - Vminibuffer_list)))) + return BASE_EQ (buffer, (Fcar (Fnthcdr (make_fixnum (minibuf_level), + Vminibuffer_list)))) ? Qt : Qnil; } @@ -1123,8 +1123,8 @@ read_minibuf_unwind (void) found: if (!EQ (exp_MB_frame, saved_selected_frame) && !NILP (exp_MB_frame)) - do_switch_frame (exp_MB_frame, 0, 0, Qt); /* This also sets - minibuf_window */ + do_switch_frame (exp_MB_frame, 0, Qt); /* This also sets + minibuf_window */ /* To keep things predictable, in case it matters, let's be in the minibuffer when we reset the relevant variables. Don't depend on @@ -1236,7 +1236,7 @@ read_minibuf_unwind (void) /* Restore the selected frame. */ if (!EQ (exp_MB_frame, saved_selected_frame) && !NILP (exp_MB_frame)) - do_switch_frame (saved_selected_frame, 0, 0, Qt); + do_switch_frame (saved_selected_frame, 0, Qt); } /* Replace the expired minibuffer in frame exp_MB_frame with the next less @@ -1545,18 +1545,6 @@ function, instead of the usual behavior. */) return unbind_to (count, result); } -static Lisp_Object -minibuf_conform_representation (Lisp_Object string, Lisp_Object basis) -{ - if (STRING_MULTIBYTE (string) == STRING_MULTIBYTE (basis)) - return string; - - if (STRING_MULTIBYTE (string)) - return Fstring_make_unibyte (string); - else - return Fstring_make_multibyte (string); -} - static bool match_regexps (Lisp_Object string, Lisp_Object regexps, bool ignore_case) @@ -1791,10 +1779,10 @@ or from one of the possible completions. */) if (bestmatchsize != SCHARS (eltstring) || bestmatchsize != matchsize || (completion_ignore_case - && !EQ (Fcompare_strings (old_bestmatch, zero, lcompare, - eltstring, zero, lcompare, - Qnil), - Qt))) + && !BASE_EQ (Fcompare_strings (old_bestmatch, zero, + lcompare, eltstring, zero, + lcompare, Qnil), + Qt))) /* Don't count the same string multiple times. */ matchcount += matchcount <= 1; bestmatchsize = matchsize; @@ -1817,7 +1805,7 @@ or from one of the possible completions. */) don't change the case of what the user typed. */ if (completion_ignore_case && bestmatchsize == SCHARS (string) && SCHARS (bestmatch) > bestmatchsize) - return minibuf_conform_representation (string, bestmatch); + return string; /* Return t if the supplied string is an exact match (counting case); it does not require any change to be made. */ @@ -2011,8 +1999,9 @@ REQUIRE-MATCH can take the following values: input, but she needs to confirm her choice if she called `minibuffer-complete' right before `minibuffer-complete-and-exit' and the input is not an element of COLLECTION. -- a function, which will be called with the input as the parameter. - If it returns a non-nil value, the minibuffer is exited with that value. +- a function, which will be called with the input as the + argument. If the function returns a non-nil value, the + minibuffer is exited with that argument as the value. - anything else behaves like t except that typing RET does not exit if it does non-null completion. @@ -2089,19 +2078,6 @@ the values STRING, PREDICATE and `lambda'. */) SSDATA (string), SCHARS (string), SBYTES (string)); - if (!SYMBOLP (tem)) - { - if (STRING_MULTIBYTE (string)) - string = Fstring_make_unibyte (string); - else - string = Fstring_make_multibyte (string); - - tem = oblookup (collection, - SSDATA (string), - SCHARS (string), - SBYTES (string)); - } - if (completion_ignore_case && !SYMBOLP (tem)) { for (i = ASIZE (collection) - 1; i >= 0; i--) @@ -2110,10 +2086,11 @@ the values STRING, PREDICATE and `lambda'. */) if (SYMBOLP (tail)) while (1) { - if (EQ (Fcompare_strings (string, make_fixnum (0), Qnil, - Fsymbol_name (tail), - make_fixnum (0) , Qnil, Qt), - Qt)) + if (BASE_EQ (Fcompare_strings (string, make_fixnum (0), + Qnil, + Fsymbol_name (tail), + make_fixnum (0) , Qnil, Qt), + Qt)) { tem = tail; break; @@ -2144,9 +2121,9 @@ the values STRING, PREDICATE and `lambda'. */) if (BASE_EQ (tem, Qunbound)) continue; Lisp_Object strkey = (SYMBOLP (tem) ? Fsymbol_name (tem) : tem); if (!STRINGP (strkey)) continue; - if (EQ (Fcompare_strings (string, Qnil, Qnil, - strkey, Qnil, Qnil, - completion_ignore_case ? Qt : Qnil), + if (BASE_EQ (Fcompare_strings (string, Qnil, Qnil, + strkey, Qnil, Qnil, + completion_ignore_case ? Qt : Qnil), Qt)) goto found_matching_key; } diff --git a/src/nsfns.m b/src/nsfns.m index 5ab2b2ee35a..2699cf37a5b 100644 --- a/src/nsfns.m +++ b/src/nsfns.m @@ -382,7 +382,7 @@ ns_set_icon_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval) /* See if it's changed. */ if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!STRINGP (oldval) && NILP (oldval) == NILP (arg)) @@ -1057,6 +1057,7 @@ frame_parm_handler ns_frame_parm_handlers[] = 0, /* x_set_override_redirect */ gui_set_no_special_glyphs, gui_set_alpha_background, + NULL, #ifdef NS_IMPL_COCOA ns_set_appearance, ns_set_transparent_titlebar, @@ -1726,7 +1727,7 @@ Optional arg DIR, if non-nil, supplies a default directory. Optional arg MUSTMATCH, if non-nil, means the returned file or directory must exist. Optional arg INIT, if non-nil, provides a default file name to use. -Optional arg DIR_ONLY_P, if non-nil, means choose only directories. */) +Optional arg DIR-ONLY-P, if non-nil, means choose only directories. */) (Lisp_Object prompt, Lisp_Object dir, Lisp_Object mustmatch, Lisp_Object init, Lisp_Object dir_only_p) { @@ -3210,7 +3211,8 @@ x_hide_tip (bool delete) DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, doc: /* SKIP: real doc in xfns.c. */) - (Lisp_Object string, Lisp_Object frame, Lisp_Object parms, Lisp_Object timeout, Lisp_Object dx, Lisp_Object dy) + (Lisp_Object string, Lisp_Object frame, Lisp_Object parms, + Lisp_Object timeout, Lisp_Object dx, Lisp_Object dy) { int root_x, root_y; specpdl_ref count = SPECPDL_INDEX (); @@ -3224,6 +3226,10 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, Lisp_Object window, size, tip_buf; char *str; NSWindow *nswindow; + bool displayed; +#ifdef ENABLE_CHECKING + struct glyph_row *row, *end; +#endif AUTO_STRING (tip, " *tip*"); @@ -3287,7 +3293,6 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame))) { if (FRAME_VISIBLE_P (XFRAME (tip_frame)) - && EQ (frame, tip_last_frame) && !NILP (Fequal_including_properties (tip_last_string, string)) && !NILP (Fequal (tip_last_parms, parms))) { @@ -3454,7 +3459,26 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, clear_glyph_matrix (w->desired_matrix); clear_glyph_matrix (w->current_matrix); SET_TEXT_POS (pos, BEGV, BEGV_BYTE); - try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + row = w->desired_matrix->rows; + end = w->desired_matrix->rows + w->desired_matrix->nrows; + + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + + eassert (row < end && row->ends_at_zv_p); +#endif + } + /* Calculate size of tooltip window. */ size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, make_fixnum (w->pixel_height), Qnil, @@ -3889,7 +3913,7 @@ Default is t. */); DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, doc: /* SKIP: real doc in xfns.c. */); - Vx_max_tooltip_size = Fcons (make_fixnum (80), make_fixnum (40)); + Vx_max_tooltip_size = Qnil; defsubr (&Sns_read_file_name); defsubr (&Sns_get_resource); diff --git a/src/nsfont.m b/src/nsfont.m index ae5e134e15b..b54118afe5d 100644 --- a/src/nsfont.m +++ b/src/nsfont.m @@ -1763,8 +1763,11 @@ syms_of_nsfont (void) DEFSYM (Qcondensed, "condensed"); DEFSYM (Qexpanded, "expanded"); DEFSYM (Qmedium, "medium"); + DEFVAR_LISP ("ns-reg-to-script", Vns_reg_to_script, - doc: /* Internal use: maps font registry to Unicode script. */); + doc: /* Internal map of font registry to Unicode script. */); + Vns_reg_to_script = Qnil; + pdumper_do_now_and_after_load (syms_of_nsfont_for_pdumper); } diff --git a/src/nsimage.m b/src/nsimage.m index 2fff987f9fc..9cb5090dd0d 100644 --- a/src/nsimage.m +++ b/src/nsimage.m @@ -142,7 +142,7 @@ ns_load_image (struct frame *f, struct image *img, eassert (valid_image_p (img->spec)); - lisp_index = Fplist_get (XCDR (img->spec), QCindex); + lisp_index = plist_get (XCDR (img->spec), QCindex); index = FIXNUMP (lisp_index) ? XFIXNAT (lisp_index) : 0; if (STRINGP (spec_file)) diff --git a/src/nsmenu.m b/src/nsmenu.m index d02d7bae4b5..ae795a0d22b 100644 --- a/src/nsmenu.m +++ b/src/nsmenu.m @@ -207,8 +207,6 @@ ns_update_menubar (struct frame *f, bool deep_p) /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); diff --git a/src/nsselect.m b/src/nsselect.m index c46bfeaf42a..5b47d746122 100644 --- a/src/nsselect.m +++ b/src/nsselect.m @@ -724,7 +724,7 @@ Return the action that the drop target actually chose to perform, or nil if no action was performed (either because there was no drop target, or the drop was rejected). If RETURN-FRAME is the symbol `now', also return any frame that mouse moves into during the -drag-and-drop operation, whilst simultaneously cancelling it. Any +drag-and-drop operation, whilst simultaneously canceling it. Any other non-nil value means to do the same, but to wait for the mouse to leave FRAME first. diff --git a/src/nsterm.h b/src/nsterm.h index c4fdc7054f7..2a4c7571a34 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -724,6 +724,7 @@ enum ns_return_frame_mode int em_whole; } +- (void) mark; - (instancetype) initFrame: (NSRect )r window: (Lisp_Object)win; - (void)setFrame: (NSRect)r; @@ -926,6 +927,9 @@ struct ns_output NSColor *cursor_color; NSColor *foreground_color; NSColor *background_color; + NSColor *relief_background_color; + NSColor *light_relief_color; + NSColor *dark_relief_color; EmacsToolbar *toolbar; #else void *view; @@ -933,6 +937,9 @@ struct ns_output void *cursor_color; void *foreground_color; void *background_color; + void *relief_background_color; + void *light_relief_color; + void *dark_relief_color; void *toolbar; #endif @@ -1373,4 +1380,7 @@ enum NSWindowTabbingMode #define NSBezelStyleRounded NSRoundedBezelStyle #define NSButtonTypeMomentaryPushIn NSMomentaryPushInButton #endif + +extern void mark_nsterm (void); + #endif /* HAVE_NS */ diff --git a/src/nsterm.m b/src/nsterm.m index 891d52ea3f0..6c6151701b8 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -358,7 +358,7 @@ mod_of_kind (Lisp_Object modifier, Lisp_Object kind) return modifier; else { - Lisp_Object val = Fplist_get (modifier, kind); + Lisp_Object val = plist_get (modifier, kind); return SYMBOLP (val) ? val : Qnil; } } @@ -2900,10 +2900,7 @@ ns_define_fringe_bitmap (int which, unsigned short *bits, int h, int w) for (int y = 0 ; y < h ; y++) for (int x = 0 ; x < w ; x++) { - /* XBM rows are always round numbers of bytes, with any unused - bits ignored. */ - int byte = y * (w/8 + (w%8 ? 1 : 0)) + x/8; - bool bit = bits[byte] & (0x80 >> x%8); + bool bit = bits[y] & (1 << (w - x - 1)); if (bit) [p appendBezierPathWithRect:NSMakeRect (x, y, 1, 1)]; } @@ -3103,16 +3100,21 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, case NO_CURSOR: break; case FILLED_BOX_CURSOR: + /* The call to draw_phys_cursor_glyph can end up undoing the + ns_focus, so unfocus here and regain focus later. */ + [ctx restoreGraphicsState]; + ns_unfocus (f); draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR); + ns_focus (f, &r, 1); break; case HOLLOW_BOX_CURSOR: - draw_phys_cursor_glyph (w, glyph_row, DRAW_NORMAL_TEXT); - /* This works like it does in PostScript, not X Windows. */ [NSBezierPath strokeRect: NSInsetRect (r, 0.5, 0.5)]; + [ctx restoreGraphicsState]; break; case HBAR_CURSOR: NSRectFill (r); + [ctx restoreGraphicsState]; break; case BAR_CURSOR: s = r; @@ -3123,10 +3125,10 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, s.origin.x += cursor_glyph->pixel_width - s.size.width; NSRectFill (s); + [ctx restoreGraphicsState]; break; } - [ctx restoreGraphicsState]; ns_unfocus (f); } @@ -3475,6 +3477,35 @@ ns_draw_box (NSRect r, CGFloat hthickness, CGFloat vthickness, } } +/* Set up colors for the relief lines around glyph string S. */ + +static void +ns_setup_relief_colors (struct glyph_string *s) +{ + struct ns_output *di = FRAME_OUTPUT_DATA (s->f); + NSColor *color; + + if (s->face->use_box_color_for_shadows_p) + color = [NSColor colorWithUnsignedLong: s->face->box_color]; + else + color = [NSColor colorWithUnsignedLong: s->face->background]; + + if (s->hl == DRAW_CURSOR) + color = FRAME_CURSOR_COLOR (s->f); + + if (color == nil) + color = [NSColor grayColor]; + + if (color != di->relief_background_color) + { + [di->relief_background_color release]; + di->relief_background_color = [color retain]; + [di->light_relief_color release]; + di->light_relief_color = [[color highlightWithLevel: 0.4] retain]; + [di->dark_relief_color release]; + di->dark_relief_color = [[color shadowWithLevel: 0.4] retain]; + } +} static void ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, @@ -3486,40 +3517,13 @@ ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, of some sides not being drawn, and because the rect will be filled. -------------------------------------------------------------------------- */ { - static NSColor *baseCol, *lightCol, *darkCol; - NSColor *newBaseCol; NSRect inner; - NSBezierPath *p; - - baseCol = nil; - lightCol = nil; - newBaseCol = nil; - p = nil; + NSBezierPath *p = nil; NSTRACE ("ns_draw_relief"); /* set up colors */ - - if (s->face->use_box_color_for_shadows_p) - newBaseCol = [NSColor colorWithUnsignedLong: s->face->box_color]; - else - newBaseCol = [NSColor colorWithUnsignedLong: s->face->background]; - - if (s->hl == DRAW_CURSOR) - newBaseCol = FRAME_CURSOR_COLOR (s->f); - - if (newBaseCol == nil) - newBaseCol = [NSColor grayColor]; - - if (newBaseCol != baseCol) /* TODO: better check */ - { - [baseCol release]; - baseCol = [newBaseCol retain]; - [lightCol release]; - lightCol = [[baseCol highlightWithLevel: 0.4] retain]; - [darkCol release]; - darkCol = [[baseCol shadowWithLevel: 0.4] retain]; - } + ns_setup_relief_colors (s); /* Calculate the inner rectangle. */ inner = outer; @@ -3542,7 +3546,9 @@ ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, if (bottom_p) inner.size.height -= hthickness; - [(raised_p ? lightCol : darkCol) set]; + struct ns_output *di = FRAME_OUTPUT_DATA (s->f); + + [(raised_p ? di->light_relief_color : di->dark_relief_color) set]; if (top_p || left_p) { @@ -3564,7 +3570,7 @@ ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, [p fill]; } - [(raised_p ? darkCol : lightCol) set]; + [(raised_p ? di->dark_relief_color : di->light_relief_color) set]; if (bottom_p || right_p) { @@ -3626,7 +3632,7 @@ ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, NSMaxY (outer) - 0.5)]; } - [darkCol set]; + [di->dark_relief_color set]; [p stroke]; if (vthickness > 1 && hthickness > 1) @@ -3987,42 +3993,104 @@ ns_dumpglyphs_image (struct glyph_string *s, NSRect r) static void -ns_dumpglyphs_stretch (struct glyph_string *s) +ns_draw_stretch_glyph_string (struct glyph_string *s) { - NSRect glyphRect; - struct face *face = s->face; - NSColor *fgCol, *bgCol; + struct face *face; - if (!s->background_filled_p) + if (s->hl == DRAW_CURSOR + && !x_stretch_cursor_p) { + /* If `x-stretch-cursor' is nil, don't draw a block cursor as + wide as the stretch glyph. */ + int width, background_width = s->background_width; + int x = s->x; + + if (!s->row->reversed_p) + { + int left_x = window_box_left_offset (s->w, TEXT_AREA); + + if (x < left_x) + { + background_width -= left_x - x; + x = left_x; + } + } + else + { + /* In R2L rows, draw the cursor on the right edge of the + stretch glyph. */ + int right_x = window_box_right (s->w, TEXT_AREA); - face = s->face; + if (x + background_width > right_x) + background_width -= x - right_x; + x += background_width; + } - bgCol = [NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)]; - fgCol = [NSColor colorWithUnsignedLong:NS_FACE_FOREGROUND (face)]; + width = min (FRAME_COLUMN_WIDTH (s->f), background_width); + if (s->row->reversed_p) + x -= width; if (s->hl == DRAW_CURSOR) + [FRAME_CURSOR_COLOR (s->f) set]; + else + [[NSColor colorWithUnsignedLong: s->face->foreground] set]; + + NSRectFill (NSMakeRect (x, s->y, width, s->height)); + + /* Clear rest using the GC of the original non-cursor face. */ + if (width < background_width) { - fgCol = bgCol; - bgCol = FRAME_CURSOR_COLOR (s->f); - } + int y = s->y; + int w = background_width - width, h = s->height; - glyphRect = NSMakeRect (s->x, s->y, s->background_width, s->height); + if (!s->row->reversed_p) + x += width; + else + x = s->x; - [bgCol set]; + if (s->row->mouse_face_p + && cursor_in_mouse_face_p (s->w)) + { + face = FACE_FROM_ID_OR_NULL (s->f, + MOUSE_HL_INFO (s->f)->mouse_face_face_id); - NSRectFill (glyphRect); + if (!s->face) + face = FACE_FROM_ID (s->f, MOUSE_FACE_ID); + prepare_face_for_display (s->f, face); - /* Draw overlining, etc. on the stretch glyph (or the part of - the stretch glyph after the cursor). If the glyph has a box, - then decorations will be drawn after drawing the box in - ns_draw_glyph_string, in order to prevent them from being - overwritten by the box. */ - if (s->face->box == FACE_NO_BOX) - ns_draw_text_decoration (s, face, fgCol, NSWidth (glyphRect), - NSMinX (glyphRect)); + [[NSColor colorWithUnsignedLong: face->background] set]; + } + else + [[NSColor colorWithUnsignedLong: s->face->background] set]; + NSRectFill (NSMakeRect (x, y, w, h)); + } + } + else if (!s->background_filled_p) + { + int background_width = s->background_width; + int x = s->x, text_left_x = window_box_left (s->w, TEXT_AREA); - s->background_filled_p = 1; + /* Don't draw into left fringe or scrollbar area except for + header line and mode line. */ + if (s->area == TEXT_AREA + && x < text_left_x && !s->row->mode_line_p) + { + background_width -= text_left_x - x; + x = text_left_x; + } + + if (!s->row->stipple_p) + s->row->stipple_p = s->stippled_p; + + if (background_width > 0) + { + if (s->hl == DRAW_CURSOR) + [FRAME_CURSOR_COLOR (s->f) set]; + else + [[NSColor colorWithUnsignedLong: s->face->background] set]; + + NSRectFill (NSMakeRect (x, s->y, background_width, s->height)); + } } } @@ -4173,6 +4241,8 @@ ns_draw_glyphless_glyph_string_foreground (struct glyph_string *s) ? CHAR_TABLE_REF (Vglyphless_char_display, glyph->u.glyphless.ch) : XCHAR_TABLE (Vglyphless_char_display)->extras[0]); + if (CONSP (acronym)) + acronym = XCAR (acronym); if (STRINGP (acronym)) str = SSDATA (acronym); } @@ -4244,13 +4314,9 @@ ns_draw_glyph_string (struct glyph_string *s) n = ns_get_glyph_string_clip_rect (s->next, r); ns_focus (s->f, r, n); if (next->first_glyph->type != STRETCH_GLYPH) - { - ns_maybe_dumpglyphs_background (s->next, 1); - } - else - { - ns_dumpglyphs_stretch (s->next); - } + ns_maybe_dumpglyphs_background (s->next, 1); + else + ns_draw_stretch_glyph_string (s->next); ns_unfocus (s->f); next->num_clips = 0; } @@ -4290,7 +4356,7 @@ ns_draw_glyph_string (struct glyph_string *s) break; case STRETCH_GLYPH: - ns_dumpglyphs_stretch (s); + ns_draw_stretch_glyph_string (s); break; case CHAR_GLYPH: @@ -9924,6 +9990,16 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) return ret; } +- (void) mark +{ + if (window) + { + Lisp_Object win; + XSETWINDOW (win, window); + mark_object (win); + } +} + - (void)resetCursorRects { @@ -10665,6 +10741,26 @@ ns_xlfd_to_fontname (const char *xlfd) return ret; } +void +mark_nsterm (void) +{ + NSTRACE ("mark_nsterm"); + Lisp_Object tail, frame; + FOR_EACH_FRAME (tail, frame) + { + struct frame *f = XFRAME (frame); + if (FRAME_NS_P (f)) + { + NSArray *subviews = [[FRAME_NS_VIEW (f) superview] subviews]; + for (int i = [subviews count] - 1; i >= 0; --i) + { + id scroller = [subviews objectAtIndex: i]; + if ([scroller isKindOfClass: [EmacsScroller class]]) + [scroller mark]; + } + } + } +} void syms_of_nsterm (void) @@ -10883,7 +10979,7 @@ This variable is ignored on macOS < 10.7 and GNUstep. Default is t. */); It is called with three arguments FRAME, X, and Y, whenever the user moves the mouse over an Emacs frame as part of a drag-and-drop operation. FRAME is the frame the mouse is on top of, and X and Y are -the frame-relative positions of the mouse in the X and Y axises +the frame-relative positions of the mouse in the X and Y axes respectively. */); Vns_drag_motion_function = Qns_handle_drag_motion; diff --git a/src/pdumper.c b/src/pdumper.c index 50ae4f85e7e..903298f17d2 100644 --- a/src/pdumper.c +++ b/src/pdumper.c @@ -1210,8 +1210,8 @@ dump_queue_find_score_of_one_weight_queue (struct dump_queue *dump_queue, static Lisp_Object dump_queue_dequeue (struct dump_queue *dump_queue, dump_off basis) { - eassert (EQ (Fhash_table_count (dump_queue->sequence_numbers), - Fhash_table_count (dump_queue->link_weights))); + eassert (BASE_EQ (Fhash_table_count (dump_queue->sequence_numbers), + Fhash_table_count (dump_queue->link_weights))); eassert (XFIXNUM (Fhash_table_count (dump_queue->sequence_numbers)) <= (dump_tailq_length (&dump_queue->fancy_weight_objects) @@ -2701,7 +2701,7 @@ dump_hash_table (struct dump_context *ctx, static dump_off dump_buffer (struct dump_context *ctx, const struct buffer *in_buffer) { -#if CHECK_STRUCTS && !defined HASH_buffer_F8FE65D42F +#if CHECK_STRUCTS && !defined HASH_buffer_AA373AEE10 # error "buffer changed. See CHECK_STRUCTS comment in config.h." #endif struct buffer munged_buffer = *in_buffer; @@ -2764,6 +2764,7 @@ dump_buffer (struct dump_context *ctx, const struct buffer *in_buffer) DUMP_FIELD_COPY (out, buffer, own_text.end_unchanged); DUMP_FIELD_COPY (out, buffer, own_text.unchanged_modified); DUMP_FIELD_COPY (out, buffer, own_text.overlay_unchanged_modified); + DUMP_FIELD_COPY (out, buffer, own_text.chars_unchanged_modified); if (buffer->own_text.intervals) dump_field_fixup_later (ctx, out, buffer, &buffer->own_text.intervals); dump_field_lv_rawptr (ctx, out, buffer, &buffer->own_text.markers, @@ -2813,6 +2814,7 @@ dump_buffer (struct dump_context *ctx, const struct buffer *in_buffer) DUMP_FIELD_COPY (out, buffer, prevent_redisplay_optimizations_p); DUMP_FIELD_COPY (out, buffer, clip_changed); DUMP_FIELD_COPY (out, buffer, inhibit_buffer_hooks); + DUMP_FIELD_COPY (out, buffer, long_line_optimizations_p); dump_field_lv_rawptr (ctx, out, buffer, &buffer->overlays_before, Lisp_Vectorlike, WEIGHT_NORMAL); @@ -2909,6 +2911,9 @@ static dump_off dump_native_comp_unit (struct dump_context *ctx, struct Lisp_Native_Comp_Unit *comp_u) { + if (!CONSP (comp_u->file)) + error ("Trying to dump non fixed-up eln file"); + /* Have function documentation always lazy loaded to optimize load-time. */ comp_u->data_fdoc_v = Qnil; START_DUMP_PVEC (ctx, &comp_u->header, struct Lisp_Native_Comp_Unit, out); @@ -4039,6 +4044,8 @@ types. */) if (!NILP (XCDR (Fall_threads ()))) error ("No other Lisp threads can be running when this function is called"); + check_pure_size (); + /* Clear out any detritus in memory. */ do { diff --git a/src/pgtkfns.c b/src/pgtkfns.c index 294bdb37917..beaf28f69d9 100644 --- a/src/pgtkfns.c +++ b/src/pgtkfns.c @@ -613,7 +613,7 @@ pgtk_set_icon_type (struct frame *f, Lisp_Object arg, Lisp_Object oldval) if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!STRINGP (oldval) && NILP (oldval) == NILP (arg)) @@ -643,7 +643,7 @@ pgtk_set_icon_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval) if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!NILP (arg) || NILP (oldval)) @@ -991,6 +991,7 @@ frame_parm_handler pgtk_frame_parm_handlers[] = pgtk_set_override_redirect, gui_set_no_special_glyphs, pgtk_set_alpha_background, + NULL, }; @@ -2043,7 +2044,7 @@ use `(length \(display-monitor-attributes-list TERMINAL))' instead. */) DEFUN ("x-display-mm-height", Fx_display_mm_height, Sx_display_mm_height, 0, 1, 0, - doc: /* Return the height in millimeters of the the display TERMINAL. + doc: /* Return the height in millimeters of the display TERMINAL. The optional argument TERMINAL specifies which display to ask about. TERMINAL should be a terminal object, a frame or a display name (a string). If omitted or nil, that stands for the selected frame's display. @@ -2084,7 +2085,7 @@ for each physical monitor, use `display-monitor-attributes-list'. */) DEFUN ("x-display-mm-width", Fx_display_mm_width, Sx_display_mm_width, 0, 1, 0, - doc: /* Return the width in millimeters of the the display TERMINAL. + doc: /* Return the width in millimeters of the display TERMINAL. The optional argument TERMINAL specifies which display to ask about. TERMINAL should be a terminal object, a frame or a display name (a string). If omitted or nil, that stands for the selected frame's display. @@ -2125,7 +2126,7 @@ for each physical monitor, use `display-monitor-attributes-list'. */) DEFUN ("x-display-backing-store", Fx_display_backing_store, Sx_display_backing_store, 0, 1, 0, - doc: /* Return an indication of whether the the display TERMINAL does backing store. + doc: /* Return an indication of whether the display TERMINAL does backing store. The value may be `buffered', `retained', or `non-retained'. The optional argument TERMINAL specifies which display to ask about. TERMINAL should be a terminal object, a frame or a display name (a string). @@ -2138,7 +2139,7 @@ If omitted or nil, that stands for the selected frame's display. */) DEFUN ("x-display-visual-class", Fx_display_visual_class, Sx_display_visual_class, 0, 1, 0, - doc: /* Return the visual class of the the display TERMINAL. + doc: /* Return the visual class of the display TERMINAL. The value is one of the symbols `static-gray', `gray-scale', `static-color', `pseudo-color', `true-color', or `direct-color'. @@ -3130,6 +3131,10 @@ Text larger than the specified size is clipped. */) int old_windows_or_buffers_changed = windows_or_buffers_changed; specpdl_ref count = SPECPDL_INDEX (); Lisp_Object window, size, tip_buf; + bool displayed; +#ifdef ENABLE_CHECKING + struct glyph_row *row, *end; +#endif AUTO_STRING (tip, " *tip*"); specbind (Qinhibit_redisplay, Qt); @@ -3334,7 +3339,26 @@ Text larger than the specified size is clipped. */) clear_glyph_matrix (w->desired_matrix); clear_glyph_matrix (w->current_matrix); SET_TEXT_POS (pos, BEGV, BEGV_BYTE); - try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + row = w->desired_matrix->rows; + end = w->desired_matrix->rows + w->desired_matrix->nrows; + + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + + eassert (row < end && row->ends_at_zv_p); +#endif + } + /* Calculate size of tooltip window. */ size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, make_fixnum (w->pixel_height), Qnil, @@ -3924,7 +3948,7 @@ syms_of_pgtkfns (void) DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, doc: /* SKIP: real doc in xfns.c. */); - Vx_max_tooltip_size = Fcons (make_fixnum (80), make_fixnum (40)); + Vx_max_tooltip_size = Qnil; DEFSYM (Qmono, "mono"); DEFSYM (Qassq_delete_all, "assq-delete-all"); diff --git a/src/pgtkmenu.c b/src/pgtkmenu.c index 2eabf6ac1bc..d147f4b4168 100644 --- a/src/pgtkmenu.c +++ b/src/pgtkmenu.c @@ -289,8 +289,6 @@ set_frame_menubar (struct frame *f, bool deep_p) /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ - if (!NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); diff --git a/src/pgtkselect.c b/src/pgtkselect.c index 76901b9eb1d..212bbd56aa4 100644 --- a/src/pgtkselect.c +++ b/src/pgtkselect.c @@ -17,16 +17,6 @@ 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/>. */ -/* FIXME: this file needs a major rewrite to replace the use of GTK's - own high-level GtkClipboard API with the GDK selection API: - - https://developer-old.gnome.org/gdk3/stable/gdk3-Selections.html - - That way, most of the code can be shared with X, and non-text - targets along with drag-and-drop can be supported. GDK implements - selections according to the ICCCM, as on X, but its selection API - will work on any supported window system. */ - /* This should be the first include, as it may set up #defines affecting interpretation of even the system includes. */ #include <config.h> @@ -35,21 +25,37 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "pgtkterm.h" #include "termhooks.h" #include "keyboard.h" -#include "pgtkselect.h" -#include <gdk/gdk.h> - -static GQuark quark_primary_data = 0; -static GQuark quark_primary_size = 0; -static GQuark quark_secondary_data = 0; -static GQuark quark_secondary_size = 0; -static GQuark quark_clipboard_data = 0; -static GQuark quark_clipboard_size = 0; - -/* ========================================================================== - - Internal utility functions - - ========================================================================== */ +#include "atimer.h" +#include "blockinput.h" + +/* This file deliberately does not implement INCR, since it adds a + bunch of extra code for no real gain, as PGTK isn't supposed to + support X11 anyway. */ + +/* Advance declaration of structs. */ +struct selection_data; +struct prop_location; + +static void pgtk_decline_selection_request (struct selection_input_event *); +static bool pgtk_convert_selection (Lisp_Object, Lisp_Object, GdkAtom, bool, + struct pgtk_display_info *); +static bool waiting_for_other_props_on_window (GdkDisplay *, GdkWindow *); +#if 0 +static struct prop_location *expect_property_change (GdkDisplay *, GdkWindow *, + GdkAtom, int); +#endif +static void unexpect_property_change (struct prop_location *); +static void wait_for_property_change (struct prop_location *); +static Lisp_Object pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *, + GdkWindow *, GdkAtom, + Lisp_Object, GdkAtom, bool); +static Lisp_Object selection_data_to_lisp_data (struct pgtk_display_info *, + const unsigned char *, + ptrdiff_t, GdkAtom, int); +static void lisp_data_to_selection_data (struct pgtk_display_info *, Lisp_Object, + struct selection_data *); +static Lisp_Object pgtk_get_local_selection (Lisp_Object, Lisp_Object, + bool, struct pgtk_display_info *); /* From a Lisp_Object, return a suitable frame for selection operations. OBJECT may be a frame, a terminal object, or nil @@ -98,397 +104,1763 @@ frame_for_pgtk_selection (Lisp_Object object) return NULL; } -static GtkClipboard * -symbol_to_gtk_clipboard (GtkWidget * widget, Lisp_Object symbol) +#define LOCAL_SELECTION(selection_symbol, dpyinfo) \ + assq_no_quit (selection_symbol, dpyinfo->terminal->Vselection_alist) + +static GdkAtom +symbol_to_gdk_atom (Lisp_Object sym) { - GdkAtom atom; + if (NILP (sym)) + return GDK_NONE; - CHECK_SYMBOL (symbol); - if (NILP (symbol)) - { - atom = GDK_SELECTION_PRIMARY; - } - else if (EQ (symbol, QCLIPBOARD)) + if (EQ (sym, QPRIMARY)) + return GDK_SELECTION_PRIMARY; + if (EQ (sym, QSECONDARY)) + return GDK_SELECTION_SECONDARY; + if (EQ (sym, QCLIPBOARD)) + return GDK_SELECTION_CLIPBOARD; + + if (!SYMBOLP (sym)) + emacs_abort (); + + return gdk_atom_intern (SSDATA (SYMBOL_NAME (sym)), FALSE); +} + +static Lisp_Object +gdk_atom_to_symbol (GdkAtom atom) +{ + return intern (gdk_atom_name (atom)); +} + + + +/* Do protocol to assert ourself as a selection owner. + FRAME shall be the owner; it must be a valid GDK frame. + Update the Vselection_alist so that we can reply to later requests for + our selection. */ + +static void +pgtk_own_selection (Lisp_Object selection_name, Lisp_Object selection_value, + Lisp_Object frame) +{ + struct frame *f = XFRAME (frame); + struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + guint32 timestamp = gtk_get_current_event_time (); + GdkAtom selection_atom = symbol_to_gdk_atom (selection_name); + Lisp_Object targets; + ptrdiff_t i, ntargets; + GtkTargetEntry *gtargets; + + if (timestamp == GDK_CURRENT_TIME) + timestamp = dpyinfo->last_user_time; + + /* Assert ownership over the selection. Ideally we would use only + the GDK selection API for this, but it just doesn't work on + Wayland. */ + + if (!gdk_selection_owner_set_for_display (dpyinfo->display, + FRAME_GDK_WINDOW (f), + selection_atom, + timestamp, TRUE)) + signal_error ("Could not assert ownership over selection", selection_name); + + /* Update the local cache */ + { + Lisp_Object selection_data; + Lisp_Object prev_value; + + selection_data = list4 (selection_name, selection_value, + INT_TO_INTEGER (timestamp), frame); + prev_value = LOCAL_SELECTION (selection_name, dpyinfo); + + tset_selection_alist + (dpyinfo->terminal, + Fcons (selection_data, dpyinfo->terminal->Vselection_alist)); + + /* If we already owned the selection, remove the old selection + data. Don't use Fdelq as that may quit. */ + if (!NILP (prev_value)) + { + /* We know it's not the CAR, so it's easy. */ + Lisp_Object rest = dpyinfo->terminal->Vselection_alist; + for (; CONSP (rest); rest = XCDR (rest)) + if (EQ (prev_value, Fcar (XCDR (rest)))) + { + XSETCDR (rest, XCDR (XCDR (rest))); + break; + } + } + } + + /* Announce the targets to the display server. This isn't required + on X, but is on Wayland. */ + + targets = pgtk_get_local_selection (selection_name, QTARGETS, + true, dpyinfo); + + /* GC must not happen inside this segment. */ + block_input (); + gtk_selection_clear_targets (FRAME_GTK_WIDGET (f), selection_atom); + + if (VECTORP (targets)) { - atom = GDK_SELECTION_CLIPBOARD; + gtargets = xzalloc (sizeof *gtargets * ASIZE (targets)); + ntargets = 0; + + for (i = 0; i < ASIZE (targets); ++i) + { + if (SYMBOLP (AREF (targets, i))) + gtargets[ntargets++].target + = SSDATA (SYMBOL_NAME (AREF (targets, i))); + } + + gtk_selection_add_targets (FRAME_GTK_WIDGET (f), + selection_atom, gtargets, + ntargets); + + xfree (gtargets); } - else if (EQ (symbol, QPRIMARY)) + unblock_input (); +} + +static Lisp_Object +pgtk_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type, + bool local_request, struct pgtk_display_info *dpyinfo) +{ + Lisp_Object local_value, tem; + Lisp_Object handler_fn, value, check; + + local_value = LOCAL_SELECTION (selection_symbol, dpyinfo); + + if (NILP (local_value)) return Qnil; + + /* TIMESTAMP is a special case. */ + if (EQ (target_type, QTIMESTAMP)) { - atom = GDK_SELECTION_PRIMARY; + handler_fn = Qnil; + value = XCAR (XCDR (XCDR (local_value))); } - else if (EQ (symbol, QSECONDARY)) + else { - atom = GDK_SELECTION_SECONDARY; + /* Don't allow a quit within the converter. + When the user types C-g, he would be surprised + if by luck it came during a converter. */ + specpdl_ref count = SPECPDL_INDEX (); + specbind (Qinhibit_quit, Qt); + + CHECK_SYMBOL (target_type); + handler_fn = Fcdr (Fassq (target_type, Vselection_converter_alist)); + + if (CONSP (handler_fn)) + handler_fn = XCDR (handler_fn); + + tem = XCAR (XCDR (local_value)); + + if (STRINGP (tem)) + { + local_value = Fget_text_property (make_fixnum (0), + target_type, tem); + + if (!NILP (local_value)) + tem = local_value; + } + + if (!NILP (handler_fn)) + value = call3 (handler_fn, selection_symbol, + (local_request + ? Qnil + : target_type), + tem); + else + value = Qnil; + value = unbind_to (count, value); } - else if (EQ (symbol, Qt)) + + /* Make sure this value is of a type that we could transmit + to another client. */ + + check = value; + if (CONSP (value) + && SYMBOLP (XCAR (value))) + check = XCDR (value); + + if (STRINGP (check) + || VECTORP (check) + || SYMBOLP (check) + || INTEGERP (check) + || NILP (value)) + return value; + /* Check for a value that CONS_TO_INTEGER could handle. */ + else if (CONSP (check) + && INTEGERP (XCAR (check)) + && (INTEGERP (XCDR (check)) + || + (CONSP (XCDR (check)) + && INTEGERP (XCAR (XCDR (check))) + && NILP (XCDR (XCDR (check)))))) + return value; + + signal_error ("Invalid data returned by selection-conversion function", + list2 (handler_fn, value)); +} + +static void +pgtk_decline_selection_request (struct selection_input_event *event) +{ + gdk_selection_send_notify (SELECTION_EVENT_REQUESTOR (event), + SELECTION_EVENT_SELECTION (event), + SELECTION_EVENT_TARGET (event), + GDK_NONE, SELECTION_EVENT_TIME (event)); +} + +struct selection_data +{ + unsigned char *data; + ptrdiff_t size; + int format; + GdkAtom type; + bool nofree; + GdkAtom property; + + /* This can be set to non-NULL during x_reply_selection_request, if + the selection is waiting for an INCR transfer to complete. Don't + free these; that's done by unexpect_property_change. */ + struct prop_location *wait_object; + struct selection_data *next; +}; + +struct pgtk_selection_request +{ + /* The last element in this stack. */ + struct pgtk_selection_request *last; + + /* Its display info. */ + struct pgtk_display_info *dpyinfo; + + /* Its selection input event. */ + struct selection_input_event *request; + + /* Linked list of the above (in support of MULTIPLE targets). */ + struct selection_data *converted_selections; + + /* "Data" to send a requestor for a failed MULTIPLE subtarget. */ + GdkAtom conversion_fail_tag; + + /* Whether or not conversion was successful. */ + bool converted; +}; + +/* Stack of selections currently being processed. + NULL if all requests have been fully processed. */ + +struct pgtk_selection_request *selection_request_stack; + +static void +pgtk_push_current_selection_request (struct selection_input_event *se, + struct pgtk_display_info *dpyinfo) +{ + struct pgtk_selection_request *frame; + + frame = xmalloc (sizeof *frame); + frame->converted = false; + frame->last = selection_request_stack; + frame->request = se; + frame->dpyinfo = dpyinfo; + frame->converted_selections = NULL; + frame->conversion_fail_tag = GDK_NONE; + + selection_request_stack = frame; +} + +static void +pgtk_pop_current_selection_request (void) +{ + struct pgtk_selection_request *tem; + + tem = selection_request_stack; + selection_request_stack = selection_request_stack->last; + + xfree (tem); +} + +/* Used as an unwind-protect clause so that, if a selection-converter signals + an error, we tell the requestor that we were unable to do what they wanted + before we throw to top-level or go into the debugger or whatever. */ + +static void +pgtk_selection_request_lisp_error (void) +{ + struct selection_data *cs, *next; + struct pgtk_selection_request *frame; + + frame = selection_request_stack; + + for (cs = frame->converted_selections; cs; cs = next) { - atom = GDK_SELECTION_SECONDARY; + next = cs->next; + if (! cs->nofree && cs->data) + xfree (cs->data); + xfree (cs); } - else + frame->converted_selections = NULL; + + if (!frame->converted && frame->dpyinfo->display) + pgtk_decline_selection_request (frame->request); +} + +/* This stuff is so that INCR selections are reentrant (that is, so we can + be servicing multiple INCR selection requests simultaneously.) I haven't + actually tested that yet. */ + +/* Keep a list of the property changes that are awaited. */ + +struct prop_location +{ + int identifier; + GdkDisplay *display; + GdkWindow *window; + GdkAtom property; + int desired_state; + bool arrived; + struct prop_location *next; +}; + +#if 0 + +static int prop_location_identifier; + +#endif + +static Lisp_Object property_change_reply; + +static struct prop_location *property_change_reply_object; + +static struct prop_location *property_change_wait_list; + +static void +set_property_change_object (struct prop_location *location) +{ + /* Input must be blocked so we don't get the event before we set these. */ + if (!input_blocked_p ()) + emacs_abort (); + + XSETCAR (property_change_reply, Qnil); + property_change_reply_object = location; +} + + +/* Send the reply to a selection request event EVENT. */ + +static void +pgtk_reply_selection_request (struct selection_input_event *event, + struct pgtk_display_info *dpyinfo) +{ + GdkDisplay *display = SELECTION_EVENT_DISPLAY (event); + GdkWindow *window = SELECTION_EVENT_REQUESTOR (event); + ptrdiff_t bytes_remaining; + struct selection_data *cs; + struct pgtk_selection_request *frame; + + frame = selection_request_stack; + + block_input (); + /* Loop over converted selections, storing them in the requested + properties. If data is large, only store the first N bytes + (section 2.7.2 of ICCCM). Note that we store the data for a + MULTIPLE request in the opposite order; the ICCM says only that + the conversion itself must be done in the same order. */ + for (cs = frame->converted_selections; cs; cs = cs->next) { - atom = 0; - error ("Bad selection"); + if (cs->property == GDK_NONE) + continue; + + bytes_remaining = cs->size; + bytes_remaining *= cs->format >> 3; + + gdk_property_change (window, cs->property, + cs->type, cs->format, + GDK_PROP_MODE_APPEND, + cs->data, cs->size); } - return gtk_widget_get_clipboard (widget, atom); + /* Now issue the SelectionNotify event. */ + gdk_selection_send_notify (window, + SELECTION_EVENT_SELECTION (event), + SELECTION_EVENT_TARGET (event), + SELECTION_EVENT_PROPERTY (event), + SELECTION_EVENT_TIME (event)); + gdk_display_flush (display); + + /* Finish sending the rest of each of the INCR values. This should + be improved; there's a chance of deadlock if more than one + subtarget in a MULTIPLE selection requires an INCR transfer, and + the requestor and Emacs loop waiting on different transfers. */ + for (cs = frame->converted_selections; cs; cs = cs->next) + if (cs->wait_object) + { + int format_bytes = cs->format / 8; + + /* Must set this inside block_input (). unblock_input may read + events and setting property_change_reply in + wait_for_property_change is then too late. */ + set_property_change_object (cs->wait_object); + unblock_input (); + + bytes_remaining = cs->size; + bytes_remaining *= format_bytes; + + /* Wait for the requestor to ack by deleting the property. + This can run Lisp code (process handlers) or signal. */ + wait_for_property_change (cs->wait_object); + + /* Now write a zero-length chunk to the property to tell the + requestor that we're done. */ + block_input (); + if (! waiting_for_other_props_on_window (display, window)) + gdk_window_set_events (window, 0); + gdk_property_change (window, cs->property, cs->type, cs->format, + GDK_PROP_MODE_REPLACE, cs->data, 0); + } + + gdk_display_sync (display); + unblock_input (); } + + +/* Handle a SelectionRequest event EVENT. + This is called from keyboard.c when such an event is found in the queue. */ + static void -selection_type_to_quarks (GdkAtom type, GQuark * quark_data, - GQuark * quark_size) +pgtk_handle_selection_request (struct selection_input_event *event) { - if (type == GDK_SELECTION_PRIMARY) + guint32 local_selection_time; + struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event); + GdkAtom selection = SELECTION_EVENT_SELECTION (event); + Lisp_Object selection_symbol = gdk_atom_to_symbol (selection); + GdkAtom target = SELECTION_EVENT_TARGET (event); + Lisp_Object target_symbol = gdk_atom_to_symbol (target); + GdkAtom property = SELECTION_EVENT_PROPERTY (event); + Lisp_Object local_selection_data; + bool success = false; + specpdl_ref count = SPECPDL_INDEX (); + bool pushed; + Lisp_Object alias, tem; + + alias = Vpgtk_selection_alias_alist; + + FOR_EACH_TAIL_SAFE (alias) { - *quark_data = quark_primary_data; - *quark_size = quark_primary_size; + tem = Qnil; + + if (CONSP (alias)) + tem = XCAR (alias); + + if (CONSP (tem) + && EQ (XCAR (tem), selection_symbol) + && SYMBOLP (XCDR (tem))) + { + selection_symbol = XCDR (tem); + break; + } } - else if (type == GDK_SELECTION_SECONDARY) + + pushed = false; + + if (!dpyinfo) + goto DONE; + + local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo); + + /* Decline if we don't own any selections. */ + if (NILP (local_selection_data)) goto DONE; + + /* Decline requests issued prior to our acquiring the selection. */ + CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))), + guint32, local_selection_time); + if (SELECTION_EVENT_TIME (event) != GDK_CURRENT_TIME + && local_selection_time > SELECTION_EVENT_TIME (event)) + goto DONE; + + block_input (); + pushed = true; + pgtk_push_current_selection_request (event, dpyinfo); + record_unwind_protect_void (pgtk_pop_current_selection_request); + record_unwind_protect_void (pgtk_selection_request_lisp_error); + unblock_input (); + + if (EQ (target_symbol, QMULTIPLE)) { - *quark_data = quark_secondary_data; - *quark_size = quark_secondary_size; + /* For MULTIPLE targets, the event property names a list of atom + pairs; the first atom names a target and the second names a + non-GDK_NONE property. */ + GdkWindow *requestor = SELECTION_EVENT_REQUESTOR (event); + Lisp_Object multprop; + ptrdiff_t j, nselections; + struct selection_data cs; + + if (property == GDK_NONE) + goto DONE; + + multprop = pgtk_get_window_property_as_lisp_data (dpyinfo, + requestor, + property, + QMULTIPLE, + selection, + true); + + if (!VECTORP (multprop) || ASIZE (multprop) % 2) + goto DONE; + + nselections = ASIZE (multprop) / 2; + /* Perform conversions. This can signal. */ + for (j = 0; j < nselections; j++) + { + Lisp_Object subtarget = AREF (multprop, 2*j); + GdkAtom subproperty = symbol_to_gdk_atom (AREF (multprop, 2 * j + 1)); + bool subsuccess = false; + + if (subproperty != GDK_NONE) + subsuccess = pgtk_convert_selection (selection_symbol, subtarget, + subproperty, true, dpyinfo); + if (!subsuccess) + ASET (multprop, 2*j+1, Qnil); + } + /* Save conversion results */ + lisp_data_to_selection_data (dpyinfo, multprop, &cs); + gdk_property_change (requestor, property, + cs.type, cs.format, + GDK_PROP_MODE_REPLACE, + cs.data, cs.size); + success = true; } - else if (type == GDK_SELECTION_CLIPBOARD) + else { - *quark_data = quark_clipboard_data; - *quark_size = quark_clipboard_size; + if (property == GDK_NONE) + property = SELECTION_EVENT_TARGET (event); + + success = pgtk_convert_selection (selection_symbol, + target_symbol, property, + false, dpyinfo); } + + DONE: + + if (pushed) + selection_request_stack->converted = true; + + if (success) + pgtk_reply_selection_request (event, dpyinfo); else - /* FIXME: Is it safe to use 'error' here? */ - error ("Unknown selection type."); + pgtk_decline_selection_request (event); + + /* Run the `pgtk-sent-selection-functions' abnormal hook. */ + if (!NILP (Vpgtk_sent_selection_functions) + && !BASE_EQ (Vpgtk_sent_selection_functions, Qunbound)) + CALLN (Frun_hook_with_args, Qpgtk_sent_selection_functions, + selection_symbol, target_symbol, success ? Qt : Qnil); + + unbind_to (count, Qnil); } -static void -get_func (GtkClipboard * cb, GtkSelectionData * data, guint info, - gpointer user_data_or_owner) +/* Perform the requested selection conversion, and write the data to + the converted_selections linked list, where it can be accessed by + x_reply_selection_request. If FOR_MULTIPLE, write out + the data even if conversion fails, using conversion_fail_tag. + + Return true if (and only if) successful. */ + +static bool +pgtk_convert_selection (Lisp_Object selection_symbol, + Lisp_Object target_symbol, GdkAtom property, + bool for_multiple, struct pgtk_display_info *dpyinfo) { - GObject *obj = G_OBJECT (user_data_or_owner); - const char *str; - int size; - GQuark quark_data, quark_size; + Lisp_Object lisp_selection; + struct selection_data *cs; + struct pgtk_selection_request *frame; + + lisp_selection + = pgtk_get_local_selection (selection_symbol, target_symbol, + false, dpyinfo); + + frame = selection_request_stack; + + /* A nil return value means we can't perform the conversion. */ + if (NILP (lisp_selection) + || (CONSP (lisp_selection) && NILP (XCDR (lisp_selection)))) + { + if (for_multiple) + { + cs = xmalloc (sizeof *cs); + cs->data = ((unsigned char *) + &selection_request_stack->conversion_fail_tag); + cs->size = 1; + cs->format = 32; + cs->type = GDK_SELECTION_TYPE_ATOM; + cs->nofree = true; + cs->property = property; + cs->wait_object = NULL; + cs->next = frame->converted_selections; + frame->converted_selections = cs; + } - selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data, - &quark_size); + return false; + } - str = g_object_get_qdata (obj, quark_data); - size = GPOINTER_TO_SIZE (g_object_get_qdata (obj, quark_size)); - gtk_selection_data_set_text (data, str, size); + /* Otherwise, record the converted selection to binary. */ + cs = xmalloc (sizeof *cs); + cs->data = NULL; + cs->nofree = true; + cs->property = property; + cs->wait_object = NULL; + cs->next = frame->converted_selections; + frame->converted_selections = cs; + lisp_data_to_selection_data (dpyinfo, lisp_selection, cs); + return true; } + + +/* Handle a SelectionClear event EVENT, which indicates that some + client cleared out our previously asserted selection. + This is called from keyboard.c when such an event is found in the queue. */ + static void -clear_func (GtkClipboard * cb, gpointer user_data_or_owner) +pgtk_handle_selection_clear (struct selection_input_event *event) { - GObject *obj = G_OBJECT (user_data_or_owner); - GQuark quark_data, quark_size; + GdkAtom selection = SELECTION_EVENT_SELECTION (event); + guint32 changed_owner_time = SELECTION_EVENT_TIME (event); - selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data, - &quark_size); + Lisp_Object selection_symbol, local_selection_data; + guint32 local_selection_time; + struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event); + Lisp_Object Vselection_alist; - g_object_set_qdata (obj, quark_data, NULL); - g_object_set_qdata (obj, quark_size, 0); -} + if (!dpyinfo) return; + selection_symbol = gdk_atom_to_symbol (selection); + local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo); -/* ========================================================================== + /* Well, we already believe that we don't own it, so that's just fine. */ + if (NILP (local_selection_data)) return; - Functions used externally + CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))), + guint32, local_selection_time); - ========================================================================== */ + /* We have reasserted the selection since this SelectionClear was + generated, so we can disregard it. */ + if (changed_owner_time != GDK_CURRENT_TIME + && local_selection_time > changed_owner_time) + return; + + /* Otherwise, really clear. Don't use Fdelq as that may quit. */ + Vselection_alist = dpyinfo->terminal->Vselection_alist; + if (EQ (local_selection_data, CAR (Vselection_alist))) + Vselection_alist = XCDR (Vselection_alist); + else + { + Lisp_Object rest; + for (rest = Vselection_alist; CONSP (rest); rest = XCDR (rest)) + if (EQ (local_selection_data, CAR (XCDR (rest)))) + { + XSETCDR (rest, XCDR (XCDR (rest))); + break; + } + } + tset_selection_alist (dpyinfo->terminal, Vselection_alist); + + /* Run the `pgtk-lost-selection-functions' abnormal hook. */ + CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions, selection_symbol); + + redisplay_preserve_echo_area (20); +} void -pgtk_selection_init (void) +pgtk_handle_selection_event (struct selection_input_event *event) +{ + if (event->kind != SELECTION_REQUEST_EVENT) + pgtk_handle_selection_clear (event); + else + pgtk_handle_selection_request (event); +} + +/* Clear all selections that were made from frame F. + We do this when about to delete a frame. */ + +void +pgtk_clear_frame_selections (struct frame *f) +{ + Lisp_Object frame, rest, timestamp, symbol; + guint32 time; + struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + struct terminal *t = dpyinfo->terminal; + + XSETFRAME (frame, f); + + /* Delete elements from the beginning of Vselection_alist. */ + while (CONSP (t->Vselection_alist) + && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (t->Vselection_alist))))))) + { + symbol = Fcar (Fcar (t->Vselection_alist)); + + /* Run the `pgtk-lost-selection-functions' abnormal hook. */ + CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions, + symbol); + + timestamp = Fcar (Fcdr (Fcdr (Fcar (t->Vselection_alist)))); + CONS_TO_INTEGER (timestamp, guint32, time); + + /* On Wayland, GDK will still ask the (now non-existent) frame for + selection data, even though we no longer think the selection is + owned by us. Manually relinquish ownership of the selection. */ + gdk_selection_owner_set_for_display (dpyinfo->display, + NULL, + symbol_to_gdk_atom (symbol), + time, TRUE); + + tset_selection_alist (t, XCDR (t->Vselection_alist)); + } + + /* Delete elements after the beginning of Vselection_alist. */ + for (rest = t->Vselection_alist; CONSP (rest); rest = XCDR (rest)) + if (CONSP (XCDR (rest)) + && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (XCDR (rest)))))))) + { + symbol = XCAR (XCAR (XCDR (rest))); + CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions, + symbol); + + timestamp = XCAR (XCDR (XCDR (XCAR (XCDR (rest))))); + CONS_TO_INTEGER (timestamp, guint32, time); + + gdk_selection_owner_set_for_display (dpyinfo->display, + NULL, + symbol_to_gdk_atom (symbol), + time, TRUE); + + XSETCDR (rest, XCDR (XCDR (rest))); + break; + } +} + +/* True if any properties for DISPLAY and WINDOW + are on the list of what we are waiting for. */ + +static bool +waiting_for_other_props_on_window (GdkDisplay *display, GdkWindow *window) +{ + for (struct prop_location *p = property_change_wait_list; p; p = p->next) + if (p->display == display && p->window == window) + return true; + return false; +} + +/* Add an entry to the list of property changes we are waiting for. + DISPLAY, WINDOW, PROPERTY, STATE describe what we will wait for. + The return value is a number that uniquely identifies + this awaited property change. */ + +/* Currently unused -- uncomment later if we decide to implement INCR + transfer for X. */ + +#if 0 + +static struct prop_location * +expect_property_change (GdkDisplay *display, GdkWindow *window, + GdkAtom property, int state) +{ + struct prop_location *pl = xmalloc (sizeof *pl); + pl->identifier = ++prop_location_identifier; + pl->display = display; + pl->window = window; + pl->property = property; + pl->desired_state = state; + pl->next = property_change_wait_list; + pl->arrived = false; + property_change_wait_list = pl; + return pl; +} + +#endif + +/* Delete an entry from the list of property changes we are waiting for. + IDENTIFIER is the number that uniquely identifies the entry. */ + +static void +unexpect_property_change (struct prop_location *location) +{ + struct prop_location *prop, **pprev = &property_change_wait_list; + + for (prop = property_change_wait_list; prop; prop = *pprev) + { + if (prop == location) + { + *pprev = prop->next; + xfree (prop); + break; + } + else + pprev = &prop->next; + } +} + +/* Remove the property change expectation element for IDENTIFIER. */ + +static void +wait_for_property_change_unwind (void *loc) +{ + struct prop_location *location = loc; + + unexpect_property_change (location); + if (location == property_change_reply_object) + property_change_reply_object = 0; +} + +/* Actually wait for a property change. + IDENTIFIER should be the value that expect_property_change returned. */ + +static void +wait_for_property_change (struct prop_location *location) { - if (quark_primary_data == 0) + specpdl_ref count = SPECPDL_INDEX (); + + /* Make sure to do unexpect_property_change if we quit or err. */ + record_unwind_protect_ptr (wait_for_property_change_unwind, location); + + /* See comment in x_reply_selection_request about setting + property_change_reply. Do not do it here. */ + + /* If the event we are waiting for arrives beyond here, it will set + property_change_reply, because property_change_reply_object says so. */ + if (! location->arrived) { - quark_primary_data = g_quark_from_static_string ("pgtk-primary-data"); - quark_primary_size = g_quark_from_static_string ("pgtk-primary-size"); - quark_secondary_data = - g_quark_from_static_string ("pgtk-secondary-data"); - quark_secondary_size = - g_quark_from_static_string ("pgtk-secondary-size"); - quark_clipboard_data = - g_quark_from_static_string ("pgtk-clipboard-data"); - quark_clipboard_size = - g_quark_from_static_string ("pgtk-clipboard-size"); + intmax_t timeout = max (0, pgtk_selection_timeout); + intmax_t secs = timeout / 1000; + int nsecs = (timeout % 1000) * 1000000; + + wait_reading_process_output (secs, nsecs, 0, false, + property_change_reply, NULL, 0); + + if (NILP (XCAR (property_change_reply))) + error ("Timed out waiting for property-notify event"); } + + unbind_to (count, Qnil); } +/* Called from the big filter in response to a PropertyNotify + event. */ + void -pgtk_selection_lost (GtkWidget * widget, GdkEventSelection * event, - gpointer user_data) +pgtk_handle_property_notify (GdkEventProperty *event) { - GQuark quark_data, quark_size; + struct prop_location *rest; + GdkDisplay *dpy; - selection_type_to_quarks (event->selection, &quark_data, &quark_size); + dpy = gdk_window_get_display (event->window); - g_object_set_qdata (G_OBJECT (widget), quark_data, NULL); - g_object_set_qdata (G_OBJECT (widget), quark_size, 0); + for (rest = property_change_wait_list; rest; rest = rest->next) + { + if (!rest->arrived + && rest->property == event->atom + && rest->window == event->window + && rest->display == dpy + && rest->desired_state == event->state) + { + rest->arrived = true; + + /* If this is the one wait_for_property_change is waiting for, + tell it to wake up. */ + if (rest == property_change_reply_object) + XSETCAR (property_change_reply, Qt); + + return; + } + } } -static bool -pgtk_selection_usable (void) +static void +pgtk_display_selection_waiting_message (struct atimer *timer) { - if (pgtk_enable_selection_on_multi_display) - return true; + Lisp_Object val; - /* Gdk uses `gdk_display_get_default' when handling selections, so - selections don't work properly when Emacs is connected to - multiple displays. */ + val = build_string ("Waiting for reply from selection owner..."); + message3_nolog (val); +} - GdkDisplayManager *dpyman = gdk_display_manager_get (); - GSList *list = gdk_display_manager_list_displays (dpyman); - int len = g_slist_length (list); - g_slist_free (list); - return len < 2; +static void +pgtk_cancel_atimer (void *atimer) +{ + cancel_atimer (atimer); } -/* ========================================================================== + +/* Variables for communication with pgtk_handle_selection_notify. */ +static GdkAtom reading_which_selection; +static Lisp_Object reading_selection_reply; +static GdkWindow *reading_selection_window; - Lisp Defuns +/* Do protocol to read selection-data from the window server. + Converts this to Lisp data and returns it. + FRAME is the frame whose window shall request the selection. */ - ========================================================================== */ +static Lisp_Object +pgtk_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type, + Lisp_Object time_stamp, Lisp_Object frame) +{ + struct frame *f = XFRAME (frame); + struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + GdkWindow *requestor_window = FRAME_GDK_WINDOW (f); + guint32 requestor_time = dpyinfo->last_user_time; + GdkAtom selection_atom = symbol_to_gdk_atom (selection_symbol); + GdkAtom type_atom = (CONSP (target_type) + ? symbol_to_gdk_atom (XCAR (target_type)) + : symbol_to_gdk_atom (target_type)); + struct atimer *delayed_message; + struct timespec message_interval; + specpdl_ref count; + + count = SPECPDL_INDEX (); + + if (!FRAME_LIVE_P (f)) + return unbind_to (count, Qnil); + + if (!NILP (time_stamp)) + CONS_TO_INTEGER (time_stamp, guint32, requestor_time); + + block_input (); + /* Prepare to block until the reply has been read. */ + reading_selection_window = requestor_window; + reading_which_selection = selection_atom; + XSETCAR (reading_selection_reply, Qnil); + + gdk_selection_convert (requestor_window, selection_atom, + type_atom, requestor_time); + unblock_input (); + + /* It should not be necessary to stop handling selection requests + during this time. In fact, the SAVE_TARGETS mechanism requires + us to handle a clipboard manager's requests before it returns + GDK_SELECTION_NOTIFY. */ + + message_interval = make_timespec (1, 0); + delayed_message = start_atimer (ATIMER_RELATIVE, message_interval, + pgtk_display_selection_waiting_message, + NULL); + record_unwind_protect_ptr (pgtk_cancel_atimer, delayed_message); + + /* This allows quits. Also, don't wait forever. */ + intmax_t timeout = max (0, pgtk_selection_timeout); + intmax_t secs = timeout / 1000; + int nsecs = (timeout % 1000) * 1000000; + + wait_reading_process_output (secs, nsecs, 0, false, + reading_selection_reply, NULL, 0); + + if (NILP (XCAR (reading_selection_reply))) + error ("Timed out waiting for reply from selection owner"); + if (EQ (XCAR (reading_selection_reply), Qlambda)) + return unbind_to (count, Qnil); + + /* Otherwise, the selection is waiting for us on the requested property. */ + return unbind_to (count, + pgtk_get_window_property_as_lisp_data (dpyinfo, + requestor_window, + GDK_NONE, + target_type, + selection_atom, + false)); +} +/* Subroutines of pgtk_get_window_property_as_lisp_data */ -DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal, Spgtk_own_selection_internal, 2, 3, 0, - doc: /* Assert an X selection of type SELECTION and value VALUE. -SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'. -\(Those are literal upper-case symbol names, since that's what X expects.) -VALUE is typically a string, or a cons of two markers, but may be -anything that the functions on `selection-converter-alist' know about. +static ptrdiff_t +pgtk_size_for_format (gint format) +{ + switch (format) + { + case 8: + return sizeof (unsigned char); + case 16: + return sizeof (unsigned short); + case 32: + return sizeof (unsigned long); + + default: + emacs_abort (); + } +} -FRAME should be a frame that should own the selection. If omitted or -nil, it defaults to the selected frame. */) - (Lisp_Object selection, Lisp_Object value, Lisp_Object frame) +/* Use xfree, not g_free, to free the data obtained with this function. */ + +static void +pgtk_get_window_property (GdkWindow *window, unsigned char **data_ret, + ptrdiff_t *bytes_ret, GdkAtom *actual_type_ret, + int *actual_format_ret, unsigned long *actual_size_ret) { - Lisp_Object successful_p = Qnil; - Lisp_Object target_symbol, rest; - GtkClipboard *cb; - struct frame *f; - GQuark quark_data, quark_size; + gint length, actual_format; + unsigned char *data; + ptrdiff_t element_size; + void *xdata; + GdkAtom actual_type; + unsigned long i; + unsigned int *idata; + unsigned long *ldata; + + data = NULL; + + length = gdk_selection_property_get (window, &data, + &actual_type, + &actual_format); + + if (!data) + { + *data_ret = NULL; + *actual_type_ret = GDK_NONE; + *bytes_ret = 0; + *actual_format_ret = 8; + *actual_size_ret = 0; - check_window_system (NULL); + return; + } - if (!pgtk_selection_usable ()) - return Qnil; + if (actual_type == GDK_SELECTION_TYPE_ATOM + || actual_type == gdk_atom_intern_static_string ("ATOM_PAIR")) + { + /* GDK should not allow anything else. */ + eassert (actual_format == 32); - if (NILP (frame)) - frame = selected_frame; - if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame))) - error ("pgtk selection unavailable for this frame"); - f = XFRAME (frame); + length = length / sizeof (GdkAtom); + xdata = xmalloc (sizeof (GdkAtom) * length + 1); + memcpy (xdata, data, 1 + length * sizeof (GdkAtom)); - cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection); - selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data, - &quark_size); + g_free (data); + + *data_ret = xdata; + *actual_type_ret = actual_type; + *bytes_ret = length * sizeof (GdkAtom); + *actual_format_ret = 32; + *actual_size_ret = length; + + return; + } - /* We only support copy of text. */ - target_symbol = QTEXT; - if (STRINGP (value)) + element_size = pgtk_size_for_format (actual_format); + length = length / element_size; + + /* Add an extra byte on the end. GDK guarantees that it is + NULL. */ + xdata = xmalloc (1 + element_size * length); + memcpy (xdata, data, 1 + element_size * length); + + if (actual_format == 32 && LONG_WIDTH > 32) { - GtkTargetList *list; - GtkTargetEntry *targets; - gint n_targets; - GtkWidget *widget; + ldata = (typeof (ldata)) data; + idata = xdata; - list = gtk_target_list_new (NULL, 0); - gtk_target_list_add_text_targets (list, 0); + for (i = 0; i < length; ++i) + idata[i] = ldata[i]; - { - /* text/plain: Strings encoded by Gtk are not correctly decoded by Chromium(Wayland). */ - GdkAtom atom_text_plain = gdk_atom_intern ("text/plain", false); - gtk_target_list_remove (list, atom_text_plain); - } + /* There is always enough space in idata. */ + idata[length] = 0; + *bytes_ret = sizeof *idata * length; + } + else + /* I think GDK itself prevents element_size from exceeding the + length at which this computation fails. */ + *bytes_ret = element_size * length; + + /* Now free the original `data' allocated by GDK. */ + g_free (data); + + *data_ret = xdata; + *actual_type_ret = GDK_NONE; + *actual_size_ret = length; + *actual_format_ret = actual_format; + *actual_type_ret = actual_type; +} + +static Lisp_Object +pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *dpyinfo, + GdkWindow *window, GdkAtom property, + Lisp_Object target_type, GdkAtom selection_atom, + bool for_multiple) +{ + GdkAtom actual_type; + int actual_format; + unsigned long actual_size; + unsigned char *data = 0; + ptrdiff_t bytes = 0; + Lisp_Object val; + GdkDisplay *display = dpyinfo->display; + + pgtk_get_window_property (window, &data, &bytes, + &actual_type, &actual_format, + &actual_size); + + if (!data) + { + if (for_multiple) + return Qnil; + + if (gdk_selection_owner_get_for_display (display, selection_atom)) + { + AUTO_STRING (format, "Selection owner couldn't convert: %s"); + CALLN (Fmessage, format, + actual_type + ? list2 (target_type, + gdk_atom_to_symbol (actual_type)) + : target_type); + return Qnil; + } + else + { + AUTO_STRING (format, "No selection: %s"); + CALLN (Fmessage, format, + gdk_atom_to_symbol (selection_atom)); + return Qnil; + } + } + + if (!for_multiple && property != GDK_NONE) + gdk_property_delete (window, property); - targets = gtk_target_table_new_from_list (list, &n_targets); + /* It's been read. Now convert it to a lisp object in some semi-rational + manner. */ + val = selection_data_to_lisp_data (dpyinfo, data, bytes, + actual_type, actual_format); - int size = SBYTES (value); - gchar *str = xmalloc (size + 1); - memcpy (str, SSDATA (value), size); - str[size] = '\0'; + /* Use xfree, not g_free, because pgtk_get_window_property calls + xmalloc itself. */ + xfree (data); + return val; +} - widget = FRAME_GTK_WIDGET (f); - g_object_set_qdata_full (G_OBJECT (widget), quark_data, str, xfree); - g_object_set_qdata_full (G_OBJECT (widget), quark_size, - GSIZE_TO_POINTER (size), NULL); + + +/* These functions convert from the selection data read from the + server into something that we can use from Lisp, and vice versa. + + Type: Format: Size: Lisp Type: + ----- ------- ----- ----------- + * 8 * String + ATOM 32 1 Symbol + ATOM 32 > 1 Vector of Symbols + * 16 1 Integer + * 16 > 1 Vector of Integers + * 32 1 Integer + * 32 > 1 Vector of the above + + When converting an object to C, it may be of the form (SYMBOL + . <data>) where SYMBOL is what we should claim that the type is. + Format and representation are as above. + + Important: When format is 32, data should contain an array of int, + not an array of long as GDK returns. Unless TYPE is also + GDK_SELECTION_TYPE_ATOM, in which case data should be an array of + GdkAtom. This makes a difference when sizeof (long) != sizeof + (int). */ + +static Lisp_Object +selection_data_to_lisp_data (struct pgtk_display_info *dpyinfo, + const unsigned char *data, + ptrdiff_t size, GdkAtom type, int format) +{ + if (type == gdk_atom_intern_static_string ("NULL")) + return QNULL; + /* Convert any 8-bit data to a string, for compactness. */ + else if (format == 8) + { + Lisp_Object str, lispy_type; - if (gtk_clipboard_set_with_owner (cb, - targets, n_targets, - get_func, clear_func, - G_OBJECT (FRAME_GTK_WIDGET (f)))) + str = make_unibyte_string ((char *) data, size); + /* Indicate that this string is from foreign selection by a text + property `foreign-selection' so that the caller of + x-get-selection-internal (usually x-get-selection) can know + that the string must be decode. */ + if (type == gdk_atom_intern_static_string ("COMPOUND_TEXT")) + lispy_type = QCOMPOUND_TEXT; + else if (type == gdk_atom_intern_static_string ("UTF8_STRING")) + lispy_type = QUTF8_STRING; + else + lispy_type = QSTRING; + + Fput_text_property (make_fixnum (0), make_fixnum (size), + Qforeign_selection, lispy_type, str); + return str; + } + /* Convert a single atom to a Lisp_Symbol. Convert a set of atoms to + a vector of symbols. */ + else if (format == 32 + && (type == GDK_SELECTION_TYPE_ATOM + /* Treat ATOM_PAIR type similar to list of atoms. */ + || type == gdk_atom_intern_static_string ("ATOM_PAIR"))) + { + ptrdiff_t i; + GdkAtom *idata = (GdkAtom *) data; + + if (size == sizeof (GdkAtom)) + return gdk_atom_to_symbol (idata[0]); + else { - successful_p = Qt; + Lisp_Object v = make_nil_vector (size / sizeof (GdkAtom)); + + for (i = 0; i < size / sizeof (GdkAtom); i++) + ASET (v, i, gdk_atom_to_symbol (idata[i])); + return v; } - gtk_clipboard_set_can_store (cb, NULL, 0); + } + + /* Convert a single 16-bit number or a small 32-bit number to a Lisp_Int. + If the number is 32 bits and won't fit in a Lisp_Int, convert it + to a bignum. + + INTEGER is a signed type, CARDINAL is unsigned. + Assume any other types are unsigned as well. + */ + else if (format == 32 && size == sizeof (int)) + { + if (type == GDK_SELECTION_TYPE_INTEGER) + return INT_TO_INTEGER (((int *) data) [0]); + else + return INT_TO_INTEGER (((unsigned int *) data) [0]); + } + else if (format == 16 && size == sizeof (short)) + { + if (type == GDK_SELECTION_TYPE_INTEGER) + return make_fixnum (((short *) data) [0]); + else + return make_fixnum (((unsigned short *) data) [0]); + } + /* Convert any other kind of data to a vector of numbers, represented + as above (as an integer, or a cons of two 16 bit integers.) + */ + else if (format == 16) + { + ptrdiff_t i; + Lisp_Object v = make_uninit_vector (size / 2); + + if (type == GDK_SELECTION_TYPE_INTEGER) + { + for (i = 0; i < size / 2; i++) + { + short j = ((short *) data) [i]; + ASET (v, i, make_fixnum (j)); + } + } + else + { + for (i = 0; i < size / 2; i++) + { + unsigned short j = ((unsigned short *) data) [i]; + ASET (v, i, make_fixnum (j)); + } + } + return v; + } + else + { + ptrdiff_t i; + Lisp_Object v = make_nil_vector (size / sizeof (gint)); + + if (type == GDK_SELECTION_TYPE_INTEGER) + { + for (i = 0; i < size / sizeof (gint); i++) + { + int j = ((gint *) data) [i]; + ASET (v, i, INT_TO_INTEGER (j)); + } + } + else + { + for (i = 0; i < size / sizeof (gint); i++) + { + unsigned int j = ((unsigned int *) data) [i]; + ASET (v, i, INT_TO_INTEGER (j)); + } + } + return v; + } +} - gtk_target_table_free (targets, n_targets); - gtk_target_list_unref (list); +/* Convert OBJ to an X long value, and return it as unsigned long. + OBJ should be an integer or a cons representing an integer. + Treat values in the range X_LONG_MAX + 1 .. X_ULONG_MAX as X + unsigned long values: in theory these values are supposed to be + signed but in practice unsigned 32-bit data are communicated via X + selections and we need to support that. */ +static unsigned long +cons_to_gdk_long (Lisp_Object obj) +{ + if (G_MAXUINT32 <= INTMAX_MAX + || NILP (Fnatnump (CONSP (obj) ? XCAR (obj) : obj))) + return cons_to_signed (obj, 0, min (G_MAXUINT32, INTMAX_MAX)); + else + return cons_to_unsigned (obj, G_MAXUINT32); +} + +/* Use xfree, not XFree, to free the data obtained with this function. */ + +static void +lisp_data_to_selection_data (struct pgtk_display_info *dpyinfo, + Lisp_Object obj, struct selection_data *cs) +{ + Lisp_Object type = Qnil; + + eassert (cs != NULL); + cs->nofree = false; + + if (CONSP (obj) && SYMBOLP (XCAR (obj))) + { + type = XCAR (obj); + obj = XCDR (obj); + if (CONSP (obj) && NILP (XCDR (obj))) + obj = XCAR (obj); + } + + if (EQ (obj, QNULL) || (EQ (type, QNULL))) + { /* This is not the same as declining */ + cs->format = 32; + cs->size = 0; + cs->data = NULL; + type = QNULL; + } + else if (STRINGP (obj)) + { + if (SCHARS (obj) < SBYTES (obj)) + /* OBJ is a multibyte string containing a non-ASCII char. */ + signal_error ("Non-ASCII string must be encoded in advance", obj); + if (NILP (type)) + type = QSTRING; + cs->format = 8; + cs->size = SBYTES (obj); + cs->data = SDATA (obj); + cs->nofree = true; + } + else if (SYMBOLP (obj)) + { + void *data = xmalloc (sizeof (GdkAtom) + 1); + GdkAtom *x_atom_ptr = data; + cs->data = data; + cs->format = 32; + cs->size = 1; + cs->data[sizeof (GdkAtom)] = 0; + *x_atom_ptr = symbol_to_gdk_atom (obj); + if (NILP (type)) type = QATOM; + } + else if (RANGED_FIXNUMP (SHRT_MIN, obj, SHRT_MAX)) + { + void *data = xmalloc (sizeof (short) + 1); + short *short_ptr = data; + cs->data = data; + cs->format = 16; + cs->size = 1; + cs->data[sizeof (short)] = 0; + *short_ptr = XFIXNUM (obj); + if (NILP (type)) type = QINTEGER; + } + else if (INTEGERP (obj) + || (CONSP (obj) && INTEGERP (XCAR (obj)) + && (FIXNUMP (XCDR (obj)) + || (CONSP (XCDR (obj)) + && FIXNUMP (XCAR (XCDR (obj))))))) + { + void *data = xmalloc (sizeof (unsigned long) + 1); + unsigned long *x_long_ptr = data; + cs->data = data; + cs->format = 32; + cs->size = 1; + cs->data[sizeof (unsigned long)] = 0; + *x_long_ptr = cons_to_gdk_long (obj); + if (NILP (type)) type = QINTEGER; } + else if (VECTORP (obj)) + { + /* Lisp_Vectors may represent a set of ATOMs; + a set of 16 or 32 bit INTEGERs; + or a set of ATOM_PAIRs (represented as [[A1 A2] [A3 A4] ...] + */ + ptrdiff_t i; + ptrdiff_t size = ASIZE (obj); + + if (SYMBOLP (AREF (obj, 0))) + /* This vector is an ATOM set */ + { + void *data; + GdkAtom *x_atoms; + if (NILP (type)) type = QATOM; + for (i = 0; i < size; i++) + if (!SYMBOLP (AREF (obj, i))) + signal_error ("All elements of selection vector must have same type", obj); + + cs->data = data = xnmalloc (size, sizeof *x_atoms); + x_atoms = data; + cs->format = 32; + cs->size = size; + for (i = 0; i < size; i++) + x_atoms[i] = symbol_to_gdk_atom (AREF (obj, i)); + } + else + /* This vector is an INTEGER set, or something like it */ + { + int format = 16; + int data_size = sizeof (short); + void *data; + unsigned long *x_atoms; + short *shorts; + if (NILP (type)) type = QINTEGER; + for (i = 0; i < size; i++) + { + if (! RANGED_FIXNUMP (SHRT_MIN, AREF (obj, i), SHRT_MAX)) + { + /* Use sizeof (long) even if it is more than 32 bits. + See comment in x_get_window_property and + x_fill_property_data. */ + data_size = sizeof (long); + format = 32; + break; + } + } + cs->data = data = xnmalloc (size, data_size); + x_atoms = data; + shorts = data; + cs->format = format; + cs->size = size; + for (i = 0; i < size; i++) + { + if (format == 32) + x_atoms[i] = cons_to_gdk_long (AREF (obj, i)); + else + shorts[i] = XFIXNUM (AREF (obj, i)); + } + } + } + else + signal_error (/* Qselection_error */ "Unrecognized selection data", obj); - if (!BASE_EQ (Vpgtk_sent_selection_hooks, Qunbound)) + cs->type = symbol_to_gdk_atom (type); +} + +static Lisp_Object +clean_local_selection_data (Lisp_Object obj) +{ + if (CONSP (obj) + && INTEGERP (XCAR (obj)) + && CONSP (XCDR (obj)) + && FIXNUMP (XCAR (XCDR (obj))) + && NILP (XCDR (XCDR (obj)))) + obj = Fcons (XCAR (obj), XCDR (obj)); + + if (CONSP (obj) + && INTEGERP (XCAR (obj)) + && FIXNUMP (XCDR (obj))) { - /* FIXME: Use run-hook-with-args! */ - for (rest = Vpgtk_sent_selection_hooks; CONSP (rest); - rest = Fcdr (rest)) - call3 (Fcar (rest), selection, target_symbol, successful_p); + if (BASE_EQ (XCAR (obj), make_fixnum (0))) + return XCDR (obj); + if (BASE_EQ (XCAR (obj), make_fixnum (-1))) + return make_fixnum (- XFIXNUM (XCDR (obj))); } + if (VECTORP (obj)) + { + ptrdiff_t i; + ptrdiff_t size = ASIZE (obj); + Lisp_Object copy; + if (size == 1) + return clean_local_selection_data (AREF (obj, 0)); + copy = make_nil_vector (size); + for (i = 0; i < size; i++) + ASET (copy, i, clean_local_selection_data (AREF (obj, i))); + return copy; + } + return obj; +} + +DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal, + Spgtk_own_selection_internal, 2, 3, 0, + doc: /* Assert a selection of type SELECTION and value VALUE. +SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'. +\(Those are literal upper-case symbol names, since that's what GDK expects.) +VALUE is typically a string, or a cons of two markers, but may be +anything that the functions on `selection-converter-alist' know about. + +FRAME should be a frame that should own the selection. If omitted or +nil, it defaults to the selected frame. */) + (Lisp_Object selection, Lisp_Object value, Lisp_Object frame) +{ + if (NILP (frame)) frame = selected_frame; + if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame))) + error ("GDK selection unavailable for this frame"); + CHECK_SYMBOL (selection); + if (NILP (value)) error ("VALUE may not be nil"); + pgtk_own_selection (selection, value, frame); return value; } +/* Request the selection value from the owner. If we are the owner, + simply return our selection value. If we are not the owner, this + will block until all of the data has arrived. */ -DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal, - Spgtk_disown_selection_internal, 1, 2, 0, - doc: /* If we own the selection SELECTION, disown it. -Disowning it means there is no such selection. +DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal, + Spgtk_get_selection_internal, 2, 4, 0, + doc: /* Return text selected from some X window. +SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'. +\(Those are literal upper-case symbol names, since that's what X expects.) +TARGET-TYPE is the type of data desired, typically `STRING'. + +TIME-STAMP is the time to use in the XConvertSelection call for foreign +selections. If omitted, defaults to the time for the last event. TERMINAL should be a terminal object or a frame specifying the X server to query. If omitted or nil, that stands for the selected frame's display, or the first available X display. */) - (Lisp_Object selection, Lisp_Object terminal) + (Lisp_Object selection_symbol, Lisp_Object target_type, + Lisp_Object time_stamp, Lisp_Object terminal) { + Lisp_Object val = Qnil; + Lisp_Object maybe_alias; struct frame *f = frame_for_pgtk_selection (terminal); - GtkClipboard *cb; - if (!pgtk_selection_usable ()) - return Qnil; + CHECK_SYMBOL (selection_symbol); + CHECK_SYMBOL (target_type); + if (EQ (target_type, QMULTIPLE)) + error ("Retrieving MULTIPLE selections is currently unimplemented"); if (!f) - return Qnil; + error ("GDK selection unavailable for this frame"); - cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection); + /* Quitting inside this function is okay, so we don't have to use + FOR_EACH_TAIL_SAFE. */ + maybe_alias = Fassq (selection_symbol, Vpgtk_selection_alias_alist); - gtk_clipboard_clear (cb); + if (!NILP (maybe_alias)) + { + selection_symbol = XCDR (maybe_alias); + CHECK_SYMBOL (selection_symbol); + } - return Qt; + val = pgtk_get_local_selection (selection_symbol, target_type, true, + FRAME_DISPLAY_INFO (f)); + + if (NILP (val) && FRAME_LIVE_P (f)) + { + Lisp_Object frame; + XSETFRAME (frame, f); + return pgtk_get_foreign_selection (selection_symbol, target_type, + time_stamp, frame); + } + + if (CONSP (val) && SYMBOLP (XCAR (val))) + { + val = XCDR (val); + if (CONSP (val) && NILP (XCDR (val))) + val = XCAR (val); + } + return clean_local_selection_data (val); } +DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal, + Spgtk_disown_selection_internal, 1, 3, 0, + doc: /* If we own the selection SELECTION, disown it. +Disowning it means there is no such selection. -DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p, 0, 2, 0, - doc: /* Whether there is an owner for the given X selection. -SELECTION should be the name of the selection in question, typically -one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'. (X expects -these literal upper-case names.) The symbol nil is the same as -`PRIMARY', and t is the same as `SECONDARY'. +Sets the last-change time for the selection to TIME-OBJECT (by default +the time of the last event). TERMINAL should be a terminal object or a frame specifying the X server to query. If omitted or nil, that stands for the selected -frame's display, or the first available X display. - -On Nextstep, TERMINAL is unused. */) - (Lisp_Object selection, Lisp_Object terminal) +frame's display, or the first available X display. */) + (Lisp_Object selection, Lisp_Object time_object, Lisp_Object terminal) { + guint32 timestamp; + GdkAtom selection_atom; struct frame *f = frame_for_pgtk_selection (terminal); - GtkClipboard *cb; + struct pgtk_display_info *dpyinfo; - if (!pgtk_selection_usable ()) + if (!f) return Qnil; - if (!f) + dpyinfo = FRAME_DISPLAY_INFO (f); + CHECK_SYMBOL (selection); + + /* Don't disown the selection when we're not the owner. */ + if (NILP (LOCAL_SELECTION (selection, dpyinfo))) return Qnil; - cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection); + selection_atom = symbol_to_gdk_atom (selection); - return gtk_clipboard_wait_is_text_available (cb) ? Qt : Qnil; -} + block_input (); + if (NILP (time_object)) + timestamp = dpyinfo->last_user_time; + else + CONS_TO_INTEGER (time_object, guint32, timestamp); + gdk_selection_owner_set_for_display (dpyinfo->display, NULL, + selection_atom, timestamp, + TRUE); + unblock_input (); + return Qt; +} -DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p, 0, 2, 0, - doc: /* Whether the current Emacs process owns the given X Selection. +DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p, + 0, 2, 0, + doc: /* Whether the current Emacs process owns the given selection. The arg should be the name of the selection in question, typically one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'. -\(Those are literal upper-case symbol names, since that's what X expects.) +\(Those are literal upper-case symbol names, since that's what GDK expects.) For convenience, the symbol nil is the same as `PRIMARY', and t is the same as `SECONDARY'. -TERMINAL should be a terminal object or a frame specifying the X +TERMINAL should be a terminal object or a frame specifying the GDK server to query. If omitted or nil, that stands for the selected -frame's display, or the first available X display. - -On Nextstep, TERMINAL is unused. */) +frame's display, or the first available X display. */) (Lisp_Object selection, Lisp_Object terminal) { struct frame *f = frame_for_pgtk_selection (terminal); - GtkClipboard *cb; - GObject *obj; - GQuark quark_data, quark_size; - if (!pgtk_selection_usable ()) - return Qnil; + CHECK_SYMBOL (selection); + if (NILP (selection)) selection = QPRIMARY; + if (EQ (selection, Qt)) selection = QSECONDARY; - cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection); - selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data, - &quark_size); - - obj = gtk_clipboard_get_owner (cb); - - return obj && g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil; + if (f && !NILP (LOCAL_SELECTION (selection, FRAME_DISPLAY_INFO (f)))) + return Qt; + else + return Qnil; } +DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p, + 0, 2, 0, + doc: /* Whether there is an owner for the given selection. +SELECTION should be the name of the selection in question, typically +one of the symbols `PRIMARY', `SECONDARY', `CLIPBOARD', or +`CLIPBOARD_MANAGER' (GDK expects these literal upper-case names.) The +symbol nil is the same as `PRIMARY', and t is the same as `SECONDARY'. -DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal, - Spgtk_get_selection_internal, 2, 3, 0, - doc: /* Return text selected from some program. -SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'. -\(Those are literal upper-case symbol names, since that's what X expects.) -TARGET-TYPE is the type of data desired, typically `STRING'. - -TERMINAL should be a terminal object or a frame specifying the X +TERMINAL should be a terminal object or a frame specifying the GDK server to query. If omitted or nil, that stands for the selected -frame's display, or the first available display. */) - (Lisp_Object selection_symbol, Lisp_Object target_type, - Lisp_Object terminal) +frame's display, or the first available X display. */) + (Lisp_Object selection, Lisp_Object terminal) { + GdkWindow *owner; + GdkAtom atom; struct frame *f = frame_for_pgtk_selection (terminal); - GtkClipboard *cb; + struct pgtk_display_info *dpyinfo; - CHECK_SYMBOL (selection_symbol); - CHECK_SYMBOL (target_type); + CHECK_SYMBOL (selection); + if (NILP (selection)) selection = QPRIMARY; + if (EQ (selection, Qt)) selection = QSECONDARY; - if (EQ (target_type, QMULTIPLE)) - error ("Retrieving MULTIPLE selections is currently unimplemented"); if (!f) - error ("PGTK selection unavailable for this frame"); - - if (!pgtk_selection_usable ()) return Qnil; - cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection_symbol); + dpyinfo = FRAME_DISPLAY_INFO (f); - GdkAtom target_atom = gdk_atom_intern (SSDATA (SYMBOL_NAME (target_type)), false); - GtkSelectionData *seldata = gtk_clipboard_wait_for_contents (cb, target_atom); + if (!NILP (LOCAL_SELECTION (selection, dpyinfo))) + return Qt; - if (seldata == NULL) - return Qnil; + atom = symbol_to_gdk_atom (selection); + if (atom == 0) return Qnil; + block_input (); + owner = gdk_selection_owner_get_for_display (dpyinfo->display, atom); + unblock_input (); + return (owner ? Qt : Qnil); +} + +/* Called to handle GDK_SELECTION_NOTIFY events. + If it's the selection we are waiting for, stop waiting + by setting the car of reading_selection_reply to non-nil. + We store t there if the reply is successful, lambda if not. */ - const guchar *sd_data = gtk_selection_data_get_data (seldata); - int sd_len = gtk_selection_data_get_length (seldata); - int sd_format = gtk_selection_data_get_format (seldata); - GdkAtom sd_type = gtk_selection_data_get_data_type (seldata); +void +pgtk_handle_selection_notify (GdkEventSelection *event) +{ + /* GDK doesn't populate event->requestor, contrary to what the ICCCM + says should be done with SelectionNotify events. */ - if (sd_format == 8) + if (event->selection != reading_which_selection) + return; + + XSETCAR (reading_selection_reply, + (event->property != GDK_NONE ? Qt : Qlambda)); +} + + +/*********************************************************************** + Drag and drop support +***********************************************************************/ + +DEFUN ("pgtk-register-dnd-targets", Fpgtk_register_dnd_targets, + Spgtk_register_dnd_targets, 2, 2, 0, + doc: /* Register TARGETS on FRAME. +TARGETS should be a list of strings describing data types (selection +targets) that can be dropped on top of FRAME. */) + (Lisp_Object frame, Lisp_Object targets) +{ + struct frame *f; + GtkTargetEntry *entries; + GtkTargetList *list; + ptrdiff_t length, n; + Lisp_Object tem, t; + char *buf; + USE_SAFE_ALLOCA; + + f = decode_window_system_frame (frame); + CHECK_LIST (targets); + length = list_length (targets); + n = 0; + entries = SAFE_ALLOCA (sizeof *entries * length); + memset (entries, 0, sizeof *entries * length); + tem = targets; + + FOR_EACH_TAIL (tem) { - Lisp_Object str, lispy_type; + if (!CONSP (tem)) + continue; - str = make_unibyte_string ((char *) sd_data, sd_len); - /* Indicate that this string is from foreign selection by a text - property `foreign-selection' so that the caller of - x-get-selection-internal (usually x-get-selection) can know - that the string must be decode. */ - if (sd_type == gdk_atom_intern ("COMPOUND_TEXT", false)) - lispy_type = QCOMPOUND_TEXT; - else if (sd_type == gdk_atom_intern ("UTF8_STRING", false)) - lispy_type = QUTF8_STRING; - else if (sd_type == gdk_atom_intern ("text/plain;charset=utf-8", false)) - lispy_type = Qtext_plain_charset_utf_8; - else - lispy_type = QSTRING; - Fput_text_property (make_fixnum (0), make_fixnum (sd_len), - Qforeign_selection, lispy_type, str); + t = XCAR (tem); - gtk_selection_data_free (seldata); - return str; + CHECK_STRING (t); + SAFE_ALLOCA_STRING (buf, t); + + entries[n++].target = buf; } + CHECK_LIST_END (tem, targets); + + if (n != length) + emacs_abort (); + + list = gtk_target_list_new (entries, n); + gtk_drag_dest_set_target_list (FRAME_GTK_WIDGET (f), list); + gtk_target_list_unref (list); + + SAFE_FREE (); + + return Qnil; +} + +DEFUN ("pgtk-drop-finish", Fpgtk_drop_finish, Spgtk_drop_finish, 3, 3, 0, + doc: /* Finish the drag-n-drop event that happened at TIMESTAMP. +SUCCESS is whether or not the drop was successful, i.e. the action +chosen in the last call to `pgtk-update-drop-status' was performed. +TIMESTAMP is the time associated with the drag-n-drop event that is +being finished. +DELETE is whether or not the action was `move'. */) + (Lisp_Object success, Lisp_Object timestamp, Lisp_Object delete) +{ + pgtk_finish_drop (success, timestamp, delete); + + return Qnil; +} + +DEFUN ("pgtk-update-drop-status", Fpgtk_update_drop_status, + Spgtk_update_drop_status, 2, 2, 0, + doc: /* Update the status of the current drag-and-drop operation. +ACTION is the action the drop source should take. +TIMESTAMP is the same as in `pgtk-drop-finish'. */) + (Lisp_Object action, Lisp_Object timestamp) +{ + pgtk_update_drop_status (action, timestamp); - gtk_selection_data_free (seldata); return Qnil; } @@ -499,19 +1871,56 @@ syms_of_pgtkselect (void) DEFSYM (QSECONDARY, "SECONDARY"); DEFSYM (QTEXT, "TEXT"); DEFSYM (QFILE_NAME, "FILE_NAME"); + DEFSYM (QSTRING, "STRING"); + DEFSYM (QINTEGER, "INTEGER"); + DEFSYM (QTIMESTAMP, "TIMESTAMP"); + DEFSYM (QTEXT, "TEXT"); DEFSYM (QMULTIPLE, "MULTIPLE"); - - DEFSYM (Qforeign_selection, "foreign-selection"); + DEFSYM (QNULL, "NULL"); + DEFSYM (QATOM, "ATOM"); + DEFSYM (QTARGETS, "TARGETS"); DEFSYM (QUTF8_STRING, "UTF8_STRING"); - DEFSYM (QSTRING, "STRING"); DEFSYM (QCOMPOUND_TEXT, "COMPOUND_TEXT"); - DEFSYM (Qtext_plain_charset_utf_8, "text/plain;charset=utf-8"); + + DEFSYM (Qforeign_selection, "foreign-selection"); + + DEFSYM (Qpgtk_sent_selection_functions, "pgtk-sent-selection-functions"); + DEFSYM (Qpgtk_lost_selection_functions, "pgtk-lost-selection-functions"); defsubr (&Spgtk_disown_selection_internal); defsubr (&Spgtk_get_selection_internal); defsubr (&Spgtk_own_selection_internal); defsubr (&Spgtk_selection_exists_p); defsubr (&Spgtk_selection_owner_p); + defsubr (&Spgtk_register_dnd_targets); + defsubr (&Spgtk_update_drop_status); + defsubr (&Spgtk_drop_finish); + + DEFVAR_LISP ("selection-converter-alist", Vselection_converter_alist, + doc: /* SKIP: real doc in xselect.c. */); + Vselection_converter_alist = Qnil; + + DEFVAR_LISP ("pgtk-lost-selection-functions", Vpgtk_lost_selection_functions, + doc: /* A list of functions to be called when Emacs loses a selection. +\(This happens when some other client makes its own selection +or when a Lisp program explicitly clears the selection.) +The functions are called with one argument, the selection type +\(a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'). */); + Vpgtk_lost_selection_functions = Qnil; + + DEFVAR_LISP ("pgtk-sent-selection-functions", Vpgtk_sent_selection_functions, + doc: /* A list of functions to be called when Emacs answers a selection request. +The functions are called with three arguments: + - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD'); + - the selection-type which Emacs was asked to convert the + selection into before sending (for example, `STRING' or `LENGTH'); + - a flag indicating success or failure for responding to the request. +We might have failed (and declined the request) for any number of reasons, +including being asked for a selection that we no longer own, or being asked +to convert into a type that we don't know about or that is inappropriate. +xThis hook doesn't let you change the behavior of Emacs's selection replies, +it merely informs you that they have happened. */); + Vpgtk_sent_selection_functions = Qnil; DEFVAR_LISP ("pgtk-sent-selection-hooks", Vpgtk_sent_selection_hooks, doc: /* A list of functions to be called when Emacs answers a selection request @@ -527,10 +1936,24 @@ This hook doesn't let you change the behavior of Emacs's selection replies, it merely informs you that they have happened. */); Vpgtk_sent_selection_hooks = Qnil; - DEFVAR_BOOL ("pgtk-enable-selection-on-multi-display", pgtk_enable_selection_on_multi_display, - doc: /* Enable selections when connected to multiple displays. -This may cause crashes due to a GTK bug, which assumes that clients -will connect to a single display. It might also cause selections to -not arrive at the correct display. */); - pgtk_enable_selection_on_multi_display = false; + DEFVAR_INT ("pgtk-selection-timeout", pgtk_selection_timeout, + doc: /* Number of milliseconds to wait for a selection reply. +If the selection owner doesn't reply in this time, we give up. +A value of 0 means wait as long as necessary. */); + pgtk_selection_timeout = 0; + + DEFVAR_LISP ("pgtk-selection-alias-alist", Vpgtk_selection_alias_alist, + doc: /* List of selections to alias to another. +It should be an alist of a selection name to another. When a +selection request arrives for the first selection, Emacs will respond +as if the request was meant for the other. + +Note that this does not affect setting or owning selections. */); + Vpgtk_selection_alias_alist = Qnil; + + reading_selection_reply = Fcons (Qnil, Qnil); + staticpro (&reading_selection_reply); + + property_change_reply = Fcons (Qnil, Qnil); + staticpro (&property_change_reply); } diff --git a/src/pgtkselect.h b/src/pgtkselect.h deleted file mode 100644 index fd9910b2d18..00000000000 --- a/src/pgtkselect.h +++ /dev/null @@ -1,31 +0,0 @@ -/* Definitions and headers for selection of pure Gtk+3. - Copyright (C) 1989, 1993, 2005, 2008-2022 Free Software Foundation, - Inc. - -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/>. */ - - -#include "dispextern.h" -#include "frame.h" - -#ifdef HAVE_PGTK - -#include <gtk/gtk.h> - -extern void pgtk_selection_init (void); -extern void pgtk_selection_lost (GtkWidget *, GdkEventSelection *, gpointer); - -#endif /* HAVE_PGTK */ diff --git a/src/pgtkterm.c b/src/pgtkterm.c index da958a6664a..491ba338821 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -61,7 +61,6 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "buffer.h" #include "font.h" #include "xsettings.h" -#include "pgtkselect.h" #include "emacsgtkfixed.h" #ifdef GDK_WINDOWING_WAYLAND @@ -77,25 +76,36 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ static bool any_help_event_p; -struct pgtk_display_info *x_display_list; /* Chain of existing displays */ -extern Lisp_Object tip_frame; +/* Chain of existing displays */ +struct pgtk_display_info *x_display_list; -static struct event_queue_t +struct event_queue_t { union buffered_input_event *q; int nr, cap; -} event_q = { - NULL, 0, 0, }; +/* A queue of events that will be read by the read_socket_hook. */ +static struct event_queue_t event_q; + /* Non-zero timeout value means ignore next mouse click if it arrives before that timeout elapses (i.e. as part of the same sequence of events resulting from clicking on a frame to select it). */ - static Time ignore_next_mouse_click_timeout; +/* The default Emacs icon . */ static Lisp_Object xg_default_icon_file; +/* The current GdkDragContext of a drop. */ +static GdkDragContext *current_drop_context; + +/* Whether or not current_drop_context was set from a drop + handler. */ +static bool current_drop_context_drop; + +/* The time of the last drop. */ +static guint32 current_drop_time; + static void pgtk_delete_display (struct pgtk_display_info *); static void pgtk_clear_frame_area (struct frame *, int, int, int, int); static void pgtk_fill_rectangle (struct frame *, unsigned long, int, int, @@ -290,6 +300,9 @@ static void evq_enqueue (union buffered_input_event *ev) { struct event_queue_t *evq = &event_q; + struct frame *frame; + struct pgtk_display_info *dpyinfo; + if (evq->cap == 0) { evq->cap = 4; @@ -303,6 +316,27 @@ evq_enqueue (union buffered_input_event *ev) } evq->q[evq->nr++] = *ev; + + if (ev->ie.kind != SELECTION_REQUEST_EVENT + && ev->ie.kind != SELECTION_CLEAR_EVENT) + { + frame = NULL; + + if (WINDOWP (ev->ie.frame_or_window)) + frame = WINDOW_XFRAME (XWINDOW (ev->ie.frame_or_window)); + + if (FRAMEP (ev->ie.frame_or_window)) + frame = XFRAME (ev->ie.frame_or_window); + + if (frame) + { + dpyinfo = FRAME_DISPLAY_INFO (frame); + + if (dpyinfo->last_user_time < ev->ie.timestamp) + dpyinfo->last_user_time = ev->ie.timestamp; + } + } + raise (SIGIO); } @@ -1550,6 +1584,8 @@ pgtk_draw_glyphless_glyph_string_foreground (struct glyph_string *s) ? CHAR_TABLE_REF (Vglyphless_char_display, glyph->u.glyphless.ch) : XCHAR_TABLE (Vglyphless_char_display)->extras[0]); + if (CONSP (acronym)) + acronym = XCAR (acronym); if (STRINGP (acronym)) str = SSDATA (acronym); } @@ -4809,16 +4845,16 @@ pgtk_any_window_to_frame (GdkWindow *window) return NULL; FOR_EACH_FRAME (tail, frame) - { - if (found) - break; - f = XFRAME (frame); - if (FRAME_PGTK_P (f)) - { - if (pgtk_window_is_of_frame (f, window)) - found = f; - } - } + { + if (found) + break; + f = XFRAME (frame); + if (FRAME_PGTK_P (f)) + { + if (pgtk_window_is_of_frame (f, window)) + found = f; + } + } return found; } @@ -5420,15 +5456,18 @@ window_state_event (GtkWidget *widget, gpointer *user_data) { struct frame *f = pgtk_any_window_to_frame (event->window_state.window); + GdkWindowState new_state; union buffered_input_event inev; + new_state = event->window_state.new_window_state; + EVENT_INIT (inev.ie); inev.ie.kind = NO_EVENT; inev.ie.arg = Qnil; if (f) { - if (event->window_state.new_window_state & GDK_WINDOW_STATE_FOCUSED) + if (new_state & GDK_WINDOW_STATE_FOCUSED) { if (FRAME_ICONIFIED_P (f)) { @@ -5444,17 +5483,24 @@ window_state_event (GtkWidget *widget, } } - if (event->window_state.new_window_state - & GDK_WINDOW_STATE_FULLSCREEN) + if (new_state & GDK_WINDOW_STATE_FULLSCREEN) store_frame_param (f, Qfullscreen, Qfullboth); - else if (event->window_state.new_window_state - & GDK_WINDOW_STATE_MAXIMIZED) + else if (new_state & GDK_WINDOW_STATE_MAXIMIZED) store_frame_param (f, Qfullscreen, Qmaximized); + else if ((new_state & GDK_WINDOW_STATE_TOP_TILED) + && (new_state & GDK_WINDOW_STATE_BOTTOM_TILED) + && !(new_state & GDK_WINDOW_STATE_TOP_RESIZABLE) + && !(new_state & GDK_WINDOW_STATE_BOTTOM_RESIZABLE)) + store_frame_param (f, Qfullscreen, Qfullheight); + else if ((new_state & GDK_WINDOW_STATE_LEFT_TILED) + && (new_state & GDK_WINDOW_STATE_RIGHT_TILED) + && !(new_state & GDK_WINDOW_STATE_LEFT_RESIZABLE) + && !(new_state & GDK_WINDOW_STATE_RIGHT_RESIZABLE)) + store_frame_param (f, Qfullscreen, Qfullwidth); else store_frame_param (f, Qfullscreen, Qnil); - if (event->window_state.new_window_state - & GDK_WINDOW_STATE_ICONIFIED) + if (new_state & GDK_WINDOW_STATE_ICONIFIED) SET_FRAME_ICONIFIED (f, true); else { @@ -5464,8 +5510,7 @@ window_state_event (GtkWidget *widget, SET_FRAME_ICONIFIED (f, false); } - if (event->window_state.new_window_state - & GDK_WINDOW_STATE_STICKY) + if (new_state & GDK_WINDOW_STATE_STICKY) store_frame_param (f, Qsticky, Qt); else store_frame_param (f, Qsticky, Qnil); @@ -5868,8 +5913,7 @@ construct_mouse_click (struct input_event *result, } static gboolean -button_event (GtkWidget *widget, - GdkEvent *event, +button_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { union buffered_input_event inev; @@ -6124,40 +6168,219 @@ scroll_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) return TRUE; } + + +/* C part of drop handling code. + The Lisp part is in pgtk-dnd.el. */ + +static GdkDragAction +symbol_to_drag_action (Lisp_Object act) +{ + if (EQ (act, Qcopy)) + return GDK_ACTION_COPY; + + if (EQ (act, Qmove)) + return GDK_ACTION_MOVE; + + if (EQ (act, Qlink)) + return GDK_ACTION_LINK; + + if (EQ (act, Qprivate)) + return GDK_ACTION_PRIVATE; + + if (NILP (act)) + return GDK_ACTION_DEFAULT; + + signal_error ("Invalid drag acction", act); +} + +static Lisp_Object +drag_action_to_symbol (GdkDragAction action) +{ + switch (action) + { + case GDK_ACTION_COPY: + return Qcopy; + + case GDK_ACTION_MOVE: + return Qmove; + + case GDK_ACTION_LINK: + return Qlink; + + case GDK_ACTION_PRIVATE: + return Qprivate; + + case GDK_ACTION_DEFAULT: + default: + return Qnil; + } +} + +void +pgtk_update_drop_status (Lisp_Object action, Lisp_Object event_time) +{ + guint32 time; + + CONS_TO_INTEGER (event_time, guint32, time); + + if (!current_drop_context || time < current_drop_time) + return; + + gdk_drag_status (current_drop_context, + symbol_to_drag_action (action), + time); +} + +void +pgtk_finish_drop (Lisp_Object success, Lisp_Object event_time, + Lisp_Object del) +{ + guint32 time; + + CONS_TO_INTEGER (event_time, guint32, time); + + if (!current_drop_context || time < current_drop_time) + return; + + gtk_drag_finish (current_drop_context, !NILP (success), + !NILP (del), time); + + if (current_drop_context_drop) + g_clear_pointer (¤t_drop_context, + g_object_unref); +} + static void -drag_data_received (GtkWidget *widget, GdkDragContext *context, - gint x, gint y, GtkSelectionData *data, - guint info, guint time, gpointer user_data) +drag_leave (GtkWidget *widget, GdkDragContext *context, + guint time, gpointer user_data) { - struct frame *f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); - gchar **uris = gtk_selection_data_get_uris (data); + struct frame *f; + union buffered_input_event inev; - if (uris != NULL) + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + if (current_drop_context) { - for (int i = 0; uris[i] != NULL; i++) - { - union buffered_input_event inev; - Lisp_Object arg = Qnil; + if (current_drop_context_drop) + gtk_drag_finish (current_drop_context, + FALSE, FALSE, current_drop_time); - EVENT_INIT (inev.ie); - inev.ie.kind = NO_EVENT; - inev.ie.arg = Qnil; + g_clear_pointer (¤t_drop_context, + g_object_unref); + } - arg = list2 (Qurl, build_string (uris[i])); + EVENT_INIT (inev.ie); - inev.ie.kind = DRAG_N_DROP_EVENT; - inev.ie.modifiers = 0; - XSETINT (inev.ie.x, x); - XSETINT (inev.ie.y, y); - XSETFRAME (inev.ie.frame_or_window, f); - inev.ie.arg = arg; - inev.ie.timestamp = 0; + inev.ie.kind = DRAG_N_DROP_EVENT; + inev.ie.modifiers = 0; + inev.ie.arg = Qnil; + inev.ie.timestamp = time; - evq_enqueue (&inev); - } + XSETINT (inev.ie.x, 0); + XSETINT (inev.ie.y, 0); + XSETFRAME (inev.ie.frame_or_window, f); + + evq_enqueue (&inev); +} + +static gboolean +drag_motion (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time) + +{ + struct frame *f; + union buffered_input_event inev; + GdkAtom name; + GdkDragAction suggestion; + + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + if (!f) + return FALSE; + + if (current_drop_context) + { + if (current_drop_context_drop) + gtk_drag_finish (current_drop_context, + FALSE, FALSE, current_drop_time); + + g_clear_pointer (¤t_drop_context, + g_object_unref); } - gtk_drag_finish (context, TRUE, FALSE, time); + current_drop_context = g_object_ref (context); + current_drop_time = time; + current_drop_context_drop = false; + + name = gdk_drag_get_selection (context); + suggestion = gdk_drag_context_get_suggested_action (context); + + EVENT_INIT (inev.ie); + + inev.ie.kind = DRAG_N_DROP_EVENT; + inev.ie.modifiers = 0; + inev.ie.arg = list4 (Qlambda, intern (gdk_atom_name (name)), + make_uint (time), + drag_action_to_symbol (suggestion)); + inev.ie.timestamp = time; + + XSETINT (inev.ie.x, x); + XSETINT (inev.ie.y, y); + XSETFRAME (inev.ie.frame_or_window, f); + + evq_enqueue (&inev); + + return TRUE; +} + +static gboolean +drag_drop (GtkWidget *widget, GdkDragContext *context, + int x, int y, guint time, gpointer user_data) +{ + struct frame *f; + union buffered_input_event inev; + GdkAtom name; + GdkDragAction selected_action; + + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + if (!f) + return FALSE; + + if (current_drop_context) + { + if (current_drop_context_drop) + gtk_drag_finish (current_drop_context, + FALSE, FALSE, current_drop_time); + + g_clear_pointer (¤t_drop_context, + g_object_unref); + } + + current_drop_context = g_object_ref (context); + current_drop_time = time; + current_drop_context_drop = true; + + name = gdk_drag_get_selection (context); + selected_action = gdk_drag_context_get_selected_action (context); + + EVENT_INIT (inev.ie); + + inev.ie.kind = DRAG_N_DROP_EVENT; + inev.ie.modifiers = 0; + inev.ie.arg = list4 (Qquote, intern (gdk_atom_name (name)), + make_uint (time), + drag_action_to_symbol (selected_action)); + inev.ie.timestamp = time; + + XSETINT (inev.ie.x, x); + XSETINT (inev.ie.y, y); + XSETFRAME (inev.ie.frame_or_window, f); + + evq_enqueue (&inev); + + return TRUE; } static void @@ -6174,6 +6397,8 @@ pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data) evq_enqueue (&inev); } +static gboolean pgtk_selection_event (GtkWidget *, GdkEvent *, gpointer); + void pgtk_set_event_handler (struct frame *f) { @@ -6184,9 +6409,9 @@ pgtk_set_event_handler (struct frame *f) return; } - gtk_drag_dest_set (FRAME_GTK_WIDGET (f), GTK_DEST_DEFAULT_ALL, NULL, 0, - GDK_ACTION_COPY); - gtk_drag_dest_add_uri_targets (FRAME_GTK_WIDGET (f)); + gtk_drag_dest_set (FRAME_GTK_WIDGET (f), 0, NULL, 0, + (GDK_ACTION_MOVE | GDK_ACTION_COPY + | GDK_ACTION_LINK | GDK_ACTION_PRIVATE)); if (FRAME_GTK_OUTER_WIDGET (f)) { @@ -6225,14 +6450,24 @@ pgtk_set_event_handler (struct frame *f) G_CALLBACK (button_event), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "scroll-event", G_CALLBACK (scroll_event), NULL); - g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event", - G_CALLBACK (pgtk_selection_lost), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "configure-event", G_CALLBACK (configure_event), NULL); - g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-data-received", - G_CALLBACK (drag_data_received), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-leave", + G_CALLBACK (drag_leave), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-motion", + G_CALLBACK (drag_motion), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-drop", + G_CALLBACK (drag_drop), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "draw", G_CALLBACK (pgtk_handle_draw), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "property-notify-event", + G_CALLBACK (pgtk_selection_event), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event", + G_CALLBACK (pgtk_selection_event), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-request-event", + G_CALLBACK (pgtk_selection_event), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-notify-event", + G_CALLBACK (pgtk_selection_event), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "event", G_CALLBACK (pgtk_handle_event), NULL); } @@ -6292,6 +6527,73 @@ same_x_server (const char *name1, const char *name2) && (*name2 == '.' || *name2 == '\0')); } +static struct frame * +pgtk_find_selection_owner (GdkWindow *window) +{ + Lisp_Object tail, tem; + struct frame *f; + + FOR_EACH_FRAME (tail, tem) + { + f = XFRAME (tem); + + if (FRAME_PGTK_P (f) + && (FRAME_GDK_WINDOW (f) == window)) + return f; + } + + return NULL; +} + +static gboolean +pgtk_selection_event (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ + struct frame *f; + union buffered_input_event inev; + + if (event->type == GDK_PROPERTY_NOTIFY) + pgtk_handle_property_notify (&event->property); + else if (event->type == GDK_SELECTION_CLEAR + || event->type == GDK_SELECTION_REQUEST) + { + f = pgtk_find_selection_owner (event->selection.window); + + if (f) + { + EVENT_INIT (inev.ie); + + inev.sie.kind = (event->type == GDK_SELECTION_CLEAR + ? SELECTION_CLEAR_EVENT + : SELECTION_REQUEST_EVENT); + + SELECTION_EVENT_DPYINFO (&inev.sie) = FRAME_DISPLAY_INFO (f); + SELECTION_EVENT_SELECTION (&inev.sie) = event->selection.selection; + SELECTION_EVENT_TIME (&inev.sie) = event->selection.time; + + if (event->type == GDK_SELECTION_REQUEST) + { + /* FIXME: when does GDK destroy the requestor GdkWindow + object? + + It would make sense to wait for the transfer to + complete. But I don't know if GDK actually does + that. */ + SELECTION_EVENT_REQUESTOR (&inev.sie) = event->selection.requestor; + SELECTION_EVENT_TARGET (&inev.sie) = event->selection.target; + SELECTION_EVENT_PROPERTY (&inev.sie) = event->selection.property; + } + + evq_enqueue (&inev); + return TRUE; + } + } + else if (event->type == GDK_SELECTION_NOTIFY) + pgtk_handle_selection_notify (&event->selection); + + return FALSE; +} + /* Open a connection to X display DISPLAY_NAME, and return the structure that describes the open display. If we cannot contact the display, return null. */ @@ -6525,8 +6827,6 @@ pgtk_term_init (Lisp_Object display_name, char *resource_name) xsettings_initialize (dpyinfo); - pgtk_selection_init (); - pgtk_im_init (dpyinfo); g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-added", @@ -6708,12 +7008,17 @@ syms_of_pgtkterm (void) DEFSYM (Qlatin_1, "latin-1"); - xg_default_icon_file = - build_pure_c_string ("icons/hicolor/scalable/apps/emacs.svg"); + xg_default_icon_file + = build_pure_c_string ("icons/hicolor/scalable/apps/emacs.svg"); staticpro (&xg_default_icon_file); DEFSYM (Qx_gtk_map_stock, "x-gtk-map-stock"); + DEFSYM (Qcopy, "copy"); + DEFSYM (Qmove, "move"); + DEFSYM (Qlink, "link"); + DEFSYM (Qprivate, "private"); + Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier)); Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier)); @@ -6998,8 +7303,3 @@ pgtk_cr_export_frames (Lisp_Object frames, cairo_surface_type_t surface_type) return CALLN (Fapply, intern ("concat"), Fnreverse (acc)); } - -void -init_pgtkterm (void) -{ -} diff --git a/src/pgtkterm.h b/src/pgtkterm.h index e31e62ae193..fcc6c5310e9 100644 --- a/src/pgtkterm.h +++ b/src/pgtkterm.h @@ -127,8 +127,14 @@ struct pgtk_display_info /* The generic display parameters corresponding to this PGTK display. */ struct terminal *terminal; - /* This says how to access this display in Gdk. */ - GdkDisplay *gdpy; + union + { + /* This says how to access this display through GDK. */ + GdkDisplay *gdpy; + + /* An alias defined to make porting X code easier. */ + GdkDisplay *display; + }; /* This is a cons cell of the form (NAME . FONT-LIST-CACHE). */ Lisp_Object name_list_element; @@ -210,6 +216,9 @@ struct pgtk_display_info /* Time of last mouse movement. */ Time last_mouse_movement_time; + /* Time of last user interaction. */ + guint32 last_user_time; + /* The scroll bar in which the last motion event occurred. */ void *last_mouse_scroll_bar; @@ -439,14 +448,15 @@ enum #define FRAME_FONT(f) (FRAME_X_OUTPUT (f)->font) #define FRAME_GTK_OUTER_WIDGET(f) (FRAME_X_OUTPUT (f)->widget) #define FRAME_GTK_WIDGET(f) (FRAME_X_OUTPUT (f)->edit_widget) -#define FRAME_WIDGET(f) (FRAME_GTK_OUTER_WIDGET (f) ? \ - FRAME_GTK_OUTER_WIDGET (f) : \ - FRAME_GTK_WIDGET (f)) +#define FRAME_WIDGET(f) (FRAME_GTK_OUTER_WIDGET (f) \ + ? FRAME_GTK_OUTER_WIDGET (f) \ + : FRAME_GTK_WIDGET (f)) -/* aliases */ #define FRAME_PGTK_VIEW(f) FRAME_GTK_WIDGET (f) #define FRAME_X_WINDOW(f) FRAME_GTK_OUTER_WIDGET (f) #define FRAME_NATIVE_WINDOW(f) GTK_WINDOW (FRAME_X_WINDOW (f)) +#define FRAME_GDK_WINDOW(f) \ + (gtk_widget_get_window (FRAME_GTK_WIDGET (f))) #define FRAME_X_DISPLAY(f) (FRAME_DISPLAY_INFO (f)->gdpy) @@ -484,70 +494,101 @@ enum #define FRAME_CR_SURFACE_DESIRED_HEIGHT(f) \ ((f)->output_data.pgtk->cr_surface_desired_height) + +/* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT + or SELECTION_CLEAR_EVENT, then its contents are really described + by this structure. */ + +/* For an event of kind SELECTION_REQUEST_EVENT, + this structure really describes the contents. */ + +struct selection_input_event +{ + ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH; + struct pgtk_display_info *dpyinfo; + /* We spell it with an "o" here because X does. */ + GdkWindow *requestor; + GdkAtom selection, target, property; + guint32 time; +}; + +/* Unlike macros below, this can't be used as an lvalue. */ +INLINE GdkDisplay * +SELECTION_EVENT_DISPLAY (struct selection_input_event *ev) +{ + return ev->dpyinfo->display; +} +#define SELECTION_EVENT_DPYINFO(eventp) \ + ((eventp)->dpyinfo) +/* We spell it with an "o" here because X does. */ +#define SELECTION_EVENT_REQUESTOR(eventp) \ + ((eventp)->requestor) +#define SELECTION_EVENT_SELECTION(eventp) \ + ((eventp)->selection) +#define SELECTION_EVENT_TARGET(eventp) \ + ((eventp)->target) +#define SELECTION_EVENT_PROPERTY(eventp) \ + ((eventp)->property) +#define SELECTION_EVENT_TIME(eventp) \ + ((eventp)->time) + +extern void pgtk_handle_selection_event (struct selection_input_event *); +extern void pgtk_clear_frame_selections (struct frame *); +extern void pgtk_handle_property_notify (GdkEventProperty *); +extern void pgtk_handle_selection_notify (GdkEventSelection *); + /* Display init/shutdown functions implemented in pgtkterm.c */ -extern struct pgtk_display_info *pgtk_term_init (Lisp_Object display_name, - char *resource_name); -extern void pgtk_term_shutdown (int sig); +extern struct pgtk_display_info *pgtk_term_init (Lisp_Object, char *); +extern void pgtk_term_shutdown (int); /* Implemented in pgtkterm, published in or needed from pgtkfns. */ -extern void pgtk_clear_frame (struct frame *f); -extern char *pgtk_xlfd_to_fontname (const char *xlfd); +extern void pgtk_clear_frame (struct frame *); +extern char *pgtk_xlfd_to_fontname (const char *); -/* Implemented in pgtkfns. */ +/* Implemented in pgtkfns.c. */ extern void pgtk_set_doc_edited (void); -extern const char *pgtk_get_defaults_value (const char *key); -extern const char *pgtk_get_string_resource (XrmDatabase rdb, - const char *name, - const char *class); -extern void pgtk_implicitly_set_name (struct frame *f, Lisp_Object arg, - Lisp_Object oldval); +extern const char *pgtk_get_defaults_value (const char *); +extern const char *pgtk_get_string_resource (XrmDatabase, const char *, const char *); +extern void pgtk_implicitly_set_name (struct frame *, Lisp_Object, Lisp_Object); /* Color management implemented in pgtkterm. */ -extern bool pgtk_defined_color (struct frame *f, - const char *name, - Emacs_Color * color_def, bool alloc, - bool makeIndex); -extern void pgtk_query_color (struct frame *f, Emacs_Color * color); -extern void pgtk_query_colors (struct frame *f, Emacs_Color * colors, - int ncolors); -extern int pgtk_parse_color (struct frame *f, const char *color_name, - Emacs_Color * color); +extern bool pgtk_defined_color (struct frame *, const char *, + Emacs_Color *, bool, bool); +extern void pgtk_query_color (struct frame *, Emacs_Color *); +extern void pgtk_query_colors (struct frame *, Emacs_Color *, int); +extern int pgtk_parse_color (struct frame *, const char *, Emacs_Color *); /* Implemented in pgtkterm.c */ -extern void pgtk_clear_area (struct frame *f, int x, int y, int width, - int height); -extern int pgtk_gtk_to_emacs_modifiers (struct pgtk_display_info *dpyinfo, - int state); -extern void pgtk_clear_under_internal_border (struct frame *f); -extern void pgtk_set_event_handler (struct frame *f); +extern void pgtk_clear_area (struct frame *, int, int, int, int); +extern int pgtk_gtk_to_emacs_modifiers (struct pgtk_display_info *, int); +extern void pgtk_clear_under_internal_border (struct frame *); +extern void pgtk_set_event_handler (struct frame *); /* Implemented in pgtkterm.c */ extern int pgtk_display_pixel_height (struct pgtk_display_info *); extern int pgtk_display_pixel_width (struct pgtk_display_info *); -extern void pgtk_destroy_window (struct frame *f); -extern void pgtk_set_parent_frame (struct frame *f, Lisp_Object, Lisp_Object); +extern void pgtk_destroy_window (struct frame *); +extern void pgtk_set_parent_frame (struct frame *, Lisp_Object, Lisp_Object); extern void pgtk_set_no_focus_on_map (struct frame *, Lisp_Object, Lisp_Object); extern void pgtk_set_no_accept_focus (struct frame *, Lisp_Object, Lisp_Object); extern void pgtk_set_z_group (struct frame *, Lisp_Object, Lisp_Object); /* Cairo related functions implemented in pgtkterm.c */ extern void pgtk_cr_update_surface_desired_size (struct frame *, int, int, bool); -extern cairo_t *pgtk_begin_cr_clip (struct frame *f); -extern void pgtk_end_cr_clip (struct frame *f); +extern cairo_t *pgtk_begin_cr_clip (struct frame *); +extern void pgtk_end_cr_clip (struct frame *); extern void pgtk_set_cr_source_with_gc_foreground (struct frame *, Emacs_GC *, bool); extern void pgtk_set_cr_source_with_gc_background (struct frame *, Emacs_GC *, bool); extern void pgtk_set_cr_source_with_color (struct frame *, unsigned long, bool); -extern void pgtk_cr_draw_frame (cairo_t * cr, struct frame *f); -extern void pgtk_cr_destroy_frame_context (struct frame *f); -extern Lisp_Object pgtk_cr_export_frames (Lisp_Object frames, cairo_surface_type_t surface_type); +extern void pgtk_cr_draw_frame (cairo_t *, struct frame *); +extern void pgtk_cr_destroy_frame_context (struct frame *); +extern Lisp_Object pgtk_cr_export_frames (Lisp_Object , cairo_surface_type_t); /* Defined in pgtkmenu.c */ -extern Lisp_Object pgtk_popup_dialog (struct frame *f, Lisp_Object header, - Lisp_Object contents); -extern Lisp_Object pgtk_dialog_show (struct frame *f, Lisp_Object title, - Lisp_Object header, - const char **error_name); +extern Lisp_Object pgtk_popup_dialog (struct frame *, Lisp_Object, Lisp_Object); +extern Lisp_Object pgtk_dialog_show (struct frame *, Lisp_Object, Lisp_Object, + const char **); extern void initialize_frame_menubar (struct frame *); @@ -559,44 +600,46 @@ extern void syms_of_pgtkselect (void); extern void syms_of_pgtkim (void); /* Initialization and marking implemented in pgtkterm.c */ -extern void init_pgtkterm (void); extern void mark_pgtkterm (void); -extern void pgtk_delete_terminal (struct terminal *terminal); +extern void pgtk_delete_terminal (struct terminal *); -extern void pgtk_make_frame_visible (struct frame *f); -extern void pgtk_make_frame_invisible (struct frame *f); +extern void pgtk_make_frame_visible (struct frame *); +extern void pgtk_make_frame_invisible (struct frame *); extern void pgtk_free_frame_resources (struct frame *); -extern void pgtk_iconify_frame (struct frame *f); -extern void pgtk_focus_frame (struct frame *f, bool noactivate); -extern void pgtk_set_scroll_bar_default_width (struct frame *f); -extern void pgtk_set_scroll_bar_default_height (struct frame *f); -extern Lisp_Object pgtk_get_focus_frame (struct frame *frame); +extern void pgtk_iconify_frame (struct frame *); +extern void pgtk_focus_frame (struct frame *, bool); +extern void pgtk_set_scroll_bar_default_width (struct frame *); +extern void pgtk_set_scroll_bar_default_height (struct frame *); +extern Lisp_Object pgtk_get_focus_frame (struct frame *); -extern void pgtk_frame_rehighlight (struct pgtk_display_info *dpyinfo); +extern void pgtk_frame_rehighlight (struct pgtk_display_info *); extern void pgtk_change_tab_bar_height (struct frame *, int); -extern struct pgtk_display_info *check_pgtk_display_info (Lisp_Object object); +extern struct pgtk_display_info *check_pgtk_display_info (Lisp_Object); -extern void pgtk_default_font_parameter (struct frame *f, Lisp_Object parms); +extern void pgtk_default_font_parameter (struct frame *, Lisp_Object); -extern void pgtk_menu_set_in_use (bool in_use); +extern void pgtk_menu_set_in_use (bool); +/* Drag and drop functions used by Lisp. */ +extern void pgtk_update_drop_status (Lisp_Object, Lisp_Object); +extern void pgtk_finish_drop (Lisp_Object, Lisp_Object, Lisp_Object); -extern void pgtk_enqueue_string (struct frame *f, gchar * str); -extern void pgtk_enqueue_preedit (struct frame *f, Lisp_Object image_data); -extern void pgtk_im_focus_in (struct frame *f); -extern void pgtk_im_focus_out (struct frame *f); -extern bool pgtk_im_filter_keypress (struct frame *f, GdkEventKey * ev); -extern void pgtk_im_set_cursor_location (struct frame *f, int x, int y, - int width, int height); -extern void pgtk_im_init (struct pgtk_display_info *dpyinfo); -extern void pgtk_im_finish (struct pgtk_display_info *dpyinfo); +extern void pgtk_enqueue_string (struct frame *, gchar *); +extern void pgtk_enqueue_preedit (struct frame *, Lisp_Object); +extern void pgtk_im_focus_in (struct frame *); +extern void pgtk_im_focus_out (struct frame *); +extern bool pgtk_im_filter_keypress (struct frame *, GdkEventKey *); +extern void pgtk_im_set_cursor_location (struct frame *, int, int, + int, int); +extern void pgtk_im_init (struct pgtk_display_info *); +extern void pgtk_im_finish (struct pgtk_display_info *); extern bool xg_set_icon (struct frame *, Lisp_Object); -extern bool xg_set_icon_from_xpm_data (struct frame *f, const char **data); +extern bool xg_set_icon_from_xpm_data (struct frame *, const char **); -extern bool pgtk_text_icon (struct frame *f, const char *icon_name); +extern bool pgtk_text_icon (struct frame *, const char *); extern double pgtk_frame_scale_factor (struct frame *); extern int pgtk_emacs_to_gtk_modifiers (struct pgtk_display_info *, int); diff --git a/src/print.c b/src/print.c index 5aee5731e40..12b50874359 100644 --- a/src/print.c +++ b/src/print.c @@ -67,16 +67,17 @@ static Lisp_Object being_printed[PRINT_CIRCLE]; /* Last char printed to stdout by printchar. */ static unsigned int printchar_stdout_last; +struct print_buffer +{ + char *buffer; /* Allocated buffer. */ + ptrdiff_t size; /* Size of allocated buffer. */ + ptrdiff_t pos; /* Chars stored in buffer. */ + ptrdiff_t pos_byte; /* Bytes stored in buffer. */ +}; + /* When printing into a buffer, first we put the text in this block, then insert it all at once. */ -static char *print_buffer; - -/* Size allocated in print_buffer. */ -static ptrdiff_t print_buffer_size; -/* Chars stored in print_buffer. */ -static ptrdiff_t print_buffer_pos; -/* Bytes stored in print_buffer. */ -static ptrdiff_t print_buffer_pos_byte; +static struct print_buffer print_buffer; /* Vprint_number_table is a table, that keeps objects that are going to be printed, to allow use of #n= and #n# to express sharing. @@ -95,117 +96,139 @@ bool print_output_debug_flag EXTERNALLY_VISIBLE = 1; /* Low level output routines for characters and strings. */ -/* Lisp functions to do output using a stream - must have the stream in a variable called printcharfun - and must start with PRINTPREPARE, end with PRINTFINISH. - Use printchar to output one character, - or call strout to output a block of characters. */ - -#define PRINTPREPARE \ - struct buffer *old = current_buffer; \ - ptrdiff_t old_point = -1, start_point = -1; \ - ptrdiff_t old_point_byte = -1, start_point_byte = -1; \ - specpdl_ref specpdl_count = SPECPDL_INDEX (); \ - bool free_print_buffer = 0; \ - bool multibyte \ - = !NILP (BVAR (current_buffer, enable_multibyte_characters)); \ - Lisp_Object original = printcharfun; \ - if (NILP (printcharfun)) printcharfun = Qt; \ - if (BUFFERP (printcharfun)) \ - { \ - if (XBUFFER (printcharfun) != current_buffer) \ - Fset_buffer (printcharfun); \ - printcharfun = Qnil; \ - } \ - if (MARKERP (printcharfun)) \ - { \ - ptrdiff_t marker_pos; \ - if (! XMARKER (printcharfun)->buffer) \ - error ("Marker does not point anywhere"); \ - if (XMARKER (printcharfun)->buffer != current_buffer) \ - set_buffer_internal (XMARKER (printcharfun)->buffer); \ - marker_pos = marker_position (printcharfun); \ - if (marker_pos < BEGV || marker_pos > ZV) \ - signal_error ("Marker is outside the accessible " \ - "part of the buffer", printcharfun); \ - old_point = PT; \ - old_point_byte = PT_BYTE; \ - SET_PT_BOTH (marker_pos, \ - marker_byte_position (printcharfun)); \ - start_point = PT; \ - start_point_byte = PT_BYTE; \ - printcharfun = Qnil; \ - } \ - if (NILP (printcharfun)) \ - { \ - Lisp_Object string; \ - if (NILP (BVAR (current_buffer, enable_multibyte_characters)) \ - && ! print_escape_multibyte) \ - specbind (Qprint_escape_multibyte, Qt); \ - if (! NILP (BVAR (current_buffer, enable_multibyte_characters)) \ - && ! print_escape_nonascii) \ - specbind (Qprint_escape_nonascii, Qt); \ - if (print_buffer != 0) \ - { \ - string = make_string_from_bytes (print_buffer, \ - print_buffer_pos, \ - print_buffer_pos_byte); \ - record_unwind_protect (print_unwind, string); \ - } \ - else \ - { \ - int new_size = 1000; \ - print_buffer = xmalloc (new_size); \ - print_buffer_size = new_size; \ - free_print_buffer = 1; \ - } \ - print_buffer_pos = 0; \ - print_buffer_pos_byte = 0; \ - } \ - if (EQ (printcharfun, Qt) && ! noninteractive) \ - setup_echo_area_for_printing (multibyte); - -#define PRINTFINISH \ - if (NILP (printcharfun)) \ - { \ - if (print_buffer_pos != print_buffer_pos_byte \ - && NILP (BVAR (current_buffer, enable_multibyte_characters)))\ - { \ - USE_SAFE_ALLOCA; \ - unsigned char *temp = SAFE_ALLOCA (print_buffer_pos + 1); \ - copy_text ((unsigned char *) print_buffer, temp, \ - print_buffer_pos_byte, 1, 0); \ - insert_1_both ((char *) temp, print_buffer_pos, \ - print_buffer_pos, 0, 1, 0); \ - SAFE_FREE (); \ - } \ - else \ - insert_1_both (print_buffer, print_buffer_pos, \ - print_buffer_pos_byte, 0, 1, 0); \ - signal_after_change (PT - print_buffer_pos, 0, print_buffer_pos);\ - } \ - if (free_print_buffer) \ - { \ - xfree (print_buffer); \ - print_buffer = 0; \ - } \ - unbind_to (specpdl_count, Qnil); \ - if (MARKERP (original)) \ - set_marker_both (original, Qnil, PT, PT_BYTE); \ - if (old_point >= 0) \ - SET_PT_BOTH (old_point + (old_point >= start_point \ - ? PT - start_point : 0), \ - old_point_byte + (old_point_byte >= start_point_byte \ - ? PT_BYTE - start_point_byte : 0)); \ - set_buffer_internal (old); +/* This is used to free the print buffer; we don't simply record xfree + since print_buffer can be reallocated during the printing. */ +static void +print_free_buffer (void) +{ + xfree (print_buffer.buffer); + print_buffer.buffer = NULL; +} /* This is used to restore the saved contents of print_buffer when there is a recursive call to print. */ - static void print_unwind (Lisp_Object saved_text) { - memcpy (print_buffer, SDATA (saved_text), SCHARS (saved_text)); + memcpy (print_buffer.buffer, SDATA (saved_text), SCHARS (saved_text)); +} + +/* Lisp functions to do output using a stream must start with a call to + print_prepare, and end with calling print_finish. + Use printchar to output one character, or call strout to output a + block of characters. */ + +/* State carried between print_prepare and print_finish. */ +struct print_context +{ + Lisp_Object printcharfun; + Lisp_Object old_printcharfun; + ptrdiff_t old_point, start_point; + ptrdiff_t old_point_byte, start_point_byte; + specpdl_ref specpdl_count; +}; + +static inline struct print_context +print_prepare (Lisp_Object printcharfun) +{ + struct print_context pc = { + .old_printcharfun = printcharfun, + .old_point = -1, + .start_point = -1, + .old_point_byte = -1, + .start_point_byte = -1, + .specpdl_count = SPECPDL_INDEX (), + }; + bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters)); + record_unwind_current_buffer (); + specbind(Qprint__unreadable_callback_buffer, Fcurrent_buffer ()); + if (NILP (printcharfun)) + printcharfun = Qt; + if (BUFFERP (printcharfun)) + { + if (XBUFFER (printcharfun) != current_buffer) + Fset_buffer (printcharfun); + printcharfun = Qnil; + } + if (MARKERP (printcharfun)) + { + if (! XMARKER (printcharfun)->buffer) + error ("Marker does not point anywhere"); + if (XMARKER (printcharfun)->buffer != current_buffer) + set_buffer_internal (XMARKER (printcharfun)->buffer); + ptrdiff_t marker_pos = marker_position (printcharfun); + if (marker_pos < BEGV || marker_pos > ZV) + signal_error ("Marker is outside the accessible part of the buffer", + printcharfun); + pc.old_point = PT; + pc.old_point_byte = PT_BYTE; + SET_PT_BOTH (marker_pos, marker_byte_position (printcharfun)); + pc.start_point = PT; + pc.start_point_byte = PT_BYTE; + printcharfun = Qnil; + } + if (NILP (printcharfun)) + { + if (NILP (BVAR (current_buffer, enable_multibyte_characters)) + && ! print_escape_multibyte) + specbind (Qprint_escape_multibyte, Qt); + if (! NILP (BVAR (current_buffer, enable_multibyte_characters)) + && ! print_escape_nonascii) + specbind (Qprint_escape_nonascii, Qt); + if (print_buffer.buffer != NULL) + { + Lisp_Object string = make_string_from_bytes (print_buffer.buffer, + print_buffer.pos, + print_buffer.pos_byte); + record_unwind_protect (print_unwind, string); + } + else + { + int new_size = 1000; + print_buffer.buffer = xmalloc (new_size); + print_buffer.size = new_size; + record_unwind_protect_void (print_free_buffer); + } + print_buffer.pos = 0; + print_buffer.pos_byte = 0; + } + if (EQ (printcharfun, Qt) && ! noninteractive) + setup_echo_area_for_printing (multibyte); + pc.printcharfun = printcharfun; + return pc; +} + +static inline void +print_finish (struct print_context *pc) +{ + if (NILP (pc->printcharfun)) + { + if (print_buffer.pos != print_buffer.pos_byte + && NILP (BVAR (current_buffer, enable_multibyte_characters))) + { + USE_SAFE_ALLOCA; + unsigned char *temp = SAFE_ALLOCA (print_buffer.pos + 1); + copy_text ((unsigned char *) print_buffer.buffer, temp, + print_buffer.pos_byte, 1, 0); + insert_1_both ((char *) temp, print_buffer.pos, + print_buffer.pos, 0, 1, 0); + SAFE_FREE (); + } + else + insert_1_both (print_buffer.buffer, print_buffer.pos, + print_buffer.pos_byte, 0, 1, 0); + signal_after_change (PT - print_buffer.pos, 0, print_buffer.pos); + } + if (MARKERP (pc->old_printcharfun)) + set_marker_both (pc->old_printcharfun, Qnil, PT, PT_BYTE); + if (pc->old_point >= 0) + SET_PT_BOTH (pc->old_point + + (pc->old_point >= pc->start_point + ? PT - pc->start_point : 0), + pc->old_point_byte + + (pc->old_point_byte >= pc->start_point_byte + ? PT_BYTE - pc->start_point_byte : 0)); + unbind_to (pc->specpdl_count, Qnil); } /* Print character CH to the stdio stream STREAM. */ @@ -293,13 +316,14 @@ printchar (unsigned int ch, Lisp_Object fun) if (NILP (fun)) { - ptrdiff_t incr = len - (print_buffer_size - print_buffer_pos_byte); + ptrdiff_t incr = len - (print_buffer.size - print_buffer.pos_byte); if (incr > 0) - print_buffer = xpalloc (print_buffer, &print_buffer_size, - incr, -1, 1); - memcpy (print_buffer + print_buffer_pos_byte, str, len); - print_buffer_pos += 1; - print_buffer_pos_byte += len; + print_buffer.buffer = xpalloc (print_buffer.buffer, + &print_buffer.size, + incr, -1, 1); + memcpy (print_buffer.buffer + print_buffer.pos_byte, str, len); + print_buffer.pos += 1; + print_buffer.pos_byte += len; } else if (noninteractive) { @@ -358,12 +382,13 @@ strout (const char *ptr, ptrdiff_t size, ptrdiff_t size_byte, { if (NILP (printcharfun)) { - ptrdiff_t incr = size_byte - (print_buffer_size - print_buffer_pos_byte); + ptrdiff_t incr = size_byte - (print_buffer.size - print_buffer.pos_byte); if (incr > 0) - print_buffer = xpalloc (print_buffer, &print_buffer_size, incr, -1, 1); - memcpy (print_buffer + print_buffer_pos_byte, ptr, size_byte); - print_buffer_pos += size; - print_buffer_pos_byte += size_byte; + print_buffer.buffer = xpalloc (print_buffer.buffer, + &print_buffer.size, incr, -1, 1); + memcpy (print_buffer.buffer + print_buffer.pos_byte, ptr, size_byte); + print_buffer.pos += size; + print_buffer.pos_byte += size_byte; } else if (noninteractive && EQ (printcharfun, Qt)) { @@ -471,8 +496,7 @@ print_string (Lisp_Object string, Lisp_Object printcharfun) if (chars < bytes) { newstr = make_uninit_multibyte_string (chars, bytes); - memcpy (SDATA (newstr), SDATA (string), chars); - str_to_multibyte (SDATA (newstr), bytes, chars); + str_to_multibyte (SDATA (newstr), SDATA (string), chars); string = newstr; } } @@ -528,14 +552,14 @@ PRINTCHARFUN defaults to the value of `standard-output' (which see). */) if (NILP (printcharfun)) printcharfun = Vstandard_output; CHECK_FIXNUM (character); - PRINTPREPARE; - printchar (XFIXNUM (character), printcharfun); - PRINTFINISH; + struct print_context pc = print_prepare (printcharfun); + printchar (XFIXNUM (character), pc.printcharfun); + print_finish (&pc); return character; } /* Print the contents of a unibyte C string STRING using PRINTCHARFUN. - The caller should arrange to put this inside PRINTPREPARE and PRINTFINISH. + The caller should arrange to put this inside print_prepare and print_finish. Do not use this on the contents of a Lisp string. */ static void @@ -551,9 +575,9 @@ print_c_string (char const *string, Lisp_Object printcharfun) static void write_string (const char *data, Lisp_Object printcharfun) { - PRINTPREPARE; - print_c_string (data, printcharfun); - PRINTFINISH; + struct print_context pc = print_prepare (printcharfun); + print_c_string (data, pc.printcharfun); + print_finish (&pc); } @@ -606,21 +630,21 @@ If PRINTCHARFUN is omitted or nil, the value of `standard-output' is used. */) if (NILP (printcharfun)) printcharfun = Vstandard_output; - PRINTPREPARE; + struct print_context pc = print_prepare (printcharfun); if (NILP (ensure)) val = Qt; /* Difficult to check if at line beginning so abort. */ - else if (FUNCTIONP (printcharfun)) - signal_error ("Unsupported function argument", printcharfun); - else if (noninteractive && !NILP (printcharfun)) + else if (FUNCTIONP (pc.printcharfun)) + signal_error ("Unsupported function argument", pc.printcharfun); + else if (noninteractive && !NILP (pc.printcharfun)) val = printchar_stdout_last == 10 ? Qnil : Qt; else val = NILP (Fbolp ()) ? Qt : Qnil; if (!NILP (val)) - printchar ('\n', printcharfun); - PRINTFINISH; + printchar ('\n', pc.printcharfun); + print_finish (&pc); return val; } @@ -731,7 +755,7 @@ Optional argument OVERRIDES should be a list of settings for print-related variables. An element in this list can be the symbol t, which means "reset all the values to their defaults". Otherwise, an element should be a pair, where the `car' or the pair is the setting symbol, and the `cdr' is the -value of of the setting to use for this `prin1' call. +value of the setting to use for this `prin1' call. For instance: @@ -751,9 +775,9 @@ means "use default values for all the print-related settings". */) if (!NILP (overrides)) print_bind_overrides (overrides); - PRINTPREPARE; - print (object, printcharfun, 1); - PRINTFINISH; + struct print_context pc = print_prepare (printcharfun); + print (object, pc.printcharfun, 1); + print_finish (&pc); return unbind_to (count, object); } @@ -788,11 +812,10 @@ A printed representation of an object is text which describes that object. */) No need for specbind, since errors deactivate the mark. */ Lisp_Object save_deactivate_mark = Vdeactivate_mark; - Lisp_Object printcharfun = Vprin1_to_string_buffer; - PRINTPREPARE; - print (object, printcharfun, NILP (noescape)); - /* Make Vprin1_to_string_buffer be the default buffer after PRINTFINISH */ - PRINTFINISH; + struct print_context pc = print_prepare (Vprin1_to_string_buffer); + print (object, pc.printcharfun, NILP (noescape)); + /* Make Vprin1_to_string_buffer be the default buffer after print_finish */ + print_finish (&pc); struct buffer *previous = current_buffer; set_buffer_internal (XBUFFER (Vprin1_to_string_buffer)); @@ -837,15 +860,15 @@ is used instead. */) { if (NILP (printcharfun)) printcharfun = Vstandard_output; - PRINTPREPARE; + struct print_context pc = print_prepare (printcharfun); if (STRINGP (object) && !string_intervals (object) && NILP (Vprint_continuous_numbering)) /* fast path for plain strings */ - print_string (object, printcharfun); + print_string (object, pc.printcharfun); else - print (object, printcharfun, 0); - PRINTFINISH; + print (object, pc.printcharfun, 0); + print_finish (&pc); return object; } @@ -876,11 +899,11 @@ is used instead. */) { if (NILP (printcharfun)) printcharfun = Vstandard_output; - PRINTPREPARE; - printchar ('\n', printcharfun); - print (object, printcharfun, 1); - printchar ('\n', printcharfun); - PRINTFINISH; + struct print_context pc = print_prepare (printcharfun); + printchar ('\n', pc.printcharfun); + print (object, pc.printcharfun, 1); + printchar ('\n', pc.printcharfun); + print_finish (&pc); return object; } @@ -1657,6 +1680,17 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag, infinite recursion in the function called. */ Lisp_Object func = Vprint_unreadable_function; specbind (Qprint_unreadable_function, Qnil); + + /* If we're being called from `prin1-to-string' or the like, + we're now in the secret " prin1" buffer. This can lead to + problems if, for instance, the callback function switches a + window to this buffer -- this will make Emacs segfault. */ + if (!NILP (Vprint__unreadable_callback_buffer) + && !NILP (Fbuffer_live_p (Vprint__unreadable_callback_buffer))) + { + record_unwind_current_buffer (); + set_buffer_internal (XBUFFER (Vprint__unreadable_callback_buffer)); + } Lisp_Object result = CALLN (Ffuncall, func, obj, escapeflag? Qt: Qnil); unbind_to (count, Qnil); @@ -1731,10 +1765,10 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag, case PVEC_USER_PTR: { - void *finalizer = XUSER_PTR (obj)->finalizer; print_c_string ("#<user-ptr ", printcharfun); int i = sprintf (buf, "ptr=%p finalizer=%p", - XUSER_PTR (obj)->p, finalizer); + XUSER_PTR (obj)->p, + (void *) XUSER_PTR (obj)->finalizer); strout (buf, i, i, printcharfun); printchar ('>', printcharfun); } @@ -2942,6 +2976,15 @@ be printed. */); Vprint_unreadable_function = Qnil; DEFSYM (Qprint_unreadable_function, "print-unreadable-function"); + DEFVAR_LISP ("print--unreadable-callback-buffer", + Vprint__unreadable_callback_buffer, + doc: /* Dynamically bound to indicate current buffer. */); + Vprint__unreadable_callback_buffer = Qnil; + DEFSYM (Qprint__unreadable_callback_buffer, + "print--unreadable-callback-buffer"); + /* Don't export this variable to Elisp. */ + Funintern (Qprint__unreadable_callback_buffer, Qnil); + defsubr (&Sflush_standard_output); /* Initialized in print_create_variable_mapping. */ diff --git a/src/process.c b/src/process.c index ccfc0bdf547..7a133cda00f 100644 --- a/src/process.c +++ b/src/process.c @@ -1209,8 +1209,8 @@ If PROCESS has not yet exited or died, return 0. */) DEFUN ("process-id", Fprocess_id, Sprocess_id, 1, 1, 0, doc: /* Return the process id of PROCESS. -This is the pid of the external process which PROCESS uses or talks to. -It is a fixnum if the value is small enough, otherwise a bignum. +This is the pid of the external process which PROCESS uses or talks to, +an integer. For a network, serial, and pipe connections, this value is nil. */) (register Lisp_Object process) { @@ -1243,14 +1243,31 @@ or t (process is stopped). */) return XPROCESS (process)->command; } -DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0, +DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0, doc: /* Return the name of the terminal PROCESS uses, or nil if none. This is the terminal that the process itself reads and writes on, -not the name of the pty that Emacs uses to talk with that terminal. */) - (register Lisp_Object process) +not the name of the pty that Emacs uses to talk with that terminal. + +If STREAM is nil, return the terminal name if any of PROCESS's +standard streams use a terminal for communication. If STREAM is one +of `stdin', `stdout', or `stderr', return the name of the terminal +PROCESS uses for that stream specifically, or nil if that stream +communicates via a pipe. */) + (register Lisp_Object process, Lisp_Object stream) { CHECK_PROCESS (process); - return XPROCESS (process)->tty_name; + register struct Lisp_Process *p = XPROCESS (process); + + if (NILP (stream)) + return p->tty_name; + else if (EQ (stream, Qstdin)) + return p->pty_in ? p->tty_name : Qnil; + else if (EQ (stream, Qstdout)) + return p->pty_out ? p->tty_name : Qnil; + else if (EQ (stream, Qstderr)) + return p->pty_out && NILP (p->stderrproc) ? p->tty_name : Qnil; + else + signal_error ("Unknown stream", stream); } static void @@ -1281,7 +1298,7 @@ Return BUFFER. */) update_process_mark (p); } if (NETCONN1_P (p) || SERIALCONN1_P (p) || PIPECONN1_P (p)) - pset_childp (p, Fplist_put (p->childp, QCbuffer, buffer)); + pset_childp (p, plist_put (p->childp, QCbuffer, buffer)); setup_process_coding_systems (process); return buffer; } @@ -1316,6 +1333,19 @@ set_process_filter_masks (struct Lisp_Process *p) add_process_read_fd (p->infd); } +static bool +is_pty_from_symbol (Lisp_Object symbol) +{ + if (EQ (symbol, Qpty)) + return true; + else if (EQ (symbol, Qpipe)) + return false; + else if (NILP (symbol)) + return !NILP (Vprocess_connection_type); + else + report_file_error ("Unknown connection type", symbol); +} + DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter, 2, 2, 0, doc: /* Give PROCESS the filter function FILTER; nil means default. @@ -1360,7 +1390,7 @@ The string argument is normally a multibyte string, except: pset_filter (p, filter); if (NETCONN1_P (p) || SERIALCONN1_P (p) || PIPECONN1_P (p)) - pset_childp (p, Fplist_put (p->childp, QCfilter, filter)); + pset_childp (p, plist_put (p->childp, QCfilter, filter)); setup_process_coding_systems (process); return filter; } @@ -1392,7 +1422,7 @@ It gets two arguments: the process, and a string describing the change. */) pset_sentinel (p, sentinel); if (NETCONN1_P (p) || SERIALCONN1_P (p) || PIPECONN1_P (p)) - pset_childp (p, Fplist_put (p->childp, QCsentinel, sentinel)); + pset_childp (p, plist_put (p->childp, QCsentinel, sentinel)); return sentinel; } @@ -1553,25 +1583,25 @@ waiting for the process to be fully set up.*/) if (DATAGRAM_CONN_P (process) && (EQ (key, Qt) || EQ (key, QCremote))) - contact = Fplist_put (contact, QCremote, - Fprocess_datagram_address (process)); + contact = plist_put (contact, QCremote, + Fprocess_datagram_address (process)); #endif if ((!NETCONN_P (process) && !SERIALCONN_P (process) && !PIPECONN_P (process)) || EQ (key, Qt)) return contact; if (NILP (key) && NETCONN_P (process)) - return list2 (Fplist_get (contact, QChost), - Fplist_get (contact, QCservice)); + return list2 (plist_get (contact, QChost), + plist_get (contact, QCservice)); if (NILP (key) && SERIALCONN_P (process)) - return list2 (Fplist_get (contact, QCport), - Fplist_get (contact, QCspeed)); + return list2 (plist_get (contact, QCport), + plist_get (contact, QCspeed)); /* FIXME: Return a meaningful value (e.g., the child end of the pipe) if the pipe process is useful for purposes other than receiving stderr. */ if (NILP (key) && PIPECONN_P (process)) return Qt; - return Fplist_get (contact, key); + return plist_get (contact, key); } DEFUN ("process-plist", Fprocess_plist, Sprocess_plist, @@ -1741,15 +1771,18 @@ signals to stop and continue a process. :connection-type TYPE -- TYPE is control type of device used to communicate with subprocesses. Values are `pipe' to use a pipe, `pty' to use a pty, or nil to use the default specified through -`process-connection-type'. +`process-connection-type'. If TYPE is a cons (INPUT . OUTPUT), then +INPUT will be used for standard input and OUTPUT for standard output +(and standard error if `:stderr' is nil). :filter FILTER -- Install FILTER as the process filter. :sentinel SENTINEL -- Install SENTINEL as the process sentinel. :stderr STDERR -- STDERR is either a buffer or a pipe process attached -to the standard error of subprocess. Specifying this implies -`:connection-type' is set to `pipe'. If STDERR is nil, standard error +to the standard error of subprocess. When specifying this, the +subprocess's standard error will always communicate via a pipe, no +matter the value of `:connection-type'. If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER. (Note that specifying :stderr will create a new, separate (but associated) process, with its own filter and sentinel. See @@ -1773,7 +1806,7 @@ usage: (make-process &rest ARGS) */) /* Save arguments for process-contact and clone-process. */ contact = Flist (nargs, args); - if (!NILP (Fplist_get (contact, QCfile_handler))) + if (!NILP (plist_get (contact, QCfile_handler))) { Lisp_Object file_handler = Ffind_file_name_handler (BVAR (current_buffer, directory), @@ -1782,7 +1815,7 @@ usage: (make-process &rest ARGS) */) return CALLN (Fapply, file_handler, Qmake_process, contact); } - buffer = Fplist_get (contact, QCbuffer); + buffer = plist_get (contact, QCbuffer); if (!NILP (buffer)) buffer = Fget_buffer_create (buffer, Qnil); @@ -1792,10 +1825,10 @@ usage: (make-process &rest ARGS) */) chdir, since it's in a vfork. */ current_dir = get_current_directory (true); - name = Fplist_get (contact, QCname); + name = plist_get (contact, QCname); CHECK_STRING (name); - command = Fplist_get (contact, QCcommand); + command = plist_get (contact, QCcommand); if (CONSP (command)) program = XCAR (command); else @@ -1804,10 +1837,10 @@ usage: (make-process &rest ARGS) */) if (!NILP (program)) CHECK_STRING (program); - bool query_on_exit = NILP (Fplist_get (contact, QCnoquery)); + bool query_on_exit = NILP (plist_get (contact, QCnoquery)); stderrproc = Qnil; - xstderr = Fplist_get (contact, QCstderr); + xstderr = plist_get (contact, QCstderr); if (PROCESSP (xstderr)) { if (!PIPECONN_P (xstderr)) @@ -1833,34 +1866,32 @@ usage: (make-process &rest ARGS) */) eassert (NILP (XPROCESS (proc)->plist)); pset_type (XPROCESS (proc), Qreal); pset_buffer (XPROCESS (proc), buffer); - pset_sentinel (XPROCESS (proc), Fplist_get (contact, QCsentinel)); - pset_filter (XPROCESS (proc), Fplist_get (contact, QCfilter)); + pset_sentinel (XPROCESS (proc), plist_get (contact, QCsentinel)); + pset_filter (XPROCESS (proc), plist_get (contact, QCfilter)); pset_command (XPROCESS (proc), Fcopy_sequence (command)); if (!query_on_exit) XPROCESS (proc)->kill_without_query = 1; - tem = Fplist_get (contact, QCstop); + tem = plist_get (contact, QCstop); /* Normal processes can't be started in a stopped state, see Bug#30460. */ CHECK_TYPE (NILP (tem), Qnull, tem); - tem = Fplist_get (contact, QCconnection_type); - if (EQ (tem, Qpty)) - XPROCESS (proc)->pty_flag = true; - else if (EQ (tem, Qpipe)) - XPROCESS (proc)->pty_flag = false; - else if (NILP (tem)) - XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type); + tem = plist_get (contact, QCconnection_type); + if (CONSP (tem)) + { + XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem)); + XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem)); + } else - report_file_error ("Unknown connection type", tem); - - if (!NILP (stderrproc)) { - pset_stderrproc (XPROCESS (proc), stderrproc); - - XPROCESS (proc)->pty_flag = false; + XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out = + is_pty_from_symbol (tem); } + if (!NILP (stderrproc)) + pset_stderrproc (XPROCESS (proc), stderrproc); + #ifdef HAVE_GNUTLS /* AKA GNUTLS_INITSTAGE(proc). */ verify (GNUTLS_STAGE_EMPTY == 0); @@ -1886,7 +1917,7 @@ usage: (make-process &rest ARGS) */) Lisp_Object coding_systems = Qt; Lisp_Object val, *args2; - tem = Fplist_get (contact, QCcoding); + tem = plist_get (contact, QCcoding); if (!NILP (tem)) { val = tem; @@ -2099,66 +2130,80 @@ static void create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) { struct Lisp_Process *p = XPROCESS (process); - int inchannel, outchannel; + int inchannel = -1, outchannel = -1; pid_t pid = -1; int vfork_errno; int forkin, forkout, forkerr = -1; - bool pty_flag = 0; + bool pty_in = false, pty_out = false; char pty_name[PTY_NAME_SIZE]; Lisp_Object lisp_pty_name = Qnil; + int ptychannel = -1, pty_tty = -1; sigset_t oldset; /* Ensure that the SIGCHLD handler can notify `wait_reading_process_output'. */ child_signal_init (); - inchannel = outchannel = -1; + if (p->pty_in || p->pty_out) + ptychannel = allocate_pty (pty_name); - if (p->pty_flag) - outchannel = inchannel = allocate_pty (pty_name); - - if (inchannel >= 0) + if (ptychannel >= 0) { - p->open_fd[READ_FROM_SUBPROCESS] = inchannel; #if ! defined (USG) || defined (USG_SUBTTY_WORKS) /* On most USG systems it does not work to open the pty's tty here, then close it and reopen it in the child. */ /* Don't let this terminal become our controlling terminal (in case we don't have one). */ - forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0); - if (forkin < 0) + pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0); + if (pty_tty < 0) report_file_error ("Opening pty", Qnil); - p->open_fd[SUBPROCESS_STDIN] = forkin; -#else - forkin = forkout = -1; #endif /* not USG, or USG_SUBTTY_WORKS */ - pty_flag = 1; + pty_in = p->pty_in; + pty_out = p->pty_out; lisp_pty_name = build_string (pty_name); } + + /* Set up stdin for the child process. */ + if (ptychannel >= 0 && p->pty_in) + { + p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty; + outchannel = ptychannel; + } else { - if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0 - || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0) + if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0) report_file_error ("Creating pipe", Qnil); forkin = p->open_fd[SUBPROCESS_STDIN]; outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; + } + + /* Set up stdout for the child process. */ + if (ptychannel >= 0 && p->pty_out) + { + forkout = pty_tty; + p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel; + } + else + { + if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0) + report_file_error ("Creating pipe", Qnil); inchannel = p->open_fd[READ_FROM_SUBPROCESS]; forkout = p->open_fd[SUBPROCESS_STDOUT]; #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ) fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max); #endif + } - if (!NILP (p->stderrproc)) - { - struct Lisp_Process *pp = XPROCESS (p->stderrproc); + if (!NILP (p->stderrproc)) + { + struct Lisp_Process *pp = XPROCESS (p->stderrproc); - forkerr = pp->open_fd[SUBPROCESS_STDOUT]; + forkerr = pp->open_fd[SUBPROCESS_STDOUT]; - /* Close unnecessary file descriptors. */ - close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]); - close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]); - } + /* Close unnecessary file descriptors. */ + close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]); + close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]); } if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) @@ -2183,7 +2228,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) we just reopen the device (see emacs_get_tty_pgrp) as this is more portable (see USG_SUBTTY_WORKS above). */ - p->pty_flag = pty_flag; + p->pty_in = pty_in; + p->pty_out = pty_out; pset_status (p, Qrun); if (!EQ (p->command, Qt) @@ -2199,13 +2245,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) block_input (); block_child_signal (&oldset); - pty_flag = p->pty_flag; - eassert (pty_flag == ! NILP (lisp_pty_name)); + pty_in = p->pty_in; + pty_out = p->pty_out; + eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name)); vfork_errno = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env, SSDATA (current_dir), - pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset); + pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL, + pty_in, pty_out, &oldset); eassert ((vfork_errno == 0) == (0 < pid)); @@ -2263,7 +2311,7 @@ create_pty (Lisp_Object process) { struct Lisp_Process *p = XPROCESS (process); char pty_name[PTY_NAME_SIZE]; - int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name); + int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name); if (pty_fd >= 0) { @@ -2301,7 +2349,7 @@ create_pty (Lisp_Object process) we just reopen the device (see emacs_get_tty_pgrp) as this is more portable (see USG_SUBTTY_WORKS above). */ - p->pty_flag = 1; + p->pty_in = p->pty_out = true; pset_status (p, Qrun); setup_process_coding_systems (process); @@ -2364,7 +2412,7 @@ usage: (make-pipe-process &rest ARGS) */) contact = Flist (nargs, args); - name = Fplist_get (contact, QCname); + name = plist_get (contact, QCname); CHECK_STRING (name); proc = make_process (name); specpdl_ref specpdl_count = SPECPDL_INDEX (); @@ -2396,23 +2444,23 @@ usage: (make-pipe-process &rest ARGS) */) if (inchannel > max_desc) max_desc = inchannel; - buffer = Fplist_get (contact, QCbuffer); + buffer = plist_get (contact, QCbuffer); if (NILP (buffer)) buffer = name; buffer = Fget_buffer_create (buffer, Qnil); pset_buffer (p, buffer); pset_childp (p, contact); - pset_plist (p, Fcopy_sequence (Fplist_get (contact, QCplist))); + pset_plist (p, Fcopy_sequence (plist_get (contact, QCplist))); pset_type (p, Qpipe); - pset_sentinel (p, Fplist_get (contact, QCsentinel)); - pset_filter (p, Fplist_get (contact, QCfilter)); + pset_sentinel (p, plist_get (contact, QCsentinel)); + pset_filter (p, plist_get (contact, QCfilter)); eassert (NILP (p->log)); - if (tem = Fplist_get (contact, QCnoquery), !NILP (tem)) + if (tem = plist_get (contact, QCnoquery), !NILP (tem)) p->kill_without_query = 1; - if (tem = Fplist_get (contact, QCstop), !NILP (tem)) + if (tem = plist_get (contact, QCstop), !NILP (tem)) pset_command (p, Qt); - eassert (! p->pty_flag); + eassert (! p->pty_in && ! p->pty_out); if (!EQ (p->command, Qt) && !EQ (p->filter, Qt)) @@ -2431,7 +2479,7 @@ usage: (make-pipe-process &rest ARGS) */) Lisp_Object coding_systems = Qt; Lisp_Object val; - tem = Fplist_get (contact, QCcoding); + tem = plist_get (contact, QCcoding); val = Qnil; if (!NILP (tem)) { @@ -2918,7 +2966,7 @@ set up yet, this function will block until socket setup has completed. */) if (set_socket_option (s, option, value)) { - pset_childp (p, Fplist_put (p->childp, option, value)); + pset_childp (p, plist_put (p->childp, option, value)); return Qt; } @@ -2996,19 +3044,19 @@ usage: (serial-process-configure &rest ARGS) */) contact = Flist (nargs, args); - proc = Fplist_get (contact, QCprocess); + proc = plist_get (contact, QCprocess); if (NILP (proc)) - proc = Fplist_get (contact, QCname); + proc = plist_get (contact, QCname); if (NILP (proc)) - proc = Fplist_get (contact, QCbuffer); + proc = plist_get (contact, QCbuffer); if (NILP (proc)) - proc = Fplist_get (contact, QCport); + proc = plist_get (contact, QCport); proc = get_process (proc); p = XPROCESS (proc); if (!EQ (p->type, Qserial)) error ("Not a serial process"); - if (NILP (Fplist_get (p->childp, QCspeed))) + if (NILP (plist_get (p->childp, QCspeed))) return Qnil; serial_configure (p, contact); @@ -3101,17 +3149,17 @@ usage: (make-serial-process &rest ARGS) */) contact = Flist (nargs, args); - port = Fplist_get (contact, QCport); + port = plist_get (contact, QCport); if (NILP (port)) error ("No port specified"); CHECK_STRING (port); - if (NILP (Fplist_member (contact, QCspeed))) + if (NILP (plist_member (contact, QCspeed))) error (":speed not specified"); - if (!NILP (Fplist_get (contact, QCspeed))) - CHECK_FIXNUM (Fplist_get (contact, QCspeed)); + if (!NILP (plist_get (contact, QCspeed))) + CHECK_FIXNUM (plist_get (contact, QCspeed)); - name = Fplist_get (contact, QCname); + name = plist_get (contact, QCname); if (NILP (name)) name = port; CHECK_STRING (name); @@ -3131,23 +3179,23 @@ usage: (make-serial-process &rest ARGS) */) eassert (0 <= fd && fd < FD_SETSIZE); chan_process[fd] = proc; - buffer = Fplist_get (contact, QCbuffer); + buffer = plist_get (contact, QCbuffer); if (NILP (buffer)) buffer = name; buffer = Fget_buffer_create (buffer, Qnil); pset_buffer (p, buffer); pset_childp (p, contact); - pset_plist (p, Fcopy_sequence (Fplist_get (contact, QCplist))); + pset_plist (p, Fcopy_sequence (plist_get (contact, QCplist))); pset_type (p, Qserial); - pset_sentinel (p, Fplist_get (contact, QCsentinel)); - pset_filter (p, Fplist_get (contact, QCfilter)); + pset_sentinel (p, plist_get (contact, QCsentinel)); + pset_filter (p, plist_get (contact, QCfilter)); eassert (NILP (p->log)); - if (tem = Fplist_get (contact, QCnoquery), !NILP (tem)) + if (tem = plist_get (contact, QCnoquery), !NILP (tem)) p->kill_without_query = 1; - if (tem = Fplist_get (contact, QCstop), !NILP (tem)) + if (tem = plist_get (contact, QCstop), !NILP (tem)) pset_command (p, Qt); - eassert (! p->pty_flag); + eassert (! p->pty_in && ! p->pty_out); if (!EQ (p->command, Qt) && !EQ (p->filter, Qt)) @@ -3155,7 +3203,7 @@ usage: (make-serial-process &rest ARGS) */) update_process_mark (p); - tem = Fplist_get (contact, QCcoding); + tem = plist_get (contact, QCcoding); val = Qnil; if (!NILP (tem)) @@ -3209,7 +3257,7 @@ set_network_socket_coding_system (Lisp_Object proc, Lisp_Object host, Lisp_Object coding_systems = Qt; Lisp_Object val; - tem = Fplist_get (contact, QCcoding); + tem = plist_get (contact, QCcoding); /* Setup coding systems for communicating with the network stream. */ /* Qt denotes we have not yet called Ffind_operation_coding_system. */ @@ -3297,8 +3345,8 @@ finish_after_tls_connection (Lisp_Object proc) if (!NILP (Ffboundp (Qnsm_verify_connection))) result = call3 (Qnsm_verify_connection, proc, - Fplist_get (contact, QChost), - Fplist_get (contact, QCservice)); + plist_get (contact, QChost), + plist_get (contact, QCservice)); eassert (p->outfd < FD_SETSIZE); if (NILP (result)) @@ -3479,7 +3527,7 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, if (getsockname (s, psa1, &len1) == 0) { Lisp_Object service = make_fixnum (ntohs (sa1.sin_port)); - contact = Fplist_put (contact, QCservice, service); + contact = plist_put (contact, QCservice, service); /* Save the port number so that we can stash it in the process object later. */ DECLARE_POINTER_ALIAS (psa, struct sockaddr_in, sa); @@ -3570,7 +3618,7 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, { Lisp_Object remote; memset (datagram_address[s].sa, 0, addrlen); - if (remote = Fplist_get (contact, QCremote), !NILP (remote)) + if (remote = plist_get (contact, QCremote), !NILP (remote)) { int rfamily; ptrdiff_t rlen = get_lisp_to_sockaddr_size (remote, &rfamily); @@ -3585,8 +3633,8 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, } #endif - contact = Fplist_put (contact, p->is_server? QClocal: QCremote, - conv_sockaddr_to_lisp (sa, addrlen)); + contact = plist_put (contact, p->is_server? QClocal: QCremote, + conv_sockaddr_to_lisp (sa, addrlen)); #ifdef HAVE_GETSOCKNAME if (!p->is_server) { @@ -3594,8 +3642,8 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, socklen_t len1 = sizeof (sa1); DECLARE_POINTER_ALIAS (psa1, struct sockaddr, &sa1); if (getsockname (s, psa1, &len1) == 0) - contact = Fplist_put (contact, QClocal, - conv_sockaddr_to_lisp (psa1, len1)); + contact = plist_put (contact, QClocal, + conv_sockaddr_to_lisp (psa1, len1)); } #endif } @@ -3908,7 +3956,7 @@ usage: (make-network-process &rest ARGS) */) #endif /* :type TYPE (nil: stream, datagram */ - tem = Fplist_get (contact, QCtype); + tem = plist_get (contact, QCtype); if (NILP (tem)) socktype = SOCK_STREAM; #ifdef DATAGRAM_SOCKETS @@ -3922,13 +3970,13 @@ usage: (make-network-process &rest ARGS) */) else error ("Unsupported connection type"); - name = Fplist_get (contact, QCname); - buffer = Fplist_get (contact, QCbuffer); - filter = Fplist_get (contact, QCfilter); - sentinel = Fplist_get (contact, QCsentinel); - use_external_socket_p = Fplist_get (contact, QCuse_external_socket); - Lisp_Object server = Fplist_get (contact, QCserver); - bool nowait = !NILP (Fplist_get (contact, QCnowait)); + name = plist_get (contact, QCname); + buffer = plist_get (contact, QCbuffer); + filter = plist_get (contact, QCfilter); + sentinel = plist_get (contact, QCsentinel); + use_external_socket_p = plist_get (contact, QCuse_external_socket); + Lisp_Object server = plist_get (contact, QCserver); + bool nowait = !NILP (plist_get (contact, QCnowait)); if (!NILP (server) && nowait) error ("`:server' is incompatible with `:nowait'"); @@ -3936,9 +3984,9 @@ usage: (make-network-process &rest ARGS) */) /* :local ADDRESS or :remote ADDRESS */ if (NILP (server)) - address = Fplist_get (contact, QCremote); + address = plist_get (contact, QCremote); else - address = Fplist_get (contact, QClocal); + address = plist_get (contact, QClocal); if (!NILP (address)) { host = service = Qnil; @@ -3951,7 +3999,7 @@ usage: (make-network-process &rest ARGS) */) } /* :family FAMILY -- nil (for Inet), local, or integer. */ - tem = Fplist_get (contact, QCfamily); + tem = plist_get (contact, QCfamily); if (NILP (tem)) { #ifdef AF_INET6 @@ -3976,10 +4024,10 @@ usage: (make-network-process &rest ARGS) */) error ("Unknown address family"); /* :service SERVICE -- string, integer (port number), or t (random port). */ - service = Fplist_get (contact, QCservice); + service = plist_get (contact, QCservice); /* :host HOST -- hostname, ip address, or 'local for localhost. */ - host = Fplist_get (contact, QChost); + host = plist_get (contact, QChost); if (NILP (host)) { /* The "connection" function gets it bind info from the address we're @@ -4018,7 +4066,7 @@ usage: (make-network-process &rest ARGS) */) if (!NILP (host)) { message (":family local ignores the :host property"); - contact = Fplist_put (contact, QChost, Qnil); + contact = plist_put (contact, QChost, Qnil); host = Qnil; } CHECK_STRING (service); @@ -4172,16 +4220,16 @@ usage: (make-network-process &rest ARGS) */) record_unwind_protect (remove_process, proc); p = XPROCESS (proc); pset_childp (p, contact); - pset_plist (p, Fcopy_sequence (Fplist_get (contact, QCplist))); + pset_plist (p, Fcopy_sequence (plist_get (contact, QCplist))); pset_type (p, Qnetwork); pset_buffer (p, buffer); pset_sentinel (p, sentinel); pset_filter (p, filter); - pset_log (p, Fplist_get (contact, QClog)); - if (tem = Fplist_get (contact, QCnoquery), !NILP (tem)) + pset_log (p, plist_get (contact, QClog)); + if (tem = plist_get (contact, QCnoquery), !NILP (tem)) p->kill_without_query = 1; - if ((tem = Fplist_get (contact, QCstop), !NILP (tem))) + if ((tem = plist_get (contact, QCstop), !NILP (tem))) pset_command (p, Qt); eassert (p->pid == 0); p->backlog = 5; @@ -4193,7 +4241,7 @@ usage: (make-network-process &rest ARGS) */) eassert (! p->dns_request); #endif #ifdef HAVE_GNUTLS - tem = Fplist_get (contact, QCtls_parameters); + tem = plist_get (contact, QCtls_parameters); CHECK_LIST (tem); p->gnutls_boot_parameters = tem; #endif @@ -4641,15 +4689,20 @@ network_lookup_address_info_1 (Lisp_Object host, const char *service, } DEFUN ("network-lookup-address-info", Fnetwork_lookup_address_info, - Snetwork_lookup_address_info, 1, 2, 0, + Snetwork_lookup_address_info, 1, 3, 0, doc: /* Look up Internet Protocol (IP) address info of NAME. -Optional parameter FAMILY controls whether to look up IPv4 or IPv6 +Optional argument FAMILY controls whether to look up IPv4 or IPv6 addresses. The default of nil means both, symbol `ipv4' means IPv4 -only, symbol `ipv6' means IPv6 only. Returns a list of addresses, or -nil if none were found. Each address is a vector of integers, as per -the description of ADDRESS in `make-network-process'. In case of -error displays the error message. */) - (Lisp_Object name, Lisp_Object family) +only, symbol `ipv6' means IPv6 only. +Optional argument HINTS allows specifying the hints passed to the +underlying library call. The only supported value is `numeric', which +means treat NAME as a numeric IP address. This also suppresses DNS +traffic. +Return a list of addresses, or nil if none were found. Each address +is a vector of integers, as per the description of ADDRESS in +`make-network-process'. In case of error log the error message +returned from the lookup. */) + (Lisp_Object name, Lisp_Object family, Lisp_Object hint) { Lisp_Object addresses = Qnil; Lisp_Object msg = Qnil; @@ -4667,9 +4720,14 @@ error displays the error message. */) hints.ai_family = AF_INET6; #endif else - error ("Unsupported lookup type"); + error ("Unsupported family"); hints.ai_socktype = SOCK_DGRAM; + if (EQ (hint, Qnumeric)) + hints.ai_flags = AI_NUMERICHOST; + else if (!NILP (hint)) + error ("Unsupported hints value"); + msg = network_lookup_address_info_1 (name, NULL, &hints, &res); if (!EQ (msg, Qt)) message ("%s", SSDATA(msg)); @@ -4775,7 +4833,7 @@ corresponding connection was closed. */) /* Can't wait for a process that is dedicated to a different thread. */ - if (!NILP (proc->thread) && !EQ (proc->thread, Fcurrent_thread ())) + if (!NILP (proc->thread) && !BASE_EQ (proc->thread, Fcurrent_thread ())) { Lisp_Object proc_thread_name = XTHREAD (proc->thread)->name; @@ -4969,17 +5027,17 @@ server_accept_connection (Lisp_Object server, int channel) /* Build new contact information for this setup. */ contact = Fcopy_sequence (ps->childp); - contact = Fplist_put (contact, QCserver, Qnil); - contact = Fplist_put (contact, QChost, host); + contact = plist_put (contact, QCserver, Qnil); + contact = plist_put (contact, QChost, host); if (!NILP (service)) - contact = Fplist_put (contact, QCservice, service); - contact = Fplist_put (contact, QCremote, - conv_sockaddr_to_lisp (&saddr.sa, len)); + contact = plist_put (contact, QCservice, service); + contact = plist_put (contact, QCremote, + conv_sockaddr_to_lisp (&saddr.sa, len)); #ifdef HAVE_GETSOCKNAME len = sizeof saddr; if (getsockname (s, &saddr.sa, &len) == 0) - contact = Fplist_put (contact, QClocal, - conv_sockaddr_to_lisp (&saddr.sa, len)); + contact = plist_put (contact, QClocal, + conv_sockaddr_to_lisp (&saddr.sa, len)); #endif pset_childp (p, contact); @@ -5492,7 +5550,17 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, triggered by processing X events). In the latter case, set nfds to 1 to avoid breaking the loop. */ no_avail = 0; - if ((read_kbd || !NILP (wait_for_cell)) + if ((read_kbd + /* The following code doesn't make any sense for just the + wait_for_cell case, because detect_input_pending returns + whether or not the keyboard buffer isn't empty or there + is mouse movement. Any keyboard input that arrives + while waiting for a cell will cause the select call to + be skipped, and gobble_input to be called even when + there is no input available from the terminal itself. + Skipping the call to select also causes the timeout to + be ignored. (bug#46935) */ + /* || !NILP (wait_for_cell) */) && detect_input_pending ()) { nfds = read_kbd ? 0 : 1; @@ -6788,7 +6856,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group, error ("Process %s is not active", SDATA (p->name)); - if (!p->pty_flag) + if (! p->pty_in) current_group = Qnil; /* If we are using pgrps, get a pgrp number and make it negative. */ @@ -7099,7 +7167,7 @@ See function `signal-process' for more details on usage. */) } DEFUN ("signal-process", Fsignal_process, Ssignal_process, - 2, 3, "sProcess (name or number): \nnSignal code: ", + 2, 3, "(list (read-string \"Process (name or number): \") (read-signal-name))", doc: /* Send PROCESS the signal with code SIGCODE. PROCESS may also be a number specifying the process id of the process to signal; in this case, the process need not be a child of @@ -7157,7 +7225,7 @@ process has been transmitted to the serial port. */) send_process (proc, "", 0, Qnil); } - if (XPROCESS (proc)->pty_flag) + if (XPROCESS (proc)->pty_in) send_process (proc, "\004", 1, Qnil); else if (EQ (XPROCESS (proc)->type, Qserial)) { @@ -7705,46 +7773,6 @@ DEFUN ("process-coding-system", XPROCESS (process)->encode_coding_system); } -DEFUN ("set-process-filter-multibyte", Fset_process_filter_multibyte, - Sset_process_filter_multibyte, 2, 2, 0, - doc: /* Set multibyteness of the strings given to PROCESS's filter. -If FLAG is non-nil, the filter is given multibyte strings. -If FLAG is nil, the filter is given unibyte strings. In this case, -all character code conversion except for end-of-line conversion is -suppressed. */) - (Lisp_Object process, Lisp_Object flag) -{ - CHECK_PROCESS (process); - - struct Lisp_Process *p = XPROCESS (process); - if (NILP (flag)) - pset_decode_coding_system - (p, raw_text_coding_system (p->decode_coding_system)); - - /* If the sockets haven't been set up yet, the final setup part of - this will be called asynchronously. */ - if (p->infd < 0 || p->outfd < 0) - return Qnil; - - setup_process_coding_systems (process); - - return Qnil; -} - -DEFUN ("process-filter-multibyte-p", Fprocess_filter_multibyte_p, - Sprocess_filter_multibyte_p, 1, 1, 0, - doc: /* Return t if a multibyte string is given to PROCESS's filter.*/) - (Lisp_Object process) -{ - CHECK_PROCESS (process); - struct Lisp_Process *p = XPROCESS (process); - if (p->infd < 0) - return Qnil; - eassert (p->infd < FD_SETSIZE); - struct coding_system *coding = proc_decode_coding_system[p->infd]; - return (CODING_FOR_UNIBYTE (coding) ? Qnil : Qt); -} - @@ -8307,6 +8335,27 @@ If QUERY is `all', also count processors not available. */) #endif } +DEFUN ("signal-names", Fsignal_names, Ssignal_names, 0, 0, 0, + doc: /* Return a list of known signal names on this system. */) + (void) +{ +#ifndef MSDOS + int i; + char name[SIG2STR_MAX]; + Lisp_Object names = Qnil; + + for (i = 0; i <= SIGNUM_BOUND; ++i) + { + if (!sig2str (i, name)) + names = Fcons (build_string (name), names); + } + + return names; +#else + return Qnil; +#endif +} + #ifdef subprocesses /* Arrange to catch SIGCHLD if this hasn't already been arranged. Invoke this after init_process_emacs, and after glib and/or GNUstep @@ -8484,6 +8533,7 @@ syms_of_process (void) #ifdef AF_INET6 DEFSYM (Qipv6, "ipv6"); #endif + DEFSYM (Qnumeric, "numeric"); DEFSYM (Qdatagram, "datagram"); DEFSYM (Qseqpacket, "seqpacket"); @@ -8718,8 +8768,6 @@ sentinel or a process filter function has an error. */); defsubr (&Sinternal_default_process_filter); defsubr (&Sset_process_coding_system); defsubr (&Sprocess_coding_system); - defsubr (&Sset_process_filter_multibyte); - defsubr (&Sprocess_filter_multibyte_p); { Lisp_Object subfeatures = Qnil; @@ -8760,4 +8808,5 @@ sentinel or a process filter function has an error. */); defsubr (&Slist_system_processes); defsubr (&Sprocess_attributes); defsubr (&Snum_processors); + defsubr (&Ssignal_names); } diff --git a/src/process.h b/src/process.h index 392b661ce69..92baf0c4cb9 100644 --- a/src/process.h +++ b/src/process.h @@ -156,8 +156,9 @@ struct Lisp_Process /* True means kill silently if Emacs is exited. This is the inverse of the `query-on-exit' flag. */ bool_bf kill_without_query : 1; - /* True if communicating through a pty. */ - bool_bf pty_flag : 1; + /* True if communicating through a pty for input or output. */ + bool_bf pty_in : 1; + bool_bf pty_out : 1; /* Flag to set coding-system of the process buffer from the coding_system used to decode process output. */ bool_bf inherit_coding_system_flag : 1; diff --git a/src/puresize.h b/src/puresize.h index 5516747ac2b..4b746924bb1 100644 --- a/src/puresize.h +++ b/src/puresize.h @@ -47,7 +47,7 @@ INLINE_HEADER_BEGIN #endif #ifndef BASE_PURESIZE -#define BASE_PURESIZE (2000000 + SYSTEM_PURESIZE_EXTRA + SITELOAD_PURESIZE_EXTRA) +#define BASE_PURESIZE (2750000 + SYSTEM_PURESIZE_EXTRA + SITELOAD_PURESIZE_EXTRA) #endif /* Increase BASE_PURESIZE by a ratio depending on the machine's word size. */ diff --git a/src/regex-emacs.c b/src/regex-emacs.c index 8662fe8d6d0..9b2c14c413d 100644 --- a/src/regex-emacs.c +++ b/src/regex-emacs.c @@ -33,6 +33,7 @@ #include "buffer.h" #include "syntax.h" #include "category.h" +#include "dispextern.h" /* Maximum number of duplicates an interval can allow. Some systems define this in other header files, but we want our value, so remove @@ -3953,6 +3954,9 @@ re_match_2_internal (struct re_pattern_buffer *bufp, and need to test it, it's not garbage. */ re_char *match_end = NULL; + /* This keeps track of how many buffer/string positions we examined. */ + ptrdiff_t nchars = 0; + #ifdef DEBUG_COMPILES_ARGUMENTS /* Counts the total number of registers pushed. */ ptrdiff_t num_regs_pushed = 0; @@ -4209,6 +4213,12 @@ re_match_2_internal (struct re_pattern_buffer *bufp, unbind_to (count, Qnil); SAFE_FREE (); + /* The factor of 50 below is a heuristic that needs to be tuned. It + means we consider 50 buffer positions examined by this function + roughly equivalent to the display engine iterating over a single + buffer position. */ + if (max_redisplay_ticks > 0 && nchars > 0) + update_redisplay_ticks (nchars / 50 + 1, NULL); return dcnt; } @@ -4261,6 +4271,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, p += pat_charlen; d += buf_charlen; mcnt -= pat_charlen; + nchars++; } while (mcnt > 0); else @@ -4298,6 +4309,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, p += pat_charlen; d++; mcnt -= pat_charlen; + nchars++; } while (mcnt > 0); @@ -4321,6 +4333,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, DEBUG_PRINT (" Matched \"%d\".\n", *d); d += buf_charlen; + nchars++; } break; @@ -4373,6 +4386,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, goto fail; d += len; + nchars++; } break; @@ -4492,6 +4506,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, goto fail; } d += dcnt, d2 += dcnt; + nchars++; } } break; @@ -4773,10 +4788,12 @@ re_match_2_internal (struct re_pattern_buffer *bufp, ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1; UPDATE_SYNTAX_TABLE (charpos); GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2); + nchars++; s1 = SYNTAX (c1); UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1); PREFETCH_NOLIMIT (); GET_CHAR_AFTER (c2, d, dummy); + nchars++; s2 = SYNTAX (c2); if (/* Case 2: Only one of S1 and S2 is Sword. */ @@ -4812,6 +4829,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, UPDATE_SYNTAX_TABLE (charpos); PREFETCH (); GET_CHAR_AFTER (c2, d, dummy); + nchars++; s2 = SYNTAX (c2); /* Case 2: S2 is not Sword. */ @@ -4822,6 +4840,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, if (!AT_STRINGS_BEG (d)) { GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2); + nchars++; UPDATE_SYNTAX_TABLE_BACKWARD (charpos - 1); s1 = SYNTAX (c1); @@ -4852,6 +4871,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1; UPDATE_SYNTAX_TABLE (charpos); GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2); + nchars++; s1 = SYNTAX (c1); /* Case 2: S1 is not Sword. */ @@ -4863,6 +4883,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, { PREFETCH_NOLIMIT (); GET_CHAR_AFTER (c2, d, dummy); + nchars++; UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1); s2 = SYNTAX (c2); @@ -4893,6 +4914,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, UPDATE_SYNTAX_TABLE (charpos); PREFETCH (); c2 = RE_STRING_CHAR (d, target_multibyte); + nchars++; s2 = SYNTAX (c2); /* Case 2: S2 is neither Sword nor Ssymbol. */ @@ -4903,6 +4925,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, if (!AT_STRINGS_BEG (d)) { GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2); + nchars++; UPDATE_SYNTAX_TABLE_BACKWARD (charpos - 1); s1 = SYNTAX (c1); @@ -4931,6 +4954,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1; UPDATE_SYNTAX_TABLE (charpos); GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2); + nchars++; s1 = SYNTAX (c1); /* Case 2: S1 is neither Ssymbol nor Sword. */ @@ -4942,6 +4966,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, { PREFETCH_NOLIMIT (); c2 = RE_STRING_CHAR (d, target_multibyte); + nchars++; UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1); s2 = SYNTAX (c2); @@ -4973,6 +4998,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, if ((SYNTAX (c) != (enum syntaxcode) mcnt) ^ not) goto fail; d += len; + nchars++; } } break; @@ -4999,6 +5025,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, if ((!CHAR_HAS_CATEGORY (c, mcnt)) ^ not) goto fail; d += len; + nchars++; } } break; @@ -5060,6 +5087,9 @@ re_match_2_internal (struct re_pattern_buffer *bufp, unbind_to (count, Qnil); SAFE_FREE (); + if (max_redisplay_ticks > 0 && nchars > 0) + update_redisplay_ticks (nchars / 50 + 1, NULL); + return -1; /* Failure to match. */ } diff --git a/src/search.c b/src/search.c index 816a757c188..b5d6a442c0f 100644 --- a/src/search.c +++ b/src/search.c @@ -370,7 +370,6 @@ string_match_1 (Lisp_Object regexp, Lisp_Object string, Lisp_Object start, bool posix, bool modify_data) { ptrdiff_t val; - struct re_pattern_buffer *bufp; EMACS_INT pos; ptrdiff_t pos_byte, i; bool modify_match_data = NILP (Vinhibit_changing_match_data) && modify_data; @@ -401,17 +400,22 @@ string_match_1 (Lisp_Object regexp, Lisp_Object string, Lisp_Object start, set_char_table_extras (BVAR (current_buffer, case_canon_table), 2, BVAR (current_buffer, case_eqv_table)); - bufp = &compile_pattern (regexp, - (modify_match_data ? &search_regs : NULL), - (!NILP (BVAR (current_buffer, case_fold_search)) - ? BVAR (current_buffer, case_canon_table) : Qnil), - posix, - STRING_MULTIBYTE (string))->buf; + specpdl_ref count = SPECPDL_INDEX (); + struct regexp_cache *cache_entry + = compile_pattern (regexp, + modify_match_data ? &search_regs : NULL, + (!NILP (BVAR (current_buffer, case_fold_search)) + ? BVAR (current_buffer, case_canon_table) + : Qnil), + posix, + STRING_MULTIBYTE (string)); + freeze_pattern (cache_entry); re_match_object = string; - val = re_search (bufp, SSDATA (string), + val = re_search (&cache_entry->buf, SSDATA (string), SBYTES (string), pos_byte, SBYTES (string) - pos_byte, (modify_match_data ? &search_regs : NULL)); + unbind_to (count, Qnil); /* Set last_thing_searched only when match data is changed. */ if (modify_match_data) @@ -480,15 +484,15 @@ ptrdiff_t fast_string_match_internal (Lisp_Object regexp, Lisp_Object string, Lisp_Object table) { - ptrdiff_t val; - struct re_pattern_buffer *bufp; - - bufp = &compile_pattern (regexp, 0, table, - 0, STRING_MULTIBYTE (string))->buf; re_match_object = string; - val = re_search (bufp, SSDATA (string), - SBYTES (string), 0, - SBYTES (string), 0); + specpdl_ref count = SPECPDL_INDEX (); + struct regexp_cache *cache_entry + = compile_pattern (regexp, 0, table, 0, STRING_MULTIBYTE (string)); + freeze_pattern (cache_entry); + ptrdiff_t val = re_search (&cache_entry->buf, SSDATA (string), + SBYTES (string), 0, + SBYTES (string), 0); + unbind_to (count, Qnil); return val; } @@ -501,15 +505,14 @@ ptrdiff_t fast_c_string_match_ignore_case (Lisp_Object regexp, const char *string, ptrdiff_t len) { - ptrdiff_t val; - struct re_pattern_buffer *bufp; - regexp = string_make_unibyte (regexp); - bufp = &compile_pattern (regexp, 0, - Vascii_canon_table, 0, - 0)->buf; + specpdl_ref count = SPECPDL_INDEX (); + struct regexp_cache *cache_entry + = compile_pattern (regexp, 0, Vascii_canon_table, 0, 0); + freeze_pattern (cache_entry); re_match_object = Qt; - val = re_search (bufp, string, len, 0, len, 0); + ptrdiff_t val = re_search (&cache_entry->buf, string, len, 0, len, 0); + unbind_to (count, Qnil); return val; } @@ -3189,7 +3192,7 @@ DEFUN ("regexp-quote", Fregexp_quote, Sregexp_quote, 1, 1, 0, } /* Like find_newline, but doesn't use the cache, and only searches forward. */ -static ptrdiff_t +ptrdiff_t find_newline1 (ptrdiff_t start, ptrdiff_t start_byte, ptrdiff_t end, ptrdiff_t end_byte, ptrdiff_t count, ptrdiff_t *counted, ptrdiff_t *bytepos, bool allow_quit) diff --git a/src/sheap.h b/src/sheap.h index 297b7cf317d..cef111bc2f9 100644 --- a/src/sheap.h +++ b/src/sheap.h @@ -23,7 +23,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ /* Size of the static heap. Guess a value that is probably too large, by up to a factor of four or so. Typically the unused part is not paged in and so does not cost much. */ -enum { STATIC_HEAP_SIZE = sizeof (Lisp_Object) << 22 }; +enum { STATIC_HEAP_SIZE = sizeof (Lisp_Object) << 24 }; extern char bss_sbrk_buffer[STATIC_HEAP_SIZE]; extern char *max_bss_sbrk_ptr; diff --git a/src/sound.c b/src/sound.c index 93c84a03b1f..0a307828008 100644 --- a/src/sound.c +++ b/src/sound.c @@ -361,10 +361,10 @@ parse_sound (Lisp_Object sound, Lisp_Object *attrs) return 0; sound = XCDR (sound); - attrs[SOUND_FILE] = Fplist_get (sound, QCfile); - attrs[SOUND_DATA] = Fplist_get (sound, QCdata); - attrs[SOUND_DEVICE] = Fplist_get (sound, QCdevice); - attrs[SOUND_VOLUME] = Fplist_get (sound, QCvolume); + attrs[SOUND_FILE] = plist_get (sound, QCfile); + attrs[SOUND_DATA] = plist_get (sound, QCdata); + attrs[SOUND_DEVICE] = plist_get (sound, QCdevice); + attrs[SOUND_VOLUME] = plist_get (sound, QCvolume); #ifndef WINDOWSNT /* File name or data must be specified. */ diff --git a/src/sqlite.c b/src/sqlite.c index 75a3b2ea32c..54bfb7b6c61 100644 --- a/src/sqlite.c +++ b/src/sqlite.c @@ -246,8 +246,10 @@ If FILE is nil, an in-memory database will be opened instead. */) (Lisp_Object file) { Lisp_Object name; - int flags = (SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX - | SQLITE_OPEN_READWRITE); + int flags = (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE); +#ifdef SQLITE_OPEN_FULLMUTEX + flags |= SQLITE_OPEN_FULLMUTEX; +#endif #ifdef SQLITE_OPEN_URI flags |= SQLITE_OPEN_URI; #endif diff --git a/src/syntax.c b/src/syntax.c index f9022d18d26..15625b4d0e2 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -20,6 +20,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include "lisp.h" +#include "dispextern.h" #include "character.h" #include "buffer.h" #include "regex-emacs.h" @@ -3195,6 +3196,7 @@ scan_sexps_forward (struct lisp_parse_state *state, ptrdiff_t out_bytepos, out_charpos; int temp; unsigned short int quit_count = 0; + ptrdiff_t started_from = from; prev_from = from; prev_from_byte = from_byte; @@ -3474,6 +3476,13 @@ do { prev_from = from; \ state->levelstarts); state->prev_syntax = (SYNTAX_FLAGS_COMSTARTEND_FIRST (prev_from_syntax) || state->quoted) ? prev_from_syntax : Smax; + + /* The factor of 10 below is a heuristic that needs to be tuned. It + means we consider 10 buffer positions examined by this function + roughly equivalent to the display engine iterating over a single + buffer position. */ + if (max_redisplay_ticks > 0 && from > started_from) + update_redisplay_ticks ((from - started_from) / 10 + 1, NULL); } /* Convert a (lisp) parse state to the internal form used in diff --git a/src/sysdep.c b/src/sysdep.c index 95295e7e676..efd9638b07a 100644 --- a/src/sysdep.c +++ b/src/sysdep.c @@ -2939,21 +2939,21 @@ serial_configure (struct Lisp_Process *p, #endif /* Configure speed. */ - if (!NILP (Fplist_member (contact, QCspeed))) - tem = Fplist_get (contact, QCspeed); + if (!NILP (plist_member (contact, QCspeed))) + tem = plist_get (contact, QCspeed); else - tem = Fplist_get (p->childp, QCspeed); + tem = plist_get (p->childp, QCspeed); CHECK_FIXNUM (tem); err = cfsetspeed (&attr, convert_speed (XFIXNUM (tem))); if (err != 0) report_file_error ("Failed cfsetspeed", tem); - childp2 = Fplist_put (childp2, QCspeed, tem); + childp2 = plist_put (childp2, QCspeed, tem); /* Configure bytesize. */ - if (!NILP (Fplist_member (contact, QCbytesize))) - tem = Fplist_get (contact, QCbytesize); + if (!NILP (plist_member (contact, QCbytesize))) + tem = plist_get (contact, QCbytesize); else - tem = Fplist_get (p->childp, QCbytesize); + tem = plist_get (p->childp, QCbytesize); if (NILP (tem)) tem = make_fixnum (8); CHECK_FIXNUM (tem); @@ -2968,13 +2968,13 @@ serial_configure (struct Lisp_Process *p, if (XFIXNUM (tem) != 8) error ("Bytesize cannot be changed"); #endif - childp2 = Fplist_put (childp2, QCbytesize, tem); + childp2 = plist_put (childp2, QCbytesize, tem); /* Configure parity. */ - if (!NILP (Fplist_member (contact, QCparity))) - tem = Fplist_get (contact, QCparity); + if (!NILP (plist_member (contact, QCparity))) + tem = plist_get (contact, QCparity); else - tem = Fplist_get (p->childp, QCparity); + tem = plist_get (p->childp, QCparity); if (!NILP (tem) && !EQ (tem, Qeven) && !EQ (tem, Qodd)) error (":parity must be nil (no parity), `even', or `odd'"); #if defined (PARENB) && defined (PARODD) && defined (IGNPAR) && defined (INPCK) @@ -3001,13 +3001,13 @@ serial_configure (struct Lisp_Process *p, if (!NILP (tem)) error ("Parity cannot be configured"); #endif - childp2 = Fplist_put (childp2, QCparity, tem); + childp2 = plist_put (childp2, QCparity, tem); /* Configure stopbits. */ - if (!NILP (Fplist_member (contact, QCstopbits))) - tem = Fplist_get (contact, QCstopbits); + if (!NILP (plist_member (contact, QCstopbits))) + tem = plist_get (contact, QCstopbits); else - tem = Fplist_get (p->childp, QCstopbits); + tem = plist_get (p->childp, QCstopbits); if (NILP (tem)) tem = make_fixnum (1); CHECK_FIXNUM (tem); @@ -3023,13 +3023,13 @@ serial_configure (struct Lisp_Process *p, if (XFIXNUM (tem) != 1) error ("Stopbits cannot be configured"); #endif - childp2 = Fplist_put (childp2, QCstopbits, tem); + childp2 = plist_put (childp2, QCstopbits, tem); /* Configure flowcontrol. */ - if (!NILP (Fplist_member (contact, QCflowcontrol))) - tem = Fplist_get (contact, QCflowcontrol); + if (!NILP (plist_member (contact, QCflowcontrol))) + tem = plist_get (contact, QCflowcontrol); else - tem = Fplist_get (p->childp, QCflowcontrol); + tem = plist_get (p->childp, QCflowcontrol); if (!NILP (tem) && !EQ (tem, Qhw) && !EQ (tem, Qsw)) error (":flowcontrol must be nil (no flowcontrol), `hw', or `sw'"); #if defined (CRTSCTS) @@ -3063,14 +3063,14 @@ serial_configure (struct Lisp_Process *p, error ("Software flowcontrol (XON/XOFF) not supported"); #endif } - childp2 = Fplist_put (childp2, QCflowcontrol, tem); + childp2 = plist_put (childp2, QCflowcontrol, tem); /* Activate configuration. */ err = tcsetattr (p->outfd, TCSANOW, &attr); if (err != 0) report_file_error ("Failed tcsetattr", Qnil); - childp2 = Fplist_put (childp2, QCsummary, build_string (summary)); + childp2 = plist_put (childp2, QCsummary, build_string (summary)); pset_childp (p, childp2); } #endif /* not DOS_NT */ @@ -3169,7 +3169,8 @@ list_system_processes (void) #endif /* !defined (WINDOWSNT) */ -#if defined __FreeBSD__ || defined DARWIN_OS || defined __OpenBSD__ +#if (HAVE_GETRUSAGE \ + || defined __FreeBSD__ || defined DARWIN_OS || defined __OpenBSD__) static Lisp_Object make_lisp_s_us (time_t s, long us) @@ -3869,7 +3870,7 @@ system_process_attributes (Lisp_Object pid) Lisp_Object system_process_attributes (Lisp_Object pid) { - int proc_id, nentries, fscale, i; + int proc_id, fscale, i; int pagesize = getpagesize (); int mib[6]; size_t len; @@ -4276,7 +4277,7 @@ does the same thing as `current-time'. */) usecs -= 1000000; secs++; } - return make_lisp_time (make_timespec (secs, usecs * 1000)); + return make_lisp_s_us (secs, usecs); #else /* ! HAVE_GETRUSAGE */ #ifdef WINDOWSNT return w32_get_internal_run_time (); diff --git a/src/systime.h b/src/systime.h index 75088bd4a62..085a7ddeaba 100644 --- a/src/systime.h +++ b/src/systime.h @@ -26,6 +26,9 @@ INLINE_HEADER_BEGIN #ifdef HAVE_X_WINDOWS # include <X11/X.h> +#elif defined HAVE_HAIKU +# include <support/SupportDefs.h> +typedef int64 Time; #else typedef unsigned long Time; #endif diff --git a/src/term.c b/src/term.c index 3bea621dbda..2e43d89232f 100644 --- a/src/term.c +++ b/src/term.c @@ -1862,12 +1862,24 @@ produce_glyphless_glyph (struct it *it, Lisp_Object acronym) acronym = CHAR_TABLE_REF (Vglyphless_char_display, it->c); if (CONSP (acronym)) acronym = XCDR (acronym); - buf[0] = '['; str = STRINGP (acronym) ? SSDATA (acronym) : ""; - for (len = 0; len < 6 && str[len] && ASCII_CHAR_P (str[len]); len++) - buf[1 + len] = str[len]; - buf[1 + len] = ']'; - len += 2; + /* A special kludgey feature for single-character acronyms: + don't put them in a box, effectively treating them as a + replacement character. */ + if (STRINGP (acronym) && SCHARS (acronym) == 1) + { + buf[0] = str[0]; + len = 1; + } + else + { + buf[0] = '['; + for (len = 0; + len < 6 && str[len] && ASCII_CHAR_P (str[len]); len++) + buf[1 + len] = str[len]; + buf[1 + len] = ']'; + len += 2; + } } else { diff --git a/src/termhooks.h b/src/termhooks.h index d7190e77362..c5f1e286e92 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -209,30 +209,6 @@ enum event_kind representation of the dropped items. .timestamp gives a timestamp (in milliseconds) for the click. */ -#ifdef HAVE_X_WINDOWS - UNSUPPORTED_DROP_EVENT, /* Event sent when the regular C - drag-and-drop machinery could not - handle a drop to a window. - - .code is the XID of the window that - could not be dropped to. - - .arg is a list of the local value of - XdndSelection, a list of selection - targets, and the intended action to - be taken upon drop, and .timestamp - gives the timestamp where the drop - happened. - - .modifiers gives a number that - determines if an event was already - handled by - `x_dnd_begin_drag_and_drop'. - - .x and .y give the coordinates of - the drop originating from the root - window. */ -#endif USER_SIGNAL_EVENT, /* A user signal. code is a number identifying it, index into lispy_user_signals. */ @@ -877,6 +853,13 @@ struct terminal MENU_BAR_P if X and Y are in FRAME's toolkit menu bar, and true into TOOL_BAR_P if X and Y are in FRAME's toolkit tool bar. */ void (*toolkit_position_hook) (struct frame *, int, int, bool *, bool *); + +#ifdef HAVE_WINDOW_SYSTEM + /* Called to determine if the mouse is grabbed on the given display. + If either dpyinfo->grabbed or this returns true, then the display + will be considered as grabbed. */ + bool (*any_grab_hook) (Display_Info *); +#endif } GCALIGNED_STRUCT; INLINE bool diff --git a/src/terminal.c b/src/terminal.c index dcde8e9f557..d366e9d2438 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -402,7 +402,7 @@ but if the second argument FORCE is non-nil, you may do so. */) DEFUN ("frame-terminal", Fframe_terminal, Sframe_terminal, 0, 1, 0, doc: /* Return the terminal that FRAME is displayed on. -If FRAME is nil, the selected frame is used. +If FRAME is nil, use the selected frame. The terminal device is represented by its integer identifier. */) (Lisp_Object frame) @@ -421,10 +421,12 @@ The terminal device is represented by its integer identifier. */) DEFUN ("terminal-live-p", Fterminal_live_p, Sterminal_live_p, 1, 1, 0, doc: /* Return non-nil if OBJECT is a terminal which has not been deleted. -Value is nil if OBJECT is not a live display terminal. -If object is a live display terminal, the return value indicates what -sort of output terminal it uses. See the documentation of `framep' for -possible return values. */) +Return nil if OBJECT is not a live display terminal. +OBJECT may be a terminal object, a frame, or nil (meaning the +selected frame's terminal). +If OBJECT is a live display terminal, return what sort of output +terminal it uses. See the documentation of `framep' for possible +return values. */) (Lisp_Object object) { struct terminal *t = decode_terminal (object); diff --git a/src/textprop.c b/src/textprop.c index c11ee98f020..c91a2b729c6 100644 --- a/src/textprop.c +++ b/src/textprop.c @@ -88,7 +88,7 @@ modify_text_properties (Lisp_Object buffer, Lisp_Object start, Lisp_Object end) BUF_COMPUTE_UNCHANGED (buf, b - 1, e); if (MODIFF <= SAVE_MODIFF) record_first_change (); - modiff_incr (&MODIFF); + modiff_incr (&MODIFF, 1); bset_point_before_scroll (current_buffer, Qnil); @@ -1407,8 +1407,8 @@ set_text_properties (Lisp_Object start, Lisp_Object end, Lisp_Object properties, /* If we want no properties for a whole string, get rid of its intervals. */ if (NILP (properties) && STRINGP (object) - && EQ (start, make_fixnum (0)) - && EQ (end, make_fixnum (SCHARS (object)))) + && BASE_EQ (start, make_fixnum (0)) + && BASE_EQ (end, make_fixnum (SCHARS (object)))) { if (!string_intervals (object)) return Qnil; @@ -2249,7 +2249,7 @@ verify_interval_modification (struct buffer *buf, tem = textget (i->plist, Qfront_sticky); if (TMEM (Qread_only, tem) - || (NILP (Fplist_get (i->plist, Qread_only)) + || (NILP (plist_get (i->plist, Qread_only)) && TMEM (Qcategory, tem))) text_read_only (after); } @@ -2269,7 +2269,7 @@ verify_interval_modification (struct buffer *buf, tem = textget (prev->plist, Qrear_nonsticky); if (! TMEM (Qread_only, tem) - && (! NILP (Fplist_get (prev->plist,Qread_only)) + && (! NILP (plist_get (prev->plist,Qread_only)) || ! TMEM (Qcategory, tem))) text_read_only (before); } @@ -2288,13 +2288,13 @@ verify_interval_modification (struct buffer *buf, tem = textget (i->plist, Qfront_sticky); if (TMEM (Qread_only, tem) - || (NILP (Fplist_get (i->plist, Qread_only)) + || (NILP (plist_get (i->plist, Qread_only)) && TMEM (Qcategory, tem))) text_read_only (after); tem = textget (prev->plist, Qrear_nonsticky); if (! TMEM (Qread_only, tem) - && (! NILP (Fplist_get (prev->plist, Qread_only)) + && (! NILP (plist_get (prev->plist, Qread_only)) || ! TMEM (Qcategory, tem))) text_read_only (after); } diff --git a/src/timefns.c b/src/timefns.c index 7d2e3f64143..eed2edf1cc0 100644 --- a/src/timefns.c +++ b/src/timefns.c @@ -212,7 +212,7 @@ tzlookup (Lisp_Object zone, bool settz) if (NILP (zone)) return local_tz; - else if (EQ (zone, Qt) || EQ (zone, make_fixnum (0))) + else if (BASE_EQ (zone, make_fixnum (0)) || BASE2_EQ (zone, Qt)) { zone_string = "UTC0"; new_tz = utc_tz; @@ -221,7 +221,7 @@ tzlookup (Lisp_Object zone, bool settz) { bool plain_integer = FIXNUMP (zone); - if (EQ (zone, Qwall)) + if (BASE2_EQ (zone, Qwall)) zone_string = 0; else if (STRINGP (zone)) zone_string = SSDATA (ENCODE_SYSTEM (zone)); @@ -387,9 +387,9 @@ enum { flt_radix_power_size = DBL_MANT_DIG - DBL_MIN_EXP + 1 }; equals FLT_RADIX**P. */ static Lisp_Object flt_radix_power; -/* Convert T into an Emacs time *RESULT, truncating toward minus infinity. - Return zero if successful, an error number otherwise. */ -static int +/* Convert the finite number T into an Emacs time *RESULT, truncating + toward minus infinity. Signal an error if unsuccessful. */ +static void decode_float_time (double t, struct lisp_time *result) { Lisp_Object ticks, hz; @@ -401,6 +401,11 @@ decode_float_time (double t, struct lisp_time *result) else { int scale = double_integer_scale (t); + /* FIXME: `double_integer_scale` often returns values that are + "pessimistic" (i.e. larger than necessary), so 3.5 gets converted + to (7881299347898368 . 2251799813685248) rather than (7 . 2). + On 64bit systems, this should not matter very much, tho. */ + eassume (scale < flt_radix_power_size); if (scale < 0) { @@ -412,8 +417,6 @@ decode_float_time (double t, struct lisp_time *result) which is typically better than signaling overflow. */ scale = 0; } - else if (flt_radix_power_size <= scale) - return isnan (t) ? EDOM : EOVERFLOW; /* Compute TICKS, HZ such that TICKS / HZ exactly equals T, where HZ is T's frequency or 1, whichever is greater. Here, “frequency” means @@ -431,7 +434,6 @@ decode_float_time (double t, struct lisp_time *result) } result->ticks = ticks; result->hz = hz; - return 0; } /* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ. @@ -516,7 +518,7 @@ lisp_time_hz_ticks (struct lisp_time t, Lisp_Object hz) /* The idea is to return the floor of ((T.ticks * HZ) / T.hz). */ /* For speed, just return T.ticks if T.hz == HZ. */ - if (FASTER_TIMEFNS && EQ (t.hz, hz)) + if (FASTER_TIMEFNS && BASE_EQ (t.hz, hz)) return t.ticks; /* Check HZ for validity. */ @@ -705,7 +707,7 @@ enum timeform TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */ }; -/* From the valid form FORM and the time components HIGH, LOW, USEC +/* From the non-float form FORM and the time components HIGH, LOW, USEC and PSEC, generate the corresponding time value. If LOW is floating point, the other components should be zero and FORM should not be TIMEFORM_TICKS_HZ. @@ -729,21 +731,12 @@ decode_time_components (enum timeform form, case TIMEFORM_TICKS_HZ: if (INTEGERP (high) - && (!NILP (Fnatnump (low)) && !EQ (low, make_fixnum (0)))) + && !NILP (Fnatnump (low)) && !BASE_EQ (low, make_fixnum (0))) return decode_ticks_hz (high, low, result, dresult); return EINVAL; case TIMEFORM_FLOAT: - { - double t = XFLOAT_DATA (low); - if (result) - return decode_float_time (t, result); - else - { - *dresult = t; - return 0; - } - } + eassume (false); case TIMEFORM_NIL: return decode_ticks_hz (timespec_ticks (current_timespec ()), @@ -829,8 +822,6 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only, if (NILP (specified_time)) form = TIMEFORM_NIL; - else if (FLOATP (specified_time)) - form = TIMEFORM_FLOAT; else if (CONSP (specified_time)) { high = XCAR (specified_time); @@ -870,6 +861,22 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only, if (! INTEGERP (low)) form = TIMEFORM_INVALID; } + else if (FASTER_TIMEFNS && INTEGERP (specified_time)) + { + decode_ticks_hz (specified_time, make_fixnum (1), result, dresult); + return form; + } + else if (FLOATP (specified_time)) + { + double d = XFLOAT_DATA (specified_time); + if (!isfinite (d)) + time_error (isnan (d) ? EDOM : EOVERFLOW); + if (result) + decode_float_time (d, result); + else + *dresult = d; + return TIMEFORM_FLOAT; + } int err = decode_time_components (form, high, low, usec, psec, result, dresult); @@ -878,7 +885,7 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only, return form; } -/* Convert a Lisp timestamp SPECIFIED_TIME to double. +/* Convert a non-float Lisp timestamp SPECIFIED_TIME to double. Signal an error if unsuccessful. */ double float_time (Lisp_Object specified_time) @@ -923,7 +930,7 @@ lisp_to_timespec (struct lisp_time t) yielding quotient Q (tv_sec) and remainder NS (tv_nsec). Return an invalid timespec if Q does not fit in time_t. For speed, prefer fixnum arithmetic if it works. */ - if (FASTER_TIMEFNS && EQ (t.hz, timespec_hz)) + if (FASTER_TIMEFNS && BASE_EQ (t.hz, timespec_hz)) { if (FIXNUMP (t.ticks)) { @@ -942,7 +949,7 @@ lisp_to_timespec (struct lisp_time t) else ns = mpz_fdiv_q_ui (*q, *xbignum_val (t.ticks), TIMESPEC_HZ); } - else if (FASTER_TIMEFNS && EQ (t.hz, make_fixnum (1))) + else if (FASTER_TIMEFNS && BASE_EQ (t.hz, make_fixnum (1))) { ns = 0; if (FIXNUMP (t.ticks)) @@ -1043,7 +1050,7 @@ lispint_arith (Lisp_Object a, Lisp_Object b, bool subtract) if (FASTER_TIMEFNS && FIXNUMP (b)) { - if (EQ (b, make_fixnum (0))) + if (BASE_EQ (b, make_fixnum (0))) return a; /* For speed, use EMACS_INT arithmetic if it will do. */ @@ -1074,30 +1081,12 @@ lispint_arith (Lisp_Object a, Lisp_Object b, bool subtract) static Lisp_Object time_arith (Lisp_Object a, Lisp_Object b, bool subtract) { - if (FLOATP (a) && !isfinite (XFLOAT_DATA (a))) - { - double da = XFLOAT_DATA (a); - double db = float_time (b); - return make_float (subtract ? da - db : da + db); - } enum timeform aform, bform; struct lisp_time ta = lisp_time_struct (a, &aform); - - if (FLOATP (b) && !isfinite (XFLOAT_DATA (b))) - return subtract ? make_float (-XFLOAT_DATA (b)) : b; - - /* Subtract nil from nil correctly, and handle other eq values - quicker while we're at it. Compare here rather than earlier, to - handle NaNs and check formats. */ - struct lisp_time tb; - if (EQ (a, b)) - bform = aform, tb = ta; - else - tb = lisp_time_struct (b, &bform); - + struct lisp_time tb = lisp_time_struct (b, &bform); Lisp_Object ticks, hz; - if (FASTER_TIMEFNS && EQ (ta.hz, tb.hz)) + if (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz)) { hz = ta.hz; ticks = lispint_arith (ta.ticks, tb.ticks, subtract); @@ -1175,7 +1164,7 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract) either input used (TICKS . HZ) form or the result can't be expressed exactly in (HI LO US PS) form, otherwise the (HI LO US PS) form for backward compatibility. */ - return (EQ (hz, make_fixnum (1)) + return (BASE_EQ (hz, make_fixnum (1)) ? ticks : (!current_time_list || aform == TIMEFORM_TICKS_HZ @@ -1201,37 +1190,45 @@ See `format-time-string' for the various forms of a time value. For example, nil stands for the current time. */) (Lisp_Object a, Lisp_Object b) { + /* Subtract nil from nil correctly, and handle other eq values + quicker while we're at it. This means (time-subtract X X) does + not signal an error if X is not a valid time value, but that's OK. */ + if (BASE_EQ (a, b)) + return timespec_to_lisp ((struct timespec) {0}); + return time_arith (a, b, true); } -/* Return negative, 0, positive if a < b, a == b, a > b respectively. - Return positive if either a or b is a NaN; this is good enough - for the current callers. */ -static int +/* Return negative, 0, positive if A < B, A == B, A > B respectively. + A and B should be Lisp time values. */ +static EMACS_INT time_cmp (Lisp_Object a, Lisp_Object b) { - if ((FLOATP (a) && !isfinite (XFLOAT_DATA (a))) - || (FLOATP (b) && !isfinite (XFLOAT_DATA (b)))) - { - double da = FLOATP (a) ? XFLOAT_DATA (a) : 0; - double db = FLOATP (b) ? XFLOAT_DATA (b) : 0; - return da < db ? -1 : da != db; - } - /* Compare nil to nil correctly, and handle other eq values quicker - while we're at it. Compare here rather than earlier, to handle - NaNs. This means (time-equal-p X X) does not signal an error if - X is not a valid time value, but that's OK. */ - if (EQ (a, b)) + while we're at it. This means (time-equal-p X X) does not signal + an error if X is not a valid time value, but that's OK. */ + if (BASE_EQ (a, b)) return 0; + /* Compare (X . Z) to (Y . Z) quickly if X and Y are fixnums. + Do not inspect Z, as it is OK to not signal if A and B are invalid. + Also, compare X to Y quickly if X and Y are fixnums. */ + if (FASTER_TIMEFNS) + { + Lisp_Object x = a, y = b; + if (CONSP (a) && CONSP (b) && BASE_EQ (XCDR (a), XCDR (b))) + x = XCAR (a), y = XCAR (b); + if (FIXNUMP (x) && FIXNUMP (y)) + return XFIXNUM (x) - XFIXNUM (y); + } + /* Compare (ATICKS . AZ) to (BTICKS . BHZ) by comparing ATICKS * BHZ to BTICKS * AHZ. */ struct lisp_time ta = lisp_time_struct (a, 0); struct lisp_time tb = lisp_time_struct (b, 0); mpz_t const *za = bignum_integer (&mpz[0], ta.ticks); mpz_t const *zb = bignum_integer (&mpz[1], tb.ticks); - if (! (FASTER_TIMEFNS && EQ (ta.hz, tb.hz))) + if (! (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz))) { /* This could be sped up by looking at the signs, sizes, and number of bits of the two sides; see how GMP does mpq_cmp. @@ -1258,7 +1255,9 @@ DEFUN ("time-equal-p", Ftime_equal_p, Stime_equal_p, 2, 2, 0, See `format-time-string' for the various forms of a time value. */) (Lisp_Object a, Lisp_Object b) { - return time_cmp (a, b) == 0 ? Qt : Qnil; + /* A nil arg compares unequal to a non-nil arg. This also saves the + expense of current_timespec if either arg is nil. */ + return NILP (a) == NILP (b) && time_cmp (a, b) == 0 ? Qt : Qnil; } @@ -1269,11 +1268,12 @@ instead of the current time. See `format-time-string' for the various forms of a time value. WARNING: Since the result is floating point, it may not be exact. -If precise time stamps are required, use either `encode-time', +If precise time stamps are required, use either `time-convert', or (if you need time as a string) `format-time-string'. */) (Lisp_Object specified_time) { - return make_float (float_time (specified_time)); + return (FLOATP (specified_time) ? specified_time + : make_float (float_time (specified_time))); } /* Write information into buffer S of size MAXSIZE, according to the @@ -1463,7 +1463,7 @@ usage: (format-time-string FORMAT-STRING &optional TIME ZONE) */) } DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0, - doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF). + doc: /* Decode a timestamp into (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF). The optional TIME is the time value to convert. See `format-time-string' for the various forms of a time value. @@ -1535,7 +1535,7 @@ usage: (decode-time &optional TIME ZONE FORM) */) /* Compute SEC from LOCAL_TM.tm_sec and HZ. */ Lisp_Object hz = lt.hz, sec; - if (EQ (hz, make_fixnum (1)) || !EQ (form, Qt)) + if (BASE_EQ (hz, make_fixnum (1)) || !BASE2_EQ (form, Qt)) sec = make_fixnum (local_tm.tm_sec); else { @@ -1685,7 +1685,7 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) struct lisp_time lt; decode_lisp_time (secarg, false, <, 0); Lisp_Object hz = lt.hz, sec, subsecticks; - if (FASTER_TIMEFNS && EQ (hz, make_fixnum (1))) + if (FASTER_TIMEFNS && BASE_EQ (hz, make_fixnum (1))) { sec = lt.ticks; subsecticks = make_fixnum (0); @@ -1715,7 +1715,7 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) if (tm.tm_wday < 0) time_error (mktime_errno); - if (EQ (hz, make_fixnum (1))) + if (BASE_EQ (hz, make_fixnum (1))) return (current_time_list ? list2 (hi_time (value), lo_time (value)) : INT_TO_INTEGER (value)); @@ -1729,33 +1729,43 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) } DEFUN ("time-convert", Ftime_convert, Stime_convert, 1, 2, 0, - doc: /* Convert TIME value to a Lisp timestamp. -With optional FORM, convert to that timestamp form. + doc: /* Convert TIME value to a Lisp timestamp of the given FORM. Truncate the returned value toward minus infinity. -If FORM is nil (the default), return the same form as `current-time'. If FORM is a positive integer, return a pair of integers (TICKS . FORM), where TICKS is the number of clock ticks and FORM is the clock frequency -in ticks per second. If FORM is t, return (TICKS . PHZ), where -PHZ is a suitable clock frequency in ticks per second. If FORM is -`integer', return an integer count of seconds. If FORM is `list', -return an integer list (HIGH LOW USEC PSEC), where HIGH has the most -significant bits of the seconds, LOW has the least significant 16 -bits, and USEC and PSEC are the microsecond and picosecond counts. */) +in ticks per second. + +If FORM is t, return (TICKS . PHZ), where PHZ is a suitable clock +frequency in ticks per second. + +If FORM is `integer', return an integer count of seconds. + +If FORM is `list', return an integer list (HIGH LOW USEC PSEC), where +HIGH has the most significant bits of the seconds, LOW has the least +significant 16 bits, and USEC and PSEC are the microsecond and +picosecond counts. + +If FORM is nil, the behavior depends on `current-time-list', +but new code should not rely on it. */) (Lisp_Object time, Lisp_Object form) { + /* FIXME: Any reason why we don't offer a `float` output format option as + well, since we accept it as input? */ struct lisp_time t; enum timeform input_form = decode_lisp_time (time, false, &t, 0); if (NILP (form)) form = current_time_list ? Qlist : Qt; - if (EQ (form, Qlist)) + if (symbols_with_pos_enabled && SYMBOL_WITH_POS_P (form)) + form = SYMBOL_WITH_POS_SYM (form); + if (BASE_EQ (form, Qlist)) return ticks_hz_list4 (t.ticks, t.hz); - if (EQ (form, Qinteger)) + if (BASE_EQ (form, Qinteger)) return FASTER_TIMEFNS && INTEGERP (time) ? time : lisp_time_seconds (t); - if (EQ (form, Qt)) + if (BASE_EQ (form, Qt)) form = t.hz; if (FASTER_TIMEFNS - && input_form == TIMEFORM_TICKS_HZ && EQ (form, XCDR (time))) + && input_form == TIMEFORM_TICKS_HZ && BASE_EQ (form, XCDR (time))) return time; return Fcons (lisp_time_hz_ticks (t, form), form); } diff --git a/src/undo.c b/src/undo.c index 36664d16424..f76977dbe50 100644 --- a/src/undo.c +++ b/src/undo.c @@ -218,7 +218,7 @@ record_first_change (void) base_buffer = base_buffer->base_buffer; bset_undo_list (current_buffer, - Fcons (Fcons (Qt, Fvisited_file_modtime ()), + Fcons (Fcons (Qt, buffer_visited_file_modtime (base_buffer)), BVAR (current_buffer, undo_list))); } diff --git a/src/w32.c b/src/w32.c index 590d9e85d93..44c279602cf 100644 --- a/src/w32.c +++ b/src/w32.c @@ -5992,12 +5992,22 @@ sys_umask (int mode) #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY #define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 #endif +#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE +#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 +#endif int symlink (char const *filename, char const *linkname) { char linkfn[MAX_UTF8_PATH], *tgtfn; - DWORD flags = 0; + /* The SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag is + supported from Windows 10 build 14972. It is only supported if + Developer Mode is enabled, and is ignored if it isn't. */ + DWORD flags = + (os_subtype == OS_SUBTYPE_NT + && (w32_major_version > 10 + || (w32_major_version == 10 && w32_build_number >= 14972))) + ? SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE : 0; int dir_access, filename_ends_in_slash; /* Diagnostics follows Posix as much as possible. */ @@ -6055,7 +6065,7 @@ symlink (char const *filename, char const *linkname) directory. */ filename_ends_in_slash = IS_DIRECTORY_SEP (filename[strlen (filename) - 1]); if (dir_access == 0 || filename_ends_in_slash) - flags = SYMBOLIC_LINK_FLAG_DIRECTORY; + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; tgtfn = (char *)map_w32_filename (filename, NULL); if (filename_ends_in_slash) @@ -8573,6 +8583,7 @@ int sys_close (int fd) { int rc = -1; + bool reader_thread_exited = false; if (fd < 0) { @@ -8583,6 +8594,13 @@ sys_close (int fd) if (fd < MAXDESC && fd_info[fd].cp) { child_process * cp = fd_info[fd].cp; + DWORD thrd_status = STILL_ACTIVE; + + /* Thread handle will be NULL if we already called delete_child. */ + if (cp->thrd != NULL + && GetExitCodeThread (cp->thrd, &thrd_status) + && thrd_status != STILL_ACTIVE) + reader_thread_exited = true; fd_info[fd].cp = NULL; @@ -8633,7 +8651,11 @@ sys_close (int fd) because socket handles are fully fledged kernel handles. */ if (fd < MAXDESC) { - if ((fd_info[fd].flags & FILE_DONT_CLOSE) == 0) + if ((fd_info[fd].flags & FILE_DONT_CLOSE) == 0 + /* If the reader thread already exited, close the descriptor, + since otherwise no one will close it, and we will be + leaking descriptors. */ + || reader_thread_exited) { fd_info[fd].flags = 0; rc = _close (fd); @@ -8641,10 +8663,11 @@ sys_close (int fd) else { /* We don't close here descriptors open by pipe processes - for reading from the pipe, because the reader thread - might be stuck in _sys_read_ahead, and then we will hang - here. If the reader thread exits normally, it will close - the descriptor; otherwise we will leave a zombie thread + for reading from the pipe when the reader thread might + still be running, since that thread might be stuck in + _sys_read_ahead, and then we will hang here. If the + reader thread exits normally, it will close the + descriptor; otherwise we will leave a zombie thread hanging around. */ rc = 0; /* Leave the flag set for the reader thread to close the @@ -10953,19 +10976,19 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact) dcb.EvtChar = 0; /* Configure speed. */ - if (!NILP (Fplist_member (contact, QCspeed))) - tem = Fplist_get (contact, QCspeed); + if (!NILP (plist_member (contact, QCspeed))) + tem = plist_get (contact, QCspeed); else - tem = Fplist_get (p->childp, QCspeed); + tem = plist_get (p->childp, QCspeed); CHECK_FIXNUM (tem); dcb.BaudRate = XFIXNUM (tem); - childp2 = Fplist_put (childp2, QCspeed, tem); + childp2 = plist_put (childp2, QCspeed, tem); /* Configure bytesize. */ - if (!NILP (Fplist_member (contact, QCbytesize))) - tem = Fplist_get (contact, QCbytesize); + if (!NILP (plist_member (contact, QCbytesize))) + tem = plist_get (contact, QCbytesize); else - tem = Fplist_get (p->childp, QCbytesize); + tem = plist_get (p->childp, QCbytesize); if (NILP (tem)) tem = make_fixnum (8); CHECK_FIXNUM (tem); @@ -10973,13 +10996,13 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact) error (":bytesize must be nil (8), 7, or 8"); dcb.ByteSize = XFIXNUM (tem); summary[0] = XFIXNUM (tem) + '0'; - childp2 = Fplist_put (childp2, QCbytesize, tem); + childp2 = plist_put (childp2, QCbytesize, tem); /* Configure parity. */ - if (!NILP (Fplist_member (contact, QCparity))) - tem = Fplist_get (contact, QCparity); + if (!NILP (plist_member (contact, QCparity))) + tem = plist_get (contact, QCparity); else - tem = Fplist_get (p->childp, QCparity); + tem = plist_get (p->childp, QCparity); if (!NILP (tem) && !EQ (tem, Qeven) && !EQ (tem, Qodd)) error (":parity must be nil (no parity), `even', or `odd'"); dcb.fParity = FALSE; @@ -11003,13 +11026,13 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact) dcb.Parity = ODDPARITY; dcb.fErrorChar = TRUE; } - childp2 = Fplist_put (childp2, QCparity, tem); + childp2 = plist_put (childp2, QCparity, tem); /* Configure stopbits. */ - if (!NILP (Fplist_member (contact, QCstopbits))) - tem = Fplist_get (contact, QCstopbits); + if (!NILP (plist_member (contact, QCstopbits))) + tem = plist_get (contact, QCstopbits); else - tem = Fplist_get (p->childp, QCstopbits); + tem = plist_get (p->childp, QCstopbits); if (NILP (tem)) tem = make_fixnum (1); CHECK_FIXNUM (tem); @@ -11020,13 +11043,13 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact) dcb.StopBits = ONESTOPBIT; else if (XFIXNUM (tem) == 2) dcb.StopBits = TWOSTOPBITS; - childp2 = Fplist_put (childp2, QCstopbits, tem); + childp2 = plist_put (childp2, QCstopbits, tem); /* Configure flowcontrol. */ - if (!NILP (Fplist_member (contact, QCflowcontrol))) - tem = Fplist_get (contact, QCflowcontrol); + if (!NILP (plist_member (contact, QCflowcontrol))) + tem = plist_get (contact, QCflowcontrol); else - tem = Fplist_get (p->childp, QCflowcontrol); + tem = plist_get (p->childp, QCflowcontrol); if (!NILP (tem) && !EQ (tem, Qhw) && !EQ (tem, Qsw)) error (":flowcontrol must be nil (no flowcontrol), `hw', or `sw'"); dcb.fOutxCtsFlow = FALSE; @@ -11053,13 +11076,13 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact) dcb.fOutX = TRUE; dcb.fInX = TRUE; } - childp2 = Fplist_put (childp2, QCflowcontrol, tem); + childp2 = plist_put (childp2, QCflowcontrol, tem); /* Activate configuration. */ if (!SetCommState (hnd, &dcb)) error ("SetCommState() failed"); - childp2 = Fplist_put (childp2, QCsummary, build_string (summary)); + childp2 = plist_put (childp2, QCsummary, build_string (summary)); pset_childp (p, childp2); } diff --git a/src/w32fns.c b/src/w32fns.c index 8716b762eb0..28d13a68d45 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -797,13 +797,6 @@ w32_default_color_map (void) return (cmap); } -DEFUN ("w32-default-color-map", Fw32_default_color_map, Sw32_default_color_map, - 0, 0, 0, doc: /* Return the default color map. */) - (void) -{ - return w32_default_color_map (); -} - static Lisp_Object w32_color_map_lookup (const char *colorname) { @@ -1463,7 +1456,7 @@ w32_set_icon_type (struct frame *f, Lisp_Object arg, Lisp_Object oldval) return; if (STRINGP (arg) && STRINGP (oldval) - && EQ (Fstring_equal (oldval, arg), Qt)) + && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; if (SYMBOLP (arg) && SYMBOLP (oldval) && EQ (arg, oldval)) @@ -1486,7 +1479,7 @@ w32_set_icon_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval) { if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!NILP (arg) || NILP (oldval)) @@ -7575,7 +7568,23 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, clear_glyph_matrix (w->desired_matrix); clear_glyph_matrix (w->current_matrix); SET_TEXT_POS (pos, BEGV, BEGV_BYTE); - try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + bool displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + struct glyph_row *row = w->desired_matrix->rows; + struct glyph_row *end = + w->desired_matrix->rows + w->desired_matrix->nrows; + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + eassert (row < end && row->ends_at_zv_p); +#endif + } /* Calculate size of tooltip window. */ size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, make_fixnum (w->pixel_height), Qnil, @@ -10212,21 +10221,21 @@ usage: (w32-notification-notify &rest PARAMS) */) arg_plist = Flist (nargs, args); /* Icon. */ - lres = Fplist_get (arg_plist, QCicon); + lres = plist_get (arg_plist, QCicon); if (STRINGP (lres)) icon = SSDATA (ENCODE_FILE (Fexpand_file_name (lres, Qnil))); else icon = (char *)""; /* Tip. */ - lres = Fplist_get (arg_plist, QCtip); + lres = plist_get (arg_plist, QCtip); if (STRINGP (lres)) tip = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1)); else tip = (char *)"Emacs notification"; /* Severity. */ - lres = Fplist_get (arg_plist, QClevel); + lres = plist_get (arg_plist, QClevel); if (NILP (lres)) severity = Ni_None; else if (EQ (lres, Qinfo)) @@ -10239,14 +10248,14 @@ usage: (w32-notification-notify &rest PARAMS) */) severity = Ni_Info; /* Title. */ - lres = Fplist_get (arg_plist, QCtitle); + lres = plist_get (arg_plist, QCtitle); if (STRINGP (lres)) title = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1)); else title = (char *)""; /* Notification body text. */ - lres = Fplist_get (arg_plist, QCbody); + lres = plist_get (arg_plist, QCbody); if (STRINGP (lres)) msg = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1)); else @@ -10499,6 +10508,7 @@ frame_parm_handler w32_frame_parm_handlers[] = 0, /* x_set_override_redirect */ gui_set_no_special_glyphs, gui_set_alpha_background, + 0, /* x_set_use_frame_synchronization */ }; void @@ -10777,7 +10787,7 @@ bass-down, bass-boost, bass-up, treble-down, treble-up */); DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, doc: /* SKIP: real doc in xfns.c. */); - Vx_max_tooltip_size = Fcons (make_fixnum (80), make_fixnum (40)); + Vx_max_tooltip_size = Qnil; DEFVAR_LISP ("x-no-window-manager", Vx_no_window_manager, doc: /* SKIP: real doc in xfns.c. */); @@ -10879,7 +10889,6 @@ keys when IME input is received. */); /* W32 specific functions */ defsubr (&Sw32_define_rgb_color); - defsubr (&Sw32_default_color_map); defsubr (&Sw32_display_monitor_attributes_list); defsubr (&Sw32_send_sys_command); defsubr (&Sw32_shell_execute); diff --git a/src/w32image.c b/src/w32image.c index 1f7c4921b31..da748b8dab4 100644 --- a/src/w32image.c +++ b/src/w32image.c @@ -382,7 +382,7 @@ w32_select_active_frame (GpBitmap *pBitmap, int frame, int *nframes, static ARGB w32_image_bg_color (struct frame *f, struct image *img) { - Lisp_Object specified_bg = Fplist_get (XCDR (img->spec), QCbackground); + Lisp_Object specified_bg = plist_get (XCDR (img->spec), QCbackground); Emacs_Color color; /* If the user specified a color, try to use it; if not, use the @@ -435,7 +435,7 @@ w32_load_image (struct frame *f, struct image *img, if (status == Ok) { /* In multiframe pictures, select the first frame. */ - Lisp_Object lisp_index = Fplist_get (XCDR (img->spec), QCindex); + Lisp_Object lisp_index = plist_get (XCDR (img->spec), QCindex); int index = FIXNATP (lisp_index) ? XFIXNAT (lisp_index) : 0; int nframes; double delay; diff --git a/src/w32proc.c b/src/w32proc.c index 7acfba64d70..f771ebc8511 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -1287,10 +1287,21 @@ reader_thread (void *arg) } /* If this thread was reading from a pipe process, close the descriptor used for reading, as sys_close doesn't in that case. */ - if (fd_info[fd].flags == FILE_DONT_CLOSE) + if ((fd_info[fd].flags & FILE_DONT_CLOSE) == FILE_DONT_CLOSE) { - fd_info[fd].flags = 0; - _close (fd); + int i; + /* If w32.c:sys_close is still processing this descriptor, wait + for a while for it to finish. */ + for (i = 0; i < 5; i++) + { + if (fd_info[fd].flags == FILE_DONT_CLOSE) + { + fd_info[fd].flags = 0; + _close (fd); + break; + } + Sleep (5); + } } return 0; } diff --git a/src/w32select.c b/src/w32select.c index eae1a0bac02..37206118127 100644 --- a/src/w32select.c +++ b/src/w32select.c @@ -631,7 +631,7 @@ validate_coding_system (Lisp_Object coding_system) eol_type = Fcoding_system_eol_type (coding_system); /* Already a DOS coding system? */ - if (EQ (eol_type, make_fixnum (1))) + if (BASE_EQ (eol_type, make_fixnum (1))) return coding_system; /* Get EOL_TYPE vector of the base of CODING_SYSTEM. */ diff --git a/src/w32term.c b/src/w32term.c index d0577efccc1..dff21489e5b 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -1490,6 +1490,8 @@ w32_draw_glyphless_glyph_string_foreground (struct glyph_string *s) ? CHAR_TABLE_REF (Vglyphless_char_display, glyph->u.glyphless.ch) : XCHAR_TABLE (Vglyphless_char_display)->extras[0]); + if (CONSP (acronym)) + acronym = XCAR (acronym); if (STRINGP (acronym)) str = SSDATA (acronym); } diff --git a/src/window.c b/src/window.c index ac8408a9a97..2bce4c9723d 100644 --- a/src/window.c +++ b/src/window.c @@ -556,7 +556,9 @@ select_window (Lisp_Object window, Lisp_Object norecord, frame is active. */ Fselect_frame (frame, norecord); /* Fselect_frame called us back so we've done all the work already. */ - eassert (EQ (window, selected_window)); + eassert (EQ (window, selected_window) + || (EQ (window, f->minibuffer_window) + && NILP (Fminibufferp (XWINDOW (window)->contents, Qt)))); return window; } else @@ -1028,7 +1030,7 @@ window_body_unit_from_symbol (Lisp_Object unit) /* Return the number of lines/pixels of W's body. Don't count any mode or header line or horizontal divider of W. Rounds down to nearest integer when not working pixelwise. */ -static int +int window_body_height (struct window *w, enum window_body_unit pixelwise) { int height = (w->pixel_height @@ -1275,7 +1277,10 @@ set_window_hscroll (struct window *w, EMACS_INT hscroll) /* Prevent redisplay shortcuts when changing the hscroll. */ if (w->hscroll != new_hscroll) - XBUFFER (w->contents)->prevent_redisplay_optimizations_p = true; + { + XBUFFER (w->contents)->prevent_redisplay_optimizations_p = true; + wset_redisplay (w); + } w->hscroll = new_hscroll; w->suspend_auto_hscroll = true; @@ -1289,7 +1294,7 @@ WINDOW must be a live window and defaults to the selected one. Clip the number to a reasonable value if out of range. Return the new number. NCOL should be zero or positive. -Note that if `automatic-hscrolling' is non-nil, you cannot scroll the +Note that if `auto-hscroll-mode' is non-nil, you cannot scroll the window so that the location of point moves off-window. */) (Lisp_Object window, Lisp_Object ncol) { @@ -1297,31 +1302,6 @@ window so that the location of point moves off-window. */) return set_window_hscroll (decode_live_window (window), XFIXNUM (ncol)); } -DEFUN ("window-redisplay-end-trigger", Fwindow_redisplay_end_trigger, - Swindow_redisplay_end_trigger, 0, 1, 0, - doc: /* Return WINDOW's redisplay end trigger value. -WINDOW must be a live window and defaults to the selected one. -See `set-window-redisplay-end-trigger' for more information. */) - (Lisp_Object window) -{ - return decode_live_window (window)->redisplay_end_trigger; -} - -DEFUN ("set-window-redisplay-end-trigger", Fset_window_redisplay_end_trigger, - Sset_window_redisplay_end_trigger, 2, 2, 0, - doc: /* Set WINDOW's redisplay end trigger value to VALUE. -WINDOW must be a live window and defaults to the selected one. VALUE -should be a buffer position (typically a marker) or nil. If it is a -buffer position, then if redisplay in WINDOW reaches a position beyond -VALUE, the functions in `redisplay-end-trigger-functions' are called -with two arguments: WINDOW, and the end trigger value. Afterwards the -end-trigger value is reset to nil. */) - (register Lisp_Object window, Lisp_Object value) -{ - wset_redisplay_end_trigger (decode_live_window (window), value); - return value; -} - /* Test if the character at column X, row Y is within window W. If it is not, return ON_NOTHING; if it is on the window's vertical divider, return @@ -2786,7 +2766,7 @@ decode_next_window_args (Lisp_Object *window, Lisp_Object *minibuf, Lisp_Object ? miniwin : Qnil); else if (EQ (*all_frames, Qvisible)) ; - else if (EQ (*all_frames, make_fixnum (0))) + else if (BASE_EQ (*all_frames, make_fixnum (0))) ; else if (FRAMEP (*all_frames)) ; @@ -3083,7 +3063,7 @@ window_loop (enum window_loop type, Lisp_Object obj, bool mini, if (f) frame_arg = Qlambda; - else if (EQ (frames, make_fixnum (0))) + else if (BASE_EQ (frames, make_fixnum (0))) frame_arg = frames; else if (EQ (frames, Qvisible)) frame_arg = frames; @@ -5568,7 +5548,11 @@ window_scroll (Lisp_Object window, EMACS_INT n, bool whole, bool noerror) /* On GUI frames, use the pixel-based version which is much slower than the line-based one but can handle varying line heights. */ if (FRAME_WINDOW_P (XFRAME (XWINDOW (window)->frame))) - window_scroll_pixel_based (window, n, whole, noerror); + { + record_unwind_protect_void (unwind_display_working_on_window); + display_working_on_window_p = true; + window_scroll_pixel_based (window, n, whole, noerror); + } else window_scroll_line_based (window, n, whole, noerror); @@ -6496,9 +6480,14 @@ displayed_window_lines (struct window *w) CLIP_TEXT_POS_FROM_MARKER (start, w->start); itdata = bidi_shelve_cache (); + + specpdl_ref count = SPECPDL_INDEX (); + record_unwind_protect_void (unwind_display_working_on_window); + display_working_on_window_p = true; start_display (&it, w, start); move_it_vertically (&it, height); bottom_y = line_bottom_y (&it); + unbind_to (count, Qnil); bidi_unshelve_cache (itdata, false); /* Add in empty lines at the bottom of the window. */ @@ -6588,10 +6577,17 @@ and redisplay normally--don't erase and redraw the frame. */) in case scroll_margin is buffer-local. */ this_scroll_margin = window_scroll_margin (w, MARGIN_IN_LINES); - /* Don't use redisplay code for initial frames, as the necessary - data structures might not be set up yet then. */ - if (!FRAME_INITIAL_P (XFRAME (w->frame))) + /* Don't use the display code for initial frames, as the necessary + data structures might not be set up yet then. Also don't use it + for buffers with very long lines, as it tremdously slows down + redisplay, especially when lines are truncated. */ + if (!FRAME_INITIAL_P (XFRAME (w->frame)) + && !current_buffer->long_line_optimizations_p) { + specpdl_ref count = SPECPDL_INDEX (); + + record_unwind_protect_void (unwind_display_working_on_window); + display_working_on_window_p = true; if (center_p) { struct it it; @@ -6654,6 +6650,7 @@ and redisplay normally--don't erase and redraw the frame. */) if (h <= 0) { bidi_unshelve_cache (itdata, false); + unbind_to (count, Qnil); return Qnil; } @@ -6670,7 +6667,7 @@ and redisplay normally--don't erase and redraw the frame. */) considered to be part of the visible height of the line. */ h += extra_line_spacing; - while (-it.current_y > h) + while (-it.current_y > h && it.what != IT_EOB) move_it_by_lines (&it, 1); charpos = IT_CHARPOS (it); @@ -6708,6 +6705,7 @@ and redisplay normally--don't erase and redraw the frame. */) bidi_unshelve_cache (itdata, false); } + unbind_to (count, Qnil); } else { @@ -6901,6 +6899,7 @@ struct saved_window Lisp_Object left_col, top_line, total_cols, total_lines; Lisp_Object normal_cols, normal_lines; Lisp_Object hscroll, min_hscroll, hscroll_whole, suspend_auto_hscroll; + Lisp_Object vscroll; Lisp_Object parent, prev; Lisp_Object start_at_line_beg; Lisp_Object display_table; @@ -7128,6 +7127,7 @@ the return value is nil. Otherwise the value is t. */) w->suspend_auto_hscroll = !NILP (p->suspend_auto_hscroll); w->min_hscroll = XFIXNAT (p->min_hscroll); w->hscroll_whole = XFIXNAT (p->hscroll_whole); + w->vscroll = -XFIXNAT (p->vscroll); wset_display_table (w, p->display_table); w->left_margin_cols = XFIXNUM (p->left_margin_cols); w->right_margin_cols = XFIXNUM (p->right_margin_cols); @@ -7282,7 +7282,7 @@ the return value is nil. Otherwise the value is t. */) do_switch_frame (NILP (dont_set_frame) ? data->selected_frame : old_frame - , 0, 0, Qnil); + , 0, Qnil); } FRAME_WINDOW_CHANGE (f) = true; @@ -7462,6 +7462,7 @@ save_window_save (Lisp_Object window, struct Lisp_Vector *vector, ptrdiff_t i) p->suspend_auto_hscroll = w->suspend_auto_hscroll ? Qt : Qnil; XSETFASTINT (p->min_hscroll, w->min_hscroll); XSETFASTINT (p->hscroll_whole, w->hscroll_whole); + XSETFASTINT (p->vscroll, -w->vscroll); p->display_table = w->display_table; p->left_margin_cols = make_fixnum (w->left_margin_cols); p->right_margin_cols = make_fixnum (w->right_margin_cols); @@ -7493,7 +7494,7 @@ save_window_save (Lisp_Object window, struct Lisp_Vector *vector, ptrdiff_t i) hare = XCDR (hare); tortoise = XCDR (tortoise); - if (EQ (hare, tortoise)) + if (BASE_EQ (hare, tortoise)) /* Reset Vwindow_persistent_parameters to Qnil. */ { Vwindow_persistent_parameters = Qnil; @@ -8619,8 +8620,6 @@ displayed after a scrolling operation to be somewhat inaccurate. */); defsubr (&Swindow_body_width); defsubr (&Swindow_hscroll); defsubr (&Sset_window_hscroll); - defsubr (&Swindow_redisplay_end_trigger); - defsubr (&Sset_window_redisplay_end_trigger); defsubr (&Swindow_mode_line_height); defsubr (&Swindow_header_line_height); defsubr (&Swindow_tab_line_height); diff --git a/src/window.h b/src/window.h index 298a80a5366..93817a95445 100644 --- a/src/window.h +++ b/src/window.h @@ -199,10 +199,6 @@ struct window and Qt, so bitfield can't be used here. */ Lisp_Object dedicated; - /* If redisplay in this window goes beyond this buffer position, - must run the redisplay-end-trigger-hook. */ - Lisp_Object redisplay_end_trigger; - /* t means this window's child windows are not (re-)combined. */ Lisp_Object combination_limit; @@ -498,12 +494,6 @@ wset_prev (struct window *w, Lisp_Object val) } INLINE void -wset_redisplay_end_trigger (struct window *w, Lisp_Object val) -{ - w->redisplay_end_trigger = val; -} - -INLINE void wset_mode_line_help_echo (struct window *w, Lisp_Object val) { w->mode_line_help_echo = val; @@ -1193,6 +1183,7 @@ enum window_body_unit WINDOW_BODY_IN_REMAPPED_CHARS }; extern int window_body_width (struct window *w, enum window_body_unit); +extern int window_body_height (struct window *w, enum window_body_unit); enum margin_unit { MARGIN_IN_LINES, MARGIN_IN_PIXELS }; extern int window_scroll_margin (struct window *, enum margin_unit); extern void temp_output_buffer_show (Lisp_Object); diff --git a/src/xdisp.c b/src/xdisp.c index b02375ab2d8..70f6936dd0b 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -832,7 +832,7 @@ void wset_redisplay (struct window *w) { /* Beware: selected_window can be nil during early stages. */ - if (!EQ (make_lisp_ptr (w, Lisp_Vectorlike), selected_window)) + if (!BASE_EQ (make_lisp_ptr (w, Lisp_Vectorlike), selected_window)) redisplay_other_windows (); w->redisplay = true; } @@ -1030,6 +1030,15 @@ static struct glyph_slice null_glyph_slice = { 0, 0, 0, 0 }; bool redisplaying_p; +/* True while some display-engine code is working on layout of some + window. + + WARNING: Use sparingly, preferably only in top level of commands + and important functions, because using it in nested calls might + reset the flag when the inner call returns, behind the back of + the callers. */ +bool display_working_on_window_p; + /* If a string, XTread_socket generates an event to display that string. (The display is done in read_char.) */ @@ -1129,7 +1138,6 @@ static int display_string (const char *, Lisp_Object, Lisp_Object, ptrdiff_t, ptrdiff_t, struct it *, int, int, int, int); static void compute_line_metrics (struct it *); -static void run_redisplay_end_trigger_hook (struct it *); static bool get_overlay_strings (struct it *, ptrdiff_t); static bool get_overlay_strings_1 (struct it *, ptrdiff_t, bool); static void next_overlay_string (struct it *); @@ -3221,6 +3229,10 @@ init_iterator (struct it *it, struct window *w, it->f = XFRAME (w->frame); it->cmp_it.id = -1; + it->cmp_it.parent_it = it; + + if (max_redisplay_ticks > 0) + update_redisplay_ticks (0, w); /* Extra space between lines (on window systems only). */ if (base_face_id == DEFAULT_FACE_ID @@ -3267,17 +3279,6 @@ init_iterator (struct it *it, struct window *w, /* Are multibyte characters enabled in current_buffer? */ it->multibyte_p = !NILP (BVAR (current_buffer, enable_multibyte_characters)); - /* Get the position at which the redisplay_end_trigger hook should - be run, if it is to be run at all. */ - if (MARKERP (w->redisplay_end_trigger) - && XMARKER (w->redisplay_end_trigger)->buffer != 0) - it->redisplay_end_trigger_charpos - = marker_position (w->redisplay_end_trigger); - else if (FIXNUMP (w->redisplay_end_trigger)) - it->redisplay_end_trigger_charpos - = clip_to_bounds (PTRDIFF_MIN, XFIXNUM (w->redisplay_end_trigger), - PTRDIFF_MAX); - it->tab_width = SANE_TAB_WIDTH (current_buffer); /* Are lines in the display truncated? */ @@ -3472,6 +3473,10 @@ init_iterator (struct it *it, struct window *w, &it->bidi_it); } + /* This is set only when long_line_optimizations_p is non-zero + for the current buffer. */ + it->narrowed_begv = 0; + /* Compute faces etc. */ reseat (it, it->current.pos, true); } @@ -3479,6 +3484,70 @@ init_iterator (struct it *it, struct window *w, CHECK_IT (it); } +/* Compute a suitable alternate value for BEGV and ZV that may be used + temporarily to optimize display if the buffer in window W contains + long lines. */ + +static int +get_narrowed_width (struct window *w) +{ + int fact; + /* In a character-only terminal, only one font size is used, so we + can use a smaller factor. */ + fact = EQ (Fterminal_live_p (Qnil), Qt) ? 2 : 3; + return fact * window_body_width (w, WINDOW_BODY_IN_CANONICAL_CHARS); +} + +static int +get_narrowed_len (struct window *w) +{ + return get_narrowed_width (w) * + window_body_height (w, WINDOW_BODY_IN_CANONICAL_CHARS); +} + +ptrdiff_t +get_narrowed_begv (struct window *w, ptrdiff_t pos) +{ + int len = get_narrowed_len (w); + return max ((pos / len - 1) * len, BEGV); +} + +ptrdiff_t +get_narrowed_zv (struct window *w, ptrdiff_t pos) +{ + int len = get_narrowed_len (w); + return min ((pos / len + 1) * len, ZV); +} + +ptrdiff_t +get_closer_narrowed_begv (struct window *w, ptrdiff_t pos) +{ + int len = get_narrowed_width (w); + return max ((pos / len - 1) * len, BEGV); +} + +static void +unwind_narrowed_begv (Lisp_Object point_min) +{ + SET_BUF_BEGV (current_buffer, XFIXNUM (point_min)); +} + +/* Set DST to EXPR. When IT indicates that BEGV should temporarily be + updated to optimize display, evaluate EXPR with BEGV set to BV. */ + +#define SET_WITH_NARROWED_BEGV(IT,DST,EXPR,BV) \ + do { \ + if (IT->narrowed_begv) \ + { \ + specpdl_ref count = SPECPDL_INDEX (); \ + record_unwind_protect (unwind_narrowed_begv, Fpoint_min ()); \ + SET_BUF_BEGV (current_buffer, BV); \ + DST = EXPR; \ + unbind_to (count, Qnil); \ + } \ + else \ + DST = EXPR; \ + } while (0) /* Initialize IT for the display of window W with window start POS. */ @@ -4323,6 +4392,20 @@ handle_fontified_prop (struct it *it) eassert (it->end_charpos == ZV); + if (current_buffer->long_line_optimizations_p) + { + ptrdiff_t begv = it->narrowed_begv; + ptrdiff_t zv = it->narrowed_zv; + ptrdiff_t charpos = IT_CHARPOS (*it); + if (charpos < begv || charpos > zv) + { + begv = get_narrowed_begv (it->w, charpos); + zv = get_narrowed_zv (it->w, charpos); + } + narrow_to_region_internal (make_fixnum (begv), make_fixnum (zv), true); + specbind (Qrestrictions_locked, Qt); + } + /* Don't allow Lisp that runs from 'fontification-functions' clear our face and image caches behind our back. */ it->f->inhibit_clear_image_cache = true; @@ -6980,7 +7063,109 @@ back_to_previous_line_start (struct it *it) ptrdiff_t cp = IT_CHARPOS (*it), bp = IT_BYTEPOS (*it); dec_both (&cp, &bp); - IT_CHARPOS (*it) = find_newline_no_quit (cp, bp, -1, &IT_BYTEPOS (*it)); + SET_WITH_NARROWED_BEGV (it, IT_CHARPOS (*it), + find_newline_no_quit (cp, bp, -1, &IT_BYTEPOS (*it)), + get_closer_narrowed_begv (it->w, IT_CHARPOS (*it))); +} + +/* Find in the current buffer the first display or overlay string + between STARTPOS and ENDPOS that includes embedded newlines. + Consider only overlays that apply to window W. + Value is non-zero if such a display/overlay string is found. */ +static bool +strings_with_newlines (ptrdiff_t startpos, ptrdiff_t endpos, struct window *w) +{ + /* Process overlays before the overlay center. */ + for (struct Lisp_Overlay *ov = current_buffer->overlays_before; + ov; ov = ov->next) + { + Lisp_Object overlay = make_lisp_ptr (ov, Lisp_Vectorlike); + eassert (OVERLAYP (overlay)); + + /* Skip this overlay if it doesn't apply to our window. */ + Lisp_Object window = Foverlay_get (overlay, Qwindow); + if (WINDOWP (window) && XWINDOW (window) != w) + continue; + + ptrdiff_t ostart = OVERLAY_POSITION (OVERLAY_START (overlay)); + ptrdiff_t oend = OVERLAY_POSITION (OVERLAY_END (overlay)); + + /* Due to the order of overlays in overlays_before, once we get + to an overlay whose end position is before STARTPOS, all the + rest also end before STARTPOS, and thus are of no concern to us. */ + if (oend < startpos) + break; + + /* Skip overlays that don't overlap the range. */ + if (!((startpos < oend && ostart < endpos) + || (ostart == oend + && (startpos == oend || (endpos == ZV && oend == endpos))))) + continue; + + Lisp_Object str; + str = Foverlay_get (overlay, Qbefore_string); + if (STRINGP (str) && SCHARS (str) + && memchr (SDATA (str), '\n', SBYTES (str))) + return true; + str = Foverlay_get (overlay, Qafter_string); + if (STRINGP (str) && SCHARS (str) + && memchr (SDATA (str), '\n', SBYTES (str))) + return true; + } + + /* Process overlays after the overlay center. */ + for (struct Lisp_Overlay *ov = current_buffer->overlays_after; + ov; ov = ov->next) + { + Lisp_Object overlay = make_lisp_ptr (ov, Lisp_Vectorlike); + eassert (OVERLAYP (overlay)); + + /* Skip this overlay if it doesn't apply to our window. */ + Lisp_Object window = Foverlay_get (overlay, Qwindow); + if (WINDOWP (window) && XWINDOW (window) != w) + continue; + + ptrdiff_t ostart = OVERLAY_POSITION (OVERLAY_START (overlay)); + ptrdiff_t oend = OVERLAY_POSITION (OVERLAY_END (overlay)); + + /* Due to the order of overlays in overlays_after, once we get + to an overlay whose start position is after ENDPOS, all the + rest also start after ENDPOS, and thus are of no concern to us. */ + if (ostart > endpos) + break; + + /* Skip overlays that don't overlap the range. */ + if (!((startpos < oend && ostart < endpos) + || (ostart == oend + && (startpos == oend || (endpos == ZV && oend == endpos))))) + continue; + + Lisp_Object str; + str = Foverlay_get (overlay, Qbefore_string); + if (STRINGP (str) && SCHARS (str) + && memchr (SDATA (str), '\n', SBYTES (str))) + return true; + str = Foverlay_get (overlay, Qafter_string); + if (STRINGP (str) && SCHARS (str) + && memchr (SDATA (str), '\n', SBYTES (str))) + return true; + } + + /* Check for 'display' properties whose values include strings. */ + Lisp_Object cpos = make_fixnum (startpos); + Lisp_Object limpos = make_fixnum (endpos); + + while ((cpos = Fnext_single_property_change (cpos, Qdisplay, Qnil, limpos), + !(NILP (cpos) || XFIXNAT (cpos) >= endpos))) + { + Lisp_Object spec = Fget_char_property (cpos, Qdisplay, Qnil); + Lisp_Object string = string_from_display_spec (spec); + if (STRINGP (string) + && memchr (SDATA (string), '\n', SBYTES (string))) + return true; + } + + return false; } @@ -7035,7 +7220,8 @@ forward_to_next_line_start (struct it *it, bool *skipped_p, it->selective = 0; /* Scan for a newline within MAX_NEWLINE_DISTANCE display elements - from buffer text. */ + from buffer text, or till the end of the string if iterating a + string. */ for (n = 0; !newline_found_p && n < MAX_NEWLINE_DISTANCE; n += !STRINGP (it->string)) @@ -7055,27 +7241,55 @@ forward_to_next_line_start (struct it *it, bool *skipped_p, ptrdiff_t bytepos, start = IT_CHARPOS (*it); ptrdiff_t limit = find_newline_no_quit (start, IT_BYTEPOS (*it), 1, &bytepos); - Lisp_Object pos; - eassert (!STRINGP (it->string)); - /* If there isn't any `display' property in sight, and no - overlays, we can just use the position of the newline in - buffer text. */ - if (it->stop_charpos >= limit - || ((pos = Fnext_single_property_change (make_fixnum (start), - Qdisplay, Qnil, - make_fixnum (limit)), - NILP (pos)) - && next_overlay_change (start) == ZV)) + /* it->stop_charpos >= limit means we already know there's no + stop position up until the newline at LIMIT, so there's no + need for any further checks. */ + bool no_strings_with_newlines = it->stop_charpos >= limit; + + if (!no_strings_with_newlines) { - if (!it->bidi_p) + if (!(current_buffer->long_line_optimizations_p + && it->line_wrap == TRUNCATE)) + { + /* Quick-and-dirty check: if there isn't any `display' + property in sight, and no overlays, we're done. */ + Lisp_Object pos = + Fnext_single_property_change (make_fixnum (start), + Qdisplay, Qnil, + make_fixnum (limit)); + no_strings_with_newlines = + (NILP (pos) || XFIXNAT (pos) == limit) /* no 'display' props */ + && next_overlay_change (start) == ZV; /* no overlays */ + } + else + { + /* For buffers with very long and truncated lines we try + harder, because it's worth our while to spend some + time looking into the overlays and 'display' properties + if we can then avoid iterating through all of them. */ + no_strings_with_newlines = + !strings_with_newlines (start, limit, it->w); + } + } + + /* If there's no display or overlay strings with embedded + newlines until the position of the newline in buffer text, we + can just use that position. */ + if (no_strings_with_newlines) + { + if (!it->bidi_p || !bidi_it_prev) { + /* The optimal case: just jump there. */ IT_CHARPOS (*it) = limit; IT_BYTEPOS (*it) = bytepos; } else { + /* The less optimal case: need to bidi-walk there, but + this is still cheaper that the full iteration using + get_next_display_element and set_iterator_to_next. */ struct bidi_it bprev; /* Help bidi.c avoid expensive searches for display @@ -7099,6 +7313,7 @@ forward_to_next_line_start (struct it *it, bool *skipped_p, } else { + /* The slow case. */ while (!newline_found_p) { if (!get_next_display_element (it)) @@ -7198,7 +7413,8 @@ back_to_previous_visible_line_start (struct it *it) it->continuation_lines_width = 0; eassert (IT_CHARPOS (*it) >= BEGV); - eassert (IT_CHARPOS (*it) == BEGV + eassert (it->narrowed_begv > 0 /* long-line optimizations: all bets off */ + || IT_CHARPOS (*it) == BEGV || FETCH_BYTE (IT_BYTEPOS (*it) - 1) == '\n'); CHECK_IT (it); } @@ -7231,7 +7447,8 @@ reseat_at_next_visible_line_start (struct it *it, bool on_newline_p) bool skipped_p = false; struct bidi_it bidi_it_prev; bool newline_found_p - = forward_to_next_line_start (it, &skipped_p, &bidi_it_prev); + = forward_to_next_line_start (it, &skipped_p, + on_newline_p ? &bidi_it_prev : NULL); /* Skip over lines that are invisible because they are indented more than the value of IT->selective. */ @@ -7243,7 +7460,8 @@ reseat_at_next_visible_line_start (struct it *it, bool on_newline_p) eassert (IT_BYTEPOS (*it) == BEGV || FETCH_BYTE (IT_BYTEPOS (*it) - 1) == '\n'); newline_found_p = - forward_to_next_line_start (it, &skipped_p, &bidi_it_prev); + forward_to_next_line_start (it, &skipped_p, + on_newline_p ? &bidi_it_prev : NULL); } /* Position on the newline if that's what's requested. */ @@ -7311,6 +7529,21 @@ reseat (struct it *it, struct text_pos pos, bool force_p) reseat_1 (it, pos, false); + if (current_buffer->long_line_optimizations_p) + { + if (!it->narrowed_begv) + { + it->narrowed_begv = get_narrowed_begv (it->w, window_point (it->w)); + it->narrowed_zv = get_narrowed_zv (it->w, window_point (it->w)); + } + else if ((pos.charpos < it->narrowed_begv || pos.charpos > it->narrowed_zv) + && (!redisplaying_p || it->line_wrap == TRUNCATE)) + { + it->narrowed_begv = get_narrowed_begv (it->w, pos.charpos); + it->narrowed_zv = get_narrowed_zv (it->w, pos.charpos); + } + } + /* Determine where to check text properties. Avoid doing it where possible because text property lookup is very expensive. */ if (force_p @@ -7609,15 +7842,14 @@ lookup_glyphless_char_display (int c, struct it *it) && CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (Vglyphless_char_display)) >= 1) { if (c >= 0) - { - glyphless_method = CHAR_TABLE_REF (Vglyphless_char_display, c); - if (CONSP (glyphless_method)) - glyphless_method = FRAME_WINDOW_P (it->f) - ? XCAR (glyphless_method) - : XCDR (glyphless_method); - } + glyphless_method = CHAR_TABLE_REF (Vglyphless_char_display, c); else glyphless_method = XCHAR_TABLE (Vglyphless_char_display)->extras[0]; + + if (CONSP (glyphless_method)) + glyphless_method = FRAME_WINDOW_P (it->f) + ? XCAR (glyphless_method) + : XCDR (glyphless_method); } retry: @@ -8175,6 +8407,9 @@ void set_iterator_to_next (struct it *it, bool reseat_p) { + if (max_redisplay_ticks > 0) + update_redisplay_ticks (1, it->w); + switch (it->method) { case GET_FROM_BUFFER: @@ -8608,7 +8843,13 @@ get_visually_first_element (struct it *it) { bool string_p = STRINGP (it->string) || it->s; ptrdiff_t eob = (string_p ? it->bidi_it.string.schars : ZV); - ptrdiff_t bob = (string_p ? 0 : BEGV); + ptrdiff_t bob; + ptrdiff_t obegv = BEGV; + + SET_WITH_NARROWED_BEGV (it, bob, + string_p ? 0 : + IT_CHARPOS (*it) < BEGV ? obegv : BEGV, + it->narrowed_begv); if (STRINGP (it->string)) { @@ -8648,9 +8889,11 @@ get_visually_first_element (struct it *it) if (string_p) it->bidi_it.charpos = it->bidi_it.bytepos = 0; else - it->bidi_it.charpos = find_newline_no_quit (IT_CHARPOS (*it), - IT_BYTEPOS (*it), -1, - &it->bidi_it.bytepos); + SET_WITH_NARROWED_BEGV (it, it->bidi_it.charpos, + find_newline_no_quit (IT_CHARPOS (*it), + IT_BYTEPOS (*it), -1, + &it->bidi_it.bytepos), + it->narrowed_begv); bidi_paragraph_init (it->paragraph_embedding, &it->bidi_it, true); do { @@ -9186,13 +9429,6 @@ next_element_from_buffer (struct it *it) previously seen overlays is no longer valid. */ it->ignore_overlay_strings_at_pos_p = false; - /* Maybe run the redisplay end trigger hook. Performance note: - This doesn't seem to cost measurable time. */ - if (it->redisplay_end_trigger_charpos - && it->glyph_row - && IT_CHARPOS (*it) >= it->redisplay_end_trigger_charpos) - run_redisplay_end_trigger_hook (it); - if (composition_break_at_point && !NILP (BVAR (current_buffer, enable_multibyte_characters)) && !NILP (Vauto_composition_mode)) @@ -9260,29 +9496,6 @@ next_element_from_buffer (struct it *it) } -/* Run the redisplay end trigger hook for IT. */ - -static void -run_redisplay_end_trigger_hook (struct it *it) -{ - /* IT->glyph_row should be non-null, i.e. we should be actually - displaying something, or otherwise we should not run the hook. */ - eassert (it->glyph_row); - - ptrdiff_t charpos = it->redisplay_end_trigger_charpos; - it->redisplay_end_trigger_charpos = 0; - - /* Since we are *trying* to run these functions, don't try to run - them again, even if they get an error. */ - wset_redisplay_end_trigger (it->w, Qnil); - CALLN (Frun_hook_with_args, Qredisplay_end_trigger_functions, it->window, - make_fixnum (charpos)); - - /* Notice if it changed the face of the character we are on. */ - handle_face_prop (it); -} - - /* Deliver a composition display element. Unlike the other next_element_from_XXX, this function is not registered in the array get_next_element[]. It is called from next_element_from_buffer and @@ -10497,6 +10710,11 @@ move_it_vertically_backward (struct it *it, int dy) while (nlines-- && IT_CHARPOS (*it) > pos_limit) back_to_previous_visible_line_start (it); + /* Move one line more back, for the (rare) situation where we have + bidi-reordered continued lines, and we start from the top-most + screen line, which is the last in logical order. */ + if (it->bidi_p && dy == 0) + back_to_previous_visible_line_start (it); /* Reseat the iterator here. When moving backward, we don't want reseat to skip forward over invisible text, set up the iterator to deliver from overlay strings at the new position etc. So, @@ -10568,7 +10786,9 @@ move_it_vertically_backward (struct it *it, int dy) ptrdiff_t cp = IT_CHARPOS (*it), bp = IT_BYTEPOS (*it); dec_both (&cp, &bp); - cp = find_newline_no_quit (cp, bp, -1, NULL); + SET_WITH_NARROWED_BEGV (it, cp, + find_newline_no_quit (cp, bp, -1, NULL), + get_closer_narrowed_begv (it->w, IT_CHARPOS (*it))); move_it_to (it, cp, -1, -1, -1, MOVE_TO_POS); } bidi_unshelve_cache (it3data, true); @@ -10746,6 +10966,7 @@ move_it_by_lines (struct it *it, ptrdiff_t dvpos) position. This may actually move vertically backwards, in case of overlays, so adjust dvpos accordingly. */ dvpos += it->vpos; + start_charpos = IT_CHARPOS (*it); move_it_vertically_backward (it, 0); dvpos -= it->vpos; @@ -10799,7 +11020,7 @@ move_it_by_lines (struct it *it, ptrdiff_t dvpos) SAVE_IT (it2, *it, it2data); move_it_to (it, -1, -1, -1, it->vpos + delta, MOVE_TO_VPOS); /* Move back again if we got too far ahead. */ - if (IT_CHARPOS (*it) >= start_charpos) + if (it->vpos - it2.vpos > delta) RESTORE_IT (it, &it2, it2data); else bidi_unshelve_cache (it2data, true); @@ -10832,6 +11053,15 @@ move_it_by_lines (struct it *it, ptrdiff_t dvpos) int partial_line_height (struct it *it_origin) { + /* In a buffer with very long and truncated lines, we ignore the + possibly-partial height of the last line in the window: it is too + expensive to compute that (since in most cases that involves + going all the way to ZV), and the effect of ignoring it is + relatively minor. */ + if (XBUFFER (it_origin->w->contents)->long_line_optimizations_p + && it_origin->line_wrap == TRUNCATE) + return 0; + int partial_height; void *it_data = NULL; struct it it; @@ -10855,6 +11085,51 @@ partial_line_height (struct it *it_origin) return partial_height; } +/* Approximate move_it_in_display_line_to for very long and truncated + display lines, when moving horizontally. This is used when the + buffer's long_line_optimizations_p flag is set. It ignores various + complications, like different font sizes, invisible text, display + and overlay strings, and, to some degree, bidirectional text. So + caveat emptor! + + Starting from IT's position, reseat IT after skipping NCHARS + characters or to the next newline/ZV, whichever comes first. Return + what move_it_in_display_line_to would have returned in this case. */ + +static enum move_it_result +fast_move_it_horizontally (struct it *it, ptrdiff_t nchars) +{ + ptrdiff_t nl_bytepos; + ptrdiff_t nl_pos = find_newline_no_quit (IT_CHARPOS (*it), IT_BYTEPOS (*it), + 1, &nl_bytepos); + struct text_pos new_pos; + enum move_it_result move_result; + + if (nl_pos - IT_CHARPOS (*it) > nchars) + { + SET_TEXT_POS (new_pos, + IT_CHARPOS (*it) + nchars, + CHAR_TO_BYTE (IT_CHARPOS (*it) + nchars)); + move_result = MOVE_X_REACHED; + } + else + { + if (nl_bytepos < ZV_BYTE + || (nl_bytepos > BEGV_BYTE + && FETCH_BYTE (nl_bytepos - 1) == '\n')) + { + nl_pos--; + nl_bytepos--; + move_result = MOVE_NEWLINE_OR_CR; + } + else + move_result = MOVE_POS_MATCH_OR_ZV; + SET_TEXT_POS (new_pos, nl_pos, nl_bytepos); + } + reseat (it, new_pos, false); + return move_result; +} + /* Return true if IT points into the middle of a display vector. */ bool @@ -10957,6 +11232,7 @@ window_text_pixel_size (Lisp_Object window, Lisp_Object from, Lisp_Object to, max_y = XFIXNUM (y_limit); itdata = bidi_shelve_cache (); + start_display (&it, w, startp); int start_y = it.current_y; @@ -13452,9 +13728,6 @@ update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run) /* If it has changed current-menubar from previous value, really recompute the menu-bar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); - safe_run_hooks (Qmenu_bar_update_hook); hooks_run = true; @@ -14041,15 +14314,41 @@ redisplay_tab_bar (struct frame *f) return false; } + /* Build a string that represents the contents of the tab-bar. */ + build_desired_tab_bar_string (f); + + int new_nrows; + int new_height = tab_bar_height (f, &new_nrows, true); + + if (f->n_tab_bar_rows == 0) + { + f->n_tab_bar_rows = new_nrows; + if (new_height != WINDOW_PIXEL_HEIGHT (w)) + frame_default_tab_bar_height = new_height; + } + + /* If new_height or new_nrows indicate that we need to enlarge the + tab-bar window, we can return right away. */ + if (new_nrows > f->n_tab_bar_rows + || (EQ (Vauto_resize_tab_bars, Qgrow_only) + && !f->minimize_tab_bar_window_p + && new_height > WINDOW_PIXEL_HEIGHT (w))) + { + if (FRAME_TERMINAL (f)->change_tab_bar_height_hook) + FRAME_TERMINAL (f)->change_tab_bar_height_hook (f, new_height); + if (new_nrows != f->n_tab_bar_rows) + f->n_tab_bar_rows = new_nrows; + clear_glyph_matrix (w->desired_matrix); + f->fonts_changed = true; + return true; + } + /* Set up an iterator for the tab-bar window. */ init_iterator (&it, w, -1, -1, w->desired_matrix->rows, TAB_BAR_FACE_ID); it.first_visible_x = 0; it.last_visible_x = WINDOW_PIXEL_WIDTH (w); row = it.glyph_row; row->reversed_p = false; - - /* Build a string that represents the contents of the tab-bar. */ - build_desired_tab_bar_string (f); reseat_to_string (&it, NULL, f->desired_tab_bar_string, 0, 0, 0, STRING_MULTIBYTE (f->desired_tab_bar_string)); /* FIXME: This should be controlled by a user option. But it @@ -14061,22 +14360,6 @@ redisplay_tab_bar (struct frame *f) do. */ it.paragraph_embedding = L2R; - if (f->n_tab_bar_rows == 0) - { - int new_height = tab_bar_height (f, &f->n_tab_bar_rows, true); - - if (new_height != WINDOW_PIXEL_HEIGHT (w)) - { - if (FRAME_TERMINAL (f)->change_tab_bar_height_hook) - FRAME_TERMINAL (f)->change_tab_bar_height_hook (f, new_height); - frame_default_tab_bar_height = new_height; - /* Always do that now. */ - clear_glyph_matrix (w->desired_matrix); - f->fonts_changed = true; - return true; - } - } - /* Display as many lines as needed to display all tab-bar items. */ if (f->n_tab_bar_rows > 0) @@ -14122,7 +14405,7 @@ redisplay_tab_bar (struct frame *f) if (!NILP (Vauto_resize_tab_bars)) { - bool change_height_p = true; + bool change_height_p = false; /* If we couldn't display everything, change the tab-bar's height if there is room for more. */ @@ -14678,7 +14961,7 @@ build_desired_tool_bar_string (struct frame *f) selected. */ if (selected_p) { - plist = Fplist_put (plist, QCrelief, make_fixnum (-relief)); + plist = plist_put (plist, QCrelief, make_fixnum (-relief)); hmargin -= relief; vmargin -= relief; } @@ -14688,10 +14971,10 @@ build_desired_tool_bar_string (struct frame *f) /* If image is selected, display it pressed, i.e. with a negative relief. If it's not selected, display it with a raised relief. */ - plist = Fplist_put (plist, QCrelief, - (selected_p - ? make_fixnum (-relief) - : make_fixnum (relief))); + plist = plist_put (plist, QCrelief, + (selected_p + ? make_fixnum (-relief) + : make_fixnum (relief))); hmargin -= relief; vmargin -= relief; } @@ -14700,18 +14983,18 @@ build_desired_tool_bar_string (struct frame *f) if (hmargin || vmargin) { if (hmargin == vmargin) - plist = Fplist_put (plist, QCmargin, make_fixnum (hmargin)); + plist = plist_put (plist, QCmargin, make_fixnum (hmargin)); else - plist = Fplist_put (plist, QCmargin, - Fcons (make_fixnum (hmargin), - make_fixnum (vmargin))); + plist = plist_put (plist, QCmargin, + Fcons (make_fixnum (hmargin), + make_fixnum (vmargin))); } /* If button is not enabled, and we don't have special images for the disabled state, make the image appear disabled by applying an appropriate algorithm to it. */ if (!enabled_p && idx < 0) - plist = Fplist_put (plist, QCconversion, Qdisabled); + plist = plist_put (plist, QCconversion, Qdisabled); /* Put a `display' text property on the string for the image to display. Put a `menu-item' property on the string that gives @@ -15050,7 +15333,7 @@ redisplay_tool_bar (struct frame *f) if (!NILP (Vauto_resize_tool_bars)) { - bool change_height_p = true; + bool change_height_p = false; /* If we couldn't display everything, change the tool-bar's height if there is room for more. */ @@ -15559,7 +15842,20 @@ hscroll_window_tree (Lisp_Object window) it.first_visible_x = window_hscroll_limited (w, it.f) * FRAME_COLUMN_WIDTH (it.f); it.last_visible_x = DISP_INFINITY; - move_it_in_display_line_to (&it, pt, -1, MOVE_TO_POS); + + ptrdiff_t nchars = pt - IT_CHARPOS (it); + if (current_buffer->long_line_optimizations_p + && nchars > large_hscroll_threshold) + { + /* Special optimization for very long and truncated + lines which need to be hscrolled far to the left: + jump directly to the (approximate) first position + that is visible, instead of slowly walking there. */ + fast_move_it_horizontally (&it, nchars); + it.current_x += nchars * FRAME_COLUMN_WIDTH (it.f); + } + else + move_it_in_display_line_to (&it, pt, -1, MOVE_TO_POS); /* If the line ends in an overlay string with a newline, we might infloop, because displaying the window will want to put the cursor after the overlay, i.e. at X @@ -15572,7 +15868,14 @@ hscroll_window_tree (Lisp_Object window) if (hscl) it.first_visible_x = (window_hscroll_limited (w, it.f) * FRAME_COLUMN_WIDTH (it.f)); - move_it_in_display_line_to (&it, pt - 1, -1, MOVE_TO_POS); + if (current_buffer->long_line_optimizations_p + && nchars > large_hscroll_threshold) + { + fast_move_it_horizontally (&it, nchars - 1); + it.current_x += (nchars - 1) * FRAME_COLUMN_WIDTH (it.f); + } + else + move_it_in_display_line_to (&it, pt - 1, -1, MOVE_TO_POS); } current_buffer = saved_current_buffer; @@ -16510,9 +16813,23 @@ redisplay_internal (void) it.current_y = this_line_y; it.vpos = this_line_vpos; - /* The call to move_it_to stops in front of PT, but - moves over before-strings. */ - move_it_to (&it, PT, -1, -1, -1, MOVE_TO_POS); + if (current_buffer->long_line_optimizations_p + && it.line_wrap == TRUNCATE + && PT - CHARPOS (tlbufpos) > large_hscroll_threshold) + { + /* When lines are very long and truncated, jumping to + the next visible line is much faster than slowly + iterating there. */ + reseat_at_next_visible_line_start (&it, false); + if (IT_CHARPOS (it) <= PT) /* point moved off this line */ + it.vpos = this_line_vpos + 1; + } + else + { + /* The call to move_it_to stops in front of PT, but + moves over before-strings. */ + move_it_to (&it, PT, -1, -1, -1, MOVE_TO_POS); + } if (it.vpos == this_line_vpos && (row = MATRIX_ROW (w->current_matrix, this_line_vpos), @@ -16724,9 +17041,14 @@ redisplay_internal (void) list_of_error, redisplay_window_error); if (update_miniwindow_p) - internal_condition_case_1 (redisplay_window_1, - FRAME_MINIBUF_WINDOW (sf), list_of_error, - redisplay_window_error); + { + Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (sf); + + displayed_buffer = XBUFFER (XWINDOW (mini_window)->contents); + internal_condition_case_1 (redisplay_window_1, mini_window, + list_of_error, + redisplay_window_error); + } /* Compare desired and current matrices, perform output. */ @@ -16904,6 +17226,11 @@ redisplay_internal (void) if (interrupt_input && interrupts_deferred) request_sigio (); + /* We're done with this redisplay cycle, so reset the tick count in + preparation for the next redisplay cycle. */ + if (max_redisplay_ticks > 0) + update_redisplay_ticks (0, NULL); + unbind_to (count, Qnil); RESUME_POLLING; } @@ -16961,6 +17288,13 @@ unwind_redisplay (void) unblock_buffer_flips (); } +/* Function registered with record_unwind_protect before calling + start_display outside of redisplay_internal. */ +void +unwind_display_working_on_window (void) +{ + display_working_on_window_p = false; +} /* Mark the display of leaf window W as accurate or inaccurate. If ACCURATE_P, mark display of W as accurate. @@ -16988,6 +17322,7 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p) BUF_UNCHANGED_MODIFIED (b) = BUF_MODIFF (b); BUF_OVERLAY_UNCHANGED_MODIFIED (b) = BUF_OVERLAY_MODIFF (b); + BUF_CHARS_UNCHANGED_MODIFIED (b) = BUF_CHARS_MODIFF (b); BUF_BEG_UNCHANGED (b) = BUF_GPT (b) - BUF_BEG (b); BUF_END_UNCHANGED (b) = BUF_Z (b) - BUF_GPT (b); @@ -17135,9 +17470,19 @@ redisplay_windows (Lisp_Object window) } static Lisp_Object -redisplay_window_error (Lisp_Object ignore) +redisplay_window_error (Lisp_Object error_data) { displayed_buffer->display_error_modiff = BUF_MODIFF (displayed_buffer); + + /* When in redisplay, the error is captured and not shown. Arrange + for it to be shown later. */ + if (max_redisplay_ticks > 0 + && CONSP (error_data) + && EQ (XCAR (error_data), Qerror) + && STRINGP (XCAR (XCDR (error_data)))) + Vdelayed_warnings_list = Fcons (list2 (XCAR (error_data), + XCAR (XCDR (error_data))), + Vdelayed_warnings_list); return Qnil; } @@ -17156,6 +17501,73 @@ redisplay_window_1 (Lisp_Object window) redisplay_window (window, true); return Qnil; } + + +/*********************************************************************** + Aborting runaway redisplay + ***********************************************************************/ + +/* Update the redisplay-tick count for window W, and signal an error + if the tick count is above some threshold, indicating that + redisplay of the window takes "too long". + + TICKS is the amount of ticks to add to the W's current count; zero + means to initialize the tick count to zero. + + W can be NULL if TICKS is zero: that means unconditionally + re-initialize the current tick count to zero. + + W can also be NULL if the caller doesn't know which window is being + processed by the display code. In that case, if TICKS is non-zero, + we assume it's the last window that shows the current buffer. */ +void +update_redisplay_ticks (int ticks, struct window *w) +{ + /* This keeps track of the window on which redisplay is working. */ + static struct window *cwindow; + static EMACS_INT window_ticks; + + /* We only initialize the count if this is a different window or + NULL. Otherwise, this is a call from init_iterator for the same + window we tracked before, and we should keep the count. */ + if (!ticks && w != cwindow) + { + cwindow = w; + window_ticks = 0; + } + /* Some callers can be run in contexts unrelated to display code, so + don't abort them and don't update the tick count in those cases. */ + if ((!w && !redisplaying_p && !display_working_on_window_p) + /* We never disable redisplay of a mini-window, since that is + absolutely essential for communicating with Emacs. */ + || (w && MINI_WINDOW_P (w))) + return; + + if (ticks > 0) + window_ticks += ticks; + if (max_redisplay_ticks > 0 && window_ticks > max_redisplay_ticks) + { + /* In addition to a buffer, this could be a window (for non-leaf + windows, not expected here) or nil (for pseudo-windows like + the one used for the native tool bar). */ + Lisp_Object contents = w ? w->contents : Qnil; + char *bufname = + NILP (contents) + ? SSDATA (BVAR (current_buffer, name)) + : (BUFFERP (contents) + ? SSDATA (BVAR (XBUFFER (contents), name)) + : (char *) "<unknown>"); + + windows_or_buffers_changed = 177; + /* scrolling_window depends too much on the glyph matrices being + correct, and we cannot guarantee that if we abort the + redisplay of this window. */ + if (w && w->desired_matrix) + w->desired_matrix->no_scrolling_p = true; + error ("Window showing buffer %s takes too long to redisplay", bufname); + } +} + /* Set cursor position of W. PT is assumed to be displayed in ROW. @@ -17808,8 +18220,8 @@ run_window_scroll_functions (Lisp_Object window, struct text_pos startp) { specpdl_ref count = SPECPDL_INDEX (); specbind (Qinhibit_quit, Qt); - run_hook_with_args_2 (Qwindow_scroll_functions, window, - make_fixnum (CHARPOS (startp))); + safe_run_hooks_2 + (Qwindow_scroll_functions, window, make_fixnum (CHARPOS (startp))); unbind_to (count, Qnil); SET_TEXT_POS_FROM_MARKER (startp, w->start); /* In case the hook functions switch buffers. */ @@ -18518,6 +18930,8 @@ try_cursor_movement (Lisp_Object window, struct text_pos startp, { /* Cursor has to be moved backward. Note that PT >= CHARPOS (startp) because of the outer if-statement. */ + struct glyph_row *row0 = row; + while (!row->mode_line_p && (MATRIX_ROW_START_CHARPOS (row) > PT || (MATRIX_ROW_START_CHARPOS (row) == PT @@ -18532,6 +18946,23 @@ try_cursor_movement (Lisp_Object window, struct text_pos startp, --row; } + /* With bidi-reordered rows we can have buffer positions + _decrease_ when going down by rows. If we haven't + found our row in the loop above, give it another try + now going in the other direction from the original row. */ + if (!(MATRIX_ROW_START_CHARPOS (row) <= PT + && PT <= MATRIX_ROW_END_CHARPOS (row)) + && row0->continued_p) + { + row = row0; + while (MATRIX_ROW_START_CHARPOS (row) > PT + && MATRIX_ROW_BOTTOM_Y (row) < last_y) + { + eassert (row->enabled_p); + ++row; + } + } + /* Consider the following case: Window starts at BEGV, there is invisible, intangible text at BEGV, so that display starts at some point START > BEGV. It can @@ -18555,9 +18986,16 @@ try_cursor_movement (Lisp_Object window, struct text_pos startp, && !cursor_row_p (row)) ++row; - /* If within the scroll margin, scroll. */ - if (row->y < top_scroll_margin - && CHARPOS (startp) != BEGV) + /* If within the scroll margin, either the top one or + the bottom one, scroll. */ + if ((row->y < top_scroll_margin + && CHARPOS (startp) != BEGV) + || MATRIX_ROW_BOTTOM_Y (row) > last_y + || PT > MATRIX_ROW_END_CHARPOS (row) + || (MATRIX_ROW_BOTTOM_Y (row) == last_y + && PT == MATRIX_ROW_END_CHARPOS (row) + && !row->ends_at_zv_p + && !MATRIX_ROW_ENDS_IN_MIDDLE_OF_CHAR_P (row))) scroll_p = true; } else @@ -18762,12 +19200,31 @@ set_vertical_scroll_bar (struct window *w) && NILP (echo_area_buffer[0]))) { struct buffer *buf = XBUFFER (w->contents); + whole = BUF_ZV (buf) - BUF_BEGV (buf); start = marker_position (w->start) - BUF_BEGV (buf); - /* I don't think this is guaranteed to be right. For the - moment, we'll pretend it is. */ end = BUF_Z (buf) - w->window_end_pos - BUF_BEGV (buf); + /* If w->window_end_pos cannot be trusted, recompute it "the + hard way". But don't bother to be too accurate when + long-line shortcuts are in effect. */ + if (!w->window_end_valid && !buf->long_line_optimizations_p) + { + struct it it; + struct text_pos start_pos; + struct buffer *obuf = current_buffer; + /* When we display the scroll bar of a mini-window, + current_buffer is not guaranteed to be the mini-window's + buffer, see the beginning of redisplay_window. */ + set_buffer_internal_1 (XBUFFER (w->contents)); + SET_TEXT_POS_FROM_MARKER (start_pos, w->start); + start_display (&it, w, start_pos); + move_it_to (&it, -1, it.last_visible_x, window_box_height (w), -1, + MOVE_TO_X | MOVE_TO_Y); + end -= (BUF_Z (buf) - IT_CHARPOS (it)) - w->window_end_pos; + set_buffer_internal_1 (obuf); + } + if (end < start) end = start; if (whole < (end - start)) @@ -18873,6 +19330,16 @@ window_start_acceptable_p (Lisp_Object window, ptrdiff_t startp) return true; } +DEFUN ("long-line-optimizations-p", Flong_line_optimizations_p, Slong_line_optimizations_p, + 0, 0, 0, + doc: /* Return non-nil if long-line optimizations are in effect in current buffer. +See `long-line-threshold' and `large-hscroll-threshold' for what these +optimizations mean and when they are in effect. */) + (void) +{ + return current_buffer->long_line_optimizations_p ? Qt : Qnil; +} + /* Redisplay leaf window WINDOW. JUST_THIS_ONE_P means only selected_window is redisplayed. @@ -19115,6 +19582,24 @@ redisplay_window (Lisp_Object window, bool just_this_one_p) } } + /* Check whether the buffer to be displayed contains long lines. */ + if (!NILP (Vlong_line_threshold) + && !current_buffer->long_line_optimizations_p + && CHARS_MODIFF - CHARS_UNCHANGED_MODIFIED > 8) + { + ptrdiff_t cur, next, found, max = 0, threshold; + threshold = XFIXNUM (Vlong_line_threshold); + for (cur = BEGV; cur < ZV; cur = next) + { + next = find_newline1 (cur, CHAR_TO_BYTE (cur), 0, -1, 1, + &found, NULL, true); + if (next - cur > max) max = next - cur; + if (!found || max > threshold) break; + } + if (max > threshold) + current_buffer->long_line_optimizations_p = true; + } + /* If window-start is screwed up, choose a new one. */ if (XMARKER (w->start)->buffer != current_buffer) goto recenter; @@ -19130,33 +19615,36 @@ redisplay_window (Lisp_Object window, bool just_this_one_p) ptrdiff_t it_charpos; w->optional_new_start = false; - start_display (&it, w, startp); - move_it_to (&it, PT, 0, it.last_visible_y, -1, - MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y); - /* Record IT's position now, since line_bottom_y might change - that. */ - it_charpos = IT_CHARPOS (it); - /* Make sure we set the force_start flag only if the cursor row - will be fully visible. Otherwise, the code under force_start - label below will try to move point back into view, which is - not what the code which sets optional_new_start wants. */ - if ((it.current_y == 0 || line_bottom_y (&it) < it.last_visible_y) - && !w->force_start) - { - if (it_charpos == PT) - w->force_start = true; - /* IT may overshoot PT if text at PT is invisible. */ - else if (it_charpos > PT && CHARPOS (startp) <= PT) - w->force_start = true; + if (!w->force_start) + { + start_display (&it, w, startp); + move_it_to (&it, PT, 0, it.last_visible_y, -1, + MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y); + /* Record IT's position now, since line_bottom_y might + change that. */ + it_charpos = IT_CHARPOS (it); + /* Make sure we set the force_start flag only if the cursor + row will be fully visible. Otherwise, the code under + force_start label below will try to move point back into + view, which is not what the code which sets + optional_new_start wants. */ + if (it.current_y == 0 || line_bottom_y (&it) < it.last_visible_y) + { + if (it_charpos == PT) + w->force_start = true; + /* IT may overshoot PT if text at PT is invisible. */ + else if (it_charpos > PT && CHARPOS (startp) <= PT) + w->force_start = true; #ifdef GLYPH_DEBUG - if (w->force_start) - { - if (window_frozen_p (w)) - debug_method_add (w, "set force_start from frozen window start"); - else - debug_method_add (w, "set force_start from optional_new_start"); - } + if (w->force_start) + { + if (window_frozen_p (w)) + debug_method_add (w, "set force_start from frozen window start"); + else + debug_method_add (w, "set force_start from optional_new_start"); + } #endif + } } } @@ -20064,11 +20552,19 @@ redisplay_window (Lisp_Object window, bool just_this_one_p) buffer position POS. Value is 1 if successful. It is zero if fonts were loaded during - redisplay which makes re-adjusting glyph matrices necessary, and -1 - if point would appear in the scroll margins. - (We check the former only if TRY_WINDOW_IGNORE_FONTS_CHANGE is - unset in FLAGS, and the latter only if TRY_WINDOW_CHECK_MARGINS is - set in FLAGS.) */ + redisplay or the dimensions of the desired matrix were found + insufficient, which makes re-adjusting glyph matrices necessary. + Value is -1 if point would appear in the scroll margins. (We check + the former only if TRY_WINDOW_IGNORE_FONTS_CHANGE is unset in + FLAGS, and the latter only if TRY_WINDOW_CHECK_MARGINS is set in + FLAGS.) + + Note that 'x-show-tip' invokes this function in a special way, and + in that case the return value of zero doesn't necessarily mean the + glyph matrices need to be re-adjusted, if the entire text of the + tooltip was processed and has its glyphs in the matrix's glyph + rows, i.e. if the dimensions of the matrix were found insufficient + while producing empty glyph rows beyond ZV. */ int try_window (Lisp_Object window, struct text_pos pos, int flags) @@ -20093,9 +20589,16 @@ try_window (Lisp_Object window, struct text_pos pos, int flags) /* Display all lines of W. */ while (it.current_y < it.last_visible_y) { + int last_row_scale = it.w->nrows_scale_factor; + int last_col_scale = it.w->ncols_scale_factor; if (display_line (&it, cursor_vpos)) last_text_row = it.glyph_row - 1; - if (f->fonts_changed && !(flags & TRY_WINDOW_IGNORE_FONTS_CHANGE)) + if (f->fonts_changed + && !((flags & TRY_WINDOW_IGNORE_FONTS_CHANGE) + /* If the matrix dimensions are insufficient, we _must_ + fail and let dispnew.c reallocate the matrix. */ + && last_row_scale == it.w->nrows_scale_factor + && last_col_scale == it.w->ncols_scale_factor)) return 0; } @@ -21082,6 +21585,12 @@ try_window_id (struct window *w) w->frame)))) GIVE_UP (24); + /* composition-break-at-point is incompatible with the optimizations + in this function, because we need to recompose characters when + point moves off their positions. */ + if (composition_break_at_point) + GIVE_UP (27); + /* Make sure beg_unchanged and end_unchanged are up to date. Do it only if buffer has really changed. The reason is that the gap is initially at Z for freshly visited files. The code below would @@ -24067,7 +24576,7 @@ display_line (struct it *it, int cursor_vpos) row->displays_text_p = true; row->starts_in_middle_of_char_p = it->starts_in_middle_of_char_p; it->starts_in_middle_of_char_p = false; - it->tab_offset = 0; + it->stretch_adjust = 0; it->line_number_produced_p = false; /* Arrange the overlays nicely for our purposes. Usually, we call @@ -24100,8 +24609,26 @@ display_line (struct it *it, int cursor_vpos) it->first_visible_x += x_incr; it->last_visible_x += x_incr; } - move_result = move_it_in_display_line_to (it, ZV, it->first_visible_x, - MOVE_TO_POS | MOVE_TO_X); + if (current_buffer->long_line_optimizations_p + && it->line_wrap == TRUNCATE + && window_hscroll_limited (it->w, it->f) > large_hscroll_threshold) + { + /* Special optimization for very long and truncated lines + which are hscrolled far to the left: jump directly to the + (approximate) position that is visible, instead of slowly + walking there. */ + ptrdiff_t chars_to_skip = + it->first_visible_x / FRAME_COLUMN_WIDTH (it->f); + move_result = fast_move_it_horizontally (it, chars_to_skip); + + if (move_result == MOVE_X_REACHED) + it->current_x = it->first_visible_x; + else /* use arbitrary value < first_visible_x */ + it->current_x = it->first_visible_x - FRAME_COLUMN_WIDTH (it->f); + } + else + move_result = move_it_in_display_line_to (it, ZV, it->first_visible_x, + MOVE_TO_POS | MOVE_TO_X); /* If we are under a large hscroll, move_it_in_display_line_to could hit the end of the line without reaching first_visible_x. Pretend that we did reach it. This is @@ -26394,8 +26921,8 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, tem = props; while (CONSP (tem)) { - oprops = Fplist_put (oprops, XCAR (tem), - XCAR (XCDR (tem))); + oprops = plist_put (oprops, XCAR (tem), + XCAR (XCDR (tem))); tem = XCDR (XCDR (tem)); } props = oprops; @@ -26846,13 +27373,13 @@ store_mode_line_string (const char *string, Lisp_Object lisp_string, props = mode_line_string_face_prop; else if (!NILP (mode_line_string_face)) { - Lisp_Object face = Fplist_get (props, Qface); + Lisp_Object face = plist_get (props, Qface); props = Fcopy_sequence (props); if (NILP (face)) face = mode_line_string_face; else face = list2 (face, mode_line_string_face); - props = Fplist_put (props, Qface, face); + props = plist_put (props, Qface, face); } Fadd_text_properties (make_fixnum (0), make_fixnum (len), props, lisp_string); @@ -26871,7 +27398,7 @@ store_mode_line_string (const char *string, Lisp_Object lisp_string, Lisp_Object face; if (NILP (props)) props = Ftext_properties_at (make_fixnum (0), lisp_string); - face = Fplist_get (props, Qface); + face = plist_get (props, Qface); if (NILP (face)) face = mode_line_string_face; else @@ -27921,7 +28448,7 @@ display_string (const char *string, Lisp_Object lisp_string, Lisp_Object face_st face_string); if (!NILP (display)) { - Lisp_Object min_width = Fplist_get (display, Qmin_width); + Lisp_Object min_width = plist_get (display, Qmin_width); if (!NILP (min_width)) display_min_width (it, 0, face_string, min_width); } @@ -28255,6 +28782,11 @@ static bool calc_pixel_width_or_height (double *res, struct it *it, Lisp_Object prop, struct font *font, bool width_p, int *align_to) { + /* Don't adjust for line number if we didn't yet produce it for this + screen line. This is for when this function is called from + move_it_in_display_line_to that was called by display_line to get + past the glyphs hscrolled off the left side of the window. */ + int lnum_pixel_width = it->line_number_produced_p ? it->lnum_pixel_width : 0; double pixels; # define OK_PIXELS(val) (*res = (val), true) @@ -28311,7 +28843,7 @@ calc_pixel_width_or_height (double *res, struct it *it, Lisp_Object prop, if (EQ (prop, Qtext)) return OK_PIXELS (width_p ? (window_box_width (it->w, TEXT_AREA) - - it->lnum_pixel_width) + - lnum_pixel_width) : WINDOW_BOX_HEIGHT_NO_MODE_LINE (it->w)); /* ':align_to'. First time we compute the value, window @@ -28323,14 +28855,14 @@ calc_pixel_width_or_height (double *res, struct it *it, Lisp_Object prop, /* 'left': left edge of the text area. */ if (EQ (prop, Qleft)) return OK_ALIGN_TO (window_box_left_offset (it->w, TEXT_AREA) - + it->lnum_pixel_width); + + lnum_pixel_width); /* 'right': right edge of the text area. */ if (EQ (prop, Qright)) return OK_ALIGN_TO (window_box_right_offset (it->w, TEXT_AREA)); /* 'center': the center of the text area. */ if (EQ (prop, Qcenter)) return OK_ALIGN_TO (window_box_left_offset (it->w, TEXT_AREA) - + it->lnum_pixel_width + + lnum_pixel_width + window_box_width (it->w, TEXT_AREA) / 2); /* 'left-fringe': left edge of the left fringe. */ if (EQ (prop, Qleft_fringe)) @@ -28383,7 +28915,7 @@ calc_pixel_width_or_height (double *res, struct it *it, Lisp_Object prop, ? FRAME_COLUMN_WIDTH (it->f) : FRAME_LINE_HEIGHT (it->f)); if (width_p && align_to && *align_to < 0) - return OK_PIXELS (XFLOATINT (prop) * base_unit + it->lnum_pixel_width); + return OK_PIXELS (XFLOATINT (prop) * base_unit + lnum_pixel_width); return OK_PIXELS (XFLOATINT (prop) * base_unit); } @@ -28445,7 +28977,7 @@ calc_pixel_width_or_height (double *res, struct it *it, Lisp_Object prop, { double fact; int offset = - width_p && align_to && *align_to < 0 ? it->lnum_pixel_width : 0; + width_p && align_to && *align_to < 0 ? lnum_pixel_width : 0; pixels = XFLOATINT (car); if (NILP (cdr)) return OK_PIXELS (pixels + offset); @@ -30614,14 +31146,14 @@ produce_stretch_glyph (struct it *it) plist = XCDR (it->object); /* Compute the width of the stretch. */ - if ((prop = Fplist_get (plist, QCwidth), !NILP (prop)) + if ((prop = plist_get (plist, QCwidth), !NILP (prop)) && calc_pixel_width_or_height (&tem, it, prop, font, true, NULL)) { /* Absolute width `:width WIDTH' specified and valid. */ zero_width_ok_p = true; width = (int)tem; } - else if (prop = Fplist_get (plist, QCrelative_width), NUMVAL (prop) > 0) + else if (prop = plist_get (plist, QCrelative_width), NUMVAL (prop) > 0) { /* Relative width `:relative-width FACTOR' specified and valid. Compute the width of the characters having this `display' @@ -30658,17 +31190,43 @@ produce_stretch_glyph (struct it *it) PRODUCE_GLYPHS (&it2); width = NUMVAL (prop) * it2.pixel_width; } - else if ((prop = Fplist_get (plist, QCalign_to), !NILP (prop)) + else if ((prop = plist_get (plist, QCalign_to), !NILP (prop)) && calc_pixel_width_or_height (&tem, it, prop, font, true, &align_to)) { + int x = it->current_x + it->continuation_lines_width; + int x0 = x; + /* Adjust for line numbers, if needed. */ + if (!NILP (Vdisplay_line_numbers) && it->line_number_produced_p) + { + x -= it->lnum_pixel_width; + /* Restore the original width, if required. */ + if (x + it->stretch_adjust >= it->first_visible_x) + x += it->stretch_adjust; + } + if (it->glyph_row == NULL || !it->glyph_row->mode_line_p) align_to = (align_to < 0 ? 0 : align_to - window_box_left_offset (it->w, TEXT_AREA)); else if (align_to < 0) align_to = window_box_left_offset (it->w, TEXT_AREA); - width = max (0, (int)tem + align_to - it->current_x); + width = max (0, (int)tem + align_to - x); + + int next_x = x + width; + if (!NILP (Vdisplay_line_numbers) && it->line_number_produced_p) + { + /* If the line is hscrolled, and the stretch starts before + the first visible pixel, simulate negative row->x. */ + if (x < it->first_visible_x) + { + next_x -= it->first_visible_x - x; + it->stretch_adjust = it->first_visible_x - x; + } + else + next_x -= it->stretch_adjust; + } + width = next_x - x0; zero_width_ok_p = true; } else @@ -30684,13 +31242,13 @@ produce_stretch_glyph (struct it *it) { int default_height = normal_char_height (font, ' '); - if ((prop = Fplist_get (plist, QCheight), !NILP (prop)) + if ((prop = plist_get (plist, QCheight), !NILP (prop)) && calc_pixel_width_or_height (&tem, it, prop, font, false, NULL)) { height = (int)tem; zero_height_ok_p = true; } - else if (prop = Fplist_get (plist, QCrelative_height), + else if (prop = plist_get (plist, QCrelative_height), NUMVAL (prop) > 0) height = default_height * NUMVAL (prop); else @@ -30702,7 +31260,7 @@ produce_stretch_glyph (struct it *it) /* Compute percentage of height used for ascent. If `:ascent ASCENT' is present and valid, use that. Otherwise, derive the ascent from the font in use. */ - if (prop = Fplist_get (plist, QCascent), + if (prop = plist_get (plist, QCascent), NUMVAL (prop) > 0 && NUMVAL (prop) <= 100) ascent = height * NUMVAL (prop) / 100.0; else if (!NILP (prop) @@ -31458,8 +32016,8 @@ gui_produce_glyphs (struct it *it) { x -= it->lnum_pixel_width; /* Restore the original TAB width, if required. */ - if (x + it->tab_offset >= it->first_visible_x) - x += it->tab_offset; + if (x + it->stretch_adjust >= it->first_visible_x) + x += it->stretch_adjust; } int next_tab_x = ((1 + x + tab_width - 1) / tab_width) * tab_width; @@ -31477,10 +32035,10 @@ gui_produce_glyphs (struct it *it) if (x < it->first_visible_x) { next_tab_x -= it->first_visible_x - x; - it->tab_offset = it->first_visible_x - x; + it->stretch_adjust = it->first_visible_x - x; } else - next_tab_x -= it->tab_offset; + next_tab_x -= it->stretch_adjust; } it->pixel_width = next_tab_x - x0; @@ -34049,7 +34607,7 @@ note_mode_line_or_margin_highlight (Lisp_Object window, int x, int y, if (IMAGEP (object)) { Lisp_Object image_map, hotspot; - if ((image_map = Fplist_get (XCDR (object), QCmap), + if ((image_map = plist_get (XCDR (object), QCmap), !NILP (image_map)) && (hotspot = find_hot_spot (image_map, dx, dy), CONSP (hotspot)) @@ -34064,10 +34622,10 @@ note_mode_line_or_margin_highlight (Lisp_Object window, int x, int y, if (CONSP (hotspot) && (plist = XCAR (hotspot), CONSP (plist))) { - pointer = Fplist_get (plist, Qpointer); + pointer = plist_get (plist, Qpointer); if (NILP (pointer)) pointer = Qhand; - help = Fplist_get (plist, Qhelp_echo); + help = plist_get (plist, Qhelp_echo); if (!NILP (help)) { help_echo_string = help; @@ -34078,7 +34636,7 @@ note_mode_line_or_margin_highlight (Lisp_Object window, int x, int y, } } if (NILP (pointer)) - pointer = Fplist_get (XCDR (object), QCpointer); + pointer = plist_get (XCDR (object), QCpointer); } #endif /* HAVE_WINDOW_SYSTEM */ @@ -34564,7 +35122,7 @@ note_mouse_highlight (struct frame *f, int x, int y) if (img != NULL && IMAGEP (img->spec)) { Lisp_Object image_map, hotspot; - if ((image_map = Fplist_get (XCDR (img->spec), QCmap), + if ((image_map = plist_get (XCDR (img->spec), QCmap), !NILP (image_map)) && (hotspot = find_hot_spot (image_map, glyph->slice.img.x + dx, @@ -34582,10 +35140,10 @@ note_mouse_highlight (struct frame *f, int x, int y) if (CONSP (hotspot) && (plist = XCAR (hotspot), CONSP (plist))) { - pointer = Fplist_get (plist, Qpointer); + pointer = plist_get (plist, Qpointer); if (NILP (pointer)) pointer = Qhand; - help_echo_string = Fplist_get (plist, Qhelp_echo); + help_echo_string = plist_get (plist, Qhelp_echo); if (!NILP (help_echo_string)) { help_echo_window = window; @@ -34595,7 +35153,7 @@ note_mouse_highlight (struct frame *f, int x, int y) } } if (NILP (pointer)) - pointer = Fplist_get (XCDR (img->spec), QCpointer); + pointer = plist_get (XCDR (img->spec), QCpointer); } } #endif /* HAVE_WINDOW_SYSTEM */ @@ -35674,12 +36232,12 @@ be let-bound around code that needs to disable messages temporarily. */); defsubr (&Sbidi_find_overridden_directionality); defsubr (&Sdisplay__line_is_continued_p); defsubr (&Sget_display_property); + defsubr (&Slong_line_optimizations_p); DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook"); DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map"); DEFSYM (Qoverriding_local_map, "overriding-local-map"); DEFSYM (Qwindow_scroll_functions, "window-scroll-functions"); - DEFSYM (Qredisplay_end_trigger_functions, "redisplay-end-trigger-functions"); DEFSYM (Qinhibit_point_motion_hooks, "inhibit-point-motion-hooks"); DEFSYM (Qeval, "eval"); DEFSYM (QCdata, ":data"); @@ -35777,7 +36335,7 @@ be let-bound around code that needs to disable messages temporarily. */); DEFSYM (Qinhibit_free_realized_faces, "inhibit-free-realized-faces"); - list_of_error = list1 (list2 (Qerror, Qvoid_variable)); + list_of_error = list1 (Qerror); staticpro (&list_of_error); /* Values of those variables at last redisplay are stored as @@ -36083,12 +36641,6 @@ is scrolled. It is not designed for that, and such use probably won't work. */); Vwindow_scroll_functions = Qnil; - DEFVAR_LISP ("redisplay-end-trigger-functions", Vredisplay_end_trigger_functions, - doc: /* Functions called when redisplay of a window reaches the end trigger. -Each function is called with two arguments, the window and the end trigger value. -See `set-window-redisplay-end-trigger'. */); - Vredisplay_end_trigger_functions = Qnil; - DEFVAR_LISP ("mouse-autoselect-window", Vmouse_autoselect_window, doc: /* Non-nil means autoselect window with mouse pointer. If nil, do not autoselect windows. @@ -36217,7 +36769,13 @@ The tool bar style must also show labels for this to have any effect, see doc: /* List of functions to call to fontify regions of text. Each function is called with one argument POS. Functions must fontify a region starting at POS in the current buffer, and give -fontified regions the property `fontified'. */); +fontified regions the property `fontified' with a non-nil value. + +Note that, when the buffer contains one or more lines whose length is +above `long-line-threshold', these functions are called with the buffer +narrowed to a small portion around POS, and the narrowing is locked (see +`narrow-to-region'), so that these functions cannot use `widen' to gain +access to other portions of buffer text. */); Vfontification_functions = Qnil; Fmake_variable_buffer_local (Qfontification_functions); @@ -36558,7 +37116,9 @@ GRAPHICAL and TEXT should each have one of the values listed above. The char-table has one extra slot to control the display of a character for which no font is found. This slot only takes effect on graphical terminals. Its value should be an ASCII acronym string, `hex-code', `empty-box', or -`thin-space'. The default is `empty-box'. +`thin-space'. It could also be a cons cell of any two of these, to specify +separate values for graphical and text terminals. +The default is `empty-box'. If a character has a non-nil entry in an active display table, the display table takes effect; in this case, Emacs does not consult @@ -36667,6 +37227,22 @@ and display the most important part of the minibuffer. */); This makes it easier to edit character sequences that are composed on display. */); composition_break_at_point = false; + + DEFVAR_INT ("max-redisplay-ticks", max_redisplay_ticks, + doc: /* Maximum number of redisplay ticks before aborting redisplay of a window. + +This allows to abort the display of a window if the amount of low-level +redisplay operations exceeds the value of this variable. When display of +a window is aborted due to this reason, the buffer shown in that window +will not have its windows redisplayed until the buffer is modified or until +you type \\[recenter-top-bottom] with one of its windows selected. +You can also decide to kill the buffer and visit it in some +other way, like under `so-long-mode' or literally. + +The default value is zero, which disables this feature. +The recommended non-zero value is between 100000 and 1000000, +depending on your patience and the speed of your system. */); + max_redisplay_ticks = 0; } diff --git a/src/xfaces.c b/src/xfaces.c index 7395ce157ec..70d5cbeb4c7 100644 --- a/src/xfaces.c +++ b/src/xfaces.c @@ -295,6 +295,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #define IGNORE_DEFFACE_P(ATTR) EQ ((ATTR), QCignore_defface) +/* True if face attribute ATTR is `reset'. */ + +#define RESET_P(ATTR) EQ ((ATTR), Qreset) + /* Size of hash table of realized faces in face caches (should be a prime number). */ @@ -961,7 +965,7 @@ DEFUN ("color-values-from-color-spec", Scolor_values_from_color_spec, 1, 1, 0, doc: /* Parse color SPEC as a numeric color and return (RED GREEN BLUE). -This function recognises the following formats for SPEC: +This function recognizes the following formats for SPEC: #RGB, where R, G and B are hex numbers of equal length, 1-4 digits each. rgb:R/G/B, where R, G, and B are hex numbers, 1-4 digits each. @@ -1584,8 +1588,9 @@ the face font sort order, see `face-font-selection-order'. */) /* If the font was specified in a way different from XLFD (e.g., on MS-Windows), we will have a number there, not 'p'. */ - || EQ (spacing, - make_fixnum (FONT_SPACING_PROPORTIONAL))) + || BASE_EQ (spacing, + make_fixnum + (FONT_SPACING_PROPORTIONAL))) ? Qnil : Qt, Ffont_xlfd_name (font, Qnil), AREF (font, FONT_REGISTRY_INDEX)); @@ -1756,57 +1761,72 @@ check_lface_attrs (Lisp_Object attrs[LFACE_VECTOR_SIZE]) { eassert (UNSPECIFIEDP (attrs[LFACE_FAMILY_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_FAMILY_INDEX]) + || RESET_P (attrs[LFACE_FAMILY_INDEX]) || STRINGP (attrs[LFACE_FAMILY_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_FOUNDRY_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_FOUNDRY_INDEX]) + || RESET_P (attrs[LFACE_FOUNDRY_INDEX]) || STRINGP (attrs[LFACE_FOUNDRY_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_SWIDTH_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_SWIDTH_INDEX]) + || RESET_P (attrs[LFACE_SWIDTH_INDEX]) || SYMBOLP (attrs[LFACE_SWIDTH_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_HEIGHT_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_HEIGHT_INDEX]) + || RESET_P (attrs[LFACE_HEIGHT_INDEX]) || NUMBERP (attrs[LFACE_HEIGHT_INDEX]) || FUNCTIONP (attrs[LFACE_HEIGHT_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_WEIGHT_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_WEIGHT_INDEX]) + || RESET_P (attrs[LFACE_WEIGHT_INDEX]) || SYMBOLP (attrs[LFACE_WEIGHT_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_SLANT_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_SLANT_INDEX]) + || RESET_P (attrs[LFACE_SLANT_INDEX]) || SYMBOLP (attrs[LFACE_SLANT_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_UNDERLINE_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_UNDERLINE_INDEX]) + || RESET_P (attrs[LFACE_UNDERLINE_INDEX]) || SYMBOLP (attrs[LFACE_UNDERLINE_INDEX]) || STRINGP (attrs[LFACE_UNDERLINE_INDEX]) || CONSP (attrs[LFACE_UNDERLINE_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_EXTEND_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_EXTEND_INDEX]) + || RESET_P (attrs[LFACE_EXTEND_INDEX]) || SYMBOLP (attrs[LFACE_EXTEND_INDEX]) || STRINGP (attrs[LFACE_EXTEND_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_OVERLINE_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_OVERLINE_INDEX]) + || RESET_P (attrs[LFACE_OVERLINE_INDEX]) || SYMBOLP (attrs[LFACE_OVERLINE_INDEX]) || STRINGP (attrs[LFACE_OVERLINE_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_STRIKE_THROUGH_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_STRIKE_THROUGH_INDEX]) + || RESET_P (attrs[LFACE_STRIKE_THROUGH_INDEX]) || SYMBOLP (attrs[LFACE_STRIKE_THROUGH_INDEX]) || STRINGP (attrs[LFACE_STRIKE_THROUGH_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_BOX_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_BOX_INDEX]) + || RESET_P (attrs[LFACE_BOX_INDEX]) || SYMBOLP (attrs[LFACE_BOX_INDEX]) || STRINGP (attrs[LFACE_BOX_INDEX]) || FIXNUMP (attrs[LFACE_BOX_INDEX]) || CONSP (attrs[LFACE_BOX_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_INVERSE_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_INVERSE_INDEX]) + || RESET_P (attrs[LFACE_INVERSE_INDEX]) || SYMBOLP (attrs[LFACE_INVERSE_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_FOREGROUND_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_FOREGROUND_INDEX]) + || RESET_P (attrs[LFACE_FOREGROUND_INDEX]) || STRINGP (attrs[LFACE_FOREGROUND_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_DISTANT_FOREGROUND_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_DISTANT_FOREGROUND_INDEX]) + || RESET_P (attrs[LFACE_DISTANT_FOREGROUND_INDEX]) || STRINGP (attrs[LFACE_DISTANT_FOREGROUND_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_BACKGROUND_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_BACKGROUND_INDEX]) + || RESET_P (attrs[LFACE_BACKGROUND_INDEX]) || STRINGP (attrs[LFACE_BACKGROUND_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_INHERIT_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_INHERIT_INDEX]) @@ -1816,13 +1836,16 @@ check_lface_attrs (Lisp_Object attrs[LFACE_VECTOR_SIZE]) #ifdef HAVE_WINDOW_SYSTEM eassert (UNSPECIFIEDP (attrs[LFACE_STIPPLE_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_STIPPLE_INDEX]) + || RESET_P (attrs[LFACE_STIPPLE_INDEX]) || SYMBOLP (attrs[LFACE_STIPPLE_INDEX]) || !NILP (Fbitmap_spec_p (attrs[LFACE_STIPPLE_INDEX]))); eassert (UNSPECIFIEDP (attrs[LFACE_FONT_INDEX]) || IGNORE_DEFFACE_P (attrs[LFACE_FONT_INDEX]) + || RESET_P (attrs[LFACE_FONT_INDEX]) || FONTP (attrs[LFACE_FONT_INDEX])); eassert (UNSPECIFIEDP (attrs[LFACE_FONTSET_INDEX]) || STRINGP (attrs[LFACE_FONTSET_INDEX]) + || RESET_P (attrs[LFACE_FONTSET_INDEX]) || NILP (attrs[LFACE_FONTSET_INDEX])); #endif } @@ -1942,7 +1965,7 @@ resolve_face_name (Lisp_Object face_name, bool signal_p) break; tortoise = Fget (tortoise, Qface_alias); - if (EQ (hare, tortoise)) + if (BASE_EQ (hare, tortoise)) { if (signal_p) circular_list (orig_face); @@ -2082,7 +2105,7 @@ lface_fully_specified_p (Lisp_Object attrs[LFACE_VECTOR_SIZE]) #ifdef HAVE_WINDOW_SYSTEM /* Set font-related attributes of Lisp face LFACE from FONT-OBJECT. - If FORCE_P, set only unspecified attributes of LFACE. The + If FORCE_P is zero, set only unspecified attributes of LFACE. The exception is `font' attribute. It is set to FONT_OBJECT regardless of FORCE_P. */ @@ -2338,6 +2361,14 @@ merge_named_face (struct window *w, Lisp_Object from[LFACE_VECTOR_SIZE], val; bool ok = get_lface_attributes (w, f, face_name, from, false, named_merge_points); + if (ok && !EQ (face_name, Qdefault)) + { + struct face *deflt = FACE_FROM_ID (f, DEFAULT_FACE_ID); + int i; + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + if (EQ (from[i], Qreset)) + from[i] = deflt->lface[i]; + } if (ok && (attr_filter == 0 /* No filter. */ || (!NILP (from[attr_filter]) /* Filter, but specified. */ @@ -3086,7 +3117,9 @@ FRAME 0 means change the face on all frames, and change the default if (EQ (attr, QCfamily)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_STRING (value); if (SCHARS (value) == 0) @@ -3098,7 +3131,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCfoundry)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_STRING (value); if (SCHARS (value) == 0) @@ -3110,7 +3145,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCheight)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { if (EQ (face, Qdefault)) { @@ -3138,7 +3175,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCweight)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_SYMBOL (value); if (FONT_WEIGHT_NAME_NUMERIC (value) < 0) @@ -3150,7 +3189,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCslant)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_SYMBOL (value); if (FONT_SLANT_NAME_NUMERIC (value) < 0) @@ -3164,7 +3205,7 @@ FRAME 0 means change the face on all frames, and change the default { bool valid_p = false; - if (UNSPECIFIEDP (value) || IGNORE_DEFFACE_P (value)) + if (UNSPECIFIEDP (value) || IGNORE_DEFFACE_P (value) || RESET_P (value)) valid_p = true; else if (NILP (value) || EQ (value, Qt)) valid_p = true; @@ -3222,7 +3263,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCoverline)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) if ((SYMBOLP (value) && !EQ (value, Qt) && !NILP (value)) @@ -3236,7 +3279,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCstrike_through)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) if ((SYMBOLP (value) && !EQ (value, Qt) && !NILP (value)) @@ -3257,7 +3302,7 @@ FRAME 0 means change the face on all frames, and change the default if (EQ (value, Qt)) value = make_fixnum (1); - if (UNSPECIFIEDP (value) || IGNORE_DEFFACE_P (value)) + if (UNSPECIFIEDP (value) || IGNORE_DEFFACE_P (value) || RESET_P (value)) valid_p = true; else if (NILP (value)) valid_p = true; @@ -3319,7 +3364,9 @@ FRAME 0 means change the face on all frames, and change the default else if (EQ (attr, QCinverse_video) || EQ (attr, QCreverse_video)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_SYMBOL (value); if (!EQ (value, Qt) && !NILP (value)) @@ -3330,7 +3377,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCextend)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_SYMBOL (value); if (!EQ (value, Qt) && !NILP (value)) @@ -3344,7 +3393,9 @@ FRAME 0 means change the face on all frames, and change the default /* Compatibility with 20.x. */ if (NILP (value)) value = Qunspecified; - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { /* Don't check for valid color names here because it depends on the frame (display) whether the color will be valid @@ -3361,7 +3412,9 @@ FRAME 0 means change the face on all frames, and change the default /* Compatibility with 20.x. */ if (NILP (value)) value = Qunspecified; - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { /* Don't check for valid color names here because it depends on the frame (display) whether the color will be valid @@ -3378,7 +3431,9 @@ FRAME 0 means change the face on all frames, and change the default /* Compatibility with 20.x. */ if (NILP (value)) value = Qunspecified; - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { /* Don't check for valid color names here because it depends on the frame (display) whether the color will be valid @@ -3393,7 +3448,9 @@ FRAME 0 means change the face on all frames, and change the default else if (EQ (attr, QCstipple)) { #if defined (HAVE_WINDOW_SYSTEM) - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value) && !NILP (value) && NILP (Fbitmap_spec_p (value))) signal_error ("Invalid stipple attribute", value); @@ -3403,7 +3460,9 @@ FRAME 0 means change the face on all frames, and change the default } else if (EQ (attr, QCwidth)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { CHECK_SYMBOL (value); if (FONT_WIDTH_NAME_NUMERIC (value) < 0) @@ -3418,7 +3477,9 @@ FRAME 0 means change the face on all frames, and change the default #ifdef HAVE_WINDOW_SYSTEM if (EQ (frame, Qt) || FRAME_WINDOW_P (f)) { - if (!UNSPECIFIEDP (value) && !IGNORE_DEFFACE_P (value)) + if (!UNSPECIFIEDP (value) + && !IGNORE_DEFFACE_P (value) + && !RESET_P (value)) { struct frame *f1; @@ -3475,12 +3536,15 @@ FRAME 0 means change the face on all frames, and change the default #ifdef HAVE_WINDOW_SYSTEM if (EQ (frame, Qt) || FRAME_WINDOW_P (f)) { - Lisp_Object tmp; + Lisp_Object tmp = value; old_value = LFACE_FONTSET (lface); - tmp = Fquery_fontset (value, Qnil); - if (NILP (tmp)) - signal_error ("Invalid fontset name", value); + if (!RESET_P (value)) + { + tmp = Fquery_fontset (value, Qnil); + if (NILP (tmp)) + signal_error ("Invalid fontset name", value); + } ASET (lface, LFACE_FONTSET_INDEX, value = tmp); } #endif /* HAVE_WINDOW_SYSTEM */ @@ -3502,14 +3566,20 @@ FRAME 0 means change the face on all frames, and change the default else if (EQ (attr, QCbold)) { old_value = LFACE_WEIGHT (lface); - ASET (lface, LFACE_WEIGHT_INDEX, NILP (value) ? Qnormal : Qbold); + if (RESET_P (value)) + ASET (lface, LFACE_WEIGHT_INDEX, value); + else + ASET (lface, LFACE_WEIGHT_INDEX, NILP (value) ? Qnormal : Qbold); prop_index = FONT_WEIGHT_INDEX; } else if (EQ (attr, QCitalic)) { attr = QCslant; old_value = LFACE_SLANT (lface); - ASET (lface, LFACE_SLANT_INDEX, NILP (value) ? Qnormal : Qitalic); + if (RESET_P (value)) + ASET (lface, LFACE_SLANT_INDEX, value); + else + ASET (lface, LFACE_SLANT_INDEX, NILP (value) ? Qnormal : Qitalic); prop_index = FONT_SLANT_INDEX; } else @@ -4119,6 +4189,7 @@ Default face attributes override any local face attributes. */) /* Ensure that the face vector is fully specified by merging the previously-cached vector. */ memcpy (attrs, oldface->lface, sizeof attrs); + merge_face_vectors (NULL, f, lvec, attrs, 0); vcopy (local_lface, 0, attrs, LFACE_VECTOR_SIZE); newface = realize_face (c, lvec, DEFAULT_FACE_ID); @@ -4885,6 +4956,13 @@ lookup_named_face (struct window *w, struct frame *f, return -1; memcpy (attrs, default_face->lface, sizeof attrs); + + /* Make explicit any attributes whose value is 'reset'. */ + int i; + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + if (EQ (symbol_attrs[i], Qreset)) + symbol_attrs[i] = attrs[i]; + merge_face_vectors (w, f, symbol_attrs, attrs, 0); return lookup_face (f, attrs); @@ -5055,6 +5133,13 @@ lookup_derived_face (struct window *w, default_face = FACE_FROM_ID (f, face_id); memcpy (attrs, default_face->lface, sizeof attrs); + + /* Make explicit any attributes whose value is 'reset'. */ + int i; + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + if (EQ (symbol_attrs[i], Qreset)) + symbol_attrs[i] = attrs[i]; + merge_face_vectors (w, f, symbol_attrs, attrs, 0); return lookup_face (f, attrs); } @@ -5102,49 +5187,60 @@ gui_supports_face_attributes_p (struct frame *f, struct face *def_face) { Lisp_Object *def_attrs = def_face->lface; + Lisp_Object lattrs[LFACE_VECTOR_SIZE]; + + /* Make explicit any attributes whose value is 'reset'. */ + int i; + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + { + if (EQ (attrs[i], Qreset)) + lattrs[i] = def_attrs[i]; + else + lattrs[i] = attrs[i]; + } /* Check that other specified attributes are different from the default face. */ - if ((!UNSPECIFIEDP (attrs[LFACE_UNDERLINE_INDEX]) - && face_attr_equal_p (attrs[LFACE_UNDERLINE_INDEX], + if ((!UNSPECIFIEDP (lattrs[LFACE_UNDERLINE_INDEX]) + && face_attr_equal_p (lattrs[LFACE_UNDERLINE_INDEX], def_attrs[LFACE_UNDERLINE_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_INVERSE_INDEX]) - && face_attr_equal_p (attrs[LFACE_INVERSE_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_INVERSE_INDEX]) + && face_attr_equal_p (lattrs[LFACE_INVERSE_INDEX], def_attrs[LFACE_INVERSE_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_EXTEND_INDEX]) - && face_attr_equal_p (attrs[LFACE_EXTEND_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_EXTEND_INDEX]) + && face_attr_equal_p (lattrs[LFACE_EXTEND_INDEX], def_attrs[LFACE_EXTEND_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_FOREGROUND_INDEX]) - && face_attr_equal_p (attrs[LFACE_FOREGROUND_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_FOREGROUND_INDEX]) + && face_attr_equal_p (lattrs[LFACE_FOREGROUND_INDEX], def_attrs[LFACE_FOREGROUND_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_DISTANT_FOREGROUND_INDEX]) - && face_attr_equal_p (attrs[LFACE_DISTANT_FOREGROUND_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_DISTANT_FOREGROUND_INDEX]) + && face_attr_equal_p (lattrs[LFACE_DISTANT_FOREGROUND_INDEX], def_attrs[LFACE_DISTANT_FOREGROUND_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_BACKGROUND_INDEX]) - && face_attr_equal_p (attrs[LFACE_BACKGROUND_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_BACKGROUND_INDEX]) + && face_attr_equal_p (lattrs[LFACE_BACKGROUND_INDEX], def_attrs[LFACE_BACKGROUND_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_STIPPLE_INDEX]) - && face_attr_equal_p (attrs[LFACE_STIPPLE_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_STIPPLE_INDEX]) + && face_attr_equal_p (lattrs[LFACE_STIPPLE_INDEX], def_attrs[LFACE_STIPPLE_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_OVERLINE_INDEX]) - && face_attr_equal_p (attrs[LFACE_OVERLINE_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_OVERLINE_INDEX]) + && face_attr_equal_p (lattrs[LFACE_OVERLINE_INDEX], def_attrs[LFACE_OVERLINE_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_STRIKE_THROUGH_INDEX]) - && face_attr_equal_p (attrs[LFACE_STRIKE_THROUGH_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_STRIKE_THROUGH_INDEX]) + && face_attr_equal_p (lattrs[LFACE_STRIKE_THROUGH_INDEX], def_attrs[LFACE_STRIKE_THROUGH_INDEX])) - || (!UNSPECIFIEDP (attrs[LFACE_BOX_INDEX]) - && face_attr_equal_p (attrs[LFACE_BOX_INDEX], + || (!UNSPECIFIEDP (lattrs[LFACE_BOX_INDEX]) + && face_attr_equal_p (lattrs[LFACE_BOX_INDEX], def_attrs[LFACE_BOX_INDEX]))) return false; /* Check font-related attributes, as those are the most commonly "unsupported" on a window-system (because of missing fonts). */ - if (!UNSPECIFIEDP (attrs[LFACE_FAMILY_INDEX]) - || !UNSPECIFIEDP (attrs[LFACE_FOUNDRY_INDEX]) - || !UNSPECIFIEDP (attrs[LFACE_HEIGHT_INDEX]) - || !UNSPECIFIEDP (attrs[LFACE_WEIGHT_INDEX]) - || !UNSPECIFIEDP (attrs[LFACE_SLANT_INDEX]) - || !UNSPECIFIEDP (attrs[LFACE_SWIDTH_INDEX])) + if (!UNSPECIFIEDP (lattrs[LFACE_FAMILY_INDEX]) + || !UNSPECIFIEDP (lattrs[LFACE_FOUNDRY_INDEX]) + || !UNSPECIFIEDP (lattrs[LFACE_HEIGHT_INDEX]) + || !UNSPECIFIEDP (lattrs[LFACE_WEIGHT_INDEX]) + || !UNSPECIFIEDP (lattrs[LFACE_SLANT_INDEX]) + || !UNSPECIFIEDP (lattrs[LFACE_SWIDTH_INDEX])) { int face_id; struct face *face; @@ -5176,8 +5272,9 @@ gui_supports_face_attributes_p (struct frame *f, return true; s1 = SYMBOL_NAME (face->font->props[i]); s2 = SYMBOL_NAME (def_face->font->props[i]); - if (! EQ (Fcompare_strings (s1, make_fixnum (0), Qnil, - s2, make_fixnum (0), Qnil, Qt), Qt)) + if (! BASE_EQ (Fcompare_strings (s1, make_fixnum (0), Qnil, + s2, make_fixnum (0), Qnil, Qt), + Qt)) return true; } return false; @@ -5809,8 +5906,16 @@ realize_named_face (struct frame *f, Lisp_Object symbol, int id) lface = Finternal_make_lisp_face (symbol, frame); } - /* Merge SYMBOL's face with the default face. */ + get_lface_attributes_no_remap (f, symbol, symbol_attrs, true); + + /* Handle the 'reset' pseudo-value of any attribute by replacing it + with the corresponding value of the default face. */ + int i; + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + if (EQ (symbol_attrs[i], Qreset)) + symbol_attrs[i] = attrs[i]; + /* Merge SYMBOL's face with the default face. */ merge_face_vectors (NULL, f, symbol_attrs, attrs, 0); /* Realize the face. */ @@ -5950,7 +6055,7 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE] } if (! FONT_OBJECT_P (attrs[LFACE_FONT_INDEX])) attrs[LFACE_FONT_INDEX] - = font_load_for_lface (f, attrs, Ffont_spec (0, NULL)); + = font_load_for_lface (f, attrs, attrs[LFACE_FONT_INDEX]); if (FONT_OBJECT_P (attrs[LFACE_FONT_INDEX])) { face->font = XFONT_OBJECT (attrs[LFACE_FONT_INDEX]); @@ -6727,7 +6832,21 @@ merge_faces (struct window *w, Lisp_Object face_name, int face_id, if (!face) return base_face_id; - merge_face_vectors (w, f, face->lface, attrs, 0); + if (face_id != DEFAULT_FACE_ID) + { + struct face *deflt = FACE_FROM_ID (f, DEFAULT_FACE_ID); + Lisp_Object lface_attrs[LFACE_VECTOR_SIZE]; + int i; + + memcpy (lface_attrs, face->lface, LFACE_VECTOR_SIZE); + /* Make explicit any attributes whose value is 'reset'. */ + for (i = 1; i < LFACE_VECTOR_SIZE; i++) + if (EQ (lface_attrs[i], Qreset)) + lface_attrs[i] = deflt->lface[i]; + merge_face_vectors (w, f, lface_attrs, attrs, 0); + } + else + merge_face_vectors (w, f, face->lface, attrs, 0); } /* Look up a realized face with the given face attributes, @@ -6996,6 +7115,7 @@ syms_of_xfaces (void) DEFSYM (Qblack, "black"); DEFSYM (Qoblique, "oblique"); DEFSYM (Qitalic, "italic"); + DEFSYM (Qreset, "reset"); /* The symbols `foreground-color' and `background-color' which can be used as part of a `face' property. This is for compatibility with diff --git a/src/xfns.c b/src/xfns.c index 05023524a7e..0b1f707e9fc 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -609,24 +609,24 @@ x_relative_mouse_position (struct frame *f, int *x, int *y) block_input (); - XQueryPointer (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, + x_query_pointer (FRAME_X_DISPLAY (f), + FRAME_DISPLAY_INFO (f)->root_window, - /* The root window which contains the pointer. */ - &root, + /* The root window which contains the pointer. */ + &root, - /* Window pointer is on, not used */ - &dummy_window, + /* Window pointer is on, not used */ + &dummy_window, - /* The position on that root window. */ - x, y, + /* The position on that root window. */ + x, y, - /* x/y in dummy_window coordinates, not used. */ - &dummy, &dummy, + /* x/y in dummy_window coordinates, not used. */ + &dummy, &dummy, - /* Modifier keys and pointer buttons, about which - we don't care. */ - (unsigned int *) &dummy); + /* Modifier keys and pointer buttons, about which + we don't care. */ + (unsigned int *) &dummy); XTranslateCoordinates (FRAME_X_DISPLAY (f), @@ -838,21 +838,9 @@ x_set_inhibit_double_buffering (struct frame *f, block_input (); if (want_double_buffering != was_double_buffered) - { - /* Force XftDraw etc to be recreated with the new double - buffered drawable. */ - font_drop_xrender_surfaces (f); - - /* Scroll bars decide whether or not to use a back buffer - based on the value of this frame parameter, so destroy - all scroll bars. */ -#ifndef USE_TOOLKIT_SCROLL_BARS - if (FRAME_TERMINAL (f)->condemn_scroll_bars_hook) - FRAME_TERMINAL (f)->condemn_scroll_bars_hook (f); - if (FRAME_TERMINAL (f)->judge_scroll_bars_hook) - FRAME_TERMINAL (f)->judge_scroll_bars_hook (f); -#endif - } + /* Force XftDraw etc to be recreated with the new double + buffered drawable. */ + font_drop_xrender_surfaces (f); if (FRAME_X_DOUBLE_BUFFERED_P (f) && !want_double_buffering) tear_down_x_back_buffer (f); else if (!FRAME_X_DOUBLE_BUFFERED_P (f) && want_double_buffering) @@ -976,6 +964,16 @@ x_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_valu gdk_x11_window_set_frame_sync_enabled (window, FALSE); } #endif + +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* Frame synchronization can't be used in child frames since + they are not directly managed by the compositing manager. + Re-enabling vsync in former child frames also leads to + inconsistent display. In addition, they can only be updated + outside of a toplevel frame. */ + FRAME_X_OUTPUT (f)->use_vsync_p = false; + FRAME_X_WAITING_FOR_DRAW (f) = false; +#endif unblock_input (); fset_parent_frame (f, new_value); @@ -1204,20 +1202,6 @@ x_set_background_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval) xg_set_background_color (f, bg); #endif -#ifndef USE_TOOLKIT_SCROLL_BARS /* Turns out to be annoying with - toolkit scroll bars. */ - { - Lisp_Object bar; - for (bar = FRAME_SCROLL_BARS (f); - !NILP (bar); - bar = XSCROLL_BAR (bar)->next) - { - Window window = XSCROLL_BAR (bar)->x_window; - XSetWindowBackground (dpy, window, bg); - } - } -#endif /* USE_TOOLKIT_SCROLL_BARS */ - unblock_input (); update_face_from_frame_parameter (f, Qbackground_color, arg); @@ -1584,7 +1568,7 @@ x_set_icon_type (struct frame *f, Lisp_Object arg, Lisp_Object oldval) if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!STRINGP (oldval) && NILP (oldval) == NILP (arg)) @@ -1616,7 +1600,7 @@ x_set_icon_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval) if (STRINGP (arg)) { - if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt)) + if (STRINGP (oldval) && BASE_EQ (Fstring_equal (oldval, arg), Qt)) return; } else if (!NILP (arg) || NILP (oldval)) @@ -2433,6 +2417,28 @@ x_set_alpha (struct frame *f, Lisp_Object arg, Lisp_Object oldval) } } +static void +x_set_use_frame_synchronization (struct frame *f, Lisp_Object arg, + Lisp_Object oldval) +{ +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + struct x_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (!NILP (arg) && FRAME_X_EXTENDED_COUNTER (f)) + FRAME_X_OUTPUT (f)->use_vsync_p + = x_wm_supports (f, dpyinfo->Xatom_net_wm_frame_drawn); + else + FRAME_X_OUTPUT (f)->use_vsync_p = false; + + store_frame_param (f, Quse_frame_synchronization, + FRAME_X_OUTPUT (f)->use_vsync_p ? Qt : Qnil); +#else + store_frame_param (f, Quse_frame_synchronization, Qnil); +#endif +} + /* Record in frame F the specified or default value according to ALIST of the parameter named PROP (a Lisp symbol). If no value is @@ -3712,6 +3718,16 @@ setup_xi_event_mask (struct frame *f) XIEventMask mask; ptrdiff_t l = XIMaskLen (XI_LASTEVENT); unsigned char *m; +#ifndef HAVE_XINPUT2_1 + /* Set up fallback values, since XIGetSelectedEvents doesn't work + with this version of libXi. */ + XIEventMask *selected; + + selected = xzalloc (sizeof *selected + l); + selected->mask = ((unsigned char *) selected) + sizeof *selected; + selected->mask_len = l; + selected->deviceid = XIAllMasterDevices; +#endif mask.mask = m = alloca (l); memset (m, 0, l); @@ -3736,6 +3752,12 @@ setup_xi_event_mask (struct frame *f) FRAME_X_WINDOW (f), &mask, 1); + /* Fortunately `xi_masks' isn't used on GTK 3, where we really have + to get the event mask from the X server. */ +#ifndef HAVE_XINPUT2_1 + memcpy (selected->mask, m, l); +#endif + memset (m, 0, l); #endif /* !HAVE_GTK3 */ @@ -3751,14 +3773,11 @@ setup_xi_event_mask (struct frame *f) memset (m, 0, l); #endif - mask.deviceid = XIAllDevices; - - XISetMask (m, XI_PropertyEvent); - XISetMask (m, XI_HierarchyChanged); - XISetMask (m, XI_DeviceChanged); #ifdef HAVE_XINPUT2_2 if (FRAME_DISPLAY_INFO (f)->xi2_version >= 2) { + mask.deviceid = XIAllDevices; + XISetMask (m, XI_TouchBegin); XISetMask (m, XI_TouchUpdate); XISetMask (m, XI_TouchEnd); @@ -3770,11 +3789,18 @@ setup_xi_event_mask (struct frame *f) XISetMask (m, XI_GesturePinchEnd); } #endif + + XISelectEvents (FRAME_X_DISPLAY (f), + FRAME_X_WINDOW (f), + &mask, 1); } #endif - XISelectEvents (FRAME_X_DISPLAY (f), - FRAME_X_WINDOW (f), - &mask, 1); + +#ifndef HAVE_XINPUT2_1 + FRAME_X_OUTPUT (f)->xi_masks = selected; + FRAME_X_OUTPUT (f)->num_xi_masks = 1; +#endif + unblock_input (); } #endif @@ -4935,7 +4961,10 @@ This function is an internal primitive--use `make-frame' instead. */) x_icon (f, parms); x_make_gc (f); -#ifdef HAVE_XINPUT2 + /* While this function is present in versions of libXi that only + support 2.0, it does not release the display lock after + finishing, leading to a deadlock. */ +#if defined HAVE_XINPUT2 && defined HAVE_XINPUT2_1 if (dpyinfo->supports_xi2) FRAME_X_OUTPUT (f)->xi_masks = XIGetSelectedEvents (dpyinfo->display, FRAME_X_WINDOW (f), @@ -5088,7 +5117,10 @@ This function is an internal primitive--use `make-frame' instead. */) } #ifdef HAVE_XSYNC - if (dpyinfo->xsync_supported_p) + if (dpyinfo->xsync_supported_p + /* Frame synchronization isn't supported in child frames. */ + && NILP (parent_frame) + && !f->output_data.x->explicit_parent) { #ifndef HAVE_GTK3 XSyncValue initial_value; @@ -5123,12 +5155,21 @@ This function is an internal primitive--use `make-frame' instead. */) (unsigned char *) &counters, ((STRINGP (value) && !strcmp (SSDATA (value), "extended")) ? 2 : 1)); + +#if defined HAVE_XSYNCTRIGGERFENCE && !defined USE_GTK \ + && defined HAVE_CLOCK_GETTIME + x_sync_init_fences (f); +#endif #endif } #endif unblock_input (); + /* Set whether or not frame synchronization is enabled. */ + gui_default_parameter (f, parms, Quse_frame_synchronization, Qt, + NULL, NULL, RES_TYPE_BOOLEAN); + /* Works iff frame has been already mapped. */ gui_default_parameter (f, parms, Qskip_taskbar, Qnil, NULL, NULL, RES_TYPE_BOOLEAN); @@ -5387,9 +5428,9 @@ DEFUN ("x-server-input-extension-version", Fx_server_input_extension_version, doc: /* Return the version of the X Input Extension supported by TERMINAL. The value is nil if TERMINAL's X server doesn't support the X Input Extension extension, or if Emacs doesn't support the version present -on that server. Otherwise, the return value is a list of the the -major and minor versions of the X Input Extension extension running on -that server. */) +on that server. Otherwise, the return value is a list of the major +and minor versions of the X Input Extension extension running on that +server. */) (Lisp_Object terminal) { #ifdef HAVE_XINPUT2 @@ -6781,10 +6822,10 @@ selected frame's display. */) return Qnil; block_input (); - XQueryPointer (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, - &root, &dummy_window, &x, &y, &dummy, &dummy, - (unsigned int *) &dummy); + x_query_pointer (FRAME_X_DISPLAY (f), + FRAME_DISPLAY_INFO (f)->root_window, + &root, &dummy_window, &x, &y, &dummy, &dummy, + (unsigned int *) &dummy); unblock_input (); return Fcons (make_fixnum (x), make_fixnum (y)); @@ -6809,17 +6850,16 @@ The coordinates X and Y are interpreted in pixels relative to a position #ifdef HAVE_XINPUT2 int deviceid; - if (FRAME_DISPLAY_INFO (f)->supports_xi2) + deviceid = FRAME_DISPLAY_INFO (f)->client_pointer_device; + + if (FRAME_DISPLAY_INFO (f)->supports_xi2 + && deviceid != -1) { - XGrabServer (FRAME_X_DISPLAY (f)); - if (XIGetClientPointer (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - &deviceid)) - { - XIWarpPointer (FRAME_X_DISPLAY (f), deviceid, None, - FRAME_DISPLAY_INFO (f)->root_window, - 0, 0, 0, 0, xval, yval); - } - XUngrabServer (FRAME_X_DISPLAY (f)); + x_catch_errors_for_lisp (FRAME_DISPLAY_INFO (f)); + XIWarpPointer (FRAME_X_DISPLAY (f), deviceid, None, + FRAME_DISPLAY_INFO (f)->root_window, + 0, 0, 0, 0, xval, yval); + x_uncatch_errors_for_lisp (FRAME_DISPLAY_INFO (f)); } else #endif @@ -6899,7 +6939,7 @@ that mouse buttons are being held down, such as immediately after a int ntargets = 0, nnames = 0; char *target_names[2048]; Atom *target_atoms; - Lisp_Object lval, original, tem, t1, t2; + Lisp_Object lval, original, targets_arg, tem, t1, t2; Atom xaction; Atom action_list[2048]; char *name_list[2048]; @@ -6908,11 +6948,11 @@ that mouse buttons are being held down, such as immediately after a CHECK_LIST (targets); original = targets; + targets_arg = targets; - for (; CONSP (targets); targets = XCDR (targets)) + FOR_EACH_TAIL (targets) { CHECK_STRING (XCAR (targets)); - maybe_quit (); if (ntargets < 2048) { @@ -6936,15 +6976,19 @@ that mouse buttons are being held down, such as immediately after a xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionPrivate; else if (EQ (action, QXdndActionAsk)) xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk; + else if (SYMBOLP (action)) + /* This is to accommodate non-standard DND protocols such as XDS + that are explicitly implemented by Emacs, and is not documented + for that reason. */ + xaction = symbol_to_x_atom (FRAME_DISPLAY_INFO (f), action); else if (CONSP (action)) { xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk; original = action; CHECK_LIST (action); - for (; CONSP (action); action = XCDR (action)) + FOR_EACH_TAIL (action) { - maybe_quit (); tem = XCAR (action); CHECK_CONS (tem); t1 = XCAR (tem); @@ -6995,7 +7039,7 @@ that mouse buttons are being held down, such as immediately after a xaction, return_frame, action_list, (const char **) &name_list, nnames, !NILP (allow_current_frame), target_atoms, - ntargets, original, !NILP (follow_tooltip)); + ntargets, targets_arg, !NILP (follow_tooltip)); SAFE_FREE (); return lval; @@ -7335,18 +7379,23 @@ If VALUE is a string and FORMAT is 32, then the format of VALUE is system-specific. VALUE must contain unsigned integer data in native endian-ness in multiples of the size of the C type 'long': the low 32 bits of each such number are used as the value of each element of the -property. */) +property. + +Wait for the request to complete and signal any error, unless +`x-fast-protocol-requests' is non-nil, in which case errors will be +silently ignored. */) (Lisp_Object prop, Lisp_Object value, Lisp_Object frame, Lisp_Object type, Lisp_Object format, Lisp_Object outer_p, Lisp_Object window_id) { - struct frame *f = decode_window_system_frame (frame); + struct frame *f; Atom prop_atom; Atom target_type = XA_STRING; int element_format = 8; unsigned char *data; int nelements; Window target_window; + struct x_display_info *dpyinfo; #ifdef USE_XCB bool intern_prop; bool intern_target; @@ -7357,6 +7406,9 @@ property. */) bool rc; #endif + f = decode_window_system_frame (frame); + dpyinfo = FRAME_DISPLAY_INFO (f); + CHECK_STRING (prop); if (! NILP (format)) @@ -7408,7 +7460,7 @@ property. */) { CONS_TO_INTEGER (window_id, Window, target_window); if (! target_window) - target_window = FRAME_DISPLAY_INFO (f)->root_window; + target_window = dpyinfo->root_window; } else { @@ -7420,47 +7472,47 @@ property. */) block_input (); #ifndef USE_XCB - prop_atom = x_intern_cached_atom (FRAME_DISPLAY_INFO (f), - SSDATA (prop), false); + prop_atom = x_intern_cached_atom (dpyinfo, SSDATA (prop), + false); if (! NILP (type)) { CHECK_STRING (type); - target_type = x_intern_cached_atom (FRAME_DISPLAY_INFO (f), - SSDATA (type), false); + target_type = x_intern_cached_atom (dpyinfo, SSDATA (type), + false); } #else rc = true; intern_target = true; intern_prop = true; - prop_atom = x_intern_cached_atom (FRAME_DISPLAY_INFO (f), - SSDATA (prop), true); + prop_atom = x_intern_cached_atom (dpyinfo, SSDATA (prop), + true); if (prop_atom != None) intern_prop = false; else prop_atom_cookie - = xcb_intern_atom (FRAME_DISPLAY_INFO (f)->xcb_connection, + = xcb_intern_atom (dpyinfo->xcb_connection, 0, SBYTES (prop), SSDATA (prop)); if (!NILP (type)) { CHECK_STRING (type); - target_type = x_intern_cached_atom (FRAME_DISPLAY_INFO (f), - SSDATA (type), true); + target_type = x_intern_cached_atom (dpyinfo, SSDATA (type), + true); if (target_type) intern_target = false; else target_type_cookie - = xcb_intern_atom (FRAME_DISPLAY_INFO (f)->xcb_connection, + = xcb_intern_atom (dpyinfo->xcb_connection, 0, SBYTES (type), SSDATA (type)); } if (intern_prop) { - reply = xcb_intern_atom_reply (FRAME_DISPLAY_INFO (f)->xcb_connection, + reply = xcb_intern_atom_reply (dpyinfo->xcb_connection, prop_atom_cookie, &generic_error); if (reply) @@ -7477,7 +7529,7 @@ property. */) if (!NILP (type) && intern_target) { - reply = xcb_intern_atom_reply (FRAME_DISPLAY_INFO (f)->xcb_connection, + reply = xcb_intern_atom_reply (dpyinfo->xcb_connection, target_type_cookie, &generic_error); if (reply) @@ -7496,15 +7548,18 @@ property. */) error ("Failed to intern type or property atom"); #endif - x_catch_errors (FRAME_X_DISPLAY (f)); - XChangeProperty (FRAME_X_DISPLAY (f), target_window, - prop_atom, target_type, element_format, PropModeReplace, - data, nelements); + x_catch_errors_for_lisp (dpyinfo); - if (CONSP (value)) xfree (data); - x_check_errors (FRAME_X_DISPLAY (f), - "Couldn't change window property: %s"); - x_uncatch_errors_after_check (); + XChangeProperty (dpyinfo->display, target_window, + prop_atom, target_type, element_format, + PropModeReplace, data, nelements); + + if (CONSP (value)) + xfree (data); + + x_check_errors_for_lisp (dpyinfo, + "Couldn't change window property: %s"); + x_uncatch_errors_for_lisp (dpyinfo); unblock_input (); return value; @@ -7521,7 +7576,11 @@ If WINDOW-ID is non-nil, remove property from that window instead across X displays or screens on the same display, so FRAME provides context for the window ID. -Value is PROP. */) +Value is PROP. + +Wait for the request to complete and signal any error, unless +`x-fast-protocol-requests' is non-nil, in which case errors will be +silently ignored. */) (Lisp_Object prop, Lisp_Object frame, Lisp_Object window_id) { struct frame *f = decode_window_system_frame (frame); @@ -7541,11 +7600,11 @@ Value is PROP. */) prop_atom = x_intern_cached_atom (FRAME_DISPLAY_INFO (f), SSDATA (prop), false); - x_catch_errors (FRAME_X_DISPLAY (f)); + x_catch_errors_for_lisp (FRAME_DISPLAY_INFO (f)); XDeleteProperty (FRAME_X_DISPLAY (f), target_window, prop_atom); - x_check_errors (FRAME_X_DISPLAY (f), - "Couldn't delete window property: %s"); - x_uncatch_errors_after_check (); + x_check_errors_for_lisp (FRAME_DISPLAY_INFO (f), + "Couldn't delete window property: %s"); + x_uncatch_errors_for_lisp (FRAME_DISPLAY_INFO (f)); unblock_input (); return prop; @@ -7789,6 +7848,92 @@ Otherwise, the return value is a vector with the following fields: return prop_attr; } + +/*********************************************************************** + Coordinate management + ***********************************************************************/ + +DEFUN ("x-translate-coordinates", Fx_translate_coordinates, + Sx_translate_coordinates, + 1, 5, 0, doc: /* Translate coordinates from FRAME. +Translate the given coordinates SOURCE-X and SOURCE-Y from +SOURCE-WINDOW's coordinate space to that of DEST-WINDOW, on FRAME. + +If SOURCE-X and SOURCE-Y are nil, use 0 instead. + +FRAME can either be a terminal or a frame. If nil, it defaults to the +selected frame. SOURCE-WINDOW must be an X window ID, 0 (which means +to use the root window), or nil, which means to use FRAME's inner +window. DEST-WINDOW must be another X window ID, or nil (which means +to use the root window). + +Return a list of (X Y CHILD) if the given coordinates are on the same +screen, or nil otherwise, where X and Y are the coordinates in +DEST-WINDOW's coordinate space, and CHILD is the window ID of any +mapped child in DEST-WINDOW at those coordinates, or nil if there is +no such window. */) + (Lisp_Object frame, Lisp_Object source_window, + Lisp_Object dest_window, Lisp_Object source_x, + Lisp_Object source_y) +{ + struct x_display_info *dpyinfo; + struct frame *source_frame; + int dest_x, dest_y; + Window child_return, src, dest; + Bool rc; + + dpyinfo = check_x_display_info (frame); + dest_x = 0; + dest_y = 0; + + if (!NILP (source_x)) + { + CHECK_FIXNUM (source_x); + dest_x = XFIXNUM (source_x); + } + + if (!NILP (source_y)) + { + CHECK_FIXNUM (source_y); + dest_y = XFIXNUM (source_y); + } + + if (!NILP (source_window)) + CONS_TO_INTEGER (source_window, Window, src); + else + { + source_frame = decode_window_system_frame (frame); + src = FRAME_X_WINDOW (source_frame); + } + + if (!src) + src = dpyinfo->root_window; + + if (!NILP (dest_window)) + CONS_TO_INTEGER (dest_window, Window, dest); + else + dest = dpyinfo->root_window; + + block_input (); + x_catch_errors (dpyinfo->display); + rc = XTranslateCoordinates (dpyinfo->display, src, dest, + dest_x, dest_y, &dest_x, &dest_y, + &child_return); + x_check_errors (dpyinfo->display, + "Couldn't translate coordinates: %s"); + x_uncatch_errors_after_check (); + unblock_input (); + + if (!rc) + return Qnil; + + return list3 (make_int (dest_x), + make_int (dest_y), + (child_return != None + ? make_uint (child_return) + : Qnil)); +} + /*********************************************************************** Tool tips ***********************************************************************/ @@ -8235,8 +8380,8 @@ compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx, Lisp_Object frame, attributes, monitor, geometry; block_input (); - XQueryPointer (FRAME_X_DISPLAY (f), FRAME_DISPLAY_INFO (f)->root_window, - &root, &child, root_x, root_y, &win_x, &win_y, &pmask); + x_query_pointer (FRAME_X_DISPLAY (f), FRAME_DISPLAY_INFO (f)->root_window, + &root, &child, root_x, root_y, &win_x, &win_y, &pmask); unblock_input (); XSETFRAME (frame, f); @@ -8493,6 +8638,10 @@ Text larger than the specified size is clipped. */) Window child; XWindowAttributes child_attrs; int dest_x_return, dest_y_return; + bool displayed; +#ifdef ENABLE_CHECKING + struct glyph_row *row, *end; +#endif AUTO_STRING (tip, " *tip*"); specbind (Qinhibit_redisplay, Qt); @@ -8547,7 +8696,8 @@ Text larger than the specified size is clipped. */) if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame))) { if (FRAME_VISIBLE_P (XFRAME (tip_frame)) - && BASE_EQ (frame, tip_last_frame) + && (FRAME_X_DISPLAY (XFRAME (frame)) + == FRAME_X_DISPLAY (XFRAME (tip_last_frame))) && !NILP (Fequal_including_properties (tip_last_string, string)) && !NILP (Fequal (tip_last_parms, parms))) { @@ -8705,7 +8855,26 @@ Text larger than the specified size is clipped. */) clear_glyph_matrix (w->desired_matrix); clear_glyph_matrix (w->current_matrix); SET_TEXT_POS (pos, BEGV, BEGV_BYTE); - try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + row = w->desired_matrix->rows; + end = w->desired_matrix->rows + w->desired_matrix->nrows; + + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + + eassert (row < end && row->ends_at_zv_p); +#endif + } + /* Calculate size of tooltip window. */ size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, make_fixnum (w->pixel_height), Qnil, @@ -8806,9 +8975,8 @@ DEFUN ("x-double-buffered-p", Fx_double_buffered_p, Sx_double_buffered_p, doc: /* Return t if FRAME is being double buffered. */) (Lisp_Object frame) { - struct frame *f = decode_live_frame (frame); - #ifdef HAVE_XDBE + struct frame *f = decode_live_frame (frame); return FRAME_X_DOUBLE_BUFFERED_P (f) ? Qt : Qnil; #else return Qnil; @@ -9297,6 +9465,24 @@ present and mapped to the usual X keysyms. */) #endif } +DEFUN ("x-get-modifier-masks", Fx_get_modifier_masks, Sx_get_modifier_masks, + 0, 1, 0, + doc: /* Return the X modifier masks corresponding to keyboard modifiers. +The optional second argument TERMINAL specifies which display to fetch +modifier masks from. TERMINAL should be a terminal object, a frame or +a display name (a string). If TERMINAL is omitted or nil, that stands +for the selected frame's display. + +Return a list of (HYPER SUPER ALT SHIFT-LOCK META), each element being +a number describing the modifier mask for the corresponding Emacs +modifier. */) + (Lisp_Object terminal) +{ + struct x_display_info *dpyinfo; + + dpyinfo = check_x_display_info (terminal); + return x_get_keyboard_modifiers (dpyinfo); +} /*********************************************************************** @@ -9472,6 +9658,28 @@ DEFUN ("x-gtk-debug", Fx_gtk_debug, Sx_gtk_debug, 1, 1, 0, #endif /* HAVE_GTK3 */ #endif /* USE_GTK */ +DEFUN ("x-display-set-last-user-time", Fx_display_last_user_time, + Sx_display_set_last_user_time, 1, 2, 0, + doc: /* Set the last user time of TERMINAL to TIME-OBJECT. +TIME-OBJECT is the X server time, in milliseconds, of the last user +interaction. This is the timestamp that `x-get-selection-internal' +will use by default to fetch selection data. +The optional second argument TERMINAL specifies which display to act +on. TERMINAL should be a terminal object, a frame or a display name +(a string). If TERMINAL is omitted or nil, that stands for the +selected frame's display. */) + (Lisp_Object time_object, Lisp_Object terminal) +{ + struct x_display_info *dpyinfo; + Time time; + + dpyinfo = check_x_display_info (terminal); + CONS_TO_INTEGER (time_object, Time, time); + + x_set_last_user_time_from_lisp (dpyinfo, time); + return Qnil; +} + DEFUN ("x-internal-focus-input-context", Fx_internal_focus_input_context, Sx_internal_focus_input_context, 1, 1, 0, doc: /* Focus and set the client window of all focused frames' GTK input context. @@ -9575,6 +9783,7 @@ frame_parm_handler x_frame_parm_handlers[] = x_set_override_redirect, gui_set_no_special_glyphs, x_set_alpha_background, + x_set_use_frame_synchronization, x_set_shaded, }; @@ -9780,7 +9989,7 @@ or when you set the mouse color. */); DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, doc: /* Maximum size for tooltips. Value is a pair (COLUMNS . ROWS). Text larger than this is clipped. */); - Vx_max_tooltip_size = Fcons (make_fixnum (80), make_fixnum (40)); + Vx_max_tooltip_size = Qnil; DEFVAR_LISP ("x-no-window-manager", Vx_no_window_manager, doc: /* Non-nil if no X window manager is in use. @@ -9937,6 +10146,10 @@ eliminated in future versions of Emacs. */); defsubr (&Sx_hide_tip); defsubr (&Sx_double_buffered_p); defsubr (&Sx_begin_drag); + defsubr (&Sx_display_set_last_user_time); + defsubr (&Sx_translate_coordinates); + defsubr (&Sx_get_modifier_masks); + tip_timer = Qnil; staticpro (&tip_timer); tip_frame = Qnil; diff --git a/src/xmenu.c b/src/xmenu.c index 7134bf22c83..1452b3c6d12 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -232,6 +232,7 @@ static void x_menu_translate_generic_event (XEvent *event) { struct x_display_info *dpyinfo; + struct xi_device_t *device; XEvent copy; XIDeviceEvent *xev; @@ -241,45 +242,52 @@ x_menu_translate_generic_event (XEvent *event) { eassert (!event->xcookie.data); - if (XGetEventData (dpyinfo->display, &event->xcookie)) + switch (event->xcookie.evtype) { - switch (event->xcookie.evtype) - { - case XI_ButtonPress: - case XI_ButtonRelease: - xev = (XIDeviceEvent *) event->xcookie.data; - copy.xbutton.type = (event->xcookie.evtype == XI_ButtonPress - ? ButtonPress : ButtonRelease); - copy.xbutton.serial = xev->serial; - copy.xbutton.send_event = xev->send_event; - copy.xbutton.display = dpyinfo->display; - copy.xbutton.window = xev->event; - copy.xbutton.root = xev->root; - copy.xbutton.subwindow = xev->child; - copy.xbutton.time = xev->time; - copy.xbutton.x = lrint (xev->event_x); - copy.xbutton.y = lrint (xev->event_y); - copy.xbutton.x_root = lrint (xev->root_x); - copy.xbutton.y_root = lrint (xev->root_y); - copy.xbutton.state = xev->mods.effective; - copy.xbutton.button = xev->detail; - copy.xbutton.same_screen = True; - - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy.xbutton.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy.xbutton.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy.xbutton.state |= Button3Mask; - } + case XI_ButtonPress: + case XI_ButtonRelease: - XPutBackEvent (dpyinfo->display, ©); + if (!XGetEventData (dpyinfo->display, &event->xcookie)) + break; - break; - } + xev = (XIDeviceEvent *) event->xcookie.data; + copy.xbutton.type = (event->xcookie.evtype == XI_ButtonPress + ? ButtonPress : ButtonRelease); + copy.xbutton.serial = xev->serial; + copy.xbutton.send_event = xev->send_event; + copy.xbutton.display = dpyinfo->display; + copy.xbutton.window = xev->event; + copy.xbutton.root = xev->root; + copy.xbutton.subwindow = xev->child; + copy.xbutton.time = xev->time; + copy.xbutton.x = lrint (xev->event_x); + copy.xbutton.y = lrint (xev->event_y); + copy.xbutton.x_root = lrint (xev->root_x); + copy.xbutton.y_root = lrint (xev->root_y); + copy.xbutton.state = xi_convert_event_state (xev); + copy.xbutton.button = xev->detail; + copy.xbutton.same_screen = True; + + device = xi_device_from_id (dpyinfo, xev->deviceid); + + /* I don't know the repercussions of changing + device->grab on XI_ButtonPress events, so be safe and + only do what is necessary to prevent the grab from + being left invalid as XMenuActivate swallows + events. */ + if (device && xev->evtype == XI_ButtonRelease) + device->grab &= ~(1 << xev->detail); + + XPutBackEvent (dpyinfo->display, ©); XFreeEventData (dpyinfo->display, &event->xcookie); + + break; + + case XI_HierarchyChanged: + case XI_DeviceChanged: + /* These events must always be handled. */ + x_dispatch_event (event, dpyinfo->display); + break; } } } @@ -397,7 +405,7 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, copy.xbutton.y = lrint (xev->event_y); copy.xbutton.x_root = lrint (xev->root_x); copy.xbutton.y_root = lrint (xev->root_y); - copy.xbutton.state = xev->mods.effective; + copy.xbutton.state = xi_convert_event_state (xev); copy.xbutton.button = xev->detail; copy.xbutton.same_screen = True; @@ -412,16 +420,6 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, copy.xbutton.state = 0; #endif - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy.xbutton.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy.xbutton.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy.xbutton.state |= Button3Mask; - } - break; } case XI_KeyPress: @@ -442,7 +440,7 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, copy.xkey.y = lrint (xev->event_y); copy.xkey.x_root = lrint (xev->root_x); copy.xkey.y_root = lrint (xev->root_y); - copy.xkey.state = xev->mods.effective; + copy.xkey.state = xi_convert_event_state (xev); copy.xkey.keycode = xev->detail; copy.xkey.same_screen = True; @@ -996,8 +994,6 @@ set_frame_menubar (struct frame *f, bool deep_p) /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); @@ -2529,6 +2525,10 @@ pop_down_menu (void *arg) struct pop_down_menu *data = arg; struct frame *f = data->frame; XMenu *menu = data->menu; +#ifdef HAVE_XINPUT2 + int i; + struct xi_device_t *device; +#endif block_input (); #ifndef MSDOS @@ -2548,6 +2548,17 @@ pop_down_menu (void *arg) results, and it is a pain to ask which are actually held now. */ FRAME_DISPLAY_INFO (f)->grabbed = 0; +#ifdef HAVE_XINPUT2 + /* Likewise for XI grabs when the mouse is released on top of the + menu itself. */ + + for (i = 0; i < FRAME_DISPLAY_INFO (f)->num_devices; ++i) + { + device = &FRAME_DISPLAY_INFO (f)->devices[i]; + device->grab = 0; + } +#endif + #endif /* HAVE_X_WINDOWS */ unblock_input (); @@ -2558,6 +2569,9 @@ Lisp_Object x_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title, const char **error_name) { +#ifdef HAVE_X_WINDOWS + Window dummy_window; +#endif Window root; XMenu *menu; int pane, selidx, lpane, status; @@ -2606,20 +2620,22 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, inhibit_garbage_collection (); #ifdef HAVE_X_WINDOWS - { - /* Adjust coordinates to relative to the outer (window manager) window. */ - int left_off, top_off; + XTranslateCoordinates (FRAME_X_DISPLAY (f), - x_real_pos_and_offsets (f, &left_off, NULL, &top_off, NULL, - NULL, NULL, NULL, NULL, NULL); + /* From-window, to-window. */ + FRAME_X_WINDOW (f), + FRAME_DISPLAY_INFO (f)->root_window, - x += left_off; - y += top_off; - } -#endif /* HAVE_X_WINDOWS */ + /* From-position, to-position. */ + x, y, &x, &y, + /* Child of win. */ + &dummy_window); +#else + /* MSDOS without X support. */ x += f->left_pos; y += f->top_pos; +#endif /* Create all the necessary panes and their items. */ maxwidth = maxlines = lines = i = 0; @@ -2774,6 +2790,9 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, DEFER_SELECTIONS; XMenuActivateSetWaitFunction (x_menu_wait_for_event, FRAME_X_DISPLAY (f)); + /* When the input extension is in use, the owner_events grab will + report extension events on frames, which the XMenu library does + not normally understand. */ #ifdef HAVE_XINPUT2 XMenuActivateSetTranslateFunction (x_menu_translate_generic_event); #endif diff --git a/src/xml.c b/src/xml.c index 522efd224c6..2cccff12331 100644 --- a/src/xml.c +++ b/src/xml.c @@ -186,6 +186,12 @@ parse_region (Lisp_Object start, Lisp_Object end, Lisp_Object base_url, xmlCheckVersion (LIBXML_VERSION); + if (NILP (start)) + start = Fpoint_min (); + + if (NILP (end)) + end = Fpoint_max (); + validate_region (&start, &end); istart = XFIXNUM (start); @@ -269,8 +275,11 @@ xml_cleanup_parser (void) DEFUN ("libxml-parse-html-region", Flibxml_parse_html_region, Slibxml_parse_html_region, - 2, 4, 0, + 0, 4, 0, doc: /* Parse the region as an HTML document and return the parse tree. +If START is nil, it defaults to `point-min'. If END is nil, it +defaults to `point-max'. + If BASE-URL is non-nil, it is used to expand relative URLs. If you want comments to be stripped, use the `xml-remove-comments' @@ -284,8 +293,11 @@ function to strip comments before calling this function. */) DEFUN ("libxml-parse-xml-region", Flibxml_parse_xml_region, Slibxml_parse_xml_region, - 2, 4, 0, + 0, 4, 0, doc: /* Parse the region as an XML document and return the parse tree. +If START is nil, it defaults to `point-min'. If END is nil, it +defaults to `point-max'. + If BASE-URL is non-nil, it is used to expand relative URLs. If you want comments to be stripped, use the `xml-remove-comments' diff --git a/src/xselect.c b/src/xselect.c index 96c1e9830fb..bab0400540e 100644 --- a/src/xselect.c +++ b/src/xselect.c @@ -36,17 +36,16 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "termhooks.h" #include "keyboard.h" #include "pdumper.h" +#include "atimer.h" #include <X11/Xproto.h> -static Time pending_dnd_time; - struct prop_location; struct selection_data; static void x_decline_selection_request (struct selection_input_event *); static bool x_convert_selection (Lisp_Object, Lisp_Object, Atom, bool, - struct x_display_info *); + struct x_display_info *, bool); static bool waiting_for_other_props_on_window (Display *, Window); static struct prop_location *expect_property_change (Display *, Window, Atom, int); @@ -120,7 +119,7 @@ selection_quantum (Display *display) /* This converts a Lisp symbol to a server Atom, avoiding a server roundtrip whenever possible. */ -static Atom +Atom symbol_to_x_atom (struct x_display_info *dpyinfo, Lisp_Object sym) { Atom val; @@ -164,6 +163,12 @@ symbol_to_x_atom (struct x_display_info *dpyinfo, Lisp_Object sym) return dpyinfo->Xatom_XmTRANSFER_SUCCESS; if (EQ (sym, QXmTRANSFER_FAILURE)) return dpyinfo->Xatom_XmTRANSFER_FAILURE; + if (EQ (sym, QXdndDirectSave0)) + return dpyinfo->Xatom_XdndDirectSave0; + if (EQ (sym, Qtext_plain)) + return dpyinfo->Xatom_text_plain; + if (EQ (sym, QXdndActionDirectSave)) + return dpyinfo->Xatom_XdndActionDirectSave; if (!SYMBOLP (sym)) emacs_abort (); @@ -232,6 +237,12 @@ x_atom_to_symbol (struct x_display_info *dpyinfo, Atom atom) return QXmTRANSFER_SUCCESS; if (atom == dpyinfo->Xatom_XmTRANSFER_FAILURE) return QXmTRANSFER_FAILURE; + if (atom == dpyinfo->Xatom_XdndDirectSave0) + return QXdndDirectSave0; + if (atom == dpyinfo->Xatom_text_plain) + return Qtext_plain; + if (atom == dpyinfo->Xatom_XdndActionDirectSave) + return QXdndActionDirectSave; x_catch_errors (dpyinfo->display); str = x_get_atom_name (dpyinfo, atom, NULL); @@ -249,20 +260,26 @@ x_atom_to_symbol (struct x_display_info *dpyinfo, Atom atom) /* Do protocol to assert ourself as a selection owner. FRAME shall be the owner; it must be a valid X frame. + TIMESTAMP should be the timestamp where selection ownership will be + assumed. + DND_DATA is the local value that will be used for selection requests + with `dpyinfo->pending_dnd_time'. Update the Vselection_alist so that we can reply to later requests for our selection. */ void x_own_selection (Lisp_Object selection_name, Lisp_Object selection_value, - Lisp_Object frame) + Lisp_Object frame, Lisp_Object dnd_data, Time timestamp) { struct frame *f = XFRAME (frame); Window selecting_window = FRAME_X_WINDOW (f); struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); Display *display = dpyinfo->display; - Time timestamp = dpyinfo->last_user_time; Atom selection_atom = symbol_to_x_atom (dpyinfo, selection_name); + if (!timestamp) + timestamp = dpyinfo->last_user_time; + block_input (); x_catch_errors (display); XSetSelectionOwner (display, selection_atom, selecting_window, timestamp); @@ -275,8 +292,9 @@ x_own_selection (Lisp_Object selection_name, Lisp_Object selection_value, Lisp_Object selection_data; Lisp_Object prev_value; - selection_data = list4 (selection_name, selection_value, - INT_TO_INTEGER (timestamp), frame); + selection_data = list5 (selection_name, selection_value, + INT_TO_INTEGER (timestamp), frame, + dnd_data); prev_value = LOCAL_SELECTION (selection_name, dpyinfo); tset_selection_alist @@ -306,18 +324,33 @@ x_own_selection (Lisp_Object selection_name, Lisp_Object selection_value, This function is used both for remote requests (LOCAL_REQUEST is zero) and for local x-get-selection-internal (LOCAL_REQUEST is nonzero). + If LOCAL_VALUE is non-nil, use it as the local copy. Also allow + quitting in that case, and let DPYINFO be NULL. + + If NEED_ALTERNATE is true, use the drag-and-drop local value + instead. + This calls random Lisp code, and may signal or gc. */ static Lisp_Object x_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type, - bool local_request, struct x_display_info *dpyinfo) + bool local_request, struct x_display_info *dpyinfo, + Lisp_Object local_value, bool need_alternate) { - Lisp_Object local_value, tem; + Lisp_Object tem; Lisp_Object handler_fn, value, check; + bool may_quit; + specpdl_ref count; - local_value = LOCAL_SELECTION (selection_symbol, dpyinfo); + may_quit = false; - if (NILP (local_value)) return Qnil; + if (NILP (local_value)) + local_value = LOCAL_SELECTION (selection_symbol, dpyinfo); + else + may_quit = true; + + if (NILP (local_value)) + return Qnil; /* TIMESTAMP is a special case. */ if (EQ (target_type, QTIMESTAMP)) @@ -330,8 +363,10 @@ x_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type, /* Don't allow a quit within the converter. When the user types C-g, he would be surprised if by luck it came during a converter. */ - specpdl_ref count = SPECPDL_INDEX (); - specbind (Qinhibit_quit, Qt); + count = SPECPDL_INDEX (); + + if (!may_quit) + specbind (Qinhibit_quit, Qt); CHECK_SYMBOL (target_type); handler_fn = Fcdr (Fassq (target_type, Vselection_converter_alist)); @@ -339,7 +374,10 @@ x_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type, if (CONSP (handler_fn)) handler_fn = XCDR (handler_fn); - tem = XCAR (XCDR (local_value)); + if (!need_alternate) + tem = XCAR (XCDR (local_value)); + else + tem = XCAR (XCDR (XCDR (XCDR (XCDR (local_value))))); if (STRINGP (tem)) { @@ -399,10 +437,19 @@ static void x_decline_selection_request (struct selection_input_event *event) { XEvent reply_base; - XSelectionEvent *reply = &(reply_base.xselection); + XSelectionEvent *reply; + Display *dpy; + struct x_display_info *dpyinfo; + + reply = &(reply_base.xselection); + dpy = SELECTION_EVENT_DISPLAY (event); + dpyinfo = x_display_info_for_display (dpy); + + if (!dpyinfo) + return; reply->type = SelectionNotify; - reply->display = SELECTION_EVENT_DISPLAY (event); + reply->display = dpy; reply->requestor = SELECTION_EVENT_REQUESTOR (event); reply->selection = SELECTION_EVENT_SELECTION (event); reply->time = SELECTION_EVENT_TIME (event); @@ -412,10 +459,12 @@ x_decline_selection_request (struct selection_input_event *event) /* The reason for the error may be that the receiver has died in the meantime. Handle that case. */ block_input (); - x_catch_errors (reply->display); - XSendEvent (reply->display, reply->requestor, False, 0, &reply_base); - XFlush (reply->display); - x_uncatch_errors (); + x_ignore_errors_for_next_request (dpyinfo); + XSendEvent (dpyinfo->display, reply->requestor, + False, 0, &reply_base); + x_stop_ignoring_errors (dpyinfo); + + XFlush (dpyinfo->display); unblock_input (); } @@ -773,18 +822,31 @@ x_handle_selection_request (struct selection_input_event *event) Lisp_Object local_selection_data; bool success = false; specpdl_ref count = SPECPDL_INDEX (); - bool pushed; + bool pushed, use_alternate; + Lisp_Object alias, tem; + + alias = Vx_selection_alias_alist; + + FOR_EACH_TAIL_SAFE (alias) + { + tem = Qnil; + + if (CONSP (alias)) + tem = XCAR (alias); + + if (CONSP (tem) + && EQ (XCAR (tem), selection_symbol) + && SYMBOLP (XCDR (tem))) + { + selection_symbol = XCDR (tem); + break; + } + } pushed = false; if (!dpyinfo) - goto DONE; - - /* This is how the XDND protocol recommends dropping text onto a - target that doesn't support XDND. */ - if (SELECTION_EVENT_TIME (event) == pending_dnd_time + 1 - || SELECTION_EVENT_TIME (event) == pending_dnd_time + 2) - selection_symbol = QXdndSelection; + goto REALLY_DONE; local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo); @@ -798,6 +860,17 @@ x_handle_selection_request (struct selection_input_event *event) && local_selection_time > SELECTION_EVENT_TIME (event)) goto DONE; + use_alternate = false; + + /* This is how the XDND protocol recommends dropping text onto a + target that doesn't support XDND. */ + if (dpyinfo->pending_dnd_time + && ((SELECTION_EVENT_TIME (event) + == dpyinfo->pending_dnd_time + 1) + || (SELECTION_EVENT_TIME (event) + == dpyinfo->pending_dnd_time + 2))) + use_alternate = true; + block_input (); pushed = true; x_push_current_selection_request (event, dpyinfo); @@ -838,7 +911,8 @@ x_handle_selection_request (struct selection_input_event *event) if (subproperty != None) subsuccess = x_convert_selection (selection_symbol, subtarget, - subproperty, true, dpyinfo); + subproperty, true, dpyinfo, + use_alternate); if (!subsuccess) ASET (multprop, 2*j+1, Qnil); } @@ -855,7 +929,8 @@ x_handle_selection_request (struct selection_input_event *event) property = SELECTION_EVENT_TARGET (event); success = x_convert_selection (selection_symbol, target_symbol, property, - false, dpyinfo); + false, dpyinfo, + use_alternate); } DONE: @@ -874,6 +949,9 @@ x_handle_selection_request (struct selection_input_event *event) CALLN (Frun_hook_with_args, Qx_sent_selection_functions, selection_symbol, target_symbol, success ? Qt : Qnil); + /* Used to punt when dpyinfo is NULL. */ + REALLY_DONE: + unbind_to (count, Qnil); } @@ -887,7 +965,8 @@ x_handle_selection_request (struct selection_input_event *event) static bool x_convert_selection (Lisp_Object selection_symbol, Lisp_Object target_symbol, Atom property, - bool for_multiple, struct x_display_info *dpyinfo) + bool for_multiple, struct x_display_info *dpyinfo, + bool use_alternate) { Lisp_Object lisp_selection; struct selection_data *cs; @@ -895,7 +974,7 @@ x_convert_selection (Lisp_Object selection_symbol, lisp_selection = x_get_local_selection (selection_symbol, target_symbol, - false, dpyinfo); + false, dpyinfo, Qnil, use_alternate); frame = selection_request_stack; @@ -1005,6 +1084,26 @@ x_handle_selection_event (struct selection_input_event *event) x_handle_selection_request (event); } +static bool +x_should_preserve_selection (Lisp_Object selection) +{ + Lisp_Object tem; + + tem = Vx_auto_preserve_selections; + + if (CONSP (Vx_auto_preserve_selections)) + { + FOR_EACH_TAIL_SAFE (tem) + { + if (EQ (XCAR (tem), selection)) + return true; + } + + return false; + } + + return !NILP (tem); +} /* Clear all selections that were made from frame F. We do this when about to delete a frame. */ @@ -1012,20 +1111,25 @@ x_handle_selection_event (struct selection_input_event *event) void x_clear_frame_selections (struct frame *f) { - Lisp_Object frame; - Lisp_Object rest; + Lisp_Object frame, rest, lost, selection; struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); struct terminal *t = dpyinfo->terminal; XSETFRAME (frame, f); + lost = Qnil; /* Delete elements from the beginning of Vselection_alist. */ while (CONSP (t->Vselection_alist) && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (t->Vselection_alist))))))) { - /* Run the `x-lost-selection-functions' abnormal hook. */ - CALLN (Frun_hook_with_args, Qx_lost_selection_functions, - Fcar (Fcar (t->Vselection_alist))); + selection = Fcar (Fcar (t->Vselection_alist)); + + if (!x_should_preserve_selection (selection)) + /* Run the `x-lost-selection-functions' abnormal hook. */ + CALLN (Frun_hook_with_args, Qx_lost_selection_functions, + selection); + else + lost = Fcons (Fcar (t->Vselection_alist), lost); tset_selection_alist (t, XCDR (t->Vselection_alist)); } @@ -1035,11 +1139,20 @@ x_clear_frame_selections (struct frame *f) if (CONSP (XCDR (rest)) && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (XCDR (rest)))))))) { - CALLN (Frun_hook_with_args, Qx_lost_selection_functions, - XCAR (XCAR (XCDR (rest)))); + selection = XCAR (XCAR (XCDR (rest))); + + if (!x_should_preserve_selection (selection)) + CALLN (Frun_hook_with_args, Qx_lost_selection_functions, + selection); + else + lost = Fcons (XCAR (XCDR (rest)), lost); + XSETCDR (rest, XCDR (XCDR (rest))); break; } + + if (!NILP (lost)) + x_preserve_selections (dpyinfo, lost, frame); } /* True if any properties for DISPLAY and WINDOW @@ -1179,6 +1292,20 @@ x_handle_property_notify (const XPropertyEvent *event) } } +static void +x_display_selection_waiting_message (struct atimer *timer) +{ + Lisp_Object val; + + val = build_string ("Waiting for reply from selection owner..."); + message3_nolog (val); +} + +static void +x_cancel_atimer (void *atimer) +{ + cancel_atimer (atimer); +} /* Variables for communication with x_handle_selection_notify. */ @@ -1204,9 +1331,14 @@ x_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type, Atom type_atom = (CONSP (target_type) ? symbol_to_x_atom (dpyinfo, XCAR (target_type)) : symbol_to_x_atom (dpyinfo, target_type)); + struct atimer *delayed_message; + struct timespec message_interval; + specpdl_ref count; + + count = SPECPDL_INDEX (); if (!FRAME_LIVE_P (f)) - return Qnil; + return unbind_to (count, Qnil); if (! NILP (time_stamp)) CONS_TO_INTEGER (time_stamp, Time, requestor_time); @@ -1238,11 +1370,23 @@ x_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type, unblock_input (); + message_interval = make_timespec (1, 0); + delayed_message = start_atimer (ATIMER_RELATIVE, message_interval, + x_display_selection_waiting_message, + NULL); + record_unwind_protect_ptr (x_cancel_atimer, delayed_message); + /* This allows quits. Also, don't wait forever. */ intmax_t timeout = max (0, x_selection_timeout); intmax_t secs = timeout / 1000; int nsecs = (timeout % 1000) * 1000000; - TRACE1 (" Start waiting %"PRIdMAX" secs for SelectionNotify", secs); + TRACE1 (" Start waiting %"PRIdMAX" secs for SelectionNotify.", secs); + + if (input_blocked_p ()) + TRACE0 (" Input is blocked."); + else + TRACE1 (" Waiting for %d nsecs in addition.", nsecs); + /* This function can be called with input blocked inside Xt or GTK timeouts run inside popup menus, so use a function that works when input is blocked. Prefer wait_reading_process_output @@ -1263,13 +1407,16 @@ x_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type, if (NILP (XCAR (reading_selection_reply))) error ("Timed out waiting for reply from selection owner"); if (EQ (XCAR (reading_selection_reply), Qlambda)) - return Qnil; + return unbind_to (count, Qnil); /* Otherwise, the selection is waiting for us on the requested property. */ - return - x_get_window_property_as_lisp_data (dpyinfo, requestor_window, - target_property, target_type, - selection_atom, false); + return unbind_to (count, + x_get_window_property_as_lisp_data (dpyinfo, + requestor_window, + target_property, + target_type, + selection_atom, + false)); } /* Subroutines of x_get_window_property_as_lisp_data */ @@ -1607,8 +1754,7 @@ x_get_window_property_as_lisp_data (struct x_display_info *dpyinfo, ATOM 32 > 1 Vector of Symbols * 16 1 Integer * 16 > 1 Vector of Integers - * 32 1 if small enough: fixnum - otherwise: bignum + * 32 1 Integer * 32 > 1 Vector of the above When converting an object to C, it may be of the form (SYMBOL . <data>) @@ -1920,9 +2066,9 @@ clean_local_selection_data (Lisp_Object obj) && INTEGERP (XCAR (obj)) && FIXNUMP (XCDR (obj))) { - if (EQ (XCAR (obj), make_fixnum (0))) + if (BASE_EQ (XCAR (obj), make_fixnum (0))) return XCDR (obj); - if (EQ (XCAR (obj), make_fixnum (-1))) + if (BASE_EQ (XCAR (obj), make_fixnum (-1))) return make_fixnum (- XFIXNUM (XCDR (obj))); } if (VECTORP (obj)) @@ -2027,7 +2173,7 @@ On Nextstep, FRAME is unused. */) CHECK_SYMBOL (selection); if (NILP (value)) error ("VALUE may not be nil"); - x_own_selection (selection, value, frame); + x_own_selection (selection, value, frame, Qnil, 0); return value; } @@ -2055,17 +2201,29 @@ On Nextstep, TIME-STAMP and TERMINAL are unused. */) Lisp_Object time_stamp, Lisp_Object terminal) { Lisp_Object val = Qnil; + Lisp_Object maybe_alias; struct frame *f = frame_for_x_selection (terminal); CHECK_SYMBOL (selection_symbol); CHECK_SYMBOL (target_type); + if (EQ (target_type, QMULTIPLE)) error ("Retrieving MULTIPLE selections is currently unimplemented"); if (!f) error ("X selection unavailable for this frame"); + /* Quitting inside this function is okay, so we don't have to use + FOR_EACH_TAIL_SAFE. */ + maybe_alias = Fassq (selection_symbol, Vx_selection_alias_alist); + + if (!NILP (maybe_alias)) + { + selection_symbol = XCDR (maybe_alias); + CHECK_SYMBOL (selection_symbol); + } + val = x_get_local_selection (selection_symbol, target_type, true, - FRAME_DISPLAY_INFO (f)); + FRAME_DISPLAY_INFO (f), Qnil, false); if (NILP (val) && FRAME_LIVE_P (f)) { @@ -2207,6 +2365,49 @@ On Nextstep, TERMINAL is unused. */) return (owner ? Qt : Qnil); } +DEFUN ("x-get-local-selection", Fx_get_local_selection, Sx_get_local_selection, + 0, 2, 0, + doc: /* Run selection converters for VALUE, and return the result. +TARGET is the selection target that is used to find a suitable +converter. VALUE is a list of 4 values NAME, SELECTION-VALUE, +TIMESTAMP and FRAME. NAME is the name of the selection that will be +passed to selection converters, SELECTION-VALUE is the value of the +selection used by the converter, TIMESTAMP is not meaningful (but must +be a number that fits in an X timestamp), and FRAME is the frame +describing the terminal for which the selection converter will be +run. */) + (Lisp_Object value, Lisp_Object target) +{ + Time time; + Lisp_Object name, timestamp, frame, result; + + CHECK_SYMBOL (target); + + /* Check that VALUE has 4 elements, for x_get_local_selection. */ + Lisp_Object v = value; CHECK_CONS (v); + name = XCAR (v); v = XCDR (v); CHECK_CONS (v); + v = XCDR (v); CHECK_CONS (v); + timestamp = XCAR (v); v = XCDR (v); CHECK_CONS (v); + frame = XCAR (v); + + CHECK_SYMBOL (name); + CONS_TO_INTEGER (timestamp, Time, time); + check_window_system (decode_live_frame (frame)); + + result = x_get_local_selection (name, target, true, + NULL, value, false); + + if (CONSP (result) && SYMBOLP (XCAR (result))) + { + result = XCDR (result); + + if (CONSP (result) && NILP (XCDR (result))) + result = XCAR (result); + } + + return clean_local_selection_data (result); +} + /* Send clipboard manager a SAVE_TARGETS request with a UTF8_STRING property (https://www.freedesktop.org/wiki/ClipboardManager/). */ @@ -2609,7 +2810,11 @@ to send. If a value is a string, it is converted to an Atom and the value of the Atom is sent. If a value is a cons, it is converted to a 32 bit number with the high 16 bits from the car and the lower 16 bit from the cdr. If more values than fits into the event is given, the excessive values -are ignored. */) +are ignored. + +Wait for the event to be sent and signal any error, unless +`x-fast-protocol-requests' is non-nil, in which case errors will be +silently ignored. */) (Lisp_Object display, Lisp_Object dest, Lisp_Object from, Lisp_Object message_type, Lisp_Object format, Lisp_Object values) { @@ -2690,7 +2895,7 @@ x_send_client_event (Lisp_Object display, Lisp_Object dest, Lisp_Object from, the destination window. But if we are sending to the root window, there is no such client. Then we set the event mask to 0xffffff. The event then goes to clients selecting for events on the root window. */ - x_catch_errors (dpyinfo->display); + x_catch_errors_for_lisp (dpyinfo); { bool propagate = !to_root; long mask = to_root ? 0xffffff : 0; @@ -2698,7 +2903,8 @@ x_send_client_event (Lisp_Object display, Lisp_Object dest, Lisp_Object from, XSendEvent (dpyinfo->display, wdest, propagate, mask, &event); XFlush (dpyinfo->display); } - x_uncatch_errors (); + x_check_errors_for_lisp (dpyinfo, "Failed to send client event: %s"); + x_uncatch_errors_for_lisp (dpyinfo); unblock_input (); } @@ -2723,12 +2929,6 @@ x_timestamp_for_selection (struct x_display_info *dpyinfo, return value; } -void -x_set_pending_dnd_time (Time time) -{ - pending_dnd_time = time; -} - static void syms_of_xselect_for_pdumper (void); void @@ -2743,6 +2943,7 @@ syms_of_xselect (void) defsubr (&Sx_get_atom_name); defsubr (&Sx_send_client_message); defsubr (&Sx_register_dnd_atom); + defsubr (&Sx_get_local_selection); reading_selection_reply = Fcons (Qnil, Qnil); staticpro (&reading_selection_reply); @@ -2818,6 +3019,15 @@ If non-nil, selection converters for string types (`STRING', when Emacs itself is converting the selection. */); Vx_treat_local_requests_remotely = Qnil; + DEFVAR_LISP ("x-selection-alias-alist", Vx_selection_alias_alist, + doc: /* List of selections to alias to another. +It should be an alist of a selection name to another. When a +selection request arrives for the first selection, Emacs will respond +as if the request was meant for the other. + +Note that this does not affect setting or owning selections. */); + Vx_selection_alias_alist = Qnil; + /* QPRIMARY is defined in keyboard.c. */ DEFSYM (QSECONDARY, "SECONDARY"); DEFSYM (QSTRING, "STRING"); @@ -2839,6 +3049,9 @@ when Emacs itself is converting the selection. */); DEFSYM (QCLIPBOARD_MANAGER, "CLIPBOARD_MANAGER"); DEFSYM (QSAVE_TARGETS, "SAVE_TARGETS"); DEFSYM (QNULL, "NULL"); + DEFSYM (QXdndDirectSave0, "XdndDirectSave0"); + DEFSYM (QXdndActionDirectSave, "XdndActionDirectSave"); + DEFSYM (Qtext_plain, "text/plain"); DEFSYM (Qforeign_selection, "foreign-selection"); DEFSYM (Qx_lost_selection_functions, "x-lost-selection-functions"); DEFSYM (Qx_sent_selection_functions, "x-sent-selection-functions"); diff --git a/src/xsettings.c b/src/xsettings.c index c29a844e0a8..9c60ff825a4 100644 --- a/src/xsettings.c +++ b/src/xsettings.c @@ -964,9 +964,10 @@ read_and_apply_settings (Display_Info *dpyinfo, bool send_event_p) #endif #ifndef HAVE_PGTK -/* Check if EVENT for the display in DPYINFO is XSettings related. */ +/* Check if EVENT for the display in DPYINFO is XSettings related. + Return true if it is, after performing associated side effects. */ -void +bool xft_settings_event (Display_Info *dpyinfo, const XEvent *event) { bool check_window_p = false, apply_settings_p = false; @@ -1004,6 +1005,8 @@ xft_settings_event (Display_Info *dpyinfo, const XEvent *event) if (apply_settings_p) read_and_apply_settings (dpyinfo, true); + + return check_window_p || apply_settings_p; } #endif diff --git a/src/xsettings.h b/src/xsettings.h index 5e5df37062b..833c2b367dc 100644 --- a/src/xsettings.h +++ b/src/xsettings.h @@ -36,7 +36,7 @@ typedef struct pgtk_display_info Display_Info; extern void xsettings_initialize (Display_Info *); #ifndef HAVE_PGTK -extern void xft_settings_event (Display_Info *, const XEvent *); +extern bool xft_settings_event (Display_Info *, const XEvent *); #endif extern const char *xsettings_get_system_font (void); #ifdef USE_LUCID diff --git a/src/xterm.c b/src/xterm.c index 2cc17b455da..7a0a21b1369 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -233,18 +233,19 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ EMACS_CHECK_MODULES([XFIXES], [$XFIXES_MODULES]) if test $HAVE_XFIXES = no; then # Test old way in case pkg-config doesn't have it (older machines). - AC_CHECK_HEADER(X11/extensions/Xfixes.h, - [AC_CHECK_LIB(Xfixes, XFixesHideCursor, HAVE_XFIXES=yes)]) + AC_CHECK_HEADER([X11/extensions/Xfixes.h], + [AC_CHECK_LIB([Xfixes], [XFixesHideCursor], [HAVE_XFIXES=yes])]) if test $HAVE_XFIXES = yes; then XFIXES_LIBS=-lXfixes fi fi if test $HAVE_XFIXES = yes; then - AC_DEFINE(HAVE_XFIXES, 1, [Define to 1 if you have the Xfixes extension.]) + AC_DEFINE([HAVE_XFIXES], [1], + [Define to 1 if you have the Xfixes extension.]) fi fi - AC_SUBST(XFIXES_CFLAGS) - AC_SUBST(XFIXES_LIBS) + AC_SUBST([XFIXES_CFLAGS]) + AC_SUBST([XFIXES_LIBS]) Then, make sure to adjust CFLAGS and LIBES in src/Makefile.in and add the new XFIXES_CFLAGS and XFIXES_LIBS variables to @@ -497,7 +498,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ data.l[1] = timestamp data.l[2] = low 32 bits of a provided frame counter value data.l[3] = high 32 bits of a provided frame counter value - data.l[4] = 1 if the the extended frame counter should be updated, + data.l[4] = 1 if the extended frame counter should be updated, otherwise 0 Upon receiving such an event, Emacs constructs and saves a counter @@ -520,9 +521,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ replying to the initiating client) is performed from Lisp inside `x-dnd.el'. - However, dragging contents from Emacs is implemented entirely in C. - X Windows has several competing drag-and-drop protocols, of which - Emacs supports two: the XDND protocol (see + However, dragging contents from Emacs is implemented almost entirely + in C. X Windows has several competing drag-and-drop protocols, of + which Emacs supports two on the C level: the XDND protocol (see https://freedesktop.org/wiki/Specifications/XDND) and the Motif drag and drop protocols. These protocols are based on the initiator owning a special selection, specifying an action the recipient @@ -545,7 +546,16 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ released over the recipient window, Emacs sends a "drop" message to the target window, waits for a reply, and returns the action selected by the recipient to the Lisp code that initiated the - drag-and-drop operation. */ + drag-and-drop operation. + + When a drop happens on a window not supporting any protocol + implemented on the C level, the function inside + `x-dnd-unsupported-drop-function' is called with some parameters of + the drop. If it returns non-nil, then Emacs tries to simulate a + drop happening with the primary selection and synthetic button + events (see `x_dnd_do_unsupported_drop'). That function implements + the OffiX drag-and-drop protocol by default. See + `x-dnd-handle-unsupported-drop' in `x-dnd.el' for more details. */ #include <config.h> #include <stdlib.h> @@ -710,6 +720,12 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <X11/XKBlib.h> #endif +/* Although X11/Xlib.h commonly defines the types XErrorHandler and + XIOErrorHandler, they are not in the Xlib spec, so for portability + define and use names with an Emacs_ prefix instead. */ +typedef int (*Emacs_XErrorHandler) (Display *, XErrorEvent *); +typedef int (*Emacs_XIOErrorHandler) (Display *); + #if defined USE_XCB && defined USE_CAIRO_XCB #define USE_CAIRO_XCB_SURFACE #endif @@ -789,10 +805,28 @@ static int current_finish; static struct input_event *current_hold_quit; #endif +#ifdef HAVE_XINPUT2 +#ifndef X_XIGrabDevice +#define X_XIGrabDevice 51 +#endif + +#ifndef X_XIUngrabDevice +#define X_XIUngrabDevice 52 +#endif + +#ifndef X_XIAllowEvents +#define X_XIAllowEvents 53 +#endif +#endif + /* Queue selection requests in `pending_selection_requests' if more than 0. */ static int x_use_pending_selection_requests; +/* Like `next_kbd_event', but for use in X code. */ +#define X_NEXT_KBD_EVENT(ptr) \ + ((ptr) == kbd_buffer + KBD_BUFFER_SIZE - 1 ? kbd_buffer : (ptr) + 1) + static void x_push_selection_request (struct selection_input_event *); /* Defer selection requests. Between this and @@ -829,14 +863,18 @@ x_defer_selection_requests (void) avoids exhausting the keyboard buffer with some over-enthusiastic clipboard managers. */ if (!between) - kbd_fetch_ptr = (event == kbd_buffer + KBD_BUFFER_SIZE - 1 - ? kbd_buffer : event + 1); + { + kbd_fetch_ptr = X_NEXT_KBD_EVENT (event); + + /* `detect_input_pending' will then recompute + whether or not pending input events exist. */ + input_pending = false; + } } else between = true; - event = (event == kbd_buffer + KBD_BUFFER_SIZE - 1 - ? kbd_buffer : event + 1); + event = X_NEXT_KBD_EVENT (event); } } @@ -914,7 +952,7 @@ static const struct x_atom_ref x_atom_refs[] = ATOM_REFS_INIT ("MULTIPLE", Xatom_MULTIPLE) ATOM_REFS_INIT ("INCR", Xatom_INCR) ATOM_REFS_INIT ("_EMACS_TMP_", Xatom_EMACS_TMP) - ATOM_REFS_INIT ("EMACS_SERVER_TIME_PROP", Xatom_EMACS_SERVER_TIME_PROP) + ATOM_REFS_INIT ("_EMACS_SERVER_TIME_PROP", Xatom_EMACS_SERVER_TIME_PROP) ATOM_REFS_INIT ("TARGETS", Xatom_TARGETS) ATOM_REFS_INIT ("NULL", Xatom_NULL) ATOM_REFS_INIT ("ATOM", Xatom_ATOM) @@ -922,6 +960,7 @@ static const struct x_atom_ref x_atom_refs[] = ATOM_REFS_INIT ("CLIPBOARD_MANAGER", Xatom_CLIPBOARD_MANAGER) ATOM_REFS_INIT ("_XEMBED_INFO", Xatom_XEMBED_INFO) ATOM_REFS_INIT ("_MOTIF_WM_HINTS", Xatom_MOTIF_WM_HINTS) + ATOM_REFS_INIT ("_EMACS_DRAG_ATOM", Xatom_EMACS_DRAG_ATOM) /* For properties of font. */ ATOM_REFS_INIT ("PIXEL_SIZE", Xatom_PIXEL_SIZE) ATOM_REFS_INIT ("AVERAGE_WIDTH", Xatom_AVERAGE_WIDTH) @@ -958,7 +997,9 @@ static const struct x_atom_ref x_atom_refs[] = ATOM_REFS_INIT ("_NET_WORKAREA", Xatom_net_workarea) ATOM_REFS_INIT ("_NET_WM_SYNC_REQUEST", Xatom_net_wm_sync_request) ATOM_REFS_INIT ("_NET_WM_SYNC_REQUEST_COUNTER", Xatom_net_wm_sync_request_counter) + ATOM_REFS_INIT ("_NET_WM_SYNC_FENCES", Xatom_net_wm_sync_fences) ATOM_REFS_INIT ("_NET_WM_FRAME_DRAWN", Xatom_net_wm_frame_drawn) + ATOM_REFS_INIT ("_NET_WM_FRAME_TIMINGS", Xatom_net_wm_frame_timings) ATOM_REFS_INIT ("_NET_WM_USER_TIME", Xatom_net_wm_user_time) ATOM_REFS_INIT ("_NET_WM_USER_TIME_WINDOW", Xatom_net_wm_user_time_window) ATOM_REFS_INIT ("_NET_CLIENT_LIST_STACKING", Xatom_net_client_list_stacking) @@ -997,6 +1038,10 @@ static const struct x_atom_ref x_atom_refs[] = ATOM_REFS_INIT ("XdndLeave", Xatom_XdndLeave) ATOM_REFS_INIT ("XdndDrop", Xatom_XdndDrop) ATOM_REFS_INIT ("XdndFinished", Xatom_XdndFinished) + /* XDS source and target. */ + ATOM_REFS_INIT ("XdndDirectSave0", Xatom_XdndDirectSave0) + ATOM_REFS_INIT ("XdndActionDirectSave", Xatom_XdndActionDirectSave) + ATOM_REFS_INIT ("text/plain", Xatom_text_plain) /* Motif drop protocol support. */ ATOM_REFS_INIT ("_MOTIF_DRAG_WINDOW", Xatom_MOTIF_DRAG_WINDOW) ATOM_REFS_INIT ("_MOTIF_DRAG_TARGETS", Xatom_MOTIF_DRAG_TARGETS) @@ -1008,6 +1053,9 @@ static const struct x_atom_ref x_atom_refs[] = Xatom_MOTIF_DRAG_RECEIVER_INFO) ATOM_REFS_INIT ("XmTRANSFER_SUCCESS", Xatom_XmTRANSFER_SUCCESS) ATOM_REFS_INIT ("XmTRANSFER_FAILURE", Xatom_XmTRANSFER_FAILURE) + /* Old OffiX (a.k.a. old KDE) drop protocol support. */ + ATOM_REFS_INIT ("DndProtocol", Xatom_DndProtocol) + ATOM_REFS_INIT ("_DND_PROTOCOL", Xatom_DND_PROTOCOL) }; enum @@ -1050,6 +1098,7 @@ static void x_frame_rehighlight (struct x_display_info *); static void x_clip_to_row (struct window *, struct glyph_row *, enum glyph_row_area, GC); static struct scroll_bar *x_window_to_scroll_bar (Display *, Window, int); +static struct frame *x_window_to_frame (struct x_display_info *, int); static void x_scroll_bar_report_motion (struct frame **, Lisp_Object *, enum scroll_bar_part *, Lisp_Object *, Lisp_Object *, @@ -1081,27 +1130,24 @@ static void x_initialize (void); static bool x_get_current_wm_state (struct frame *, Window, int *, bool *, bool *); static void x_update_opaque_region (struct frame *, XEvent *); -#if !defined USE_TOOLKIT_SCROLL_BARS && defined HAVE_XDBE -static void x_scroll_bar_end_update (struct x_display_info *, struct scroll_bar *); -#endif - #ifdef HAVE_X_I18N static int x_filter_event (struct x_display_info *, XEvent *); #endif +static void x_clean_failable_requests (struct x_display_info *); static struct frame *x_tooltip_window_to_frame (struct x_display_info *, Window, bool *); static Window x_get_window_below (Display *, Window, int, int, int *, int *); +#ifndef USE_TOOLKIT_SCROLL_BARS +static void x_scroll_bar_redraw (struct scroll_bar *); +#endif + /* Global state maintained during a drag-and-drop operation. */ /* Flag that indicates if a drag-and-drop operation is in progress. */ bool x_dnd_in_progress; -/* Number that indicates the last "generation" of - UNSUPPORTED_DROP_EVENTs handled. */ -unsigned x_dnd_unsupported_event_level; - /* The frame where the drag-and-drop operation originated. */ struct frame *x_dnd_frame; @@ -1116,6 +1162,20 @@ struct frame *x_dnd_finish_frame; important information. */ bool x_dnd_waiting_for_finish; +/* Flag that means (when set in addition to + `x_dnd_waiting_for_finish') to run the unsupported drop function + with the given arguments. */ +static bool x_dnd_run_unsupported_drop_function; + +/* The "before"-time of the unsupported drop. */ +static Time x_dnd_unsupported_drop_time; + +/* The target window of the unsupported drop. */ +static Window x_dnd_unsupported_drop_window; + +/* The Lisp data associated with the unsupported drop function. */ +static Lisp_Object x_dnd_unsupported_drop_data; + /* Whether or not to move the tooltip along with the mouse pointer during drag-and-drop. */ static bool x_dnd_update_tooltip; @@ -1151,6 +1211,9 @@ static bool x_dnd_xm_use_help; /* Whether or not Motif drag initiator info was set up. */ static bool x_dnd_motif_setup_p; +/* The Motif drag atom used during the drag-and-drop operation. */ +static Atom x_dnd_motif_atom; + /* The target window we are waiting for an XdndFinished message from. */ static Window x_dnd_pending_finish_target; @@ -1216,6 +1279,30 @@ static Window x_dnd_mouse_rect_target; drop target if the mouse pointer lies within. */ static XRectangle x_dnd_mouse_rect; +/* If not None, Emacs is waiting for an XdndStatus event from this + window. */ +static Window x_dnd_waiting_for_status_window; + +/* If .type != 0, an event that should be sent to .xclient.window + upon receiving an XdndStatus event from said window. */ +static XEvent x_dnd_pending_send_position; + +/* Whether or not that event corresponds to a button press. */ +static bool x_dnd_pending_send_position_button; + +/* The root-window position of that event. */ +static int x_dnd_pending_send_position_root_x; + +/* Likewise. */ +static int x_dnd_pending_send_position_root_y; + +/* If true, send a drop from `x_dnd_finish_frame' to the pending + status window after receiving all pending XdndStatus events. */ +static bool x_dnd_need_send_drop; + +/* The protocol version of any such drop. */ +static int x_dnd_send_drop_proto; + /* The action the drop target actually chose to perform. Under XDND, this is set upon receiving the XdndFinished or @@ -1268,6 +1355,21 @@ static struct frame *x_dnd_movement_frame; with. */ static int x_dnd_movement_x, x_dnd_movement_y; +/* The frame for which `x-dnd-wheel-function' should be called. */ +static struct frame *x_dnd_wheel_frame; + +/* The coordinates which the wheel function should be called with. */ +static int x_dnd_wheel_x, x_dnd_wheel_y; + +/* The button that was pressed. */ +static int x_dnd_wheel_button; + +/* The modifier state when the button was pressed. */ +static int x_dnd_wheel_state; + +/* When the button was pressed. */ +static Time x_dnd_wheel_time; + #ifdef HAVE_XKB /* The keyboard state during the drag-and-drop operation. */ static unsigned int x_dnd_keyboard_state; @@ -1285,6 +1387,26 @@ static bool x_dnd_inside_handle_one_xevent; started. */ static int x_dnd_recursion_depth; +/* The cons cell containing the selection alias between the Motif drag + selection and `XdndSelection'. The car and cdr are only set when + initiating Motif drag-and-drop for the first time. */ +static Lisp_Object x_dnd_selection_alias_cell; + +/* The last known position of the tooltip window. */ +static int x_dnd_last_tooltip_x, x_dnd_last_tooltip_y; + +/* Whether or not those values are actually known yet. */ +static bool x_dnd_last_tooltip_valid; + +#ifdef HAVE_XINPUT2 +/* The master pointer device being used for the drag-and-drop + operation. */ +static int x_dnd_pointer_device; + +/* The keyboard device attached to that pointer device. */ +static int x_dnd_keyboard_device; +#endif + /* Structure describing a single window that can be the target of drag-and-drop operations. */ struct x_client_list_window @@ -1352,7 +1474,7 @@ static bool x_dnd_use_toplevels; /* Motif drag-and-drop protocol support. */ /* Pointer to a variable which stores whether or not an X error - occured while trying to create the Motif drag window. */ + occurred while trying to create the Motif drag window. */ static volatile bool *xm_drag_window_error; typedef enum xm_byte_order @@ -1471,6 +1593,17 @@ typedef struct xm_drag_motion_message /* CARD16 */ uint16_t x, y; } xm_drag_motion_message; +typedef struct xm_drag_motion_reply +{ + /* BYTE */ uint8_t reason; + /* BYTE */ uint8_t byte_order; + + /* CARD16 */ uint16_t side_effects; + /* CARD32 */ uint32_t timestamp; + /* CARD16 */ uint16_t better_x; + /* CARD16 */ uint16_t better_y; +} xm_drag_motion_reply; + typedef struct xm_top_level_leave_message { /* BYTE */ uint8_t reason; @@ -1493,12 +1626,16 @@ typedef struct xm_top_level_leave_message enum xm_drag_operation { - XM_DRAG_NOOP = 0, - XM_DRAG_MOVE = (1L << 0), - XM_DRAG_COPY = (1L << 1), - XM_DRAG_LINK = (1L << 2), + XM_DRAG_NOOP = 0, + XM_DRAG_MOVE = (1L << 0), + XM_DRAG_COPY = (1L << 1), + XM_DRAG_LINK = (1L << 2), + XM_DRAG_LINK_REC = 3, }; +#define XM_DRAG_OPERATION_IS_LINK(op) ((op) == XM_DRAG_LINK \ + || (op) == XM_DRAG_LINK_REC) + enum xm_drag_action { XM_DROP_ACTION_DROP = 0, @@ -1627,12 +1764,12 @@ xm_read_targets_table_rec (uint8_t *bytes, ptrdiff_t length, nitems = *(uint16_t *) bytes; - if (length < 2 + nitems * 4) - return NULL; - if (byteorder != XM_BYTE_ORDER_CUR_FIRST) SWAPCARD16 (nitems); + if (length < 2 + nitems * 4) + return NULL; + rec = xmalloc (FLEXSIZEOF (struct xm_targets_table_rec, targets, nitems * 4)); rec->n_targets = nitems; @@ -1767,20 +1904,59 @@ xm_drag_window_io_error_handler (Display *dpy) siglongjmp (x_dnd_disconnect_handler, 1); } +/* Determine whether or not WINDOW exists on DPYINFO by selecting for + input from it. */ +static bool +x_special_window_exists_p (struct x_display_info *dpyinfo, + Window window) +{ + bool rc; + + x_catch_errors (dpyinfo->display); + XSelectInput (dpyinfo->display, window, + StructureNotifyMask); + rc = !x_had_errors_p (dpyinfo->display); + x_uncatch_errors_after_check (); + + return rc; +} + +/* Drag window creation strategy (very tricky, but race-free): + + First look for _MOTIF_DRAG_WINDOW. If it is already present, + return it immediately to avoid the overhead of new display + connections. + + Otherwise, create a new connection to the display. In that + connection, create a window, which will be the new drag window. Set + the client disconnect mode of the new connection to + RetainPermanent, and close it. + + Grab the current display. Look up _MOTIF_DRAG_WINDOW, the current + drag window. If it exists (which means _MOTIF_DRAG_WINDOW was + created between the first step and now), kill the client that + created the new drag window to free the client slot on the X + server. Otherwise, set _MOTIF_DRAG_WINDOW to the new drag window. + + Ungrab the display and return whichever window is currently in + _MOTIF_DRAG_WINDOW. */ + static Window -xm_get_drag_window (struct x_display_info *dpyinfo) +xm_get_drag_window_1 (struct x_display_info *dpyinfo) { - Atom actual_type, _MOTIF_DRAG_WINDOW; + Atom actual_type; int rc, actual_format; unsigned long nitems, bytes_remaining; unsigned char *tmp_data = NULL; Window drag_window; XSetWindowAttributes attrs; Display *temp_display; - void *old_handler, *old_io_handler; - /* These are volatile because GCC mistakenly warns about them being + Emacs_XErrorHandler old_handler; + Emacs_XIOErrorHandler old_io_handler; + + /* This is volatile because GCC mistakenly warns about them being clobbered by longjmp. */ - volatile bool error, created; + volatile bool error; drag_window = None; rc = XGetWindowProperty (dpyinfo->display, dpyinfo->root_window, @@ -1789,28 +1965,20 @@ xm_get_drag_window (struct x_display_info *dpyinfo) &actual_format, &nitems, &bytes_remaining, &tmp_data) == Success; - if (rc) + if (rc && actual_type == XA_WINDOW + && actual_format == 32 && nitems == 1 + && tmp_data) { - if (actual_type == XA_WINDOW - && actual_format == 32 && nitems == 1) - { - drag_window = *(Window *) tmp_data; - x_catch_errors (dpyinfo->display); - /* We use ButtonPressMask since it's one of the events an - input-only window can never get. */ - XSelectInput (dpyinfo->display, drag_window, - ButtonPressMask); - rc = !x_had_errors_p (dpyinfo->display); - x_uncatch_errors_after_check (); + drag_window = *(Window *) tmp_data; + rc = x_special_window_exists_p (dpyinfo, drag_window); - if (!rc) - drag_window = None; - } - - if (tmp_data) - XFree (tmp_data); + if (!rc) + drag_window = None; } + if (tmp_data) + XFree (tmp_data); + if (drag_window == None) { block_input (); @@ -1839,74 +2007,22 @@ xm_get_drag_window (struct x_display_info *dpyinfo) error = false; xm_drag_window_error = &error; - XGrabServer (temp_display); XSetCloseDownMode (temp_display, RetainPermanent); + old_handler = XSetErrorHandler (xm_drag_window_error_handler); - /* We can't use XErrorHandler since it's not in the Xlib - specification, and Emacs tries to be portable. */ - old_handler = (void *) XSetErrorHandler (xm_drag_window_error_handler); - - _MOTIF_DRAG_WINDOW = XInternAtom (temp_display, - "_MOTIF_DRAG_WINDOW", False); - - if (error) - goto give_up; - - /* Some other program might've created a drag window between now - and when we first looked. Use that if it exists. */ - - tmp_data = NULL; - rc = XGetWindowProperty (temp_display, DefaultRootWindow (temp_display), - _MOTIF_DRAG_WINDOW, 0, 1, False, XA_WINDOW, - &actual_type, &actual_format, &nitems, - &bytes_remaining, &tmp_data) == Success; - - if (rc && actual_type == XA_WINDOW - && actual_format == 32 && nitems == 1 - && tmp_data) - drag_window = *(Window *) tmp_data; - - if (tmp_data) - XFree (tmp_data); - - error = false; - - if (drag_window == None) - { - created = true; - - attrs.override_redirect = True; - drag_window = XCreateWindow (temp_display, DefaultRootWindow (temp_display), - -1, -1, 1, 1, 0, CopyFromParent, InputOnly, - CopyFromParent, CWOverrideRedirect, &attrs); - XChangeProperty (temp_display, DefaultRootWindow (temp_display), - _MOTIF_DRAG_WINDOW, XA_WINDOW, 32, PropModeReplace, - (unsigned char *) &drag_window, 1); - } - else - created = false; + attrs.override_redirect = True; + drag_window = XCreateWindow (temp_display, DefaultRootWindow (temp_display), + -1, -1, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrs); /* Handle all errors now. */ XSync (temp_display, False); - give_up: - /* Some part of the drag window creation process failed, so - punt. */ + punt. Release all resources too. */ if (error) { - /* If the drag window was actually created, delete it now. - Probably, a BadAlloc happened during the XChangeProperty - request. */ - if (created) - { - if (drag_window != None) - XDestroyWindow (temp_display, drag_window); - - XDeleteProperty (temp_display, DefaultRootWindow (temp_display), - _MOTIF_DRAG_WINDOW); - } - + XSetCloseDownMode (temp_display, DestroyAll); drag_window = None; } @@ -1924,23 +2040,64 @@ xm_get_drag_window (struct x_display_info *dpyinfo) /* Make sure the drag window created is actually valid for the current display, and the XOpenDisplay above didn't accidentally connect to some other display. */ - x_catch_errors (dpyinfo->display); - /* We use ButtonPressMask since it's one of the events an - input-only window can never get. */ - XSelectInput (dpyinfo->display, drag_window, - ButtonPressMask); - rc = !x_had_errors_p (dpyinfo->display); - x_uncatch_errors_after_check (); + if (!x_special_window_exists_p (dpyinfo, drag_window)) + drag_window = None; unblock_input (); - /* We connected to the wrong display, so just give up. */ - if (!rc) - drag_window = None; + if (drag_window != None) + { + XGrabServer (dpyinfo->display); + + x_catch_errors (dpyinfo->display); + tmp_data = NULL; + + rc = XGetWindowProperty (dpyinfo->display, dpyinfo->root_window, + dpyinfo->Xatom_MOTIF_DRAG_WINDOW, + 0, 1, False, XA_WINDOW, &actual_type, + &actual_format, &nitems, &bytes_remaining, + &tmp_data) == Success; + + if (rc && actual_type == XA_WINDOW + && actual_format == 32 && nitems == 1 + && tmp_data + && x_special_window_exists_p (dpyinfo, + *(Window *) tmp_data)) + { + /* Kill the client now to avoid leaking a client slot, + which is a limited resource. */ + XKillClient (dpyinfo->display, drag_window); + drag_window = *(Window *) tmp_data; + } + else + XChangeProperty (dpyinfo->display, dpyinfo->root_window, + dpyinfo->Xatom_MOTIF_DRAG_WINDOW, + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &drag_window, 1); + + if (tmp_data) + XFree (tmp_data); + + if (x_had_errors_p (dpyinfo->display)) + drag_window = None; + x_uncatch_errors (); + + XUngrabServer (dpyinfo->display); + } } return drag_window; } +static Window +xm_get_drag_window (struct x_display_info *dpyinfo) +{ + if (dpyinfo->motif_drag_window != None) + return dpyinfo->motif_drag_window; + + dpyinfo->motif_drag_window = xm_get_drag_window_1 (dpyinfo); + return dpyinfo->motif_drag_window; +} + static int xm_setup_dnd_targets (struct x_display_info *dpyinfo, Atom *targets, int ntargets) @@ -1950,13 +2107,16 @@ xm_setup_dnd_targets (struct x_display_info *dpyinfo, unsigned char *tmp_data = NULL; unsigned long nitems, bytes_remaining; int rc, actual_format, idx; + bool had_errors; xm_targets_table_header header; - xm_targets_table_rec **recs; + xm_targets_table_rec **recs UNINIT; xm_byte_order byteorder; uint8_t *data; ptrdiff_t total_bytes, total_items, i; uint32_t size, target_count; + retry_drag_window: + drag_window = xm_get_drag_window (dpyinfo); if (drag_window == None || ntargets > 64) @@ -1969,12 +2129,26 @@ xm_setup_dnd_targets (struct x_display_info *dpyinfo, sizeof (Atom), x_atoms_compare); XGrabServer (dpyinfo->display); + + x_catch_errors (dpyinfo->display); rc = XGetWindowProperty (dpyinfo->display, drag_window, dpyinfo->Xatom_MOTIF_DRAG_TARGETS, 0L, LONG_MAX, False, dpyinfo->Xatom_MOTIF_DRAG_TARGETS, &actual_type, &actual_format, &nitems, &bytes_remaining, &tmp_data) == Success; + had_errors = x_had_errors_p (dpyinfo->display); + x_uncatch_errors_after_check (); + + /* The drag window is probably invalid, so remove our record of + it. */ + if (had_errors) + { + dpyinfo->motif_drag_window = None; + XUngrabServer (dpyinfo->display); + + goto retry_drag_window; + } if (rc && tmp_data && !bytes_remaining && actual_type == dpyinfo->Xatom_MOTIF_DRAG_TARGETS @@ -2145,13 +2319,155 @@ xm_setup_dnd_targets (struct x_display_info *dpyinfo, return idx; } +/* Allocate an atom that will be used for the Motif selection during + the drag-and-drop operation. + + Grab the server, and then retrieve a list of atoms named + _EMACS_DRAG_ATOM from the root window. Find the first atom that + has no selection owner, own it and return it. If there is no such + atom, add a unique atom to the end of the list and return that + instead. */ + +static Atom +xm_get_drag_atom_1 (struct x_display_info *dpyinfo, + struct frame *source_frame) +{ + Atom actual_type, *atoms, atom; + unsigned long nitems, bytes_remaining; + unsigned char *tmp_data; + int rc, actual_format; + unsigned long i; + char *buffer; + Window owner; + + /* Make sure this operation is done atomically. */ + XGrabServer (dpyinfo->display); + + rc = XGetWindowProperty (dpyinfo->display, dpyinfo->root_window, + dpyinfo->Xatom_EMACS_DRAG_ATOM, + 0, LONG_MAX, False, XA_ATOM, &actual_type, + &actual_format, &nitems, &bytes_remaining, + &tmp_data); + atom = None; + /* GCC thinks i is used uninitialized, but it's always initialized if + `atoms' exists at that particular spot. */ + i = 0; + + if (rc == Success + && actual_format == 32 && nitems + && actual_type == XA_ATOM) + { + atoms = (Atom *) tmp_data; + + x_catch_errors (dpyinfo->display); + + for (i = 0; i < nitems; ++i) + { + owner = XGetSelectionOwner (dpyinfo->display, atoms[i]); + + if (!x_had_errors_p (dpyinfo->display) + && (owner == None + /* If we already own this selection (even if another + frame owns it), use it. There is no way of + knowing when ownership was asserted, so it still + has to be owned again. */ + || x_window_to_frame (dpyinfo, owner))) + { + atom = atoms[i]; + + break; + } + } + + x_uncatch_errors (); + } + + if (tmp_data) + XFree (tmp_data); + + buffer = dpyinfo->motif_drag_atom_name; + + if (atom) + { + sprintf (buffer, "_EMACS_ATOM_%lu", i + 1); + XSetSelectionOwner (dpyinfo->display, atom, + FRAME_X_WINDOW (source_frame), + dpyinfo->last_user_time); + + /* The selection's last-change time is newer than our + last_user_time, so create a new selection instead. */ + if (XGetSelectionOwner (dpyinfo->display, atom) + != FRAME_X_WINDOW (source_frame)) + atom = None; + } + + while (!atom) + { + sprintf (buffer, "_EMACS_ATOM_%lu", nitems + 1); + atom = XInternAtom (dpyinfo->display, buffer, False); + + XSetSelectionOwner (dpyinfo->display, atom, + FRAME_X_WINDOW (source_frame), + dpyinfo->last_user_time); + + XChangeProperty (dpyinfo->display, dpyinfo->root_window, + dpyinfo->Xatom_EMACS_DRAG_ATOM, XA_ATOM, 32, + (rc != Success + || (actual_format != 32 + || actual_type != XA_ATOM) + ? PropModeReplace : PropModeAppend), + (unsigned char *) &atom, 1); + + actual_format = 32; + actual_type = XA_ATOM; + rc = Success; + nitems += 1; + + /* The selection's last-change time is newer than our + last_user_time, so create a new selection (again). */ + if (XGetSelectionOwner (dpyinfo->display, atom) + != FRAME_X_WINDOW (source_frame)) + atom = None; + } + + dpyinfo->motif_drag_atom_time = dpyinfo->last_user_time; + dpyinfo->motif_drag_atom_owner = source_frame; + + XUngrabServer (dpyinfo->display); + return atom; +} + +static Atom +xm_get_drag_atom (struct x_display_info *dpyinfo) +{ + Atom atom; + + if (dpyinfo->motif_drag_atom != None) + atom = dpyinfo->motif_drag_atom; + else + atom = xm_get_drag_atom_1 (dpyinfo, x_dnd_frame); + + dpyinfo->motif_drag_atom = atom; + return atom; +} + static void xm_setup_drag_info (struct x_display_info *dpyinfo, struct frame *source_frame) { + Atom atom; xm_drag_initiator_info drag_initiator_info; int idx; + atom = xm_get_drag_atom (dpyinfo); + + if (atom == None) + return; + + XSETCAR (x_dnd_selection_alias_cell, + x_atom_to_symbol (dpyinfo, atom)); + XSETCDR (x_dnd_selection_alias_cell, QXdndSelection); + idx = xm_setup_dnd_targets (dpyinfo, x_dnd_targets, x_dnd_n_targets); @@ -2160,14 +2476,15 @@ xm_setup_drag_info (struct x_display_info *dpyinfo, drag_initiator_info.byteorder = XM_BYTE_ORDER_CUR_FIRST; drag_initiator_info.protocol = XM_DRAG_PROTOCOL_VERSION; drag_initiator_info.table_index = idx; - drag_initiator_info.selection = dpyinfo->Xatom_XdndSelection; + drag_initiator_info.selection = atom; - xm_write_drag_initiator_info (dpyinfo->display, FRAME_X_WINDOW (source_frame), - dpyinfo->Xatom_XdndSelection, + xm_write_drag_initiator_info (dpyinfo->display, + FRAME_X_WINDOW (source_frame), atom, dpyinfo->Xatom_MOTIF_DRAG_INITIATOR_INFO, &drag_initiator_info); x_dnd_motif_setup_p = true; + x_dnd_motif_atom = atom; } } @@ -2191,9 +2508,9 @@ xm_send_drop_message (struct x_display_info *dpyinfo, Window source, *((uint32_t *) &msg.xclient.data.b[12]) = dmsg->index_atom; *((uint32_t *) &msg.xclient.data.b[16]) = dmsg->source_window; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static void @@ -2218,9 +2535,9 @@ xm_send_top_level_enter_message (struct x_display_info *dpyinfo, Window source, msg.xclient.data.b[18] = 0; msg.xclient.data.b[19] = 0; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static void @@ -2249,9 +2566,9 @@ xm_send_drag_motion_message (struct x_display_info *dpyinfo, Window source, msg.xclient.data.b[18] = 0; msg.xclient.data.b[19] = 0; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static void @@ -2278,8 +2595,13 @@ xm_send_top_level_leave_message (struct x_display_info *dpyinfo, Window source, XM_DROP_SITE_NONE, x_dnd_motif_operations, XM_DROP_ACTION_DROP_CANCEL); mmsg.timestamp = dmsg->timestamp; - mmsg.x = 65535; - mmsg.y = 65535; + + /* Use X_SHRT_MAX instead of the max value of uint16_t since + that will be interpreted as a plausible position by Motif, + and as such breaks if the drop target is beneath that + position. */ + mmsg.x = X_SHRT_MAX; + mmsg.y = X_SHRT_MAX; xm_send_drag_motion_message (dpyinfo, source, target, &mmsg); } @@ -2303,9 +2625,9 @@ xm_send_top_level_leave_message (struct x_display_info *dpyinfo, Window source, msg.xclient.data.b[18] = 0; msg.xclient.data.b[19] = 0; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static int @@ -2467,6 +2789,39 @@ xm_read_drag_motion_message (const XEvent *msg, return 0; } +static int +xm_read_drag_motion_reply (const XEvent *msg, xm_drag_motion_reply *reply) +{ + const uint8_t *data; + + data = (const uint8_t *) &msg->xclient.data.b[0]; + + if ((XM_DRAG_REASON_CODE (data[0]) + != XM_DRAG_REASON_DRAG_MOTION) + || (XM_DRAG_REASON_ORIGINATOR (data[0]) + != XM_DRAG_ORIGINATOR_RECEIVER)) + return 1; + + reply->reason = *(data++); + reply->byte_order = *(data++); + reply->side_effects = *(uint16_t *) data; + reply->timestamp = *(uint32_t *) (data + 2); + reply->better_x = *(uint16_t *) (data + 6); + reply->better_y = *(uint16_t *) (data + 8); + + if (reply->byte_order != XM_BYTE_ORDER_CUR_FIRST) + { + SWAPCARD16 (reply->side_effects); + SWAPCARD32 (reply->timestamp); + SWAPCARD16 (reply->better_x); + SWAPCARD16 (reply->better_y); + } + + reply->byte_order = XM_BYTE_ORDER_CUR_FIRST; + + return 0; +} + static void x_dnd_send_xm_leave_for_drop (struct x_display_info *dpyinfo, struct frame *f, Window wdesc, @@ -2492,19 +2847,16 @@ x_dnd_free_toplevels (bool display_alive) struct x_client_list_window *last; struct x_client_list_window *tem = x_dnd_toplevels; ptrdiff_t n_windows, i, buffer_size; - Window *destroy_windows; - unsigned long *prev_masks; + Window *destroy_windows UNINIT; + unsigned long *prev_masks UNINIT; specpdl_ref count; - Display *dpy; + Display *dpy UNINIT; + struct x_display_info *dpyinfo; if (!x_dnd_toplevels) /* Probably called inside an IO error handler. */ return; - /* Pacify GCC. */ - prev_masks = NULL; - destroy_windows = NULL; - if (display_alive) { buffer_size = 1024; @@ -2563,17 +2915,23 @@ x_dnd_free_toplevels (bool display_alive) if (display_alive) { - x_catch_errors (dpy); + dpyinfo = x_display_info_for_display (dpy); - for (i = 0; i < n_windows; ++i) + if (n_windows) { - XSelectInput (dpy, destroy_windows[i], prev_masks[i]); + eassume (dpyinfo); + x_ignore_errors_for_next_request (dpyinfo); + + for (i = 0; i < n_windows; ++i) + { + XSelectInput (dpy, destroy_windows[i], prev_masks[i]); #ifdef HAVE_XSHAPE - XShapeSelectInput (dpy, destroy_windows[i], None); + XShapeSelectInput (dpy, destroy_windows[i], None); #endif - } + } - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); + } } unbind_to (count, Qnil); @@ -2587,7 +2945,7 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo) Window *toplevels; int format, rc; unsigned long nitems, bytes_after; - unsigned long i; + unsigned long i, real_nitems; unsigned char *data = NULL; int frame_extents[4]; @@ -2651,6 +3009,16 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo) toplevels = (Window *) data; + for (i = 0, real_nitems = 0; i < nitems; ++i) + { + /* Some window managers with built in compositors end up putting + tooltips in the client list, which is silly. */ + if (!x_tooltip_window_to_frame (dpyinfo, toplevels[i], NULL)) + toplevels[real_nitems++] = toplevels[i]; + } + + nitems = real_nitems; + #ifdef USE_XCB USE_SAFE_ALLOCA; @@ -2922,11 +3290,11 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo) if (dpyinfo->xshape_supported_p) { - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XShapeSelectInput (dpyinfo->display, toplevels[i], ShapeNotifyMask); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); #ifndef HAVE_XCB_SHAPE x_catch_errors (dpyinfo->display); @@ -3087,12 +3455,12 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo) } #endif - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSelectInput (dpyinfo->display, toplevels[i], (attrs.your_event_mask | StructureNotifyMask | PropertyChangeMask)); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); x_dnd_toplevels = tem; } @@ -3281,6 +3649,7 @@ x_dnd_get_target_window_1 (struct x_display_info *dpyinfo, root_x and root_y are. */ *motif_out = XM_DRAG_STYLE_NONE; + for (tem = x_dnd_toplevels; tem; tem = tem->next) { if (!tem->mapped_p || tem->wm_state != NormalState) @@ -3355,7 +3724,9 @@ x_dnd_get_target_window_1 (struct x_display_info *dpyinfo, if (chosen) { - *motif_out = chosen->xm_protocol_style; + *motif_out = (x_dnd_disable_motif_protocol + ? XM_DRAG_STYLE_NONE + : chosen->xm_protocol_style); return chosen->window; } else @@ -3537,7 +3908,17 @@ x_dnd_do_unsupported_drop (struct x_display_info *dpyinfo, { XEvent event; int dest_x, dest_y; - Window child_return, child; + Window child_return, child, owner; + Lisp_Object current_value; + struct frame *f; + + f = decode_window_system_frame (frame); + + if (NILP (value)) + return; + + if (!x_dnd_use_unsupported_drop) + return; event.xbutton.serial = 0; event.xbutton.send_event = True; @@ -3558,11 +3939,27 @@ x_dnd_do_unsupported_drop (struct x_display_info *dpyinfo, && child_return != None) child = child_return; - if (CONSP (value)) - x_own_selection (QPRIMARY, Fnth (make_fixnum (1), value), - frame); - else - error ("Lost ownership of XdndSelection"); + x_uncatch_errors (); + + if (!CONSP (value)) + return; + + current_value = assq_no_quit (QPRIMARY, + dpyinfo->terminal->Vselection_alist); + + if (!NILP (current_value)) + current_value = XCAR (XCDR (current_value)); + + x_own_selection (QPRIMARY, current_value, frame, + XCAR (XCDR (value)), before); + + owner = XGetSelectionOwner (dpyinfo->display, XA_PRIMARY); + + /* If we didn't successfully obtain selection ownership, refrain + from generating events that will insert something else. */ + + if (owner != FRAME_X_WINDOW (f)) + return; event.xbutton.window = child; event.xbutton.subwindow = None; @@ -3572,33 +3969,37 @@ x_dnd_do_unsupported_drop (struct x_display_info *dpyinfo, event.xbutton.button = 2; event.xbutton.same_screen = True; - x_set_pending_dnd_time (before); + dpyinfo->pending_dnd_time = before; event.xbutton.type = ButtonPress; event.xbutton.time = before + 1; + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, child, True, ButtonPressMask, &event); + x_stop_ignoring_errors (dpyinfo); event.xbutton.type = ButtonRelease; event.xbutton.time = before + 2; + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (dpyinfo->display, child, True, ButtonReleaseMask, &event); + x_stop_ignoring_errors (dpyinfo); - x_uncatch_errors (); + x_dnd_action_symbol = QXdndActionPrivate; + + return; } static void x_dnd_send_unsupported_drop (struct x_display_info *dpyinfo, Window target_window, int root_x, int root_y, Time before) { - struct input_event ie; Lisp_Object targets, arg; int i; char **atom_names, *name; - EVENT_INIT (ie); targets = Qnil; atom_names = alloca (sizeof *atom_names * x_dnd_n_targets); @@ -3606,8 +4007,6 @@ x_dnd_send_unsupported_drop (struct x_display_info *dpyinfo, Window target_windo x_dnd_n_targets, atom_names)) return; - x_dnd_action = dpyinfo->Xatom_XdndActionPrivate; - for (i = x_dnd_n_targets; i > 0; --i) { targets = Fcons (build_string (atom_names[i - 1]), @@ -3626,19 +4025,29 @@ x_dnd_send_unsupported_drop (struct x_display_info *dpyinfo, Window target_windo else arg = Qnil; - ie.kind = UNSUPPORTED_DROP_EVENT; - ie.code = (unsigned) target_window; - ie.modifiers = x_dnd_unsupported_event_level; - ie.arg = list3 (assq_no_quit (QXdndSelection, - dpyinfo->terminal->Vselection_alist), - targets, arg); - ie.timestamp = before; + x_dnd_run_unsupported_drop_function = true; + x_dnd_unsupported_drop_time = before; + x_dnd_unsupported_drop_window = target_window; + x_dnd_unsupported_drop_data + = listn (5, assq_no_quit (QXdndSelection, + dpyinfo->terminal->Vselection_alist), + targets, arg, make_fixnum (root_x), + make_fixnum (root_y)); - XSETINT (ie.x, root_x); - XSETINT (ie.y, root_y); - XSETFRAME (ie.frame_or_window, x_dnd_frame); + x_dnd_waiting_for_finish = true; + x_dnd_finish_display = dpyinfo->display; +} - kbd_buffer_store_event (&ie); +static Window +x_dnd_fill_empty_target (int *proto_out, int *motif_out, + Window *toplevel_out, bool *was_frame) +{ + *proto_out = -1; + *motif_out = XM_DRAG_STYLE_NONE; + *toplevel_out = None; + *was_frame = false; + + return None; } static Window @@ -3853,7 +4262,8 @@ x_dnd_get_target_window (struct x_display_info *dpyinfo, || proto != -1 || motif != XM_DRAG_STYLE_NONE) { *proto_out = proto; - *motif_out = motif; + *motif_out = (x_dnd_disable_motif_protocol + ? XM_DRAG_STYLE_NONE : motif); *toplevel_out = child_return; x_uncatch_errors (); @@ -4077,9 +4487,9 @@ x_dnd_send_enter (struct frame *f, Window target, int supported) so we don't have to set it again. */ x_dnd_init_type_lists = true; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (FRAME_X_DISPLAY (f), target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static void @@ -4091,19 +4501,6 @@ x_dnd_send_position (struct frame *f, Window target, int supported, struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); XEvent msg; - if (target == x_dnd_mouse_rect_target - && x_dnd_mouse_rect.width - && x_dnd_mouse_rect.height) - { - if (root_x >= x_dnd_mouse_rect.x - && root_x < (x_dnd_mouse_rect.x - + x_dnd_mouse_rect.width) - && root_y >= x_dnd_mouse_rect.y - && root_y < (x_dnd_mouse_rect.y - + x_dnd_mouse_rect.height)) - return; - } - msg.xclient.type = ClientMessage; msg.xclient.message_type = dpyinfo->Xatom_XdndPosition; msg.xclient.format = 32; @@ -4111,21 +4508,22 @@ x_dnd_send_position (struct frame *f, Window target, int supported, msg.xclient.data.l[0] = FRAME_X_WINDOW (f); msg.xclient.data.l[1] = 0; - if (supported >= 5) + /* This is problematic because it's not specified in the + freedesktop.org copy of the protocol specification, but the copy + maintained by the original author of the protocol specifies it + for all versions. Since at least one program supports these + flags, but uses protocol v4 (and not v5), set them for all + protocool versions. */ + if (button >= 4 && button <= 7) { - if (button >= 4 && button <= 7) - { - msg.xclient.data.l[1] |= (1 << 9); - msg.xclient.data.l[1] |= (button - 4) << 7; - } - else if (button) - return; - - msg.xclient.data.l[1] |= state & 0x3f; + msg.xclient.data.l[1] |= (1 << 10); + msg.xclient.data.l[1] |= (button - 4) << 8; } else if (button) return; + msg.xclient.data.l[1] |= state & 0xff; + msg.xclient.data.l[2] = (root_x << 16) | root_y; msg.xclient.data.l[3] = 0; msg.xclient.data.l[4] = 0; @@ -4136,9 +4534,37 @@ x_dnd_send_position (struct frame *f, Window target, int supported, if (supported >= 4) msg.xclient.data.l[4] = action; - x_catch_errors (dpyinfo->display); - XSendEvent (FRAME_X_DISPLAY (f), target, False, NoEventMask, &msg); - x_uncatch_errors (); + if (x_dnd_waiting_for_status_window == target) + { + x_dnd_pending_send_position = msg; + x_dnd_pending_send_position_button = button; + x_dnd_pending_send_position_root_x = root_x; + x_dnd_pending_send_position_root_y = root_y; + } + else + { + if (target == x_dnd_mouse_rect_target + && x_dnd_mouse_rect.width + && x_dnd_mouse_rect.height + /* Ignore the mouse rectangle if we're supposed to be sending a + button press instead. */ + && !button) + { + if (root_x >= x_dnd_mouse_rect.x + && root_x < (x_dnd_mouse_rect.x + + x_dnd_mouse_rect.width) + && root_y >= x_dnd_mouse_rect.y + && root_y < (x_dnd_mouse_rect.y + + x_dnd_mouse_rect.height)) + return; + } + + x_ignore_errors_for_next_request (dpyinfo); + XSendEvent (FRAME_X_DISPLAY (f), target, False, NoEventMask, &msg); + x_stop_ignoring_errors (dpyinfo); + + x_dnd_waiting_for_status_window = target; + } } static void @@ -4157,9 +4583,12 @@ x_dnd_send_leave (struct frame *f, Window target) msg.xclient.data.l[3] = 0; msg.xclient.data.l[4] = 0; - x_catch_errors (dpyinfo->display); + x_dnd_waiting_for_status_window = None; + x_dnd_pending_send_position.type = 0; + + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (FRAME_X_DISPLAY (f), target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); } static bool @@ -4190,9 +4619,22 @@ x_dnd_send_drop (struct frame *f, Window target, Time timestamp, if (supported >= 1) msg.xclient.data.l[2] = timestamp; - x_catch_errors (dpyinfo->display); + x_ignore_errors_for_next_request (dpyinfo); XSendEvent (FRAME_X_DISPLAY (f), target, False, NoEventMask, &msg); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); + return true; +} + +static bool +x_dnd_do_drop (Window target, int supported) +{ + if (x_dnd_waiting_for_status_window != target) + return x_dnd_send_drop (x_dnd_frame, target, + x_dnd_selection_timestamp, supported); + + x_dnd_need_send_drop = true; + x_dnd_send_drop_proto = supported; + return true; } @@ -4222,10 +4664,14 @@ x_free_dnd_targets (void) x_dnd_n_targets = 0; } +/* Clear some Lisp variables after the drop finishes, so they are + freed by the GC. */ + static void -x_clear_dnd_monitors (void) +x_clear_dnd_variables (void) { x_dnd_monitors = Qnil; + x_dnd_unsupported_drop_data = Qnil; } static void @@ -4240,6 +4686,95 @@ x_free_dnd_toplevels (void) x_dnd_free_toplevels (true); } +/* Restore event masks and window properties changed during a + drag-and-drop operation, after it finishes. */ +static void +x_restore_events_after_dnd (struct frame *f, XWindowAttributes *wa) +{ + struct x_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + + /* Restore the old event mask. */ + XSelectInput (dpyinfo->display, dpyinfo->root_window, + wa->your_event_mask); +#ifdef HAVE_XKB + if (dpyinfo->supports_xkb) + XkbSelectEvents (dpyinfo->display, XkbUseCoreKbd, + XkbStateNotifyMask, 0); +#endif + /* Delete the Motif drag initiator info if it was set up. */ + if (x_dnd_motif_setup_p) + XDeleteProperty (dpyinfo->display, FRAME_X_WINDOW (f), + x_dnd_motif_atom); + + /* Remove any type list set as well. */ + if (x_dnd_init_type_lists && x_dnd_n_targets > 3) + XDeleteProperty (dpyinfo->display, FRAME_X_WINDOW (f), + dpyinfo->Xatom_XdndTypeList); +} + +#ifdef HAVE_XINPUT2 + +/* Cancel the current drag-and-drop operation, sending leave messages + to any relevant toplevels. This is called from the event loop when + an event is received telling Emacs to gracefully cancel the + drag-and-drop operation. */ + +static void +x_dnd_cancel_dnd_early (void) +{ + struct frame *f; + xm_drop_start_message dmsg; + + eassert (x_dnd_frame && x_dnd_in_progress); + + f = x_dnd_frame; + + if (x_dnd_last_seen_window != None + && x_dnd_last_protocol_version != -1) + x_dnd_send_leave (x_dnd_frame, + x_dnd_last_seen_window); + else if (x_dnd_last_seen_window != None + && !XM_DRAG_STYLE_IS_DROP_ONLY (x_dnd_last_motif_style) + && x_dnd_last_motif_style != XM_DRAG_STYLE_NONE + && x_dnd_motif_setup_p) + { + dmsg.reason = XM_DRAG_REASON (XM_DRAG_ORIGINATOR_INITIATOR, + XM_DRAG_REASON_DROP_START); + dmsg.byte_order = XM_BYTE_ORDER_CUR_FIRST; + dmsg.timestamp = FRAME_DISPLAY_INFO (f)->last_user_time; + dmsg.side_effects + = XM_DRAG_SIDE_EFFECT (xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), + x_dnd_wanted_action), + XM_DROP_SITE_VALID, x_dnd_motif_operations, + XM_DROP_ACTION_DROP_CANCEL); + dmsg.x = 0; + dmsg.y = 0; + dmsg.index_atom = x_dnd_motif_atom; + dmsg.source_window = FRAME_X_WINDOW (f); + + x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, + x_dnd_last_seen_window, + FRAME_DISPLAY_INFO (f)->last_user_time); + xm_send_drop_message (FRAME_DISPLAY_INFO (f), FRAME_X_WINDOW (f), + x_dnd_last_seen_window, &dmsg); + } + + x_dnd_last_seen_window = None; + x_dnd_last_seen_toplevel = None; + x_dnd_in_progress = false; + x_dnd_waiting_for_finish = false; + x_dnd_return_frame_object = NULL; + x_dnd_movement_frame = NULL; + x_dnd_wheel_frame = NULL; + x_dnd_frame = NULL; + x_dnd_action = None; + x_dnd_action_symbol = Qnil; +} + +#endif + static void x_dnd_cleanup_drag_and_drop (void *frame) { @@ -4274,7 +4809,7 @@ x_dnd_cleanup_drag_and_drop (void *frame) XM_DROP_ACTION_DROP_CANCEL); dmsg.x = 0; dmsg.y = 0; - dmsg.index_atom = FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection; + dmsg.index_atom = x_dnd_motif_atom; dmsg.source_window = FRAME_X_WINDOW (f); x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, @@ -4299,32 +4834,10 @@ x_dnd_cleanup_drag_and_drop (void *frame) #endif x_dnd_return_frame_object = NULL; x_dnd_movement_frame = NULL; - - block_input (); - /* Restore the old event mask. */ - XSelectInput (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, - x_dnd_old_window_attrs.your_event_mask); - -#ifdef HAVE_XKB - if (FRAME_DISPLAY_INFO (f)->supports_xkb) - XkbSelectEvents (FRAME_X_DISPLAY (f), XkbUseCoreKbd, - XkbStateNotifyMask, 0); -#endif - - /* Delete the Motif drag initiator info if it was set up. */ - if (x_dnd_motif_setup_p) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection); - - /* Remove any type list set as well. */ - if (x_dnd_init_type_lists && x_dnd_n_targets > 3) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndTypeList); - - unblock_input (); - + x_dnd_wheel_frame = NULL; x_dnd_frame = NULL; + + x_restore_events_after_dnd (f, &x_dnd_old_window_attrs); } static void @@ -4352,6 +4865,37 @@ x_dnd_note_self_position (struct x_display_info *dpyinfo, Window target, } static void +x_dnd_note_self_wheel (struct x_display_info *dpyinfo, Window target, + unsigned short root_x, unsigned short root_y, + int button, unsigned int state, Time time) +{ + struct frame *f; + int dest_x, dest_y; + Window child_return; + + if (button < 4 || button > 7) + return; + + f = x_top_window_to_frame (dpyinfo, target); + + if (f && XTranslateCoordinates (dpyinfo->display, + dpyinfo->root_window, + FRAME_X_WINDOW (f), + root_x, root_y, &dest_x, + &dest_y, &child_return)) + { + x_dnd_wheel_frame = f; + x_dnd_wheel_x = dest_x; + x_dnd_wheel_y = dest_y; + x_dnd_wheel_button = button; + x_dnd_wheel_state = state; + x_dnd_wheel_time = time; + + return; + } +} + +static void x_dnd_note_self_drop (struct x_display_info *dpyinfo, Window target, unsigned short root_x, unsigned short root_y, Time timestamp) @@ -4421,6 +4965,9 @@ x_dnd_note_self_drop (struct x_display_info *dpyinfo, Window target, XFree (atom_names[i - 1]); } + lval = Fcons (assq_no_quit (QXdndSelection, + FRAME_TERMINAL (f)->Vselection_alist), + lval); lval = Fcons (intern (name), lval); lval = Fcons (QXdndSelection, lval); ie.arg = lval; @@ -4484,15 +5031,6 @@ x_xr_ensure_picture (struct frame *f) } #endif -/* Remove calls to XFlush by defining XFlush to an empty replacement. - Calls to XFlush should be unnecessary because the X output buffer - is flushed automatically as needed by calls to XPending, - XNextEvent, or XWindowEvent according to the XFlush man page. - XTread_socket calls XPending. Removing XFlush improves - performance. */ - -#define XFlush(DISPLAY) (void) 0 - /*********************************************************************** Debugging @@ -4644,6 +5182,14 @@ x_update_opaque_region (struct frame *f, XEvent *configure) #if defined USE_CAIRO || defined HAVE_XRENDER +static int +x_gc_free_ext_data_private (XExtData *extension) +{ + xfree (extension->private_data); + + return 0; +} + static struct x_gc_ext_data * x_gc_get_ext_data (struct frame *f, GC gc, int create_if_not_found_p) { @@ -4663,6 +5209,7 @@ x_gc_get_ext_data (struct frame *f, GC gc, int create_if_not_found_p) ext_data = xzalloc (sizeof (*ext_data)); ext_data->number = dpyinfo->ext_codes->extension; ext_data->private_data = xzalloc (sizeof (struct x_gc_ext_data)); + ext_data->free_private = x_gc_free_ext_data_private; XAddToExtensionList (head, ext_data); } } @@ -4690,7 +5237,42 @@ x_extension_initialize (struct x_display_info *dpyinfo) #ifdef HAVE_XINPUT2 -/* Free all XI2 devices on dpyinfo. */ +/* Convert XI2 button state IN to a standard X button modifier + mask, and place it in OUT. */ +static void +xi_convert_button_state (XIButtonState *in, unsigned int *out) +{ + int i; + + if (in->mask_len) + { + for (i = 1; i <= 8; ++i) + { + if (XIMaskIsSet (in->mask, i)) + *out |= (Button1Mask << (i - 1)); + } + } +} + +/* Return the modifier state in XEV as a standard X modifier mask. */ + +#ifdef USE_GTK +static +#endif +unsigned int +xi_convert_event_state (XIDeviceEvent *xev) +{ + unsigned int mods, buttons; + + mods = xev->mods.effective; + buttons = 0; + + xi_convert_button_state (&xev->buttons, &buttons); + + return mods | buttons; +} + +/* Free all XI2 devices on DPYINFO. */ static void x_free_xi_devices (struct x_display_info *dpyinfo) { @@ -4770,15 +5352,15 @@ xi_populate_device_from_info (struct xi_device_t *xi_device, * device->num_classes); values = NULL; #endif -#ifdef HAVE_XINPUT2_2 - xi_device->touchpoints = NULL; -#endif xi_device->use = device->use; + xi_device->name = build_string (device->name); + xi_device->attachment = device->attachment; + #ifdef HAVE_XINPUT2_2 + xi_device->touchpoints = NULL; xi_device->direct_p = false; #endif - xi_device->name = build_string (device->name); for (c = 0; c < device->num_classes; ++c) { @@ -4851,12 +5433,16 @@ xi_populate_device_from_info (struct xi_device_t *xi_device, #endif } -/* The code below handles the tracking of scroll valuators on XInput - 2, in order to support scroll wheels that report information more - granular than a screen line. +/* Populate our client-side record of all devices, which includes + basic information about the device and also touchscreen tracking + information and scroll valuators. - On X, when the XInput 2 extension is being utilized, the states of - the mouse wheels in each axis are stored as absolute values inside + Keeping track of scroll valuators is required in order to support + scroll wheels that report information in a fashion more detailed + than a single turn of a "step" in the wheel. + + When the input extension is being utilized, the states of the mouse + wheels on each axis are stored as absolute values inside "valuators" attached to each mouse device. To obtain the delta of the scroll wheel from a motion event (which is used to report that some valuator has changed), it is necessary to iterate over every @@ -4870,22 +5456,15 @@ xi_populate_device_from_info (struct xi_device_t *xi_device, This delta however is still intermediate, to make driver implementations easier. The XInput developers recommend (and most programs use) the following algorithm to convert from scroll unit - deltas to pixel deltas: + deltas to pixel deltas by which the display must actually be + scrolled: pixels_scrolled = pow (window_height, 2.0 / 3.0) * delta; */ -/* Setup valuator tracking for XI2 master devices on - DPYINFO->display. */ - -/* This function's name is a misnomer: these days, it keeps a - client-side record of all devices, which includes basic information - about the device and also touchscreen tracking information, instead - of just scroll valuators. */ - static void -x_init_master_valuators (struct x_display_info *dpyinfo) +x_cache_xi_devices (struct x_display_info *dpyinfo) { - int ndevices, actual_devices; + int ndevices, actual_devices, i; XIDeviceInfo *infos; actual_devices = 0; @@ -4902,9 +5481,9 @@ x_init_master_valuators (struct x_display_info *dpyinfo) return; } - dpyinfo->devices = xmalloc (sizeof *dpyinfo->devices * ndevices); + dpyinfo->devices = xzalloc (sizeof *dpyinfo->devices * ndevices); - for (int i = 0; i < ndevices; ++i) + for (i = 0; i < ndevices; ++i) { if (infos[i].enabled) xi_populate_device_from_info (&dpyinfo->devices[actual_devices++], @@ -6025,20 +6604,6 @@ x_set_frame_alpha (struct frame *f) unsigned long opac; Window parent; -#ifndef USE_XCB - unsigned char *data = NULL; - Atom actual; - int rc, format; - unsigned long n, left; - unsigned long value; -#else - xcb_get_property_cookie_t opacity_cookie; - xcb_get_property_reply_t *opacity_reply; - xcb_generic_error_t *error; - bool rc; - uint32_t value; -#endif - if (dpyinfo->highlight_frame == f) alpha = f->alpha[0]; else @@ -6059,8 +6624,6 @@ x_set_frame_alpha (struct frame *f) opac = alpha * OPAQUE; - x_catch_errors (dpy); - /* If there is a parent from the window manager, put the property there also, to work around broken window managers that fail to do that. Do this unconditionally as this function is called on reparent when @@ -6069,82 +6632,407 @@ x_set_frame_alpha (struct frame *f) if (!FRAME_PARENT_FRAME (f)) { parent = x_find_topmost_parent (f); + if (parent != None) - XChangeProperty (dpy, parent, dpyinfo->Xatom_net_wm_window_opacity, - XA_CARDINAL, 32, PropModeReplace, - (unsigned char *) &opac, 1); + { + x_ignore_errors_for_next_request (dpyinfo); + XChangeProperty (dpy, parent, + dpyinfo->Xatom_net_wm_window_opacity, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &opac, 1); + x_stop_ignoring_errors (dpyinfo); + } } - /* return unless necessary */ - { -#ifndef USE_XCB - rc = XGetWindowProperty (dpy, win, dpyinfo->Xatom_net_wm_window_opacity, - 0, 1, False, XA_CARDINAL, - &actual, &format, &n, &left, - &data); + x_ignore_errors_for_next_request (dpyinfo); + XChangeProperty (dpy, win, dpyinfo->Xatom_net_wm_window_opacity, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &opac, 1); + x_stop_ignoring_errors (dpyinfo); +} - if (rc == Success && actual != None - && n && format == XA_CARDINAL && data) - { - value = *(unsigned long *) data; +/*********************************************************************** + Starting and ending an update + ***********************************************************************/ - /* Xlib sign-extends values greater than 0x7fffffff on 64-bit - machines. Get the low bits by ourself. */ +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME - value &= 0xffffffff; +/* Wait for an event matching PREDICATE to show up in the event + queue, or TIMEOUT to elapse. - if (value == opac) - { - x_uncatch_errors (); - XFree (data); - return; - } - } + If TIMEOUT passes without an event being found, return 1. + Otherwise, return 0 and behave as XIfEvent would. */ - if (data) - XFree (data); -#else - /* Avoid the confusing Xlib sign-extension mess by using XCB - instead. */ - opacity_cookie - = xcb_get_property (dpyinfo->xcb_connection, 0, (xcb_window_t) win, - (xcb_atom_t) dpyinfo->Xatom_net_wm_window_opacity, - XCB_ATOM_CARDINAL, 0, 1); - opacity_reply - = xcb_get_property_reply (dpyinfo->xcb_connection, - opacity_cookie, &error); - - rc = opacity_reply; - - if (!opacity_reply) - free (error); - else - { - rc = (opacity_reply->format == 32 - && opacity_reply->type == XCB_ATOM_CARDINAL - && (xcb_get_property_value_length (opacity_reply) >= 4)); +static int +x_if_event (Display *dpy, XEvent *event_return, + Bool (*predicate) (Display *, XEvent *, XPointer), + XPointer arg, struct timespec timeout) +{ + struct timespec current_time, target; + int fd; + fd_set fds; - if (rc) - value = *(uint32_t *) xcb_get_property_value (opacity_reply); - } + fd = ConnectionNumber (dpy); + current_time = current_timespec (); + target = timespec_add (current_time, timeout); - if (opacity_reply) - free (opacity_reply); + /* Check if an event is already in the queue. If it is, avoid + syncing. */ + if (XCheckIfEvent (dpy, event_return, predicate, arg)) + return 0; - if (rc && value == opac) - return; + while (true) + { + /* Get events into the queue. */ + XSync (dpy, False); + + /* Look for an event again. */ + if (XCheckIfEvent (dpy, event_return, predicate, arg)) + return 0; + + /* Calculate the timeout. */ + current_time = current_timespec (); + timeout = timespec_sub (target, current_time); + + /* If not, wait for some input to show up on the X connection, + or for the timeout to elapse. */ + FD_ZERO (&fds); + FD_SET (fd, &fds); + + /* If this fails due to an IO error, XSync will call the IO + error handler. */ + pselect (fd + 1, &fds, NULL, NULL, &timeout, NULL); + + /* Timeout elapsed. */ + current_time = current_timespec (); + if (timespec_cmp (target, current_time) < 0) + return 1; + } +} + +/* Return the monotonic time corresponding to the high-resolution + server timestamp TIMESTAMP. Return 0 if the necessary information + is not available. */ + +static uint_fast64_t +x_sync_get_monotonic_time (struct x_display_info *dpyinfo, + uint_fast64_t timestamp) +{ + if (dpyinfo->server_time_monotonic_p) + return timestamp; + + /* This means we haven't yet initialized the server time offset. */ + if (!dpyinfo->server_time_offset) + return 0; + + uint_fast64_t t; + return (INT_SUBTRACT_WRAPV (timestamp, dpyinfo->server_time_offset, &t) + ? 0 : t); +} + +# ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC CLOCK_REALTIME +# endif + +/* Return the current monotonic time in the same format as a + high-resolution server timestamp, or 0 if not available. */ + +static uint_fast64_t +x_sync_current_monotonic_time (void) +{ + struct timespec time; + uint_fast64_t t; + return (((clock_gettime (CLOCK_MONOTONIC, &time) != 0 + && (CLOCK_MONOTONIC == CLOCK_REALTIME + || clock_gettime (CLOCK_REALTIME, &time) != 0)) + || INT_MULTIPLY_WRAPV (time.tv_sec, 1000000, &t) + || INT_ADD_WRAPV (t, time.tv_nsec / 1000, &t)) + ? 0 : t); +} + +/* Decode a _NET_WM_FRAME_DRAWN message and calculate the time it took + to draw the last frame. */ + +static void +x_sync_note_frame_times (struct x_display_info *dpyinfo, + struct frame *f, XEvent *event) +{ + uint_fast64_t low, high, time; + struct x_output *output; + + low = event->xclient.data.l[2]; + high = event->xclient.data.l[3]; + output = FRAME_X_OUTPUT (f); + + time = x_sync_get_monotonic_time (dpyinfo, low | (high << 32)); + + if (!time || !output->temp_frame_time + || INT_SUBTRACT_WRAPV (time, output->temp_frame_time, + &output->last_frame_time)) + output->last_frame_time = 0; + +#ifdef FRAME_DEBUG + uint_fast64_t last_frame_ms = output->last_frame_time / 1000; + fprintf (stderr, + "Drawing the last frame took: %"PRIuFAST64" ms (%"PRIuFAST64")\n", + last_frame_ms, time); #endif - } +} - XChangeProperty (dpy, win, dpyinfo->Xatom_net_wm_window_opacity, - XA_CARDINAL, 32, PropModeReplace, - (unsigned char *) &opac, 1); - x_uncatch_errors (); +static Bool +x_sync_is_frame_drawn_event (Display *dpy, XEvent *event, + XPointer user_data) +{ + struct frame *f; + struct x_display_info *dpyinfo; + + f = (struct frame *) user_data; + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (event->type == ClientMessage + && (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_drawn) + && event->xclient.window == FRAME_OUTER_WINDOW (f)) + return True; + + return False; } -/*********************************************************************** - Starting and ending an update - ***********************************************************************/ +/* Wait for the compositing manager to finish drawing the last frame. + If the compositing manager has already drawn everything, do + nothing. */ + +static void +x_sync_wait_for_frame_drawn_event (struct frame *f) +{ + XEvent event; + + if (!FRAME_X_WAITING_FOR_DRAW (f) + /* The compositing manager can't draw a frame if it is + unmapped. */ + || !FRAME_VISIBLE_P (f)) + return; + + /* Wait for the frame drawn message to arrive. */ + if (x_if_event (FRAME_X_DISPLAY (f), &event, + x_sync_is_frame_drawn_event, (XPointer) f, + make_timespec (1, 0))) + { + /* TODO: display this warning in the echo area. */ + fprintf (stderr, "Warning: compositing manager spent more than 1 second " + "drawing a frame. Frame synchronization has been disabled\n"); + FRAME_X_OUTPUT (f)->use_vsync_p = false; + + /* Also change the frame parameter to reflect the new state. */ + store_frame_param (f, Quse_frame_synchronization, Qnil); + } + else + x_sync_note_frame_times (FRAME_DISPLAY_INFO (f), f, &event); + + FRAME_X_WAITING_FOR_DRAW (f) = false; +} + +/* Tell the compositing manager to postpone updates of F until a frame + has finished drawing. */ + +static void +x_sync_update_begin (struct frame *f) +{ + XSyncValue value, add; + Bool overflow; + + if (FRAME_X_EXTENDED_COUNTER (f) == None) + return; + + value = FRAME_X_COUNTER_VALUE (f); + + if (FRAME_X_OUTPUT (f)->ext_sync_end_pending_p) + { + FRAME_X_COUNTER_VALUE (f) + = FRAME_X_OUTPUT (f)->resize_counter_value; + + value = FRAME_X_COUNTER_VALUE (f); + + if (XSyncValueLow32 (value) % 2) + { + XSyncIntToValue (&add, 1); + XSyncValueAdd (&value, value, add, &overflow); + + if (overflow) + XSyncIntToValue (&value, 0); + } + + FRAME_X_OUTPUT (f)->ext_sync_end_pending_p = false; + } + + /* Since a frame is already in progress, there is no point in + continuing. */ + if (XSyncValueLow32 (value) % 2) + return; + + /* Wait for the last frame to be drawn before drawing this one. */ + x_sync_wait_for_frame_drawn_event (f); + + /* Make a note of the time at which we started to draw this + frame. */ + FRAME_X_OUTPUT (f)->temp_frame_time = x_sync_current_monotonic_time (); + + /* Since Emacs needs a non-urgent redraw, ensure that value % 4 == + 1. Later, add 3 to create the even counter value. */ + if (XSyncValueLow32 (value) % 4 == 2) + XSyncIntToValue (&add, 3); + else + XSyncIntToValue (&add, 1); + + XSyncValueAdd (&FRAME_X_COUNTER_VALUE (f), + value, add, &overflow); + + if (overflow) + XSyncIntToValue (&FRAME_X_COUNTER_VALUE (f), 3); + + eassert (XSyncValueLow32 (FRAME_X_COUNTER_VALUE (f)) % 4 == 1); + + XSyncSetCounter (FRAME_X_DISPLAY (f), + FRAME_X_EXTENDED_COUNTER (f), + FRAME_X_COUNTER_VALUE (f)); +} + +#ifdef HAVE_XSYNCTRIGGERFENCE + +/* Trigger the sync fence for counter VALUE immediately before a frame + finishes. */ + +static void +x_sync_trigger_fence (struct frame *f, XSyncValue value) +{ + uint_fast64_t n, low, high, idx; + + /* Sync fences aren't supported by the X server. */ + if (FRAME_DISPLAY_INFO (f)->xsync_major < 3 + || (FRAME_DISPLAY_INFO (f)->xsync_major == 3 + && FRAME_DISPLAY_INFO (f)->xsync_minor < 1)) + return; + + low = XSyncValueLow32 (value); + high = XSyncValueHigh32 (value); + + n = low | (high << 32); + idx = (n / 4) % 2; + +#ifdef FRAME_DEBUG + fprintf (stderr, "Triggering synchronization fence: %lu\n", idx); +#endif + + XSyncTriggerFence (FRAME_X_DISPLAY (f), + FRAME_X_OUTPUT (f)->sync_fences[idx]); +} + +/* Initialize the sync fences on F. */ + +void +x_sync_init_fences (struct frame *f) +{ + struct x_output *output; + struct x_display_info *dpyinfo; + + output = FRAME_X_OUTPUT (f); + dpyinfo = FRAME_DISPLAY_INFO (f); + + /* Sync fences aren't supported by the X server. */ + if (dpyinfo->xsync_major < 3 + || (dpyinfo->xsync_major == 3 + && dpyinfo->xsync_minor < 1)) + return; + + output->sync_fences[0] + = XSyncCreateFence (FRAME_X_DISPLAY (f), + /* The drawable given below is only used to + determine the screen on which the fence is + created. */ + FRAME_X_WINDOW (f), + False); + output->sync_fences[1] + = XSyncCreateFence (FRAME_X_DISPLAY (f), + FRAME_X_WINDOW (f), + False); + + XChangeProperty (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_sync_fences, XA_CARDINAL, + 32, PropModeReplace, + (unsigned char *) &output->sync_fences, 2); +} + +static void +x_sync_free_fences (struct frame *f) +{ + if (FRAME_X_OUTPUT (f)->sync_fences[0] != None) + XSyncDestroyFence (FRAME_X_DISPLAY (f), + FRAME_X_OUTPUT (f)->sync_fences[0]); + + if (FRAME_X_OUTPUT (f)->sync_fences[1] != None) + XSyncDestroyFence (FRAME_X_DISPLAY (f), + FRAME_X_OUTPUT (f)->sync_fences[1]); +} + +#endif + +/* Tell the compositing manager that FRAME has been drawn and can be + updated. */ + +static void +x_sync_update_finish (struct frame *f) +{ + XSyncValue value, add; + Bool overflow; + + if (FRAME_X_EXTENDED_COUNTER (f) == None) + return; + + value = FRAME_X_COUNTER_VALUE (f); + + if (!(XSyncValueLow32 (value) % 2)) + return; + + if ((XSyncValueLow32 (value) % 4) == 1) + /* This means the frame is non-urgent and should be drawn at the + next redraw point. */ + XSyncIntToValue (&add, 3); + else + /* Otherwise, the frame is urgent and should be drawn as soon as + possible. */ + XSyncIntToValue (&add, 1); + + XSyncValueAdd (&FRAME_X_COUNTER_VALUE (f), + value, add, &overflow); + + if (overflow) + XSyncIntToValue (&FRAME_X_COUNTER_VALUE (f), 0); + + /* Trigger any sync fences if necessary. */ +#ifdef HAVE_XSYNCTRIGGERFENCE + x_sync_trigger_fence (f, FRAME_X_COUNTER_VALUE (f)); +#endif + + XSyncSetCounter (FRAME_X_DISPLAY (f), + FRAME_X_EXTENDED_COUNTER (f), + FRAME_X_COUNTER_VALUE (f)); + + if (FRAME_OUTPUT_DATA (f)->use_vsync_p) + FRAME_X_WAITING_FOR_DRAW (f) = true; +} + +/* Handle a _NET_WM_FRAME_DRAWN message from the compositor. */ + +static void +x_sync_handle_frame_drawn (struct x_display_info *dpyinfo, + XEvent *message, struct frame *f) +{ + if (FRAME_OUTER_WINDOW (f) == message->xclient.window) + FRAME_X_WAITING_FOR_DRAW (f) = false; + + x_sync_note_frame_times (dpyinfo, f, message); +} +#endif /* Start an update of frame F. This function is installed as a hook for update_begin, i.e. it is called when update_begin is called. @@ -6155,7 +7043,16 @@ x_set_frame_alpha (struct frame *f) static void x_update_begin (struct frame *f) { +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* If F is double-buffered, we can make the entire frame center + around XdbeSwapBuffers. */ +#ifdef HAVE_XDBE + if (!FRAME_X_DOUBLE_BUFFERED_P (f)) +#endif + x_sync_update_begin (f); +#else /* Nothing to do. */ +#endif } /* Draw a vertical window border from (x,y0) to (x,y1) */ @@ -6237,33 +7134,54 @@ x_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) } } +#ifdef HAVE_XDBE + /* Show the frame back buffer. If frame is double-buffered, atomically publish to the user's screen graphics updates made since the last call to show_back_buffer. */ -#ifdef HAVE_XDBE static void show_back_buffer (struct frame *f) { + XdbeSwapInfo swap_info; +#ifdef USE_CAIRO + cairo_t *cr; +#endif + block_input (); if (FRAME_X_DOUBLE_BUFFERED_P (f)) { +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* Wait for drawing of the previous frame to complete before + displaying this new frame. */ + x_sync_wait_for_frame_drawn_event (f); + + /* Begin a new frame. */ + x_sync_update_begin (f); +#endif + #ifdef USE_CAIRO - cairo_t *cr = FRAME_CR_CONTEXT (f); + cr = FRAME_CR_CONTEXT (f); if (cr) cairo_surface_flush (cairo_get_target (cr)); #endif - XdbeSwapInfo swap_info; memset (&swap_info, 0, sizeof (swap_info)); swap_info.swap_window = FRAME_X_WINDOW (f); swap_info.swap_action = XdbeCopied; XdbeSwapBuffers (FRAME_X_DISPLAY (f), &swap_info, 1); + +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* Finish the frame here. */ + x_sync_update_finish (f); +#endif } + FRAME_X_NEED_BUFFER_FLIP (f) = false; unblock_input (); } + #endif /* Updates back buffer and flushes changes to display. Called from @@ -6301,17 +7219,17 @@ x_update_end (struct frame *f) #ifdef USE_CAIRO if (!FRAME_X_DOUBLE_BUFFERED_P (f) && FRAME_CR_CONTEXT (f)) - { - block_input (); - cairo_surface_flush (cairo_get_target (FRAME_CR_CONTEXT (f))); - unblock_input (); - } + cairo_surface_flush (cairo_get_target (FRAME_CR_CONTEXT (f))); #endif -#ifndef XFlush - block_input (); - XFlush (FRAME_X_DISPLAY (f)); - unblock_input (); + /* If double buffering is disabled, finish the update here. + Otherwise, finish the update when the back buffer is next + displayed. */ +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME +#ifdef HAVE_XDBE + if (!FRAME_X_DOUBLE_BUFFERED_P (f)) +#endif + x_sync_update_finish (f); #endif } @@ -6321,11 +7239,7 @@ x_update_end (struct frame *f) static void XTframe_up_to_date (struct frame *f) { -#if defined HAVE_XSYNC && !defined HAVE_GTK3 - XSyncValue add; - XSyncValue current; - Bool overflow_p; -#elif defined HAVE_XSYNC +#if defined HAVE_XSYNC && defined HAVE_GTK3 GtkWidget *widget; GdkWindow *window; GdkFrameClock *clock; @@ -6351,29 +7265,6 @@ XTframe_up_to_date (struct frame *f) FRAME_X_OUTPUT (f)->pending_basic_counter_value); FRAME_X_OUTPUT (f)->sync_end_pending_p = false; } - - if (FRAME_X_OUTPUT (f)->ext_sync_end_pending_p - && FRAME_X_EXTENDED_COUNTER (f) != None) - { - current = FRAME_X_OUTPUT (f)->current_extended_counter_value; - - if (XSyncValueLow32 (current) % 2) - XSyncIntToValue (&add, 1); - else - XSyncIntToValue (&add, 2); - - XSyncValueAdd (&FRAME_X_OUTPUT (f)->current_extended_counter_value, - current, add, &overflow_p); - - if (overflow_p) - emacs_abort (); - - XSyncSetCounter (FRAME_X_DISPLAY (f), - FRAME_X_EXTENDED_COUNTER (f), - FRAME_X_OUTPUT (f)->current_extended_counter_value); - - FRAME_X_OUTPUT (f)->ext_sync_end_pending_p = false; - } #else if (FRAME_X_OUTPUT (f)->xg_sync_end_pending_p) { @@ -6710,72 +7601,66 @@ static void x_scroll_bar_clear (struct frame *); static void x_check_font (struct frame *, struct font *); #endif +/* If SEND_EVENT, make sure that TIME is larger than the current last + user time. We don't sanitize timestamps from events sent by the X + server itself because some Lisp might have set the user time to a + ridiculously large value, and this way a more reasonable timestamp + can be obtained upon the next event. */ + static void -x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time) +x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time, + bool send_event) { #ifndef USE_GTK - struct frame *focus_frame = dpyinfo->x_focus_frame; - struct x_output *output; + struct frame *focus_frame; + Time old_time; + + focus_frame = dpyinfo->x_focus_frame; + old_time = dpyinfo->last_user_time; #endif #ifdef ENABLE_CHECKING eassert (time <= X_ULONG_MAX); #endif - dpyinfo->last_user_time = time; -#ifndef USE_GTK - if (focus_frame - && (dpyinfo->last_user_time - > (dpyinfo->last_user_check_time + 2000))) + if (!send_event || time > dpyinfo->last_user_time) + dpyinfo->last_user_time = time; + +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + if (!send_event) { - output = FRAME_X_OUTPUT (focus_frame); + /* See if the current CLOCK_MONOTONIC time is reasonably close + to the X server time. */ + uint_fast64_t monotonic_time = x_sync_current_monotonic_time (); + uint_fast64_t monotonic_ms = monotonic_time / 1000; + int_fast64_t diff_ms; - if (!x_wm_supports (focus_frame, - dpyinfo->Xatom_net_wm_user_time_window)) - { - if (output->user_time_window == None) - output->user_time_window = FRAME_OUTER_WINDOW (focus_frame); - else if (output->user_time_window != FRAME_OUTER_WINDOW (focus_frame)) - { - XDestroyWindow (dpyinfo->display, - output->user_time_window); - XDeleteProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (focus_frame), - dpyinfo->Xatom_net_wm_user_time_window); - output->user_time_window = FRAME_OUTER_WINDOW (focus_frame); - } - } - else + dpyinfo->server_time_monotonic_p + = (monotonic_time != 0 + && !INT_SUBTRACT_WRAPV (time, monotonic_ms, &diff_ms) + && -500 < diff_ms && diff_ms < 500); + + if (!dpyinfo->server_time_monotonic_p) { - if (output->user_time_window == FRAME_OUTER_WINDOW (focus_frame) - || output->user_time_window == None) - { - XSetWindowAttributes attrs; - memset (&attrs, 0, sizeof attrs); - - output->user_time_window - = XCreateWindow (dpyinfo->display, - FRAME_X_WINDOW (focus_frame), - -1, -1, 1, 1, 0, 0, InputOnly, - CopyFromParent, 0, &attrs); - - XDeleteProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (focus_frame), - dpyinfo->Xatom_net_wm_user_time); - XChangeProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (focus_frame), - dpyinfo->Xatom_net_wm_user_time_window, - XA_WINDOW, 32, PropModeReplace, - (unsigned char *) &output->user_time_window, - 1); - } + /* Compute an offset that can be subtracted from the server + time to estimate the monotonic time on the X server. */ + + if (!monotonic_time + || INT_MULTIPLY_WRAPV (time, 1000, &dpyinfo->server_time_offset) + || INT_SUBTRACT_WRAPV (dpyinfo->server_time_offset, + monotonic_time, + &dpyinfo->server_time_offset)) + dpyinfo->server_time_offset = 0; } - - dpyinfo->last_user_check_time = time; } +#endif - if (focus_frame) +#ifndef USE_GTK + /* Don't waste bandwidth if the time hasn't actually changed. */ + if (focus_frame && old_time != dpyinfo->last_user_time) { + time = dpyinfo->last_user_time; + while (FRAME_PARENT_FRAME (focus_frame)) focus_frame = FRAME_PARENT_FRAME (focus_frame); @@ -6789,6 +7674,102 @@ x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time) #endif } +#ifdef USE_GTK + +static void +x_set_gtk_user_time (struct frame *f, Time time) +{ + GtkWidget *widget; + GdkWindow *window; + + widget = FRAME_GTK_OUTER_WIDGET (f); + window = gtk_widget_get_window (widget); + + /* This widget isn't realized yet. */ + if (!window) + return; + + gdk_x11_window_set_user_time (window, time); +} + +#endif + +/* Not needed on GTK because GTK handles reporting the user time + itself. */ + +#ifndef USE_GTK +static void +x_update_frame_user_time_window (struct frame *f) +{ + struct x_output *output; + struct x_display_info *dpyinfo; + XSetWindowAttributes attrs; + + output = FRAME_X_OUTPUT (f); + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (!NILP (Vx_no_window_manager) + || !x_wm_supports (f, dpyinfo->Xatom_net_wm_user_time)) + { + if (output->user_time_window != None + && output->user_time_window != FRAME_OUTER_WINDOW (f)) + { + XDestroyWindow (dpyinfo->display, output->user_time_window); + XDeleteProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_user_time_window); + } + else + XDeleteProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_user_time); + + output->user_time_window = None; + return; + } + + if (!x_wm_supports (f, dpyinfo->Xatom_net_wm_user_time_window)) + { + if (output->user_time_window == None) + output->user_time_window = FRAME_OUTER_WINDOW (f); + else if (output->user_time_window != FRAME_OUTER_WINDOW (f)) + { + XDestroyWindow (dpyinfo->display, + output->user_time_window); + XDeleteProperty (dpyinfo->display, + FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_user_time_window); + output->user_time_window = FRAME_OUTER_WINDOW (f); + } + } + else + { + if (output->user_time_window == FRAME_OUTER_WINDOW (f) + || output->user_time_window == None) + { + memset (&attrs, 0, sizeof attrs); + + output->user_time_window + = XCreateWindow (dpyinfo->display, FRAME_X_WINDOW (f), + -1, -1, 1, 1, 0, 0, InputOnly, + CopyFromParent, 0, &attrs); + + XDeleteProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_user_time); + XChangeProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + dpyinfo->Xatom_net_wm_user_time_window, + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &output->user_time_window, 1); + } + } +} +#endif + +void +x_set_last_user_time_from_lisp (struct x_display_info *dpyinfo, + Time time) +{ + x_display_set_last_user_time (dpyinfo, time, true); +} + /* Set S->gc to a suitable GC for drawing glyph string S in cursor face. */ @@ -7335,6 +8316,8 @@ x_draw_glyphless_glyph_string_foreground (struct glyph_string *s) ? CHAR_TABLE_REF (Vglyphless_char_display, glyph->u.glyphless.ch) : XCHAR_TABLE (Vglyphless_char_display)->extras[0]); + if (CONSP (acronym)) + acronym = XCAR (acronym); if (STRINGP (acronym)) str = SSDATA (acronym); } @@ -8602,9 +9585,7 @@ x_composite_image (struct glyph_string *s, Pixmap dest, { Picture destination; XRenderPictFormat *default_format; - XRenderPictureAttributes attr; - /* Pacify GCC. */ - memset (&attr, 0, sizeof attr); + XRenderPictureAttributes attr UNINIT; default_format = FRAME_X_PICTURE_FORMAT (s->f); destination = XRenderCreatePicture (display, dest, @@ -9760,16 +10741,12 @@ x_clear_frame (struct frame *f) mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f))); block_input (); - font_drop_xrender_surfaces (f); x_clear_window (f); /* We have to clear the scroll bars. If we have changed colors or something like that, then they should be notified. */ x_scroll_bar_clear (f); - - XFlush (FRAME_X_DISPLAY (f)); - unblock_input (); } @@ -10147,7 +11124,6 @@ x_scroll_run (struct window *w, struct run *run) view->clip_bottom - view->clip_top); } xwidget_expose (view); - XFlush (dpy); } } } @@ -10237,6 +11213,10 @@ x_scroll_run (struct window *w, struct run *run) static void x_frame_highlight (struct frame *f) { + struct x_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + /* We used to only do this if Vx_no_window_manager was non-nil, but the ICCCM (section 4.1.6) says that the window's border pixmap and border pixel are window attributes which are "private to the @@ -10246,10 +11226,10 @@ x_frame_highlight (struct frame *f) the window-manager in use, tho something more is at play since I've been using that same window-manager binary for ever. Let's not crash just because of this (bug#9310). */ - x_catch_errors (FRAME_X_DISPLAY (f)); + x_ignore_errors_for_next_request (dpyinfo); XSetWindowBorder (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), f->output_data.x->border_pixel); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); unblock_input (); gui_update_cursor (f, true); x_set_frame_alpha (f); @@ -10258,17 +11238,23 @@ x_frame_highlight (struct frame *f) static void x_frame_unhighlight (struct frame *f) { + struct x_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + /* We used to only do this if Vx_no_window_manager was non-nil, but the ICCCM (section 4.1.6) says that the window's border pixmap and border pixel are window attributes which are "private to the client", so we can always change it to whatever we want. */ + block_input (); /* Same as above for XSetWindowBorder (bug#9310). */ - x_catch_errors (FRAME_X_DISPLAY (f)); + x_ignore_errors_for_next_request (dpyinfo); XSetWindowBorderPixmap (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), f->output_data.x->border_tile); - x_uncatch_errors (); + x_stop_ignoring_errors (dpyinfo); unblock_input (); + gui_update_cursor (f, true); x_set_frame_alpha (f); } @@ -10579,7 +11565,8 @@ x_tooltip_window_to_frame (struct x_display_info *dpyinfo, GdkWindow *tooltip_window; #endif - *unrelated_tooltip_p = false; + if (unrelated_tooltip_p) + *unrelated_tooltip_p = false; FOR_EACH_FRAME (tail, frame) { @@ -10608,14 +11595,16 @@ x_tooltip_window_to_frame (struct x_display_info *dpyinfo, if (tooltip_window && (gdk_x11_window_get_xid (tooltip_window) == wdesc)) { - *unrelated_tooltip_p = true; + if (unrelated_tooltip_p) + *unrelated_tooltip_p = true; break; } #else if (tooltip_window && (GDK_WINDOW_XID (tooltip_window) == wdesc)) { - *unrelated_tooltip_p = true; + if (unrelated_tooltip_p) + *unrelated_tooltip_p = true; break; } #endif @@ -10867,7 +11856,7 @@ x_push_selection_request (struct selection_input_event *se) bool x_detect_pending_selection_requests (void) { - return pending_selection_requests; + return !!pending_selection_requests; } static void @@ -10876,6 +11865,95 @@ x_clear_dnd_action (void) x_dnd_action_symbol = Qnil; } +/* Delete action descriptions from F after drag-and-drop. */ +static void +x_dnd_delete_action_list (Lisp_Object frame) +{ + struct frame *f; + + /* Delete those two properties, since some clients look at them and + not the action to decide whether or not the user should be + prompted to select an action. This can be called with FRAME no + longer alive (or its display dead). */ + + f = XFRAME (frame); + + if (!FRAME_LIVE_P (f) || !FRAME_DISPLAY_INFO (f)->display) + return; + + block_input (); + XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), + FRAME_DISPLAY_INFO (f)->Xatom_XdndActionList); + XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), + FRAME_DISPLAY_INFO (f)->Xatom_XdndActionDescription); + unblock_input (); +} + +static void +x_dnd_lose_ownership (Lisp_Object timestamp_and_frame) +{ + struct frame *f; + + f = XFRAME (XCDR (timestamp_and_frame)); + + if (FRAME_LIVE_P (f)) + Fx_disown_selection_internal (QXdndSelection, + XCAR (timestamp_and_frame), + XCDR (timestamp_and_frame)); +} + +/* Clean up an existing drag-and-drop operation in preparation for its + sudden termination. */ + +static void +x_dnd_process_quit (struct frame *f, Time timestamp) +{ + xm_drop_start_message dmsg; + + if (x_dnd_in_progress) + { + if (x_dnd_last_seen_window != None + && x_dnd_last_protocol_version != -1) + x_dnd_send_leave (f, x_dnd_last_seen_window); + else if (x_dnd_last_seen_window != None + && !XM_DRAG_STYLE_IS_DROP_ONLY (x_dnd_last_motif_style) + && x_dnd_last_motif_style != XM_DRAG_STYLE_NONE + && x_dnd_motif_setup_p) + { + dmsg.reason = XM_DRAG_REASON (XM_DRAG_ORIGINATOR_INITIATOR, + XM_DRAG_REASON_DROP_START); + dmsg.byte_order = XM_BYTE_ORDER_CUR_FIRST; + dmsg.timestamp = timestamp; + dmsg.side_effects + = XM_DRAG_SIDE_EFFECT (xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), + x_dnd_wanted_action), + XM_DROP_SITE_VALID, x_dnd_motif_operations, + XM_DROP_ACTION_DROP_CANCEL); + dmsg.x = 0; + dmsg.y = 0; + dmsg.index_atom = x_dnd_motif_atom; + dmsg.source_window = FRAME_X_WINDOW (f); + + x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, + x_dnd_last_seen_window, + timestamp); + xm_send_drop_message (FRAME_DISPLAY_INFO (f), FRAME_X_WINDOW (f), + x_dnd_last_seen_window, &dmsg); + } + + x_dnd_end_window = x_dnd_last_seen_window; + x_dnd_last_seen_window = None; + x_dnd_last_seen_toplevel = None; + x_dnd_in_progress = false; + x_dnd_frame = NULL; + } + + x_dnd_waiting_for_finish = false; + x_dnd_return_frame_object = NULL; + x_dnd_movement_frame = NULL; + x_dnd_wheel_frame = NULL; +} + /* This function is defined far away from the rest of the XDND code so it can utilize `x_any_window_to_frame'. */ @@ -10908,11 +11986,10 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, XWindowAttributes root_window_attrs; struct input_event hold_quit; char *atom_name, *ask_actions; - Lisp_Object action, ltimestamp; + Lisp_Object action, ltimestamp, val; specpdl_ref ref, count, base; ptrdiff_t i, end, fill; XTextProperty prop; - xm_drop_start_message dmsg; Lisp_Object frame_object, x, y, frame, local_value; bool signals_were_pending, need_sync; #ifdef HAVE_XKB @@ -10921,9 +11998,10 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, #ifndef USE_GTK struct x_display_info *event_display; #endif - union buffered_input_event *events, *event; - int n_events; - struct frame *event_frame; + unsigned int additional_mask; +#ifdef HAVE_XINPUT2 + struct xi_device_t *device; +#endif base = SPECPDL_INDEX (); @@ -10931,66 +12009,6 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, Fx_begin_drag. */ specbind (Qx_dnd_targets_list, selection_target_list); - /* Before starting drag-and-drop, walk through the keyboard buffer - to see if there are any UNSUPPORTED_DROP_EVENTs, and run them now - if they exist, to prevent race conditions from happening due to - multiple unsupported drops running at once. */ - - block_input (); - events = alloca (sizeof *events * KBD_BUFFER_SIZE); - n_events = 0; - event = kbd_fetch_ptr; - - while (event != kbd_store_ptr) - { - if (event->ie.kind == UNSUPPORTED_DROP_EVENT - && event->ie.modifiers < x_dnd_unsupported_event_level) - events[n_events++] = *event; - - event = (event == kbd_buffer + KBD_BUFFER_SIZE - 1 - ? kbd_buffer : event + 1); - } - - x_dnd_unsupported_event_level += 1; - unblock_input (); - - for (i = 0; i < n_events; ++i) - { - maybe_quit (); - - event = &events[i]; - event_frame = XFRAME (event->ie.frame_or_window); - - if (!FRAME_LIVE_P (event_frame)) - continue; - - if (!NILP (Vx_dnd_unsupported_drop_function)) - { - if (!NILP (call7 (Vx_dnd_unsupported_drop_function, - XCAR (XCDR (event->ie.arg)), event->ie.x, - event->ie.y, XCAR (XCDR (XCDR (event->ie.arg))), - make_uint (event->ie.code), - event->ie.frame_or_window, - make_int (event->ie.timestamp)))) - continue; - } - - /* `x-dnd-unsupported-drop-function' could have deleted the - event frame. */ - if (!FRAME_LIVE_P (event_frame)) - continue; - - x_dnd_do_unsupported_drop (FRAME_DISPLAY_INFO (event_frame), - event->ie.frame_or_window, - XCAR (event->ie.arg), - XCAR (XCDR (event->ie.arg)), - (Window) event->ie.code, - XFIXNUM (event->ie.x), - XFIXNUM (event->ie.y), - event->ie.timestamp); - break; - } - if (!FRAME_VISIBLE_P (f)) error ("Frame must be visible"); @@ -11006,12 +12024,6 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, /* If local_value is nil, then we lost ownership of XdndSelection. Signal a more informative error than args-out-of-range. */ if (NILP (local_value)) - error ("Lost ownership of XdndSelection"); - - if (CONSP (local_value)) - x_own_selection (QXdndSelection, - Fnth (make_fixnum (1), local_value), frame); - else error ("No local value for XdndSelection"); if (popup_activated ()) @@ -11024,11 +12036,22 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, ltimestamp = x_timestamp_for_selection (FRAME_DISPLAY_INFO (f), QXdndSelection); + if (NILP (ltimestamp)) + error ("No local value for XdndSelection"); + if (BIGNUMP (ltimestamp)) x_dnd_selection_timestamp = bignum_to_intmax (ltimestamp); else x_dnd_selection_timestamp = XFIXNUM (ltimestamp); + /* Release ownership of XdndSelection after this function returns. + VirtualBox uses the owner of XdndSelection to determine whether + or not mouse motion is part of a drag-and-drop operation. */ + + if (!x_dnd_preserve_selection_data) + record_unwind_protect (x_dnd_lose_ownership, + Fcons (ltimestamp, frame)); + x_dnd_motif_operations = xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), xaction); @@ -11044,6 +12067,8 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, = xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), ask_action_list[0]); + record_unwind_protect (x_dnd_delete_action_list, frame); + ask_actions = NULL; end = 0; count = SPECPDL_INDEX (); @@ -11088,19 +12113,8 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, unbind_to (count, Qnil); } - else - { - /* Delete those two properties, since some clients look at them - and not the action to decide whether or not the user should - be prompted to select an action. */ - block_input (); - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndActionList); - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndActionDescription); - unblock_input (); - } + record_unwind_protect_void (x_clear_dnd_variables); if (follow_tooltip) { @@ -11112,8 +12126,6 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, #endif x_dnd_monitors = Fx_display_monitor_attributes_list (frame); - - record_unwind_protect_void (x_clear_dnd_monitors); } x_dnd_update_tooltip = follow_tooltip; @@ -11129,6 +12141,16 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, record_unwind_protect_void (release_xg_select); #endif + /* Set up a meaningless alias. */ + XSETCAR (x_dnd_selection_alias_cell, QSECONDARY); + XSETCDR (x_dnd_selection_alias_cell, QSECONDARY); + + /* Bind this here. The cell doesn't actually alias between + anything until `xm_setup_dnd_targets' is called. */ + specbind (Qx_selection_alias_alist, + Fcons (x_dnd_selection_alias_cell, + Vx_selection_alias_alist)); + /* Initialize most of the state for the drag-and-drop operation. */ x_dnd_in_progress = true; x_dnd_recursion_depth = command_loop_level + minibuf_level; @@ -11145,15 +12167,53 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, x_dnd_return_frame = 0; x_dnd_waiting_for_finish = false; x_dnd_waiting_for_motif_finish = 0; + x_dnd_waiting_for_status_window = None; + x_dnd_pending_send_position.type = 0; x_dnd_xm_use_help = false; x_dnd_motif_setup_p = false; x_dnd_end_window = None; + x_dnd_run_unsupported_drop_function = false; x_dnd_use_toplevels = x_wm_supports (f, FRAME_DISPLAY_INFO (f)->Xatom_net_client_list_stacking); + x_dnd_last_tooltip_valid = false; x_dnd_toplevels = NULL; x_dnd_allow_current_frame = allow_current_frame; x_dnd_movement_frame = NULL; + x_dnd_wheel_frame = NULL; x_dnd_init_type_lists = false; + x_dnd_need_send_drop = false; + +#ifdef HAVE_XINPUT2 + + if (FRAME_DISPLAY_INFO (f)->supports_xi2) + { + /* Only accept input from the last master pointer to have interacted + with Emacs. This prevents another pointer device getting our + idea of the button state messed up. */ + if (FRAME_DISPLAY_INFO (f)->client_pointer_device != -1) + x_dnd_pointer_device + = FRAME_DISPLAY_INFO (f)->client_pointer_device; + else + /* This returns Bool but cannot actually fail. */ + XIGetClientPointer (FRAME_X_DISPLAY (f), None, + &x_dnd_pointer_device); + + x_dnd_keyboard_device = -1; + + device = xi_device_from_id (FRAME_DISPLAY_INFO (f), + x_dnd_pointer_device); + + if (device) + x_dnd_keyboard_device = device->attachment; + } + else + { + x_dnd_pointer_device = -1; + x_dnd_keyboard_device = -1; + } + +#endif + #ifdef HAVE_XKB x_dnd_keyboard_state = 0; @@ -11186,7 +12246,7 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, if (EQ (return_frame, Qnow)) x_dnd_return_frame = 2; - /* Now select for SubstructureNotifyMask and PropertyNotifyMask on + /* Now select for SubstructureNotifyMask and PropertyChangeMask on the root window, so we can get notified when window stacking changes, a common operation during drag-and-drop. */ @@ -11194,11 +12254,15 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, FRAME_DISPLAY_INFO (f)->root_window, &root_window_attrs); + additional_mask = SubstructureNotifyMask; + + if (x_dnd_use_toplevels) + additional_mask |= PropertyChangeMask; + XSelectInput (FRAME_X_DISPLAY (f), FRAME_DISPLAY_INFO (f)->root_window, root_window_attrs.your_event_mask - | SubstructureNotifyMask - | PropertyChangeMask); + | additional_mask); if (EQ (return_frame, Qnow)) x_dnd_update_state (FRAME_DISPLAY_INFO (f), CurrentTime); @@ -11259,6 +12323,9 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, #endif x_dnd_inside_handle_one_xevent = false; + /* Clean up any event handlers that are now out of date. */ + x_clean_failable_requests (FRAME_DISPLAY_INFO (f)); + /* The unblock_input below might try to read input, but XTread_socket does nothing inside a drag-and-drop event loop, so don't let it clear the pending_signals flag. */ @@ -11306,77 +12373,59 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, Fposn_at_x_y (x, y, frame_object, Qnil)); x_dnd_unwind_flag = false; unbind_to (ref, Qnil); + + /* Redisplay this way to preserve the echo area. + Otherwise, the contents will abruptly disappear + when the mouse moves over a frame. */ + redisplay_preserve_echo_area (33); } } - if (hold_quit.kind != NO_EVENT) + if (x_dnd_wheel_frame + && (x_dnd_in_progress || x_dnd_waiting_for_finish)) { - if (x_dnd_in_progress) - { - if (x_dnd_last_seen_window != None - && x_dnd_last_protocol_version != -1) - x_dnd_send_leave (f, x_dnd_last_seen_window); - else if (x_dnd_last_seen_window != None - && !XM_DRAG_STYLE_IS_DROP_ONLY (x_dnd_last_motif_style) - && x_dnd_last_motif_style != XM_DRAG_STYLE_NONE - && x_dnd_motif_setup_p) - { - dmsg.reason = XM_DRAG_REASON (XM_DRAG_ORIGINATOR_INITIATOR, - XM_DRAG_REASON_DROP_START); - dmsg.byte_order = XM_BYTE_ORDER_CUR_FIRST; - dmsg.timestamp = hold_quit.timestamp; - dmsg.side_effects - = XM_DRAG_SIDE_EFFECT (xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), - x_dnd_wanted_action), - XM_DROP_SITE_VALID, x_dnd_motif_operations, - XM_DROP_ACTION_DROP_CANCEL); - dmsg.x = 0; - dmsg.y = 0; - dmsg.index_atom = FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection; - dmsg.source_window = FRAME_X_WINDOW (f); - - x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, - x_dnd_last_seen_window, - hold_quit.timestamp); - xm_send_drop_message (FRAME_DISPLAY_INFO (f), FRAME_X_WINDOW (f), - x_dnd_last_seen_window, &dmsg); - } + XSETFRAME (frame_object, x_dnd_wheel_frame); + XSETINT (x, x_dnd_wheel_x); + XSETINT (y, x_dnd_wheel_y); + x_dnd_wheel_frame = NULL; - x_dnd_end_window = x_dnd_last_seen_window; - x_dnd_last_seen_window = None; - x_dnd_last_seen_toplevel = None; - x_dnd_in_progress = false; - x_dnd_frame = NULL; - } + if (!NILP (Vx_dnd_wheel_function) + && FRAME_LIVE_P (XFRAME (frame_object)) + && !FRAME_TOOLTIP_P (XFRAME (frame_object)) + && x_dnd_movement_x >= 0 + && x_dnd_movement_y >= 0 + && x_dnd_frame + && (XFRAME (frame_object) != x_dnd_frame + || x_dnd_allow_current_frame)) + { + x_dnd_old_window_attrs = root_window_attrs; + x_dnd_unwind_flag = true; - x_dnd_waiting_for_finish = false; - x_dnd_return_frame_object = NULL; - x_dnd_movement_frame = NULL; + ref = SPECPDL_INDEX (); + record_unwind_protect_ptr (x_dnd_cleanup_drag_and_drop, f); + call4 (Vx_dnd_wheel_function, + Fposn_at_x_y (x, y, frame_object, Qnil), + make_fixnum (x_dnd_wheel_button), + make_uint (x_dnd_wheel_state), + make_uint (x_dnd_wheel_time)); + x_dnd_unwind_flag = false; + unbind_to (ref, Qnil); - /* Don't clear dpyinfo->grabbed if we're quitting. */ + /* Redisplay this way to preserve the echo area. + Otherwise, the contents will abruptly disappear + when the mouse moves over a frame. */ + redisplay_preserve_echo_area (33); + } + } + if (hold_quit.kind != NO_EVENT) + { + x_dnd_process_quit (f, hold_quit.timestamp); #ifdef USE_GTK current_hold_quit = NULL; #endif /* Restore the old event mask. */ - XSelectInput (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, - root_window_attrs.your_event_mask); -#ifdef HAVE_XKB - if (FRAME_DISPLAY_INFO (f)->supports_xkb) - XkbSelectEvents (FRAME_X_DISPLAY (f), XkbUseCoreKbd, - XkbStateNotifyMask, 0); -#endif - /* Delete the Motif drag initiator info if it was set up. */ - if (x_dnd_motif_setup_p) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection); - - - /* Remove any type list set as well. */ - if (x_dnd_init_type_lists && x_dnd_n_targets > 3) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndTypeList); + x_restore_events_after_dnd (f, &root_window_attrs); /* Call kbd_buffer_store event, which calls handle_interrupt and sets `last-event-frame' along @@ -11399,78 +12448,70 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, unbind_to (ref, Qnil); } -#ifdef USE_GTK - if (xg_pending_quit_event.kind != NO_EVENT) + /* Sometimes C-g can be pressed inside a selection + converter, where quitting is inhibited. We want + to quit after the converter exits. */ + if (!NILP (Vquit_flag) && !NILP (Vinhibit_quit)) { - xg_pending_quit_event.kind = NO_EVENT; - - if (x_dnd_in_progress) - { - if (x_dnd_last_seen_window != None - && x_dnd_last_protocol_version != -1) - x_dnd_send_leave (f, x_dnd_last_seen_window); - else if (x_dnd_last_seen_window != None - && !XM_DRAG_STYLE_IS_DROP_ONLY (x_dnd_last_motif_style) - && x_dnd_last_motif_style != XM_DRAG_STYLE_NONE - && x_dnd_motif_setup_p) - { - dmsg.reason = XM_DRAG_REASON (XM_DRAG_ORIGINATOR_INITIATOR, - XM_DRAG_REASON_DROP_START); - dmsg.byte_order = XM_BYTE_ORDER_CUR_FIRST; - dmsg.timestamp = xg_pending_quit_event.timestamp; - dmsg.side_effects - = XM_DRAG_SIDE_EFFECT (xm_side_effect_from_action (FRAME_DISPLAY_INFO (f), - x_dnd_wanted_action), - XM_DROP_SITE_VALID, x_dnd_motif_operations, - XM_DROP_ACTION_DROP_CANCEL); - dmsg.x = 0; - dmsg.y = 0; - dmsg.index_atom = FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection; - dmsg.source_window = FRAME_X_WINDOW (f); - - x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, - x_dnd_last_seen_window, - xg_pending_quit_event.timestamp); - xm_send_drop_message (FRAME_DISPLAY_INFO (f), FRAME_X_WINDOW (f), - x_dnd_last_seen_window, &dmsg); - } - - x_dnd_end_window = x_dnd_last_seen_window; - x_dnd_last_seen_window = None; - x_dnd_last_seen_toplevel = None; - x_dnd_in_progress = false; - x_dnd_frame = NULL; - } + x_dnd_process_quit (f, FRAME_DISPLAY_INFO (f)->last_user_time); +#ifdef USE_GTK + current_hold_quit = NULL; +#endif + x_restore_events_after_dnd (f, &root_window_attrs); + quit (); + } + if (x_dnd_run_unsupported_drop_function + && x_dnd_waiting_for_finish) + { + x_dnd_run_unsupported_drop_function = false; x_dnd_waiting_for_finish = false; - x_dnd_return_frame_object = NULL; - x_dnd_movement_frame = NULL; + x_dnd_unwind_flag = true; - FRAME_DISPLAY_INFO (f)->grabbed = 0; - current_hold_quit = NULL; + ref = SPECPDL_INDEX (); + record_unwind_protect_ptr (x_dnd_cleanup_drag_and_drop, f); - block_input (); - /* Restore the old event mask. */ - XSelectInput (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, - root_window_attrs.your_event_mask); -#ifdef HAVE_XKB - if (FRAME_DISPLAY_INFO (f)->supports_xkb) - XkbSelectEvents (FRAME_X_DISPLAY (f), XkbUseCoreKbd, - XkbStateNotifyMask, 0); -#endif - /* Delete the Motif drag initiator info if it was set up. */ - if (x_dnd_motif_setup_p) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection); + if (!NILP (Vx_dnd_unsupported_drop_function)) + val = call8 (Vx_dnd_unsupported_drop_function, + XCAR (XCDR (x_dnd_unsupported_drop_data)), + Fnth (make_fixnum (3), x_dnd_unsupported_drop_data), + Fnth (make_fixnum (4), x_dnd_unsupported_drop_data), + Fnth (make_fixnum (2), x_dnd_unsupported_drop_data), + make_uint (x_dnd_unsupported_drop_window), + frame, make_uint (x_dnd_unsupported_drop_time), + Fcopy_sequence (XCAR (x_dnd_unsupported_drop_data))); + else + val = Qnil; + + if (NILP (val)) + x_dnd_do_unsupported_drop (FRAME_DISPLAY_INFO (f), + frame, XCAR (x_dnd_unsupported_drop_data), + XCAR (XCDR (x_dnd_unsupported_drop_data)), + x_dnd_unsupported_drop_window, + XFIXNUM (Fnth (make_fixnum (3), + x_dnd_unsupported_drop_data)), + XFIXNUM (Fnth (make_fixnum (4), + x_dnd_unsupported_drop_data)), + x_dnd_unsupported_drop_time); + else if (SYMBOLP (val)) + x_dnd_action_symbol = val; + x_dnd_unwind_flag = false; + unbind_to (ref, Qnil); - /* Remove any type list set as well. */ - if (x_dnd_init_type_lists && x_dnd_n_targets > 3) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndTypeList); - unblock_input (); + /* Break out of the loop now, since DND has + completed. */ + break; + } + +#ifdef USE_GTK + if (xg_pending_quit_event.kind != NO_EVENT) + { + xg_pending_quit_event.kind = NO_EVENT; + current_hold_quit = NULL; + x_dnd_process_quit (f, FRAME_DISPLAY_INFO (f)->last_user_time); + x_restore_events_after_dnd (f, &root_window_attrs); quit (); } #else @@ -11480,6 +12521,9 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, if (x_dnd_movement_frame) x_dnd_movement_frame = NULL; + if (x_dnd_wheel_frame) + x_dnd_wheel_frame = NULL; + if (hold_quit.kind != NO_EVENT) EVENT_INIT (hold_quit); } @@ -11492,27 +12536,8 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, current_hold_quit = NULL; #endif x_dnd_movement_frame = NULL; - - block_input (); - /* Restore the old event mask. */ - XSelectInput (FRAME_X_DISPLAY (f), - FRAME_DISPLAY_INFO (f)->root_window, - root_window_attrs.your_event_mask); -#ifdef HAVE_XKB - if (FRAME_DISPLAY_INFO (f)->supports_xkb) - XkbSelectEvents (FRAME_X_DISPLAY (f), XkbUseCoreKbd, - XkbStateNotifyMask, 0); -#endif - /* Delete the Motif drag initiator info if it was set up. */ - if (x_dnd_motif_setup_p) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection); - - /* Remove any type list set as well. */ - if (x_dnd_init_type_lists && x_dnd_n_targets > 3) - XDeleteProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), - FRAME_DISPLAY_INFO (f)->Xatom_XdndTypeList); - unblock_input (); + x_dnd_wheel_frame = NULL; + x_restore_events_after_dnd (f, &root_window_attrs); if (x_dnd_return_frame == 3 && FRAME_LIVE_P (x_dnd_return_frame_object)) @@ -11566,6 +12591,503 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, return unbind_to (base, Qnil); } +#ifdef HAVE_XINPUT2 + +/* Since the input extension assigns a keyboard focus to each master + device, there is no longer a 1:1 correspondence between the + selected frame and the focus frame immediately after the keyboard + focus is switched to a given frame. This situation is handled by + keeping track of each master device's focus frame, the time of the + last interaction with that frame, and always keeping the focus on + the most recently selected frame. We also use the pointer of the + device that is keeping the current frame focused in functions like + `mouse-position'. */ + +static void +xi_handle_focus_change (struct x_display_info *dpyinfo) +{ + struct input_event ie; + struct frame *focus, *new; + struct xi_device_t *device, *source = NULL; + ptrdiff_t i; + Time time; +#ifdef USE_GTK + struct x_output *output; + GtkWidget *widget; +#endif + + focus = dpyinfo->x_focus_frame; + new = NULL; + time = 0; + + dpyinfo->client_pointer_device = -1; + + for (i = 0; i < dpyinfo->num_devices; ++i) + { + device = &dpyinfo->devices[i]; + + if (device->focus_frame + && device->focus_frame_time > time) + { + new = device->focus_frame; + time = device->focus_frame_time; + source = device; + + /* Use this device for future calls to `mouse-position' etc. + If it is a keyboard, use its attached pointer. */ + + if (device->use == XIMasterKeyboard) + dpyinfo->client_pointer_device = device->attachment; + else + dpyinfo->client_pointer_device = device->device_id; + } + + if (device->focus_implicit_frame + && device->focus_implicit_time > time) + { + new = device->focus_implicit_frame; + time = device->focus_implicit_time; + source = device; + + /* Use this device for future calls to `mouse-position' etc. + If it is a keyboard, use its attached pointer. */ + + if (device->use == XIMasterKeyboard) + dpyinfo->client_pointer_device = device->attachment; + else + dpyinfo->client_pointer_device = device->device_id; + } + } + + if (new != focus && focus) + { +#ifdef HAVE_X_I18N + if (FRAME_XIC (focus)) + XUnsetICFocus (FRAME_XIC (focus)); +#endif + +#ifdef USE_GTK + output = FRAME_X_OUTPUT (focus); + + if (x_gtk_use_native_input) + { + gtk_im_context_focus_out (output->im_context); + gtk_im_context_set_client_window (output->im_context, + NULL); + } +#endif + + EVENT_INIT (ie); + ie.kind = FOCUS_OUT_EVENT; + XSETFRAME (ie.frame_or_window, focus); + + kbd_buffer_store_event (&ie); + } + + if (new != focus && new) + { +#ifdef HAVE_X_I18N + if (FRAME_XIC (new)) + XSetICFocus (FRAME_XIC (new)); +#endif + +#ifdef USE_GTK + output = FRAME_X_OUTPUT (new); + + if (x_gtk_use_native_input) + { + widget = FRAME_GTK_OUTER_WIDGET (new); + + gtk_im_context_focus_in (output->im_context); + gtk_im_context_set_client_window (output->im_context, + gtk_widget_get_window (widget)); + } +#endif + + EVENT_INIT (ie); + ie.kind = FOCUS_IN_EVENT; + ie.device = source->name; + XSETFRAME (ie.frame_or_window, new); + + kbd_buffer_store_event (&ie); + } + + x_new_focus_frame (dpyinfo, new); +} + +static void +xi_focus_handle_for_device (struct x_display_info *dpyinfo, + struct frame *mentioned_frame, + XIEvent *base_event) +{ + struct xi_device_t *device; + XIEnterEvent *event; + + /* XILeaveEvent, XIFocusInEvent, etc are just synonyms for + XIEnterEvent. */ + event = (XIEnterEvent *) base_event; + device = xi_device_from_id (dpyinfo, event->deviceid); + + if (!device) + return; + + switch (event->evtype) + { + case XI_FocusIn: + device->focus_frame = mentioned_frame; + device->focus_frame_time = event->time; + break; + + case XI_FocusOut: + device->focus_frame = NULL; + break; + + case XI_Enter: + if (!event->focus) + break; + + if (device->use == XIMasterPointer) + device = xi_device_from_id (dpyinfo, device->attachment); + + if (!device) + break; + + device->focus_implicit_frame = mentioned_frame; + device->focus_implicit_time = event->time; + break; + + case XI_Leave: + if (!event->focus) + break; + + if (device->use == XIMasterPointer) + device = xi_device_from_id (dpyinfo, device->attachment); + + if (!device) + break; + + device->focus_implicit_frame = NULL; + break; + } + + xi_handle_focus_change (dpyinfo); +} + +static void +xi_handle_delete_frame (struct x_display_info *dpyinfo, + struct frame *f) +{ + struct xi_device_t *device; + ptrdiff_t i; + + for (i = 0; i < dpyinfo->num_devices; ++i) + { + device = &dpyinfo->devices[i]; + + if (device->focus_frame == f) + device->focus_frame = NULL; + + if (device->focus_implicit_frame == f) + device->focus_implicit_frame = NULL; + } +} + +/* Handle an interaction by DEVICE on frame F. TIME is the time of + the interaction; if F isn't currently the global focus frame, but + is the focus of DEVICE, make it the focus frame. */ + +static void +xi_handle_interaction (struct x_display_info *dpyinfo, + struct frame *f, struct xi_device_t *device, + Time time) +{ + bool change; + + /* If DEVICE is a pointer, use its attached keyboard device. */ + if (device->use == XIMasterPointer) + device = xi_device_from_id (dpyinfo, device->attachment); + + if (!device) + return; + + change = false; + + if (device->focus_frame == f) + { + device->focus_frame_time = time; + change = true; + } + + if (device->focus_implicit_frame == f) + { + device->focus_implicit_time = time; + change = true; + } + + /* If F isn't currently focused, update the focus state. */ + if (change && f != dpyinfo->x_focus_frame) + xi_handle_focus_change (dpyinfo); +} + +/* Return whether or not XEV actually represents a change in the + position of the pointer on DEVICE, with respect to the last event + received. This is necessary because the input extension reports + motion events in very high resolution, while Emacs is only fast + enough to process motion events aligned to the pixel grid. */ + +static bool +xi_position_changed (struct xi_device_t *device, XIDeviceEvent *xev) +{ + bool changed; + + changed = true; + + if (xev->event != device->last_motion_window) + goto out; + + if (lrint (xev->event_x) == device->last_motion_x + && lrint (xev->event_y) == device->last_motion_y) + { + changed = false; + goto out; + } + + out: + device->last_motion_x = lrint (xev->event_x); + device->last_motion_y = lrint (xev->event_y); + device->last_motion_window = xev->event; + + return changed; +} + +static void +xi_report_motion_window_clear (struct xi_device_t *device) +{ + device->last_motion_window = None; +} + +#ifdef HAVE_XINPUT2_1 + +/* Look up a scroll valuator in DEVICE by NUMBER. */ + +static struct xi_scroll_valuator_t * +xi_get_scroll_valuator (struct xi_device_t *device, int number) +{ + int i; + + for (i = 0; i < device->scroll_valuator_count; ++i) + { + if (device->valuators[i].number == number) + return &device->valuators[i]; + } + + return NULL; +} + +#endif + +/* Handle EVENT, a DeviceChanged event. Look up the device that + changed, and update its information with the data in EVENT. */ + +static void +xi_handle_device_changed (struct x_display_info *dpyinfo, + struct xi_device_t *device, + XIDeviceChangedEvent *event) +{ +#ifdef HAVE_XINPUT2_1 + XIDeviceInfo *info; + XIScrollClassInfo *scroll; + int i, ndevices; + struct xi_scroll_valuator_t *valuator; + XIValuatorClassInfo *valuator_info; +#endif +#ifdef HAVE_XINPUT2_2 + struct xi_touch_point_t *tem, *last; + XITouchClassInfo *touch; +#endif + +#ifdef HAVE_XINPUT2_1 + /* When a DeviceChange event is received for a master device, we + don't get any scroll valuators along with it. This is possibly + an X server bug but I really don't want to dig any further, so + fetch the scroll valuators manually. (bug#57020) */ + + x_catch_errors (dpyinfo->display); + info = XIQueryDevice (dpyinfo->display, event->deviceid, + /* ndevices is always 1 if a deviceid is + specified. If the request fails, NULL will + be returned. */ + &ndevices); + x_uncatch_errors (); + + if (info) + { + device->valuators = xrealloc (device->valuators, + (info->num_classes + * sizeof *device->valuators)); + device->scroll_valuator_count = 0; +#ifdef HAVE_XINPUT2_2 + device->direct_p = false; +#endif + + for (i = 0; i < info->num_classes; ++i) + { + switch (info->classes[i]->type) + { + case XIScrollClass: + scroll = (XIScrollClassInfo *) info->classes[i]; + + valuator = &device->valuators[device->scroll_valuator_count++]; + valuator->horizontal = (scroll->scroll_type + == XIScrollTypeHorizontal); + valuator->invalid_p = true; + valuator->emacs_value = DBL_MIN; + valuator->increment = scroll->increment; + valuator->number = scroll->number; + break; + +#ifdef HAVE_XINPUT2_2 + case XITouchClass: + touch = (XITouchClassInfo *) info->classes[i]; + + if (touch->mode == XIDirectTouch) + device->direct_p = true; + break; +#endif + } + } + + /* Restore the values of any scroll valuators that we already + know about. */ + + for (i = 0; i < info->num_classes; ++i) + { + switch (info->classes[i]->type) + { + case XIValuatorClass: + valuator_info = (XIValuatorClassInfo *) info->classes[i]; + + valuator = xi_get_scroll_valuator (device, + valuator_info->number); + if (valuator) + { + valuator->invalid_p = false; + valuator->current_value = valuator_info->value; + + /* Make sure that this is reset if the pointer moves + into a window of ours. + + Otherwise the valuator state could be left + invalid if the DeviceChange event happened with + the pointer outside any Emacs frame. */ + valuator->pending_enter_reset = true; + } + + break; + } + } + +#ifdef HAVE_XINPUT2_2 + /* The device is no longer a DirectTouch device, so + remove any touchpoints that we might have + recorded. */ + if (!device->direct_p) + { + tem = device->touchpoints; + + while (tem) + { + last = tem; + tem = tem->next; + xfree (last); + } + + device->touchpoints = NULL; + } +#endif + + XIFreeDeviceInfo (info); + } +#endif +} + +/* Remove the client-side record of every device in TO_DISABLE. + Called while processing XI_HierarchyChanged events. We batch up + multiple disabled devices because it is more efficient to disable + them at once. */ + +static void +xi_disable_devices (struct x_display_info *dpyinfo, + int *to_disable, int n_disabled) +{ + struct xi_device_t *devices; + int ndevices, i, j; +#ifdef HAVE_XINPUT2_2 + struct xi_touch_point_t *tem, *last; +#endif + + /* Don't pointlessly copy dpyinfo->devices if there are no devices + to disable. */ + if (!n_disabled) + return; + + ndevices = 0; + devices = xzalloc (sizeof *devices * dpyinfo->num_devices); + + /* Loop through every device currently in DPYINFO, and copy it to + DEVICES if it is not in TO_DISABLE. Note that this function + should be called with input blocked, since xfree can otherwise + call GC, which will call mark_xterm with invalid state. */ + for (i = 0; i < dpyinfo->num_devices; ++i) + { + for (j = 0; j < n_disabled; ++j) + { + if (to_disable[j] == dpyinfo->devices[i].device_id) + { + if (x_dnd_in_progress + /* If the drag-and-drop pointer device is being + disabled, then cancel the drag and drop + operation. */ + && to_disable[j] == x_dnd_pointer_device) + x_dnd_cancel_dnd_early (); + + /* Free any scroll valuators that might be on this + device. */ +#ifdef HAVE_XINPUT2_1 + xfree (dpyinfo->devices[i].valuators); +#endif + + /* Free any currently active touch points on this + device. */ +#ifdef HAVE_XINPUT2_2 + tem = dpyinfo->devices[i].touchpoints; + while (tem) + { + last = tem; + tem = tem->next; + xfree (last); + } +#endif + + goto out; + } + + devices[ndevices++] = dpyinfo->devices[i]; + + out: + continue; + } + } + + /* Free the old devices array and replace it with ndevices. */ + xfree (dpyinfo->devices); + + dpyinfo->devices = devices; + dpyinfo->num_devices = ndevices; +} + +#endif + /* The focus may have changed. Figure out if it is a real focus change, by checking both FocusIn/Out and Enter/LeaveNotify events. @@ -11596,37 +13118,6 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame, } break; -#ifdef HAVE_XINPUT2 - case GenericEvent: - { - XIEvent *xi_event = event->xcookie.data; - XIEnterEvent *enter_or_focus = event->xcookie.data; - - struct frame *focus_frame = dpyinfo->x_focus_event_frame; - int focus_state - = focus_frame ? focus_frame->output_data.x->focus_state : 0; - - if (xi_event->evtype == XI_FocusIn - || xi_event->evtype == XI_FocusOut) - x_focus_changed ((xi_event->evtype == XI_FocusIn - ? FocusIn : FocusOut), - ((enter_or_focus->detail - == XINotifyPointer) - ? FOCUS_IMPLICIT : FOCUS_EXPLICIT), - dpyinfo, frame, bufp); - else if ((xi_event->evtype == XI_Enter - || xi_event->evtype == XI_Leave) - && (enter_or_focus->detail != XINotifyInferior) - && enter_or_focus->focus - && !(focus_state & FOCUS_EXPLICIT)) - x_focus_changed ((xi_event->evtype == XI_Enter - ? FocusIn : FocusOut), - FOCUS_IMPLICIT, - dpyinfo, frame, bufp); - break; - } -#endif - case FocusIn: case FocusOut: /* Ignore transient focus events from hotkeys, window manager @@ -11636,8 +13127,8 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame, really has focus, and these kinds of focus event don't correspond to real user input changes. GTK+ uses the same filtering. */ - if (event->xfocus.mode == NotifyGrab || - event->xfocus.mode == NotifyUngrab) + if (event->xfocus.mode == NotifyGrab + || event->xfocus.mode == NotifyUngrab) return; x_focus_changed (event->type, (event->xfocus.detail == NotifyPointer ? @@ -11972,6 +13463,105 @@ get_keysym_name (int keysym) return value; } +static Bool +x_query_pointer_1 (struct x_display_info *dpyinfo, + int client_pointer_device, Window w, + Window *root_return, Window *child_return, + int *root_x_return, int *root_y_return, + int *win_x_return, int *win_y_return, + unsigned int *mask_return) +{ + Bool rc; + Display *dpy; +#ifdef HAVE_XINPUT2 + bool had_errors; + XIModifierState modifiers; + XIButtonState buttons; + XIGroupState group; /* Unused. */ + double root_x, root_y, win_x, win_y; + unsigned int state; +#endif + + dpy = dpyinfo->display; + +#ifdef HAVE_XINPUT2 + if (client_pointer_device != -1) + { + /* Catch errors caused by the device going away. This is not + very expensive, since XIQueryPointer will sync anyway. */ + x_catch_errors (dpy); + rc = XIQueryPointer (dpyinfo->display, + dpyinfo->client_pointer_device, + w, root_return, child_return, + &root_x, &root_y, &win_x, &win_y, + &buttons, &modifiers, &group); + had_errors = x_had_errors_p (dpy); + x_uncatch_errors_after_check (); + + if (had_errors) + { + /* If the specified client pointer is the display's client + pointer, clear it now. A new client pointer might not be + found before the next call to x_query_pointer_1 and + waiting for the error leads to excessive syncing. */ + + if (client_pointer_device == dpyinfo->client_pointer_device) + dpyinfo->client_pointer_device = -1; + + rc = XQueryPointer (dpyinfo->display, w, root_return, + child_return, root_x_return, + root_y_return, win_x_return, + win_y_return, mask_return); + } + else + { + state = 0; + + xi_convert_button_state (&buttons, &state); + *mask_return = state | modifiers.effective; + + XFree (buttons.mask); + + *root_x_return = lrint (root_x); + *root_y_return = lrint (root_y); + *win_x_return = lrint (win_x); + *win_y_return = lrint (win_y); + } + } + else +#endif + rc = XQueryPointer (dpy, w, root_return, child_return, + root_x_return, root_y_return, win_x_return, + win_y_return, mask_return); + + return rc; +} + +Bool +x_query_pointer (Display *dpy, Window w, Window *root_return, + Window *child_return, int *root_x_return, + int *root_y_return, int *win_x_return, + int *win_y_return, unsigned int *mask_return) +{ + struct x_display_info *dpyinfo; + + dpyinfo = x_display_info_for_display (dpy); + + if (!dpyinfo) + emacs_abort (); + +#ifdef HAVE_XINPUT2 + return x_query_pointer_1 (dpyinfo, dpyinfo->client_pointer_device, + w, root_return, child_return, root_x_return, + root_y_return, win_x_return, win_y_return, + mask_return); +#else + return x_query_pointer_1 (dpyinfo, -1, w, root_return, child_return, + root_x_return, root_y_return, win_x_return, + win_y_return, mask_return); +#endif +} + /* Mouse clicks and mouse movement. Rah. Formerly, we used PointerMotionHintMask (in standard_event_mask) @@ -12052,10 +13642,11 @@ x_construct_mouse_click (struct input_event *result, The XMotionEvent structure passed as EVENT might not come from the X server, and instead be artificially constructed from input extension events. In these special events, the only fields that - are initialized are `time', `window', and `x' and `y'. This - function should not access any other fields in EVENT without also - initializing the corresponding fields in `ev' under the XI_Motion, - XI_Enter and XI_Leave labels inside `handle_one_xevent'. */ + are initialized are `time', `window', `send_event', `x' and `y'. + This function should not access any other fields in EVENT without + also initializing the corresponding fields in `ev' under the + XI_Motion, XI_Enter and XI_Leave labels inside + `handle_one_xevent'. */ static bool x_note_mouse_movement (struct frame *frame, const XMotionEvent *event, @@ -12069,6 +13660,7 @@ x_note_mouse_movement (struct frame *frame, const XMotionEvent *event, dpyinfo = FRAME_DISPLAY_INFO (frame); dpyinfo->last_mouse_movement_time = event->time; + dpyinfo->last_mouse_movement_time_send_event = event->send_event; dpyinfo->last_mouse_motion_frame = frame; dpyinfo->last_mouse_motion_x = event->x; dpyinfo->last_mouse_motion_y = event->y; @@ -12236,20 +13828,20 @@ XTmouse_position (struct frame **fp, int insist, Lisp_Object *bar_window, dpyinfo->last_mouse_scroll_bar = NULL; /* Figure out which root window we're on. */ - XQueryPointer (FRAME_X_DISPLAY (*fp), - DefaultRootWindow (FRAME_X_DISPLAY (*fp)), - /* The root window which contains the pointer. */ - &root, - /* Trash which we can't trust if the pointer is on - a different screen. */ - &dummy_window, - /* The position on that root window. */ - &root_x, &root_y, - /* More trash we can't trust. */ - &dummy, &dummy, - /* Modifier keys and pointer buttons, about which - we don't care. */ - (unsigned int *) &dummy); + x_query_pointer (FRAME_X_DISPLAY (*fp), + DefaultRootWindow (FRAME_X_DISPLAY (*fp)), + /* The root window which contains the pointer. */ + &root, + /* Trash which we can't trust if the pointer is on + a different screen. */ + &dummy_window, + /* The position on that root window. */ + &root_x, &root_y, + /* More trash we can't trust. */ + &dummy, &dummy, + /* Modifier keys and pointer buttons, about which + we don't care. */ + (unsigned int *) &dummy); /* Now we have a position on the root; find the innermost window containing the pointer. */ @@ -12384,7 +13976,8 @@ XTmouse_position (struct frame **fp, int insist, Lisp_Object *bar_window, && (dpyinfo->last_user_time < dpyinfo->last_mouse_movement_time)) x_display_set_last_user_time (dpyinfo, - dpyinfo->last_mouse_movement_time); + dpyinfo->last_mouse_movement_time, + dpyinfo->last_mouse_movement_time_send_event); if ((!f1 || FRAME_TOOLTIP_P (f1)) && (EQ (track_mouse, Qdropping) @@ -12483,6 +14076,37 @@ XTmouse_position (struct frame **fp, int insist, Lisp_Object *bar_window, /* Scroll bar support. */ +#if defined HAVE_XINPUT2 + +/* Select for input extension events used by scroll bars. This will + result in the corresponding core events not being generated for + SCROLL_BAR. */ + +MAYBE_UNUSED static void +xi_select_scroll_bar_events (struct x_display_info *dpyinfo, + Window scroll_bar) +{ + XIEventMask mask; + unsigned char *m; + ptrdiff_t length; + + length = XIMaskLen (XI_LASTEVENT); + mask.mask = m = alloca (length); + memset (m, 0, length); + mask.mask_len = length; + + mask.deviceid = XIAllMasterDevices; + XISetMask (m, XI_ButtonPress); + XISetMask (m, XI_ButtonRelease); + XISetMask (m, XI_Motion); + XISetMask (m, XI_Enter); + XISetMask (m, XI_Leave); + + XISelectEvents (dpyinfo->display, scroll_bar, &mask, 1); +} + +#endif + /* Given an X window ID and a DISPLAY, find the struct scroll_bar which manages it. This can be called in GC, so we have to make sure to strip off mark @@ -12564,15 +14188,26 @@ static void x_send_scroll_bar_event (Lisp_Object, enum scroll_bar_part, static Lisp_Object window_being_scrolled; -/* Whether this is an Xaw with arrow-scrollbars. This should imply - that movements of 1/20 of the screen size are mapped to up/down. */ +static Time +x_get_last_toolkit_time (struct x_display_info *dpyinfo) +{ +#ifdef USE_X_TOOLKIT + return XtLastTimestampProcessed (dpyinfo->display); +#else + return dpyinfo->last_user_time; +#endif +} #ifndef USE_GTK -/* Id of action hook installed for scroll bars. */ +/* Id of action hook installed for scroll bars and horizontal scroll + bars. */ static XtActionHookId action_hook_id; static XtActionHookId horizontal_action_hook_id; +/* Whether this is an Xaw with arrow-scrollbars. This should imply + that movements of 1/20 of the screen size are mapped to up/down. */ + static Boolean xaw3d_arrow_scroll; /* Whether the drag scrolling maintains the mouse at the top of the @@ -12735,11 +14370,13 @@ x_send_scroll_bar_event (Lisp_Object window, enum scroll_bar_part part, ev->window = FRAME_X_WINDOW (f); ev->format = 32; - /* A 32-bit X client on a 64-bit X server can pass a window pointer - as-is. A 64-bit client on a 32-bit X server is in trouble - because a pointer does not fit and would be truncated while - passing through the server. So use two slots and hope that X12 - will resolve such issues someday. */ + /* A 32-bit X client can pass a window pointer through the X server + as-is. + + A 64-bit client is in trouble because a pointer does not fit in + the 32 bits given for ClientMessage data and will be truncated by + Xlib. So use two slots and hope that X12 will resolve such + issues someday. */ ev->data.l[0] = iw >> 31 >> 1; ev->data.l[1] = sign_shift <= 0 ? iw : iw << sign_shift >> sign_shift; ev->data.l[2] = part; @@ -12783,12 +14420,8 @@ x_scroll_bar_to_input_event (const XEvent *event, ievent->kind = SCROLL_BAR_CLICK_EVENT; ievent->frame_or_window = window; ievent->arg = Qnil; -#ifdef USE_GTK - ievent->timestamp = CurrentTime; -#else - ievent->timestamp = - XtLastTimestampProcessed (FRAME_X_DISPLAY (XFRAME (w->frame))); -#endif + ievent->timestamp + = x_get_last_toolkit_time (FRAME_DISPLAY_INFO (XFRAME (w->frame))); ievent->code = 0; ievent->part = ev->data.l[2]; ievent->x = make_fixnum (ev->data.l[3]); @@ -12818,12 +14451,8 @@ x_horizontal_scroll_bar_to_input_event (const XEvent *event, ievent->kind = HORIZONTAL_SCROLL_BAR_CLICK_EVENT; ievent->frame_or_window = window; ievent->arg = Qnil; -#ifdef USE_GTK - ievent->timestamp = CurrentTime; -#else - ievent->timestamp = - XtLastTimestampProcessed (FRAME_X_DISPLAY (XFRAME (w->frame))); -#endif + ievent->timestamp + = x_get_last_toolkit_time (FRAME_DISPLAY_INFO (XFRAME (w->frame))); ievent->code = 0; ievent->part = ev->data.l[2]; ievent->x = make_fixnum (ev->data.l[3]); @@ -12927,19 +14556,31 @@ xm_scroll_callback (Widget widget, XtPointer client_data, XtPointer call_data) bar widget. DATA is a pointer to the scroll_bar structure. */ static gboolean -xg_scroll_callback (GtkRange *range, - GtkScrollType scroll, - gdouble value, - gpointer user_data) +xg_scroll_callback (GtkRange *range, GtkScrollType scroll, + gdouble value, gpointer user_data) { - int whole = 0, portion = 0; - struct scroll_bar *bar = user_data; - enum scroll_bar_part part = scroll_bar_nowhere; - GtkAdjustment *adj = GTK_ADJUSTMENT (gtk_range_get_adjustment (range)); - struct frame *f = g_object_get_data (G_OBJECT (range), XG_FRAME_DATA); + int whole, portion; + struct scroll_bar *bar; + enum scroll_bar_part part; + GtkAdjustment *adj; + struct frame *f; + guint32 time; + struct x_display_info *dpyinfo; if (xg_ignore_gtk_scrollbar) return false; + whole = 0; + portion = 0; + bar = user_data; + part = scroll_bar_nowhere; + adj = GTK_ADJUSTMENT (gtk_range_get_adjustment (range)); + f = g_object_get_data (G_OBJECT (range), XG_FRAME_DATA); + time = gtk_get_current_event_time (); + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (time != GDK_CURRENT_TIME) + x_display_set_last_user_time (dpyinfo, time, true); + switch (scroll) { case GTK_SCROLL_JUMP: @@ -13006,8 +14647,11 @@ xg_end_scroll_callback (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { - struct scroll_bar *bar = user_data; + struct scroll_bar *bar; + + bar = user_data; bar->dragging = -1; + if (WINDOWP (window_being_scrolled)) { x_send_scroll_bar_event (window_being_scrolled, @@ -13255,25 +14899,8 @@ x_create_toolkit_scroll_bar (struct frame *f, struct scroll_bar *bar) /* Ask for input extension button and motion events. This lets us send the proper `wheel-up' or `wheel-down' events to Emacs. */ if (FRAME_DISPLAY_INFO (f)->supports_xi2) - { - XIEventMask mask; - ptrdiff_t l = XIMaskLen (XI_LASTEVENT); - unsigned char *m; - - mask.mask = m = alloca (l); - memset (m, 0, l); - mask.mask_len = l; - - mask.deviceid = XIAllMasterDevices; - XISetMask (m, XI_ButtonPress); - XISetMask (m, XI_ButtonRelease); - XISetMask (m, XI_Motion); - XISetMask (m, XI_Enter); - XISetMask (m, XI_Leave); - - XISelectEvents (XtDisplay (widget), XtWindow (widget), - &mask, 1); - } + xi_select_scroll_bar_events (FRAME_DISPLAY_INFO (f), + XtWindow (widget)); #endif #else /* !USE_MOTIF i.e. use Xaw */ @@ -13480,25 +15107,8 @@ x_create_horizontal_toolkit_scroll_bar (struct frame *f, struct scroll_bar *bar) /* Ask for input extension button and motion events. This lets us send the proper `wheel-up' or `wheel-down' events to Emacs. */ if (FRAME_DISPLAY_INFO (f)->supports_xi2) - { - XIEventMask mask; - ptrdiff_t l = XIMaskLen (XI_LASTEVENT); - unsigned char *m; - - mask.mask = m = alloca (l); - memset (m, 0, l); - mask.mask_len = l; - - mask.deviceid = XIAllMasterDevices; - XISetMask (m, XI_ButtonPress); - XISetMask (m, XI_ButtonRelease); - XISetMask (m, XI_Motion); - XISetMask (m, XI_Enter); - XISetMask (m, XI_Leave); - - XISelectEvents (XtDisplay (widget), XtWindow (widget), - &mask, 1); - } + xi_select_scroll_bar_events (FRAME_DISPLAY_INFO (f), + XtWindow (widget)); #endif #else /* !USE_MOTIF i.e. use Xaw */ @@ -13886,20 +15496,12 @@ x_scroll_bar_create (struct window *w, int top, int left, XSetWindowAttributes a; unsigned long mask; Window window; -#ifdef HAVE_XDBE - Drawable drawable; -#endif - - a.background_pixel = f->output_data.x->scroll_bar_background_pixel; - if (a.background_pixel == -1) - a.background_pixel = FRAME_BACKGROUND_PIXEL (f); a.event_mask = (ButtonPressMask | ButtonReleaseMask - | ButtonMotionMask | PointerMotionHintMask - | ExposureMask); + | ButtonMotionMask | PointerMotionHintMask); a.cursor = FRAME_DISPLAY_INFO (f)->vertical_scroll_bar_cursor; - mask = (CWBackPixel | CWEventMask | CWCursor); + mask = (CWEventMask | CWCursor); /* Clear the area of W that will serve as a scroll bar. This is for the case that a window has been split horizontally. In @@ -13907,61 +15509,32 @@ x_scroll_bar_create (struct window *w, int top, int left, if (width > 0 && window_box_height (w) > 0) x_clear_area (f, left, top, width, window_box_height (w)); + /* Create an input only window. Scroll bar contents are drawn to + the frame window itself, so they can be double buffered and + synchronized using the same mechanism as the frame. */ window = XCreateWindow (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), /* Position and size of scroll bar. */ left, top, width, height, - /* Border width, depth, class, and visual. */ + /* Border width. */ 0, + /* Depth. */ CopyFromParent, - CopyFromParent, + /* Class. */ + InputOnly, + /* Visual class. */ CopyFromParent, /* Attributes. */ mask, &a); -#ifdef HAVE_XDBE - if (FRAME_DISPLAY_INFO (f)->supports_xdbe - && FRAME_X_DOUBLE_BUFFERED_P (f)) - { - x_catch_errors (FRAME_X_DISPLAY (f)); - drawable = XdbeAllocateBackBufferName (FRAME_X_DISPLAY (f), - window, XdbeCopied); - if (x_had_errors_p (FRAME_X_DISPLAY (f))) - drawable = window; - else - XSetWindowBackgroundPixmap (FRAME_X_DISPLAY (f), window, None); - x_uncatch_errors_after_check (); - } - else - drawable = window; -#endif #ifdef HAVE_XINPUT2 /* Ask for input extension button and motion events. This lets us send the proper `wheel-up' or `wheel-down' events to Emacs. */ if (FRAME_DISPLAY_INFO (f)->supports_xi2) - { - XIEventMask mask; - ptrdiff_t l = XIMaskLen (XI_LASTEVENT); - unsigned char *m; - - mask.mask = m = alloca (l); - memset (m, 0, l); - mask.mask_len = l; - - mask.deviceid = XIAllMasterDevices; - XISetMask (m, XI_ButtonPress); - XISetMask (m, XI_ButtonRelease); - XISetMask (m, XI_Motion); - XISetMask (m, XI_Enter); - XISetMask (m, XI_Leave); - - XISelectEvents (FRAME_X_DISPLAY (f), window, &mask, 1); - } + xi_select_scroll_bar_events (FRAME_DISPLAY_INFO (f), + window); #endif bar->x_window = window; -#ifdef HAVE_XDBE - bar->x_drawable = drawable; -#endif } #endif /* not USE_TOOLKIT_SCROLL_BARS */ @@ -14034,14 +15607,11 @@ static void x_scroll_bar_set_handle (struct scroll_bar *bar, int start, int end, bool rebuild) { - bool dragging = bar->dragging != -1; -#ifndef HAVE_XDBE - Window w = bar->x_window; -#else - Drawable w = bar->x_drawable; -#endif - struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); - GC gc = f->output_data.x->normal_gc; + bool dragging; + struct frame *f; + Drawable w; + GC gc; + int inside_width, inside_height, top_range, length; /* If the display is already accurate, do nothing. */ if (! rebuild @@ -14049,106 +15619,102 @@ x_scroll_bar_set_handle (struct scroll_bar *bar, int start, int end, && end == bar->end) return; - block_input (); - - { - int inside_width = VERTICAL_SCROLL_BAR_INSIDE_WIDTH (f, bar->width); - int inside_height = VERTICAL_SCROLL_BAR_INSIDE_HEIGHT (f, bar->height); - int top_range = VERTICAL_SCROLL_BAR_TOP_RANGE (f, bar->height); + f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); + dragging = bar->dragging != -1; + gc = f->output_data.x->normal_gc; + w = FRAME_X_DRAWABLE (f); - /* Make sure the values are reasonable, and try to preserve - the distance between start and end. */ - { - int length = end - start; + block_input (); - if (start < 0) - start = 0; - else if (start > top_range) - start = top_range; - end = start + length; + inside_width = VERTICAL_SCROLL_BAR_INSIDE_WIDTH (f, bar->width); + inside_height = VERTICAL_SCROLL_BAR_INSIDE_HEIGHT (f, bar->height); + top_range = VERTICAL_SCROLL_BAR_TOP_RANGE (f, bar->height); - if (end < start) - end = start; - else if (end > top_range && ! dragging) - end = top_range; - } + /* Make sure the values are reasonable, and try to preserve + the distance between start and end. */ + length = end - start; - /* Store the adjusted setting in the scroll bar. */ - bar->start = start; - bar->end = end; + if (start < 0) + start = 0; + else if (start > top_range) + start = top_range; + end = start + length; - /* Clip the end position, just for display. */ - if (end > top_range) - end = top_range; + if (end < start) + end = start; + else if (end > top_range && ! dragging) + end = top_range; - /* Draw bottom positions VERTICAL_SCROLL_BAR_MIN_HANDLE pixels - below top positions, to make sure the handle is always at least - that many pixels tall. */ - end += VERTICAL_SCROLL_BAR_MIN_HANDLE; + /* Store the adjusted setting in the scroll bar. */ + bar->start = start; + bar->end = end; - /* Draw the empty space above the handle. Note that we can't clear - zero-height areas; that means "clear to end of window." */ - if ((inside_width > 0) && (start > 0)) - { - if (f->output_data.x->scroll_bar_background_pixel != -1) - XSetForeground (FRAME_X_DISPLAY (f), gc, - f->output_data.x->scroll_bar_background_pixel); - else - XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_BACKGROUND_PIXEL (f)); + /* Clip the end position, just for display. */ + if (end > top_range) + end = top_range; - XFillRectangle (FRAME_X_DISPLAY (f), w, gc, - VERTICAL_SCROLL_BAR_LEFT_BORDER, - VERTICAL_SCROLL_BAR_TOP_BORDER, - inside_width, start); + /* Draw bottom positions VERTICAL_SCROLL_BAR_MIN_HANDLE pixels + below top positions, to make sure the handle is always at least + that many pixels tall. */ + end += VERTICAL_SCROLL_BAR_MIN_HANDLE; + /* Draw the empty space above the handle. Note that we can't clear + zero-height areas; that means "clear to end of window." */ + if ((inside_width > 0) && (start > 0)) + { + if (f->output_data.x->scroll_bar_background_pixel != -1) XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_FOREGROUND_PIXEL (f)); - } + f->output_data.x->scroll_bar_background_pixel); + else + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_BACKGROUND_PIXEL (f)); - /* Change to proper foreground color if one is specified. */ - if (f->output_data.x->scroll_bar_foreground_pixel != -1) - XSetForeground (FRAME_X_DISPLAY (f), gc, - f->output_data.x->scroll_bar_foreground_pixel); + XFillRectangle (FRAME_X_DISPLAY (f), w, gc, + bar->left + VERTICAL_SCROLL_BAR_LEFT_BORDER, + bar->top + VERTICAL_SCROLL_BAR_TOP_BORDER, + inside_width, start); - /* Draw the handle itself. */ - XFillRectangle (FRAME_X_DISPLAY (f), w, gc, - /* x, y, width, height */ - VERTICAL_SCROLL_BAR_LEFT_BORDER, - VERTICAL_SCROLL_BAR_TOP_BORDER + start, - inside_width, end - start); + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_FOREGROUND_PIXEL (f)); + } + /* Change to proper foreground color if one is specified. */ + if (f->output_data.x->scroll_bar_foreground_pixel != -1) + XSetForeground (FRAME_X_DISPLAY (f), gc, + f->output_data.x->scroll_bar_foreground_pixel); - /* Draw the empty space below the handle. Note that we can't - clear zero-height areas; that means "clear to end of window." */ - if ((inside_width > 0) && (end < inside_height)) - { - if (f->output_data.x->scroll_bar_background_pixel != -1) - XSetForeground (FRAME_X_DISPLAY (f), gc, - f->output_data.x->scroll_bar_background_pixel); - else - XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_BACKGROUND_PIXEL (f)); + /* Draw the handle itself. */ + XFillRectangle (FRAME_X_DISPLAY (f), w, gc, + /* x, y, width, height */ + bar->left + VERTICAL_SCROLL_BAR_LEFT_BORDER, + bar->top + VERTICAL_SCROLL_BAR_TOP_BORDER + start, + inside_width, end - start); - XFillRectangle (FRAME_X_DISPLAY (f), w, gc, - VERTICAL_SCROLL_BAR_LEFT_BORDER, - VERTICAL_SCROLL_BAR_TOP_BORDER + end, - inside_width, inside_height - end); + /* Draw the empty space below the handle. Note that we can't + clear zero-height areas; that means "clear to end of window." */ + if ((inside_width > 0) && (end < inside_height)) + { + if (f->output_data.x->scroll_bar_background_pixel != -1) XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_FOREGROUND_PIXEL (f)); - } + f->output_data.x->scroll_bar_background_pixel); + else + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_BACKGROUND_PIXEL (f)); + + XFillRectangle (FRAME_X_DISPLAY (f), w, gc, + bar->left + VERTICAL_SCROLL_BAR_LEFT_BORDER, + bar->top + VERTICAL_SCROLL_BAR_TOP_BORDER + end, + inside_width, inside_height - end); - /* Restore the foreground color of the GC if we changed it above. */ - if (f->output_data.x->scroll_bar_foreground_pixel != -1) XSetForeground (FRAME_X_DISPLAY (f), gc, FRAME_FOREGROUND_PIXEL (f)); - } + } -#ifdef HAVE_XDBE - if (!rebuild) - x_scroll_bar_end_update (FRAME_DISPLAY_INFO (f), bar); -#endif + /* Restore the foreground color of the GC if we changed it above. */ + if (f->output_data.x->scroll_bar_foreground_pixel != -1) + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_FOREGROUND_PIXEL (f)); unblock_input (); } @@ -14171,11 +15737,6 @@ x_scroll_bar_remove (struct scroll_bar *bar) XtDestroyWidget (SCROLL_BAR_X_WIDGET (FRAME_X_DISPLAY (f), bar)); #endif /* not USE_GTK */ #else -#ifdef HAVE_XDBE - if (bar->x_window != bar->x_drawable) - XdbeDeallocateBackBufferName (FRAME_X_DISPLAY (f), - bar->x_drawable); -#endif XDestroyWindow (FRAME_X_DISPLAY (f), bar->x_window); #endif @@ -14221,6 +15782,12 @@ XTset_vertical_scroll_bar (struct window *w, int portion, int whole, int positio } bar = x_scroll_bar_create (w, top, left, width, max (height, 1), false); +#ifndef USE_TOOLKIT_SCROLL_BARS + /* Since non-toolkit scroll bars don't display their contents to + a dedicated window, no expose event will be generated. + Redraw the scroll bar manually. */ + x_scroll_bar_redraw (bar); +#endif } else { @@ -14280,6 +15847,11 @@ XTset_vertical_scroll_bar (struct window *w, int portion, int whole, int positio bar->width = width; bar->height = height; +#ifndef USE_TOOLKIT_SCROLL_BARS + /* Redraw the scroll bar. */ + x_scroll_bar_redraw (bar); +#endif + unblock_input (); } @@ -14587,60 +16159,84 @@ XTjudge_scroll_bars (struct frame *f) #ifndef USE_TOOLKIT_SCROLL_BARS -/* Handle an Expose or GraphicsExpose event on a scroll bar. This - is a no-op when using toolkit scroll bars. - - This may be called from a signal handler, so we have to ignore GC - mark bits. */ +/* Handle exposure event EVENT generated for F, by redrawing all + intersecting scroll bars. */ static void -x_scroll_bar_expose (struct scroll_bar *bar, const XEvent *event) +x_scroll_bar_handle_exposure (struct frame *f, XEvent *event) { -#ifndef HAVE_XDBE - Window w = bar->x_window; -#else - Drawable w = bar->x_drawable; -#endif int x, y, width, height; + XRectangle rect, scroll_bar_rect, intersection; + Lisp_Object bar, condemned; + struct scroll_bar *b; - if (event->type == Expose) + if (event->type == GraphicsExpose) + { + x = event->xgraphicsexpose.x; + y = event->xgraphicsexpose.y; + width = event->xgraphicsexpose.width; + height = event->xgraphicsexpose.height; + } + else { x = event->xexpose.x; y = event->xexpose.y; width = event->xexpose.width; height = event->xexpose.height; } - else + + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + + /* Scan this frame's scroll bar list for intersecting scroll + bars. */ + condemned = FRAME_CONDEMNED_SCROLL_BARS (f); + for (bar = FRAME_SCROLL_BARS (f); + /* This trick allows us to search both the ordinary and + condemned scroll bar lists with one loop. */ + !NILP (bar) || (bar = condemned, + condemned = Qnil, + !NILP (bar)); + bar = XSCROLL_BAR (bar)->next) { - x = event->xgraphicsexpose.x; - y = event->xgraphicsexpose.y; - width = event->xgraphicsexpose.width; - height = event->xgraphicsexpose.height; + b = XSCROLL_BAR (bar); + + scroll_bar_rect.x = b->left; + scroll_bar_rect.y = b->top; + scroll_bar_rect.width = b->width; + scroll_bar_rect.height = b->height; + + if (gui_intersect_rectangles (&rect, + &scroll_bar_rect, + &intersection)) + x_scroll_bar_redraw (b); } +} +/* Redraw the scroll bar BAR. Draw its border and set its thumb. + This is usually called from x_clear_frame, but is also used to + handle exposure events that overlap scroll bars. */ + +static void +x_scroll_bar_redraw (struct scroll_bar *bar) +{ struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); GC gc = f->output_data.x->normal_gc; - block_input (); - -#ifdef HAVE_XDBE - if (w != bar->x_window) - { - if (f->output_data.x->scroll_bar_background_pixel != -1) - XSetForeground (FRAME_X_DISPLAY (f), gc, - f->output_data.x->scroll_bar_background_pixel); - else - XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_BACKGROUND_PIXEL (f)); + if (f->output_data.x->scroll_bar_background_pixel != -1) + XSetForeground (FRAME_X_DISPLAY (f), gc, + f->output_data.x->scroll_bar_background_pixel); + else + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_BACKGROUND_PIXEL (f)); - XFillRectangle (FRAME_X_DISPLAY (f), - bar->x_drawable, - gc, x, y, width, height); + XFillRectangle (FRAME_X_DISPLAY (f), FRAME_X_DRAWABLE (f), gc, + bar->left, bar->top, bar->width, bar->height); - XSetForeground (FRAME_X_DISPLAY (f), gc, - FRAME_FOREGROUND_PIXEL (f)); - } -#endif + XSetForeground (FRAME_X_DISPLAY (f), gc, + FRAME_FOREGROUND_PIXEL (f)); x_scroll_bar_set_handle (bar, bar->start, bar->end, true); @@ -14650,27 +16246,13 @@ x_scroll_bar_expose (struct scroll_bar *bar, const XEvent *event) f->output_data.x->scroll_bar_foreground_pixel); /* Draw a one-pixel border just inside the edges of the scroll bar. */ - XDrawRectangle (FRAME_X_DISPLAY (f), w, gc, - /* x, y, width, height */ - 0, 0, bar->width - 1, bar->height - 1); - - /* XDrawPoint (FRAME_X_DISPLAY (f), w, gc, - bar->width - 1, bar->height - 1); - - This code is no longer required since the normal GC now uses the - regular line width. */ + XDrawRectangle (FRAME_X_DISPLAY (f), FRAME_X_DRAWABLE (f), gc, + bar->left, bar->top, bar->width - 1, bar->height - 1); /* Restore the foreground color of the GC if we changed it above. */ if (f->output_data.x->scroll_bar_foreground_pixel != -1) XSetForeground (FRAME_X_DISPLAY (f), gc, FRAME_FOREGROUND_PIXEL (f)); - -#ifdef HAVE_XDBE - x_scroll_bar_end_update (FRAME_DISPLAY_INFO (f), bar); -#endif - - unblock_input (); - } #endif /* not USE_TOOLKIT_SCROLL_BARS */ @@ -14792,6 +16374,7 @@ x_scroll_bar_note_movement (struct scroll_bar *bar, struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); dpyinfo->last_mouse_movement_time = event->time; + dpyinfo->last_mouse_movement_time_send_event = event->send_event; dpyinfo->last_mouse_scroll_bar = bar; f->mouse_moved = true; @@ -14810,24 +16393,6 @@ x_scroll_bar_note_movement (struct scroll_bar *bar, } } -#ifdef HAVE_XDBE -static void -x_scroll_bar_end_update (struct x_display_info *dpyinfo, - struct scroll_bar *bar) -{ - XdbeSwapInfo swap_info; - - /* This means the scroll bar is double-buffered. */ - if (bar->x_drawable != bar->x_window) - { - memset (&swap_info, 0, sizeof swap_info); - swap_info.swap_window = bar->x_window; - swap_info.swap_action = XdbeCopied; - XdbeSwapBuffers (dpyinfo->display, &swap_info, 1); - } -} -#endif - #endif /* !USE_TOOLKIT_SCROLL_BARS */ /* Return information to the user about the current position of the mouse @@ -14851,17 +16416,17 @@ x_scroll_bar_report_motion (struct frame **fp, Lisp_Object *bar_window, /* Get the mouse's position relative to the scroll bar window, and report that. */ - if (XQueryPointer (FRAME_X_DISPLAY (f), w, + if (x_query_pointer (FRAME_X_DISPLAY (f), w, - /* Root, child, root x and root y. */ - &dummy_window, &dummy_window, - &dummy_coord, &dummy_coord, + /* Root, child, root x and root y. */ + &dummy_window, &dummy_window, + &dummy_coord, &dummy_coord, - /* Position relative to scroll bar. */ - &win_x, &win_y, + /* Position relative to scroll bar. */ + &win_x, &win_y, - /* Mouse buttons and modifier keys. */ - &dummy_mask)) + /* Mouse buttons and modifier keys. */ + &dummy_mask)) { int top_range = VERTICAL_SCROLL_BAR_TOP_RANGE (f, bar->height); @@ -14920,17 +16485,17 @@ x_horizontal_scroll_bar_report_motion (struct frame **fp, Lisp_Object *bar_windo /* Get the mouse's position relative to the scroll bar window, and report that. */ - if (XQueryPointer (FRAME_X_DISPLAY (f), w, + if (x_query_pointer (FRAME_X_DISPLAY (f), w, - /* Root, child, root x and root y. */ - &dummy_window, &dummy_window, - &dummy_coord, &dummy_coord, + /* Root, child, root x and root y. */ + &dummy_window, &dummy_window, + &dummy_coord, &dummy_coord, - /* Position relative to scroll bar. */ - &win_x, &win_y, + /* Position relative to scroll bar. */ + &win_x, &win_y, - /* Mouse buttons and modifier keys. */ - &dummy_mask)) + /* Mouse buttons and modifier keys. */ + &dummy_mask)) { int left_range = HORIZONTAL_SCROLL_BAR_LEFT_RANGE (f, bar->width); @@ -14968,17 +16533,15 @@ x_horizontal_scroll_bar_report_motion (struct frame **fp, Lisp_Object *bar_windo } -/* The screen has been cleared so we may have changed foreground or - background colors, and the scroll bars may need to be redrawn. - Clear out the scroll bars, and ask for expose events, so we can - redraw them. */ +/* The screen has been cleared and foreground or background colors may + have changed, so the scroll bars need to be redrawn. Clear the + scroll bars and redraw them. */ static void x_scroll_bar_clear (struct frame *f) { #ifndef USE_TOOLKIT_SCROLL_BARS - Lisp_Object bar; -#ifdef HAVE_XDBE + Lisp_Object bar, condemned; GC gc = f->output_data.x->normal_gc; if (f->output_data.x->scroll_bar_background_pixel != -1) @@ -14987,35 +16550,25 @@ x_scroll_bar_clear (struct frame *f) else XSetForeground (FRAME_X_DISPLAY (f), gc, FRAME_BACKGROUND_PIXEL (f)); -#endif /* We can have scroll bars even if this is 0, if we just turned off scroll bar mode. But in that case we should not clear them. */ if (FRAME_HAS_VERTICAL_SCROLL_BARS (f)) - for (bar = FRAME_SCROLL_BARS (f); VECTORP (bar); - bar = XSCROLL_BAR (bar)->next) - { -#ifdef HAVE_XDBE - if (XSCROLL_BAR (bar)->x_window - == XSCROLL_BAR (bar)->x_drawable) -#endif - XClearArea (FRAME_X_DISPLAY (f), - XSCROLL_BAR (bar)->x_window, - 0, 0, 0, 0, True); -#ifdef HAVE_XDBE - else - XFillRectangle (FRAME_X_DISPLAY (f), - XSCROLL_BAR (bar)->x_drawable, - gc, 0, 0, XSCROLL_BAR (bar)->width, - XSCROLL_BAR (bar)->height); -#endif - } + { + condemned = FRAME_CONDEMNED_SCROLL_BARS (f); + for (bar = FRAME_SCROLL_BARS (f); + /* This trick allows us to search both the ordinary and + condemned scroll bar lists with one loop. */ + !NILP (bar) || (bar = condemned, + condemned = Qnil, + !NILP (bar)); + bar = XSCROLL_BAR (bar)->next) + x_scroll_bar_redraw (XSCROLL_BAR (bar)); + } -#ifdef HAVE_XDBE XSetForeground (FRAME_X_DISPLAY (f), gc, FRAME_FOREGROUND_PIXEL (f)); -#endif #endif /* not USE_TOOLKIT_SCROLL_BARS */ } @@ -15419,6 +16972,15 @@ x_dnd_update_tooltip_position (int root_x, int root_y) x_dnd_compute_tip_xy (&root_x, &root_y, x_dnd_monitors); + if (x_dnd_last_tooltip_valid + && root_x == x_dnd_last_tooltip_x + && root_y == x_dnd_last_tooltip_y) + return; + + x_dnd_last_tooltip_x = root_x; + x_dnd_last_tooltip_y = root_y; + x_dnd_last_tooltip_valid = true; + XMoveWindow (FRAME_X_DISPLAY (x_dnd_frame), tip_window, root_x, root_y); } @@ -15439,11 +17001,19 @@ x_dnd_update_tooltip_now (void) dpyinfo = FRAME_DISPLAY_INFO (x_dnd_frame); +#ifndef HAVE_XINPUT2 rc = XQueryPointer (dpyinfo->display, dpyinfo->root_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); +#else + rc = x_query_pointer_1 (dpyinfo, x_dnd_pointer_device, + dpyinfo->root_window, + &root, &child, &root_x, + &root_y, &win_x, &win_y, + &mask); +#endif if (rc) x_dnd_update_tooltip_position (root_x, root_y); @@ -15463,12 +17033,17 @@ x_dnd_update_state (struct x_display_info *dpyinfo, Time timestamp) xm_drop_start_message dsmsg; bool was_frame; - if (XQueryPointer (dpyinfo->display, - dpyinfo->root_window, - &dummy, &dummy_child, - &root_x, &root_y, - &dummy_x, &dummy_y, - &dummy_mask)) + if (x_query_pointer_1 (dpyinfo, +#ifdef HAVE_XINPUT2 + x_dnd_pointer_device, +#else + -1, +#endif + dpyinfo->root_window, + &dummy, &dummy_child, + &root_x, &root_y, + &dummy_x, &dummy_y, + &dummy_mask)) { target = x_dnd_get_target_window (dpyinfo, root_x, root_y, &target_proto, @@ -15569,7 +17144,7 @@ x_dnd_update_state (struct x_display_info *dpyinfo, Time timestamp) emsg.zero = 0; emsg.timestamp = timestamp; emsg.source_window = FRAME_X_WINDOW (x_dnd_frame); - emsg.index_atom = dpyinfo->Xatom_XdndSelection; + emsg.index_atom = x_dnd_motif_atom; if (x_dnd_motif_setup_p) xm_send_top_level_enter_message (dpyinfo, FRAME_X_WINDOW (x_dnd_frame), @@ -15641,8 +17216,7 @@ x_dnd_update_state (struct x_display_info *dpyinfo, Time timestamp) XM_DROP_ACTION_DROP_CANCEL); dsmsg.x = 0; dsmsg.y = 0; - dsmsg.index_atom - = FRAME_DISPLAY_INFO (x_dnd_frame)->Xatom_XdndSelection; + dsmsg.index_atom = x_dnd_motif_atom; dsmsg.source_window = FRAME_X_WINDOW (x_dnd_frame); x_dnd_send_xm_leave_for_drop (dpyinfo, x_dnd_frame, @@ -15793,6 +17367,29 @@ x_wait_for_cell_change (Lisp_Object cell, struct timespec timeout) } } +/* Find whether or not an undelivered MONITORS_CHANGED_EVENT is + already on the event queue. DPYINFO is the display any such event + must apply to. */ + +static bool +x_find_monitors_changed_event (struct x_display_info *dpyinfo) +{ + union buffered_input_event *event; + + event = kbd_fetch_ptr; + + while (event != kbd_store_ptr) + { + if (event->ie.kind == MONITORS_CHANGED_EVENT + && XTERMINAL (event->ie.arg) == dpyinfo->terminal) + return true; + + event = X_NEXT_KBD_EVENT (event); + } + + return false; +} + #ifdef USE_GTK static void x_monitors_changed_cb (GdkScreen *gscr, gpointer user_data) @@ -15810,6 +17407,9 @@ x_monitors_changed_cb (GdkScreen *gscr, gpointer user_data) if (!dpyinfo) return; + if (x_find_monitors_changed_event (dpyinfo)) + return; + XSETTERMINAL (terminal, dpyinfo->terminal); current_monitors @@ -15842,8 +17442,10 @@ x_coords_from_dnd_message (struct x_display_info *dpyinfo, XEvent *event, int *x_out, int *y_out) { xm_drag_motion_message dmsg; + xm_drag_motion_reply dreply; xm_drop_start_message smsg; xm_drop_start_reply reply; + unsigned long kde_data; if (event->type != ClientMessage) return false; @@ -15871,6 +17473,13 @@ x_coords_from_dnd_message (struct x_display_info *dpyinfo, return true; } + else if (!xm_read_drag_motion_reply (event, &dreply)) + { + *x_out = dreply.better_x; + *y_out = dreply.better_y; + + return true; + } else if (!xm_read_drop_start_message (event, &smsg)) { *x_out = smsg.x; @@ -15887,6 +17496,23 @@ x_coords_from_dnd_message (struct x_display_info *dpyinfo, } } + if (((event->xclient.message_type + == dpyinfo->Xatom_DndProtocol) + || (event->xclient.message_type + == dpyinfo->Xatom_DND_PROTOCOL)) + && event->xclient.format == 32 + /* Check that the version of the old KDE protocol is new + enough to include coordinates. */ + && event->xclient.data.l[4]) + { + kde_data = (unsigned long) event->xclient.data.l[3]; + + *x_out = (kde_data & 0xffff); + *y_out = (kde_data >> 16 & 0xffff); + + return true; + } + return false; } @@ -15911,6 +17537,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, union buffered_input_event inev; int count = 0; int do_help = 0; +#ifdef HAVE_XINPUT2 + struct xi_device_t *gen_help_device; + Time gen_help_time; +#endif ptrdiff_t nbytes = 0; struct frame *any, *f = NULL; Mouse_HLInfo *hlinfo = &dpyinfo->mouse_highlight; @@ -15940,6 +17570,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, EVENT_INIT (inev.ie); inev.ie.kind = NO_EVENT; inev.ie.arg = Qnil; +#ifdef HAVE_XINPUT2 + gen_help_device = NULL; +#endif /* Ignore events coming from various extensions, such as XFIXES and XKB. */ @@ -15966,21 +17599,31 @@ handle_one_xevent (struct x_display_info *dpyinfo, { int rc; - if (x_dnd_in_progress - && FRAME_DISPLAY_INFO (x_dnd_frame) == dpyinfo + if (((x_dnd_in_progress + && FRAME_DISPLAY_INFO (x_dnd_frame) == dpyinfo) + || (x_dnd_waiting_for_finish + && FRAME_DISPLAY_INFO (x_dnd_finish_frame) == dpyinfo)) && event->xclient.message_type == dpyinfo->Xatom_XdndStatus) { Window target; unsigned long r1, r2; + int root_x, root_y; + bool button; target = event->xclient.data.l[0]; if (x_dnd_last_protocol_version != -1 + && x_dnd_in_progress && target == x_dnd_last_seen_window - && event->xclient.data.l[1] & 2) + /* The XDND documentation is not very clearly worded. + But this should be the correct behavior, since + "kDNDStatusSendHereFlag" in the reference + implementation is 2, and means the mouse rect + should be ignored. */ + && !(event->xclient.data.l[1] & 2)) { r1 = event->xclient.data.l[2]; - r2 = event->xclient.data.l[2]; + r2 = event->xclient.data.l[3]; x_dnd_mouse_rect_target = target; x_dnd_mouse_rect.x = (r1 & 0xffff0000) >> 16; @@ -15992,7 +17635,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_dnd_mouse_rect_target = None; if (x_dnd_last_protocol_version != -1 - && target == x_dnd_last_seen_window) + && (x_dnd_in_progress + && target == x_dnd_last_seen_window)) { if (event->xclient.data.l[1] & 1) { @@ -16005,6 +17649,73 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_dnd_action = None; } + /* Send any pending XdndPosition message. */ + if (x_dnd_waiting_for_status_window == target) + { + if (x_dnd_pending_send_position.type != 0) + { + /* If the last XdndStatus specified a mouse + rectangle and this event falls inside, don't + send the event, but clear + x_dnd_waiting_for_status_window instead. */ + + root_x = x_dnd_pending_send_position_root_x; + root_y = x_dnd_pending_send_position_root_y; + button = x_dnd_pending_send_position_button; + + if (target == x_dnd_mouse_rect_target + && x_dnd_mouse_rect.width + && x_dnd_mouse_rect.height + /* Ignore the mouse rectangle if we're + supposed to be sending a button press + instead. */ + && !button + && (root_x >= x_dnd_mouse_rect.x + && root_x < (x_dnd_mouse_rect.x + + x_dnd_mouse_rect.width) + && root_y >= x_dnd_mouse_rect.y + && root_y < (x_dnd_mouse_rect.y + + x_dnd_mouse_rect.height))) + x_dnd_waiting_for_status_window = None; + else + { + x_ignore_errors_for_next_request (dpyinfo); + XSendEvent (dpyinfo->display, target, + False, NoEventMask, + &x_dnd_pending_send_position); + x_stop_ignoring_errors (dpyinfo); + x_dnd_pending_send_position.type = 0; + + /* Since we sent another XdndPosition message, we + have to wait for another one in reply, so don't + reset `x_dnd_waiting_for_status_window' + here. */ + } + } + else + x_dnd_waiting_for_status_window = None; + + /* Send any pending drop if warranted. */ + if (x_dnd_waiting_for_finish && x_dnd_need_send_drop + && x_dnd_waiting_for_status_window == None) + { + if (event->xclient.data.l[1] & 1) + { + if (x_dnd_send_drop_proto >= 2) + x_dnd_action = event->xclient.data.l[4]; + else + x_dnd_action = dpyinfo->Xatom_XdndActionCopy; + } + else + x_dnd_action = None; + + x_dnd_waiting_for_finish + = x_dnd_send_drop (x_dnd_finish_frame, + target, x_dnd_selection_timestamp, + x_dnd_send_drop_proto); + } + } + goto done; } @@ -16045,7 +17756,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (operation != XM_DRAG_MOVE && operation != XM_DRAG_COPY - && operation != XM_DRAG_LINK) + && XM_DRAG_OPERATION_IS_LINK (operation)) { x_dnd_waiting_for_finish = false; goto done; @@ -16062,15 +17773,16 @@ handle_one_xevent (struct x_display_info *dpyinfo, switch (operation) { case XM_DRAG_MOVE: - x_dnd_action = dpyinfo->Xatom_XdndActionMove; + x_dnd_action_symbol = QXdndActionMove; break; case XM_DRAG_COPY: - x_dnd_action = dpyinfo->Xatom_XdndActionCopy; + x_dnd_action_symbol = QXdndActionCopy; break; - case XM_DRAG_LINK: - x_dnd_action = dpyinfo->Xatom_XdndActionLink; + /* This means XM_DRAG_OPERATION_IS_LINK (operation). */ + default: + x_dnd_action_symbol = QXdndActionLink; break; } @@ -16160,11 +17872,14 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (event->xclient.data.l[0] == dpyinfo->Xatom_wm_delete_window) { - f = any; + f = x_top_window_to_frame (dpyinfo, + event->xclient.window); + if (!f) goto OTHER; /* May be a dialog that is to be removed */ inev.ie.kind = DELETE_WINDOW_EVENT; + inev.ie.timestamp = event->xclient.data.l[1]; XSETFRAME (inev.ie.frame_or_window, f); goto done; } @@ -16219,8 +17934,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, } else if (event->xclient.data.l[4] == 1) { - XSyncIntsToValue (&FRAME_X_OUTPUT (f)->current_extended_counter_value, - event->xclient.data.l[2], event->xclient.data.l[3]); + XSyncIntsToValue (&FRAME_X_OUTPUT (f)->resize_counter_value, + event->xclient.data.l[2], + event->xclient.data.l[3]); + FRAME_X_OUTPUT (f)->ext_sync_end_pending_p = true; } @@ -16337,10 +18054,29 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto done; } - xft_settings_event (dpyinfo, event); +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* These messages are sent by the compositing manager after a + frame is drawn under extended synchronization. */ + if (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_drawn) + { + if (any) + x_sync_handle_frame_drawn (dpyinfo, (XEvent *) event, any); + + goto done; + } + + if (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_timings) + goto done; +#endif + + if (xft_settings_event (dpyinfo, event)) + goto done; f = any; - if (!f) + /* We don't want to ever leak tooltip frames to Lisp code. */ + if (!f || FRAME_TOOLTIP_P (f)) goto OTHER; /* These values are always used initialized, but GCC doesn't @@ -16362,6 +18098,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!x_window_to_frame (dpyinfo, event->xselection.requestor)) goto OTHER; #endif /* not USE_X_TOOLKIT and not USE_GTK */ +#ifdef HAVE_GTK3 + /* GTK 3 apparently chokes on these events since they have no + associated device. (bug#56869, another bug as well that I + can't find) */ + *finish = X_EVENT_DROP; +#endif x_handle_selection_notify (&event->xselection); break; @@ -16370,9 +18112,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!x_window_to_frame (dpyinfo, event->xselectionclear.window)) goto OTHER; #endif /* not USE_X_TOOLKIT and not USE_GTK */ +#ifdef HAVE_GTK3 + *finish = X_EVENT_DROP; +#endif { const XSelectionClearEvent *eventp = &event->xselectionclear; + if (eventp->selection == dpyinfo->motif_drag_atom + && (eventp->time == CurrentTime + || dpyinfo->motif_drag_atom_time <= eventp->time)) + dpyinfo->motif_drag_atom = None; + inev.sie.kind = SELECTION_CLEAR_EVENT; SELECTION_EVENT_DPYINFO (&inev.sie) = dpyinfo; SELECTION_EVENT_SELECTION (&inev.sie) = eventp->selection; @@ -16391,6 +18141,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!x_window_to_frame (dpyinfo, event->xselectionrequest.owner)) goto OTHER; #endif /* USE_X_TOOLKIT */ +#ifdef HAVE_GTK3 + *finish = X_EVENT_DROP; +#endif { const XSelectionRequestEvent *eventp = &event->xselectionrequest; @@ -16402,9 +18155,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, SELECTION_EVENT_PROPERTY (&inev.sie) = eventp->property; SELECTION_EVENT_TIME (&inev.sie) = eventp->time; - /* If drag-and-drop is in progress, handle SelectionRequest - events immediately, by setting hold_quit to the input - event. */ + /* If drag-and-drop or another modal dialog/menu is in + progress, handle SelectionRequest events immediately, by + pushing it onto the selecction queue. */ if (x_use_pending_selection_requests) { @@ -16415,7 +18168,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (x_dnd_waiting_for_finish && x_dnd_waiting_for_motif_finish == 2 && dpyinfo == x_dnd_waiting_for_motif_finish_display - && eventp->selection == dpyinfo->Xatom_XdndSelection + && eventp->selection == x_dnd_motif_atom && (eventp->target == dpyinfo->Xatom_XmTRANSFER_SUCCESS || eventp->target == dpyinfo->Xatom_XmTRANSFER_FAILURE)) { @@ -16665,8 +18418,26 @@ handle_one_xevent (struct x_display_info *dpyinfo, { /* Maybe we shouldn't set this for child frames ?? */ f->output_data.x->parent_desc = event->xreparent.parent; + if (!FRAME_PARENT_FRAME (f)) - x_real_positions (f, &f->left_pos, &f->top_pos); + { + x_real_positions (f, &f->left_pos, &f->top_pos); + + /* Perhaps reparented due to a WM restart. Reset this. */ + FRAME_DISPLAY_INFO (f)->wm_type = X_WMTYPE_UNKNOWN; + FRAME_DISPLAY_INFO (f)->net_supported_window = 0; + +#ifndef USE_GTK + /* The window manager could have restarted and the new + window manager might not support user time windows, + so update what is used accordingly. + + Note that this doesn't handle changes between + non-reparenting window managers. */ + if (FRAME_X_OUTPUT (f)->has_been_visible) + x_update_frame_user_time_window (f); +#endif + } else { Window root; @@ -16679,10 +18450,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, unblock_input (); } - /* Perhaps reparented due to a WM restart. Reset this. */ - FRAME_DISPLAY_INFO (f)->wm_type = X_WMTYPE_UNKNOWN; - FRAME_DISPLAY_INFO (f)->net_supported_window = 0; - x_set_frame_alpha (f); } goto OTHER; @@ -16746,7 +18513,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!FRAME_GARBAGED_P (f)) { -#ifdef USE_X_TOOLKIT +#if defined USE_X_TOOLKIT && defined USE_TOOLKIT_SCROLL_BARS if (f->output_data.x->edit_widget) /* The widget's expose proc will be run in this case. */ @@ -16761,10 +18528,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif expose_frame (f, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); +#ifndef USE_TOOLKIT_SCROLL_BARS + x_scroll_bar_handle_exposure (f, (XEvent *) event); +#endif #ifdef USE_GTK x_clear_under_internal_border (f); #endif } +#ifndef USE_TOOLKIT_SCROLL_BARS + else + x_scroll_bar_handle_exposure (f, (XEvent *) event); +#endif #ifdef HAVE_XDBE if (!FRAME_GARBAGED_P (f)) @@ -16773,9 +18547,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, } else { -#ifndef USE_TOOLKIT_SCROLL_BARS - struct scroll_bar *bar; -#endif #if defined USE_LUCID /* Submenus of the Lucid menu bar aren't widgets themselves, so there's no way to dispatch events @@ -16787,20 +18558,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif /* USE_LUCID */ -#ifdef USE_TOOLKIT_SCROLL_BARS /* Dispatch event to the widget. */ goto OTHER; -#else /* not USE_TOOLKIT_SCROLL_BARS */ - bar = x_window_to_scroll_bar (event->xexpose.display, - event->xexpose.window, 2); - - if (bar) - x_scroll_bar_expose (bar, event); -#ifdef USE_X_TOOLKIT - else - goto OTHER; -#endif /* USE_X_TOOLKIT */ -#endif /* not USE_TOOLKIT_SCROLL_BARS */ } break; @@ -16814,6 +18573,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, event->xgraphicsexpose.y, event->xgraphicsexpose.width, event->xgraphicsexpose.height); +#ifndef USE_TOOLKIT_SCROLL_BARS + x_scroll_bar_handle_exposure (f, (XEvent *) event); +#endif #ifdef USE_GTK x_clear_under_internal_border (f); #endif @@ -16821,16 +18583,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, show_back_buffer (f); #endif } -#ifndef USE_TOOLKIT_SCROLL_BARS - struct scroll_bar *bar - = x_window_to_scroll_bar (dpyinfo->display, - /* Hopefully this is just a window, - not the back buffer. */ - event->xgraphicsexpose.drawable, 2); - - if (bar) - x_scroll_bar_expose (bar, event); -#endif #ifdef USE_X_TOOLKIT else goto OTHER; @@ -16934,6 +18686,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif if (x_dnd_in_progress + /* When _NET_WM_CLIENT_LIST stacking is being used, changes + in that property are watched for, and it's not necessary + to update the state in response to ordinary window + substructure events. */ + && !x_dnd_use_toplevels && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)) x_dnd_update_state (dpyinfo, dpyinfo->last_user_time); @@ -17030,8 +18787,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: - x_display_set_last_user_time (dpyinfo, event->xkey.time); + x_display_set_last_user_time (dpyinfo, event->xkey.time, + event->xkey.send_event); ignore_next_mouse_click_timeout = 0; + coding = Qlatin_1; #if defined (USE_X_TOOLKIT) || defined (USE_GTK) @@ -17042,6 +18801,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, f = any; +#ifdef USE_GTK + if (f) + x_set_gtk_user_time (f, event->xkey.time); +#endif + /* If mouse-highlight is an integer, input clears out mouse highlighting. */ if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight) @@ -17093,7 +18857,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, /* `xkey' will be modified, but it's not important to modify `event' itself. */ XKeyEvent xkey = event->xkey; - int i; + #ifdef HAVE_XINPUT2 Time pending_keystroke_time; struct xi_device_t *source; @@ -17143,27 +18907,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (modifiers & dpyinfo->meta_mod_mask) memset (&compose_status, 0, sizeof (compose_status)); -#ifdef HAVE_XKB - if (dpyinfo->xkb_desc) - { - XkbDescRec *rec = dpyinfo->xkb_desc; - - if (rec->map->modmap && rec->map->modmap[xkey.keycode]) - goto done_keysym; - } - else -#endif - { - if (dpyinfo->modmap) - { - for (i = 0; i < 8 * dpyinfo->modmap->max_keypermod; i++) - { - if (xkey.keycode == dpyinfo->modmap->modifiermap[i]) - goto done_keysym; - } - } - } - #ifdef HAVE_X_I18N if (FRAME_XIC (f)) { @@ -17196,14 +18939,49 @@ handle_one_xevent (struct x_display_info *dpyinfo, emacs_abort (); } else - nbytes = XLookupString (&xkey, (char *) copy_bufptr, - copy_bufsiz, &keysym, - &compose_status); -#else - nbytes = XLookupString (&xkey, (char *) copy_bufptr, - copy_bufsiz, &keysym, - &compose_status); #endif + { +#ifdef HAVE_XKB + int overflow; + unsigned int consumed; + + if (dpyinfo->xkb_desc) + { + if (!XkbTranslateKeyCode (dpyinfo->xkb_desc, + xkey.keycode, xkey.state, + &consumed, &keysym)) + goto done_keysym; + + overflow = 0; + + nbytes = XkbTranslateKeySym (dpyinfo->display, &keysym, + xkey.state & ~consumed, + (char *) copy_bufptr, + copy_bufsiz, &overflow); + + if (overflow) + { + copy_bufptr = SAFE_ALLOCA ((copy_bufsiz += overflow) + * sizeof *copy_bufptr); + overflow = 0; + nbytes = XkbTranslateKeySym (dpyinfo->display, &keysym, + xkey.state & ~consumed, + (char *) copy_bufptr, + copy_bufsiz, &overflow); + + if (overflow) + nbytes = 0; + } + + if (nbytes) + coding = Qnil; + } + else +#endif + nbytes = XLookupString (&xkey, (char *) copy_bufptr, + copy_bufsiz, &keysym, + &compose_status); + } #ifdef XK_F1 if (x_dnd_in_progress && keysym == XK_F1) @@ -17481,7 +19259,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case EnterNotify: - x_display_set_last_user_time (dpyinfo, event->xcrossing.time); + x_display_set_last_user_time (dpyinfo, event->xcrossing.time, + event->xcrossing.send_event); if (x_top_window_to_frame (dpyinfo, event->xcrossing.window)) x_detect_focus_change (dpyinfo, any, event, &inev.ie); @@ -17534,6 +19313,21 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case FocusIn: +#if defined HAVE_XINPUT2 \ + && (defined HAVE_GTK3 || (!defined USE_GTK && !defined USE_X_TOOLKIT)) + /* If a FocusIn event is received (because the window manager + sent us one), don't set the core focus if XInput 2 is + enabled, since that would mess up the device-specific focus + tracking. + + The long looking preprocessor conditional only enables this + code on GTK 3 and no toolkit builds, since those are the only + builds where focus is tracked specific to each master device. + Other builds use core events and the client pointer to handle + focus, much like on a build without XInput 2. */ + if (dpyinfo->supports_xi2) + goto OTHER; +#endif #ifdef USE_GTK /* Some WMs (e.g. Mutter in Gnome Shell), don't unmap minimized/iconified windows; thus, for those WMs we won't get @@ -17566,7 +19360,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case LeaveNotify: - x_display_set_last_user_time (dpyinfo, event->xcrossing.time); + x_display_set_last_user_time (dpyinfo, event->xcrossing.time, + event->xcrossing.send_event); #ifdef HAVE_XWIDGETS { @@ -17603,6 +19398,19 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif if (f) { + /* Now clear dpyinfo->last_mouse_motion_frame, or + gui_redo_mouse_highlight will end up highlighting the + last known position of the mouse if a tooltip frame is + later unmapped. */ + + if (f == dpyinfo->last_mouse_motion_frame) + dpyinfo->last_mouse_motion_frame = NULL; + + /* Something similar applies to + dpyinfo->last_mouse_glyph_frame. */ + if (f == dpyinfo->last_mouse_glyph_frame) + dpyinfo->last_mouse_glyph_frame = NULL; + if (f == hlinfo->mouse_face_mouse_frame) { /* If we move outside the frame, then we're @@ -17633,6 +19441,21 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case FocusOut: +#if defined HAVE_XINPUT2 \ + && (defined HAVE_GTK3 || (!defined USE_GTK && !defined USE_X_TOOLKIT)) + /* If a FocusIn event is received (because the window manager + sent us one), don't set the core focus if XInput 2 is + enabled, since that would mess up the device-specific focus + tracking. + + The long looking preprocessor conditional only enables this + code on GTK 3 and no toolkit builds, since those are the only + builds where focus is tracked specific to each master device. + Other builds use core events and the client pointer to handle + focus, much like on a build without XInput 2. */ + if (dpyinfo->supports_xi2) + goto OTHER; +#endif x_detect_focus_change (dpyinfo, any, event, &inev.ie); goto OTHER; @@ -17702,12 +19525,16 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } - target = x_dnd_get_target_window (dpyinfo, - event->xmotion.x_root, - event->xmotion.y_root, - &target_proto, - &motif_style, &toplevel, - &was_frame); + if (event->xmotion.same_screen) + target = x_dnd_get_target_window (dpyinfo, + event->xmotion.x_root, + event->xmotion.y_root, + &target_proto, + &motif_style, &toplevel, + &was_frame); + else + target = x_dnd_fill_empty_target (&target_proto, &motif_style, + &toplevel, &was_frame); if (toplevel != x_dnd_last_seen_toplevel) { @@ -17824,7 +19651,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, emsg.zero = 0; emsg.timestamp = event->xbutton.time; emsg.source_window = FRAME_X_WINDOW (x_dnd_frame); - emsg.index_atom = dpyinfo->Xatom_XdndSelection; + emsg.index_atom = x_dnd_motif_atom; if (x_dnd_motif_setup_p) xm_send_top_level_enter_message (dpyinfo, FRAME_X_WINDOW (x_dnd_frame), @@ -18011,13 +19838,24 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (configureEvent.xconfigure.width != dpyinfo->screen_width || configureEvent.xconfigure.height != dpyinfo->screen_height) { - inev.ie.kind = MONITORS_CHANGED_EVENT; - XSETTERMINAL (inev.ie.arg, dpyinfo->terminal); + /* Also avoid storing duplicate events here, since + Fx_display_monitor_attributes_list will return the + same information for both invocations of the + hook. */ + if (!x_find_monitors_changed_event (dpyinfo)) + { + inev.ie.kind = MONITORS_CHANGED_EVENT; + XSETTERMINAL (inev.ie.arg, dpyinfo->terminal); + + /* Store this event now since inev.ie.type could be set to + MOVE_FRAME_EVENT later. */ + kbd_buffer_store_event (&inev.ie); + inev.ie.kind = NO_EVENT; + } - /* Store this event now since inev.ie.type could be set to - MOVE_FRAME_EVENT later. */ - kbd_buffer_store_event (&inev.ie); - inev.ie.kind = NO_EVENT; + /* Also update the position of the drag-and-drop + tooltip. */ + x_dnd_update_tooltip_now (); } #endif @@ -18160,10 +19998,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, configureEvent.xconfigure.height, f->new_width, f->new_height); +#ifdef HAVE_XDBE block_input (); if (FRAME_X_DOUBLE_BUFFERED_P (f)) x_drop_xrender_surfaces (f); unblock_input (); +#endif xg_frame_resized (f, configureEvent.xconfigure.width, configureEvent.xconfigure.height); #ifdef USE_CAIRO @@ -18279,6 +20119,28 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif +#ifdef HAVE_XINPUT2 + if (f && dpyinfo->supports_xi2) + { + Mouse_HLInfo *hlinfo; + + /* The input extension doesn't report motion events when + the part of the window below the pointer changes. To + avoid outdated information from keeping + i.e. mouse-highlight at the wrong position after the + frame is moved or resized, reset the mouse highlight + and last_mouse_motion_frame. */ + + if (dpyinfo->last_mouse_motion_frame == f) + dpyinfo->last_mouse_motion_frame = NULL; + + hlinfo = MOUSE_HL_INFO (f); + + if (hlinfo->mouse_face_mouse_frame == f) + reset_mouse_highlight (hlinfo); + } +#endif + } if (x_dnd_in_progress @@ -18290,7 +20152,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, case ButtonPress: { if (event->xbutton.type == ButtonPress) - x_display_set_last_user_time (dpyinfo, event->xbutton.time); + x_display_set_last_user_time (dpyinfo, event->xbutton.time, + event->xbutton.send_event); #ifdef HAVE_XWIDGETS struct xwidget_view *xvw = xwidget_view_from_window (event->xbutton.window); @@ -18333,12 +20196,16 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (event->type == ButtonPress) { + x_display_set_last_user_time (dpyinfo, event->xbutton.time, + event->xbutton.send_event); + dpyinfo->grabbed |= (1 << event->xbutton.button); dpyinfo->last_mouse_frame = f; - if (f && !tab_bar_p) + + if (f) f->last_tab_bar_item = -1; #if ! defined (USE_GTK) - if (f && !tool_bar_p) + if (f) f->last_tool_bar_item = -1; #endif /* not USE_GTK */ } @@ -18346,18 +20213,26 @@ handle_one_xevent (struct x_display_info *dpyinfo, dpyinfo->grabbed &= ~(1 << event->xbutton.button); if (event->xbutton.type == ButtonPress - && x_dnd_last_seen_window != None - && x_dnd_last_protocol_version != -1) + && x_dnd_last_seen_window != None) { - x_dnd_send_position (x_dnd_frame, - x_dnd_last_seen_window, - x_dnd_last_protocol_version, - event->xbutton.x_root, - event->xbutton.y_root, - x_dnd_selection_timestamp, - x_dnd_wanted_action, - event->xbutton.button, - event->xbutton.state); + if (x_dnd_last_window_is_frame) + x_dnd_note_self_wheel (dpyinfo, + x_dnd_last_seen_window, + event->xbutton.x_root, + event->xbutton.y_root, + event->xbutton.button, + event->xbutton.state, + event->xbutton.time); + else if (x_dnd_last_protocol_version != -1) + x_dnd_send_position (x_dnd_frame, + x_dnd_last_seen_window, + x_dnd_last_protocol_version, + event->xbutton.x_root, + event->xbutton.y_root, + event->xbutton.time, + x_dnd_wanted_action, + event->xbutton.button, + event->xbutton.state); goto OTHER; } @@ -18402,9 +20277,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_dnd_waiting_for_finish_proto = x_dnd_last_protocol_version; x_dnd_waiting_for_finish - = x_dnd_send_drop (x_dnd_frame, x_dnd_last_seen_window, - x_dnd_selection_timestamp, - x_dnd_last_protocol_version); + = x_dnd_do_drop (x_dnd_last_seen_window, + x_dnd_last_protocol_version); x_dnd_finish_display = dpyinfo->display; } else if (x_dnd_last_seen_window != None) @@ -18414,6 +20288,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!xm_read_drag_receiver_info (dpyinfo, x_dnd_last_seen_window, &drag_receiver_info) + && !x_dnd_disable_motif_protocol && drag_receiver_info.protocol_style != XM_DRAG_STYLE_NONE && (x_dnd_allow_current_frame || x_dnd_last_seen_window != FRAME_OUTER_WINDOW (x_dnd_frame))) @@ -18438,7 +20313,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, dmsg.timestamp = event->xbutton.time; dmsg.x = event->xbutton.x_root; dmsg.y = event->xbutton.y_root; - dmsg.index_atom = dpyinfo->Xatom_XdndSelection; + dmsg.index_atom = x_dnd_motif_atom; dmsg.source_window = FRAME_X_WINDOW (x_dnd_frame); if (!XM_DRAG_STYLE_IS_DROP_ONLY (drag_receiver_info.protocol_style)) @@ -18456,23 +20331,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } else - { - x_set_pending_dnd_time (event->xbutton.time); - x_dnd_send_unsupported_drop (dpyinfo, (x_dnd_last_seen_toplevel != None - ? x_dnd_last_seen_toplevel - : x_dnd_last_seen_window), - event->xbutton.x_root, event->xbutton.y_root, - event->xbutton.time); - } + x_dnd_send_unsupported_drop (dpyinfo, (x_dnd_last_seen_toplevel != None + ? x_dnd_last_seen_toplevel + : x_dnd_last_seen_window), + event->xbutton.x_root, event->xbutton.y_root, + event->xbutton.time); } else if (x_dnd_last_seen_toplevel != None) - { - x_set_pending_dnd_time (event->xbutton.time); - x_dnd_send_unsupported_drop (dpyinfo, x_dnd_last_seen_toplevel, - event->xbutton.x_root, - event->xbutton.y_root, - event->xbutton.time); - } + x_dnd_send_unsupported_drop (dpyinfo, x_dnd_last_seen_toplevel, + event->xbutton.x_root, + event->xbutton.y_root, + event->xbutton.time); x_dnd_last_protocol_version = -1; @@ -18512,7 +20381,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, { block_input (); XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), - RevertToParent, CurrentTime); + RevertToParent, event->xbutton.time); if (FRAME_PARENT_FRAME (f)) XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f)); unblock_input (); @@ -18708,6 +20577,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, case CirculateNotify: if (x_dnd_in_progress + /* When _NET_WM_CLIENT_LIST stacking is being used, changes + in that property are watched for, and it's not necessary + to update the state in response to ordinary window + substructure events. */ + && !x_dnd_use_toplevels && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)) x_dnd_update_state (dpyinfo, dpyinfo->last_user_time); goto OTHER; @@ -18737,6 +20611,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case DestroyNotify: + if (event->xdestroywindow.window + == dpyinfo->net_supported_window) + dpyinfo->net_supported_window = None; + xft_settings_event (dpyinfo, event); break; @@ -18752,11 +20630,14 @@ handle_one_xevent (struct x_display_info *dpyinfo, bool must_free_data = false; XIEvent *xi_event = (XIEvent *) event->xcookie.data; + /* Sometimes the event is already claimed by GTK, which will free its data in due course. */ - if (!xi_event && XGetEventData (dpyinfo->display, &event->xcookie)) + if (!xi_event) { - must_free_data = true; + if (XGetEventData (dpyinfo->display, &event->xcookie)) + must_free_data = true; + xi_event = (XIEvent *) event->xcookie.data; } @@ -18764,7 +20645,25 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!xi_event) { - eassert (!must_free_data); + /* It may turn out that the event data has already been + implicitly freed for various reasons up to and + including XMenuActivate pushing some other event onto + the foreign-event queue, or x_menu_wait_for_events + calling XNextEvent through a timer that tries to wait + for input. + + In that case, XGetEventData will return True, but + cookie->data will be NULL. Since handling such input + events is not really important, we can afford to + discard them. + + The way Xlib is currently implemented makes calling + XFreeEventData unnecessary in this case, but call it + anyway, since not doing so may lead to a memory leak in + the future. */ + + if (must_free_data) + XFreeEventData (dpyinfo->display, &event->xcookie); goto OTHER; } @@ -18772,11 +20671,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, { case XI_FocusIn: { - XIFocusInEvent *focusin = (XIFocusInEvent *) xi_event; - struct xi_device_t *source; + XIFocusInEvent *focusin; + focusin = (XIFocusInEvent *) xi_event; any = x_any_window_to_frame (dpyinfo, focusin->event); - source = xi_device_from_id (dpyinfo, focusin->sourceid); + #ifdef USE_GTK /* Some WMs (e.g. Mutter in Gnome Shell), don't unmap minimized/iconified windows; thus, for those WMs we won't get @@ -18805,24 +20704,19 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } - x_detect_focus_change (dpyinfo, any, event, &inev.ie); + xi_focus_handle_for_device (dpyinfo, any, xi_event); - if (inev.ie.kind != NO_EVENT && source) - inev.ie.device = source->name; goto XI_OTHER; } case XI_FocusOut: { - XIFocusOutEvent *focusout = (XIFocusOutEvent *) xi_event; - struct xi_device_t *source; + XIFocusOutEvent *focusout; + focusout = (XIFocusOutEvent *) xi_event; any = x_any_window_to_frame (dpyinfo, focusout->event); - source = xi_device_from_id (dpyinfo, focusout->sourceid); - x_detect_focus_change (dpyinfo, any, event, &inev.ie); + xi_focus_handle_for_device (dpyinfo, any, xi_event); - if (inev.ie.kind != NO_EVENT && source) - inev.ie.device = source->name; goto XI_OTHER; } @@ -18834,12 +20728,15 @@ handle_one_xevent (struct x_display_info *dpyinfo, any = x_top_window_to_frame (dpyinfo, enter->event); source = xi_device_from_id (dpyinfo, enter->sourceid); + ev.x = lrint (enter->event_x); ev.y = lrint (enter->event_y); ev.window = enter->event; ev.time = enter->time; + ev.send_event = enter->send_event; - x_display_set_last_user_time (dpyinfo, enter->time); + x_display_set_last_user_time (dpyinfo, enter->time, + enter->send_event); #ifdef USE_MOTIF use_copy = true; @@ -18868,7 +20765,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, are an inferiors of the frame's top window, which will get virtual events. */ if (any) - x_detect_focus_change (dpyinfo, any, event, &inev.ie); + xi_focus_handle_for_device (dpyinfo, any, xi_event); if (!any) any = x_any_window_to_frame (dpyinfo, enter->event); @@ -18918,7 +20815,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, case XI_Leave: { - XILeaveEvent *leave = (XILeaveEvent *) xi_event; + XILeaveEvent *leave; + struct xi_device_t *device; + + leave = (XILeaveEvent *) xi_event; #ifdef USE_GTK struct xi_device_t *source; XMotionEvent ev; @@ -18927,6 +20827,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, ev.y = lrint (leave->event_y); ev.window = leave->event; ev.time = leave->time; + ev.send_event = leave->send_event; #endif any = x_top_window_to_frame (dpyinfo, leave->event); @@ -18934,6 +20835,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, #ifdef USE_GTK source = xi_device_from_id (dpyinfo, leave->sourceid); #endif + device = xi_device_from_id (dpyinfo, leave->deviceid); + + if (device) + xi_report_motion_window_clear (device); /* This allows us to catch LeaveNotify events generated by popup menu grabs. FIXME: this is right when there is a @@ -18988,7 +20893,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, leave->deviceid, false); #endif - x_display_set_last_user_time (dpyinfo, leave->time); + x_display_set_last_user_time (dpyinfo, leave->time, + leave->send_event); #ifdef HAVE_XWIDGETS { @@ -19006,7 +20912,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif if (any) - x_detect_focus_change (dpyinfo, any, event, &inev.ie); + xi_focus_handle_for_device (dpyinfo, any, xi_event); #ifndef USE_X_TOOLKIT f = x_top_window_to_frame (dpyinfo, leave->event); @@ -19028,8 +20934,22 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!f) f = x_top_window_to_frame (dpyinfo, leave->event); #endif + if (f) { + /* Now clear dpyinfo->last_mouse_motion_frame, or + gui_redo_mouse_highlight will end up highlighting + the last known position of the mouse if a + tooltip frame is later unmapped. */ + + if (f == dpyinfo->last_mouse_motion_frame) + dpyinfo->last_mouse_motion_frame = NULL; + + /* Something similar applies to + dpyinfo->last_mouse_glyph_frame. */ + if (f == dpyinfo->last_mouse_glyph_frame) + dpyinfo->last_mouse_glyph_frame = NULL; + if (f == hlinfo->mouse_face_mouse_frame) { /* If we move outside the frame, then we're @@ -19075,7 +20995,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, xm_top_level_leave_message lmsg; xm_top_level_enter_message emsg; xm_drag_motion_message dmsg; - int dnd_state; + unsigned int dnd_state; source = xi_device_from_id (dpyinfo, xev->sourceid); @@ -19119,9 +21039,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, bar = NULL; - /* See the comment on top of - x_init_master_valuators for more details on how - scroll wheel movement is reported on XInput 2. */ + /* See the comment on top of x_cache_xi_devices + for more details on how scroll wheel movement + is reported on XInput 2. */ delta = x_get_scroll_valuator_delta (dpyinfo, device, i, *values, &val); values++; @@ -19241,18 +21161,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, #ifdef HAVE_XWIDGETS if (xv) { - uint state = xev->mods.effective; - x_display_set_last_user_time (dpyinfo, xev->time); + unsigned int state; - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - state |= Button3Mask; - } + state = xi_convert_event_state (xev); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); if (found_valuator) xwidget_scroll (xv, xev->event_x, xev->event_y, @@ -19271,7 +21184,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif if (found_valuator) { - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); + #if defined USE_GTK && !defined HAVE_GTK3 /* Unlike on Motif, we can't select for XI @@ -19287,6 +21202,21 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto XI_OTHER; #endif + /* If this happened during a drag-and-drop + operation, don't send an event. We only have + to set the user time. */ + if (x_dnd_in_progress + /* If another master device moved the + pointer, we should put a wheel event on + the keyboard buffer as usual. It will be + run once the drag-and-drop operation + completes. */ + && xev->deviceid == x_dnd_pointer_device + && (command_loop_level + minibuf_level + <= x_dnd_recursion_depth) + && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)) + goto XI_OTHER; + if (fabs (total_x) > 0 || fabs (total_y) > 0) { inev.ie.kind = (fabs (total_y) >= fabs (total_x) @@ -19328,10 +21258,14 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif #endif /* HAVE_XINPUT2_1 */ + if (!xi_position_changed (device, xev)) + goto XI_OTHER; + ev.x = lrint (xev->event_x); ev.y = lrint (xev->event_y); ev.window = xev->event; ev.time = xev->time; + ev.send_event = xev->send_event; #ifdef USE_MOTIF use_copy = true; @@ -19348,17 +21282,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, copy.xmotion.y = lrint (xev->event_y); copy.xmotion.x_root = lrint (xev->root_x); copy.xmotion.y_root = lrint (xev->root_y); - copy.xmotion.state = 0; - - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy.xmotion.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy.xmotion.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy.xmotion.state |= Button3Mask; - } + copy.xmotion.state = xi_convert_event_state (xev); copy.xmotion.is_hint = False; copy.xmotion.same_screen = True; @@ -19383,6 +21307,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, `x-dnd-movement-function`. */ && (command_loop_level + minibuf_level <= x_dnd_recursion_depth) + && xev->deviceid == x_dnd_pointer_device && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)) { Window target, toplevel; @@ -19424,13 +21349,19 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } - target = x_dnd_get_target_window (dpyinfo, - xev->root_x, - xev->root_y, - &target_proto, - &motif_style, - &toplevel, - &was_frame); + if (xev->root == dpyinfo->root_window) + target = x_dnd_get_target_window (dpyinfo, + xev->root_x, + xev->root_y, + &target_proto, + &motif_style, + &toplevel, + &was_frame); + else + target = x_dnd_fill_empty_target (&target_proto, + &motif_style, + &toplevel, + &was_frame); if (toplevel != x_dnd_last_seen_toplevel) { @@ -19457,7 +21388,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, XM_DRAG_REASON_TOP_LEVEL_LEAVE); lmsg.byteorder = XM_BYTE_ORDER_CUR_FIRST; lmsg.zero = 0; - lmsg.timestamp = event->xmotion.time; + lmsg.timestamp = xev->time; lmsg.source_window = FRAME_X_WINDOW (x_dnd_frame); if (x_dnd_motif_setup_p) @@ -19549,7 +21480,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, emsg.zero = 0; emsg.timestamp = xev->time; emsg.source_window = FRAME_X_WINDOW (x_dnd_frame); - emsg.index_atom = dpyinfo->Xatom_XdndSelection; + emsg.index_atom = x_dnd_motif_atom; if (x_dnd_motif_setup_p) xm_send_top_level_enter_message (dpyinfo, FRAME_X_WINDOW (x_dnd_frame), @@ -19562,17 +21493,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, xev->root_x, xev->root_y); else if (x_dnd_last_protocol_version != -1 && target != None) { - dnd_state = xev->mods.effective; - - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - dnd_state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - dnd_state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - dnd_state |= Button3Mask; - } + dnd_state = xi_convert_event_state (xev); x_dnd_send_position (x_dnd_frame, target, x_dnd_last_protocol_version, @@ -19689,7 +21610,15 @@ handle_one_xevent (struct x_display_info *dpyinfo, has changed, generate a HELP_EVENT. */ if (!NILP (help_echo_string) || !NILP (previous_help_echo_string)) - do_help = 1; + { + /* Also allow the focus and client pointer to be + adjusted accordingly, in case a help tooltip is + shown. */ + gen_help_device = device; + gen_help_time = xev->time; + + do_help = 1; + } goto XI_OTHER; } @@ -19713,46 +21642,82 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (x_dnd_in_progress && (command_loop_level + minibuf_level <= x_dnd_recursion_depth) + && xev->deviceid == x_dnd_pointer_device && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)) { f = mouse_or_wdesc_frame (dpyinfo, xev->event); + device = xi_device_from_id (dpyinfo, xev->deviceid); - if (xev->evtype == XI_ButtonPress) + /* Don't track grab status for emulated pointer + events, because they are ignored by the regular + mouse click processing code. */ +#ifdef XIPointerEmulated + if (!(xev->flags & XIPointerEmulated)) { - dpyinfo->grabbed |= (1 << xev->detail); - dpyinfo->last_mouse_frame = f; - if (f && !tab_bar_p) - f->last_tab_bar_item = -1; +#endif + if (xev->evtype == XI_ButtonPress) + { + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); + + dpyinfo->grabbed |= (1 << xev->detail); + dpyinfo->last_mouse_frame = f; + + if (device) + device->grab |= (1 << xev->detail); + + if (f) + f->last_tab_bar_item = -1; #if ! defined (USE_GTK) - if (f && !tool_bar_p) - f->last_tool_bar_item = -1; + if (f) + f->last_tool_bar_item = -1; #endif /* not USE_GTK */ + } + else + { + dpyinfo->grabbed &= ~(1 << xev->detail); + if (device) + device->grab &= ~(1 << xev->detail); + } +#ifdef XIPointerEmulated } - else - dpyinfo->grabbed &= ~(1 << xev->detail); +#endif + + if (f && device) + xi_handle_interaction (dpyinfo, f, device, + xev->time); if (xev->evtype == XI_ButtonPress - && x_dnd_last_seen_window != None - && x_dnd_last_protocol_version != -1) + && x_dnd_last_seen_window != None) { - dnd_state = xev->mods.effective; + dnd_state = xi_convert_event_state (xev); - if (xev->buttons.mask_len) + if (x_dnd_last_window_is_frame) { - if (XIMaskIsSet (xev->buttons.mask, 1)) - dnd_state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - dnd_state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - dnd_state |= Button3Mask; +#ifdef XI_PointerEmulated + /* Set the last user time here even if this + is an emulated button event, since + something happened in response. */ + + if (xev->flags & XIPointerEmulated) + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); +#endif + x_dnd_note_self_wheel (dpyinfo, + x_dnd_last_seen_window, + xev->root_x, xev->root_y, + xev->detail, dnd_state, + xev->time); } - - x_dnd_send_position (x_dnd_frame, x_dnd_last_seen_window, - x_dnd_last_protocol_version, xev->root_x, - xev->root_y, x_dnd_selection_timestamp, - x_dnd_wanted_action, xev->detail, dnd_state); - - goto XI_OTHER; + else + x_dnd_send_position (x_dnd_frame, + x_dnd_last_seen_window, + x_dnd_last_protocol_version, + xev->root_x, xev->root_y, + xev->time, x_dnd_wanted_action, + xev->detail, dnd_state); + + goto OTHER; } if (xev->evtype == XI_ButtonRelease) @@ -19800,9 +21765,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_dnd_waiting_for_finish_proto = x_dnd_last_protocol_version; x_dnd_waiting_for_finish - = x_dnd_send_drop (x_dnd_frame, x_dnd_last_seen_window, - x_dnd_selection_timestamp, - x_dnd_last_protocol_version); + = x_dnd_do_drop (x_dnd_last_seen_window, + x_dnd_last_protocol_version); x_dnd_finish_display = dpyinfo->display; } else if (x_dnd_last_seen_window != None) @@ -19812,6 +21776,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!xm_read_drag_receiver_info (dpyinfo, x_dnd_last_seen_window, &drag_receiver_info) + && !x_dnd_disable_motif_protocol && drag_receiver_info.protocol_style != XM_DRAG_STYLE_NONE && (x_dnd_allow_current_frame || x_dnd_last_seen_window != FRAME_OUTER_WINDOW (x_dnd_frame))) @@ -19845,7 +21810,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, instances of Emacs try to drag into the same window at the same time. */ - dmsg.index_atom = dpyinfo->Xatom_XdndSelection; + dmsg.index_atom = x_dnd_motif_atom; dmsg.source_window = FRAME_X_WINDOW (x_dnd_frame); if (!XM_DRAG_STYLE_IS_DROP_ONLY (drag_receiver_info.protocol_style)) @@ -19863,22 +21828,16 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } else - { - x_set_pending_dnd_time (xev->time); - x_dnd_send_unsupported_drop (dpyinfo, (x_dnd_last_seen_toplevel != None - ? x_dnd_last_seen_toplevel - : x_dnd_last_seen_window), - xev->root_x, xev->root_y, xev->time); - } + x_dnd_send_unsupported_drop (dpyinfo, (x_dnd_last_seen_toplevel != None + ? x_dnd_last_seen_toplevel + : x_dnd_last_seen_window), + xev->root_x, xev->root_y, xev->time); } else if (x_dnd_last_seen_toplevel != None) - { - x_set_pending_dnd_time (xev->time); - x_dnd_send_unsupported_drop (dpyinfo, - x_dnd_last_seen_toplevel, - xev->root_x, xev->root_y, - xev->time); - } + x_dnd_send_unsupported_drop (dpyinfo, + x_dnd_last_seen_toplevel, + xev->root_x, xev->root_y, + xev->time); x_dnd_last_protocol_version = -1; x_dnd_last_motif_style = XM_DRAG_STYLE_NONE; @@ -19918,19 +21877,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, copy.xbutton.y = lrint (xev->event_y); copy.xbutton.x_root = lrint (xev->root_x); copy.xbutton.y_root = lrint (xev->root_y); - copy.xbutton.state = xev->mods.effective; + copy.xbutton.state = xi_convert_event_state (xev); copy.xbutton.button = xev->detail; copy.xbutton.same_screen = True; - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy.xbutton.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy.xbutton.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy.xbutton.state |= Button3Mask; - } #elif defined USE_GTK && !defined HAVE_GTK3 copy = gdk_event_new (xev->evtype == XI_ButtonPress ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE); @@ -19942,19 +21892,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, copy->button.y = xev->event_y; copy->button.x_root = xev->root_x; copy->button.y_root = xev->root_y; - copy->button.state = xev->mods.effective; + copy->button.state = xi_convert_event_state (xev); copy->button.button = xev->detail; - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy->button.state |= GDK_BUTTON1_MASK; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy->button.state |= GDK_BUTTON2_MASK; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy->button.state |= GDK_BUTTON3_MASK; - } - if (!copy->button.window) emacs_abort (); @@ -19989,17 +21929,27 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif if (xev->evtype == XI_ButtonPress) - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); source = xi_device_from_id (dpyinfo, xev->sourceid); + device = xi_device_from_id (dpyinfo, xev->deviceid); #ifdef HAVE_XWIDGETS xvw = xwidget_view_from_window (xev->event); if (xvw) { + /* If the user interacts with a frame that's focused + on another device, but not the current focus + frame, make it the focus frame. */ + if (device) + xi_handle_interaction (dpyinfo, xvw->frame, + device, xev->time); + xwidget_button (xvw, xev->evtype == XI_ButtonPress, lrint (xev->event_x), lrint (xev->event_y), - xev->detail, xev->mods.effective, xev->time); + xev->detail, xi_convert_event_state (xev), + xev->time); if (!EQ (selected_window, xvw->w) && (xev->detail < 4)) { @@ -20015,8 +21965,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif - device = xi_device_from_id (dpyinfo, xev->deviceid); - if (!device) goto XI_OTHER; @@ -20025,7 +21973,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, bv.x = lrint (xev->event_x); bv.y = lrint (xev->event_y); bv.window = xev->event; - bv.state = xev->mods.effective; + bv.state = xi_convert_event_state (xev); bv.time = xev->time; dpyinfo->last_mouse_glyph_frame = NULL; @@ -20046,14 +21994,42 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf))) { block_input (); +#if defined HAVE_GTK3 || (!defined USE_GTK && !defined USE_X_TOOLKIT) + if (device) + { + /* This can generate XI_BadDevice if the + device's attachment was destroyed + server-side. */ + x_ignore_errors_for_next_request (dpyinfo); + XISetFocus (dpyinfo->display, device->attachment, + /* Note that the input extension + only supports RevertToParent-type + behavior. */ + FRAME_OUTER_WINDOW (f), xev->time); + x_stop_ignoring_errors (dpyinfo); + } +#else + /* Non-no toolkit builds without GTK 3 use core + events to handle focus. */ XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), - RevertToParent, CurrentTime); + RevertToParent, xev->time); +#endif if (FRAME_PARENT_FRAME (f)) XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f)); unblock_input (); } } + if (f) + { + /* If the user interacts with a frame that's focused + on another device, but not the current focus + frame, make it the focus frame. */ + if (device) + xi_handle_interaction (dpyinfo, f, device, + xev->time); + } + #ifdef USE_GTK if (!f) { @@ -20316,22 +22292,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, copy.xkey.time = xev->time; copy.xkey.state = ((xev->mods.effective & ~(1 << 13 | 1 << 14)) | (xev->group.effective << 13)); + xi_convert_button_state (&xev->buttons, ©.xkey.state); copy.xkey.x = lrint (xev->event_x); copy.xkey.y = lrint (xev->event_y); copy.xkey.x_root = lrint (xev->root_x); copy.xkey.y_root = lrint (xev->root_y); - - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - copy.xkey.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - copy.xkey.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - copy.xkey.state |= Button3Mask; - } - copy.xkey.keycode = xev->detail; copy.xkey.same_screen = True; #endif @@ -20339,11 +22305,31 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); ignore_next_mouse_click_timeout = 0; f = x_any_window_to_frame (dpyinfo, xev->event); + /* GTK handles TAB events in an undesirable manner, so + keyboard events are always dropped. But as a side + effect, the user time will no longer be set by GDK, + so do that manually. */ +#ifdef USE_GTK + if (f) + x_set_gtk_user_time (f, xev->time); +#endif + + if (f) + { + /* If the user interacts with a frame that's focused + on another device, but not the current focus + frame, make it the focus frame. */ + if (device) + xi_handle_interaction (dpyinfo, f, device, + xev->time); + } + XKeyPressedEvent xkey; memset (&xkey, 0, sizeof xkey); @@ -20366,15 +22352,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, /* Some input methods react differently depending on the buttons that are pressed. */ - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - xkey.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - xkey.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - xkey.state |= Button3Mask; - } + xi_convert_button_state (&xev->buttons, &xkey.state); xkey.keycode = xev->detail; xkey.same_screen = True; @@ -20429,27 +22407,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, #ifdef HAVE_XKB if (dpyinfo->xkb_desc) { - XkbDescRec *rec = dpyinfo->xkb_desc; - - if (rec->map->modmap && rec->map->modmap[xev->detail]) - goto xi_done_keysym; - } - else -#endif - { - if (dpyinfo->modmap) - { - for (i = 0; i < 8 * dpyinfo->modmap->max_keypermod; i++) - { - if (xev->detail == dpyinfo->modmap->modifiermap[i]) - goto xi_done_keysym; - } - } - } - -#ifdef HAVE_XKB - if (dpyinfo->xkb_desc) - { uint xkb_state = state; xkb_state &= ~(1 << 13 | 1 << 14); xkb_state |= xev->group.effective << 13; @@ -20502,8 +22459,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif XSETFRAME (inev.ie.frame_or_window, f); - inev.ie.modifiers - = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), state); inev.ie.timestamp = xev->time; #ifdef HAVE_X_I18N @@ -20582,8 +22537,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, } } + inev.ie.modifiers = x_x_to_emacs_modifiers (dpyinfo, state); + #ifdef XK_F1 - if (x_dnd_in_progress && keysym == XK_F1) + if (x_dnd_in_progress + && xev->deviceid == x_dnd_keyboard_device + && keysym == XK_F1) { x_dnd_xm_use_help = true; goto xi_done_keysym; @@ -20806,15 +22765,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, /* Some input methods react differently depending on the buttons that are pressed. */ - if (xev->buttons.mask_len) - { - if (XIMaskIsSet (xev->buttons.mask, 1)) - xkey.state |= Button1Mask; - if (XIMaskIsSet (xev->buttons.mask, 2)) - xkey.state |= Button2Mask; - if (XIMaskIsSet (xev->buttons.mask, 3)) - xkey.state |= Button3Mask; - } + xi_convert_button_state (&xev->buttons, &xkey.state); xkey.keycode = xev->detail; xkey.same_screen = True; @@ -20855,14 +22806,14 @@ handle_one_xevent (struct x_display_info *dpyinfo, case XI_HierarchyChanged: { - XIHierarchyEvent *hev = (XIHierarchyEvent *) xi_event; + XIHierarchyEvent *hev; XIDeviceInfo *info; - int i, j, ndevices, n_disabled, *disabled; - struct xi_device_t *device, *devices; -#ifdef HAVE_XINPUT2_2 - struct xi_touch_point_t *tem, *last; -#endif + int i, ndevices, n_disabled, *disabled; + struct xi_device_t *device; + bool any_changed; + any_changed = false; + hev = (XIHierarchyEvent *) xi_event; disabled = SAFE_ALLOCA (sizeof *disabled * hev->num_info); n_disabled = 0; @@ -20872,44 +22823,16 @@ handle_one_xevent (struct x_display_info *dpyinfo, { /* Handle all disabled devices now, to prevent things happening out-of-order later. */ - if (n_disabled) - { - ndevices = 0; - devices = xmalloc (sizeof *devices * dpyinfo->num_devices); - - for (i = 0; i < dpyinfo->num_devices; ++i) - { - for (j = 0; j < n_disabled; ++j) - { - if (disabled[j] == dpyinfo->devices[i].device_id) - { -#ifdef HAVE_XINPUT2_1 - xfree (dpyinfo->devices[i].valuators); -#endif -#ifdef HAVE_XINPUT2_2 - tem = dpyinfo->devices[i].touchpoints; - while (tem) - { - last = tem; - tem = tem->next; - xfree (last); - } -#endif - goto continue_detachment; - } - } - - devices[ndevices++] = dpyinfo->devices[i]; - - continue_detachment: - continue; - } - - xfree (dpyinfo->devices); - dpyinfo->devices = devices; - dpyinfo->num_devices = ndevices; + if (ndevices) + { + xi_disable_devices (dpyinfo, disabled, n_disabled); n_disabled = 0; + + /* This flag really just means that disabled + devices were handled early and should be + used in conjunction with n_disabled. */ + any_changed = true; } x_catch_errors (dpyinfo->display); @@ -20922,6 +22845,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, dpyinfo->devices = xrealloc (dpyinfo->devices, (sizeof *dpyinfo->devices * ++dpyinfo->num_devices)); + memset (dpyinfo->devices + dpyinfo->num_devices - 1, + 0, sizeof *dpyinfo->devices); device = &dpyinfo->devices[dpyinfo->num_devices - 1]; xi_populate_device_from_info (device, info); } @@ -20942,184 +22867,53 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (info) { - if (device && info->enabled) - device->use = info->use; - else if (device) - disabled[n_disabled++] = hev->info[i].deviceid; + if (device) + { + device->use = info->use; + device->attachment = info->attachment; + } + + /* device could have been disabled by now. + But instead of removing it immediately, + wait for XIDeviceDisabled, or internal + state could be left inconsistent. */ XIFreeDeviceInfo (info); } } } - if (n_disabled) - { - ndevices = 0; - devices = xmalloc (sizeof *devices * dpyinfo->num_devices); + /* Delete all devices that were disabled by this + event. */ + xi_disable_devices (dpyinfo, disabled, n_disabled); - for (i = 0; i < dpyinfo->num_devices; ++i) - { - for (j = 0; j < n_disabled; ++j) - { - if (disabled[j] == dpyinfo->devices[i].device_id) - { -#ifdef HAVE_XINPUT2_1 - xfree (dpyinfo->devices[i].valuators); -#endif -#ifdef HAVE_XINPUT2_2 - tem = dpyinfo->devices[i].touchpoints; - while (tem) - { - last = tem; - tem = tem->next; - xfree (last); - } -#endif - goto break_detachment; - } - } - - devices[ndevices++] = dpyinfo->devices[i]; - - break_detachment: - continue; - } - - xfree (dpyinfo->devices); - dpyinfo->devices = devices; - dpyinfo->num_devices = ndevices; - } + /* If the device hierarchy has been changed, recompute + focus. This might seem like a micro-optimization but + it actually keeps the focus from changing in some + cases where it would be undesierable. */ + if (any_changed || n_disabled) + xi_handle_focus_change (dpyinfo); goto XI_OTHER; } case XI_DeviceChanged: { - XIDeviceChangedEvent *device_changed = (XIDeviceChangedEvent *) xi_event; + XIDeviceChangedEvent *device_changed; struct xi_device_t *device; -#ifdef HAVE_XINPUT2_2 - struct xi_touch_point_t *tem, *last; -#endif - int c; -#ifdef HAVE_XINPUT2_1 - int i; -#endif + device_changed = (XIDeviceChangedEvent *) xi_event; device = xi_device_from_id (dpyinfo, device_changed->deviceid); - if (!device) - { - /* An existing device might have been enabled. */ - x_init_master_valuators (dpyinfo); - - /* Now try to find the device again, in case it was - just enabled. */ - device = xi_device_from_id (dpyinfo, device_changed->deviceid); - } - - /* If it wasn't enabled, then stop handling this event. */ + /* If the device isn't enabled, then stop handling this + event. A HierarchyChanged event will be sent if it + is enabled afterwards. */ if (!device) goto XI_OTHER; - /* Free data that we will regenerate from new - information. */ -#ifdef HAVE_XINPUT2_1 - device->valuators = xrealloc (device->valuators, - (device_changed->num_classes - * sizeof *device->valuators)); - device->scroll_valuator_count = 0; -#endif -#ifdef HAVE_XINPUT2_2 - device->direct_p = false; -#endif - - for (c = 0; c < device_changed->num_classes; ++c) - { - switch (device_changed->classes[c]->type) - { -#ifdef HAVE_XINPUT2_1 - case XIScrollClass: - { - XIScrollClassInfo *info; - - info = (XIScrollClassInfo *) device_changed->classes[c]; - struct xi_scroll_valuator_t *valuator; - - valuator = &device->valuators[device->scroll_valuator_count++]; - valuator->horizontal - = (info->scroll_type == XIScrollTypeHorizontal); - valuator->invalid_p = true; - valuator->emacs_value = DBL_MIN; - valuator->increment = info->increment; - valuator->number = info->number; - - break; - } -#endif - -#ifdef HAVE_XINPUT2_2 - case XITouchClass: - { - XITouchClassInfo *info; - - info = (XITouchClassInfo *) device_changed->classes[c]; - device->direct_p = info->mode == XIDirectTouch; - } -#endif - default: - break; - } - } - -#ifdef HAVE_XINPUT2_1 - for (c = 0; c < device_changed->num_classes; ++c) - { - if (device_changed->classes[c]->type == XIValuatorClass) - { - XIValuatorClassInfo *info; - - info = (XIValuatorClassInfo *) device_changed->classes[c]; - - for (i = 0; i < device->scroll_valuator_count; ++i) - { - if (device->valuators[i].number == info->number) - { - device->valuators[i].invalid_p = false; - device->valuators[i].current_value = info->value; - - /* Make sure that this is reset if the - pointer moves into a window of ours. - - Otherwise the valuator state could be - left invalid if the DeviceChange - event happened with the pointer - outside any Emacs frame. */ - device->valuators[i].pending_enter_reset = true; - } - } - } - } -#endif - -#ifdef HAVE_XINPUT2_2 - /* The device is no longer a DirectTouch device, so - remove any touchpoints that we might have - recorded. */ - if (!device->direct_p) - { - tem = device->touchpoints; - - while (tem) - { - last = tem; - tem = tem->next; - xfree (last); - } - - device->touchpoints = NULL; - } -#endif - + /* Now handle the event by retrieving scroll valuators + and touch info. */ + xi_handle_device_changed (dpyinfo, device, device_changed); goto XI_OTHER; } @@ -21133,7 +22927,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif device = xi_device_from_id (dpyinfo, xev->deviceid); source = xi_device_from_id (dpyinfo, xev->sourceid); - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); if (!device) goto XI_OTHER; @@ -21141,7 +22936,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (xi_find_touch_point (device, xev->detail)) emacs_abort (); - f = x_any_window_to_frame (dpyinfo, xev->event); + f = x_window_to_frame (dpyinfo, xev->event); #ifdef HAVE_GTK3 menu_bar_p = (f && FRAME_X_OUTPUT (f)->menubar_widget @@ -21162,11 +22957,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (!menu_bar_p && !tool_bar_p) { - x_catch_errors (dpyinfo->display); - if (f && device->direct_p) { *finish = X_EVENT_DROP; + + x_catch_errors (dpyinfo->display); + if (x_input_grab_touch_events) XIAllowTouchEvents (dpyinfo->display, xev->deviceid, xev->detail, xev->event, XIAcceptTouch); @@ -21186,13 +22982,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (source) inev.ie.device = source->name; } + + x_uncatch_errors (); } #ifndef HAVE_GTK3 else if (x_input_grab_touch_events) - XIAllowTouchEvents (dpyinfo->display, xev->deviceid, - xev->detail, xev->event, XIRejectTouch); + { + x_ignore_errors_for_next_request (dpyinfo); + XIAllowTouchEvents (dpyinfo->display, xev->deviceid, + xev->detail, xev->event, XIRejectTouch); + x_stop_ignoring_errors (dpyinfo); + } #endif - x_uncatch_errors (); } else { @@ -21219,7 +23020,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, device = xi_device_from_id (dpyinfo, xev->deviceid); source = xi_device_from_id (dpyinfo, xev->sourceid); - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); if (!device) goto XI_OTHER; @@ -21232,7 +23034,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, touchpoint->x = xev->event_x; touchpoint->y = xev->event_y; - f = x_any_window_to_frame (dpyinfo, xev->event); + f = x_window_to_frame (dpyinfo, xev->event); if (f && device->direct_p) { @@ -21265,7 +23067,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, device = xi_device_from_id (dpyinfo, xev->deviceid); source = xi_device_from_id (dpyinfo, xev->sourceid); - x_display_set_last_user_time (dpyinfo, xev->time); + x_display_set_last_user_time (dpyinfo, xev->time, + xev->send_event); if (!device) goto XI_OTHER; @@ -21274,7 +23077,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (unlinked_p) { - f = x_any_window_to_frame (dpyinfo, xev->event); + f = x_window_to_frame (dpyinfo, xev->event); if (f && device->direct_p) { @@ -21305,7 +23108,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, device = xi_device_from_id (dpyinfo, pev->deviceid); source = xi_device_from_id (dpyinfo, pev->sourceid); - x_display_set_last_user_time (dpyinfo, pev->time); + x_display_set_last_user_time (dpyinfo, pev->time, + pev->send_event); if (!device) goto XI_OTHER; @@ -21321,7 +23125,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif - any = x_any_window_to_frame (dpyinfo, pev->event); + any = x_window_to_frame (dpyinfo, pev->event); if (any) { inev.ie.kind = PINCH_EVENT; @@ -21394,6 +23198,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (xkbevent->any.xkb_type == XkbNewKeyboardNotify || xkbevent->any.xkb_type == XkbMapNotify) { + XkbRefreshKeyboardMapping (&xkbevent->map); + if (dpyinfo->xkb_desc) { if (XkbGetUpdatedMap (dpyinfo->display, @@ -21402,11 +23208,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, | XkbModifierMapMask | XkbVirtualModsMask), dpyinfo->xkb_desc) == Success) - { - XkbGetNames (dpyinfo->display, - XkbGroupNamesMask | XkbVirtualModNamesMask, - dpyinfo->xkb_desc); - } + XkbGetNames (dpyinfo->display, + XkbGroupNamesMask | XkbVirtualModNamesMask, + dpyinfo->xkb_desc); else { XkbFreeKeyboard (dpyinfo->xkb_desc, XkbAllComponentsMask, True); @@ -21428,7 +23232,6 @@ handle_one_xevent (struct x_display_info *dpyinfo, dpyinfo->xkb_desc); } - XkbRefreshKeyboardMapping (&xkbevent->map); x_find_modifier_meanings (dpyinfo); } else if (x_dnd_in_progress @@ -21661,14 +23464,13 @@ handle_one_xevent (struct x_display_info *dpyinfo, || event->type == (dpyinfo->xrandr_event_base + RRNotify))) { - union buffered_input_event *ev; Time timestamp; Lisp_Object current_monitors; XRRScreenChangeNotifyEvent *notify; if (event->type == (dpyinfo->xrandr_event_base + RRScreenChangeNotify)) - XRRUpdateConfiguration (event); + XRRUpdateConfiguration ((XEvent *) event); if (event->type == (dpyinfo->xrandr_event_base + RRScreenChangeNotify)) @@ -21689,13 +23491,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, else timestamp = 0; - ev = (kbd_store_ptr == kbd_buffer - ? kbd_buffer + KBD_BUFFER_SIZE - 1 - : kbd_store_ptr - 1); - - if (kbd_store_ptr != kbd_fetch_ptr - && ev->ie.kind == MONITORS_CHANGED_EVENT - && XTERMINAL (ev->ie.arg) == dpyinfo->terminal) + if (x_find_monitors_changed_event (dpyinfo)) /* Don't store a MONITORS_CHANGED_EVENT if there is already an undelivered event on the queue. */ goto OTHER; @@ -21783,6 +23579,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (do_help > 0) { any_help_event_p = true; +#ifdef HAVE_XINPUT2 + if (gen_help_device) + xi_handle_interaction (dpyinfo, f, + gen_help_device, + gen_help_time); +#endif gen_help_event (help_echo_string, frame, help_echo_window, help_echo_object, help_echo_pos); } @@ -21866,6 +23668,8 @@ XTread_socket (struct terminal *terminal, struct input_event *hold_quit) && dpyinfo->display == x_dnd_finish_display))) return 0; + x_clean_failable_requests (dpyinfo); + block_input (); /* For debugging, this gives a way to fake an I/O error. */ @@ -22258,8 +24062,6 @@ x_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, int x, xic_set_preeditarea (w, x, y); #endif } - - XFlush (FRAME_X_DISPLAY (f)); } @@ -22488,7 +24290,13 @@ x_error_catcher (Display *display, XErrorEvent *event, always skips an XSync to the server, and should be used only immediately after x_had_errors_p or x_check_errors, or when it is known that no requests have been made since the last x_catch_errors - call for DPY. */ + call for DPY. + + There is no need to use this mechanism for ignoring errors from + single asynchronous requests, such as sending a ClientMessage to a + window that might no longer exist. Use + x_ignore_errors_for_next_request (paired with + x_stop_ignoring_errors) instead. */ void x_catch_errors_with_handler (Display *dpy, x_special_error_handler handler, @@ -22501,7 +24309,7 @@ x_catch_errors_with_handler (Display *dpy, x_special_error_handler handler, data->handler = handler; data->handler_data = handler_data; data->prev = x_error_message; - data->first_request = NextRequest (dpy); + data->first_request = XNextRequest (dpy); x_error_message = data; ++x_error_message_count; @@ -22513,6 +24321,135 @@ x_catch_errors (Display *dpy) x_catch_errors_with_handler (dpy, NULL, NULL); } +/* Return if errors for REQUEST should be ignored even if there is no + error handler applied. */ +static struct x_failable_request * +x_request_can_fail (struct x_display_info *dpyinfo, + unsigned long request) +{ + struct x_failable_request *failable_requests; + + for (failable_requests = dpyinfo->failable_requests; + failable_requests < dpyinfo->next_failable_request; + failable_requests++) + { + if (X_COMPARE_SERIALS (request, >=, + failable_requests->start) + && (!failable_requests->end + || X_COMPARE_SERIALS (request, <=, + failable_requests->end))) + return failable_requests; + } + + return NULL; +} + +/* Remove outdated request serials from + dpyinfo->failable_requests. */ +static void +x_clean_failable_requests (struct x_display_info *dpyinfo) +{ + struct x_failable_request *first, *last; + + last = dpyinfo->next_failable_request; + + for (first = dpyinfo->failable_requests; first < last; first++) + { + if (X_COMPARE_SERIALS (first->start, >, + LastKnownRequestProcessed (dpyinfo->display)) + || !first->end + || X_COMPARE_SERIALS (first->end, >, + LastKnownRequestProcessed (dpyinfo->display))) + break; + } + + if (first != last) + memmove (&dpyinfo->failable_requests, first, + sizeof *first * (last - first)); + + dpyinfo->next_failable_request = (dpyinfo->failable_requests + + (last - first)); +} + +void +x_ignore_errors_for_next_request (struct x_display_info *dpyinfo) +{ + struct x_failable_request *request, *max; + unsigned long next_request; +#ifdef HAVE_GTK3 + GdkDisplay *gdpy; + + /* GTK 3 tends to override our own error handler inside certain + callbacks, which this can be called from. Instead of trying to + restore our own, add a trap for the following requests with + GDK as well. */ + + gdpy = gdk_x11_lookup_xdisplay (dpyinfo->display); + + if (gdpy) + gdk_x11_display_error_trap_push (gdpy); +#endif + + if ((dpyinfo->next_failable_request + != dpyinfo->failable_requests) + && (dpyinfo->next_failable_request - 1)->end == 0) + /* A new sequence should never be started before an old one + finishes. Use `x_catch_errors' to nest error handlers. */ + emacs_abort (); + + request = dpyinfo->next_failable_request; + max = dpyinfo->failable_requests + N_FAILABLE_REQUESTS; + next_request = XNextRequest (dpyinfo->display); + + if (request >= max) + { + /* There is no point in making this extra sync if all requests + are known to have been fully processed. */ + if ((LastKnownRequestProcessed (dpyinfo->display) + != next_request - 1)) + XSync (dpyinfo->display, False); + + x_clean_failable_requests (dpyinfo); + request = dpyinfo->next_failable_request; + } + + if (request >= max) + /* A request should always be made immediately after calling this + function. */ + emacs_abort (); + + request->start = next_request; + request->end = 0; + + dpyinfo->next_failable_request++; +} + +void +x_stop_ignoring_errors (struct x_display_info *dpyinfo) +{ + struct x_failable_request *range; +#ifdef HAVE_GTK3 + GdkDisplay *gdpy; +#endif + + range = dpyinfo->next_failable_request - 1; + range->end = XNextRequest (dpyinfo->display) - 1; + + /* Abort if no request was made since + `x_ignore_errors_for_next_request'. */ + + if (X_COMPARE_SERIALS (range->end, <, + range->start)) + emacs_abort (); + +#ifdef HAVE_GTK3 + gdpy = gdk_x11_lookup_xdisplay (dpyinfo->display); + + if (gdpy) + gdk_x11_display_error_trap_pop_ignored (gdpy); +#endif +} + /* Undo the last x_catch_errors call. DPY should be the display that was passed to x_catch_errors. @@ -22541,6 +24478,7 @@ void x_uncatch_errors (void) { struct x_error_message_stack *tmp; + struct x_display_info *dpyinfo; /* In rare situations when running Emacs run in daemon mode, shutting down an emacsclient via delete-frame can cause @@ -22551,18 +24489,23 @@ x_uncatch_errors (void) block_input (); + dpyinfo = x_display_info_for_display (x_error_message->dpy); + /* The display may have been closed before this function is called. Check if it is still open before calling XSync. */ - if (x_display_info_for_display (x_error_message->dpy) != 0 + if (dpyinfo != 0 /* There is no point in making this extra sync if all requests are known to have been fully processed. */ && (LastKnownRequestProcessed (x_error_message->dpy) - != NextRequest (x_error_message->dpy) - 1) + != XNextRequest (x_error_message->dpy) - 1) /* Likewise if no request was made since the trap was installed. */ && (NextRequest (x_error_message->dpy) > x_error_message->first_request)) - XSync (x_error_message->dpy, False); + { + XSync (x_error_message->dpy, False); + x_clean_failable_requests (dpyinfo); + } tmp = x_error_message; x_error_message = x_error_message->prev; @@ -22580,6 +24523,7 @@ x_uncatch_errors (void) void x_check_errors (Display *dpy, const char *format) { + struct x_display_info *dpyinfo; char *string; /* This shouldn't happen, since x_check_errors should be called @@ -22590,11 +24534,17 @@ x_check_errors (Display *dpy, const char *format) /* There is no point in making this extra sync if all requests are known to have been fully processed. */ if ((LastKnownRequestProcessed (dpy) - != NextRequest (dpy) - 1) + != XNextRequest (dpy) - 1) && (NextRequest (dpy) > x_error_message->first_request)) XSync (dpy, False); + dpyinfo = x_display_info_for_display (dpy); + + /* Clean the array of failable requests, since a sync happened. */ + if (dpyinfo) + x_clean_failable_requests (dpyinfo); + if (x_error_message->string) { string = alloca (strlen (x_error_message->string) + 1); @@ -22610,6 +24560,8 @@ x_check_errors (Display *dpy, const char *format) bool x_had_errors_p (Display *dpy) { + struct x_display_info *dpyinfo; + /* This shouldn't happen, since x_check_errors should be called immediately inside an x_catch_errors block. */ if (dpy != x_error_message->dpy) @@ -22617,12 +24569,18 @@ x_had_errors_p (Display *dpy) /* Make sure to catch any errors incurred so far. */ if ((LastKnownRequestProcessed (dpy) - != NextRequest (dpy) - 1) + != XNextRequest (dpy) - 1) && (NextRequest (dpy) > x_error_message->first_request)) XSync (dpy, False); - return x_error_message->string; + dpyinfo = x_display_info_for_display (dpy); + + /* Clean the array of failable requests, since a sync happened. */ + if (dpyinfo) + x_clean_failable_requests (dpyinfo); + + return !!x_error_message->string; } /* Forget about any errors we have had, since we did x_catch_errors on @@ -22673,18 +24631,46 @@ x_trace_wire (Display *dpy) static char *error_msg; +/* Try to find a frame in Vframe_list, and make it the selected frame. + `delete_frame' sometimes misses the initial frame for an unknown + reason when Emacs is running as a background daemon. */ + +static void +x_try_restore_frame (void) +{ + Lisp_Object tail, frame; + + FOR_EACH_FRAME (tail, frame) + { + if (!NILP (do_switch_frame (frame, 1, Qnil))) + return; + } +} + /* Handle the loss of connection to display DPY. ERROR_MESSAGE is the text of an error message that lead to the connection loss. */ -static AVOID +static void x_connection_closed (Display *dpy, const char *error_message, bool ioerror) { struct x_display_info *dpyinfo; Lisp_Object frame, tail; specpdl_ref idx = SPECPDL_INDEX (); - void *io_error_handler; + Emacs_XIOErrorHandler io_error_handler; xm_drop_start_message dmsg; struct frame *f; + Lisp_Object minibuf_frame, tmp; + struct x_failable_request *failable; + struct x_error_message_stack *stack; + static Display *current_display; + + /* Prevent recursive calls of this function for the same display. + This is because destroying a frame might still cause an IO error + in some cases. (bug#56528) */ + if (current_display == dpy) + return; + + current_display = dpy; dpyinfo = x_display_info_for_display (dpy); error_msg = alloca (strlen (error_message) + 1); @@ -22736,7 +24722,7 @@ x_connection_closed (Display *dpy, const char *error_message, bool ioerror) XM_DROP_ACTION_DROP_CANCEL); dmsg.x = 0; dmsg.y = 0; - dmsg.index_atom = FRAME_DISPLAY_INFO (f)->Xatom_XdndSelection; + dmsg.index_atom = x_dnd_motif_atom; dmsg.source_window = FRAME_X_WINDOW (f); x_dnd_send_xm_leave_for_drop (FRAME_DISPLAY_INFO (f), f, @@ -22761,6 +24747,7 @@ x_connection_closed (Display *dpy, const char *error_message, bool ioerror) x_dnd_return_frame_object = NULL; x_dnd_movement_frame = NULL; + x_dnd_wheel_frame = NULL; x_dnd_frame = NULL; } @@ -22784,9 +24771,14 @@ x_connection_closed (Display *dpy, const char *error_message, bool ioerror) that are on the dead display. */ FOR_EACH_FRAME (tail, frame) { - Lisp_Object minibuf_frame; + /* Tooltip frames don't have these, so avoid crashing. */ + + if (FRAME_TOOLTIP_P (XFRAME (frame))) + continue; + minibuf_frame = WINDOW_FRAME (XWINDOW (FRAME_MINIBUF_WINDOW (XFRAME (frame)))); + if (FRAME_X_P (XFRAME (frame)) && FRAME_X_P (XFRAME (minibuf_frame)) && ! EQ (frame, minibuf_frame) @@ -22837,20 +24829,55 @@ For details, see etc/PROBLEMS.\n", /* We have just closed all frames on this display. */ emacs_abort (); - { - Lisp_Object tmp; - XSETTERMINAL (tmp, dpyinfo->terminal); - Fdelete_terminal (tmp, Qnoelisp); - } + /* This was the last terminal remaining, so print the error + message and associated error handlers and kill Emacs. */ + if (dpyinfo->terminal == terminal_list + && !terminal_list->next_terminal) + { + fprintf (stderr, "%s\n", error_msg); + + if (!ioerror && dpyinfo) + { + /* Dump the list of error handlers for debugging + purposes. */ + + fprintf (stderr, "X error handlers currently installed:\n"); + + for (failable = dpyinfo->failable_requests; + failable < dpyinfo->next_failable_request; + ++failable) + { + if (failable->end) + fprintf (stderr, "Ignoring errors between %lu to %lu\n", + failable->start, failable->end); + else + fprintf (stderr, "Ignoring errors from %lu onwards\n", + failable->start); + } + + for (stack = x_error_message; stack; stack = stack->prev) + fprintf (stderr, "Trapping errors from %lu\n", + stack->first_request); + } + } + + XSETTERMINAL (tmp, dpyinfo->terminal); + Fdelete_terminal (tmp, Qnoelisp); } + /* The initial "daemon" frame is sometimes not selected by + `delete_frame' when Emacs is a background daemon. */ + if (NILP (selected_frame)) + x_try_restore_frame (); + unblock_input (); - if (terminal_list == 0) - { - fprintf (stderr, "%s\n", error_msg); - Fkill_emacs (make_fixnum (70), Qnil); - } + /* Sometimes another terminal is still alive, but deleting this + terminal caused all frames to vanish. In that case, simply kill + Emacs, since the next redisplay will abort as there is no more + selected frame. (bug#56528) */ + if (terminal_list == 0 || NILP (selected_frame)) + Fkill_emacs (make_fixnum (70), Qnil); totally_unblock_input (); @@ -22860,6 +24887,7 @@ For details, see etc/PROBLEMS.\n", /* Here, we absolutely have to use a non-local exit (e.g. signal, throw, longjmp), because returning from this function would get us back into Xlib's code which will directly call `exit'. */ + current_display = NULL; error ("%s", error_msg); } @@ -22872,9 +24900,8 @@ static int x_error_handler (Display *display, XErrorEvent *event) { struct x_error_message_stack *stack; -#ifdef HAVE_XINPUT2 struct x_display_info *dpyinfo; -#endif + struct x_failable_request *fail, *last; #if defined USE_GTK && defined HAVE_GTK3 if ((event->error_code == BadMatch @@ -22883,21 +24910,41 @@ x_error_handler (Display *display, XErrorEvent *event) return 0; #endif + dpyinfo = x_display_info_for_display (display); + + if (dpyinfo) + { + fail = x_request_can_fail (dpyinfo, event->serial); + + if (fail) + { + /* Now that this request sequence has been fully handled, + remove it from the list of requests that can fail. */ + + if (event->serial == fail->end) + { + last = dpyinfo->next_failable_request; + memmove (&dpyinfo->failable_requests, fail, + sizeof *fail * (last - fail)); + dpyinfo->next_failable_request = (dpyinfo->failable_requests + + (last - fail)); + } + + return 0; + } + } + /* If we try to ungrab or grab a device that doesn't exist anymore (that happens a lot in xmenu.c), just ignore the error. */ #ifdef HAVE_XINPUT2 - dpyinfo = x_display_info_for_display (display); - - /* 51 is X_XIGrabDevice and 52 is X_XIUngrabDevice. - - 53 is X_XIAllowEvents. We handle errors from that here to avoid - a sync in handle_one_xevent. */ + /* Handle errors from some specific XI2 requests here to avoid a + sync in handle_one_xevent. */ if (dpyinfo && dpyinfo->supports_xi2 && event->request_code == dpyinfo->xi2_opcode - && (event->minor_code == 51 - || event->minor_code == 52 - || event->minor_code == 53)) + && (event->minor_code == X_XIGrabDevice + || event->minor_code == X_XIUngrabDevice + || event->minor_code == X_XIAllowEvents)) return 0; #endif @@ -22919,7 +24966,8 @@ x_error_handler (Display *display, XErrorEvent *event) static void NO_INLINE x_error_quitter (Display *display, XErrorEvent *event) { - char buf[256], buf1[356]; + char buf[256], buf1[400 + INT_STRLEN_BOUND (int) + + INT_STRLEN_BOUND (unsigned long)]; /* Ignore BadName errors. They can happen because of fonts or colors that are not defined. */ @@ -22931,8 +24979,9 @@ x_error_quitter (Display *display, XErrorEvent *event) original error handler. */ XGetErrorText (display, event->error_code, buf, sizeof (buf)); - sprintf (buf1, "X protocol error: %s on protocol request %d", - buf, event->request_code); + sprintf (buf1, "X protocol error: %s on protocol request %d\n" + "Serial no: %lu\n", buf, event->request_code, + event->serial); x_connection_closed (display, buf1, false); } @@ -22941,7 +24990,7 @@ x_error_quitter (Display *display, XErrorEvent *event) It kills all frames on the display that we lost touch with. If that was the only one, it prints an error message and kills Emacs. */ -static _Noreturn ATTRIBUTE_COLD int +static int NO_INLINE x_io_error_quitter (Display *display) { char buf[256]; @@ -22949,6 +24998,8 @@ x_io_error_quitter (Display *display) snprintf (buf, sizeof buf, "Connection lost to X server '%s'", DisplayString (display)); x_connection_closed (display, buf, true); + + return 0; } @@ -23077,14 +25128,14 @@ xim_open_dpy (struct x_display_info *dpyinfo, char *resource_name) if (xim) { -#ifdef HAVE_X11R6 +#ifdef HAVE_X11R6_XIM XIMCallback destroy; #endif /* Get supported styles and XIM values. */ XGetIMValues (xim, XNQueryInputStyle, &dpyinfo->xim_styles, NULL); -#ifdef HAVE_X11R6 +#ifdef HAVE_X11R6_XIM destroy.callback = xim_destroy_callback; destroy.client_data = (XPointer)dpyinfo; XSetIMValues (xim, XNDestroyCallback, &destroy, NULL); @@ -23377,7 +25428,11 @@ x_set_offset (struct frame *f, int xoff, int yoff, int change_gravity) #endif /* 'x_sync_with_move' is too costly for dragging child frames. */ - if (!FRAME_PARENT_FRAME (f)) + if (!FRAME_PARENT_FRAME (f) + /* If no window manager exists, just calling XSync will be + sufficient to ensure that the window geometry has been + updated. */ + && NILP (Vx_no_window_manager)) { x_sync_with_move (f, f->left_pos, f->top_pos, FRAME_DISPLAY_INFO (f)->wm_type == X_WMTYPE_UNKNOWN); @@ -23399,10 +25454,45 @@ x_set_offset (struct frame *f, int xoff, int yoff, int change_gravity) && FRAME_X_OUTPUT (f)->move_offset_top == 0)))) x_check_expected_move (f, modified_left, modified_top); } + /* Instead, just wait for the last ConfigureWindow request to + complete. No window manager is involved when moving child + frames. */ + else + XSync (FRAME_X_DISPLAY (f), False); unblock_input (); } +static Window +x_get_wm_check_window (struct x_display_info *dpyinfo) +{ + Window result; + unsigned char *tmp_data = NULL; + int rc, actual_format; + unsigned long actual_size, bytes_remaining; + Atom actual_type; + + rc = XGetWindowProperty (dpyinfo->display, dpyinfo->root_window, + dpyinfo->Xatom_net_supporting_wm_check, + 0, 1, False, XA_WINDOW, &actual_type, + &actual_format, &actual_size, + &bytes_remaining, &tmp_data); + + if (rc != Success || actual_type != XA_WINDOW + || actual_format != 32 || actual_size != 1) + { + if (tmp_data) + XFree (tmp_data); + + return None; + } + + result = *(Window *) tmp_data; + XFree (tmp_data); + + return result; +} + /* Return true if _NET_SUPPORTING_WM_CHECK window exists and _NET_SUPPORTED on the root window for frame F contains ATOMNAME. This is how a WM check shall be done according to the Window Manager @@ -23423,33 +25513,40 @@ x_wm_supports_1 (struct x_display_info *dpyinfo, Atom want_atom) unsigned char *tmp_data = NULL; Atom target_type = XA_WINDOW; + /* The user says there's no window manager, so take him up on + it. */ + if (!NILP (Vx_no_window_manager)) + return false; + block_input (); x_catch_errors (dpy); - rc = XGetWindowProperty (dpy, target_window, - dpyinfo->Xatom_net_supporting_wm_check, - 0, max_len, False, target_type, - &actual_type, &actual_format, &actual_size, - &bytes_remaining, &tmp_data); - if (rc != Success || actual_type != XA_WINDOW || x_had_errors_p (dpy)) - { - if (tmp_data) XFree (tmp_data); - x_uncatch_errors (); - unblock_input (); - return false; - } + wmcheck_window = dpyinfo->net_supported_window; - wmcheck_window = *(Window *) tmp_data; - XFree (tmp_data); + if (wmcheck_window == None) + wmcheck_window = x_get_wm_check_window (dpyinfo); - /* Check if window exists. */ - XSelectInput (dpy, wmcheck_window, StructureNotifyMask); - if (x_had_errors_p (dpy)) + if (!x_special_window_exists_p (dpyinfo, wmcheck_window)) { - x_uncatch_errors_after_check (); - unblock_input (); - return false; + if (dpyinfo->net_supported_window != None) + { + dpyinfo->net_supported_window = None; + wmcheck_window = x_get_wm_check_window (dpyinfo); + + if (!x_special_window_exists_p (dpyinfo, wmcheck_window)) + { + x_uncatch_errors (); + unblock_input (); + return false; + } + } + else + { + x_uncatch_errors (); + unblock_input (); + return false; + } } if (dpyinfo->net_supported_window != wmcheck_window) @@ -24068,11 +26165,9 @@ x_sync_with_move (struct frame *f, int left, int top, bool fuzzy) current_left = 0; current_top = 0; - /* In theory, this call to XSync only needs to happen once, but in - practice, it doesn't seem to work, hence the need for the surrounding - loop. */ - - XSync (FRAME_X_DISPLAY (f), False); + /* There is no need to call XSync (even when no window manager + is present) because x_real_positions already does that + implicitly. */ x_real_positions (f, ¤t_left, ¤t_top); if (fuzzy) @@ -24101,7 +26196,6 @@ x_sync_with_move (struct frame *f, int left, int top, bool fuzzy) wait_reading_process_output (0, 500000000, 0, false, Qnil, NULL, 0); } - /* Wait for an event on frame F matching EVENTTYPE. */ void x_wait_for_event (struct frame *f, int eventtype) @@ -24256,29 +26350,25 @@ x_set_window_size (struct frame *f, bool change_gravity, void frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y) { - block_input (); #ifdef HAVE_XINPUT2 int deviceid; - if (FRAME_DISPLAY_INFO (f)->supports_xi2) + deviceid = FRAME_DISPLAY_INFO (f)->client_pointer_device; + + if (FRAME_DISPLAY_INFO (f)->supports_xi2 + && deviceid != -1) { - if (XIGetClientPointer (FRAME_X_DISPLAY (f), - FRAME_X_WINDOW (f), - &deviceid)) - { - x_catch_errors (FRAME_X_DISPLAY (f)); - XIWarpPointer (FRAME_X_DISPLAY (f), - deviceid, None, - FRAME_X_WINDOW (f), - 0, 0, 0, 0, pix_x, pix_y); - x_uncatch_errors (); - } + block_input (); + x_ignore_errors_for_next_request (FRAME_DISPLAY_INFO (f)); + XIWarpPointer (FRAME_X_DISPLAY (f), deviceid, None, + FRAME_X_WINDOW (f), 0, 0, 0, 0, pix_x, pix_y); + x_stop_ignoring_errors (FRAME_DISPLAY_INFO (f)); + unblock_input (); } else #endif XWarpPointer (FRAME_X_DISPLAY (f), None, FRAME_X_WINDOW (f), 0, 0, 0, 0, pix_x, pix_y); - unblock_input (); } /* Raise frame F. */ @@ -24293,18 +26383,52 @@ x_raise_frame (struct frame *f) unblock_input (); } +static void +x_lower_frame_1 (struct frame *f) +{ + Window *windows; + Lisp_Object frame, tail; + struct frame *sibling; + + windows = alloca (2 * sizeof *windows); + + /* Lowering a child frame leads to the window being put below any + scroll bars on the parent. To avoid that, restack the child + frame below all of its siblings instead of just lowering it. */ + + FOR_EACH_FRAME (tail, frame) + { + sibling = XFRAME (frame); + + if (sibling == f) + continue; + + if (FRAME_PARENT_FRAME (sibling) + != FRAME_PARENT_FRAME (f)) + continue; + + windows[0] = FRAME_OUTER_WINDOW (sibling); + windows[1] = FRAME_OUTER_WINDOW (f); + + XRestackWindows (FRAME_X_DISPLAY (f), windows, 2); + } +} + /* Lower frame F. */ static void x_lower_frame (struct frame *f) { - if (FRAME_VISIBLE_P (f)) - { - block_input (); - XLowerWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f)); - XFlush (FRAME_X_DISPLAY (f)); - unblock_input (); - } + if (FRAME_PARENT_FRAME (f) + && (FRAME_HAS_VERTICAL_SCROLL_BARS (FRAME_PARENT_FRAME (f)) + || FRAME_HAS_HORIZONTAL_SCROLL_BARS (FRAME_PARENT_FRAME (f)))) + x_lower_frame_1 (f); + else + XLowerWindow (FRAME_X_DISPLAY (f), + FRAME_OUTER_WINDOW (f)); + + XFlush (FRAME_X_DISPLAY (f)); + #ifdef HAVE_XWIDGETS /* Make sure any X windows owned by xwidget views of the parent still display below the lowered frame. */ @@ -24385,7 +26509,7 @@ x_get_focus_frame (struct frame *f) /* In certain situations, when the window manager follows a click-to-focus policy, there seems to be no way around calling - XSetInputFocus to give another frame the input focus . + XSetInputFocus to give another frame the input focus. In an ideal world, XSetInputFocus should generally be avoided so that applications don't interfere with the window manager's focus @@ -24395,28 +26519,26 @@ x_get_focus_frame (struct frame *f) static void x_focus_frame (struct frame *f, bool noactivate) { - Display *dpy = FRAME_X_DISPLAY (f); + struct x_display_info *dpyinfo; - block_input (); - x_catch_errors (dpy); + dpyinfo = FRAME_DISPLAY_INFO (f); if (FRAME_X_EMBEDDED_P (f)) - { - /* For Xembedded frames, normally the embedder forwards key - events. See XEmbed Protocol Specification at - https://freedesktop.org/wiki/Specifications/xembed-spec/ */ - xembed_request_focus (f); - } + /* For Xembedded frames, normally the embedder forwards key + events. See XEmbed Protocol Specification at + https://freedesktop.org/wiki/Specifications/xembed-spec/ */ + xembed_request_focus (f); else { + /* Ignore any BadMatch error this request might result in. */ + x_ignore_errors_for_next_request (dpyinfo); XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), RevertToParent, CurrentTime); + x_stop_ignoring_errors (dpyinfo); + if (!noactivate) x_ewmh_activate_frame (f); } - - x_uncatch_errors (); - unblock_input (); } @@ -24459,9 +26581,14 @@ xembed_send_message (struct frame *f, Time t, enum xembed_message msg, event.xclient.data.l[3] = data1; event.xclient.data.l[4] = data2; + /* XXX: the XEmbed spec tells us to trap errors around this request, + but I don't understand why: there is no way for clients to + survive the death of the parent anyway. */ + + x_ignore_errors_for_next_request (FRAME_DISPLAY_INFO (f)); XSendEvent (FRAME_X_DISPLAY (f), FRAME_X_OUTPUT (f)->parent_desc, False, NoEventMask, &event); - XSync (FRAME_X_DISPLAY (f), False); + x_stop_ignoring_errors (FRAME_DISPLAY_INFO (f)); } /* Change of visibility. */ @@ -24481,6 +26608,7 @@ x_make_frame_visible (struct frame *f) struct x_display_info *dpyinfo; struct x_output *output; #endif + bool output_flushed; if (FRAME_PARENT_FRAME (f)) { @@ -24523,56 +26651,21 @@ x_make_frame_visible (struct frame *f) #ifndef USE_GTK output = FRAME_X_OUTPUT (f); + x_update_frame_user_time_window (f); - if (!x_wm_supports (f, dpyinfo->Xatom_net_wm_user_time_window)) + /* It's been a while since I wrote that code... I don't + remember if it can leave `user_time_window' unset or not. */ + if (output->user_time_window != None) { - if (output->user_time_window == None) - output->user_time_window = FRAME_OUTER_WINDOW (f); - else if (output->user_time_window != FRAME_OUTER_WINDOW (f)) - { - XDestroyWindow (dpyinfo->display, - output->user_time_window); - XDeleteProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (f), - dpyinfo->Xatom_net_wm_user_time_window); - output->user_time_window = FRAME_OUTER_WINDOW (f); - } - } - else - { - if (output->user_time_window == FRAME_OUTER_WINDOW (f) - || output->user_time_window == None) - { - XSetWindowAttributes attrs; - memset (&attrs, 0, sizeof attrs); - - output->user_time_window - = XCreateWindow (dpyinfo->display, FRAME_X_WINDOW (f), - -1, -1, 1, 1, 0, 0, InputOnly, - CopyFromParent, 0, &attrs); - - XDeleteProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (f), - dpyinfo->Xatom_net_wm_user_time); - XChangeProperty (dpyinfo->display, - FRAME_OUTER_WINDOW (f), - dpyinfo->Xatom_net_wm_user_time_window, - XA_WINDOW, 32, PropModeReplace, - (unsigned char *) &output->user_time_window, - 1); - } + if (dpyinfo->last_user_time) + XChangeProperty (dpyinfo->display, output->user_time_window, + dpyinfo->Xatom_net_wm_user_time, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &dpyinfo->last_user_time, 1); + else + XDeleteProperty (dpyinfo->display, output->user_time_window, + dpyinfo->Xatom_net_wm_user_time); } - - if (dpyinfo->last_user_time) - XChangeProperty (dpyinfo->display, - output->user_time_window, - dpyinfo->Xatom_net_wm_user_time, - XA_CARDINAL, 32, PropModeReplace, - (unsigned char *) &dpyinfo->last_user_time, 1); - else - XDeleteProperty (dpyinfo->display, - output->user_time_window, - dpyinfo->Xatom_net_wm_user_time); #endif f->output_data.x->asked_for_visible = true; @@ -24606,8 +26699,6 @@ x_make_frame_visible (struct frame *f) } } - XFlush (FRAME_X_DISPLAY (f)); - /* Synchronize to ensure Emacs knows the frame is visible before we do anything else. We do this loop with input not blocked so that incoming events are handled. */ @@ -24626,6 +26717,10 @@ x_make_frame_visible (struct frame *f) /* This must come after we set COUNT. */ unblock_input (); + /* Keep track of whether or not the output buffer was flushed, to + avoid any extra flushes. */ + output_flushed = false; + /* We unblock here so that arriving X events are processed. */ /* Now move the window back to where it was "supposed to be". @@ -24659,6 +26754,7 @@ x_make_frame_visible (struct frame *f) there, and take the potential window manager hit. */ XGetGeometry (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), &rootw, &x, &y, &width, &height, &border, &depth); + output_flushed = true; if (original_left != x || original_top != y) XMoveWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), @@ -24693,7 +26789,11 @@ x_make_frame_visible (struct frame *f) (f, build_string ("x_make_frame_visible")); x_wait_for_event (f, MapNotify); + output_flushed = true; } + + if (!output_flushed) + x_flush (f); } } @@ -24913,6 +27013,11 @@ x_free_frame_resources (struct frame *f) block_input (); +#ifdef HAVE_XINPUT2 + /* Remove any record of this frame being focused. */ + xi_handle_delete_frame (dpyinfo, f); +#endif + /* If a display connection is dead, don't try sending more commands to the X server. */ if (dpyinfo->display) @@ -25060,7 +27165,10 @@ x_free_frame_resources (struct frame *f) if (f->output_data.x->bottom_left_corner_cursor != 0) XFreeCursor (FRAME_X_DISPLAY (f), f->output_data.x->bottom_left_corner_cursor); - XFlush (FRAME_X_DISPLAY (f)); + /* Free sync fences. */ +#if defined HAVE_XSYNCTRIGGERFENCE && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + x_sync_free_fences (f); +#endif } #ifdef HAVE_GTK3 @@ -25071,6 +27179,12 @@ x_free_frame_resources (struct frame *f) g_object_unref (FRAME_OUTPUT_DATA (f)->scrollbar_foreground_css_provider); #endif + if (f == dpyinfo->motif_drag_atom_owner) + { + dpyinfo->motif_drag_atom_owner = NULL; + dpyinfo->motif_drag_atom = None; + } + if (f == dpyinfo->x_focus_frame) dpyinfo->x_focus_frame = 0; if (f == dpyinfo->x_focus_event_frame) @@ -25104,8 +27218,15 @@ x_destroy_window (struct frame *f) #endif #ifdef HAVE_XINPUT2 +#ifdef HAVE_XINPUT2_1 if (f->output_data.x->xi_masks) XFree (f->output_data.x->xi_masks); +#else + /* This is allocated by us under very old versions of libXi; see + `setup_xi_event_mask'. */ + if (f->output_data.x->xi_masks) + xfree (f->output_data.x->xi_masks); +#endif #endif xfree (f->output_data.x); @@ -25160,11 +27281,30 @@ x_intern_cached_atom (struct x_display_info *dpyinfo, if (!strcmp (name, "ATOM")) return XA_ATOM; + if (!strcmp (name, "WINDOW")) + return XA_WINDOW; + + if (!strcmp (name, "DRAWABLE")) + return XA_DRAWABLE; + + if (!strcmp (name, "BITMAP")) + return XA_BITMAP; + if (!strcmp (name, "CARDINAL")) return XA_CARDINAL; - if (!strcmp (name, "WINDOW")) - return XA_WINDOW; + if (!strcmp (name, "COLORMAP")) + return XA_COLORMAP; + + if (!strcmp (name, "CURSOR")) + return XA_CURSOR; + + if (!strcmp (name, "FONT")) + return XA_FONT; + + if (dpyinfo->motif_drag_atom != None + && !strcmp (name, dpyinfo->motif_drag_atom_name)) + return dpyinfo->motif_drag_atom; for (i = 0; i < ARRAYELTS (x_atom_refs); ++i) { @@ -25223,7 +27363,23 @@ x_get_atom_name (struct x_display_info *dpyinfo, Atom atom, case XA_WINDOW: return xstrdup ("WINDOW"); + case XA_DRAWABLE: + return xstrdup ("DRAWABLE"); + + case XA_BITMAP: + return xstrdup ("BITMAP"); + + case XA_COLORMAP: + return xstrdup ("COLORMAP"); + + case XA_FONT: + return xstrdup ("FONT"); + default: + if (dpyinfo->motif_drag_atom + && atom == dpyinfo->motif_drag_atom) + return xstrdup (dpyinfo->motif_drag_atom_name); + if (atom == dpyinfo->Xatom_xsettings_sel) { sprintf (buffer, "_XSETTINGS_S%d", @@ -25694,6 +27850,71 @@ my_log_handler (const gchar *log_domain, GLogLevelFlags log_level, connection established. */ static unsigned x_display_id; +#if defined HAVE_XINPUT2 && !defined HAVE_GTK3 + +/* Select for device change events on the root window of DPYINFO. + These include device change and hierarchy change notifications. */ + +static void +xi_select_hierarchy_events (struct x_display_info *dpyinfo) +{ + XIEventMask mask; + ptrdiff_t l; + unsigned char *m; + + l = XIMaskLen (XI_LASTEVENT); + mask.mask = m = alloca (l); + memset (m, 0, l); + mask.mask_len = l; + + mask.deviceid = XIAllDevices; + + XISetMask (m, XI_PropertyEvent); + XISetMask (m, XI_HierarchyChanged); + XISetMask (m, XI_DeviceChanged); + + XISelectEvents (dpyinfo->display, dpyinfo->root_window, + &mask, 1); +} + +#endif + +#if defined HAVE_XINPUT2 && defined HAVE_GTK3 + +/* Look up whether or not GTK already initialized the X input + extension. + + Value is 0 if GTK was not built with the input extension, or if it + was explictly disabled, 1 if GTK enabled the input extension and + the version was successfully determined, and 2 if that information + could not be determined. */ + +static int +xi_check_toolkit (Display *display) +{ + GdkDisplay *gdpy; + GdkDeviceManager *manager; + + gdpy = gdk_x11_lookup_xdisplay (display); + eassume (gdpy); + manager = gdk_display_get_device_manager (gdpy); + + if (!strcmp (G_OBJECT_TYPE_NAME (manager), + "GdkX11DeviceManagerXI2")) + return 1; + + if (!strcmp (G_OBJECT_TYPE_NAME (manager), + "GdkX11DeviceManagerCore")) + return 0; + + /* Something changed in GDK so this information is no longer + available. */ + + return 2; +} + +#endif + /* Open a connection to X display DISPLAY_NAME, and return the structure that describes the open display. If we cannot contact the display, return null. */ @@ -25885,6 +28106,8 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) dpyinfo = xzalloc (sizeof *dpyinfo); terminal = x_create_terminal (dpyinfo); + dpyinfo->next_failable_request = dpyinfo->failable_requests; + { struct x_display_info *share; @@ -26234,6 +28457,19 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) int minor = 0; #endif + dpyinfo->client_pointer_device = -1; + +#ifdef HAVE_GTK3 + /* GTK gets a chance to request use of the input extension first. + If we later try to enable it if GDK did not, then GTK will not + get the resulting extension events. */ + + rc = xi_check_toolkit (dpyinfo->display); + + if (!rc) + goto skip_xi_setup; +#endif + if (XQueryExtension (dpyinfo->display, "XInputExtension", &dpyinfo->xi2_opcode, &xi_first_event, &xi_first_error)) @@ -26302,6 +28538,13 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) } else x_uncatch_errors_after_check (); + + /* But don't delude ourselves into thinking that we can use + features provided by a version of the input extension that + libXi itself doesn't support. */ + + if (minor > original_minor) + minor = original_minor; #else if (x_had_errors_p (dpyinfo->display)) rc = BadRequest; @@ -26312,14 +28555,18 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) if (rc == Success) { dpyinfo->supports_xi2 = true; - x_init_master_valuators (dpyinfo); +#ifndef HAVE_GTK3 + /* Select for hierarchy events on the root window. GTK 3.x + does this itself. */ + xi_select_hierarchy_events (dpyinfo); +#endif + + x_cache_xi_devices (dpyinfo); } } dpyinfo->xi2_version = minor; -#ifndef HAVE_GTK3 skip_xi_setup: -#endif ; #endif @@ -26493,7 +28740,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) x_find_modifier_meanings (dpyinfo); #endif - dpyinfo->x_dnd_atoms_size = 8; + dpyinfo->x_dnd_atoms_size = 16; dpyinfo->x_dnd_atoms = xmalloc (sizeof *dpyinfo->x_dnd_atoms * dpyinfo->x_dnd_atoms_size); dpyinfo->gray @@ -26626,7 +28873,54 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) return dpyinfo; } + + +/* Remove all the selection input events on the keyboard buffer + intended for DPYINFO. */ + +static void +x_delete_selection_requests (struct x_display_info *dpyinfo) +{ + union buffered_input_event *event; + int moved_events; + + for (event = kbd_fetch_ptr; event != kbd_store_ptr; + event = X_NEXT_KBD_EVENT (event)) + { + if (event->kind == SELECTION_REQUEST_EVENT + || event->kind == SELECTION_CLEAR_EVENT) + { + if (SELECTION_EVENT_DPYINFO (&event->sie) != dpyinfo) + continue; + + /* Remove the event from the fifo buffer before processing; + otherwise swallow_events called recursively could see it + and process it again. To do this, we move the events + between kbd_fetch_ptr and EVENT one slot to the right, + cyclically. */ + + if (event < kbd_fetch_ptr) + { + memmove (kbd_buffer + 1, kbd_buffer, + (event - kbd_buffer) * sizeof *kbd_buffer); + kbd_buffer[0] = kbd_buffer[KBD_BUFFER_SIZE - 1]; + moved_events = kbd_buffer + KBD_BUFFER_SIZE - 1 - kbd_fetch_ptr; + } + else + moved_events = event - kbd_fetch_ptr; + + memmove (kbd_fetch_ptr + 1, kbd_fetch_ptr, + moved_events * sizeof *kbd_fetch_ptr); + kbd_fetch_ptr = X_NEXT_KBD_EVENT (kbd_fetch_ptr); + + /* `detect_input_pending' will then recompute whether or not + pending input events exist. */ + input_pending = false; + } + } +} + /* Get rid of display DPYINFO, deleting all frames on it, and without sending any more commands to the X server. */ @@ -26676,6 +28970,8 @@ x_delete_display (struct x_display_info *dpyinfo) last = ie; } + x_delete_selection_requests (dpyinfo); + if (next_noop_dpyinfo == dpyinfo) next_noop_dpyinfo = dpyinfo->next; @@ -26852,6 +29148,7 @@ x_delete_terminal (struct terminal *terminal) x_dnd_return_frame_object = NULL; x_dnd_movement_frame = NULL; + x_dnd_wheel_frame = NULL; x_dnd_frame = NULL; } @@ -26918,6 +29215,25 @@ x_delete_terminal (struct terminal *terminal) unblock_input (); } +#ifdef HAVE_XINPUT2 +static bool +x_have_any_grab (struct x_display_info *dpyinfo) +{ + int i; + + if (!dpyinfo->supports_xi2) + return false; + + for (i = 0; i < dpyinfo->num_devices; ++i) + { + if (dpyinfo->devices[i].grab) + return true; + } + + return false; +} +#endif + /* Create a struct terminal, initialize it with the X11 specific functions and make DISPLAY->TERMINAL point to it. */ @@ -26985,6 +29301,9 @@ x_create_terminal (struct x_display_info *dpyinfo) terminal->delete_frame_hook = x_destroy_window; terminal->delete_terminal_hook = x_delete_terminal; terminal->toolkit_position_hook = x_toolkit_position; +#ifdef HAVE_XINPUT2 + terminal->any_grab_hook = x_have_any_grab; +#endif /* Other hooks are NULL by default. */ return terminal; @@ -27068,10 +29387,13 @@ void mark_xterm (void) { Lisp_Object val; -#if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS +#if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS \ + || defined HAVE_XRANDR || defined USE_GTK struct x_display_info *dpyinfo; +#if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS int i; #endif +#endif if (x_dnd_return_frame_object) { @@ -27085,6 +29407,12 @@ mark_xterm (void) mark_object (val); } + if (x_dnd_wheel_frame) + { + XSETFRAME (val, x_dnd_wheel_frame); + mark_object (val); + } + #if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS \ || defined HAVE_XRANDR || defined USE_GTK for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next) @@ -27104,6 +29432,193 @@ mark_xterm (void) #endif } +/* Error handling functions for Lisp functions that expose X protocol + requests. They are mostly like `x_catch_errors' and friends, but + respect `x-fast-protocol-requests'. */ + +void +x_catch_errors_for_lisp (struct x_display_info *dpyinfo) +{ + if (!x_fast_protocol_requests) + x_catch_errors (dpyinfo->display); + else + x_ignore_errors_for_next_request (dpyinfo); +} + +void +x_check_errors_for_lisp (struct x_display_info *dpyinfo, + const char *format) +{ + if (!x_fast_protocol_requests) + x_check_errors (dpyinfo->display, format); +} + +void +x_uncatch_errors_for_lisp (struct x_display_info *dpyinfo) +{ + if (!x_fast_protocol_requests) + x_uncatch_errors (); + else + x_stop_ignoring_errors (dpyinfo); +} + +/* Preserve the selections in LOST in another frame on DPYINFO. LOST + is a list of local selections that were lost, due to their frame + being deleted. */ + +void +x_preserve_selections (struct x_display_info *dpyinfo, Lisp_Object lost, + Lisp_Object current_owner) +{ + Lisp_Object tail, frame, new_owner; + Time timestamp; + Window *owners; + Atom *names; + ptrdiff_t nowners, counter; + struct selection_input_event clear; +#ifdef USE_XCB + xcb_get_selection_owner_cookie_t *cookies; + xcb_generic_error_t *error; + xcb_get_selection_owner_reply_t *reply; +#endif + + new_owner = Qnil; + + FOR_EACH_FRAME (tail, frame) + { + if (FRAME_X_P (XFRAME (frame)) + && !EQ (frame, current_owner) + && FRAME_DISPLAY_INFO (XFRAME (frame)) == dpyinfo) + { + new_owner = frame; + break; + } + } + + tail = lost; + nowners = 0; + + FOR_EACH_TAIL_SAFE (tail) + { + Lisp_Object tem = XCAR (tail); + ++nowners; + + /* The selection is really lost (since we cannot find a new + owner), so run the appropriate hooks. */ + if (NILP (new_owner)) + CALLN (Frun_hook_with_args, Qx_lost_selection_functions, + XCAR (tem)); + else + { + CONS_TO_INTEGER (XCAR (XCDR (XCDR (tem))), Time, timestamp); + + /* This shouldn't be able to signal any errors, despite the + call to `x_check_errors' inside. */ + x_own_selection (XCAR (tem), XCAR (XCDR (tem)), + new_owner, XCAR (XCDR (XCDR (XCDR (XCDR (tem))))), + timestamp); + } + } + + if (!NILP (new_owner)) + { + owners = alloca (sizeof *owners * nowners); + names = alloca (sizeof *names * nowners); +#ifdef USE_XCB + cookies = alloca (sizeof *cookies * nowners); +#endif + + tail = lost; + nowners = 0; + counter = 0; + + FOR_EACH_TAIL_SAFE (tail) + { + Lisp_Object tem = XCAR (tail); + + /* Now check if we still don't own that selection, which can + happen if another program set itself as the owner. */ + names[counter++] = symbol_to_x_atom (dpyinfo, XCAR (tem)); + +#ifndef USE_XCB + owners[nowners++] = XGetSelectionOwner (dpyinfo->display, + names[counter - 1]); +#else + cookies[nowners++] + = xcb_get_selection_owner (dpyinfo->xcb_connection, + names[counter - 1]); + } + + nowners = 0; + tail = lost; + + FOR_EACH_TAIL_SAFE (tail) + { + Lisp_Object tem = XCAR (tail); + + reply = xcb_get_selection_owner_reply (dpyinfo->xcb_connection, + cookies[nowners++], &error); + if (reply) + owners[nowners - 1] = reply->owner; + else + owners[nowners - 1] = None; + + free (reply ? (void *) reply : (void *) error); +#endif + + if (owners[nowners - 1] != FRAME_X_WINDOW (XFRAME (new_owner))) + { + /* Clear the local selection, since we know we don't own + it any longer. */ + CONS_TO_INTEGER (XCAR (XCDR (XCDR (tem))), Time, timestamp); + + clear.kind = SELECTION_CLEAR_EVENT; + + SELECTION_EVENT_DPYINFO (&clear) = dpyinfo; + SELECTION_EVENT_SELECTION (&clear) = names[nowners - 1]; + SELECTION_EVENT_TIME (&clear) = timestamp; + + x_handle_selection_event (&clear); + } + } + + tail = lost; + nowners = 0; + + FOR_EACH_TAIL_SAFE (tail) + { + Lisp_Object tem = XCAR (tail); + + /* If the selection isn't owned by us anymore, note that the + selection was lost. */ + if (owners[nowners++] != FRAME_X_WINDOW (XFRAME (new_owner))) + CALLN (Frun_hook_with_args, Qx_lost_selection_functions, + XCAR (tem)); + } + } +} + +/* Return a list of the keyboard modifier masks in DPYINFO. + + Value is a list of (HYPER SUPER ALT SHIFT-LOCK META), each element + being the appropriate modifier mask. */ + +Lisp_Object +x_get_keyboard_modifiers (struct x_display_info *dpyinfo) +{ + /* This sometimes happens when the function is called during display + initialization, which can happen while obtaining vendor specific + keysyms. */ + if (!dpyinfo->xkb_desc && !dpyinfo->modmap) + x_find_modifier_meanings (dpyinfo); + + return list5 (make_uint (dpyinfo->hyper_mod_mask), + make_uint (dpyinfo->super_mod_mask), + make_uint (dpyinfo->alt_mod_mask), + make_uint (dpyinfo->shift_lock_mask), + make_uint (dpyinfo->meta_mod_mask)); +} + void syms_of_xterm (void) { @@ -27116,10 +29631,17 @@ syms_of_xterm (void) x_dnd_action_symbol = Qnil; staticpro (&x_dnd_action_symbol); + x_dnd_selection_alias_cell = Fcons (Qnil, Qnil); + staticpro (&x_dnd_selection_alias_cell); + + x_dnd_unsupported_drop_data = Qnil; + staticpro (&x_dnd_unsupported_drop_data); + DEFSYM (Qvendor_specific_keysyms, "vendor-specific-keysyms"); DEFSYM (Qlatin_1, "latin-1"); DEFSYM (Qnow, "now"); DEFSYM (Qx_dnd_targets_list, "x-dnd-targets-list"); + DEFSYM (Qx_auto_preserve_selections, "x-auto-preserve-selections"); #ifdef USE_GTK xg_default_icon_file = build_pure_c_string ("icons/hicolor/scalable/apps/emacs.svg"); @@ -27166,7 +29688,7 @@ you, try increasing the value of x_mouse_click_focus_ignore_position = false; DEFVAR_INT ("x-mouse-click-focus-ignore-time", x_mouse_click_focus_ignore_time, - doc: /* Number of miliseconds for which to ignore buttons after focus change. + doc: /* Number of milliseconds for which to ignore buttons after focus change. This variable only takes effect if `x-mouse-click-focus-ignore-position' is non-nil, and should be adjusted if the default value does not work for whatever reason. */); @@ -27204,6 +29726,7 @@ With MS Windows, Haiku windowing or Nextstep, the value is t. */); DEFSYM (Qsuper, "super"); Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier)); DEFSYM (QXdndSelection, "XdndSelection"); + DEFSYM (Qx_selection_alias_alist, "x-selection-alias-alist"); DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym, doc: /* Which keys Emacs uses for the ctrl modifier. @@ -27337,19 +29860,34 @@ where FRAME is the frame the mouse is on top of, and POSITION is a mouse position list. */); Vx_dnd_movement_function = Qnil; + DEFVAR_LISP ("x-dnd-wheel-function", Vx_dnd_wheel_function, + doc: /* Function called upon wheel movement on a frame during drag-and-drop. +It should either be nil, or accept four arguments POSITION, BUTTON, +STATE and TIME, where POSITION is a mouse position list describing +where the wheel moved, BUTTON is the wheel button that was pressed, +STATE is the X modifier state at the time of the wheel movement, and +TIME is the X server time at which the wheel moved. */); + Vx_dnd_wheel_function = Qnil; + DEFVAR_LISP ("x-dnd-unsupported-drop-function", Vx_dnd_unsupported_drop_function, doc: /* Function called when trying to drop on an unsupported window. This function is called whenever the user tries to drop something on a window that does not support either the XDND or Motif protocols for drag-and-drop. It should return a non-nil value if the drop was handled by the function, and nil if it was not. It should accept -several arguments TARGETS, X, Y, ACTION, WINDOW-ID, FRAME and TIME, -where TARGETS is the list of targets that was passed to -`x-begin-drag', WINDOW-ID is the numeric XID of the window that is +several arguments TARGETS, X, Y, ACTION, WINDOW-ID, FRAME, TIME and +LOCAL-SELECTION, where TARGETS is the list of targets that was passed +to `x-begin-drag', WINDOW-ID is the numeric XID of the window that is being dropped on, X and Y are the root window-relative coordinates where the drop happened, ACTION is the action that was passed to `x-begin-drag', FRAME is the frame which initiated the drag-and-drop -operation, and TIME is the X server time when the drop happened. */); +operation, TIME is the X server time when the drop happened, and +LOCAL-SELECTION is the contents of the `XdndSelection' when +`x-begin-drag' was run; its contents can be retrieved by calling the +function `x-get-local-selection'. + +If a symbol is returned, then it will be used as the return value of +`x-begin-drag'. */); Vx_dnd_unsupported_drop_function = Qnil; DEFVAR_INT ("x-color-cache-bucket-size", x_color_cache_bucket_size, @@ -27376,4 +29914,43 @@ should return a symbol describing what to return from If the value is nil, or the function returns a value that is not a symbol, a drop on an Emacs frame will be canceled. */); Vx_dnd_native_test_function = Qnil; + + DEFVAR_BOOL ("x-dnd-preserve-selection-data", x_dnd_preserve_selection_data, + doc: /* Preserve selection data after `x-begin-drag' returns. +This lets you inspect the contents of `XdndSelection' after a +drag-and-drop operation, which is useful when writing tests for +drag-and-drop code. */); + x_dnd_preserve_selection_data = false; + + DEFVAR_BOOL ("x-dnd-disable-motif-protocol", x_dnd_disable_motif_protocol, + doc: /* Disable the Motif drag-and-drop protocols. +When non-nil, `x-begin-drag' will not drop onto any window that only +supports the Motif drag-and-drop protocols. */); + x_dnd_disable_motif_protocol = false; + + DEFVAR_BOOL ("x-dnd-use-unsupported-drop", x_dnd_use_unsupported_drop, + doc: /* Enable the emulation of drag-and-drop based on the primary selection. +When nil, do not use the primary selection and synthetic mouse clicks +to emulate the drag-and-drop of `STRING', `UTF8_STRING', +`COMPOUND_TEXT' or `TEXT'. */); + x_dnd_use_unsupported_drop = true; + + DEFVAR_BOOL ("x-fast-protocol-requests", x_fast_protocol_requests, + doc: /* Whether or not X protocol-related functions should wait for errors. +When this is nil, functions such as `x-delete-window-property', +`x-change-window-property' and `x-send-client-message' will wait for a +reply from the X server, and signal any errors that occurred while +executing the protocol request. Otherwise, errors will be silently +ignored without waiting, which is generally faster. */); + x_fast_protocol_requests = false; + + DEFVAR_LISP ("x-auto-preserve-selections", Vx_auto_preserve_selections, + doc: /* Whether or not to transfer selection ownership when deleting a frame. +When non-nil, deleting a frame that is currently the owner of a +selection will cause its ownership to be transferred to another frame +on the same display. + +In addition, when this variable is a list, only preserve the +selections whose names are contained within. */); + Vx_auto_preserve_selections = list2 (QCLIPBOARD, QPRIMARY); } diff --git a/src/xterm.h b/src/xterm.h index 82b4308041a..a0ae3a330a9 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -75,6 +75,9 @@ typedef GtkWidget *xt_or_gtk_widget; #endif #endif /* USE_GTK */ +/* Number of "failable requests" to store. */ +#define N_FAILABLE_REQUESTS 128 + #ifdef USE_CAIRO #include <cairo-xlib.h> #ifdef CAIRO_HAS_PDF_SURFACE @@ -235,29 +238,76 @@ struct xi_touch_point_t struct xi_device_t { + /* The numerical ID of this device. */ int device_id; + #ifdef HAVE_XINPUT2_1 + /* The number of scroll valuators in `valuators'. */ int scroll_valuator_count; #endif + + /* Whether or not the device is grabbed and its use. */ int grab, use; + + /* The attached device. Only valid if USE is some kind of master + device. */ + int attachment; + #ifdef HAVE_XINPUT2_2 + /* Whether or not this device is a direct touch device. */ bool direct_p; #endif #ifdef HAVE_XINPUT2_1 + /* An array of scroll valuators Emacs knows about. */ struct xi_scroll_valuator_t *valuators; #endif + #ifdef HAVE_XINPUT2_2 + /* An array of in-progress touchscreen events. */ struct xi_touch_point_t *touchpoints; #endif + /* The name of this device. */ Lisp_Object name; + + /* The time at which `focus_frame' became the keyboard focus (only + applies to master devices). */ + Time focus_frame_time; + + /* The frame that is currently this device's keyboard focus, or + NULL. */ + struct frame *focus_frame; + + /* The time at which `focus_frame' became the implicit keyboard + focus. */ + Time focus_implicit_time; + + /* The frame that is currently this device's implicit keyboard + focus, or NULL. */ + struct frame *focus_implicit_frame; + + /* The window on which the last motion event happened. */ + Window last_motion_window; + + /* The rounded integer coordinates of the last motion event. */ + int last_motion_x, last_motion_y; }; #endif Status x_parse_color (struct frame *f, const char *color_name, XColor *color); +struct x_failable_request +{ + /* The first request making up this sequence. */ + unsigned long start; + + /* If this is zero, then the request has not yet been made. + Otherwise, this is the request that ends this sequence. */ + unsigned long end; +}; + /* For each X display, we have a structure that records information about it. */ @@ -428,8 +478,8 @@ struct x_display_info /* More atoms for font properties. The last three are private properties, see the comments in src/fontset.h. */ Atom Xatom_PIXEL_SIZE, Xatom_AVERAGE_WIDTH, - Xatom_MULE_BASELINE_OFFSET, Xatom_MULE_RELATIVE_COMPOSE, - Xatom_MULE_DEFAULT_ASCENT; + Xatom_MULE_BASELINE_OFFSET, Xatom_MULE_RELATIVE_COMPOSE, + Xatom_MULE_DEFAULT_ASCENT; /* More atoms for Ghostscript support. */ Atom Xatom_DONE, Xatom_PAGE; @@ -443,12 +493,22 @@ struct x_display_info /* Atom used to determine whether or not the screen is composited. */ Atom Xatom_NET_WM_CM_Sn; + /* Atoms used by the Motif drag and drop protocols. */ Atom Xatom_MOTIF_WM_HINTS, Xatom_MOTIF_DRAG_WINDOW, Xatom_MOTIF_DRAG_TARGETS, Xatom_MOTIF_DRAG_AND_DROP_MESSAGE, Xatom_MOTIF_DRAG_INITIATOR_INFO, Xatom_MOTIF_DRAG_RECEIVER_INFO; + /* Atoms used by Emacs internally. */ + Atom Xatom_EMACS_DRAG_ATOM; + + /* Special selections used by the Motif drop protocol to indicate + success or failure. */ Atom Xatom_XmTRANSFER_SUCCESS, Xatom_XmTRANSFER_FAILURE; + /* Atoms used by both versions of the OffiX DND protocol (the "old + KDE" protocol in x-dnd.el). */ + Atom Xatom_DndProtocol, Xatom_DND_PROTOCOL; + /* The frame (if any) which has the X window that has keyboard focus. Zero if none. This is examined by Ffocus_frame in xfns.c. Note that a mere EnterNotify event can set this; if you need to know the @@ -459,7 +519,10 @@ struct x_display_info /* The last frame mentioned in a FocusIn or FocusOut event. This is separate from x_focus_frame, because whether or not LeaveNotify events cause us to lose focus depends on whether or not we have - received a FocusIn event for it. */ + received a FocusIn event for it. + + This field is not used when the input extension is being + utilized. */ struct frame *x_focus_event_frame; /* The frame which currently has the visual highlight, and should get @@ -485,9 +548,8 @@ struct x_display_info struct scroll_bar *last_mouse_scroll_bar; /* Time of last user interaction as returned in X events on this - display, and time where WM support for `_NET_WM_USER_TIME_WINDOW' - was last checked. */ - Time last_user_time, last_user_check_time; + display. */ + Time last_user_time; /* Position where the mouse was last time we reported a motion. This is a position on last_mouse_motion_frame. */ @@ -506,6 +568,9 @@ struct x_display_info received, and return that in hopes that it's somewhat accurate. */ Time last_mouse_movement_time; + /* Whether or not the last mouse motion was synthetic. */ + bool last_mouse_movement_time_send_event; + /* The gray pixmap. */ Pixmap gray; @@ -555,6 +620,23 @@ struct x_display_info ptrdiff_t x_dnd_atoms_size; ptrdiff_t x_dnd_atoms_length; + /* The unique drag and drop atom used on Motif. None if it was not + already computed. */ + Atom motif_drag_atom; + + /* Its name. */ + char motif_drag_atom_name[sizeof "_EMACS_ATOM_%lu" - 3 + + INT_STRLEN_BOUND (unsigned long)]; + + /* When it was owned. */ + Time motif_drag_atom_time; + + /* The frame that currently owns `motif_drag_atom'. */ + struct frame *motif_drag_atom_owner; + + /* The drag window for this display. */ + Window motif_drag_window; + /* Extended window manager hints, Atoms supported by the window manager and atoms for setting the window type. */ Atom Xatom_net_supported, Xatom_net_supporting_wm_check; @@ -572,9 +654,9 @@ struct x_display_info Xatom_net_wm_state_shaded, Xatom_net_frame_extents, Xatom_net_current_desktop, Xatom_net_workarea, Xatom_net_wm_opaque_region, Xatom_net_wm_ping, Xatom_net_wm_sync_request, Xatom_net_wm_sync_request_counter, - Xatom_net_wm_frame_drawn, Xatom_net_wm_user_time, - Xatom_net_wm_user_time_window, Xatom_net_client_list_stacking, - Xatom_net_wm_pid; + Xatom_net_wm_sync_fences, Xatom_net_wm_frame_drawn, Xatom_net_wm_frame_timings, + Xatom_net_wm_user_time, Xatom_net_wm_user_time_window, + Xatom_net_client_list_stacking, Xatom_net_wm_pid; /* XSettings atoms and windows. */ Atom Xatom_xsettings_sel, Xatom_xsettings_prop, Xatom_xsettings_mgr; @@ -596,6 +678,9 @@ struct x_display_info Xatom_XdndPosition, Xatom_XdndStatus, Xatom_XdndLeave, Xatom_XdndDrop, Xatom_XdndFinished; + /* XDS source and target. */ + Atom Xatom_XdndDirectSave0, Xatom_XdndActionDirectSave, Xatom_text_plain; + #ifdef HAVE_XKB /* Virtual modifiers */ Atom Xatom_Meta, Xatom_Super, Xatom_Hyper, Xatom_ShiftLock, Xatom_Alt; @@ -633,13 +718,27 @@ struct x_display_info #ifdef HAVE_XINPUT2 bool supports_xi2; + + /* The minor version of the input extension. (Major is always + 2.x.) */ int xi2_version; + + /* The generic event opcode of XI2 events. */ int xi2_opcode; + /* The number of devices on this display known to Emacs. */ int num_devices; + + /* Array of all input extension devices on this display known to + Emacs. */ struct xi_device_t *devices; + /* Pending keystroke time. */ Time pending_keystroke_time; + + /* Pending keystroke source. If a core KeyPress event arrives with + the same timestamp as pending_keystroke_time, it will be treated + as originating from this device. */ int pending_keystroke_source; #if defined USE_GTK && !defined HAVE_GTK3 @@ -649,6 +748,10 @@ struct x_display_info input method) core key event. */ bool pending_keystroke_time_special_p; #endif + + /* The client pointer. We keep a record client-side to avoid + calling XISetClientPointer all the time. */ + int client_pointer_device; #endif #ifdef HAVE_XKB @@ -713,6 +816,28 @@ struct x_display_info RRScreenChangeNotify. */ int screen_mm_width; int screen_mm_height; + + /* Circular buffer of request serial ranges to ignore inside an + error handler in increasing order. */ + struct x_failable_request failable_requests[N_FAILABLE_REQUESTS]; + + /* Pointer to the next request in `failable_requests'. */ + struct x_failable_request *next_failable_request; + + /* The pending drag-and-drop time for middle-click based + drag-and-drop emulation. */ + Time pending_dnd_time; + +#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* Whether or not the server time is probably the same as + "clock_gettime (CLOCK_MONOTONIC, ...)". */ + bool server_time_monotonic_p; + + /* The time difference between the X server clock and the monotonic + clock, or 0 if unknown (if the difference is legitimately 0, + server_time_monotonic_p will be true). */ + int_fast64_t server_time_offset; +#endif }; #ifdef HAVE_X_I18N @@ -970,16 +1095,55 @@ struct x_output #endif #ifdef HAVE_XSYNC + /* The "basic frame counter" used for resize synchronization. */ XSyncCounter basic_frame_counter; + + /* The "extended frame counter" used for frame synchronization. */ XSyncCounter extended_frame_counter; + + /* The pending value of the basic counter. */ XSyncValue pending_basic_counter_value; + + /* The current value of the extended counter. */ XSyncValue current_extended_counter_value; + /* The configure event value of the extended counter. */ + XSyncValue resize_counter_value; + + /* Whether or not basic resize synchronization is in progress. */ bool_bf sync_end_pending_p : 1; + + /* Whether or not extended resize synchronization is in + progress. */ bool_bf ext_sync_end_pending_p : 1; + #ifdef HAVE_GTK3 + /* Whether or not GDK resize synchronization is in progress. */ bool_bf xg_sync_end_pending_p : 1; #endif + + /* Whether or Emacs is waiting for the compositing manager to draw a + frame. */ + bool_bf waiting_for_frame_p : 1; + +#if !defined USE_GTK && defined HAVE_CLOCK_GETTIME + /* Whether or not Emacs should wait for the compositing manager to + draw frames before starting a new frame. */ + bool_bf use_vsync_p : 1; + + /* The time (in microseconds) it took to draw the last frame. */ + uint_fast64_t last_frame_time; + + /* A temporary time used to calculate that value. */ + uint_fast64_t temp_frame_time; + +#ifdef HAVE_XSYNCTRIGGERFENCE + /* An array of two sync fences that are triggered in order after a + frame completes. Not initialized if the XSync extension is too + old to support sync fences. */ + XSyncFence sync_fences[2]; +#endif +#endif #endif /* Relief GCs, colors etc. */ @@ -996,7 +1160,9 @@ struct x_output /* Keep track of focus. May be EXPLICIT if we received a FocusIn for this frame, or IMPLICIT if we received an EnterNotify. - FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. */ + FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. + + Not used when the input extension is being utilized. */ int focus_state; /* The offset we need to add to compensate for type A WMs. */ @@ -1155,8 +1321,14 @@ extern void x_mark_frame_dirty (struct frame *f); #endif #ifdef HAVE_XSYNC -#define FRAME_X_BASIC_COUNTER(f) FRAME_X_OUTPUT (f)->basic_frame_counter -#define FRAME_X_EXTENDED_COUNTER(f) FRAME_X_OUTPUT (f)->extended_frame_counter +#define FRAME_X_BASIC_COUNTER(f) \ + FRAME_X_OUTPUT (f)->basic_frame_counter +#define FRAME_X_EXTENDED_COUNTER(f) \ + FRAME_X_OUTPUT (f)->extended_frame_counter +#define FRAME_X_WAITING_FOR_DRAW(f) \ + FRAME_X_OUTPUT (f)->waiting_for_frame_p +#define FRAME_X_COUNTER_VALUE(f) \ + FRAME_X_OUTPUT (f)->current_extended_counter_value #endif /* This is the Colormap which frame F uses. */ @@ -1194,11 +1366,6 @@ struct scroll_bar /* The X window representing this scroll bar. */ Window x_window; -#if defined HAVE_XDBE && !defined USE_TOOLKIT_SCROLL_BARS - /* The X drawable representing this scroll bar. */ - Drawable x_drawable; -#endif - /* The position and size of the scroll bar in pixels, relative to the frame. */ int top, left, width, height; @@ -1396,19 +1563,31 @@ extern bool x_text_icon (struct frame *, const char *); extern void x_catch_errors (Display *); extern void x_catch_errors_with_handler (Display *, x_special_error_handler, void *); +extern void x_catch_errors_for_lisp (struct x_display_info *); +extern void x_uncatch_errors_for_lisp (struct x_display_info *); +extern void x_check_errors_for_lisp (struct x_display_info *, + const char *) + ATTRIBUTE_FORMAT_PRINTF (2, 0); extern void x_check_errors (Display *, const char *) ATTRIBUTE_FORMAT_PRINTF (2, 0); extern bool x_had_errors_p (Display *); extern void x_unwind_errors_to (int); extern void x_uncatch_errors (void); extern void x_uncatch_errors_after_check (void); +extern void x_ignore_errors_for_next_request (struct x_display_info *); +extern void x_stop_ignoring_errors (struct x_display_info *); extern void x_clear_errors (Display *); -extern void x_set_window_size (struct frame *f, bool, int, int); -extern void x_make_frame_visible (struct frame *f); -extern void x_make_frame_invisible (struct frame *f); -extern void x_iconify_frame (struct frame *f); +extern void x_set_window_size (struct frame *, bool, int, int); +extern void x_set_last_user_time_from_lisp (struct x_display_info *, Time); +extern void x_make_frame_visible (struct frame *); +extern void x_make_frame_invisible (struct frame *); +extern void x_iconify_frame (struct frame *); extern void x_free_frame_resources (struct frame *); extern void x_wm_set_size_hint (struct frame *, long, bool); +#if defined HAVE_XSYNCTRIGGERFENCE && !defined USE_GTK \ + && defined HAVE_CLOCK_GETTIME +extern void x_sync_init_fences (struct frame *); +#endif extern void x_delete_terminal (struct terminal *); extern Cursor x_create_font_cursor (struct x_display_info *, int); @@ -1450,11 +1629,14 @@ extern Lisp_Object x_cr_export_frames (Lisp_Object, cairo_surface_type_t); #ifdef HAVE_XRENDER extern void x_xrender_color_from_gc_background (struct frame *, GC, XRenderColor *, bool); -extern void x_xr_ensure_picture (struct frame *f); -extern void x_xr_apply_ext_clip (struct frame *f, GC gc); -extern void x_xr_reset_ext_clip (struct frame *f); +extern void x_xr_ensure_picture (struct frame *); +extern void x_xr_apply_ext_clip (struct frame *, GC); +extern void x_xr_reset_ext_clip (struct frame *); #endif +extern Bool x_query_pointer (Display *, Window, Window *, Window *, int *, + int *, int *, int *, unsigned int *); + #ifdef HAVE_GTK3 extern void x_scroll_bar_configure (GdkEvent *); #endif @@ -1536,6 +1718,7 @@ extern void x_handle_selection_notify (const XSelectionEvent *); extern void x_handle_selection_event (struct selection_input_event *); extern void x_clear_frame_selections (struct frame *); extern Lisp_Object x_atom_to_symbol (struct x_display_info *, Atom); +extern Atom symbol_to_x_atom (struct x_display_info *, Lisp_Object); extern bool x_handle_dnd_message (struct frame *, const XClientMessageEvent *, @@ -1558,8 +1741,8 @@ extern void x_clipboard_manager_save_all (void); extern Lisp_Object x_timestamp_for_selection (struct x_display_info *, Lisp_Object); -extern void x_set_pending_dnd_time (Time); -extern void x_own_selection (Lisp_Object, Lisp_Object, Lisp_Object); +extern void x_own_selection (Lisp_Object, Lisp_Object, Lisp_Object, + Lisp_Object, Time); extern Atom x_intern_cached_atom (struct x_display_info *, const char *, bool); extern char *x_get_atom_name (struct x_display_info *, Atom, bool *) @@ -1578,6 +1761,9 @@ extern void xic_set_statusarea (struct frame *); extern void xic_set_xfontset (struct frame *, const char *); extern bool x_defined_color (struct frame *, const char *, Emacs_Color *, bool, bool); +extern void x_preserve_selections (struct x_display_info *, Lisp_Object, + Lisp_Object); +extern Lisp_Object x_get_keyboard_modifiers (struct x_display_info *); #ifdef HAVE_X_I18N extern void free_frame_xic (struct frame *); # if defined HAVE_X_WINDOWS && defined USE_X_TOOLKIT @@ -1627,12 +1813,14 @@ extern bool x_dnd_in_progress; extern bool x_dnd_waiting_for_finish; extern struct frame *x_dnd_frame; extern struct frame *x_dnd_finish_frame; -extern unsigned x_dnd_unsupported_event_level; extern int x_error_message_count; #ifdef HAVE_XINPUT2 extern struct xi_device_t *xi_device_from_id (struct x_display_info *, int); extern bool xi_frame_selected_for (struct frame *, unsigned long); +#ifndef USE_GTK +extern unsigned int xi_convert_event_state (XIDeviceEvent *); +#endif #endif extern void mark_xterm (void); |