chiark / gitweb /
sd-login: let's not needlessly yell at users
[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("Cannot update screen state: %s", strerror(-r));
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("Cannot resize pty: %s", strerror(-r));
132         }
133
134         r = term_screen_resize(t->screen, width, height);
135         if (r < 0)
136                 log_error("Cannot resize screen: %s", strerror(-r));
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("Cannot fork PTY: %s", strerror(-pid));
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("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("Cannot feed keyboard data to screen: %s",
193                                   strerror(-r));
194         }
195 }
196
197 void terminal_feed(Terminal *t, idev_data *data) {
198         switch (data->type) {
199         case IDEV_DATA_KEYBOARD:
200                 terminal_feed_keyboard(t, data);
201                 break;
202         }
203 }
204
205 static void terminal_fill(uint8_t *dst,
206                           uint32_t width,
207                           uint32_t height,
208                           uint32_t stride,
209                           uint32_t value) {
210         uint32_t i, j, *px;
211
212         for (j = 0; j < height; ++j) {
213                 px = (uint32_t*)dst;
214
215                 for (i = 0; i < width; ++i)
216                         *px++ = value;
217
218                 dst += stride;
219         }
220 }
221
222 static void terminal_blend(uint8_t *dst,
223                            uint32_t width,
224                            uint32_t height,
225                            uint32_t dst_stride,
226                            const uint8_t *src,
227                            uint32_t src_stride,
228                            uint32_t fg,
229                            uint32_t bg) {
230         uint32_t i, j, *px;
231
232         for (j = 0; j < height; ++j) {
233                 px = (uint32_t*)dst;
234
235                 for (i = 0; i < width; ++i) {
236                         if (!src || src[i / 8] & (1 << (7 - i % 8)))
237                                 *px = fg;
238                         else
239                                 *px = bg;
240
241                         ++px;
242                 }
243
244                 src += src_stride;
245                 dst += dst_stride;
246         }
247 }
248
249 typedef struct {
250         const grdev_display_target *target;
251         unifont *uf;
252         uint32_t cell_width;
253         uint32_t cell_height;
254         bool dirty;
255 } TerminalDrawContext;
256
257 static int terminal_draw_cell(term_screen *screen,
258                               void *userdata,
259                               unsigned int x,
260                               unsigned int y,
261                               const term_attr *attr,
262                               const uint32_t *ch,
263                               size_t n_ch,
264                               unsigned int ch_width) {
265         TerminalDrawContext *ctx = userdata;
266         const grdev_display_target *target = ctx->target;
267         grdev_fb *fb = target->back;
268         uint32_t xpos, ypos, width, height;
269         uint32_t fg, bg;
270         unifont_glyph g;
271         uint8_t *dst;
272         int r;
273
274         if (n_ch > 0) {
275                 r = unifont_lookup(ctx->uf, &g, *ch);
276                 if (r < 0)
277                         r = unifont_lookup(ctx->uf, &g, 0xfffd);
278                 if (r < 0)
279                         unifont_fallback(&g);
280         }
281
282         xpos = x * ctx->cell_width;
283         ypos = y * ctx->cell_height;
284
285         if (xpos >= fb->width || ypos >= fb->height)
286                 return 0;
287
288         width = MIN(fb->width - xpos, ctx->cell_width * ch_width);
289         height = MIN(fb->height - ypos, ctx->cell_height);
290
291         term_attr_to_argb32(attr, &fg, &bg, NULL);
292
293         ctx->dirty = true;
294
295         dst = fb->maps[0];
296         dst += fb->strides[0] * ypos + sizeof(uint32_t) * xpos;
297
298         if (n_ch < 1) {
299                 terminal_fill(dst,
300                               width,
301                               height,
302                               fb->strides[0],
303                               bg);
304         } else {
305                 if (width > g.width)
306                         terminal_fill(dst + sizeof(uint32_t) * g.width,
307                                       width - g.width,
308                                       height,
309                                       fb->strides[0],
310                                       bg);
311                 if (height > g.height)
312                         terminal_fill(dst + fb->strides[0] * g.height,
313                                       width,
314                                       height - g.height,
315                                       fb->strides[0],
316                                       bg);
317
318                 terminal_blend(dst,
319                                width,
320                                height,
321                                fb->strides[0],
322                                g.data,
323                                g.stride,
324                                fg,
325                                bg);
326         }
327
328         return 0;
329 }
330
331 bool terminal_draw(Terminal *t, const grdev_display_target *target) {
332         TerminalDrawContext ctx = { };
333         uint64_t age;
334
335         assert(t);
336         assert(target);
337
338         /* start up terminal on first frame */
339         terminal_run(t);
340
341         ctx.target = target;
342         ctx.uf = t->workspace->manager->uf;
343         ctx.cell_width = unifont_get_width(ctx.uf);
344         ctx.cell_height = unifont_get_height(ctx.uf);
345         ctx.dirty = false;
346
347         if (target->front) {
348                 /* if the frontbuffer is new enough, no reason to redraw */
349                 age = term_screen_get_age(t->screen);
350                 if (age != 0 && age <= target->front->data.u64)
351                         return false;
352         } else {
353                 /* force flip if no frontbuffer is set, yet */
354                 ctx.dirty = true;
355         }
356
357         term_screen_draw(t->screen, terminal_draw_cell, &ctx, &target->back->data.u64);
358
359         return ctx.dirty;
360 }