| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <fuchsia/device/manager/c/fidl.h> |
| #include <fuchsia/hardware/pty/c/fidl.h> |
| #include <hid/usages.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/zx/channel.h> |
| #include <string.h> |
| #include <sys/param.h> |
| |
| #include "keyboard-vt100.h" |
| #include "keyboard.h" |
| #include "vc.h" |
| |
| static struct list_node g_vc_list = LIST_INITIAL_VALUE(g_vc_list); |
| static unsigned g_vc_count = 0; |
| static unsigned g_active_vc_index; |
| |
| vc_t* g_active_vc; |
| int g_status_width = 0; |
| |
| // Process key sequences that affect the console (scrolling, switching |
| // console, etc.) without sending input to the current console. This |
| // returns whether this key press was handled. |
| static bool vc_handle_control_keys(uint8_t keycode, int modifiers) { |
| switch (keycode) { |
| case HID_USAGE_KEY_F1 ... HID_USAGE_KEY_F10: |
| if (modifiers & MOD_ALT) { |
| vc_set_active(keycode - HID_USAGE_KEY_F1, NULL); |
| return true; |
| } |
| break; |
| |
| case HID_USAGE_KEY_TAB: |
| if (modifiers & MOD_ALT) { |
| if (modifiers & MOD_SHIFT) { |
| vc_set_active(g_active_vc_index == 0 ? g_vc_count - 1 : g_active_vc_index - 1, NULL); |
| } else { |
| vc_set_active(g_active_vc_index == g_vc_count - 1 ? 0 : g_active_vc_index + 1, NULL); |
| } |
| return true; |
| } |
| break; |
| |
| case HID_USAGE_KEY_VOL_UP: |
| vc_set_active(g_active_vc_index == 0 ? g_vc_count - 1 : g_active_vc_index - 1, NULL); |
| break; |
| |
| case HID_USAGE_KEY_VOL_DOWN: |
| vc_set_active(g_active_vc_index == g_vc_count - 1 ? 0 : g_active_vc_index + 1, NULL); |
| break; |
| |
| case HID_USAGE_KEY_UP: |
| if (modifiers & MOD_ALT) { |
| vc_scroll_viewport(g_active_vc, -1); |
| return true; |
| } |
| break; |
| case HID_USAGE_KEY_DOWN: |
| if (modifiers & MOD_ALT) { |
| vc_scroll_viewport(g_active_vc, 1); |
| return true; |
| } |
| break; |
| case HID_USAGE_KEY_PAGEUP: |
| if (modifiers & MOD_SHIFT) { |
| vc_scroll_viewport(g_active_vc, -(vc_rows(g_active_vc) / 2)); |
| return true; |
| } |
| break; |
| case HID_USAGE_KEY_PAGEDOWN: |
| if (modifiers & MOD_SHIFT) { |
| vc_scroll_viewport(g_active_vc, vc_rows(g_active_vc) / 2); |
| return true; |
| } |
| break; |
| case HID_USAGE_KEY_HOME: |
| if (modifiers & MOD_SHIFT) { |
| vc_scroll_viewport_top(g_active_vc); |
| return true; |
| } |
| break; |
| case HID_USAGE_KEY_END: |
| if (modifiers & MOD_SHIFT) { |
| vc_scroll_viewport_bottom(g_active_vc); |
| return true; |
| } |
| break; |
| } |
| return false; |
| } |
| |
| static bool connect_to_service(const char* service, zx::channel* channel) { |
| zx::channel channel_remote; |
| zx_status_t status = zx::channel::create(0, channel, &channel_remote); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to create channel: %d\n", status); |
| return false; |
| } |
| |
| status = fdio_service_connect(service, channel_remote.release()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "failed to connect to service: %d\n", status); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Process key sequences that affect the low-level control of the system |
| // (switching display ownership, rebooting). This returns whether this key press |
| // was handled. |
| static bool vc_handle_device_control_keys(uint8_t keycode, int modifiers) { |
| switch (keycode) { |
| case HID_USAGE_KEY_DELETE: |
| // Provide a CTRL-ALT-DEL reboot sequence |
| if ((modifiers & MOD_CTRL) && (modifiers & MOD_ALT)) { |
| zx::channel channel; |
| if (connect_to_service("/svc/fuchsia.device.manager.Administrator", &channel)) { |
| zx_status_t call_status; |
| const auto flag = fuchsia_device_manager_SUSPEND_FLAG_REBOOT; |
| auto status = fuchsia_device_manager_AdministratorSuspend(channel.get(), flag, |
| &call_status); |
| if (status != ZX_OK || call_status != ZX_OK) { |
| fprintf(stderr, "Failed to reboot, statuses: local: %d remote: %d\n", |
| status, call_status); |
| } |
| } |
| return true; |
| } |
| break; |
| |
| case HID_USAGE_KEY_ESC: |
| if (modifiers & MOD_ALT) { |
| vc_toggle_framebuffer(); |
| return true; |
| } |
| break; |
| } |
| |
| return false; |
| } |
| |
| zx_status_t vc_set_active(int num, vc_t* to_vc) { |
| vc_t* vc = NULL; |
| int i = 0; |
| list_for_every_entry (&g_vc_list, vc, vc_t, node) { |
| if ((num == i) || (to_vc == vc)) { |
| if (vc == g_active_vc) { |
| return ZX_OK; |
| } |
| if (g_active_vc) { |
| g_active_vc->active = false; |
| g_active_vc->flags &= ~VC_FLAG_HASOUTPUT; |
| } |
| vc->active = true; |
| vc->flags &= ~VC_FLAG_HASOUTPUT; |
| g_active_vc = vc; |
| g_active_vc_index = i; |
| vc_full_repaint(vc); |
| vc_render(vc); |
| return ZX_OK; |
| } |
| i++; |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| void vc_show_active() { |
| vc_t* vc = NULL; |
| list_for_every_entry (&g_vc_list, vc, vc_t, node) { |
| vc_attach_gfx(vc); |
| if ((vc->fd >= 0) && isatty(vc->fd)) { |
| fuchsia_hardware_pty_WindowSize wsz = { |
| .width = vc->columns, |
| .height = vc->rows, |
| }; |
| |
| fdio_t* io = fdio_unsafe_fd_to_io(vc->fd); |
| zx_status_t status; |
| fuchsia_hardware_pty_DeviceSetWindowSize(fdio_unsafe_borrow_channel(io), &wsz, &status); |
| fdio_unsafe_release(io); |
| } |
| if (vc == g_active_vc) { |
| vc_full_repaint(vc); |
| vc_render(vc); |
| } |
| } |
| } |
| |
| void vc_status_update() { |
| vc_t* vc = NULL; |
| unsigned i = 0; |
| int x = 0; |
| |
| int w = g_status_width / (g_vc_count + 1); |
| if (w < MIN_TAB_WIDTH) { |
| w = MIN_TAB_WIDTH; |
| } else if (w > MAX_TAB_WIDTH) { |
| w = MAX_TAB_WIDTH; |
| } |
| |
| char tmp[w]; |
| |
| vc_status_clear(); |
| list_for_every_entry (&g_vc_list, vc, vc_t, node) { |
| unsigned fg; |
| if (vc->active) { |
| fg = STATUS_COLOR_ACTIVE; |
| } else if (vc->flags & VC_FLAG_HASOUTPUT) { |
| fg = STATUS_COLOR_UPDATED; |
| } else { |
| fg = STATUS_COLOR_DEFAULT; |
| } |
| |
| int lines = vc_get_scrollback_lines(vc); |
| char L = (lines > 0) && (-vc->viewport_y < lines) ? '<' : '['; |
| char R = (vc->viewport_y < 0) ? '>' : ']'; |
| |
| snprintf(tmp, w, "%c%u%c %s", L, i, R, vc->title); |
| vc_status_write(x, fg, tmp); |
| x += w; |
| i++; |
| } |
| vc_status_commit(); |
| } |
| |
| void handle_key_press(uint8_t keycode, int modifiers) { |
| // Handle vc-level control keys |
| if (vc_handle_device_control_keys(keycode, modifiers)) |
| return; |
| |
| // Handle other keys only if we own the display |
| if (!g_vc_owns_display) |
| return; |
| |
| // Handle other control keys |
| if (vc_handle_control_keys(keycode, modifiers)) |
| return; |
| |
| vc_t* vc = g_active_vc; |
| char output[4]; |
| uint32_t length = hid_key_to_vt100_code( |
| keycode, modifiers, vc->keymap, output, sizeof(output)); |
| if (length > 0) { |
| if (vc->fd >= 0) { |
| write(vc->fd, output, length); |
| } |
| vc_scroll_viewport_bottom(vc); |
| } |
| } |
| |
| ssize_t vc_write(vc_t* vc, const void* buf, size_t count, zx_off_t off) { |
| vc->invy0 = vc_rows(vc) + 1; |
| vc->invy1 = -1; |
| const uint8_t* str = (const uint8_t*)buf; |
| for (size_t i = 0; i < count; i++) { |
| vc->textcon.putc(&vc->textcon, str[i]); |
| } |
| vc_flush(vc); |
| if (!(vc->flags & VC_FLAG_HASOUTPUT) && !vc->active) { |
| vc->flags |= VC_FLAG_HASOUTPUT; |
| vc_status_update(); |
| } |
| return count; |
| } |
| |
| // Create a new vc_t and add it to the console list. |
| zx_status_t vc_create(vc_t** vc_out, bool special) { |
| zx_status_t status; |
| vc_t* vc; |
| if ((status = vc_alloc(&vc, special)) < 0) { |
| return status; |
| } |
| |
| // add to the vc list |
| list_add_tail(&g_vc_list, &vc->node); |
| g_vc_count++; |
| |
| // make this the active vc if it's the first one |
| if (!g_active_vc) { |
| vc_set_active(-1, vc); |
| } else { |
| vc_render(g_active_vc); |
| } |
| |
| *vc_out = vc; |
| return ZX_OK; |
| } |
| |
| void vc_destroy(vc_t* vc) { |
| list_delete(&vc->node); |
| g_vc_count -= 1; |
| |
| if (vc->active) { |
| g_active_vc = NULL; |
| if (g_active_vc_index >= g_vc_count) { |
| g_active_vc_index = g_vc_count - 1; |
| } |
| vc_set_active(g_active_vc_index, NULL); |
| } else if (g_active_vc) { |
| vc_full_repaint(g_active_vc); |
| vc_render(g_active_vc); |
| } |
| |
| vc_free(vc); |
| } |