diff --git a/src/keyboard.c b/src/keyboard.c
index baca4b5..f36fc62 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1233,6 +1233,15 @@ static int read_key_sequence (Lisp_Object *, int, Lisp_Object,
                               bool, bool, bool, bool);
 static void adjust_point_for_property (ptrdiff_t, bool);
 
+static void
+set_window_start_end_hook (void)
+{
+  Lisp_Object window = (selected_window);
+  struct window *w = decode_live_window (window);
+  w->window_start_end_hook_force = true;
+  w->window_start_end_hook_pending = true;
+}
+
 Lisp_Object
 command_loop_1 (void)
 {
@@ -1258,6 +1267,8 @@ command_loop_1 (void)
       if (!NILP (Vpost_command_hook) && !NILP (Vrun_hooks))
 	safe_run_hooks (Qpost_command_hook);
 
+      set_window_start_end_hook ();
+
       /* If displaying a message, resize the echo area window to fit
 	 that message's size exactly.  */
       if (!NILP (echo_area_buffer[0]))
@@ -1474,6 +1485,8 @@ command_loop_1 (void)
 
       safe_run_hooks (Qpost_command_hook);
 
+      set_window_start_end_hook ();
+
       /* If displaying a message, resize the echo area window to fit
 	 that message's size exactly.  */
       if (!NILP (echo_area_buffer[0]))
diff --git a/src/lisp.h b/src/lisp.h
index 2130170..17915db 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3933,6 +3933,7 @@ extern void init_eval_once (void);
 extern Lisp_Object safe_call (ptrdiff_t, Lisp_Object, ...);
 extern Lisp_Object safe_call1 (Lisp_Object, Lisp_Object);
 extern Lisp_Object safe_call2 (Lisp_Object, Lisp_Object, Lisp_Object);
+extern Lisp_Object safe_call7 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object);
 extern void init_eval (void);
 extern void syms_of_eval (void);
 extern void unwind_body (Lisp_Object);
diff --git a/src/window.h b/src/window.h
index c29207d..3156927 100644
--- a/src/window.h
+++ b/src/window.h
@@ -348,6 +348,12 @@ struct window
        Vwindow_scroll_functions; also by Frecenter with argument.  */
     bool_bf optional_new_start : 1;
 
+    /* True means force a call to Vwindow_start_end_hook.  */
+    bool_bf window_start_end_hook_force : 1;
+
+    /* True means the hook has not yet run with point fully visible.  */
+    bool_bf window_start_end_hook_pending : 1;
+
     /* True means the cursor is currently displayed.  This can be
        set to zero by functions overpainting the cursor image.  */
     bool_bf phys_cursor_on_p : 1;
diff --git a/src/xdisp.c b/src/xdisp.c
index 8f1e98d..bde3b23 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -2617,6 +2617,13 @@ safe_call2 (Lisp_Object fn, Lisp_Object arg1, Lisp_Object arg2)
   return safe_call (3, fn, arg1, arg2);
 }
 
