diff --git a/src/keyboard.c b/src/keyboard.c
index 64d7b13..4cbfdce 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1239,6 +1239,7 @@ command_loop_1 (void)
   EMACS_INT prev_modiff = 0;
   struct buffer *prev_buffer = NULL;
   bool already_adjusted = 0;
+  Lisp_Object window;
 
   kset_prefix_arg (current_kboard, Qnil);
   kset_last_prefix_arg (current_kboard, Qnil);
@@ -1258,6 +1259,12 @@ command_loop_1 (void)
       if (!NILP (Vpost_command_hook) && !NILP (Vrun_hooks))
 	safe_run_hooks (Qpost_command_hook);
 
+  /* Trigger the `window-start-end-hook`.
+  Redisplay resets `w->window_start_end_toggle` to `false`.  */
+  window = (selected_window);
+  register struct window *w = decode_live_window (window);
+  w->window_start_end_toggle = true;
+
       /* If displaying a message, resize the echo area window to fit
 	 that message's size exactly.  */
       if (!NILP (echo_area_buffer[0]))
@@ -1474,6 +1481,12 @@ command_loop_1 (void)
 
       safe_run_hooks (Qpost_command_hook);
 
+      /* Trigger the `window-start-end-hook`.
+      Redisplay resets `w->window_start_end_toggle` to `false`.  */
+      window = (selected_window);
+      register struct window *w = decode_live_window (window);
+      w->window_start_end_toggle = true;
+
       /* 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/window.h b/src/window.h
index c29207d..dcc3339 100644
--- a/src/window.h
+++ b/src/window.h
@@ -348,6 +348,9 @@ 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_toggle : 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 78fddd6..b4fbb38 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -13704,6 +13704,7 @@ redisplay_internal (void)
       && match_p
       && !w->force_start
       && !w->optional_new_start
+      && !w->window_start_end_toggle
       /* Point must be on the line that we have info recorded about.  */
       && PT >= CHARPOS (tlbufpos)
       && PT <= Z - CHARPOS (tlendpos)
@@ -15062,6 +15063,23 @@ run_window_scroll_functions (Lisp_Object window, struct text_pos startp)
   return startp;
 }
 
+static struct text_pos
+run_window_start_end_hook (Lisp_Object window, struct text_pos startp, int endp,
+         int pbol_startp, int peol_endp)
+{
+  struct window *w = XWINDOW (window);
+  SET_MARKER_FROM_TEXT_POS (w->start, startp);
+  eassert (current_buffer == XBUFFER (w->contents));
+  CALLN (Frun_hook_with_args, Qwindow_start_end_hook, window,
+    make_number (CHARPOS (startp)),
+    make_number (endp),
+    make_number (pbol_startp),
+    make_number (peol_endp));
+  SET_TEXT_POS_FROM_MARKER (startp, w->start);
+  /* In case the hook functions switch buffers.  */
+  set_buffer_internal (XBUFFER (w->contents));
+  return startp;
+}
 
 /* Make sure the line containing the cursor is fully visible.
    A value of true means there is nothing to be done.
@@ -15154,7 +15172,7 @@ try_scrolling (Lisp_Object window, bool just_this_one_p,
   struct frame *f = XFRAME (w->frame);
   struct text_pos pos, startp;
   struct it it;
-  int this_scroll_margin, scroll_max, rc, height;
+  int this_scroll_margin, scroll_max, rc, height, endp, pbol_startp, peol_endp;
   int dy = 0, amount_to_scroll = 0;
   bool scroll_down_p = false;
   int extra_scroll_margin_lines = last_line_misfit;
@@ -15403,6 +15421,43 @@ try_scrolling (Lisp_Object window, bool just_this_one_p,
   /* Run window scroll functions.  */
   startp = run_window_scroll_functions (window, startp);
 
+  if (!NILP (Vwindow_start_end_hook)
+      && (Flocal_variable_p (Qwindow_start_end_var, Fwindow_buffer (window))))
+    {
+    struct buffer *b;
+    Lisp_Object buf;
+    buf = w->contents;
+    CHECK_BUFFER (buf);
+    b = XBUFFER (buf);
+    struct buffer *old_buffer = NULL;
+    void *itdata = NULL;
+    if (b != current_buffer)
+      {
+      old_buffer = current_buffer;
+      set_buffer_internal (b);
+      }
+    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);
+    if (old_buffer)
+      set_buffer_internal (old_buffer);
+    ptrdiff_t original_pos = PT;
+    SET_PT (CHARPOS (startp));
+    pbol_startp = XINT (Fline_beginning_position (Qnil));
+    SET_PT (endp);
+    peol_endp = XINT (Fline_end_position (Qnil));
+    SET_PT (original_pos);
+    startp = run_window_start_end_hook (window, startp, endp, pbol_startp, peol_endp);
+#ifdef GLYPH_DEBUG
+  debug_method_add (w, "run_window_start_end_hook [1 of 3]");
+#endif
+    }
+
   /* Display the window.  Give up if new fonts are loaded, or if point
      doesn't appear.  */
   if (!try_window (window, startp, 0))
