blob: 0616ff35b780686369f7fb25c45464098d7837ea [file] [log] [blame]
// 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);
}