+/*  This is for the `window_start_end_hook'.  Call function FN with seven arguments.
+    Return the result or nil if something went wrong.  */
+Lisp_Object
+safe_call7 (Lisp_Object fn, Lisp_Object arg1, Lisp_Object arg2, Lisp_Object arg3, Lisp_Object arg4, Lisp_Object arg5, Lisp_Object arg6, Lisp_Object arg7)
+{
+  return safe_call (8, fn, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+}
 
 
 /***********************************************************************
@@ -13416,6 +13423,68 @@ do { if (! polling_stopped_here) stop_polling ();	\
 do { if (polling_stopped_here) start_polling ();	\
        polling_stopped_here = false; } while (false)
 
+static void
+run_window_start_end_hook (struct text_pos startp, struct window *w, Lisp_Object window, struct it it, char *string)
+{
+  void *itdata = NULL;
+  bool fully_p = false;
+  EMACS_INT posint = PT;
+  int x, y, rtop, rbot, rowh, vpos, startp_integer, endp, pbol_startp, peol_endp;
+  startp_integer = CHARPOS (startp);
+  struct buffer *buf;
+  buf = XBUFFER (w->contents);
+  if (!NILP (Flocal_variable_p (Qwindow_start_end_hook, Fwindow_buffer (window)))
+      && w->window_start_end_hook_pending)
+    {
+    if ((posint >= CHARPOS (startp) && posint <= BUF_ZV (buf))
+        && CHARPOS (startp) >= BUF_BEGV (buf)
+        && CHARPOS (startp) <= BUF_ZV (buf)
+        && pos_visible_p (w, posint, &x, &y, &rtop, &rbot, &rowh, &vpos))
+      fully_p = !rtop && !rbot;
+    if (fully_p)
+      {
+#ifdef GLYPH_DEBUG
+      /* See the doc-string for `pos-visible-in-window-p'.  */
+      debug_method_add (w, "%s -- x: %d | y: %d | startp: %d | endp: %d | pbol_startp: %d | peol_endp: %d",
+                           string, x, y, startp_integer, endp, pbol_startp, peol_endp);
+#endif
+      CLIP_TEXT_POS_FROM_MARKER (startp, w->start);
+      itdata = bidi_shelve_cache ();
+      start_display (&it, w, startp);
+      move_it_vertically (&it, window_box_height (w));
+      if (it.current_y < it.last_visible_y)
+        move_it_past_eol (&it);
+      endp = (IT_CHARPOS (it));
+      bidi_unshelve_cache (itdata, false);
+      SET_PT (startp_integer);
+      pbol_startp = XINT (Fline_beginning_position (Qnil));
+      SET_PT (endp);
+      peol_endp = XINT (Fline_end_position (Qnil));
+      SET_PT (posint);
+      /*  Reset the flag _before_ calling Lisp, not after, so that
+          if Lisp does signal an error, the flag will be reset, and
+          won't cause the hook to be called ad nauseam.  */
+      w->window_start_end_hook_pending = false;
+      /*  When calling Lisp from redisplay, always use safe_call, to be
+          protected against errors signaled by that Lisp.  */
+      safe_call7 (Qrun_hook_with_args, Qwindow_start_end_hook,
+          window,
+          make_number (startp_integer),
+          make_number (endp),
+          make_number (pbol_startp),
+          make_number (peol_endp),
+          fully_p ? Qt : Qnil);
+      }
+      else
+        {
+#ifdef GLYPH_DEBUG
+        /* See the doc-string for `pos-visible-in-window-p'.  */
+        debug_method_add (w, "%s -- x: %d | y: %d | rtop: %d | rbot %d | rowh: %d | vpos: %d",
+                              string, x, y, rtop, rbot, rowh, vpos);
+#endif
+        }
+    }
+}
 
 /* Perhaps in the future avoid recentering windows if it
    is not necessary; currently that causes some problems.  */
@@ -13713,6 +13782,7 @@ redisplay_internal (void)
       && match_p
       && !w->force_start
       && !w->optional_new_start
+      && !w->window_start_end_hook_force
       /* Point must be on the line that we have info recorded about.  */
       && PT >= CHARPOS (tlbufpos)
       && PT <= Z - CHARPOS (tlendpos)
@@ -15412,6 +15482,8 @@ try_scrolling (Lisp_Object window, bool just_this_one_p,
   /* Run window scroll functions.  */
   startp = run_window_scroll_functions (window, startp);
 
+  run_window_start_end_hook (startp, w, window, it, "try_scrolling");
+
   /* Display the window.  Give up if new fonts are loaded, or if point
      doesn't appear.  */
   if (!try_window (window, startp, 0))
@@ -16076,6 +16148,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   bool last_line_misfit = false;
   ptrdiff_t beg_unchanged, end_unchanged;
   int frame_line_height;
+  bool window_start_end_hook_suppress_wsf = false;
 
   SET_TEXT_POS (lpoint, PT, PT_BYTE);
   opoint = lpoint;
@@ -16266,13 +16339,16 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 
   /* If someone specified a new starting point but did not insist,
      check whether it can be used.  */
-  if ((w->optional_new_start || window_frozen_p (w))
+  if ((w->optional_new_start || w->window_start_end_hook_force || window_frozen_p (w))
       && CHARPOS (startp) >= BEGV
       && CHARPOS (startp) <= ZV)
     {
+
+      if (w->window_start_end_hook_force && !w->optional_new_start && !window_frozen_p (w) && !w->force_start)
+        window_start_end_hook_suppress_wsf = true;
+
       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);
@@ -16293,14 +16369,28 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	    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 (window_frozen_p (w))
+        {
+        debug_method_add (w, "set force_start from frozen window start");
+      }
+      else if (w->optional_new_start)
+        {
+        debug_method_add (w, "set force_start from optional_new_start");
+      }
+      else if (w->window_start_end_hook_force)
+        {
+        debug_method_add (w, "set force_start from window_start_end_hook_force");
+      }
+      else
+        {
+        debug_method_add (w, "This situation is not yet contemplated.");
+      }
+    }
 #endif
 	}
