1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fcntl.h>
6#include <hid/usages.h>
7#include <string.h>
8#include <sys/param.h>
9#include <zircon/device/pty.h>
10
11#include "keyboard-vt100.h"
12#include "keyboard.h"
13#include "vc.h"
14
15static struct list_node g_vc_list = LIST_INITIAL_VALUE(g_vc_list);
16static unsigned g_vc_count = 0;
17static unsigned g_active_vc_index;
18
19vc_t* g_active_vc;
20int g_status_width = 0;
21
22// Process key sequences that affect the console (scrolling, switching
23// console, etc.) without sending input to the current console.  This
24// returns whether this key press was handled.
25static bool vc_handle_control_keys(uint8_t keycode, int modifiers) {
26    switch (keycode) {
27    case HID_USAGE_KEY_F1 ... HID_USAGE_KEY_F10:
28        if (modifiers & MOD_ALT) {
29            vc_set_active(keycode - HID_USAGE_KEY_F1, NULL);
30            return true;
31        }
32        break;
33
34    case HID_USAGE_KEY_TAB:
35        if (modifiers & MOD_ALT) {
36            if (modifiers & MOD_SHIFT) {
37                vc_set_active(g_active_vc_index == 0 ? g_vc_count - 1 : g_active_vc_index - 1, NULL);
38            } else {
39                vc_set_active(g_active_vc_index == g_vc_count - 1 ? 0 : g_active_vc_index + 1, NULL);
40            }
41            return true;
42        }
43        break;
44
45    case HID_USAGE_KEY_VOL_UP:
46        vc_set_active(g_active_vc_index == 0 ? g_vc_count - 1 : g_active_vc_index - 1, NULL);
47        break;
48
49    case HID_USAGE_KEY_VOL_DOWN:
50        vc_set_active(g_active_vc_index == g_vc_count - 1 ? 0 : g_active_vc_index + 1, NULL);
51        break;
52
53    case HID_USAGE_KEY_UP:
54        if (modifiers & MOD_ALT) {
55            vc_scroll_viewport(g_active_vc, -1);
56            return true;
57        }
58        break;
59    case HID_USAGE_KEY_DOWN:
60        if (modifiers & MOD_ALT) {
61            vc_scroll_viewport(g_active_vc, 1);
62            return true;
63        }
64        break;
65    case HID_USAGE_KEY_PAGEUP:
66        if (modifiers & MOD_SHIFT) {
67            vc_scroll_viewport(g_active_vc, -(vc_rows(g_active_vc) / 2));
68            return true;
69        }
70        break;
71    case HID_USAGE_KEY_PAGEDOWN:
72        if (modifiers & MOD_SHIFT) {
73            vc_scroll_viewport(g_active_vc, vc_rows(g_active_vc) / 2);
74            return true;
75        }
76        break;
77    case HID_USAGE_KEY_HOME:
78        if (modifiers & MOD_SHIFT) {
79            vc_scroll_viewport_top(g_active_vc);
80            return true;
81        }
82        break;
83    case HID_USAGE_KEY_END:
84        if (modifiers & MOD_SHIFT) {
85            vc_scroll_viewport_bottom(g_active_vc);
86            return true;
87        }
88        break;
89    }
90    return false;
91}
92
93// Process key sequences that affect the low-level control of the system
94// (switching display ownership, rebooting).  This returns whether this key press
95// was handled.
96static bool vc_handle_device_control_keys(uint8_t keycode, int modifiers) {
97    switch (keycode) {
98    case HID_USAGE_KEY_DELETE:
99        // Provide a CTRL-ALT-DEL reboot sequence
100        if ((modifiers & MOD_CTRL) && (modifiers & MOD_ALT)) {
101            int fd;
102            // Send the reboot command to devmgr
103            if ((fd = open("/dev/misc/dmctl", O_WRONLY)) >= 0) {
104                write(fd, "reboot", strlen("reboot"));
105                close(fd);
106            }
107            return true;
108        }
109        break;
110
111    case HID_USAGE_KEY_ESC:
112        if (modifiers & MOD_ALT) {
113            vc_toggle_framebuffer();
114            return true;
115        }
116        break;
117
118    case HID_USAGE_KEY_LEFT_GUI:
119        // Also known as the "windows key".
120        vc_toggle_framebuffer();
121        break;
122    }
123
124    return false;
125}
126
127zx_status_t vc_set_active(int num, vc_t* to_vc) {
128    vc_t* vc = NULL;
129    int i = 0;
130    list_for_every_entry (&g_vc_list, vc, vc_t, node) {
131        if ((num == i) || (to_vc == vc)) {
132            if (vc == g_active_vc) {
133                return ZX_OK;
134            }
135            if (g_active_vc) {
136                g_active_vc->active = false;
137                g_active_vc->flags &= ~VC_FLAG_HASOUTPUT;
138            }
139            vc->active = true;
140            vc->flags &= ~VC_FLAG_HASOUTPUT;
141            g_active_vc = vc;
142            g_active_vc_index = i;
143            vc_full_repaint(vc);
144            vc_render(vc);
145            return ZX_OK;
146        }
147        i++;
148    }
149    return ZX_ERR_NOT_FOUND;
150}
151
152void vc_show_active() {
153    vc_t* vc = NULL;
154    list_for_every_entry (&g_vc_list, vc, vc_t, node) {
155        vc_attach_gfx(vc);
156        if (vc->fd >= 0) {
157            pty_window_size_t wsz = {
158                .width = vc->columns,
159                .height = vc->rows,
160            };
161            ioctl_pty_set_window_size(vc->fd, &wsz);
162        }
163        if (vc == g_active_vc) {
164            vc_full_repaint(vc);
165            vc_render(vc);
166        }
167    }
168}
169
170void vc_status_update() {
171    vc_t* vc = NULL;
172    unsigned i = 0;
173    int x = 0;
174
175    int w = g_status_width / (g_vc_count + 1);
176    if (w < MIN_TAB_WIDTH) {
177        w = MIN_TAB_WIDTH;
178    } else if (w > MAX_TAB_WIDTH) {
179        w = MAX_TAB_WIDTH;
180    }
181
182    char tmp[w];
183
184    vc_status_clear();
185    list_for_every_entry (&g_vc_list, vc, vc_t, node) {
186        unsigned fg;
187        if (vc->active) {
188            fg = STATUS_COLOR_ACTIVE;
189        } else if (vc->flags & VC_FLAG_HASOUTPUT) {
190            fg = STATUS_COLOR_UPDATED;
191        } else {
192            fg = STATUS_COLOR_DEFAULT;
193        }
194
195        int lines = vc_get_scrollback_lines(vc);
196        char L = (lines > 0) && (-vc->viewport_y < lines) ? '<' : '[';
197        char R = (vc->viewport_y < 0) ? '>' : ']';
198
199        snprintf(tmp, w, "%c%u%c %s", L, i, R, vc->title);
200        vc_status_write(x, fg, tmp);
201        x += w;
202        i++;
203    }
204    vc_status_commit();
205}
206
207void handle_key_press(uint8_t keycode, int modifiers) {
208    // Handle vc-level control keys
209    if (vc_handle_device_control_keys(keycode, modifiers))
210        return;
211
212    // Handle other keys only if we own the display
213    if (!g_vc_owns_display)
214        return;
215
216    // Handle other control keys
217    if (vc_handle_control_keys(keycode, modifiers))
218        return;
219
220    vc_t* vc = g_active_vc;
221    char output[4];
222    uint32_t length = hid_key_to_vt100_code(
223        keycode, modifiers, vc->keymap, output, sizeof(output));
224    if (length > 0) {
225        if (vc->fd >= 0) {
226            write(vc->fd, output, length);
227        }
228        vc_scroll_viewport_bottom(vc);
229    }
230}
231
232ssize_t vc_write(vc_t* vc, const void* buf, size_t count, zx_off_t off) {
233    vc->invy0 = vc_rows(vc) + 1;
234    vc->invy1 = -1;
235    const uint8_t* str = (const uint8_t*)buf;
236    for (size_t i = 0; i < count; i++) {
237        vc->textcon.putc(&vc->textcon, str[i]);
238    }
239    vc_flush(vc);
240    if (!(vc->flags & VC_FLAG_HASOUTPUT) && !vc->active) {
241        vc->flags |= VC_FLAG_HASOUTPUT;
242        vc_status_update();
243    }
244    return count;
245}
246
247// Create a new vc_t and add it to the console list.
248zx_status_t vc_create(vc_t** vc_out, bool special) {
249    zx_status_t status;
250    vc_t* vc;
251    if ((status = vc_alloc(&vc, special)) < 0) {
252        return status;
253    }
254
255    // add to the vc list
256    list_add_tail(&g_vc_list, &vc->node);
257    g_vc_count++;
258
259    // make this the active vc if it's the first one
260    if (!g_active_vc) {
261        vc_set_active(-1, vc);
262    } else {
263        vc_render(g_active_vc);
264    }
265
266    *vc_out = vc;
267    return ZX_OK;
268}
269
270void vc_destroy(vc_t* vc) {
271    list_delete(&vc->node);
272    g_vc_count -= 1;
273
274    if (vc->active) {
275        g_active_vc = NULL;
276        if (g_active_vc_index >= g_vc_count) {
277            g_active_vc_index = g_vc_count - 1;
278        }
279        vc_set_active(g_active_vc_index, NULL);
280    } else if (g_active_vc) {
281        vc_full_repaint(g_active_vc);
282        vc_render(g_active_vc);
283    }
284
285    vc_free(vc);
286}
287