diff --git a/cmd-parse.y b/cmd-parse.y index 4cd8a420..215943b1 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -758,7 +758,7 @@ static int cmd_parse_expand_alias(struct cmd_parse_command *cmd, struct cmd_parse_input *pi, struct cmd_parse_result *pr) { - struct cmd_parse_argument *arg, *arg1, *first; + struct cmd_parse_argument *first; struct cmd_parse_commands *cmds; struct cmd_parse_command *last; char *alias, *name, *cause; @@ -798,10 +798,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd, TAILQ_REMOVE(&cmd->arguments, first, entry); cmd_parse_free_argument(first); - TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) { - TAILQ_REMOVE(&cmd->arguments, arg, entry); - TAILQ_INSERT_TAIL(&last->arguments, arg, entry); - } + TAILQ_CONCAT(&last->arguments, &cmd->arguments, entry); cmd_parse_log_commands(cmds, __func__); pi->flags |= CMD_PARSE_NOALIAS; diff --git a/format.c b/format.c index 17654c30..fa7e940d 100644 --- a/format.c +++ b/format.c @@ -1947,6 +1947,18 @@ format_cb_origin_flag(struct format_tree *ft) return (NULL); } +/* Callback for synchronized_output_flag. */ +static void * +format_cb_synchronized_output_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_SYNC) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_active. */ static void * format_cb_pane_active(struct format_tree *ft) @@ -3443,6 +3455,9 @@ static const struct format_table_entry format_table[] = { { "start_time", FORMAT_TABLE_TIME, format_cb_start_time }, + { "synchronized_output_flag", FORMAT_TABLE_STRING, + format_cb_synchronized_output_flag + }, { "tree_mode_format", FORMAT_TABLE_STRING, format_cb_tree_mode_format }, diff --git a/image.c b/image.c index 6e2b1701..0948925b 100644 --- a/image.c +++ b/image.c @@ -27,11 +27,36 @@ static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); static u_int all_images_count; #define MAX_IMAGE_COUNT 20 +static void printflike(3, 4) +image_log(struct image *im, const char* from, const char* fmt, ...) +{ + va_list ap; + char s[128]; + + if (log_get_level() == 0) + return; + + if (fmt == NULL) { + log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy, + im->px, im->py); + return; + } + + va_start(ap, fmt); + vsnprintf(s, sizeof s, fmt, ap); + va_end(ap); + + log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy, + im->px, im->py, s); +} + static void image_free(struct image *im) { struct screen *s = im->s; + image_log(im, __func__, NULL); + TAILQ_REMOVE(&all_images, im, all_entry); all_images_count--; @@ -47,6 +72,8 @@ image_free_all(struct screen *s) struct image *im, *im1; int redraw = !TAILQ_EMPTY(&s->images); + if (redraw) + log_debug ("%s", __func__); TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) image_free(im); return (redraw); @@ -109,6 +136,7 @@ image_store(struct screen *s, struct sixel_image *si) image_fallback(&im->fallback, im->sx, im->sy); + image_log(im, __func__, NULL); TAILQ_INSERT_TAIL(&s->images, im, entry); TAILQ_INSERT_TAIL(&all_images, im, all_entry); @@ -122,10 +150,12 @@ int image_check_line(struct screen *s, u_int py, u_int ny) { struct image *im, *im1; - int redraw = 0; + int redraw = 0, in; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (py + ny > im->py && py < im->py + im->sy) { + in = (py + ny > im->py && py < im->py + im->sy); + image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); + if (in) { image_free(im); redraw = 1; } @@ -137,15 +167,18 @@ int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { struct image *im, *im1; - int redraw = 0; + int redraw = 0, in; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (py + ny <= im->py || py >= im->py + im->sy) - continue; - if (px + nx <= im->px || px >= im->px + im->sx) - continue; - image_free(im); - redraw = 1; + in = (py < im->py + im->sy && + py + ny > im->py && + px < im->px + im->sx && + px + nx > im->px); + image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); + if (in) { + image_free(im); + redraw = 1; + } } return (redraw); } @@ -160,17 +193,20 @@ image_scroll_up(struct screen *s, u_int lines) TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { if (im->py >= lines) { + image_log(im, __func__, "1, lines=%u", lines); im->py -= lines; redraw = 1; continue; } if (im->py + im->sy <= lines) { + image_log(im, __func__, "2, lines=%u", lines); image_free(im); redraw = 1; continue; } sx = im->sx; sy = (im->py + im->sy) - lines; + image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy); new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); sixel_free(im->data); diff --git a/input.c b/input.c index 58a1b091..98961f03 100644 --- a/input.c +++ b/input.c @@ -898,6 +898,8 @@ input_free(struct input_ctx *ictx) evbuffer_free(ictx->since_ground); event_del(&ictx->ground_timer); + screen_write_stop_sync(ictx->wp); + free(ictx); } @@ -1901,6 +1903,11 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); break; + case 2026: /* synchronized output */ + screen_write_stop_sync(ictx->wp); + if (ictx->wp != NULL) + ictx->wp->flags |= PANE_REDRAW; + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -1999,6 +2006,9 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) case 2031: screen_write_mode_set(sctx, MODE_THEME_UPDATES); break; + case 2026: /* synchronized output */ + screen_write_start_sync(ictx->wp); + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; diff --git a/key-bindings.c b/key-bindings.c index 1275bf84..12c73347 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -488,9 +488,9 @@ key_bindings_init(void) "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", /* Mouse on scrollbar. */ - "bind -n MouseDown1ScrollbarUp { copy-mode -u }", - "bind -n MouseDown1ScrollbarDown { copy-mode -d }", - "bind -n MouseDrag1ScrollbarSlider { copy-mode -S }", + "bind -n MouseDown1ScrollbarUp { if -Ft= '#{pane_in_mode}' { send -X page-up } {copy-mode -u } }", + "bind -n MouseDown1ScrollbarDown { if -Ft= '#{pane_in_mode}' { send -X page-down } {copy-mode -d } }", + "bind -n MouseDrag1ScrollbarSlider { if -Ft= '#{pane_in_mode}' { send -X scroll-to-mouse } { copy-mode -S } }", /* Copy mode (emacs) keys. */ "bind -Tcopy-mode C-Space { send -X begin-selection }", diff --git a/options-table.c b/options-table.c index 447fb52e..eac01c09 100644 --- a/options-table.c +++ b/options-table.c @@ -958,7 +958,7 @@ const struct options_table_entry options_table[] = { { .name = "prompt-cursor-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 6, + .default_num = -1, .text = "Colour of the cursor when in the command prompt." }, diff --git a/screen-redraw.c b/screen-redraw.c index 108648bb..2910ac7a 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -224,9 +224,10 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if ((wp->xoff == 0 || (int)px >= wp->xoff) && ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { - if (wp->yoff != 0 && (int)py == wp->yoff - 1) + if (pane_status != PANE_STATUS_BOTTOM && + wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if ((int)py == ey) + if (pane_status != PANE_STATUS_TOP && (int)py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } @@ -1164,6 +1165,9 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct visible_ranges *vr; u_int i, j, top, x, y, width, r; + if (wp->base.mode & MODE_SYNC) + screen_write_stop_sync(wp); + log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); if (wp->xoff + (int)wp->sx <= ctx->ox || diff --git a/screen-write.c b/screen-write.c index c7ab21bd..6d626613 100644 --- a/screen-write.c +++ b/screen-write.c @@ -894,6 +894,52 @@ screen_write_mode_clear(struct screen_write_ctx *ctx, int mode) log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } +/* Sync timeout callback. */ +static void +screen_write_sync_callback(__unused int fd, __unused short events, void *arg) +{ + struct window_pane *wp = arg; + + log_debug("%s: %%%u sync timer expired", __func__, wp->id); + evtimer_del(&wp->sync_timer); + + if (wp->base.mode & MODE_SYNC) { + wp->base.mode &= ~MODE_SYNC; + wp->flags |= PANE_REDRAW; + } +} + +/* Start sync mode. */ +void +screen_write_start_sync(struct window_pane *wp) +{ + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + + if (wp == NULL) + return; + + wp->base.mode |= MODE_SYNC; + if (!event_initialized(&wp->sync_timer)) + evtimer_set(&wp->sync_timer, screen_write_sync_callback, wp); + evtimer_add(&wp->sync_timer, &tv); + + log_debug("%s: %%%u started sync mode", __func__, wp->id); +} + +/* Stop sync mode. */ +void +screen_write_stop_sync(struct window_pane *wp) +{ + if (wp == NULL) + return; + + if (event_initialized(&wp->sync_timer)) + evtimer_del(&wp->sync_timer); + wp->base.mode &= ~MODE_SYNC; + + log_debug("%s: %%%u stopped sync mode", __func__, wp->id); +} + /* Cursor up by ny. */ void screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny) @@ -1787,6 +1833,9 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct visible_ranges *vr; struct window_pane *wp = ctx->wp; + if (s->mode & MODE_SYNC) + return; + if (ctx->scrolled != 0) { log_debug("%s: scrolled %u (region %u-%u)", __func__, ctx->scrolled, s->rupper, s->rlower); @@ -2134,7 +2183,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } /* Write to the screen. */ - if (!skip) { + if (!skip && !(s->mode & MODE_SYNC)) { if (selected) { screen_select_cell(s, &tmp_gc, gc); ttyctx.cell = &tmp_gc; @@ -2372,8 +2421,11 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; struct sixel_image *new; + if (screen_size_y(s) == 1) + return; + sixel_size_in_cells(si, &x, &y); - if (x > screen_size_x(s) || y > screen_size_y(s)) { + if (x > screen_size_x(s) || y > screen_size_y(s) - 1) { if (x > screen_size_x(s) - cx) sx = screen_size_x(s) - cx; else @@ -2384,16 +2436,14 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, sy = y; new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); sixel_free(si); - si = new; - - /* Bail out if the image cannot be scaled. */ - if (si == NULL) + if (new == NULL) return; - sixel_size_in_cells(si, &x, &y); + sixel_size_in_cells(new, &x, &y); + si = new; } sy = screen_size_y(s) - cy; - if (sy < y) { + if (sy <= y) { lines = y - sy + 1; if (image_scroll_up(s, lines) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; diff --git a/spawn.c b/spawn.c index 93ffc4b1..38816f39 100644 --- a/spawn.c +++ b/spawn.c @@ -211,7 +211,9 @@ spawn_pane(struct spawn_context *sc, char **cause) struct environ *child; struct environ_entry *ee; char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd; - const char *cmd, *tmp; + char path[PATH_MAX]; + const char *cmd, *tmp, *home = find_home(); + const char *actual_cwd = NULL; int argc; u_int idx; struct termios now; @@ -373,6 +375,16 @@ spawn_pane(struct spawn_context *sc, char **cause) goto complete; } + /* Store current working directory and change to new one. */ + if (getcwd(path, sizeof path) != NULL) { + if (chdir(new_wp->cwd) == 0) + actual_cwd = new_wp->cwd; + else if (home != NULL && chdir(home) == 0) + actual_cwd = home; + else if (chdir("/") == 0) + actual_cwd = "/"; + } + /* Fork the new process. */ new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws); if (new_wp->pid == -1) { @@ -388,8 +400,15 @@ spawn_pane(struct spawn_context *sc, char **cause) return (NULL); } - /* In the parent process, everything is done now. */ + /* + * In the parent process, everything is done now. Change the working + * directory back. + */ if (new_wp->pid != 0) { + if (actual_cwd != NULL && + chdir(path) != 0 && + (home == NULL || chdir(home) != 0)) + chdir("/"); goto complete; } @@ -404,17 +423,10 @@ spawn_pane(struct spawn_context *sc, char **cause) } #endif /* - * Child process. Change to the working directory or home if that - * fails. + * Child process. Set PWD to the working directory. */ - if (chdir(new_wp->cwd) == 0) - environ_set(child, "PWD", 0, "%s", new_wp->cwd); - else if ((tmp = find_home()) != NULL && chdir(tmp) == 0) - environ_set(child, "PWD", 0, "%s", tmp); - else if (chdir("/") == 0) - environ_set(child, "PWD", 0, "/"); - else - fatal("chdir failed"); + if (actual_cwd != NULL) + environ_set(child, "PWD", 0, "%s", actual_cwd); /* * Update terminal escape characters from the session if available and diff --git a/tmux.1 b/tmux.1 index 404909bd..ca846d7d 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1663,9 +1663,10 @@ is used, option will not be applied. .Pp .Fl T -sets the client's key table; the next key from the client will be interpreted -from +sets the client's key table; the next key will be looked up using .Ar key-table . +After that key, the client is returned to its default key table (normally +.Em root ) . This may be used to configure multiple prefix keys, or to bind commands to sequences of keys. For example, to make typing @@ -2213,6 +2214,13 @@ but also exit copy mode if the cursor reaches the bottom. Scroll so that the current line becomes the middle one while keeping the cursor on that line. .It Xo +.Ic scroll-to-mouse +.Xc +Scroll pane in copy-mode when bound to a mouse drag event. +.Fl e +causes copy mode to exit when at the bottom. +.Pp +.It Xo .Ic scroll-top .Xc Scroll down until the current line is at the top while keeping the cursor on @@ -2449,12 +2457,10 @@ cancels copy mode and any other modes. .Fl M begins a mouse drag (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . +.Pp .Fl S -scrolls when bound to a mouse drag event; for example, -.Ic copy-mode -Se -is bound to -.Ar MouseDrag1ScrollbarSlider -by default. +enters copy-mode and scrolls when bound to a mouse drag event; See +.Ic scroll-to-mouse . .Pp .Fl s copies from @@ -6258,6 +6264,7 @@ The following variables are available, where appropriate: .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "sixel_support" Ta "" Ta "1 if server has support for SIXEL" .It Li "start_time" Ta "" Ta "Server start time" +.It Li "synchronized_output_flag" Ta "" Ta "1 if pane has synchronized output enabled" .It Li "uid" Ta "" Ta "Server UID" .It Li "user" Ta "" Ta "Server user" .It Li "version" Ta "" Ta "Server version" diff --git a/tmux.h b/tmux.h index d67e96ef..477b7279 100644 --- a/tmux.h +++ b/tmux.h @@ -652,6 +652,7 @@ enum tty_code_code { #define MODE_CURSOR_BLINKING_SET 0x20000 #define MODE_KEYS_EXTENDED_2 0x40000 #define MODE_THEME_UPDATES 0x80000 +#define MODE_SYNC 0x100000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) @@ -1225,6 +1226,7 @@ struct window_pane { struct window_pane_resizes resize_queue; struct event resize_timer; + struct event sync_timer; struct input_ctx *ictx; @@ -3160,6 +3162,8 @@ void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, void screen_write_backspace(struct screen_write_ctx *); void screen_write_mode_set(struct screen_write_ctx *, int); void screen_write_mode_clear(struct screen_write_ctx *, int); +void screen_write_start_sync(struct window_pane *); +void screen_write_stop_sync(struct window_pane *); void screen_write_cursorup(struct screen_write_ctx *, u_int); void screen_write_cursordown(struct screen_write_ctx *, u_int); void screen_write_cursorright(struct screen_write_ctx *, u_int); diff --git a/tty-keys.c b/tty-keys.c index 7474621c..c59e6a68 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -909,9 +909,15 @@ first_key: * used. termios should have a better idea. */ bspace = tty->tio.c_cc[VERASE]; - if (bspace != _POSIX_VDISABLE && key == bspace) { - log_debug("%s: key %#llx is backspace", c->name, key); - key = KEYC_BSPACE; + if (bspace != _POSIX_VDISABLE) { + if (key == bspace) { + log_debug("%s: key %#llx is BSpace", c->name, key); + key = KEYC_BSPACE; + } + if (key == (bspace|KEYC_META)) { + log_debug("%s: key %#llx is M-BSpace", c->name, key); + key = KEYC_BSPACE|KEYC_META; + } } /* @@ -1304,7 +1310,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) struct client *c = tty->client; size_t end, terminator = 0, needed; char *copy, *out; - int outlen; + int outlen; struct input_request_clipboard_data cd; *size = 0; diff --git a/window-clock.c b/window-clock.c index 51620d4a..81d33858 100644 --- a/window-clock.c +++ b/window-clock.c @@ -123,6 +123,26 @@ const char window_clock_table[14][5][5] = { { 1,0,0,0,1 } }, }; +static void +window_clock_start_timer(struct window_mode_entry *wme) +{ + struct window_clock_mode_data *data = wme->data; + struct timeval tv; + struct timespec ts; + long delay; + + clock_gettime(CLOCK_REALTIME, &ts); + delay = 1000000 - (ts.tv_nsec / 1000); + + tv.tv_sec = delay / 1000000; + tv.tv_usec = delay % 1000000; + if (tv.tv_sec < 0 || (tv.tv_sec == 0 && tv.tv_usec <= 0)) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + evtimer_add(&data->timer, &tv); +} + static void window_clock_timer_callback(__unused int fd, __unused short events, void *arg) { @@ -131,23 +151,20 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) struct window_clock_mode_data *data = wme->data; struct tm now, then; time_t t; - struct timeval tv = { .tv_sec = 1 }; evtimer_del(&data->timer); - evtimer_add(&data->timer, &tv); - - if (TAILQ_FIRST(&wp->modes) != wme) - return; t = time(NULL); gmtime_r(&t, &now); gmtime_r(&data->tim, &then); - if (now.tm_sec == then.tm_sec) - return; - data->tim = t; - window_clock_draw_screen(wme); - wp->flags |= PANE_REDRAW; + if (now.tm_sec != then.tm_sec) { + data->tim = t; + window_clock_draw_screen(wme); + wp->flags |= PANE_REDRAW; + } + + window_clock_start_timer(wme); } static struct screen * @@ -157,13 +174,12 @@ window_clock_init(struct window_mode_entry *wme, struct window_pane *wp = wme->wp; struct window_clock_mode_data *data; struct screen *s; - struct timeval tv = { .tv_sec = 1 }; wme->data = data = xmalloc(sizeof *data); data->tim = time(NULL); evtimer_set(&data->timer, window_clock_timer_callback, wme); - evtimer_add(&data->timer, &tv); + window_clock_start_timer(wme); s = &data->screen; screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); diff --git a/window-copy.c b/window-copy.c index 313e08a1..957fc5d6 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1479,6 +1479,20 @@ window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) return (window_copy_cmd_scroll_to(cs, mid_value)); } +/* Scroll the pane to the mouse in the scrollbar. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_to_mouse(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_pane *wp = wme->wp; + struct client *c = cs->c; + struct mouse_event *m = cs->m; + int scroll_exit = args_has(cs->wargs, 'e'); + + window_copy_scroll(wp, c->tty.mouse_slider_mpos, m->y, scroll_exit); + return (WINDOW_COPY_CMD_NOTHING); +} + /* Scroll line containing the cursor to the top. */ static enum window_copy_cmd_action window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs) @@ -3044,6 +3058,11 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_middle }, + { .command = "scroll-to-mouse", + .args = { "e", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_scroll_to_mouse + }, { .command = "scroll-top", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, diff --git a/window.c b/window.c index 916fb7d4..bb930082 100644 --- a/window.c +++ b/window.c @@ -1104,6 +1104,8 @@ window_pane_destroy(struct window_pane *wp) if (event_initialized(&wp->resize_timer)) event_del(&wp->resize_timer); + if (event_initialized(&wp->sync_timer)) + event_del(&wp->sync_timer); TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { TAILQ_REMOVE(&wp->resize_queue, r, entry); free(r); @@ -1183,6 +1185,8 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) if (sx == wp->sx && sy == wp->sy) return; + screen_write_stop_sync(wp); + r = xmalloc(sizeof *r); r->sx = sx; r->sy = sy;