]> git.proxmox.com Git - systemd.git/blame - src/console/consoled-terminal.c
Imported Upstream version 222
[systemd.git] / src / console / consoled-terminal.c
CommitLineData
5eef597e
MP
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>
5eef597e
MP
23#include <stdlib.h>
24#include "consoled.h"
25#include "list.h"
26#include "macro.h"
27#include "util.h"
28
29static 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
42static 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)
f47781d8 54 log_error_errno(r, "Cannot update screen state: %m");
5eef597e
MP
55
56 workspace_dirty(t->workspace);
57 break;
58 }
59
60 return 0;
61}
62
63int 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
94Terminal *terminal_free(Terminal *t) {
95 if (!t)
96 return NULL;
97
98 assert(t->workspace);
99
100 if (t->pty) {
e3bff60a 101 (void) pty_signal(t->pty, SIGHUP);
5eef597e
MP
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
113void 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)
f47781d8 130 log_error_errno(r, "Cannot resize pty: %m");
5eef597e
MP
131 }
132
133 r = term_screen_resize(t->screen, width, height);
134 if (r < 0)
f47781d8 135 log_error_errno(r, "Cannot resize screen: %m");
5eef597e
MP
136}
137
138void 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) {
f47781d8 153 log_error_errno(pid, "Cannot fork PTY: %m");
5eef597e
MP
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);
f47781d8 167 log_error_errno(errno, "Cannot exec %s (%d): %m", argv[0], -errno);
5eef597e
MP
168 _exit(1);
169 }
170}
171
172static 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)
f47781d8 191 log_error_errno(r, "Cannot feed keyboard data to screen: %m");
5eef597e
MP
192 }
193}
194
195void 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
203static 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
220static 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
247typedef 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
255static 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
329bool 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}