1 |
|
|
/* $OpenBSD: format.c,v 1.146 2017/08/09 11:43:45 nicm Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 |
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 |
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/wait.h> |
21 |
|
|
|
22 |
|
|
#include <errno.h> |
23 |
|
|
#include <fnmatch.h> |
24 |
|
|
#include <libgen.h> |
25 |
|
|
#include <stdarg.h> |
26 |
|
|
#include <stdlib.h> |
27 |
|
|
#include <string.h> |
28 |
|
|
#include <time.h> |
29 |
|
|
#include <unistd.h> |
30 |
|
|
|
31 |
|
|
#include "tmux.h" |
32 |
|
|
|
33 |
|
|
/* |
34 |
|
|
* Build a list of key-value pairs and use them to expand #{key} entries in a |
35 |
|
|
* string. |
36 |
|
|
*/ |
37 |
|
|
|
38 |
|
|
struct format_entry; |
39 |
|
|
typedef void (*format_cb)(struct format_tree *, struct format_entry *); |
40 |
|
|
|
41 |
|
|
static char *format_job_get(struct format_tree *, const char *); |
42 |
|
|
static void format_job_timer(int, short, void *); |
43 |
|
|
|
44 |
|
|
static char *format_find(struct format_tree *, const char *, int); |
45 |
|
|
static void format_add_cb(struct format_tree *, const char *, format_cb); |
46 |
|
|
static void format_add_tv(struct format_tree *, const char *, |
47 |
|
|
struct timeval *); |
48 |
|
|
static int format_replace(struct format_tree *, const char *, size_t, |
49 |
|
|
char **, size_t *, size_t *); |
50 |
|
|
|
51 |
|
|
static void format_defaults_session(struct format_tree *, |
52 |
|
|
struct session *); |
53 |
|
|
static void format_defaults_client(struct format_tree *, struct client *); |
54 |
|
|
static void format_defaults_winlink(struct format_tree *, |
55 |
|
|
struct winlink *); |
56 |
|
|
|
57 |
|
|
/* Entry in format job tree. */ |
58 |
|
|
struct format_job { |
59 |
|
|
struct client *client; |
60 |
|
|
u_int tag; |
61 |
|
|
const char *cmd; |
62 |
|
|
const char *expanded; |
63 |
|
|
|
64 |
|
|
time_t last; |
65 |
|
|
char *out; |
66 |
|
|
int updated; |
67 |
|
|
|
68 |
|
|
struct job *job; |
69 |
|
|
int status; |
70 |
|
|
|
71 |
|
|
RB_ENTRY(format_job) entry; |
72 |
|
|
}; |
73 |
|
|
|
74 |
|
|
/* Format job tree. */ |
75 |
|
|
static struct event format_job_event; |
76 |
|
|
static int format_job_cmp(struct format_job *, struct format_job *); |
77 |
|
|
static RB_HEAD(format_job_tree, format_job) format_jobs = RB_INITIALIZER(); |
78 |
|
|
RB_GENERATE_STATIC(format_job_tree, format_job, entry, format_job_cmp); |
79 |
|
|
|
80 |
|
|
/* Format job tree comparison function. */ |
81 |
|
|
static int |
82 |
|
|
format_job_cmp(struct format_job *fj1, struct format_job *fj2) |
83 |
|
|
{ |
84 |
|
|
if (fj1->tag < fj2->tag) |
85 |
|
|
return (-1); |
86 |
|
|
if (fj1->tag > fj2->tag) |
87 |
|
|
return (1); |
88 |
|
|
return (strcmp(fj1->cmd, fj2->cmd)); |
89 |
|
|
} |
90 |
|
|
|
91 |
|
|
/* Format modifiers. */ |
92 |
|
|
#define FORMAT_TIMESTRING 0x1 |
93 |
|
|
#define FORMAT_BASENAME 0x2 |
94 |
|
|
#define FORMAT_DIRNAME 0x4 |
95 |
|
|
#define FORMAT_SUBSTITUTE 0x8 |
96 |
|
|
|
97 |
|
|
/* Entry in format tree. */ |
98 |
|
|
struct format_entry { |
99 |
|
|
char *key; |
100 |
|
|
char *value; |
101 |
|
|
time_t t; |
102 |
|
|
format_cb cb; |
103 |
|
|
RB_ENTRY(format_entry) entry; |
104 |
|
|
}; |
105 |
|
|
|
106 |
|
|
/* Format entry tree. */ |
107 |
|
|
struct format_tree { |
108 |
|
|
struct window *w; |
109 |
|
|
struct winlink *wl; |
110 |
|
|
struct session *s; |
111 |
|
|
struct window_pane *wp; |
112 |
|
|
|
113 |
|
|
struct client *client; |
114 |
|
|
u_int tag; |
115 |
|
|
int flags; |
116 |
|
|
|
117 |
|
|
RB_HEAD(format_entry_tree, format_entry) tree; |
118 |
|
|
}; |
119 |
|
|
static int format_entry_cmp(struct format_entry *, struct format_entry *); |
120 |
|
|
RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp); |
121 |
|
|
|
122 |
|
|
/* Format entry tree comparison function. */ |
123 |
|
|
static int |
124 |
|
|
format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2) |
125 |
|
|
{ |
126 |
|
|
return (strcmp(fe1->key, fe2->key)); |
127 |
|
|
} |
128 |
|
|
|
129 |
|
|
/* Single-character uppercase aliases. */ |
130 |
|
|
static const char *format_upper[] = { |
131 |
|
|
NULL, /* A */ |
132 |
|
|
NULL, /* B */ |
133 |
|
|
NULL, /* C */ |
134 |
|
|
"pane_id", /* D */ |
135 |
|
|
NULL, /* E */ |
136 |
|
|
"window_flags", /* F */ |
137 |
|
|
NULL, /* G */ |
138 |
|
|
"host", /* H */ |
139 |
|
|
"window_index", /* I */ |
140 |
|
|
NULL, /* J */ |
141 |
|
|
NULL, /* K */ |
142 |
|
|
NULL, /* L */ |
143 |
|
|
NULL, /* M */ |
144 |
|
|
NULL, /* N */ |
145 |
|
|
NULL, /* O */ |
146 |
|
|
"pane_index", /* P */ |
147 |
|
|
NULL, /* Q */ |
148 |
|
|
NULL, /* R */ |
149 |
|
|
"session_name", /* S */ |
150 |
|
|
"pane_title", /* T */ |
151 |
|
|
NULL, /* U */ |
152 |
|
|
NULL, /* V */ |
153 |
|
|
"window_name", /* W */ |
154 |
|
|
NULL, /* X */ |
155 |
|
|
NULL, /* Y */ |
156 |
|
|
NULL /* Z */ |
157 |
|
|
}; |
158 |
|
|
|
159 |
|
|
/* Single-character lowercase aliases. */ |
160 |
|
|
static const char *format_lower[] = { |
161 |
|
|
NULL, /* a */ |
162 |
|
|
NULL, /* b */ |
163 |
|
|
NULL, /* c */ |
164 |
|
|
NULL, /* d */ |
165 |
|
|
NULL, /* e */ |
166 |
|
|
NULL, /* f */ |
167 |
|
|
NULL, /* g */ |
168 |
|
|
"host_short", /* h */ |
169 |
|
|
NULL, /* i */ |
170 |
|
|
NULL, /* j */ |
171 |
|
|
NULL, /* k */ |
172 |
|
|
NULL, /* l */ |
173 |
|
|
NULL, /* m */ |
174 |
|
|
NULL, /* n */ |
175 |
|
|
NULL, /* o */ |
176 |
|
|
NULL, /* p */ |
177 |
|
|
NULL, /* q */ |
178 |
|
|
NULL, /* r */ |
179 |
|
|
NULL, /* s */ |
180 |
|
|
NULL, /* t */ |
181 |
|
|
NULL, /* u */ |
182 |
|
|
NULL, /* v */ |
183 |
|
|
NULL, /* w */ |
184 |
|
|
NULL, /* x */ |
185 |
|
|
NULL, /* y */ |
186 |
|
|
NULL /* z */ |
187 |
|
|
}; |
188 |
|
|
|
189 |
|
|
/* Format job update callback. */ |
190 |
|
|
static void |
191 |
|
|
format_job_update(struct job *job) |
192 |
|
|
{ |
193 |
|
|
struct format_job *fj = job->data; |
194 |
|
|
char *line; |
195 |
|
|
time_t t; |
196 |
|
|
|
197 |
|
|
if ((line = evbuffer_readline(job->event->input)) == NULL) |
198 |
|
|
return; |
199 |
|
|
fj->updated = 1; |
200 |
|
|
|
201 |
|
|
free(fj->out); |
202 |
|
|
fj->out = line; |
203 |
|
|
|
204 |
|
|
log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, fj->out); |
205 |
|
|
|
206 |
|
|
t = time(NULL); |
207 |
|
|
if (fj->status && fj->last != t) { |
208 |
|
|
if (fj->client != NULL) |
209 |
|
|
server_status_client(fj->client); |
210 |
|
|
fj->last = t; |
211 |
|
|
} |
212 |
|
|
} |
213 |
|
|
|
214 |
|
|
/* Format job complete callback. */ |
215 |
|
|
static void |
216 |
|
|
format_job_complete(struct job *job) |
217 |
|
|
{ |
218 |
|
|
struct format_job *fj = job->data; |
219 |
|
|
char *line, *buf; |
220 |
|
|
size_t len; |
221 |
|
|
|
222 |
|
|
fj->job = NULL; |
223 |
|
|
|
224 |
|
|
buf = NULL; |
225 |
|
|
if ((line = evbuffer_readline(job->event->input)) == NULL) { |
226 |
|
|
len = EVBUFFER_LENGTH(job->event->input); |
227 |
|
|
buf = xmalloc(len + 1); |
228 |
|
|
if (len != 0) |
229 |
|
|
memcpy(buf, EVBUFFER_DATA(job->event->input), len); |
230 |
|
|
buf[len] = '\0'; |
231 |
|
|
} else |
232 |
|
|
buf = line; |
233 |
|
|
|
234 |
|
|
log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, buf); |
235 |
|
|
|
236 |
|
|
if (*buf != '\0' || !fj->updated) { |
237 |
|
|
free(fj->out); |
238 |
|
|
fj->out = buf; |
239 |
|
|
} else |
240 |
|
|
free(buf); |
241 |
|
|
|
242 |
|
|
if (fj->status) { |
243 |
|
|
if (fj->client != NULL) |
244 |
|
|
server_status_client(fj->client); |
245 |
|
|
fj->status = 0; |
246 |
|
|
} |
247 |
|
|
} |
248 |
|
|
|
249 |
|
|
/* Find a job. */ |
250 |
|
|
static char * |
251 |
|
|
format_job_get(struct format_tree *ft, const char *cmd) |
252 |
|
|
{ |
253 |
|
|
struct format_job_tree *jobs; |
254 |
|
|
struct format_job fj0, *fj; |
255 |
|
|
time_t t; |
256 |
|
|
char *expanded; |
257 |
|
|
int force; |
258 |
|
|
|
259 |
|
|
if (ft->client == NULL) |
260 |
|
|
jobs = &format_jobs; |
261 |
|
|
else if (ft->client->jobs != NULL) |
262 |
|
|
jobs = ft->client->jobs; |
263 |
|
|
else { |
264 |
|
|
jobs = ft->client->jobs = xmalloc(sizeof *ft->client->jobs); |
265 |
|
|
RB_INIT(jobs); |
266 |
|
|
} |
267 |
|
|
|
268 |
|
|
fj0.tag = ft->tag; |
269 |
|
|
fj0.cmd = cmd; |
270 |
|
|
if ((fj = RB_FIND(format_job_tree, jobs, &fj0)) == NULL) { |
271 |
|
|
fj = xcalloc(1, sizeof *fj); |
272 |
|
|
fj->client = ft->client; |
273 |
|
|
fj->tag = ft->tag; |
274 |
|
|
fj->cmd = xstrdup(cmd); |
275 |
|
|
fj->expanded = NULL; |
276 |
|
|
|
277 |
|
|
xasprintf(&fj->out, "<'%s' not ready>", fj->cmd); |
278 |
|
|
|
279 |
|
|
RB_INSERT(format_job_tree, jobs, fj); |
280 |
|
|
} |
281 |
|
|
|
282 |
|
|
expanded = format_expand(ft, cmd); |
283 |
|
|
if (fj->expanded == NULL || strcmp(expanded, fj->expanded) != 0) { |
284 |
|
|
free((void *)fj->expanded); |
285 |
|
|
fj->expanded = xstrdup(expanded); |
286 |
|
|
force = 1; |
287 |
|
|
} else |
288 |
|
|
force = (ft->flags & FORMAT_FORCE); |
289 |
|
|
|
290 |
|
|
t = time(NULL); |
291 |
|
|
if (fj->job == NULL && (force || fj->last != t)) { |
292 |
|
|
fj->job = job_run(expanded, NULL, NULL, format_job_update, |
293 |
|
|
format_job_complete, NULL, fj); |
294 |
|
|
if (fj->job == NULL) { |
295 |
|
|
free(fj->out); |
296 |
|
|
xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); |
297 |
|
|
} |
298 |
|
|
fj->last = t; |
299 |
|
|
fj->updated = 0; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
if (ft->flags & FORMAT_STATUS) |
303 |
|
|
fj->status = 1; |
304 |
|
|
|
305 |
|
|
free(expanded); |
306 |
|
|
return (format_expand(ft, fj->out)); |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
/* Remove old jobs. */ |
310 |
|
|
static void |
311 |
|
|
format_job_tidy(struct format_job_tree *jobs, int force) |
312 |
|
|
{ |
313 |
|
|
struct format_job *fj, *fj1; |
314 |
|
|
time_t now; |
315 |
|
|
|
316 |
|
|
now = time(NULL); |
317 |
|
|
RB_FOREACH_SAFE(fj, format_job_tree, jobs, fj1) { |
318 |
|
|
if (!force && (fj->last > now || now - fj->last < 3600)) |
319 |
|
|
continue; |
320 |
|
|
RB_REMOVE(format_job_tree, jobs, fj); |
321 |
|
|
|
322 |
|
|
log_debug("%s: %s", __func__, fj->cmd); |
323 |
|
|
|
324 |
|
|
if (fj->job != NULL) |
325 |
|
|
job_free(fj->job); |
326 |
|
|
|
327 |
|
|
free((void *)fj->expanded); |
328 |
|
|
free((void *)fj->cmd); |
329 |
|
|
free(fj->out); |
330 |
|
|
|
331 |
|
|
free(fj); |
332 |
|
|
} |
333 |
|
|
} |
334 |
|
|
|
335 |
|
|
/* Remove old jobs for client. */ |
336 |
|
|
void |
337 |
|
|
format_lost_client(struct client *c) |
338 |
|
|
{ |
339 |
|
|
if (c->jobs != NULL) |
340 |
|
|
format_job_tidy(c->jobs, 1); |
341 |
|
|
free(c->jobs); |
342 |
|
|
} |
343 |
|
|
|
344 |
|
|
/* Remove old jobs periodically. */ |
345 |
|
|
static void |
346 |
|
|
format_job_timer(__unused int fd, __unused short events, __unused void *arg) |
347 |
|
|
{ |
348 |
|
|
struct client *c; |
349 |
|
|
struct timeval tv = { .tv_sec = 60 }; |
350 |
|
|
|
351 |
|
|
format_job_tidy(&format_jobs, 0); |
352 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
353 |
|
|
if (c->jobs != NULL) |
354 |
|
|
format_job_tidy(c->jobs, 0); |
355 |
|
|
} |
356 |
|
|
|
357 |
|
|
evtimer_del(&format_job_event); |
358 |
|
|
evtimer_add(&format_job_event, &tv); |
359 |
|
|
} |
360 |
|
|
|
361 |
|
|
/* Callback for host. */ |
362 |
|
|
static void |
363 |
|
|
format_cb_host(__unused struct format_tree *ft, struct format_entry *fe) |
364 |
|
|
{ |
365 |
|
|
char host[HOST_NAME_MAX + 1]; |
366 |
|
|
|
367 |
|
|
if (gethostname(host, sizeof host) != 0) |
368 |
|
|
fe->value = xstrdup(""); |
369 |
|
|
else |
370 |
|
|
fe->value = xstrdup(host); |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
/* Callback for host_short. */ |
374 |
|
|
static void |
375 |
|
|
format_cb_host_short(__unused struct format_tree *ft, struct format_entry *fe) |
376 |
|
|
{ |
377 |
|
|
char host[HOST_NAME_MAX + 1], *cp; |
378 |
|
|
|
379 |
|
|
if (gethostname(host, sizeof host) != 0) |
380 |
|
|
fe->value = xstrdup(""); |
381 |
|
|
else { |
382 |
|
|
if ((cp = strchr(host, '.')) != NULL) |
383 |
|
|
*cp = '\0'; |
384 |
|
|
fe->value = xstrdup(host); |
385 |
|
|
} |
386 |
|
|
} |
387 |
|
|
|
388 |
|
|
/* Callback for pid. */ |
389 |
|
|
static void |
390 |
|
|
format_cb_pid(__unused struct format_tree *ft, struct format_entry *fe) |
391 |
|
|
{ |
392 |
|
|
xasprintf(&fe->value, "%ld", (long)getpid()); |
393 |
|
|
} |
394 |
|
|
|
395 |
|
|
/* Callback for session_alerts. */ |
396 |
|
|
static void |
397 |
|
|
format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe) |
398 |
|
|
{ |
399 |
|
|
struct session *s = ft->s; |
400 |
|
|
struct winlink *wl; |
401 |
|
|
char alerts[1024], tmp[16]; |
402 |
|
|
|
403 |
|
|
if (s == NULL) |
404 |
|
|
return; |
405 |
|
|
|
406 |
|
|
*alerts = '\0'; |
407 |
|
|
RB_FOREACH(wl, winlinks, &s->windows) { |
408 |
|
|
if ((wl->flags & WINLINK_ALERTFLAGS) == 0) |
409 |
|
|
continue; |
410 |
|
|
xsnprintf(tmp, sizeof tmp, "%u", wl->idx); |
411 |
|
|
|
412 |
|
|
if (*alerts != '\0') |
413 |
|
|
strlcat(alerts, ",", sizeof alerts); |
414 |
|
|
strlcat(alerts, tmp, sizeof alerts); |
415 |
|
|
if (wl->flags & WINLINK_ACTIVITY) |
416 |
|
|
strlcat(alerts, "#", sizeof alerts); |
417 |
|
|
if (wl->flags & WINLINK_BELL) |
418 |
|
|
strlcat(alerts, "!", sizeof alerts); |
419 |
|
|
if (wl->flags & WINLINK_SILENCE) |
420 |
|
|
strlcat(alerts, "~", sizeof alerts); |
421 |
|
|
} |
422 |
|
|
fe->value = xstrdup(alerts); |
423 |
|
|
} |
424 |
|
|
|
425 |
|
|
/* Callback for session_stack. */ |
426 |
|
|
static void |
427 |
|
|
format_cb_session_stack(struct format_tree *ft, struct format_entry *fe) |
428 |
|
|
{ |
429 |
|
|
struct session *s = ft->s; |
430 |
|
|
struct winlink *wl; |
431 |
|
|
char result[1024], tmp[16]; |
432 |
|
|
|
433 |
|
|
if (s == NULL) |
434 |
|
|
return; |
435 |
|
|
|
436 |
|
|
xsnprintf(result, sizeof result, "%u", s->curw->idx); |
437 |
|
|
TAILQ_FOREACH(wl, &s->lastw, sentry) { |
438 |
|
|
xsnprintf(tmp, sizeof tmp, "%u", wl->idx); |
439 |
|
|
|
440 |
|
|
if (*result != '\0') |
441 |
|
|
strlcat(result, ",", sizeof result); |
442 |
|
|
strlcat(result, tmp, sizeof result); |
443 |
|
|
} |
444 |
|
|
fe->value = xstrdup(result); |
445 |
|
|
} |
446 |
|
|
|
447 |
|
|
/* Callback for window_stack_index. */ |
448 |
|
|
static void |
449 |
|
|
format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe) |
450 |
|
|
{ |
451 |
|
|
struct session *s = ft->wl->session; |
452 |
|
|
struct winlink *wl; |
453 |
|
|
u_int idx; |
454 |
|
|
|
455 |
|
|
idx = 0; |
456 |
|
|
TAILQ_FOREACH(wl, &s->lastw, sentry) { |
457 |
|
|
idx++; |
458 |
|
|
if (wl == ft->wl) |
459 |
|
|
break; |
460 |
|
|
} |
461 |
|
|
if (wl != NULL) |
462 |
|
|
xasprintf(&fe->value, "%u", idx); |
463 |
|
|
else |
464 |
|
|
fe->value = xstrdup("0"); |
465 |
|
|
} |
466 |
|
|
|
467 |
|
|
/* Callback for window_layout. */ |
468 |
|
|
static void |
469 |
|
|
format_cb_window_layout(struct format_tree *ft, struct format_entry *fe) |
470 |
|
|
{ |
471 |
|
|
struct window *w = ft->w; |
472 |
|
|
|
473 |
|
|
if (w == NULL) |
474 |
|
|
return; |
475 |
|
|
|
476 |
|
|
if (w->saved_layout_root != NULL) |
477 |
|
|
fe->value = layout_dump(w->saved_layout_root); |
478 |
|
|
else |
479 |
|
|
fe->value = layout_dump(w->layout_root); |
480 |
|
|
} |
481 |
|
|
|
482 |
|
|
/* Callback for window_visible_layout. */ |
483 |
|
|
static void |
484 |
|
|
format_cb_window_visible_layout(struct format_tree *ft, struct format_entry *fe) |
485 |
|
|
{ |
486 |
|
|
struct window *w = ft->w; |
487 |
|
|
|
488 |
|
|
if (w == NULL) |
489 |
|
|
return; |
490 |
|
|
|
491 |
|
|
fe->value = layout_dump(w->layout_root); |
492 |
|
|
} |
493 |
|
|
|
494 |
|
|
/* Callback for pane_start_command. */ |
495 |
|
|
static void |
496 |
|
|
format_cb_start_command(struct format_tree *ft, struct format_entry *fe) |
497 |
|
|
{ |
498 |
|
|
struct window_pane *wp = ft->wp; |
499 |
|
|
|
500 |
|
|
if (wp == NULL) |
501 |
|
|
return; |
502 |
|
|
|
503 |
|
|
fe->value = cmd_stringify_argv(wp->argc, wp->argv); |
504 |
|
|
} |
505 |
|
|
|
506 |
|
|
/* Callback for pane_current_command. */ |
507 |
|
|
static void |
508 |
|
|
format_cb_current_command(struct format_tree *ft, struct format_entry *fe) |
509 |
|
|
{ |
510 |
|
|
struct window_pane *wp = ft->wp; |
511 |
|
|
char *cmd; |
512 |
|
|
|
513 |
|
|
if (wp == NULL) |
514 |
|
|
return; |
515 |
|
|
|
516 |
|
|
cmd = get_proc_name(wp->fd, wp->tty); |
517 |
|
|
if (cmd == NULL || *cmd == '\0') { |
518 |
|
|
free(cmd); |
519 |
|
|
cmd = cmd_stringify_argv(wp->argc, wp->argv); |
520 |
|
|
if (cmd == NULL || *cmd == '\0') { |
521 |
|
|
free(cmd); |
522 |
|
|
cmd = xstrdup(wp->shell); |
523 |
|
|
} |
524 |
|
|
} |
525 |
|
|
fe->value = parse_window_name(cmd); |
526 |
|
|
free(cmd); |
527 |
|
|
} |
528 |
|
|
|
529 |
|
|
/* Callback for history_bytes. */ |
530 |
|
|
static void |
531 |
|
|
format_cb_history_bytes(struct format_tree *ft, struct format_entry *fe) |
532 |
|
|
{ |
533 |
|
|
struct window_pane *wp = ft->wp; |
534 |
|
|
struct grid *gd; |
535 |
|
|
struct grid_line *gl; |
536 |
|
|
unsigned long long size; |
537 |
|
|
u_int i; |
538 |
|
|
|
539 |
|
|
if (wp == NULL) |
540 |
|
|
return; |
541 |
|
|
gd = wp->base.grid; |
542 |
|
|
|
543 |
|
|
size = 0; |
544 |
|
|
for (i = 0; i < gd->hsize; i++) { |
545 |
|
|
gl = &gd->linedata[i]; |
546 |
|
|
size += gl->cellsize * sizeof *gl->celldata; |
547 |
|
|
size += gl->extdsize * sizeof *gl->extddata; |
548 |
|
|
} |
549 |
|
|
size += gd->hsize * sizeof *gd->linedata; |
550 |
|
|
|
551 |
|
|
xasprintf(&fe->value, "%llu", size); |
552 |
|
|
} |
553 |
|
|
|
554 |
|
|
/* Callback for pane_tabs. */ |
555 |
|
|
static void |
556 |
|
|
format_cb_pane_tabs(struct format_tree *ft, struct format_entry *fe) |
557 |
|
|
{ |
558 |
|
|
struct window_pane *wp = ft->wp; |
559 |
|
|
struct evbuffer *buffer; |
560 |
|
|
u_int i; |
561 |
|
|
int size; |
562 |
|
|
|
563 |
|
|
if (wp == NULL) |
564 |
|
|
return; |
565 |
|
|
|
566 |
|
|
buffer = evbuffer_new(); |
567 |
|
|
for (i = 0; i < wp->base.grid->sx; i++) { |
568 |
|
|
if (!bit_test(wp->base.tabs, i)) |
569 |
|
|
continue; |
570 |
|
|
|
571 |
|
|
if (EVBUFFER_LENGTH(buffer) > 0) |
572 |
|
|
evbuffer_add(buffer, ",", 1); |
573 |
|
|
evbuffer_add_printf(buffer, "%u", i); |
574 |
|
|
} |
575 |
|
|
size = EVBUFFER_LENGTH(buffer); |
576 |
|
|
xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); |
577 |
|
|
evbuffer_free(buffer); |
578 |
|
|
} |
579 |
|
|
|
580 |
|
|
/* Merge a format tree. */ |
581 |
|
|
static void |
582 |
|
|
format_merge(struct format_tree *ft, struct format_tree *from) |
583 |
|
|
{ |
584 |
|
|
struct format_entry *fe; |
585 |
|
|
|
586 |
|
|
RB_FOREACH(fe, format_entry_tree, &from->tree) { |
587 |
|
|
if (fe->value != NULL) |
588 |
|
|
format_add(ft, fe->key, "%s", fe->value); |
589 |
|
|
} |
590 |
|
|
} |
591 |
|
|
|
592 |
|
|
/* Create a new tree. */ |
593 |
|
|
struct format_tree * |
594 |
|
|
format_create(struct client *c, struct cmdq_item *item, int tag, int flags) |
595 |
|
|
{ |
596 |
|
|
struct format_tree *ft; |
597 |
|
|
|
598 |
|
|
if (!event_initialized(&format_job_event)) { |
599 |
|
|
evtimer_set(&format_job_event, format_job_timer, NULL); |
600 |
|
|
format_job_timer(-1, 0, NULL); |
601 |
|
|
} |
602 |
|
|
|
603 |
|
|
ft = xcalloc(1, sizeof *ft); |
604 |
|
|
RB_INIT(&ft->tree); |
605 |
|
|
|
606 |
|
|
if (c != NULL) { |
607 |
|
|
ft->client = c; |
608 |
|
|
ft->client->references++; |
609 |
|
|
} |
610 |
|
|
|
611 |
|
|
ft->tag = tag; |
612 |
|
|
ft->flags = flags; |
613 |
|
|
|
614 |
|
|
format_add_cb(ft, "host", format_cb_host); |
615 |
|
|
format_add_cb(ft, "host_short", format_cb_host_short); |
616 |
|
|
format_add_cb(ft, "pid", format_cb_pid); |
617 |
|
|
format_add(ft, "socket_path", "%s", socket_path); |
618 |
|
|
format_add_tv(ft, "start_time", &start_time); |
619 |
|
|
|
620 |
|
|
if (item != NULL) { |
621 |
|
|
if (item->cmd != NULL) |
622 |
|
|
format_add(ft, "command", "%s", item->cmd->entry->name); |
623 |
|
|
if (item->shared != NULL && item->shared->formats != NULL) |
624 |
|
|
format_merge(ft, item->shared->formats); |
625 |
|
|
} |
626 |
|
|
|
627 |
|
|
return (ft); |
628 |
|
|
} |
629 |
|
|
|
630 |
|
|
/* Free a tree. */ |
631 |
|
|
void |
632 |
|
|
format_free(struct format_tree *ft) |
633 |
|
|
{ |
634 |
|
|
struct format_entry *fe, *fe1; |
635 |
|
|
|
636 |
|
|
RB_FOREACH_SAFE(fe, format_entry_tree, &ft->tree, fe1) { |
637 |
|
|
RB_REMOVE(format_entry_tree, &ft->tree, fe); |
638 |
|
|
free(fe->value); |
639 |
|
|
free(fe->key); |
640 |
|
|
free(fe); |
641 |
|
|
} |
642 |
|
|
|
643 |
|
|
if (ft->client != NULL) |
644 |
|
|
server_client_unref(ft->client); |
645 |
|
|
free(ft); |
646 |
|
|
} |
647 |
|
|
|
648 |
|
|
/* Add a key-value pair. */ |
649 |
|
|
void |
650 |
|
|
format_add(struct format_tree *ft, const char *key, const char *fmt, ...) |
651 |
|
|
{ |
652 |
|
|
struct format_entry *fe; |
653 |
|
|
struct format_entry *fe_now; |
654 |
|
|
va_list ap; |
655 |
|
|
|
656 |
|
|
fe = xmalloc(sizeof *fe); |
657 |
|
|
fe->key = xstrdup(key); |
658 |
|
|
|
659 |
|
|
fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe); |
660 |
|
|
if (fe_now != NULL) { |
661 |
|
|
free(fe->key); |
662 |
|
|
free(fe); |
663 |
|
|
free(fe_now->value); |
664 |
|
|
fe = fe_now; |
665 |
|
|
} |
666 |
|
|
|
667 |
|
|
fe->cb = NULL; |
668 |
|
|
fe->t = 0; |
669 |
|
|
|
670 |
|
|
va_start(ap, fmt); |
671 |
|
|
xvasprintf(&fe->value, fmt, ap); |
672 |
|
|
va_end(ap); |
673 |
|
|
} |
674 |
|
|
|
675 |
|
|
/* Add a key and time. */ |
676 |
|
|
static void |
677 |
|
|
format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) |
678 |
|
|
{ |
679 |
|
|
struct format_entry *fe; |
680 |
|
|
struct format_entry *fe_now; |
681 |
|
|
|
682 |
|
|
fe = xmalloc(sizeof *fe); |
683 |
|
|
fe->key = xstrdup(key); |
684 |
|
|
|
685 |
|
|
fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe); |
686 |
|
|
if (fe_now != NULL) { |
687 |
|
|
free(fe->key); |
688 |
|
|
free(fe); |
689 |
|
|
free(fe_now->value); |
690 |
|
|
fe = fe_now; |
691 |
|
|
} |
692 |
|
|
|
693 |
|
|
fe->cb = NULL; |
694 |
|
|
fe->t = tv->tv_sec; |
695 |
|
|
|
696 |
|
|
fe->value = NULL; |
697 |
|
|
} |
698 |
|
|
|
699 |
|
|
/* Add a key and function. */ |
700 |
|
|
static void |
701 |
|
|
format_add_cb(struct format_tree *ft, const char *key, format_cb cb) |
702 |
|
|
{ |
703 |
|
|
struct format_entry *fe; |
704 |
|
|
struct format_entry *fe_now; |
705 |
|
|
|
706 |
|
|
fe = xmalloc(sizeof *fe); |
707 |
|
|
fe->key = xstrdup(key); |
708 |
|
|
|
709 |
|
|
fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe); |
710 |
|
|
if (fe_now != NULL) { |
711 |
|
|
free(fe->key); |
712 |
|
|
free(fe); |
713 |
|
|
free(fe_now->value); |
714 |
|
|
fe = fe_now; |
715 |
|
|
} |
716 |
|
|
|
717 |
|
|
fe->cb = cb; |
718 |
|
|
fe->t = 0; |
719 |
|
|
|
720 |
|
|
fe->value = NULL; |
721 |
|
|
} |
722 |
|
|
|
723 |
|
|
/* Find a format entry. */ |
724 |
|
|
static char * |
725 |
|
|
format_find(struct format_tree *ft, const char *key, int modifiers) |
726 |
|
|
{ |
727 |
|
|
struct format_entry *fe, fe_find; |
728 |
|
|
struct environ_entry *envent; |
729 |
|
|
static char s[64]; |
730 |
|
|
struct options_entry *o; |
731 |
|
|
const char *found; |
732 |
|
|
int idx; |
733 |
|
|
char *copy, *saved; |
734 |
|
|
|
735 |
|
|
if (~modifiers & FORMAT_TIMESTRING) { |
736 |
|
|
o = options_parse_get(global_options, key, &idx, 0); |
737 |
|
|
if (o == NULL && ft->w != NULL) |
738 |
|
|
o = options_parse_get(ft->w->options, key, &idx, 0); |
739 |
|
|
if (o == NULL) |
740 |
|
|
o = options_parse_get(global_w_options, key, &idx, 0); |
741 |
|
|
if (o == NULL && ft->s != NULL) |
742 |
|
|
o = options_parse_get(ft->s->options, key, &idx, 0); |
743 |
|
|
if (o == NULL) |
744 |
|
|
o = options_parse_get(global_s_options, key, &idx, 0); |
745 |
|
|
if (o != NULL) { |
746 |
|
|
found = options_tostring(o, idx, 1); |
747 |
|
|
goto found; |
748 |
|
|
} |
749 |
|
|
} |
750 |
|
|
found = NULL; |
751 |
|
|
|
752 |
|
|
fe_find.key = (char *) key; |
753 |
|
|
fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find); |
754 |
|
|
if (fe != NULL) { |
755 |
|
|
if (modifiers & FORMAT_TIMESTRING) { |
756 |
|
|
if (fe->t == 0) |
757 |
|
|
return (NULL); |
758 |
|
|
ctime_r(&fe->t, s); |
759 |
|
|
s[strcspn(s, "\n")] = '\0'; |
760 |
|
|
found = s; |
761 |
|
|
goto found; |
762 |
|
|
} |
763 |
|
|
if (fe->t != 0) { |
764 |
|
|
xsnprintf(s, sizeof s, "%lld", (long long)fe->t); |
765 |
|
|
found = s; |
766 |
|
|
goto found; |
767 |
|
|
} |
768 |
|
|
if (fe->value == NULL && fe->cb != NULL) |
769 |
|
|
fe->cb(ft, fe); |
770 |
|
|
found = fe->value; |
771 |
|
|
goto found; |
772 |
|
|
} |
773 |
|
|
|
774 |
|
|
if (~modifiers & FORMAT_TIMESTRING) { |
775 |
|
|
envent = NULL; |
776 |
|
|
if (ft->s != NULL) |
777 |
|
|
envent = environ_find(ft->s->environ, key); |
778 |
|
|
if (envent == NULL) |
779 |
|
|
envent = environ_find(global_environ, key); |
780 |
|
|
if (envent != NULL) { |
781 |
|
|
found = envent->value; |
782 |
|
|
goto found; |
783 |
|
|
} |
784 |
|
|
} |
785 |
|
|
|
786 |
|
|
return (NULL); |
787 |
|
|
|
788 |
|
|
found: |
789 |
|
|
if (found == NULL) |
790 |
|
|
return (NULL); |
791 |
|
|
copy = xstrdup(found); |
792 |
|
|
if (modifiers & FORMAT_BASENAME) { |
793 |
|
|
saved = copy; |
794 |
|
|
copy = xstrdup(basename(saved)); |
795 |
|
|
free(saved); |
796 |
|
|
} |
797 |
|
|
if (modifiers & FORMAT_DIRNAME) { |
798 |
|
|
saved = copy; |
799 |
|
|
copy = xstrdup(dirname(saved)); |
800 |
|
|
free(saved); |
801 |
|
|
} |
802 |
|
|
return (copy); |
803 |
|
|
} |
804 |
|
|
|
805 |
|
|
/* Skip until comma. */ |
806 |
|
|
static char * |
807 |
|
|
format_skip(char *s) |
808 |
|
|
{ |
809 |
|
|
int brackets = 0; |
810 |
|
|
|
811 |
|
|
for (; *s != '\0'; s++) { |
812 |
|
|
if (*s == '{') |
813 |
|
|
brackets++; |
814 |
|
|
if (*s == '}') |
815 |
|
|
brackets--; |
816 |
|
|
if (*s == ',' && brackets == 0) |
817 |
|
|
break; |
818 |
|
|
} |
819 |
|
|
if (*s == '\0') |
820 |
|
|
return (NULL); |
821 |
|
|
return (s); |
822 |
|
|
} |
823 |
|
|
|
824 |
|
|
/* Return left and right alternatives separated by commas. */ |
825 |
|
|
static int |
826 |
|
|
format_choose(char *s, char **left, char **right) |
827 |
|
|
{ |
828 |
|
|
char *cp; |
829 |
|
|
|
830 |
|
|
cp = format_skip(s); |
831 |
|
|
if (cp == NULL) |
832 |
|
|
return (-1); |
833 |
|
|
*cp = '\0'; |
834 |
|
|
|
835 |
|
|
*left = s; |
836 |
|
|
*right = cp + 1; |
837 |
|
|
return (0); |
838 |
|
|
} |
839 |
|
|
|
840 |
|
|
/* Is this true? */ |
841 |
|
|
int |
842 |
|
|
format_true(const char *s) |
843 |
|
|
{ |
844 |
|
|
if (s != NULL && *s != '\0' && (s[0] != '0' || s[1] != '\0')) |
845 |
|
|
return (1); |
846 |
|
|
return (0); |
847 |
|
|
} |
848 |
|
|
|
849 |
|
|
/* Replace a key. */ |
850 |
|
|
static int |
851 |
|
|
format_replace(struct format_tree *ft, const char *key, size_t keylen, |
852 |
|
|
char **buf, size_t *len, size_t *off) |
853 |
|
|
{ |
854 |
|
|
struct window_pane *wp = ft->wp; |
855 |
|
|
char *copy, *copy0, *endptr, *ptr, *found, *new; |
856 |
|
|
char *value, *from = NULL, *to = NULL, *left, *right; |
857 |
|
|
size_t valuelen, newlen, fromlen, tolen, used; |
858 |
|
|
long limit = 0; |
859 |
|
|
int modifiers = 0, compare = 0, search = 0; |
860 |
|
|
|
861 |
|
|
/* Make a copy of the key. */ |
862 |
|
|
copy0 = copy = xmalloc(keylen + 1); |
863 |
|
|
memcpy(copy, key, keylen); |
864 |
|
|
copy[keylen] = '\0'; |
865 |
|
|
|
866 |
|
|
/* Is there a length limit or whatnot? */ |
867 |
|
|
switch (copy[0]) { |
868 |
|
|
case 'm': |
869 |
|
|
if (copy[1] != ':') |
870 |
|
|
break; |
871 |
|
|
compare = -2; |
872 |
|
|
copy += 2; |
873 |
|
|
break; |
874 |
|
|
case 'C': |
875 |
|
|
if (copy[1] != ':') |
876 |
|
|
break; |
877 |
|
|
search = 1; |
878 |
|
|
copy += 2; |
879 |
|
|
break; |
880 |
|
|
case '|': |
881 |
|
|
if (copy[1] != '|' || copy[2] != ':') |
882 |
|
|
break; |
883 |
|
|
compare = -3; |
884 |
|
|
copy += 3; |
885 |
|
|
break; |
886 |
|
|
case '&': |
887 |
|
|
if (copy[1] != '&' || copy[2] != ':') |
888 |
|
|
break; |
889 |
|
|
compare = -4; |
890 |
|
|
copy += 3; |
891 |
|
|
break; |
892 |
|
|
case '!': |
893 |
|
|
if (copy[1] == '=' && copy[2] == ':') { |
894 |
|
|
compare = -1; |
895 |
|
|
copy += 3; |
896 |
|
|
break; |
897 |
|
|
} |
898 |
|
|
break; |
899 |
|
|
case '=': |
900 |
|
|
if (copy[1] == '=' && copy[2] == ':') { |
901 |
|
|
compare = 1; |
902 |
|
|
copy += 3; |
903 |
|
|
break; |
904 |
|
|
} |
905 |
|
|
errno = 0; |
906 |
|
|
limit = strtol(copy + 1, &endptr, 10); |
907 |
|
|
if (errno == ERANGE && (limit == LONG_MIN || limit == LONG_MAX)) |
908 |
|
|
break; |
909 |
|
|
if (*endptr != ':') |
910 |
|
|
break; |
911 |
|
|
copy = endptr + 1; |
912 |
|
|
break; |
913 |
|
|
case 'b': |
914 |
|
|
if (copy[1] != ':') |
915 |
|
|
break; |
916 |
|
|
modifiers |= FORMAT_BASENAME; |
917 |
|
|
copy += 2; |
918 |
|
|
break; |
919 |
|
|
case 'd': |
920 |
|
|
if (copy[1] != ':') |
921 |
|
|
break; |
922 |
|
|
modifiers |= FORMAT_DIRNAME; |
923 |
|
|
copy += 2; |
924 |
|
|
break; |
925 |
|
|
case 't': |
926 |
|
|
if (copy[1] != ':') |
927 |
|
|
break; |
928 |
|
|
modifiers |= FORMAT_TIMESTRING; |
929 |
|
|
copy += 2; |
930 |
|
|
break; |
931 |
|
|
case 's': |
932 |
|
|
if (copy[1] != '/') |
933 |
|
|
break; |
934 |
|
|
from = copy + 2; |
935 |
|
|
for (copy = from; *copy != '\0' && *copy != '/'; copy++) |
936 |
|
|
/* nothing */; |
937 |
|
|
if (copy[0] != '/' || copy == from) { |
938 |
|
|
copy = copy0; |
939 |
|
|
break; |
940 |
|
|
} |
941 |
|
|
copy[0] = '\0'; |
942 |
|
|
to = copy + 1; |
943 |
|
|
for (copy = to; *copy != '\0' && *copy != '/'; copy++) |
944 |
|
|
/* nothing */; |
945 |
|
|
if (copy[0] != '/' || copy[1] != ':') { |
946 |
|
|
copy = copy0; |
947 |
|
|
break; |
948 |
|
|
} |
949 |
|
|
copy[0] = '\0'; |
950 |
|
|
|
951 |
|
|
modifiers |= FORMAT_SUBSTITUTE; |
952 |
|
|
copy += 2; |
953 |
|
|
break; |
954 |
|
|
} |
955 |
|
|
|
956 |
|
|
/* Is this a comparison or a conditional? */ |
957 |
|
|
if (search) { |
958 |
|
|
/* Search in pane. */ |
959 |
|
|
if (wp == NULL) |
960 |
|
|
value = xstrdup("0"); |
961 |
|
|
else |
962 |
|
|
xasprintf(&value, "%u", window_pane_search(wp, copy)); |
963 |
|
|
} else if (compare != 0) { |
964 |
|
|
/* Comparison: compare comma-separated left and right. */ |
965 |
|
|
if (format_choose(copy, &left, &right) != 0) |
966 |
|
|
goto fail; |
967 |
|
|
left = format_expand(ft, left); |
968 |
|
|
right = format_expand(ft, right); |
969 |
|
|
if (compare == -3 && |
970 |
|
|
(format_true(left) || format_true(right))) |
971 |
|
|
value = xstrdup("1"); |
972 |
|
|
else if (compare == -4 && |
973 |
|
|
(format_true(left) && format_true(right))) |
974 |
|
|
value = xstrdup("1"); |
975 |
|
|
else if (compare == 1 && strcmp(left, right) == 0) |
976 |
|
|
value = xstrdup("1"); |
977 |
|
|
else if (compare == -1 && strcmp(left, right) != 0) |
978 |
|
|
value = xstrdup("1"); |
979 |
|
|
else if (compare == -2 && fnmatch(left, right, 0) == 0) |
980 |
|
|
value = xstrdup("1"); |
981 |
|
|
else |
982 |
|
|
value = xstrdup("0"); |
983 |
|
|
free(right); |
984 |
|
|
free(left); |
985 |
|
|
} else if (*copy == '?') { |
986 |
|
|
/* Conditional: check first and choose second or third. */ |
987 |
|
|
ptr = format_skip(copy); |
988 |
|
|
if (ptr == NULL) |
989 |
|
|
goto fail; |
990 |
|
|
*ptr = '\0'; |
991 |
|
|
|
992 |
|
|
found = format_find(ft, copy + 1, modifiers); |
993 |
|
|
if (found == NULL) |
994 |
|
|
found = format_expand(ft, copy + 1); |
995 |
|
|
if (format_choose(ptr + 1, &left, &right) != 0) |
996 |
|
|
goto fail; |
997 |
|
|
|
998 |
|
|
if (format_true(found)) |
999 |
|
|
value = format_expand(ft, left); |
1000 |
|
|
else |
1001 |
|
|
value = format_expand(ft, right); |
1002 |
|
|
free(found); |
1003 |
|
|
} else { |
1004 |
|
|
/* Neither: look up directly. */ |
1005 |
|
|
value = format_find(ft, copy, modifiers); |
1006 |
|
|
if (value == NULL) |
1007 |
|
|
value = xstrdup(""); |
1008 |
|
|
} |
1009 |
|
|
|
1010 |
|
|
/* Perform substitution if any. */ |
1011 |
|
|
if (modifiers & FORMAT_SUBSTITUTE) { |
1012 |
|
|
fromlen = strlen(from); |
1013 |
|
|
tolen = strlen(to); |
1014 |
|
|
|
1015 |
|
|
newlen = strlen(value) + 1; |
1016 |
|
|
copy = new = xmalloc(newlen); |
1017 |
|
|
for (ptr = value; *ptr != '\0'; /* nothing */) { |
1018 |
|
|
if (strncmp(ptr, from, fromlen) != 0) { |
1019 |
|
|
*new++ = *ptr++; |
1020 |
|
|
continue; |
1021 |
|
|
} |
1022 |
|
|
used = new - copy; |
1023 |
|
|
|
1024 |
|
|
newlen += tolen; |
1025 |
|
|
copy = xrealloc(copy, newlen); |
1026 |
|
|
|
1027 |
|
|
new = copy + used; |
1028 |
|
|
memcpy(new, to, tolen); |
1029 |
|
|
|
1030 |
|
|
new += tolen; |
1031 |
|
|
ptr += fromlen; |
1032 |
|
|
} |
1033 |
|
|
*new = '\0'; |
1034 |
|
|
free(value); |
1035 |
|
|
value = copy; |
1036 |
|
|
} |
1037 |
|
|
|
1038 |
|
|
/* Truncate the value if needed. */ |
1039 |
|
|
if (limit > 0) { |
1040 |
|
|
new = utf8_trimcstr(value, limit); |
1041 |
|
|
free(value); |
1042 |
|
|
value = new; |
1043 |
|
|
} else if (limit < 0) { |
1044 |
|
|
new = utf8_rtrimcstr(value, -limit); |
1045 |
|
|
free(value); |
1046 |
|
|
value = new; |
1047 |
|
|
} |
1048 |
|
|
|
1049 |
|
|
/* Expand the buffer and copy in the value. */ |
1050 |
|
|
valuelen = strlen(value); |
1051 |
|
|
while (*len - *off < valuelen + 1) { |
1052 |
|
|
*buf = xreallocarray(*buf, 2, *len); |
1053 |
|
|
*len *= 2; |
1054 |
|
|
} |
1055 |
|
|
memcpy(*buf + *off, value, valuelen); |
1056 |
|
|
*off += valuelen; |
1057 |
|
|
|
1058 |
|
|
free(value); |
1059 |
|
|
free(copy0); |
1060 |
|
|
return (0); |
1061 |
|
|
|
1062 |
|
|
fail: |
1063 |
|
|
free(copy0); |
1064 |
|
|
return (-1); |
1065 |
|
|
} |
1066 |
|
|
|
1067 |
|
|
/* Expand keys in a template, passing through strftime first. */ |
1068 |
|
|
char * |
1069 |
|
|
format_expand_time(struct format_tree *ft, const char *fmt, time_t t) |
1070 |
|
|
{ |
1071 |
|
|
struct tm *tm; |
1072 |
|
|
char s[2048]; |
1073 |
|
|
|
1074 |
|
|
if (fmt == NULL || *fmt == '\0') |
1075 |
|
|
return (xstrdup("")); |
1076 |
|
|
|
1077 |
|
|
tm = localtime(&t); |
1078 |
|
|
|
1079 |
|
|
if (strftime(s, sizeof s, fmt, tm) == 0) |
1080 |
|
|
return (xstrdup("")); |
1081 |
|
|
|
1082 |
|
|
return (format_expand(ft, s)); |
1083 |
|
|
} |
1084 |
|
|
|
1085 |
|
|
/* Expand keys in a template. */ |
1086 |
|
|
char * |
1087 |
|
|
format_expand(struct format_tree *ft, const char *fmt) |
1088 |
|
|
{ |
1089 |
|
|
char *buf, *out; |
1090 |
|
|
const char *ptr, *s, *saved = fmt; |
1091 |
|
|
size_t off, len, n, outlen; |
1092 |
|
|
int ch, brackets; |
1093 |
|
|
|
1094 |
|
|
if (fmt == NULL) |
1095 |
|
|
return (xstrdup("")); |
1096 |
|
|
|
1097 |
|
|
len = 64; |
1098 |
|
|
buf = xmalloc(len); |
1099 |
|
|
off = 0; |
1100 |
|
|
|
1101 |
|
|
while (*fmt != '\0') { |
1102 |
|
|
if (*fmt != '#') { |
1103 |
|
|
while (len - off < 2) { |
1104 |
|
|
buf = xreallocarray(buf, 2, len); |
1105 |
|
|
len *= 2; |
1106 |
|
|
} |
1107 |
|
|
buf[off++] = *fmt++; |
1108 |
|
|
continue; |
1109 |
|
|
} |
1110 |
|
|
fmt++; |
1111 |
|
|
|
1112 |
|
|
ch = (u_char) *fmt++; |
1113 |
|
|
switch (ch) { |
1114 |
|
|
case '(': |
1115 |
|
|
brackets = 1; |
1116 |
|
|
for (ptr = fmt; *ptr != '\0'; ptr++) { |
1117 |
|
|
if (*ptr == '(') |
1118 |
|
|
brackets++; |
1119 |
|
|
if (*ptr == ')' && --brackets == 0) |
1120 |
|
|
break; |
1121 |
|
|
} |
1122 |
|
|
if (*ptr != ')' || brackets != 0) |
1123 |
|
|
break; |
1124 |
|
|
n = ptr - fmt; |
1125 |
|
|
|
1126 |
|
|
if (ft->flags & FORMAT_NOJOBS) |
1127 |
|
|
out = xstrdup(""); |
1128 |
|
|
else |
1129 |
|
|
out = format_job_get(ft, xstrndup(fmt, n)); |
1130 |
|
|
outlen = strlen(out); |
1131 |
|
|
|
1132 |
|
|
while (len - off < outlen + 1) { |
1133 |
|
|
buf = xreallocarray(buf, 2, len); |
1134 |
|
|
len *= 2; |
1135 |
|
|
} |
1136 |
|
|
memcpy(buf + off, out, outlen); |
1137 |
|
|
off += outlen; |
1138 |
|
|
|
1139 |
|
|
free(out); |
1140 |
|
|
|
1141 |
|
|
fmt += n + 1; |
1142 |
|
|
continue; |
1143 |
|
|
case '{': |
1144 |
|
|
brackets = 1; |
1145 |
|
|
for (ptr = fmt; *ptr != '\0'; ptr++) { |
1146 |
|
|
if (*ptr == '{') |
1147 |
|
|
brackets++; |
1148 |
|
|
if (*ptr == '}' && --brackets == 0) |
1149 |
|
|
break; |
1150 |
|
|
} |
1151 |
|
|
if (*ptr != '}' || brackets != 0) |
1152 |
|
|
break; |
1153 |
|
|
n = ptr - fmt; |
1154 |
|
|
|
1155 |
|
|
if (format_replace(ft, fmt, n, &buf, &len, &off) != 0) |
1156 |
|
|
break; |
1157 |
|
|
fmt += n + 1; |
1158 |
|
|
continue; |
1159 |
|
|
case '#': |
1160 |
|
|
while (len - off < 2) { |
1161 |
|
|
buf = xreallocarray(buf, 2, len); |
1162 |
|
|
len *= 2; |
1163 |
|
|
} |
1164 |
|
|
buf[off++] = '#'; |
1165 |
|
|
continue; |
1166 |
|
|
default: |
1167 |
|
|
s = NULL; |
1168 |
|
|
if (ch >= 'A' && ch <= 'Z') |
1169 |
|
|
s = format_upper[ch - 'A']; |
1170 |
|
|
else if (ch >= 'a' && ch <= 'z') |
1171 |
|
|
s = format_lower[ch - 'a']; |
1172 |
|
|
if (s == NULL) { |
1173 |
|
|
while (len - off < 3) { |
1174 |
|
|
buf = xreallocarray(buf, 2, len); |
1175 |
|
|
len *= 2; |
1176 |
|
|
} |
1177 |
|
|
buf[off++] = '#'; |
1178 |
|
|
buf[off++] = ch; |
1179 |
|
|
continue; |
1180 |
|
|
} |
1181 |
|
|
n = strlen(s); |
1182 |
|
|
if (format_replace(ft, s, n, &buf, &len, &off) != 0) |
1183 |
|
|
break; |
1184 |
|
|
continue; |
1185 |
|
|
} |
1186 |
|
|
|
1187 |
|
|
break; |
1188 |
|
|
} |
1189 |
|
|
buf[off] = '\0'; |
1190 |
|
|
|
1191 |
|
|
log_debug("format '%s' -> '%s'", saved, buf); |
1192 |
|
|
return (buf); |
1193 |
|
|
} |
1194 |
|
|
|
1195 |
|
|
/* Expand a single string. */ |
1196 |
|
|
char * |
1197 |
|
|
format_single(struct cmdq_item *item, const char *fmt, struct client *c, |
1198 |
|
|
struct session *s, struct winlink *wl, struct window_pane *wp) |
1199 |
|
|
{ |
1200 |
|
|
struct format_tree *ft; |
1201 |
|
|
char *expanded; |
1202 |
|
|
|
1203 |
|
|
if (item != NULL) |
1204 |
|
|
ft = format_create(item->client, item, FORMAT_NONE, 0); |
1205 |
|
|
else |
1206 |
|
|
ft = format_create(NULL, item, FORMAT_NONE, 0); |
1207 |
|
|
format_defaults(ft, c, s, wl, wp); |
1208 |
|
|
|
1209 |
|
|
expanded = format_expand(ft, fmt); |
1210 |
|
|
format_free(ft); |
1211 |
|
|
return (expanded); |
1212 |
|
|
} |
1213 |
|
|
|
1214 |
|
|
/* Set defaults for any of arguments that are not NULL. */ |
1215 |
|
|
void |
1216 |
|
|
format_defaults(struct format_tree *ft, struct client *c, struct session *s, |
1217 |
|
|
struct winlink *wl, struct window_pane *wp) |
1218 |
|
|
{ |
1219 |
|
|
format_add(ft, "session_format", "%d", s != NULL); |
1220 |
|
|
format_add(ft, "window_format", "%d", wl != NULL); |
1221 |
|
|
format_add(ft, "pane_format", "%d", wp != NULL); |
1222 |
|
|
|
1223 |
|
|
if (s == NULL && c != NULL) |
1224 |
|
|
s = c->session; |
1225 |
|
|
if (wl == NULL && s != NULL) |
1226 |
|
|
wl = s->curw; |
1227 |
|
|
if (wp == NULL && wl != NULL) |
1228 |
|
|
wp = wl->window->active; |
1229 |
|
|
|
1230 |
|
|
if (c != NULL) |
1231 |
|
|
format_defaults_client(ft, c); |
1232 |
|
|
if (s != NULL) |
1233 |
|
|
format_defaults_session(ft, s); |
1234 |
|
|
if (wl != NULL) |
1235 |
|
|
format_defaults_winlink(ft, wl); |
1236 |
|
|
if (wp != NULL) |
1237 |
|
|
format_defaults_pane(ft, wp); |
1238 |
|
|
} |
1239 |
|
|
|
1240 |
|
|
/* Set default format keys for a session. */ |
1241 |
|
|
static void |
1242 |
|
|
format_defaults_session(struct format_tree *ft, struct session *s) |
1243 |
|
|
{ |
1244 |
|
|
struct session_group *sg; |
1245 |
|
|
|
1246 |
|
|
ft->s = s; |
1247 |
|
|
|
1248 |
|
|
format_add(ft, "session_name", "%s", s->name); |
1249 |
|
|
format_add(ft, "session_windows", "%u", winlink_count(&s->windows)); |
1250 |
|
|
format_add(ft, "session_width", "%u", s->sx); |
1251 |
|
|
format_add(ft, "session_height", "%u", s->sy); |
1252 |
|
|
format_add(ft, "session_id", "$%u", s->id); |
1253 |
|
|
|
1254 |
|
|
sg = session_group_contains(s); |
1255 |
|
|
format_add(ft, "session_grouped", "%d", sg != NULL); |
1256 |
|
|
if (sg != NULL) |
1257 |
|
|
format_add(ft, "session_group", "%s", sg->name); |
1258 |
|
|
|
1259 |
|
|
format_add_tv(ft, "session_created", &s->creation_time); |
1260 |
|
|
format_add_tv(ft, "session_last_attached", &s->last_attached_time); |
1261 |
|
|
format_add_tv(ft, "session_activity", &s->activity_time); |
1262 |
|
|
|
1263 |
|
|
format_add(ft, "session_attached", "%u", s->attached); |
1264 |
|
|
format_add(ft, "session_many_attached", "%d", s->attached > 1); |
1265 |
|
|
|
1266 |
|
|
format_add_cb(ft, "session_alerts", format_cb_session_alerts); |
1267 |
|
|
format_add_cb(ft, "session_stack", format_cb_session_stack); |
1268 |
|
|
} |
1269 |
|
|
|
1270 |
|
|
/* Set default format keys for a client. */ |
1271 |
|
|
static void |
1272 |
|
|
format_defaults_client(struct format_tree *ft, struct client *c) |
1273 |
|
|
{ |
1274 |
|
|
struct session *s; |
1275 |
|
|
const char *name; |
1276 |
|
|
struct tty *tty = &c->tty; |
1277 |
|
|
const char *types[] = TTY_TYPES; |
1278 |
|
|
|
1279 |
|
|
if (ft->s == NULL) |
1280 |
|
|
ft->s = c->session; |
1281 |
|
|
|
1282 |
|
|
format_add(ft, "client_name", "%s", c->name); |
1283 |
|
|
format_add(ft, "client_pid", "%ld", (long) c->pid); |
1284 |
|
|
format_add(ft, "client_height", "%u", tty->sy); |
1285 |
|
|
format_add(ft, "client_width", "%u", tty->sx); |
1286 |
|
|
format_add(ft, "client_tty", "%s", c->ttyname); |
1287 |
|
|
format_add(ft, "client_control_mode", "%d", |
1288 |
|
|
!!(c->flags & CLIENT_CONTROL)); |
1289 |
|
|
|
1290 |
|
|
if (tty->term_name != NULL) |
1291 |
|
|
format_add(ft, "client_termname", "%s", tty->term_name); |
1292 |
|
|
if (tty->term_name != NULL) |
1293 |
|
|
format_add(ft, "client_termtype", "%s", types[tty->term_type]); |
1294 |
|
|
|
1295 |
|
|
format_add_tv(ft, "client_created", &c->creation_time); |
1296 |
|
|
format_add_tv(ft, "client_activity", &c->activity_time); |
1297 |
|
|
|
1298 |
|
|
format_add(ft, "client_written", "%zu", c->written); |
1299 |
|
|
format_add(ft, "client_discarded", "%zu", c->discarded); |
1300 |
|
|
|
1301 |
|
|
name = server_client_get_key_table(c); |
1302 |
|
|
if (strcmp(c->keytable->name, name) == 0) |
1303 |
|
|
format_add(ft, "client_prefix", "%d", 0); |
1304 |
|
|
else |
1305 |
|
|
format_add(ft, "client_prefix", "%d", 1); |
1306 |
|
|
format_add(ft, "client_key_table", "%s", c->keytable->name); |
1307 |
|
|
|
1308 |
|
|
if (tty->flags & TTY_UTF8) |
1309 |
|
|
format_add(ft, "client_utf8", "%d", 1); |
1310 |
|
|
else |
1311 |
|
|
format_add(ft, "client_utf8", "%d", 0); |
1312 |
|
|
|
1313 |
|
|
if (c->flags & CLIENT_READONLY) |
1314 |
|
|
format_add(ft, "client_readonly", "%d", 1); |
1315 |
|
|
else |
1316 |
|
|
format_add(ft, "client_readonly", "%d", 0); |
1317 |
|
|
|
1318 |
|
|
s = c->session; |
1319 |
|
|
if (s != NULL) |
1320 |
|
|
format_add(ft, "client_session", "%s", s->name); |
1321 |
|
|
s = c->last_session; |
1322 |
|
|
if (s != NULL && session_alive(s)) |
1323 |
|
|
format_add(ft, "client_last_session", "%s", s->name); |
1324 |
|
|
} |
1325 |
|
|
|
1326 |
|
|
/* Set default format keys for a window. */ |
1327 |
|
|
void |
1328 |
|
|
format_defaults_window(struct format_tree *ft, struct window *w) |
1329 |
|
|
{ |
1330 |
|
|
ft->w = w; |
1331 |
|
|
|
1332 |
|
|
format_add_tv(ft, "window_activity", &w->activity_time); |
1333 |
|
|
format_add(ft, "window_id", "@%u", w->id); |
1334 |
|
|
format_add(ft, "window_name", "%s", w->name); |
1335 |
|
|
format_add(ft, "window_width", "%u", w->sx); |
1336 |
|
|
format_add(ft, "window_height", "%u", w->sy); |
1337 |
|
|
format_add_cb(ft, "window_layout", format_cb_window_layout); |
1338 |
|
|
format_add_cb(ft, "window_visible_layout", |
1339 |
|
|
format_cb_window_visible_layout); |
1340 |
|
|
format_add(ft, "window_panes", "%u", window_count_panes(w)); |
1341 |
|
|
format_add(ft, "window_zoomed_flag", "%d", |
1342 |
|
|
!!(w->flags & WINDOW_ZOOMED)); |
1343 |
|
|
} |
1344 |
|
|
|
1345 |
|
|
/* Set default format keys for a winlink. */ |
1346 |
|
|
static void |
1347 |
|
|
format_defaults_winlink(struct format_tree *ft, struct winlink *wl) |
1348 |
|
|
{ |
1349 |
|
|
struct session *s = wl->session; |
1350 |
|
|
struct window *w = wl->window; |
1351 |
|
|
|
1352 |
|
|
if (ft->w == NULL) |
1353 |
|
|
ft->w = wl->window; |
1354 |
|
|
ft->wl = wl; |
1355 |
|
|
|
1356 |
|
|
format_defaults_window(ft, w); |
1357 |
|
|
|
1358 |
|
|
format_add(ft, "window_index", "%d", wl->idx); |
1359 |
|
|
format_add_cb(ft, "window_stack_index", format_cb_window_stack_index); |
1360 |
|
|
format_add(ft, "window_flags", "%s", window_printable_flags(wl)); |
1361 |
|
|
format_add(ft, "window_active", "%d", wl == s->curw); |
1362 |
|
|
|
1363 |
|
|
format_add(ft, "window_bell_flag", "%d", |
1364 |
|
|
!!(wl->flags & WINLINK_BELL)); |
1365 |
|
|
format_add(ft, "window_activity_flag", "%d", |
1366 |
|
|
!!(wl->flags & WINLINK_ACTIVITY)); |
1367 |
|
|
format_add(ft, "window_silence_flag", "%d", |
1368 |
|
|
!!(wl->flags & WINLINK_SILENCE)); |
1369 |
|
|
format_add(ft, "window_last_flag", "%d", |
1370 |
|
|
!!(wl == TAILQ_FIRST(&s->lastw))); |
1371 |
|
|
format_add(ft, "window_linked", "%d", session_is_linked(s, wl->window)); |
1372 |
|
|
} |
1373 |
|
|
|
1374 |
|
|
/* Set default format keys for a window pane. */ |
1375 |
|
|
void |
1376 |
|
|
format_defaults_pane(struct format_tree *ft, struct window_pane *wp) |
1377 |
|
|
{ |
1378 |
|
|
struct grid *gd = wp->base.grid; |
1379 |
|
|
u_int idx; |
1380 |
|
|
int status; |
1381 |
|
|
|
1382 |
|
|
if (ft->w == NULL) |
1383 |
|
|
ft->w = wp->window; |
1384 |
|
|
ft->wp = wp; |
1385 |
|
|
|
1386 |
|
|
format_add(ft, "history_size", "%u", gd->hsize); |
1387 |
|
|
format_add(ft, "history_limit", "%u", gd->hlimit); |
1388 |
|
|
format_add_cb(ft, "history_bytes", format_cb_history_bytes); |
1389 |
|
|
|
1390 |
|
|
if (window_pane_index(wp, &idx) != 0) |
1391 |
|
|
fatalx("index not found"); |
1392 |
|
|
format_add(ft, "pane_index", "%u", idx); |
1393 |
|
|
|
1394 |
|
|
format_add(ft, "pane_width", "%u", wp->sx); |
1395 |
|
|
format_add(ft, "pane_height", "%u", wp->sy); |
1396 |
|
|
format_add(ft, "pane_title", "%s", wp->base.title); |
1397 |
|
|
format_add(ft, "pane_id", "%%%u", wp->id); |
1398 |
|
|
format_add(ft, "pane_active", "%d", wp == wp->window->active); |
1399 |
|
|
format_add(ft, "pane_input_off", "%d", !!(wp->flags & PANE_INPUTOFF)); |
1400 |
|
|
format_add(ft, "pane_pipe", "%d", wp->pipe_fd != -1); |
1401 |
|
|
|
1402 |
|
|
status = wp->status; |
1403 |
|
|
if (wp->fd == -1 && WIFEXITED(status)) |
1404 |
|
|
format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status)); |
1405 |
|
|
format_add(ft, "pane_dead", "%d", wp->fd == -1); |
1406 |
|
|
|
1407 |
|
|
if (window_pane_visible(wp)) { |
1408 |
|
|
format_add(ft, "pane_left", "%u", wp->xoff); |
1409 |
|
|
format_add(ft, "pane_top", "%u", wp->yoff); |
1410 |
|
|
format_add(ft, "pane_right", "%u", wp->xoff + wp->sx - 1); |
1411 |
|
|
format_add(ft, "pane_bottom", "%u", wp->yoff + wp->sy - 1); |
1412 |
|
|
format_add(ft, "pane_at_left", "%d", wp->xoff == 0); |
1413 |
|
|
format_add(ft, "pane_at_top", "%d", wp->yoff == 0); |
1414 |
|
|
format_add(ft, "pane_at_right", "%d", wp->xoff + wp->sx == wp->window->sx); |
1415 |
|
|
format_add(ft, "pane_at_bottom", "%d", wp->yoff + wp->sy == wp->window->sy); |
1416 |
|
|
} |
1417 |
|
|
|
1418 |
|
|
format_add(ft, "pane_in_mode", "%d", wp->screen != &wp->base); |
1419 |
|
|
if (wp->mode != NULL) |
1420 |
|
|
format_add(ft, "pane_mode", "%s", wp->mode->name); |
1421 |
|
|
|
1422 |
|
|
format_add(ft, "pane_synchronized", "%d", |
1423 |
|
|
!!options_get_number(wp->window->options, "synchronize-panes")); |
1424 |
|
|
if (wp->searchstr != NULL) |
1425 |
|
|
format_add(ft, "pane_search_string", "%s", wp->searchstr); |
1426 |
|
|
|
1427 |
|
|
format_add(ft, "pane_tty", "%s", wp->tty); |
1428 |
|
|
format_add(ft, "pane_pid", "%ld", (long) wp->pid); |
1429 |
|
|
format_add_cb(ft, "pane_start_command", format_cb_start_command); |
1430 |
|
|
format_add_cb(ft, "pane_current_command", format_cb_current_command); |
1431 |
|
|
|
1432 |
|
|
format_add(ft, "cursor_x", "%u", wp->base.cx); |
1433 |
|
|
format_add(ft, "cursor_y", "%u", wp->base.cy); |
1434 |
|
|
format_add(ft, "scroll_region_upper", "%u", wp->base.rupper); |
1435 |
|
|
format_add(ft, "scroll_region_lower", "%u", wp->base.rlower); |
1436 |
|
|
|
1437 |
|
|
window_copy_add_formats(wp, ft); |
1438 |
|
|
|
1439 |
|
|
format_add(ft, "alternate_on", "%d", wp->saved_grid ? 1 : 0); |
1440 |
|
|
format_add(ft, "alternate_saved_x", "%u", wp->saved_cx); |
1441 |
|
|
format_add(ft, "alternate_saved_y", "%u", wp->saved_cy); |
1442 |
|
|
|
1443 |
|
|
format_add(ft, "cursor_flag", "%d", |
1444 |
|
|
!!(wp->base.mode & MODE_CURSOR)); |
1445 |
|
|
format_add(ft, "insert_flag", "%d", |
1446 |
|
|
!!(wp->base.mode & MODE_INSERT)); |
1447 |
|
|
format_add(ft, "keypad_cursor_flag", "%d", |
1448 |
|
|
!!(wp->base.mode & MODE_KCURSOR)); |
1449 |
|
|
format_add(ft, "keypad_flag", "%d", |
1450 |
|
|
!!(wp->base.mode & MODE_KKEYPAD)); |
1451 |
|
|
format_add(ft, "wrap_flag", "%d", |
1452 |
|
|
!!(wp->base.mode & MODE_WRAP)); |
1453 |
|
|
|
1454 |
|
|
format_add(ft, "mouse_any_flag", "%d", |
1455 |
|
|
!!(wp->base.mode & ALL_MOUSE_MODES)); |
1456 |
|
|
format_add(ft, "mouse_standard_flag", "%d", |
1457 |
|
|
!!(wp->base.mode & MODE_MOUSE_STANDARD)); |
1458 |
|
|
format_add(ft, "mouse_button_flag", "%d", |
1459 |
|
|
!!(wp->base.mode & MODE_MOUSE_BUTTON)); |
1460 |
|
|
format_add(ft, "mouse_all_flag", "%d", |
1461 |
|
|
!!(wp->base.mode & MODE_MOUSE_ALL)); |
1462 |
|
|
|
1463 |
|
|
format_add_cb(ft, "pane_tabs", format_cb_pane_tabs); |
1464 |
|
|
} |
1465 |
|
|
|
1466 |
|
|
/* Set default format keys for paste buffer. */ |
1467 |
|
|
void |
1468 |
|
|
format_defaults_paste_buffer(struct format_tree *ft, struct paste_buffer *pb) |
1469 |
|
|
{ |
1470 |
|
|
struct timeval tv; |
1471 |
|
|
size_t size; |
1472 |
|
|
char *s; |
1473 |
|
|
|
1474 |
|
|
timerclear(&tv); |
1475 |
|
|
tv.tv_sec = paste_buffer_created(pb); |
1476 |
|
|
paste_buffer_data(pb, &size); |
1477 |
|
|
|
1478 |
|
|
format_add(ft, "buffer_size", "%zu", size); |
1479 |
|
|
format_add(ft, "buffer_name", "%s", paste_buffer_name(pb)); |
1480 |
|
|
format_add_tv(ft, "buffer_created", &tv); |
1481 |
|
|
|
1482 |
|
|
s = paste_make_sample(pb); |
1483 |
|
|
format_add(ft, "buffer_sample", "%s", s); |
1484 |
|
|
free(s); |
1485 |
|
|
} |