]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/console/consoled-terminal.c
d091579aa546e59f11aa5ad8f5c8ab4688c81e22
[thirdparty/systemd.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 }