]>
Commit | Line | Data |
---|---|---|
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 | ||
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) | |
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 | ||
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) { | |
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 | ||
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) | |
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 | ||
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) { | |
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 | ||
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) | |
f47781d8 | 191 | log_error_errno(r, "Cannot feed keyboard data to screen: %m"); |
5eef597e MP |
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 | } |