1
0
mirror of https://github.com/tmate-io/tmate-ssh-server.git synced 2020-11-18 19:53:51 -08:00

Sync OpenBSD patchset 658:

Permit keys in copy mode to be prefixed by a repeat count, entered with
[1-9] in vi mode, or M-[1-9] in emacs mode.

From Micah Cowan, tweaked a little by me.
This commit is contained in:
Tiago Cunha 2010-03-08 15:02:07 +00:00
parent 70f5384d8f
commit b34c8f5f39
4 changed files with 172 additions and 51 deletions

View File

@ -1,4 +1,4 @@
/* $Id: mode-key.c,v 1.44 2010-02-18 12:35:16 tcunha Exp $ */ /* $Id: mode-key.c,v 1.45 2010-03-08 15:02:07 tcunha Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
@ -106,6 +106,7 @@ struct mode_key_cmdstr mode_key_cmdstr_copy[] = {
{ MODEKEYCOPY_SEARCHDOWN, "search-forward" }, { MODEKEYCOPY_SEARCHDOWN, "search-forward" },
{ MODEKEYCOPY_SEARCHREVERSE, "search-reverse" }, { MODEKEYCOPY_SEARCHREVERSE, "search-reverse" },
{ MODEKEYCOPY_SEARCHUP, "search-backward" }, { MODEKEYCOPY_SEARCHUP, "search-backward" },
{ MODEKEYCOPY_STARTNUMBERPREFIX, "start-number-prefix" },
{ MODEKEYCOPY_STARTOFLINE, "start-of-line" }, { MODEKEYCOPY_STARTOFLINE, "start-of-line" },
{ MODEKEYCOPY_STARTSELECTION, "begin-selection" }, { MODEKEYCOPY_STARTSELECTION, "begin-selection" },
{ MODEKEYCOPY_TOPLINE, "top-line" }, { MODEKEYCOPY_TOPLINE, "top-line" },
@ -178,6 +179,15 @@ const struct mode_key_entry mode_key_vi_copy[] = {
{ '$', 0, MODEKEYCOPY_ENDOFLINE }, { '$', 0, MODEKEYCOPY_ENDOFLINE },
{ '/', 0, MODEKEYCOPY_SEARCHDOWN }, { '/', 0, MODEKEYCOPY_SEARCHDOWN },
{ '0', 0, MODEKEYCOPY_STARTOFLINE }, { '0', 0, MODEKEYCOPY_STARTOFLINE },
{ '1', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '2', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '3', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '4', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '5', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '6', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '7', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '8', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '9', 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ ':', 0, MODEKEYCOPY_GOTOLINE }, { ':', 0, MODEKEYCOPY_GOTOLINE },
{ '?', 0, MODEKEYCOPY_SEARCHUP }, { '?', 0, MODEKEYCOPY_SEARCHUP },
{ 'B', 0, MODEKEYCOPY_PREVIOUSSPACE }, { 'B', 0, MODEKEYCOPY_PREVIOUSSPACE },
@ -280,6 +290,15 @@ struct mode_key_tree mode_key_tree_emacs_choice;
/* emacs copy mode keys. */ /* emacs copy mode keys. */
const struct mode_key_entry mode_key_emacs_copy[] = { const struct mode_key_entry mode_key_emacs_copy[] = {
{ ' ', 0, MODEKEYCOPY_NEXTPAGE }, { ' ', 0, MODEKEYCOPY_NEXTPAGE },
{ '1' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '2' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '3' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '4' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '5' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '6' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '7' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '8' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '9' | KEYC_ESCAPE, 0, MODEKEYCOPY_STARTNUMBERPREFIX },
{ '<' | KEYC_ESCAPE, 0, MODEKEYCOPY_HISTORYTOP }, { '<' | KEYC_ESCAPE, 0, MODEKEYCOPY_HISTORYTOP },
{ '>' | KEYC_ESCAPE, 0, MODEKEYCOPY_HISTORYBOTTOM }, { '>' | KEYC_ESCAPE, 0, MODEKEYCOPY_HISTORYBOTTOM },
{ 'R' | KEYC_ESCAPE, 0, MODEKEYCOPY_TOPLINE }, { 'R' | KEYC_ESCAPE, 0, MODEKEYCOPY_TOPLINE },

15
tmux.1
View File

@ -1,4 +1,4 @@
.\" $Id: tmux.1,v 1.236 2010-02-26 13:31:39 tcunha Exp $ .\" $Id: tmux.1,v 1.237 2010-03-08 15:02:07 tcunha Exp $
.\" .\"
.\" Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> .\" Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
.\" .\"
@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" .\"
.Dd $Mdocdate: February 22 2010 $ .Dd $Mdocdate: March 2 2010 $
.Dt TMUX 1 .Dt TMUX 1
.Os .Os
.Sh NAME .Sh NAME
@ -661,7 +661,16 @@ next word and previous word to the start of the previous word.
The three next and previous space keys work similarly but use a space alone as The three next and previous space keys work similarly but use a space alone as
the word separator. the word separator.
.Pp .Pp
These key bindings are defined in a set of named tables: Commands in copy mode may be prefaced by an optional repeat count.
With vi key bindings, a prefix is entered using the number keys; with
emacs, the Alt (meta) key and a number begins prefix entry.
For example, to move the cursor forward by ten words, use
.Ql M-1 0 M-f
in emacs mode, and
.Ql 10w
in vi.
.Pp
Mode key bindings are defined in a set of named tables:
.Em vi-edit .Em vi-edit
and and
.Em emacs-edit .Em emacs-edit

3
tmux.h
View File

@ -1,4 +1,4 @@
/* $Id: tmux.h,v 1.546 2010-02-26 13:26:44 tcunha Exp $ */ /* $Id: tmux.h,v 1.547 2010-03-08 15:02:07 tcunha Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@ -476,6 +476,7 @@ enum mode_key_cmd {
MODEKEYCOPY_SEARCHDOWN, MODEKEYCOPY_SEARCHDOWN,
MODEKEYCOPY_SEARCHREVERSE, MODEKEYCOPY_SEARCHREVERSE,
MODEKEYCOPY_SEARCHUP, MODEKEYCOPY_SEARCHUP,
MODEKEYCOPY_STARTNUMBERPREFIX,
MODEKEYCOPY_STARTOFLINE, MODEKEYCOPY_STARTOFLINE,
MODEKEYCOPY_STARTSELECTION, MODEKEYCOPY_STARTSELECTION,
MODEKEYCOPY_TOPLINE, MODEKEYCOPY_TOPLINE,

View File

@ -1,4 +1,4 @@
/* $Id: window-copy.c,v 1.108 2010-03-08 14:56:17 tcunha Exp $ */ /* $Id: window-copy.c,v 1.109 2010-03-08 15:02:07 tcunha Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@ -28,6 +28,7 @@ void window_copy_free(struct window_pane *);
void window_copy_resize(struct window_pane *, u_int, u_int); void window_copy_resize(struct window_pane *, u_int, u_int);
void window_copy_key(struct window_pane *, struct client *, int); void window_copy_key(struct window_pane *, struct client *, int);
int window_copy_key_input(struct window_pane *, int); int window_copy_key_input(struct window_pane *, int);
int window_copy_key_numeric_prefix(struct window_pane *, int);
void window_copy_mouse( void window_copy_mouse(
struct window_pane *, struct client *, struct mouse_event *); struct window_pane *, struct client *, struct mouse_event *);
@ -82,6 +83,7 @@ const struct window_mode window_copy_mode = {
enum window_copy_input_type { enum window_copy_input_type {
WINDOW_COPY_OFF, WINDOW_COPY_OFF,
WINDOW_COPY_NUMERICPREFIX,
WINDOW_COPY_SEARCHUP, WINDOW_COPY_SEARCHUP,
WINDOW_COPY_SEARCHDOWN, WINDOW_COPY_SEARCHDOWN,
WINDOW_COPY_GOTOLINE, WINDOW_COPY_GOTOLINE,
@ -109,6 +111,8 @@ struct window_copy_mode_data {
const char *inputprompt; const char *inputprompt;
char *inputstr; char *inputstr;
u_int numprefix;
enum window_copy_input_type searchtype; enum window_copy_input_type searchtype;
char *searchstr; char *searchstr;
}; };
@ -135,6 +139,7 @@ window_copy_init(struct window_pane *wp)
data->inputtype = WINDOW_COPY_OFF; data->inputtype = WINDOW_COPY_OFF;
data->inputprompt = NULL; data->inputprompt = NULL;
data->inputstr = xstrdup(""); data->inputstr = xstrdup("");
data->numprefix = 0;
data->searchtype = WINDOW_COPY_OFF; data->searchtype = WINDOW_COPY_OFF;
data->searchstr = NULL; data->searchstr = NULL;
@ -229,11 +234,20 @@ window_copy_key(struct window_pane *wp, struct client *c, int key)
const char *word_separators; const char *word_separators;
struct window_copy_mode_data *data = wp->modedata; struct window_copy_mode_data *data = wp->modedata;
struct screen *s = &data->screen; struct screen *s = &data->screen;
u_int n; u_int n, np;
int keys; int keys;
enum mode_key_cmd cmd; enum mode_key_cmd cmd;
if (data->inputtype != WINDOW_COPY_OFF) { np = data->numprefix;
if (np == 0)
np = 1;
if (data->inputtype == WINDOW_COPY_NUMERICPREFIX) {
if (window_copy_key_numeric_prefix(wp, key) == 0)
return;
data->inputtype = WINDOW_COPY_OFF;
window_copy_redraw_lines(wp, screen_size_y(s) - 1, 1);
} else if (data->inputtype != WINDOW_COPY_OFF) {
if (window_copy_key_input(wp, key) != 0) if (window_copy_key_input(wp, key) != 0)
goto input_off; goto input_off;
return; return;
@ -242,55 +256,69 @@ window_copy_key(struct window_pane *wp, struct client *c, int key)
cmd = mode_key_lookup(&data->mdata, key); cmd = mode_key_lookup(&data->mdata, key);
switch (cmd) { switch (cmd) {
case MODEKEYCOPY_CANCEL: case MODEKEYCOPY_CANCEL:
for (; np != 0; np--)
window_pane_reset_mode(wp); window_pane_reset_mode(wp);
break; break;
case MODEKEYCOPY_LEFT: case MODEKEYCOPY_LEFT:
for (; np != 0; np--)
window_copy_cursor_left(wp); window_copy_cursor_left(wp);
return; break;
case MODEKEYCOPY_RIGHT: case MODEKEYCOPY_RIGHT:
for (; np != 0; np--)
window_copy_cursor_right(wp); window_copy_cursor_right(wp);
return; break;
case MODEKEYCOPY_UP: case MODEKEYCOPY_UP:
for (; np != 0; np--)
window_copy_cursor_up(wp, 0); window_copy_cursor_up(wp, 0);
return; break;
case MODEKEYCOPY_DOWN: case MODEKEYCOPY_DOWN:
for (; np != 0; np--)
window_copy_cursor_down(wp, 0); window_copy_cursor_down(wp, 0);
return; break;
case MODEKEYCOPY_SCROLLUP: case MODEKEYCOPY_SCROLLUP:
for (; np != 0; np--)
window_copy_cursor_up(wp, 1); window_copy_cursor_up(wp, 1);
break; break;
case MODEKEYCOPY_SCROLLDOWN: case MODEKEYCOPY_SCROLLDOWN:
for (; np != 0; np--)
window_copy_cursor_down(wp, 1); window_copy_cursor_down(wp, 1);
break; break;
case MODEKEYCOPY_PREVIOUSPAGE: case MODEKEYCOPY_PREVIOUSPAGE:
for (; np != 0; np--)
window_copy_pageup(wp); window_copy_pageup(wp);
break; break;
case MODEKEYCOPY_NEXTPAGE: case MODEKEYCOPY_NEXTPAGE:
n = 1; n = 1;
if (screen_size_y(s) > 2) if (screen_size_y(s) > 2)
n = screen_size_y(s) - 2; n = screen_size_y(s) - 2;
for (; np != 0; np--) {
if (data->oy < n) if (data->oy < n)
data->oy = 0; data->oy = 0;
else else
data->oy -= n; data->oy -= n;
}
window_copy_update_selection(wp); window_copy_update_selection(wp);
window_copy_redraw_screen(wp); window_copy_redraw_screen(wp);
break; break;
case MODEKEYCOPY_HALFPAGEUP: case MODEKEYCOPY_HALFPAGEUP:
n = screen_size_y(s) / 2; n = screen_size_y(s) / 2;
for (; np != 0; np--) {
if (data->oy + n > screen_hsize(&wp->base)) if (data->oy + n > screen_hsize(&wp->base))
data->oy = screen_hsize(&wp->base); data->oy = screen_hsize(&wp->base);
else else
data->oy += n; data->oy += n;
}
window_copy_update_selection(wp); window_copy_update_selection(wp);
window_copy_redraw_screen(wp); window_copy_redraw_screen(wp);
break; break;
case MODEKEYCOPY_HALFPAGEDOWN: case MODEKEYCOPY_HALFPAGEDOWN:
n = screen_size_y(s) / 2; n = screen_size_y(s) / 2;
for (; np != 0; np--) {
if (data->oy < n) if (data->oy < n)
data->oy = 0; data->oy = 0;
else else
data->oy -= n; data->oy -= n;
}
window_copy_update_selection(wp); window_copy_update_selection(wp);
window_copy_redraw_screen(wp); window_copy_redraw_screen(wp);
break; break;
@ -350,27 +378,33 @@ window_copy_key(struct window_pane *wp, struct client *c, int key)
window_copy_cursor_end_of_line(wp); window_copy_cursor_end_of_line(wp);
break; break;
case MODEKEYCOPY_NEXTSPACE: case MODEKEYCOPY_NEXTSPACE:
for (; np != 0; np--)
window_copy_cursor_next_word(wp, " "); window_copy_cursor_next_word(wp, " ");
break; break;
case MODEKEYCOPY_NEXTSPACEEND: case MODEKEYCOPY_NEXTSPACEEND:
for (; np != 0; np--)
window_copy_cursor_next_word_end(wp, " "); window_copy_cursor_next_word_end(wp, " ");
break; break;
case MODEKEYCOPY_NEXTWORD: case MODEKEYCOPY_NEXTWORD:
word_separators = word_separators =
options_get_string(&wp->window->options, "word-separators"); options_get_string(&wp->window->options, "word-separators");
for (; np != 0; np--)
window_copy_cursor_next_word(wp, word_separators); window_copy_cursor_next_word(wp, word_separators);
break; break;
case MODEKEYCOPY_NEXTWORDEND: case MODEKEYCOPY_NEXTWORDEND:
word_separators = word_separators =
options_get_string(&wp->window->options, "word-separators"); options_get_string(&wp->window->options, "word-separators");
for (; np != 0; np--)
window_copy_cursor_next_word_end(wp, word_separators); window_copy_cursor_next_word_end(wp, word_separators);
break; break;
case MODEKEYCOPY_PREVIOUSSPACE: case MODEKEYCOPY_PREVIOUSSPACE:
for (; np != 0; np--)
window_copy_cursor_previous_word(wp, " "); window_copy_cursor_previous_word(wp, " ");
break; break;
case MODEKEYCOPY_PREVIOUSWORD: case MODEKEYCOPY_PREVIOUSWORD:
word_separators = word_separators =
options_get_string(&wp->window->options, "word-separators"); options_get_string(&wp->window->options, "word-separators");
for (; np != 0; np--)
window_copy_cursor_previous_word(wp, word_separators); window_copy_cursor_previous_word(wp, word_separators);
break; break;
case MODEKEYCOPY_SEARCHUP: case MODEKEYCOPY_SEARCHUP:
@ -386,18 +420,33 @@ window_copy_key(struct window_pane *wp, struct client *c, int key)
switch (data->searchtype) { switch (data->searchtype) {
case WINDOW_COPY_OFF: case WINDOW_COPY_OFF:
case WINDOW_COPY_GOTOLINE: case WINDOW_COPY_GOTOLINE:
case WINDOW_COPY_NUMERICPREFIX:
break; break;
case WINDOW_COPY_SEARCHUP: case WINDOW_COPY_SEARCHUP:
if (cmd == MODEKEYCOPY_SEARCHAGAIN) if (cmd == MODEKEYCOPY_SEARCHAGAIN) {
window_copy_search_up(wp, data->searchstr); for (; np != 0; np--) {
else window_copy_search_up(
window_copy_search_down(wp, data->searchstr); wp, data->searchstr);
}
} else {
for (; np != 0; np--) {
window_copy_search_down(
wp, data->searchstr);
}
}
break; break;
case WINDOW_COPY_SEARCHDOWN: case WINDOW_COPY_SEARCHDOWN:
if (cmd == MODEKEYCOPY_SEARCHAGAIN) if (cmd == MODEKEYCOPY_SEARCHAGAIN) {
window_copy_search_down(wp, data->searchstr); for (; np != 0; np--) {
else window_copy_search_down(
window_copy_search_up(wp, data->searchstr); wp, data->searchstr);
}
} else {
for (; np != 0; np--) {
window_copy_search_up(
wp, data->searchstr);
}
}
break; break;
} }
break; break;
@ -406,13 +455,23 @@ window_copy_key(struct window_pane *wp, struct client *c, int key)
data->inputprompt = "Goto Line"; data->inputprompt = "Goto Line";
*data->inputstr = '\0'; *data->inputstr = '\0';
goto input_on; goto input_on;
case MODEKEYCOPY_STARTNUMBERPREFIX:
key &= 0xff;
if (key >= '0' && key <= '9') {
data->inputtype = WINDOW_COPY_NUMERICPREFIX;
data->numprefix = 0;
window_copy_key_numeric_prefix(wp, key);
return;
}
break;
case MODEKEYCOPY_RECTANGLETOGGLE: case MODEKEYCOPY_RECTANGLETOGGLE:
window_copy_rectangle_toggle(wp); window_copy_rectangle_toggle(wp);
return; break;
default: default:
break; break;
} }
data->numprefix = 0;
return; return;
input_on: input_on:
@ -444,9 +503,11 @@ window_copy_key_input(struct window_pane *wp, int key)
struct window_copy_mode_data *data = wp->modedata; struct window_copy_mode_data *data = wp->modedata;
struct screen *s = &data->screen; struct screen *s = &data->screen;
size_t inputlen; size_t inputlen;
u_int np;
switch (mode_key_lookup(&data->mdata, key)) { switch (mode_key_lookup(&data->mdata, key)) {
case MODEKEYEDIT_CANCEL: case MODEKEYEDIT_CANCEL:
data->numprefix = 0;
return (-1); return (-1);
case MODEKEYEDIT_BACKSPACE: case MODEKEYEDIT_BACKSPACE:
inputlen = strlen(data->inputstr); inputlen = strlen(data->inputstr);
@ -457,15 +518,22 @@ window_copy_key_input(struct window_pane *wp, int key)
*data->inputstr = '\0'; *data->inputstr = '\0';
break; break;
case MODEKEYEDIT_ENTER: case MODEKEYEDIT_ENTER:
np = data->numprefix;
if (np == 0)
np = 1;
switch (data->inputtype) { switch (data->inputtype) {
case WINDOW_COPY_OFF: case WINDOW_COPY_OFF:
case WINDOW_COPY_NUMERICPREFIX:
break; break;
case WINDOW_COPY_SEARCHUP: case WINDOW_COPY_SEARCHUP:
for (; np != 0; np--)
window_copy_search_up(wp, data->inputstr); window_copy_search_up(wp, data->inputstr);
data->searchtype = data->inputtype; data->searchtype = data->inputtype;
data->searchstr = xstrdup(data->inputstr); data->searchstr = xstrdup(data->inputstr);
break; break;
case WINDOW_COPY_SEARCHDOWN: case WINDOW_COPY_SEARCHDOWN:
for (; np != 0; np--)
window_copy_search_down(wp, data->inputstr); window_copy_search_down(wp, data->inputstr);
data->searchtype = data->inputtype; data->searchtype = data->inputtype;
data->searchstr = xstrdup(data->inputstr); data->searchstr = xstrdup(data->inputstr);
@ -475,6 +543,7 @@ window_copy_key_input(struct window_pane *wp, int key)
*data->inputstr = '\0'; *data->inputstr = '\0';
break; break;
} }
data->numprefix = 0;
return (1); return (1);
case MODEKEY_OTHER: case MODEKEY_OTHER:
if (key < 32 || key > 126) if (key < 32 || key > 126)
@ -493,6 +562,24 @@ window_copy_key_input(struct window_pane *wp, int key)
return (0); return (0);
} }
int
window_copy_key_numeric_prefix(struct window_pane *wp, int key)
{
struct window_copy_mode_data *data = wp->modedata;
struct screen *s = &data->screen;
key &= 0xff;
if (key < '0' || key > '9')
return 1;
if (data->numprefix >= 100) /* no more than three digits */
return 0;
data->numprefix = data->numprefix * 10 + key - '0';
window_copy_redraw_lines(wp, screen_size_y(s) - 1, 1);
return 0;
}
/* ARGSUSED */ /* ARGSUSED */
void void
window_copy_mouse( window_copy_mouse(
@ -762,8 +849,13 @@ window_copy_write_line(
screen_write_cursormove(ctx, screen_size_x(s) - size, 0); screen_write_cursormove(ctx, screen_size_x(s) - size, 0);
screen_write_puts(ctx, &gc, "%s", hdr); screen_write_puts(ctx, &gc, "%s", hdr);
} else if (py == last && data->inputtype != WINDOW_COPY_OFF) { } else if (py == last && data->inputtype != WINDOW_COPY_OFF) {
if (data->inputtype == WINDOW_COPY_NUMERICPREFIX) {
xoff = size = xsnprintf(hdr, sizeof hdr,
"Repeat: %u", data->numprefix);
} else {
xoff = size = xsnprintf(hdr, sizeof hdr, xoff = size = xsnprintf(hdr, sizeof hdr,
"%s: %s", data->inputprompt, data->inputstr); "%s: %s", data->inputprompt, data->inputstr);
}
screen_write_cursormove(ctx, 0, last); screen_write_cursormove(ctx, 0, last);
screen_write_puts(ctx, &gc, "%s", hdr); screen_write_puts(ctx, &gc, "%s", hdr);
} else } else