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
15 static struct list_node g_vc_list = LIST_INITIAL_VALUE(g_vc_list);
16 static unsigned g_vc_count = 0;
17 static unsigned g_active_vc_index;
18
19 vc_t* g_active_vc;
20 int 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.
vc_handle_control_keys(uint8_t keycode,int modifiers)25 static 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.
vc_handle_device_control_keys(uint8_t keycode,int modifiers)96 static 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
119 return false;
120 }
121
vc_set_active(int num,vc_t * to_vc)122 zx_status_t vc_set_active(int num, vc_t* to_vc) {
123 vc_t* vc = NULL;
124 int i = 0;
125 list_for_every_entry (&g_vc_list, vc, vc_t, node) {
126 if ((num == i) || (to_vc == vc)) {
127 if (vc == g_active_vc) {
128 return ZX_OK;
129 }
130 if (g_active_vc) {
131 g_active_vc->active = false;
132 g_active_vc->flags &= ~VC_FLAG_HASOUTPUT;
133 }
134 vc->active = true;
135 vc->flags &= ~VC_FLAG_HASOUTPUT;
136 g_active_vc = vc;
137 g_active_vc_index = i;
138 vc_full_repaint(vc);
139 vc_render(vc);
140 return ZX_OK;
141 }
142 i++;
143 }
144 return ZX_ERR_NOT_FOUND;
145 }
146
vc_show_active()147 void vc_show_active() {
148 vc_t* vc = NULL;
149 list_for_every_entry (&g_vc_list, vc, vc_t, node) {
150 vc_attach_gfx(vc);
151 if (vc->fd >= 0) {
152 pty_window_size_t wsz = {
153 .width = vc->columns,
154 .height = vc->rows,
155 };
156 ioctl_pty_set_window_size(vc->fd, &wsz);
157 }
158 if (vc == g_active_vc) {
159 vc_full_repaint(vc);
160 vc_render(vc);
161 }
162 }
163 }
164
vc_status_update()165 void vc_status_update() {
166 vc_t* vc = NULL;
167 unsigned i = 0;
168 int x = 0;
169
170 int w = g_status_width / (g_vc_count + 1);
171 if (w < MIN_TAB_WIDTH) {
172 w = MIN_TAB_WIDTH;
173 } else if (w > MAX_TAB_WIDTH) {
174 w = MAX_TAB_WIDTH;
175 }
176
177 char tmp[w];
178
179 vc_status_clear();
180 list_for_every_entry (&g_vc_list, vc, vc_t, node) {
181 unsigned fg;
182 if (vc->active) {
183 fg = STATUS_COLOR_ACTIVE;
184 } else if (vc->flags & VC_FLAG_HASOUTPUT) {
185 fg = STATUS_COLOR_UPDATED;
186 } else {
187 fg = STATUS_COLOR_DEFAULT;
188 }
189
190 int lines = vc_get_scrollback_lines(vc);
191 char L = (lines > 0) && (-vc->viewport_y < lines) ? '<' : '[';
192 char R = (vc->viewport_y < 0) ? '>' : ']';
193
194 snprintf(tmp, w, "%c%u%c %s", L, i, R, vc->title);
195 vc_status_write(x, fg, tmp);
196 x += w;
197 i++;
198 }
199 vc_status_commit();
200 }
201
handle_key_press(uint8_t keycode,int modifiers)202 void handle_key_press(uint8_t keycode, int modifiers) {
203 // Handle vc-level control keys
204 if (vc_handle_device_control_keys(keycode, modifiers))
205 return;
206
207 // Handle other keys only if we own the display
208 if (!g_vc_owns_display)
209 return;
210
211 // Handle other control keys
212 if (vc_handle_control_keys(keycode, modifiers))
213 return;
214
215 vc_t* vc = g_active_vc;
216 char output[4];
217 uint32_t length = hid_key_to_vt100_code(
218 keycode, modifiers, vc->keymap, output, sizeof(output));
219 if (length > 0) {
220 if (vc->fd >= 0) {
221 write(vc->fd, output, length);
222 }
223 vc_scroll_viewport_bottom(vc);
224 }
225 }
226
vc_write(vc_t * vc,const void * buf,size_t count,zx_off_t off)227 ssize_t vc_write(vc_t* vc, const void* buf, size_t count, zx_off_t off) {
228 vc->invy0 = vc_rows(vc) + 1;
229 vc->invy1 = -1;
230 const uint8_t* str = (const uint8_t*)buf;
231 for (size_t i = 0; i < count; i++) {
232 vc->textcon.putc(&vc->textcon, str[i]);
233 }
234 vc_flush(vc);
235 if (!(vc->flags & VC_FLAG_HASOUTPUT) && !vc->active) {
236 vc->flags |= VC_FLAG_HASOUTPUT;
237 vc_status_update();
238 }
239 return count;
240 }
241
242 // Create a new vc_t and add it to the console list.
vc_create(vc_t ** vc_out,bool special)243 zx_status_t vc_create(vc_t** vc_out, bool special) {
244 zx_status_t status;
245 vc_t* vc;
246 if ((status = vc_alloc(&vc, special)) < 0) {
247 return status;
248 }
249
250 // add to the vc list
251 list_add_tail(&g_vc_list, &vc->node);
252 g_vc_count++;
253
254 // make this the active vc if it's the first one
255 if (!g_active_vc) {
256 vc_set_active(-1, vc);
257 } else {
258 vc_render(g_active_vc);
259 }
260
261 *vc_out = vc;
262 return ZX_OK;
263 }
264
vc_destroy(vc_t * vc)265 void vc_destroy(vc_t* vc) {
266 list_delete(&vc->node);
267 g_vc_count -= 1;
268
269 if (vc->active) {
270 g_active_vc = NULL;
271 if (g_active_vc_index >= g_vc_count) {
272 g_active_vc_index = g_vc_count - 1;
273 }
274 vc_set_active(g_active_vc_index, NULL);
275 } else if (g_active_vc) {
276 vc_full_repaint(g_active_vc);
277 vc_render(g_active_vc);
278 }
279
280 vc_free(vc);
281 }
282