+      w->optional_new_start = false;
+      w->window_start_end_hook_force = false;
     }
 
  force_start:
@@ -16328,11 +16418,18 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	 than to get into an infinite loop calling the hook functions
 	 and having them get more errors.  */
       if (!update_mode_line
-	  || ! NILP (Vwindow_scroll_functions))
+	  || ! NILP (Vwindow_scroll_functions)
+	  || ! NILP (Vwindow_start_end_hook))
 	{
 	  update_mode_line = true;
 	  w->update_mode_line = true;
-	  startp = run_window_scroll_functions (window, startp);
+
+  /* Run window scroll functions.  */
+  if (!window_start_end_hook_suppress_wsf)
+    startp = run_window_scroll_functions (window, startp);
+
+  run_window_start_end_hook (startp, w, window, it, "redisplay_window (force_start)");
+
 	}
 
       if (CHARPOS (startp) < BEGV)
@@ -16600,6 +16697,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	     because a window scroll function can have changed the
 	     buffer.  */
 	  || !NILP (Vwindow_scroll_functions)
+	  || !NILP (Vwindow_start_end_hook)
 	  || MINI_WINDOW_P (w)
 	  || !(used_current_matrix_p
 	       = try_window_reusing_current_matrix (w)))
@@ -16797,6 +16895,8 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   /* Run scroll hooks.  */
   startp = run_window_scroll_functions (window, it.current.pos);
 
+  run_window_start_end_hook (startp, w, window, it, "redisplay_window (recenter)");
+
   /* Redisplay the window.  */
   bool use_desired_matrix = false;
   if (!current_matrix_up_to_date_p
@@ -16805,6 +16905,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
       /* Don't use try_window_reusing_current_matrix in this case
 	 because it can have changed the buffer.  */
       || !NILP (Vwindow_scroll_functions)
+      || !NILP (Vwindow_start_end_hook)
       || !just_this_one_p
       || MINI_WINDOW_P (w)
       || !(used_current_matrix_p
@@ -23680,6 +23781,20 @@ decode_mode_spec (struct window *w, register int c, int field_width,
 	  return "@";
       }
 
+    case 'w':
+      {
+  ptrdiff_t window_start = marker_position (w->start);
+  pint2str (decode_mode_spec_buf, width, window_start);
+  return decode_mode_spec_buf;
+      }
+
+    case 'W':
+      {
+  ptrdiff_t window_end = BUF_Z (b) - w->window_end_pos;
+  pint2str (decode_mode_spec_buf, width, window_end);
+  return decode_mode_spec_buf;
+      }
+
     case 'z':
       /* coding-system (not including end-of-line format) */
     case 'Z':
@@ -31145,6 +31260,7 @@ They are still logged to the *Messages* buffer.  */);
   DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map");
   DEFSYM (Qoverriding_local_map, "overriding-local-map");
   DEFSYM (Qwindow_scroll_functions, "window-scroll-functions");
+  DEFSYM (Qwindow_start_end_hook, "window-start-end-hook");
   DEFSYM (Qwindow_text_change_functions, "window-text-change-functions");
   DEFSYM (Qredisplay_end_trigger_functions, "redisplay-end-trigger-functions");
   DEFSYM (Qinhibit_point_motion_hooks, "inhibit-point-motion-hooks");
@@ -31466,6 +31582,13 @@ is scrolled.  It is not designed for that, and such use probably won't
 work.  */);
   Vwindow_scroll_functions = Qnil;
 
+  DEFVAR_LISP ("window-start-end-hook", Vwindow_start_end_hook,
+    doc: /* A bufer-local hook used to obtain new `window-start` and `window-end`
+during redisplay.  The function attached to this hook has the following arguments:
+WINDOW, WINDOW-START, WINDOW-END, PBOL-START, PEOL-END, FULLY-P.  */);
+  Vwindow_start_end_hook = Qnil;
+  Fmake_variable_buffer_local (Qwindow_start_end_hook);
+
   DEFVAR_LISP ("window-text-change-functions",
 	       Vwindow_text_change_functions,
     doc: /* Functions to call in redisplay when text in the window might change.  */);
