/*
* Copyright (C) 2006 Ondrej Palkovsky
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @addtogroup console
* @{
*/
/** @file
*/
#include <ipc/fb.h>
#include <ipc/ipc.h>
#include <async.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <align.h>
#include "console.h"
#include "gcons.h"
#define CONSOLE_TOP 66
#define CONSOLE_MARGIN 6
#define STATUS_START 110
#define STATUS_TOP 8
#define STATUS_SPACE 4
#define STATUS_WIDTH 48
#define STATUS_HEIGHT 48
#define MAIN_COLOR 0xffffff
static int use_gcons = 0;
static ipcarg_t xres,yres;
enum butstate {
CONS_DISCONNECTED = 0,
CONS_SELECTED,
CONS_IDLE,
CONS_HAS_DATA,
CONS_KERNEL,
CONS_DISCONNECTED_SEL,
CONS_LAST
};
static int console_vp;
static int cstatus_vp[CONSOLE_COUNT];
static enum butstate console_state[CONSOLE_COUNT];
static int fbphone;
/** List of pixmaps identifying these icons */
static int ic_pixmaps[CONS_LAST] = {-1, -1, -1, -1, -1, -1};
static int animation = -1;
static int active_console = 0;
static void vp_switch(int vp)
{
async_msg(fbphone,FB_VIEWPORT_SWITCH, vp);
}
/** Create view port */
static int vp_create(unsigned int x, unsigned int y,
unsigned int width, unsigned int height)
{
return async_req_2(fbphone, FB_VIEWPORT_CREATE,
(x << 16) | y, (width << 16) | height, NULL, NULL);
}
static void clear(void)
{
async_msg(fbphone, FB_CLEAR, 0);
}
static void set_style(int fgcolor, int bgcolor)
{
async_msg_2(fbphone, FB_SET_STYLE, fgcolor, bgcolor);
}
/** Transparent putchar */
static void tran_putch(char c, int row, int col)
{
async_msg_3(fbphone, FB_TRANS_PUTCHAR, c, row, col);
}
/** Redraw the button showing state of a given console */
static void redraw_state(int consnum)
{
char data[5];
int i;
enum butstate state = console_state[consnum];
vp_switch(cstatus_vp[consnum]);
if (ic_pixmaps[state] != -1)
async_msg_2(fbphone, FB_VP_DRAW_PIXMAP, cstatus_vp[consnum],
ic_pixmaps[state]);
if (state != CONS_DISCONNECTED && state != CONS_KERNEL &&
state != CONS_DISCONNECTED_SEL) {
for (i = 0; data[i]; i++)
tran_putch(data[i], 1, 2 + i);
}
}
/** Notification run on changing console (except kernel console) */
void gcons_change_console(int consnum)
{
int i;
if (!use_gcons)
return;
if (active_console == KERNEL_CONSOLE) {
for (i = 0; i < CONSOLE_COUNT; i++)
redraw_state(i);
if (animation != -1)
async_msg(fbphone, FB_ANIM_START, animation);
} else {
if (console_state[active_console] == CONS_DISCONNECTED_SEL)
console_state[active_console] = CONS_DISCONNECTED;
else
console_state[active_console] = CONS_IDLE;
redraw_state(active_console);
}
active_console = consnum;
if (console_state[consnum] == CONS_DISCONNECTED) {
console_state[consnum] = CONS_DISCONNECTED_SEL;
redraw_state(consnum);
} else
console_state[consnum] = CONS_SELECTED;
redraw_state(consnum);
vp_switch(console_vp);
}
/** Notification function that gets called on new output to virtual console */
void gcons_notify_char(int consnum)
{
if (!use_gcons)
return;
if (consnum == active_console ||
console_state[consnum] == CONS_HAS_DATA)
return;
console_state[consnum] = CONS_HAS_DATA;
if (active_console == KERNEL_CONSOLE)
return;
redraw_state(consnum);
vp_switch(console_vp);
}
/** Notification function called on service disconnect from console */
void gcons_notify_disconnect(int consnum)
{
if (!use_gcons)
return;
if (active_console == consnum)
console_state[consnum] = CONS_DISCONNECTED_SEL;
else
console_state[consnum] = CONS_DISCONNECTED;
if (active_console == KERNEL_CONSOLE)
return;
redraw_state(consnum);
vp_switch(console_vp);
}
/** Notification function called on console connect */
void gcons_notify_connect(int consnum)
{
if (!use_gcons)
return;
if (active_console == consnum)
console_state[consnum] = CONS_SELECTED;
else
console_state[consnum] = CONS_IDLE;
if (active_console == KERNEL_CONSOLE)
return;
redraw_state(consnum);
vp_switch(console_vp);
}
/** Change to kernel console */
void gcons_in_kernel(void)
{
if (console_state[active_console] == CONS_DISCONNECTED_SEL)
console_state[active_console] = CONS_DISCONNECTED;
else
console_state[active_console] = CONS_IDLE;
redraw_state(active_console);
if (animation != -1)
async_msg(fbphone, FB_ANIM_STOP, animation);
active_console = KERNEL_CONSOLE; /* Set to kernel console */
vp_switch(0);
}
/** Return x, where left <= x <= right && |a-x|==min(|a-x|) is smallest */
static inline int limit(int a,int left, int right)
{
if (a < left)
a = left;
if (a >= right)
a = right - 1;
return a;
}
int mouse_x, mouse_y;
int btn_pressed, btn_x, btn_y;
/** Handle mouse move
*
* @param dx Delta X of mouse move
* @param dy Delta Y of mouse move
*/
void gcons_mouse_move(int dx, int dy)
{
mouse_x = limit(mouse_x+dx, 0, xres);
mouse_y = limit(mouse_y+dy, 0, yres);
async_msg_2(fbphone, FB_POINTER_MOVE, mouse_x, mouse_y);
}
static int gcons_find_conbut(int x, int y)
{
int status_start = STATUS_START + (xres - 800) / 2;;
if (y < STATUS_TOP || y >= STATUS_TOP + STATUS_HEIGHT)
return -1;
if (x < status_start)
return -1;
if (x >= status_start + (STATUS_WIDTH + STATUS_SPACE) * CONSOLE_COUNT)
return -1;
if (((x - status_start) % (STATUS_WIDTH+STATUS_SPACE)) < STATUS_SPACE)
return -1;
return (x - status_start) / (STATUS_WIDTH+STATUS_SPACE);
}
/** Handle mouse click
*
* @param state New state (1-pressed, 0-depressed)
*/
int gcons_mouse_btn(int state)
{
int conbut;
if (state) {
conbut = gcons_find_conbut(mouse_x, mouse_y);
if (conbut != -1) {
btn_pressed = 1;
btn_x = mouse_x;
btn_y = mouse_y;
}
return -1;
}
if (!state && !btn_pressed)
return -1;
btn_pressed = 0;
conbut = gcons_find_conbut(mouse_x, mouse_y);
if (conbut == gcons_find_conbut(btn_x, btn_y))
return conbut;
return -1;
}
/** Draw a PPM pixmap to framebuffer
*
* @param logo Pointer to PPM data
* @param size Size of PPM data
* @param x Coordinate of upper left corner
* @param y Coordinate of upper left corner
*/
static void draw_pixmap(char *logo, size_t size, int x, int y)
{
char *shm;
int rc;
/* Create area */
shm = mmap(NULL, size, PROTO_READ | PROTO_WRITE, MAP_SHARED |
MAP_ANONYMOUS, 0, 0);
if (shm == MAP_FAILED)
return;
/* Send area */
rc = async_req_2(fbphone, FB_PREPARE_SHM, (ipcarg_t) shm, 0, NULL,
NULL);
if (rc)
rc = async_req_3(fbphone, IPC_M_AS_AREA_SEND, (ipcarg_t) shm, 0,
PROTO_READ, NULL, NULL, NULL);
if (rc)
goto drop;
/* Draw logo */
async_msg_2(fbphone, FB_DRAW_PPM, x, y);
drop:
/* Drop area */
async_msg(fbphone, FB_DROP_SHM, 0);
/* Remove area */
munmap(shm, size);
}
extern char _binary_helenos_ppm_start[0];
extern int _binary_helenos_ppm_size;
extern char _binary_nameic_ppm_start[0];
extern int _binary_nameic_ppm_size;
/** Redraws console graphics */
static void gcons_redraw_console(void)
{
int i;
if (!use_gcons)
return;
vp_switch(0);
set_style(MAIN_COLOR, MAIN_COLOR);
clear();
draw_pixmap(_binary_helenos_ppm_start,
(size_t) &_binary_helenos_ppm_size, xres - 66, 2);
draw_pixmap(_binary_nameic_ppm_start,
(size_t) &_binary_nameic_ppm_size, 5, 17);
for (i = 0;i < CONSOLE_COUNT; i++)
redraw_state(i);
vp_switch(console_vp);
}
/** Creates a pixmap on framebuffer
*
* @param data PPM data
* @param size PPM data size
* @return Pixmap identification
*/
static int make_pixmap(char *data, int size)
{
char *shm;
int rc;
int pxid = -1;
/* Create area */
shm = mmap(NULL, size, PROTO_READ | PROTO_WRITE, MAP_SHARED |
MAP_ANONYMOUS, 0, 0);
if (shm == MAP_FAILED)
return -1;
/* Send area */
rc = async_req_2(fbphone, FB_PREPARE_SHM, (ipcarg_t) shm, 0, NULL,
NULL);
if (rc)
rc = async_req_3(fbphone, IPC_M_AS_AREA_SEND, (ipcarg_t) shm, 0,
PROTO_READ, NULL, NULL, NULL);
if (rc)
goto drop;
/* Obtain pixmap */
rc = async_req(fbphone, FB_SHM2PIXMAP, 0, NULL);
if (rc < 0)
goto drop;
pxid = rc;
drop:
/* Drop area */
async_msg(fbphone, FB_DROP_SHM, 0);
/* Remove area */
munmap(shm, size);
return pxid;
}
extern char _binary_anim_1_ppm_start[0];
extern int _binary_anim_1_ppm_size;
extern char _binary_anim_2_ppm_start[0];
extern int _binary_anim_2_ppm_size;
extern char _binary_anim_3_ppm_start[0];
extern int _binary_anim_3_ppm_size;
extern char _binary_anim_4_ppm_start[0];
extern int _binary_anim_4_ppm_size;
static void make_anim(void)
{
int an;
int pm;
an = async_req(fbphone, FB_ANIM_CREATE, cstatus_vp[KERNEL_CONSOLE],
NULL);
if (an < 0)
return;
pm = make_pixmap(_binary_anim_1_ppm_start,
(int) &_binary_anim_1_ppm_size);
async_msg_2(fbphone, FB_ANIM_ADDPIXMAP, an, pm);
pm = make_pixmap(_binary_anim_2_ppm_start,
(int) &_binary_anim_2_ppm_size);
async_msg_2(fbphone, FB_ANIM_ADDPIXMAP, an, pm);
pm = make_pixmap(_binary_anim_3_ppm_start,
(int) &_binary_anim_3_ppm_size);
async_msg_2(fbphone, FB_ANIM_ADDPIXMAP, an, pm);
pm = make_pixmap(_binary_anim_4_ppm_start,
(int) &_binary_anim_4_ppm_size);
async_msg_2(fbphone, FB_ANIM_ADDPIXMAP, an, pm);
async_msg(fbphone, FB_ANIM_START, an);
animation = an;
}
extern char _binary_cons_selected_ppm_start[0];
extern int _binary_cons_selected_ppm_size;
extern char _binary_cons_idle_ppm_start[0];
extern int _binary_cons_idle_ppm_size;
extern char _binary_cons_has_data_ppm_start[0];
extern int _binary_cons_has_data_ppm_size;
extern char _binary_cons_kernel_ppm_start[0];
extern int _binary_cons_kernel_ppm_size;
/** Initialize nice graphical console environment */
void gcons_init(int phone)
{
int rc;
int i;
int status_start = STATUS_START;
fbphone = phone;
rc = async_req_2(phone, FB_GET_RESOLUTION, 0, 0, &xres, &yres);
if (rc)
return;
if (xres < 800 || yres < 600)
return;
/* create console viewport */
/* Align width & height to character size */
console_vp = vp_create(CONSOLE_MARGIN, CONSOLE_TOP,
ALIGN_DOWN(xres - 2 * CONSOLE_MARGIN, 8),
ALIGN_DOWN(yres - (CONSOLE_TOP + CONSOLE_MARGIN), 16));
if (console_vp < 0)
return;
/* Create status buttons */
status_start += (xres - 800) / 2;
for (i = 0; i < CONSOLE_COUNT; i++) {
cstatus_vp[i] = vp_create(status_start + CONSOLE_MARGIN +
i * (STATUS_WIDTH + STATUS_SPACE), STATUS_TOP,
STATUS_WIDTH, STATUS_HEIGHT);
if (cstatus_vp[i] < 0)
return;
vp_switch(cstatus_vp[i]);
set_style(0x202020, 0xffffff);
}
/* Initialize icons */
ic_pixmaps[CONS_SELECTED] =
make_pixmap(_binary_cons_selected_ppm_start,
(int) &_binary_cons_selected_ppm_size);
ic_pixmaps[CONS_IDLE] = make_pixmap(_binary_cons_idle_ppm_start,
(int) &_binary_cons_idle_ppm_size);
ic_pixmaps[CONS_HAS_DATA] =
make_pixmap(_binary_cons_has_data_ppm_start,
(int) &_binary_cons_has_data_ppm_size);
ic_pixmaps[CONS_DISCONNECTED] =
make_pixmap(_binary_cons_idle_ppm_start,
(int) &_binary_cons_idle_ppm_size);
ic_pixmaps[CONS_KERNEL] = make_pixmap(_binary_cons_kernel_ppm_start,
(int) &_binary_cons_kernel_ppm_size);
ic_pixmaps[CONS_DISCONNECTED_SEL] = ic_pixmaps[CONS_SELECTED];
make_anim();
use_gcons = 1;
console_state[0] = CONS_DISCONNECTED_SEL;
console_state[KERNEL_CONSOLE] = CONS_KERNEL;
gcons_redraw_console();
}
/** @}
*/