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