chiark / gitweb /
Move DEFINE_TRIVIAL_CLEANUP_FUNC to macro.h
[elogind.git] / src / console / consoled-terminal.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <inttypes.h>
24 #include <stdlib.h>
25 #include "consoled.h"
26 #include "list.h"
27 #include "macro.h"
28 #include "util.h"
29
30 static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) {
31         Terminal *t = userdata;
32         int r;
33
34         if (t->pty) {
35                 r = pty_write(t->pty, buf, size);
36                 if (r < 0)
37                         return log_oom();
38         }
39
40         return 0;
41 }
42
43 static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) {
44         Terminal *t = userdata;
45         int r;
46
47         switch (event) {
48         case PTY_CHILD:
49                 log_debug("PTY child exited");
50                 t->pty = pty_unref(t->pty);
51                 break;
52         case PTY_DATA:
53                 r = term_screen_feed_text(t->screen, ptr, size);
54                 if (r < 0)
55                         log_error_errno(r, "Cannot update screen state: %m");
56
57                 workspace_dirty(t->workspace);
58                 break;
59         }
60
61         return 0;
62 }
63
64 int terminal_new(Terminal **out, Workspace *w) {
65         _cleanup_(terminal_freep) Terminal *t = NULL;
66         int r;
67
68         assert(w);
69
70         t = new0(Terminal, 1);
71         if (!t)
72                 return -ENOMEM;
73
74         t->workspace = w;
75         LIST_PREPEND(terminals_by_workspace, w->terminal_list, t);
76
77         r = term_parser_new(&t->parser, true);
78         if (r < 0)
79                 return r;
80
81         r = term_screen_new(&t->screen, terminal_write_fn, t, NULL, NULL);
82         if (r < 0)
83                 return r;
84
85         r = term_screen_set_answerback(t->screen, "systemd-console");
86         if (r < 0)
87                 return r;
88
89         if (out)
90                 *out = t;
91         t = NULL;
92         return 0;
93 }
94
95 Terminal *terminal_free(Terminal *t) {
96         if (!t)
97                 return NULL;
98
99         assert(t->workspace);
100
101         if (t->pty) {
102                 (void)pty_signal(t->pty, SIGHUP);
103                 pty_close(t->pty);
104                 pty_unref(t->pty);
105         }
106         term_screen_unref(t->screen);
107         term_parser_free(t->parser);
108         LIST_REMOVE(terminals_by_workspace, t->workspace->terminal_list, t);
109         free(t);
110
111         return NULL;
112 }
113
114 void terminal_resize(Terminal *t) {
115         uint32_t width, height, fw, fh;
116         int r;
117
118         assert(t);
119
120         width = t->workspace->width;
121         height = t->workspace->height;
122         fw = unifont_get_width(t->workspace->manager->uf);
123         fh = unifont_get_height(t->workspace->manager->uf);
124
125         width = (fw > 0) ? width / fw : 0;
126         height = (fh > 0) ? height / fh : 0;
127
128         if (t->pty) {
129                 r = pty_resize(t->pty, width, height);
130                 if (r < 0)
131                         log_error_errno(r, "Cannot resize pty: %m");
132         }
133
134         r = term_screen_resize(t->screen, width, height);
135         if (r < 0)
136                 log_error_errno(r, "Cannot resize screen: %m");
137 }
138
139 void terminal_run(Terminal *t) {
140         pid_t pid;
141
142         assert(t);
143
144         if (t->pty)
145                 return;
146
147         pid = pty_fork(&t->pty,
148                        t->workspace->manager->event,
149                        terminal_pty_fn,
150                        t,
151                        term_screen_get_width(t->screen),
152                        term_screen_get_height(t->screen));
153         if (pid < 0) {
154                 log_error_errno(pid, "Cannot fork PTY: %m");
155                 return;
156         } else if (pid == 0) {
157                 /* child */
158
159                 char **argv = (char*[]){
160                         (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL,
161                         NULL
162                 };
163
164                 setenv("TERM", "xterm-256color", 1);
165                 setenv("COLORTERM", "systemd-console", 1);
166
167                 execve(argv[0], argv, environ);
168                 log_error_errno(errno, "Cannot exec %s (%d): %m", argv[0], -errno);
169                 _exit(1);
170         }
171 }
172
173 static void terminal_feed_keyboard(Terminal *t, idev_data *data) {
174         idev_data_keyboard *kdata = &data->keyboard;
175         int r;
176
177         if (!data->resync && (kdata->value == 1 || kdata->value == 2)) {
178                 assert_cc(TERM_KBDMOD_CNT == (int)IDEV_KBDMOD_CNT);
179                 assert_cc(TERM_KBDMOD_IDX_SHIFT == (int)IDEV_KBDMOD_IDX_SHIFT &&
180                           TERM_KBDMOD_IDX_CTRL == (int)IDEV_KBDMOD_IDX_CTRL &&
181                           TERM_KBDMOD_IDX_ALT == (int)IDEV_KBDMOD_IDX_ALT &&
182                           TERM_KBDMOD_IDX_LINUX == (int)IDEV_KBDMOD_IDX_LINUX &&
183                           TERM_KBDMOD_IDX_CAPS == (int)IDEV_KBDMOD_IDX_CAPS);
184
185                 r = term_screen_feed_keyboard(t->screen,
186                                               kdata->keysyms,
187                                               kdata->n_syms,
188                                               kdata->ascii,
189                                               kdata->codepoints,
190                                               kdata->mods);
191                 if (r < 0)
192                         log_error_errno(r, "Cannot feed keyboard data to screen: %m");
193         }
194 }
195
196 void terminal_feed(Terminal *t, idev_data *data) {
197         switch (data->type) {
198         case IDEV_DATA_KEYBOARD:
199                 terminal_feed_keyboard(t, data);
200                 break;
201         }
202 }
203
204 static void terminal_fill(uint8_t *dst,
205                           uint32_t width,
206                           uint32_t height,
207                           uint32_t stride,
208                           uint32_t value) {
209         uint32_t i, j, *px;
210
211         for (j = 0; j < height; ++j) {
212                 px = (uint32_t*)dst;
213
214                 for (i = 0; i < width; ++i)
215                         *px++ = value;
216
217                 dst += stride;
218         }
219 }
220
221 static void terminal_blend(uint8_t *dst,
222                            uint32_t width,
223                            uint32_t height,
224                            uint32_t dst_stride,
225                            const uint8_t *src,
226                            uint32_t src_stride,
227                            uint32_t fg,
228                            uint32_t bg) {
229         uint32_t i, j, *px;
230
231         for (j = 0; j < height; ++j) {
232                 px = (uint32_t*)dst;
233
234                 for (i = 0; i < width; ++i) {
235                         if (!src || src[i / 8] & (1 << (7 - i % 8)))
236                                 *px = fg;
237                         else
238                                 *px = bg;
239
240                         ++px;
241                 }
242
243                 src += src_stride;
244                 dst += dst_stride;
245         }
246 }
247
248 typedef struct {
249         const grdev_display_target *target;
250         unifont *uf;
251         uint32_t cell_width;
252         uint32_t cell_height;
253         bool dirty;
254 } TerminalDrawContext;
255
256 static int terminal_draw_cell(term_screen *screen,
257                               void *userdata,
258                               unsigned int x,
259                               unsigned int y,
260                               const term_attr *attr,
261                               const uint32_t *ch,
262                               size_t n_ch,
263                               unsigned int ch_width) {
264         TerminalDrawContext *ctx = userdata;
265         const grdev_display_target *target = ctx->target;
266         grdev_fb *fb = target->back;
267         uint32_t xpos, ypos, width, height;
268         uint32_t fg, bg;
269         unifont_glyph g;
270         uint8_t *dst;
271         int r;
272
273         if (n_ch > 0) {
274                 r = unifont_lookup(ctx->uf, &g, *ch);
275                 if (r < 0)
276                         r = unifont_lookup(ctx->uf, &g, 0xfffd);
277                 if (r < 0)
278                         unifont_fallback(&g);
279         }
280
281         xpos = x * ctx->cell_width;
282         ypos = y * ctx->cell_height;
283
284         if (xpos >= fb->width || ypos >= fb->height)
285                 return 0;
286
287         width = MIN(fb->width - xpos, ctx->cell_width * ch_width);
288         height = MIN(fb->height - ypos, ctx->cell_height);
289
290         term_attr_to_argb32(attr, &fg, &bg, NULL);
291
292         ctx->dirty = true;
293
294         dst = fb->maps[0];
295         dst += fb->strides[0] * ypos + sizeof(uint32_t) * xpos;
296
297         if (n_ch < 1) {
298                 terminal_fill(dst,
299                               width,
300                               height,
301                               fb->strides[0],
302                               bg);
303         } else {
304                 if (width > g.width)
305                         terminal_fill(dst + sizeof(uint32_t) * g.width,
306                                       width - g.width,
307                                       height,
308                                       fb->strides[0],
309                                       bg);
310                 if (height > g.height)
311                         terminal_fill(dst + fb->strides[0] * g.height,
312                                       width,
313                                       height - g.height,
314                                       fb->strides[0],
315                                       bg);
316
317                 terminal_blend(dst,
318                                width,
319                                height,
320                                fb->strides[0],
321                                g.data,
322                                g.stride,
323                                fg,
324                                bg);
325         }
326
327         return 0;
328 }
329
330 bool terminal_draw(Terminal *t, const grdev_display_target *target) {
331         TerminalDrawContext ctx = { };
332         uint64_t age;
333
334         assert(t);
335         assert(target);
336
337         /* start up terminal on first frame */
338         terminal_run(t);
339
340         ctx.target = target;
341         ctx.uf = t->workspace->manager->uf;
342         ctx.cell_width = unifont_get_width(ctx.uf);
343         ctx.cell_height = unifont_get_height(ctx.uf);
344         ctx.dirty = false;
345
346         if (target->front) {
347                 /* if the frontbuffer is new enough, no reason to redraw */
348                 age = term_screen_get_age(t->screen);
349                 if (age != 0 && age <= target->front->data.u64)
350                         return false;
351         } else {
352                 /* force flip if no frontbuffer is set, yet */
353                 ctx.dirty = true;
354         }
355
356         term_screen_draw(t->screen, terminal_draw_cell, &ctx, &target->back->data.u64);
357
358         return ctx.dirty;
359 }