@@ -16052,7 +16107,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   struct buffer *old = current_buffer;
   struct text_pos lpoint, opoint, startp;
   bool update_mode_line;
-  int tem;
+  int tem, endp, pbol_startp, peol_endp;
   struct it it;
   /* Record it now because it's overwritten.  */
   bool current_matrix_up_to_date_p = false;
@@ -16257,13 +16312,12 @@ 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_toggle || window_frozen_p (w))
       && CHARPOS (startp) >= BEGV
       && CHARPOS (startp) <= ZV)
     {
       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);
@@ -16284,14 +16338,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_toggle)
+        {
+        debug_method_add (w, "set force_start from window_start_end_toggle");
+      }
+      else
+        {
+        debug_method_add (w, "This situation is not yet contemplated.");
+      }
+    }
 #endif
 	}
+      w->optional_new_start = false;
+      w->window_start_end_toggle = false;
     }
 
  force_start:
@@ -16319,11 +16387,54 @@ 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;
+
+  /* Run window scroll functions.  FIXME:  set up conditions so that
+`run_window_scroll_functions` does not always run whenever
+`w->window_start_end_toggle` is `true`.  */
 	  startp = run_window_scroll_functions (window, startp);
+
+  if (!NILP (Vwindow_start_end_hook)
+      && (Flocal_variable_p (Qwindow_start_end_var, Fwindow_buffer (window))))
+    {
+    struct buffer *b;
+    Lisp_Object buf;
+    buf = w->contents;
+    CHECK_BUFFER (buf);
+    b = XBUFFER (buf);
+    struct buffer *old_buffer = NULL;
+    void *itdata = NULL;
+    if (b != current_buffer)
+      {
+      old_buffer = current_buffer;
+      set_buffer_internal (b);
+      }
+    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);
+    if (old_buffer)
+      set_buffer_internal (old_buffer);
+    ptrdiff_t original_pos = PT;
+    SET_PT (CHARPOS (startp));
+    pbol_startp = XINT (Fline_beginning_position (Qnil));
+    SET_PT (endp);
+    peol_endp = XINT (Fline_end_position (Qnil));
+    SET_PT (original_pos);
+    startp = run_window_start_end_hook (window, startp, endp, pbol_startp, peol_endp);
+#ifdef GLYPH_DEBUG
+  debug_method_add (w, "run_window_start_end_hook [2 of 3]");
+#endif
+    }
+
 	}
 
       if (CHARPOS (startp) < BEGV)
@@ -16591,6 +16702,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)))
@@ -16788,6 +16900,43 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   /* Run scroll hooks.  */
   startp = run_window_scroll_functions (window, it.current.pos);
 
+  if (!NILP (Vwindow_start_end_hook)
+      && (Flocal_variable_p (Qwindow_start_end_var, Fwindow_buffer (window))))
+    {
+    struct buffer *b;
+    Lisp_Object buf;
+    buf = w->contents;
+    CHECK_BUFFER (buf);
+    b = XBUFFER (buf);
+    struct buffer *old_buffer = NULL;
+    void *itdata = NULL;
+    if (b != current_buffer)
+      {
+      old_buffer = current_buffer;
+      set_buffer_internal (b);
+      }
+    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);
+    if (old_buffer)
+      set_buffer_internal (old_buffer);
+    ptrdiff_t original_pos = PT;
+    SET_PT (CHARPOS (startp));
+    pbol_startp = XINT (Fline_beginning_position (Qnil));
+    SET_PT (endp);
+    peol_endp = XINT (Fline_end_position (Qnil));
+    SET_PT (original_pos);
+    startp = run_window_start_end_hook (window, startp, endp, pbol_startp, peol_endp);
+#ifdef GLYPH_DEBUG
+  debug_method_add (w, "run_window_start_end_hook [3 of 3]");
+#endif
+    }
+
   /* Redisplay the window.  */
   bool use_desired_matrix = false;
   if (!current_matrix_up_to_date_p
@@ -16796,6 +16945,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
@@ -23671,6 +23821,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':
@@ -31136,6 +31300,8 @@ 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_start_end_var, "window-start-end-var");
   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");
@@ -31457,6 +31623,15 @@ 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: /* Used to obtain the new `window-start' during redisplay.  */);
+  Vwindow_start_end_hook = Qnil;
+
+  DEFVAR_LISP ("window-start-end-var", Vwindow_start_end_var,
+    doc: /* `window-start-end-hook` checks to see if `window-start-end-var` is set locally.  */);
+  Vwindow_start_end_var = Qnil;
+  Fmake_variable_buffer_local (Qwindow_start_end_var);
+
   DEFVAR_LISP ("window-text-change-functions",
 	       Vwindow_text_change_functions,
     doc: /* Functions to call in redisplay when text in the window might change.  */);
