]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/console/consoled-terminal.c
treewide: use log_*_errno whenever %m is in the format string
[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_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 }