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