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