/* * Copyright 2008, 2014 Vincent Sanders * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "desktop/browser_history.h" #include "desktop/gui.h" #include "desktop/mouse.h" #include "desktop/plotters.h" #include "desktop/netsurf.h" #include "utils/nsoption.h" #include "utils/filepath.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/types.h" #include "desktop/textinput.h" #include "render/form.h" #include "framebuffer/gui.h" #include "framebuffer/fbtk.h" #include "framebuffer/framebuffer.h" #include "framebuffer/schedule.h" #include "framebuffer/findfile.h" #include "framebuffer/image_data.h" #include "framebuffer/font.h" #include "framebuffer/clipboard.h" #include "framebuffer/fetch.h" #include "content/urldb.h" #include "content/fetch.h" #define NSFB_TOOLBAR_DEFAULT_LAYOUT "blfsrutc" fbtk_widget_t *fbtk; struct gui_window *input_window = NULL; struct gui_window *search_current_window; struct gui_window *window_list = NULL; /* private data for browser user widget */ struct browser_widget_s { struct browser_window *bw; /**< The browser window connected to this gui window */ int scrollx, scrolly; /**< scroll offsets. */ /* Pending window redraw state. */ bool redraw_required; /**< flag indicating the foreground loop * needs to redraw the browser widget. */ bbox_t redraw_box; /**< Area requiring redraw. */ bool pan_required; /**< flag indicating the foreground loop * needs to pan the window. */ int panx, pany; /**< Panning required. */ }; static struct gui_drag { enum state { GUI_DRAG_NONE, GUI_DRAG_PRESSED, GUI_DRAG_DRAG } state; int button; int x; int y; bool grabbed_pointer; } gui_drag; /* queue a redraw operation, co-ordinates are relative to the window */ static void fb_queue_redraw(struct fbtk_widget_s *widget, int x0, int y0, int x1, int y1) { struct browser_widget_s *bwidget = fbtk_get_userpw(widget); bwidget->redraw_box.x0 = min(bwidget->redraw_box.x0, x0); bwidget->redraw_box.y0 = min(bwidget->redraw_box.y0, y0); bwidget->redraw_box.x1 = max(bwidget->redraw_box.x1, x1); bwidget->redraw_box.y1 = max(bwidget->redraw_box.y1, y1); if (fbtk_clip_to_widget(widget, &bwidget->redraw_box)) { bwidget->redraw_required = true; fbtk_request_redraw(widget); } else { bwidget->redraw_box.y0 = bwidget->redraw_box.x0 = INT_MAX; bwidget->redraw_box.y1 = bwidget->redraw_box.x1 = -(INT_MAX); bwidget->redraw_required = false; } } /* queue a window scroll */ static void widget_scroll_y(struct gui_window *gw, int y, bool abs) { struct browser_widget_s *bwidget = fbtk_get_userpw(gw->browser); int content_width, content_height; int height; LOG(("window scroll")); if (abs) { bwidget->pany = y - bwidget->scrolly; } else { bwidget->pany += y; } browser_window_get_extents(gw->bw, true, &content_width, &content_height); height = fbtk_get_height(gw->browser); /* dont pan off the top */ if ((bwidget->scrolly + bwidget->pany) < 0) bwidget->pany = -bwidget->scrolly; /* do not pan off the bottom of the content */ if ((bwidget->scrolly + bwidget->pany) > (content_height - height)) bwidget->pany = (content_height - height) - bwidget->scrolly; if (bwidget->pany == 0) return; bwidget->pan_required = true; fbtk_request_redraw(gw->browser); fbtk_set_scroll_position(gw->vscroll, bwidget->scrolly + bwidget->pany); } /* queue a window scroll */ static void widget_scroll_x(struct gui_window *gw, int x, bool abs) { struct browser_widget_s *bwidget = fbtk_get_userpw(gw->browser); int content_width, content_height; int width; if (abs) { bwidget->panx = x - bwidget->scrollx; } else { bwidget->panx += x; } browser_window_get_extents(gw->bw, true, &content_width, &content_height); width = fbtk_get_width(gw->browser); /* dont pan off the left */ if ((bwidget->scrollx + bwidget->panx) < 0) bwidget->panx = - bwidget->scrollx; /* do not pan off the right of the content */ if ((bwidget->scrollx + bwidget->panx) > (content_width - width)) bwidget->panx = (content_width - width) - bwidget->scrollx; if (bwidget->panx == 0) return; bwidget->pan_required = true; fbtk_request_redraw(gw->browser); fbtk_set_scroll_position(gw->hscroll, bwidget->scrollx + bwidget->panx); } static void fb_pan(fbtk_widget_t *widget, struct browser_widget_s *bwidget, struct browser_window *bw) { int x; int y; int width; int height; nsfb_bbox_t srcbox; nsfb_bbox_t dstbox; nsfb_t *nsfb = fbtk_get_nsfb(widget); height = fbtk_get_height(widget); width = fbtk_get_width(widget); LOG(("panning %d, %d", bwidget->panx, bwidget->pany)); x = fbtk_get_absx(widget); y = fbtk_get_absy(widget); /* if the pan exceeds the viewport size just redraw the whole area */ if (bwidget->pany >= height || bwidget->pany <= -height || bwidget->panx >= width || bwidget->panx <= -width) { bwidget->scrolly += bwidget->pany; bwidget->scrollx += bwidget->panx; fb_queue_redraw(widget, 0, 0, width, height); /* ensure we don't try to scroll again */ bwidget->panx = 0; bwidget->pany = 0; bwidget->pan_required = false; return; } if (bwidget->pany < 0) { /* pan up by less then viewport height */ srcbox.x0 = x; srcbox.y0 = y; srcbox.x1 = srcbox.x0 + width; srcbox.y1 = srcbox.y0 + height + bwidget->pany; dstbox.x0 = x; dstbox.y0 = y - bwidget->pany; dstbox.x1 = dstbox.x0 + width; dstbox.y1 = dstbox.y0 + height + bwidget->pany; /* move part that remains visible up */ nsfb_plot_copy(nsfb, &srcbox, nsfb, &dstbox); /* redraw newly exposed area */ bwidget->scrolly += bwidget->pany; fb_queue_redraw(widget, 0, 0, width, - bwidget->pany); } else if (bwidget->pany > 0) { /* pan down by less then viewport height */ srcbox.x0 = x; srcbox.y0 = y + bwidget->pany; srcbox.x1 = srcbox.x0 + width; srcbox.y1 = srcbox.y0 + height - bwidget->pany; dstbox.x0 = x; dstbox.y0 = y; dstbox.x1 = dstbox.x0 + width; dstbox.y1 = dstbox.y0 + height - bwidget->pany; /* move part that remains visible down */ nsfb_plot_copy(nsfb, &srcbox, nsfb, &dstbox); /* redraw newly exposed area */ bwidget->scrolly += bwidget->pany; fb_queue_redraw(widget, 0, height - bwidget->pany, width, height); } if (bwidget->panx < 0) { /* pan left by less then viewport width */ srcbox.x0 = x; srcbox.y0 = y; srcbox.x1 = srcbox.x0 + width + bwidget->panx; srcbox.y1 = srcbox.y0 + height; dstbox.x0 = x - bwidget->panx; dstbox.y0 = y; dstbox.x1 = dstbox.x0 + width + bwidget->panx; dstbox.y1 = dstbox.y0 + height; /* move part that remains visible left */ nsfb_plot_copy(nsfb, &srcbox, nsfb, &dstbox); /* redraw newly exposed area */ bwidget->scrollx += bwidget->panx; fb_queue_redraw(widget, 0, 0, -bwidget->panx, height); } else if (bwidget->panx > 0) { /* pan right by less then viewport width */ srcbox.x0 = x + bwidget->panx; srcbox.y0 = y; srcbox.x1 = srcbox.x0 + width - bwidget->panx; srcbox.y1 = srcbox.y0 + height; dstbox.x0 = x; dstbox.y0 = y; dstbox.x1 = dstbox.x0 + width - bwidget->panx; dstbox.y1 = dstbox.y0 + height; /* move part that remains visible right */ nsfb_plot_copy(nsfb, &srcbox, nsfb, &dstbox); /* redraw newly exposed area */ bwidget->scrollx += bwidget->panx; fb_queue_redraw(widget, width - bwidget->panx, 0, width, height); } bwidget->pan_required = false; bwidget->panx = 0; bwidget->pany = 0; } static void fb_redraw(fbtk_widget_t *widget, struct browser_widget_s *bwidget, struct browser_window *bw) { int x; int y; int caret_x, caret_y, caret_h; struct rect clip; struct redraw_context ctx = { .interactive = true, .background_images = true, .plot = &fb_plotters }; nsfb_t *nsfb = fbtk_get_nsfb(widget); float scale = browser_window_get_scale(bw); LOG(("%d,%d to %d,%d", bwidget->redraw_box.x0, bwidget->redraw_box.y0, bwidget->redraw_box.x1, bwidget->redraw_box.y1)); x = fbtk_get_absx(widget); y = fbtk_get_absy(widget); /* adjust clipping co-ordinates according to window location */ bwidget->redraw_box.y0 += y; bwidget->redraw_box.y1 += y; bwidget->redraw_box.x0 += x; bwidget->redraw_box.x1 += x; nsfb_claim(nsfb, &bwidget->redraw_box); /* redraw bounding box is relative to window */ clip.x0 = bwidget->redraw_box.x0; clip.y0 = bwidget->redraw_box.y0; clip.x1 = bwidget->redraw_box.x1; clip.y1 = bwidget->redraw_box.y1; browser_window_redraw(bw, (x - bwidget->scrollx) / scale, (y - bwidget->scrolly) / scale, &clip, &ctx); if (fbtk_get_caret(widget, &caret_x, &caret_y, &caret_h)) { /* This widget has caret, so render it */ nsfb_bbox_t line; nsfb_plot_pen_t pen; line.x0 = x - bwidget->scrollx + caret_x; line.y0 = y - bwidget->scrolly + caret_y; line.x1 = x - bwidget->scrollx + caret_x; line.y1 = y - bwidget->scrolly + caret_y + caret_h; pen.stroke_type = NFSB_PLOT_OPTYPE_SOLID; pen.stroke_width = 1; pen.stroke_colour = 0xFF0000FF; nsfb_plot_line(nsfb, &line, &pen); } nsfb_update(fbtk_get_nsfb(widget), &bwidget->redraw_box); bwidget->redraw_box.y0 = bwidget->redraw_box.x0 = INT_MAX; bwidget->redraw_box.y1 = bwidget->redraw_box.x1 = INT_MIN; bwidget->redraw_required = false; } static int fb_browser_window_redraw(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; struct browser_widget_s *bwidget; bwidget = fbtk_get_userpw(widget); if (bwidget == NULL) { LOG(("browser widget from widget %p was null", widget)); return -1; } if (bwidget->pan_required) { fb_pan(widget, bwidget, gw->bw); } if (bwidget->redraw_required) { fb_redraw(widget, bwidget, gw->bw); } else { bwidget->redraw_box.x0 = 0; bwidget->redraw_box.y0 = 0; bwidget->redraw_box.x1 = fbtk_get_width(widget); bwidget->redraw_box.y1 = fbtk_get_height(widget); fb_redraw(widget, bwidget, gw->bw); } return 0; } static int fb_browser_window_destroy(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct browser_widget_s *browser_widget; if (widget == NULL) { return 0; } /* Free private data */ browser_widget = fbtk_get_userpw(widget); free(browser_widget); return 0; } static const char *fename; static int febpp; static int fewidth; static int feheight; static const char *feurl; static bool process_cmdline(int argc, char** argv) { int opt; LOG(("argc %d, argv %p", argc, argv)); fename = "sdl"; febpp = 32; fewidth = nsoption_int(window_width); if (fewidth <= 0) { fewidth = 800; } feheight = nsoption_int(window_height); if (feheight <= 0) { feheight = 600; } if ((nsoption_charp(homepage_url) != NULL) && (nsoption_charp(homepage_url)[0] != '\0')) { feurl = nsoption_charp(homepage_url); } else { feurl = NETSURF_HOMEPAGE; } while((opt = getopt(argc, argv, "f:b:w:h:")) != -1) { switch (opt) { case 'f': fename = optarg; break; case 'b': febpp = atoi(optarg); break; case 'w': fewidth = atoi(optarg); break; case 'h': feheight = atoi(optarg); break; default: fprintf(stderr, "Usage: %s [-f frontend] [-b bpp] url\n", argv[0]); return false; } } if (optind < argc) { feurl = argv[optind]; } return true; } /** * Set option defaults for framebuffer frontend * * @param defaults The option table to update. * @return error status. */ static nserror set_defaults(struct nsoption_s *defaults) { /* Set defaults for absent option strings */ nsoption_setnull_charp(cookie_file, strdup("~/.netsurf/Cookies")); nsoption_setnull_charp(cookie_jar, strdup("~/.netsurf/Cookies")); if (nsoption_charp(cookie_file) == NULL || nsoption_charp(cookie_jar) == NULL) { LOG(("Failed initialising cookie options")); return NSERROR_BAD_PARAMETER; } /* set system colours for framebuffer ui */ nsoption_set_colour(sys_colour_ActiveBorder, 0x00000000); nsoption_set_colour(sys_colour_ActiveCaption, 0x00ddddcc); nsoption_set_colour(sys_colour_AppWorkspace, 0x00eeeeee); nsoption_set_colour(sys_colour_Background, 0x00aa0000); nsoption_set_colour(sys_colour_ButtonFace, 0x00dddddd); nsoption_set_colour(sys_colour_ButtonHighlight, 0x00cccccc); nsoption_set_colour(sys_colour_ButtonShadow, 0x00bbbbbb); nsoption_set_colour(sys_colour_ButtonText, 0x00000000); nsoption_set_colour(sys_colour_CaptionText, 0x00000000); nsoption_set_colour(sys_colour_GrayText, 0x00777777); nsoption_set_colour(sys_colour_Highlight, 0x00ee0000); nsoption_set_colour(sys_colour_HighlightText, 0x00000000); nsoption_set_colour(sys_colour_InactiveBorder, 0x00000000); nsoption_set_colour(sys_colour_InactiveCaption, 0x00ffffff); nsoption_set_colour(sys_colour_InactiveCaptionText, 0x00cccccc); nsoption_set_colour(sys_colour_InfoBackground, 0x00aaaaaa); nsoption_set_colour(sys_colour_InfoText, 0x00000000); nsoption_set_colour(sys_colour_Menu, 0x00aaaaaa); nsoption_set_colour(sys_colour_MenuText, 0x00000000); nsoption_set_colour(sys_colour_Scrollbar, 0x00aaaaaa); nsoption_set_colour(sys_colour_ThreeDDarkShadow, 0x00555555); nsoption_set_colour(sys_colour_ThreeDFace, 0x00dddddd); nsoption_set_colour(sys_colour_ThreeDHighlight, 0x00aaaaaa); nsoption_set_colour(sys_colour_ThreeDLightShadow, 0x00999999); nsoption_set_colour(sys_colour_ThreeDShadow, 0x00777777); nsoption_set_colour(sys_colour_Window, 0x00aaaaaa); nsoption_set_colour(sys_colour_WindowFrame, 0x00000000); nsoption_set_colour(sys_colour_WindowText, 0x00000000); return NSERROR_OK; } /** * Ensures output logging stream is correctly configured */ static bool nslog_stream_configure(FILE *fptr) { /* set log stream to be non-buffering */ setbuf(fptr, NULL); return true; } static void framebuffer_poll(bool active) { nsfb_event_t event; int timeout; /* timeout in miliseconds */ /* run the scheduler and discover how long to wait for the next event */ timeout = schedule_run(); /* if redraws are pending do not wait for event, return immediately */ if (fbtk_get_redraw_pending(fbtk)) timeout = 0; if (fbtk_event(fbtk, &event, timeout)) { if ((event.type == NSFB_EVENT_CONTROL) && (event.value.controlcode == NSFB_CONTROL_QUIT)) netsurf_quit = true; } fbtk_redraw(fbtk); } static void gui_quit(void) { LOG(("gui_quit")); urldb_save_cookies(nsoption_charp(cookie_jar)); framebuffer_finalise(); } /* called back when click in browser window */ static int fb_browser_window_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; struct browser_widget_s *bwidget = fbtk_get_userpw(widget); browser_mouse_state mouse; float scale = browser_window_get_scale(gw->bw); int x = (cbi->x + bwidget->scrollx) / scale; int y = (cbi->y + bwidget->scrolly) / scale; unsigned int time_now; static struct { enum { CLICK_SINGLE, CLICK_DOUBLE, CLICK_TRIPLE } type; unsigned int time; } last_click; if (cbi->event->type != NSFB_EVENT_KEY_DOWN && cbi->event->type != NSFB_EVENT_KEY_UP) return 0; LOG(("browser window clicked at %d,%d", cbi->x, cbi->y)); switch (cbi->event->type) { case NSFB_EVENT_KEY_DOWN: switch (cbi->event->value.keycode) { case NSFB_KEY_MOUSE_1: browser_window_mouse_click(gw->bw, BROWSER_MOUSE_PRESS_1, x, y); gui_drag.state = GUI_DRAG_PRESSED; gui_drag.button = 1; gui_drag.x = x; gui_drag.y = y; break; case NSFB_KEY_MOUSE_3: browser_window_mouse_click(gw->bw, BROWSER_MOUSE_PRESS_2, x, y); gui_drag.state = GUI_DRAG_PRESSED; gui_drag.button = 2; gui_drag.x = x; gui_drag.y = y; break; case NSFB_KEY_MOUSE_4: /* scroll up */ if (browser_window_scroll_at_point(gw->bw, x, y, 0, -100) == false) widget_scroll_y(gw, -100, false); break; case NSFB_KEY_MOUSE_5: /* scroll down */ if (browser_window_scroll_at_point(gw->bw, x, y, 0, 100) == false) widget_scroll_y(gw, 100, false); break; default: break; } break; case NSFB_EVENT_KEY_UP: mouse = 0; time_now = wallclock(); switch (cbi->event->value.keycode) { case NSFB_KEY_MOUSE_1: if (gui_drag.state == GUI_DRAG_DRAG) { /* End of a drag, rather than click */ if (gui_drag.grabbed_pointer) { /* need to ungrab pointer */ fbtk_tgrab_pointer(widget); gui_drag.grabbed_pointer = false; } gui_drag.state = GUI_DRAG_NONE; /* Tell core */ browser_window_mouse_track(gw->bw, 0, x, y); break; } /* This is a click; * clear PRESSED state and pass to core */ gui_drag.state = GUI_DRAG_NONE; mouse = BROWSER_MOUSE_CLICK_1; break; case NSFB_KEY_MOUSE_3: if (gui_drag.state == GUI_DRAG_DRAG) { /* End of a drag, rather than click */ gui_drag.state = GUI_DRAG_NONE; if (gui_drag.grabbed_pointer) { /* need to ungrab pointer */ fbtk_tgrab_pointer(widget); gui_drag.grabbed_pointer = false; } /* Tell core */ browser_window_mouse_track(gw->bw, 0, x, y); break; } /* This is a click; * clear PRESSED state and pass to core */ gui_drag.state = GUI_DRAG_NONE; mouse = BROWSER_MOUSE_CLICK_2; break; default: break; } /* Determine if it's a double or triple click, allowing * 0.5 seconds (50cs) between clicks */ if (time_now < last_click.time + 50 && cbi->event->value.keycode != NSFB_KEY_MOUSE_4 && cbi->event->value.keycode != NSFB_KEY_MOUSE_5) { if (last_click.type == CLICK_SINGLE) { /* Set double click */ mouse |= BROWSER_MOUSE_DOUBLE_CLICK; last_click.type = CLICK_DOUBLE; } else if (last_click.type == CLICK_DOUBLE) { /* Set triple click */ mouse |= BROWSER_MOUSE_TRIPLE_CLICK; last_click.type = CLICK_TRIPLE; } else { /* Set normal click */ last_click.type = CLICK_SINGLE; } } else { last_click.type = CLICK_SINGLE; } if (mouse) browser_window_mouse_click(gw->bw, mouse, x, y); last_click.time = time_now; break; default: break; } return 1; } /* called back when movement in browser window */ static int fb_browser_window_move(fbtk_widget_t *widget, fbtk_callback_info *cbi) { browser_mouse_state mouse = 0; struct gui_window *gw = cbi->context; struct browser_widget_s *bwidget = fbtk_get_userpw(widget); float scale = browser_window_get_scale(gw->bw); int x = (cbi->x + bwidget->scrollx) / scale; int y = (cbi->y + bwidget->scrolly) / scale; if (gui_drag.state == GUI_DRAG_PRESSED && (abs(x - gui_drag.x) > 5 || abs(y - gui_drag.y) > 5)) { /* Drag started */ if (gui_drag.button == 1) { browser_window_mouse_click(gw->bw, BROWSER_MOUSE_DRAG_1, gui_drag.x, gui_drag.y); } else { browser_window_mouse_click(gw->bw, BROWSER_MOUSE_DRAG_2, gui_drag.x, gui_drag.y); } gui_drag.grabbed_pointer = fbtk_tgrab_pointer(widget); gui_drag.state = GUI_DRAG_DRAG; } if (gui_drag.state == GUI_DRAG_DRAG) { /* set up mouse state */ mouse |= BROWSER_MOUSE_DRAG_ON; if (gui_drag.button == 1) mouse |= BROWSER_MOUSE_HOLDING_1; else mouse |= BROWSER_MOUSE_HOLDING_2; } browser_window_mouse_track(gw->bw, mouse, x, y); return 0; } static int fb_browser_window_input(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; static fbtk_modifier_type modifier = FBTK_MOD_CLEAR; int ucs4 = -1; LOG(("got value %d", cbi->event->value.keycode)); switch (cbi->event->type) { case NSFB_EVENT_KEY_DOWN: switch (cbi->event->value.keycode) { case NSFB_KEY_DELETE: browser_window_key_press(gw->bw, KEY_DELETE_RIGHT); break; case NSFB_KEY_PAGEUP: if (browser_window_key_press(gw->bw, KEY_PAGE_UP) == false) widget_scroll_y(gw, -fbtk_get_height( gw->browser), false); break; case NSFB_KEY_PAGEDOWN: if (browser_window_key_press(gw->bw, KEY_PAGE_DOWN) == false) widget_scroll_y(gw, fbtk_get_height( gw->browser), false); break; case NSFB_KEY_RIGHT: if (modifier & FBTK_MOD_RCTRL || modifier & FBTK_MOD_LCTRL) { /* CTRL held */ if (browser_window_key_press(gw->bw, KEY_LINE_END) == false) widget_scroll_x(gw, INT_MAX, true); } else if (modifier & FBTK_MOD_RSHIFT || modifier & FBTK_MOD_LSHIFT) { /* SHIFT held */ if (browser_window_key_press(gw->bw, KEY_WORD_RIGHT) == false) widget_scroll_x(gw, fbtk_get_width( gw->browser), false); } else { /* no modifier */ if (browser_window_key_press(gw->bw, KEY_RIGHT) == false) widget_scroll_x(gw, 100, false); } break; case NSFB_KEY_LEFT: if (modifier & FBTK_MOD_RCTRL || modifier & FBTK_MOD_LCTRL) { /* CTRL held */ if (browser_window_key_press(gw->bw, KEY_LINE_START) == false) widget_scroll_x(gw, 0, true); } else if (modifier & FBTK_MOD_RSHIFT || modifier & FBTK_MOD_LSHIFT) { /* SHIFT held */ if (browser_window_key_press(gw->bw, KEY_WORD_LEFT) == false) widget_scroll_x(gw, -fbtk_get_width( gw->browser), false); } else { /* no modifier */ if (browser_window_key_press(gw->bw, KEY_LEFT) == false) widget_scroll_x(gw, -100, false); } break; case NSFB_KEY_UP: if (browser_window_key_press(gw->bw, KEY_UP) == false) widget_scroll_y(gw, -100, false); break; case NSFB_KEY_DOWN: if (browser_window_key_press(gw->bw, KEY_DOWN) == false) widget_scroll_y(gw, 100, false); break; case NSFB_KEY_RSHIFT: modifier |= FBTK_MOD_RSHIFT; break; case NSFB_KEY_LSHIFT: modifier |= FBTK_MOD_LSHIFT; break; case NSFB_KEY_RCTRL: modifier |= FBTK_MOD_RCTRL; break; case NSFB_KEY_LCTRL: modifier |= FBTK_MOD_LCTRL; break; case NSFB_KEY_y: case NSFB_KEY_z: if (cbi->event->value.keycode == NSFB_KEY_z && (modifier & FBTK_MOD_RCTRL || modifier & FBTK_MOD_LCTRL) && (modifier & FBTK_MOD_RSHIFT || modifier & FBTK_MOD_LSHIFT)) { /* Z pressed with CTRL and SHIFT held */ browser_window_key_press(gw->bw, KEY_REDO); break; } else if (cbi->event->value.keycode == NSFB_KEY_z && (modifier & FBTK_MOD_RCTRL || modifier & FBTK_MOD_LCTRL)) { /* Z pressed with CTRL held */ browser_window_key_press(gw->bw, KEY_UNDO); break; } else if (cbi->event->value.keycode == NSFB_KEY_y && (modifier & FBTK_MOD_RCTRL || modifier & FBTK_MOD_LCTRL)) { /* Y pressed with CTRL held */ browser_window_key_press(gw->bw, KEY_REDO); break; } /* Z or Y pressed but not undo or redo; * Fall through to default handling */ default: ucs4 = fbtk_keycode_to_ucs4(cbi->event->value.keycode, modifier); if (ucs4 != -1) browser_window_key_press(gw->bw, ucs4); break; } break; case NSFB_EVENT_KEY_UP: switch (cbi->event->value.keycode) { case NSFB_KEY_RSHIFT: modifier &= ~FBTK_MOD_RSHIFT; break; case NSFB_KEY_LSHIFT: modifier &= ~FBTK_MOD_LSHIFT; break; case NSFB_KEY_RCTRL: modifier &= ~FBTK_MOD_RCTRL; break; case NSFB_KEY_LCTRL: modifier &= ~FBTK_MOD_LCTRL; break; default: break; } break; default: break; } return 0; } static void fb_update_back_forward(struct gui_window *gw) { struct browser_window *bw = gw->bw; fbtk_set_bitmap(gw->back, (browser_window_back_available(bw)) ? &left_arrow : &left_arrow_g); fbtk_set_bitmap(gw->forward, (browser_window_forward_available(bw)) ? &right_arrow : &right_arrow_g); } /* left icon click routine */ static int fb_leftarrow_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; struct browser_window *bw = gw->bw; if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; if (browser_window_back_available(bw)) browser_window_history_back(bw, false); fb_update_back_forward(gw); return 1; } /* right arrow icon click routine */ static int fb_rightarrow_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; struct browser_window *bw = gw->bw; if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; if (browser_window_forward_available(bw)) browser_window_history_forward(bw, false); fb_update_back_forward(gw); return 1; } /* reload icon click routine */ static int fb_reload_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct browser_window *bw = cbi->context; if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; browser_window_reload(bw, true); return 1; } /* stop icon click routine */ static int fb_stop_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct browser_window *bw = cbi->context; if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; browser_window_stop(bw); return 0; } static int fb_osk_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; map_osk(); return 0; } /* close browser window icon click routine */ static int fb_close_click(fbtk_widget_t *widget, fbtk_callback_info *cbi) { if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; netsurf_quit = true; return 0; } static int fb_scroll_callback(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; switch (cbi->type) { case FBTK_CBT_SCROLLY: widget_scroll_y(gw, cbi->y, true); break; case FBTK_CBT_SCROLLX: widget_scroll_x(gw, cbi->x, true); break; default: break; } return 0; } static int fb_url_enter(void *pw, char *text) { struct browser_window *bw = pw; nsurl *url; nserror error; error = nsurl_create(text, &url); if (error != NSERROR_OK) { warn_user(messages_get_errorcode(error), 0); } else { browser_window_navigate(bw, url, NULL, BW_NAVIGATE_HISTORY, NULL, NULL, NULL); nsurl_unref(url); } return 0; } static int fb_url_move(fbtk_widget_t *widget, fbtk_callback_info *cbi) { framebuffer_set_cursor(&caret_image); return 0; } static int set_ptr_default_move(fbtk_widget_t *widget, fbtk_callback_info *cbi) { framebuffer_set_cursor(&pointer_image); return 0; } static int fb_localhistory_btn_clik(fbtk_widget_t *widget, fbtk_callback_info *cbi) { struct gui_window *gw = cbi->context; if (cbi->event->type != NSFB_EVENT_KEY_UP) return 0; fb_localhistory_map(gw->localhistory); return 0; } /** Create a toolbar window and populate it with buttons. * * The toolbar layout uses a character to define buttons type and position: * b - back * l - local history * f - forward * s - stop * r - refresh * u - url bar expands to fit remaining space * t - throbber/activity indicator * c - close the current window * * The default layout is "blfsrut" there should be no more than a * single url bar entry or behaviour will be undefined. * * @param gw Parent window * @param toolbar_height The height in pixels of the toolbar * @param padding The padding in pixels round each element of the toolbar * @param frame_col Frame colour. * @param toolbar_layout A string defining which buttons and controls * should be added to the toolbar. May be empty * string to disable the bar.. * */ static fbtk_widget_t * create_toolbar(struct gui_window *gw, int toolbar_height, int padding, colour frame_col, const char *toolbar_layout) { fbtk_widget_t *toolbar; fbtk_widget_t *widget; int xpos; /* The position of the next widget. */ int xlhs = 0; /* extent of the left hand side widgets */ int xdir = 1; /* the direction of movement + or - 1 */ const char *itmtype; /* type of the next item */ if (toolbar_layout == NULL) { toolbar_layout = NSFB_TOOLBAR_DEFAULT_LAYOUT; } LOG(("Using toolbar layout %s", toolbar_layout)); itmtype = toolbar_layout; if (*itmtype == 0) { return NULL; } toolbar = fbtk_create_window(gw->window, 0, 0, 0, toolbar_height, frame_col); if (toolbar == NULL) { return NULL; } fbtk_set_handler(toolbar, FBTK_CBT_POINTERENTER, set_ptr_default_move, NULL); xpos = padding; /* loop proceeds creating widget on the left hand side until * it runs out of layout or encounters a url bar declaration * wherupon it works backwards from the end of the layout * untill the space left is for the url bar */ while ((itmtype >= toolbar_layout) && (*itmtype != 0) && (xdir !=0)) { LOG(("toolbar adding %c", *itmtype)); switch (*itmtype) { case 'b': /* back */ widget = fbtk_create_button(toolbar, (xdir == 1) ? xpos : xpos - left_arrow.width, padding, left_arrow.width, -padding, frame_col, &left_arrow, fb_leftarrow_click, gw); gw->back = widget; /* keep reference */ break; case 'l': /* local history */ widget = fbtk_create_button(toolbar, (xdir == 1) ? xpos : xpos - history_image.width, padding, history_image.width, -padding, frame_col, &history_image, fb_localhistory_btn_clik, gw); break; case 'f': /* forward */ widget = fbtk_create_button(toolbar, (xdir == 1)?xpos : xpos - right_arrow.width, padding, right_arrow.width, -padding, frame_col, &right_arrow, fb_rightarrow_click, gw); gw->forward = widget; break; case 'c': /* close the current window */ widget = fbtk_create_button(toolbar, (xdir == 1)?xpos : xpos - stop_image_g.width, padding, stop_image_g.width, -padding, frame_col, &stop_image_g, fb_close_click, gw->bw); break; case 's': /* stop */ widget = fbtk_create_button(toolbar, (xdir == 1)?xpos : xpos - stop_image.width, padding, stop_image.width, -padding, frame_col, &stop_image, fb_stop_click, gw->bw); break; case 'r': /* reload */ widget = fbtk_create_button(toolbar, (xdir == 1)?xpos : xpos - reload.width, padding, reload.width, -padding, frame_col, &reload, fb_reload_click, gw->bw); break; case 't': /* throbber/activity indicator */ widget = fbtk_create_bitmap(toolbar, (xdir == 1)?xpos : xpos - throbber0.width, padding, throbber0.width, -padding, frame_col, &throbber0); gw->throbber = widget; break; case 'u': /* url bar*/ if (xdir == -1) { /* met the u going backwards add url * now we know available extent */ widget = fbtk_create_writable_text(toolbar, xlhs, padding, xpos - xlhs, -padding, FB_COLOUR_WHITE, FB_COLOUR_BLACK, true, fb_url_enter, gw->bw); fbtk_set_handler(widget, FBTK_CBT_POINTERENTER, fb_url_move, gw->bw); gw->url = widget; /* keep reference */ /* toolbar is complete */ xdir = 0; break; } /* met url going forwards, note position and * reverse direction */ itmtype = toolbar_layout + strlen(toolbar_layout); xdir = -1; xlhs = xpos; xpos = (2 * fbtk_get_width(toolbar)); widget = toolbar; break; default: widget = NULL; xdir = 0; LOG(("Unknown element %c in toolbar layout", *itmtype)); break; } if (widget != NULL) { xpos += (xdir * (fbtk_get_width(widget) + padding)); } LOG(("xpos is %d",xpos)); itmtype += xdir; } fbtk_set_mapping(toolbar, true); return toolbar; } /** Routine called when "stripped of focus" event occours for browser widget. * * @param widget The widget reciving "stripped of focus" event. * @param cbi The callback parameters. * @return The callback result. */ static int fb_browser_window_strip_focus(fbtk_widget_t *widget, fbtk_callback_info *cbi) { fbtk_set_caret(widget, false, 0, 0, 0, NULL); return 0; } static void create_browser_widget(struct gui_window *gw, int toolbar_height, int furniture_width) { struct browser_widget_s *browser_widget; browser_widget = calloc(1, sizeof(struct browser_widget_s)); gw->browser = fbtk_create_user(gw->window, 0, toolbar_height, -furniture_width, -furniture_width, browser_widget); fbtk_set_handler(gw->browser, FBTK_CBT_REDRAW, fb_browser_window_redraw, gw); fbtk_set_handler(gw->browser, FBTK_CBT_DESTROY, fb_browser_window_destroy, gw); fbtk_set_handler(gw->browser, FBTK_CBT_INPUT, fb_browser_window_input, gw); fbtk_set_handler(gw->browser, FBTK_CBT_CLICK, fb_browser_window_click, gw); fbtk_set_handler(gw->browser, FBTK_CBT_STRIP_FOCUS, fb_browser_window_strip_focus, gw); fbtk_set_handler(gw->browser, FBTK_CBT_POINTERMOVE, fb_browser_window_move, gw); } static void create_normal_browser_window(struct gui_window *gw, int furniture_width) { fbtk_widget_t *widget; fbtk_widget_t *toolbar; int statusbar_width = 0; int toolbar_height = nsoption_int(fb_toolbar_size); LOG(("Normal window")); gw->window = fbtk_create_window(fbtk, 0, 0, 0, 0, 0); statusbar_width = nsoption_int(toolbar_status_size) * fbtk_get_width(gw->window) / 10000; /* toolbar */ toolbar = create_toolbar(gw, toolbar_height, 2, FB_FRAME_COLOUR, nsoption_charp(fb_toolbar_layout)); /* set the actually created toolbar height */ if (toolbar != NULL) { toolbar_height = fbtk_get_height(toolbar); } else { toolbar_height = 0; } /* status bar */ gw->status = fbtk_create_text(gw->window, 0, fbtk_get_height(gw->window) - furniture_width, statusbar_width, furniture_width, FB_FRAME_COLOUR, FB_COLOUR_BLACK, false); fbtk_set_handler(gw->status, FBTK_CBT_POINTERENTER, set_ptr_default_move, NULL); LOG(("status bar %p at %d,%d", gw->status, fbtk_get_absx(gw->status), fbtk_get_absy(gw->status))); /* create horizontal scrollbar */ gw->hscroll = fbtk_create_hscroll(gw->window, statusbar_width, fbtk_get_height(gw->window) - furniture_width, fbtk_get_width(gw->window) - statusbar_width - furniture_width, furniture_width, FB_SCROLL_COLOUR, FB_FRAME_COLOUR, fb_scroll_callback, gw); /* fill bottom right area */ if (nsoption_bool(fb_osk) == true) { widget = fbtk_create_text_button(gw->window, fbtk_get_width(gw->window) - furniture_width, fbtk_get_height(gw->window) - furniture_width, furniture_width, furniture_width, FB_FRAME_COLOUR, FB_COLOUR_BLACK, fb_osk_click, NULL); widget = fbtk_create_button(gw->window, fbtk_get_width(gw->window) - furniture_width, fbtk_get_height(gw->window) - furniture_width, furniture_width, furniture_width, FB_FRAME_COLOUR, &osk_image, fb_osk_click, NULL); } else { widget = fbtk_create_fill(gw->window, fbtk_get_width(gw->window) - furniture_width, fbtk_get_height(gw->window) - furniture_width, furniture_width, furniture_width, FB_FRAME_COLOUR); fbtk_set_handler(widget, FBTK_CBT_POINTERENTER, set_ptr_default_move, NULL); } /* create vertical scrollbar */ gw->vscroll = fbtk_create_vscroll(gw->window, fbtk_get_width(gw->window) - furniture_width, toolbar_height, furniture_width, fbtk_get_height(gw->window) - toolbar_height - furniture_width, FB_SCROLL_COLOUR, FB_FRAME_COLOUR, fb_scroll_callback, gw); /* browser widget */ create_browser_widget(gw, toolbar_height, nsoption_int(fb_furniture_size)); /* Give browser_window's user widget input focus */ fbtk_set_focus(gw->browser); } static struct gui_window * gui_window_create(struct browser_window *bw, struct gui_window *existing, gui_window_create_flags flags) { struct gui_window *gw; gw = calloc(1, sizeof(struct gui_window)); if (gw == NULL) return NULL; /* associate the gui window with the underlying browser window */ gw->bw = bw; create_normal_browser_window(gw, nsoption_int(fb_furniture_size)); gw->localhistory = fb_create_localhistory(bw, fbtk, nsoption_int(fb_furniture_size)); /* map and request redraw of gui window */ fbtk_set_mapping(gw->window, true); return gw; } static void gui_window_destroy(struct gui_window *gw) { fbtk_destroy_widget(gw->window); free(gw); } static void gui_window_redraw_window(struct gui_window *g) { fb_queue_redraw(g->browser, 0, 0, fbtk_get_width(g->browser), fbtk_get_height(g->browser) ); } static void gui_window_update_box(struct gui_window *g, const struct rect *rect) { struct browser_widget_s *bwidget = fbtk_get_userpw(g->browser); fb_queue_redraw(g->browser, rect->x0 - bwidget->scrollx, rect->y0 - bwidget->scrolly, rect->x1 - bwidget->scrollx, rect->y1 - bwidget->scrolly); } static bool gui_window_get_scroll(struct gui_window *g, int *sx, int *sy) { struct browser_widget_s *bwidget = fbtk_get_userpw(g->browser); float scale = browser_window_get_scale(g->bw); *sx = bwidget->scrollx / scale; *sy = bwidget->scrolly / scale; return true; } static void gui_window_set_scroll(struct gui_window *gw, int sx, int sy) { struct browser_widget_s *bwidget = fbtk_get_userpw(gw->browser); float scale = browser_window_get_scale(gw->bw); assert(bwidget); widget_scroll_x(gw, sx * scale, true); widget_scroll_y(gw, sy * scale, true); } static void gui_window_get_dimensions(struct gui_window *g, int *width, int *height, bool scaled) { float scale = browser_window_get_scale(g->bw); *width = fbtk_get_width(g->browser); *height = fbtk_get_height(g->browser); if (scaled) { *width /= scale; *height /= scale; } } static void gui_window_update_extent(struct gui_window *gw) { int w, h; browser_window_get_extents(gw->bw, true, &w, &h); fbtk_set_scroll_parameters(gw->hscroll, 0, w, fbtk_get_width(gw->browser), 100); fbtk_set_scroll_parameters(gw->vscroll, 0, h, fbtk_get_height(gw->browser), 100); } static void gui_window_set_status(struct gui_window *g, const char *text) { fbtk_set_text(g->status, text); } static void gui_window_set_pointer(struct gui_window *g, gui_pointer_shape shape) { switch (shape) { case GUI_POINTER_POINT: framebuffer_set_cursor(&hand_image); break; case GUI_POINTER_CARET: framebuffer_set_cursor(&caret_image); break; case GUI_POINTER_MENU: framebuffer_set_cursor(&menu_image); break; case GUI_POINTER_PROGRESS: framebuffer_set_cursor(&progress_image); break; case GUI_POINTER_MOVE: framebuffer_set_cursor(&move_image); break; default: framebuffer_set_cursor(&pointer_image); break; } } static void gui_window_set_url(struct gui_window *g, const char *url) { fbtk_set_text(g->url, url); } static void throbber_advance(void *pw) { struct gui_window *g = pw; struct fbtk_bitmap *image; switch (g->throbber_index) { case 0: image = &throbber1; g->throbber_index = 1; break; case 1: image = &throbber2; g->throbber_index = 2; break; case 2: image = &throbber3; g->throbber_index = 3; break; case 3: image = &throbber4; g->throbber_index = 4; break; case 4: image = &throbber5; g->throbber_index = 5; break; case 5: image = &throbber6; g->throbber_index = 6; break; case 6: image = &throbber7; g->throbber_index = 7; break; case 7: image = &throbber8; g->throbber_index = 0; break; default: return; } if (g->throbber_index >= 0) { fbtk_set_bitmap(g->throbber, image); framebuffer_schedule(100, throbber_advance, g); } } static void gui_window_start_throbber(struct gui_window *g) { g->throbber_index = 0; framebuffer_schedule(100, throbber_advance, g); } static void gui_window_stop_throbber(struct gui_window *gw) { gw->throbber_index = -1; fbtk_set_bitmap(gw->throbber, &throbber0); fb_update_back_forward(gw); } static void gui_window_remove_caret_cb(fbtk_widget_t *widget) { struct browser_widget_s *bwidget = fbtk_get_userpw(widget); int c_x, c_y, c_h; if (fbtk_get_caret(widget, &c_x, &c_y, &c_h)) { /* browser window already had caret: * redraw its area to remove it first */ fb_queue_redraw(widget, c_x - bwidget->scrollx, c_y - bwidget->scrolly, c_x + 1 - bwidget->scrollx, c_y + c_h - bwidget->scrolly); } } static void gui_window_place_caret(struct gui_window *g, int x, int y, int height, const struct rect *clip) { struct browser_widget_s *bwidget = fbtk_get_userpw(g->browser); /* set new pos */ fbtk_set_caret(g->browser, true, x, y, height, gui_window_remove_caret_cb); /* redraw new caret pos */ fb_queue_redraw(g->browser, x - bwidget->scrollx, y - bwidget->scrolly, x + 1 - bwidget->scrollx, y + height - bwidget->scrolly); } static void gui_window_remove_caret(struct gui_window *g) { int c_x, c_y, c_h; if (fbtk_get_caret(g->browser, &c_x, &c_y, &c_h)) { /* browser window owns the caret, so can remove it */ fbtk_set_caret(g->browser, false, 0, 0, 0, NULL); } } static struct gui_window_table framebuffer_window_table = { .create = gui_window_create, .destroy = gui_window_destroy, .redraw = gui_window_redraw_window, .update = gui_window_update_box, .get_scroll = gui_window_get_scroll, .set_scroll = gui_window_set_scroll, .get_dimensions = gui_window_get_dimensions, .update_extent = gui_window_update_extent, .set_url = gui_window_set_url, .set_status = gui_window_set_status, .set_pointer = gui_window_set_pointer, .place_caret = gui_window_place_caret, .remove_caret = gui_window_remove_caret, .start_throbber = gui_window_start_throbber, .stop_throbber = gui_window_stop_throbber, }; static struct gui_browser_table framebuffer_browser_table = { .poll = framebuffer_poll, .schedule = framebuffer_schedule, .quit = gui_quit, }; /** Entry point from OS. * * /param argc The number of arguments in the string vector. * /param argv The argument string vector. * /return The return code to the OS */ int main(int argc, char** argv) { struct browser_window *bw; char *options; char *messages; nsurl *url; nserror ret; nsfb_t *nsfb; struct netsurf_table framebuffer_table = { .browser = &framebuffer_browser_table, .window = &framebuffer_window_table, .clipboard = framebuffer_clipboard_table, .fetch = framebuffer_fetch_table, .utf8 = framebuffer_utf8_table, }; ret = netsurf_register(&framebuffer_table); if (ret != NSERROR_OK) { die("NetSurf operation table failed registration"); } respaths = fb_init_resource(NETSURF_FB_RESPATH":"NETSURF_FB_FONTPATH); /* initialise logging. Not fatal if it fails but not much we * can do about it either. */ nslog_init(nslog_stream_configure, &argc, argv); /* user options setup */ ret = nsoption_init(set_defaults, &nsoptions, &nsoptions_default); if (ret != NSERROR_OK) { die("Options failed to initialise"); } options = filepath_find(respaths, "Choices"); nsoption_read(options, nsoptions); free(options); nsoption_commandline(&argc, argv, nsoptions); /* common initialisation */ messages = filepath_find(respaths, "Messages"); ret = netsurf_init(messages, NULL); free(messages); if (ret != NSERROR_OK) { die("NetSurf failed to initialise"); } /* Override, since we have no support for non-core SELECT menu */ nsoption_set_bool(core_select_menu, true); if (process_cmdline(argc,argv) != true) die("unable to process command line.\n"); nsfb = framebuffer_initialise(fename, fewidth, feheight, febpp); if (nsfb == NULL) die("Unable to initialise framebuffer"); framebuffer_set_cursor(&pointer_image); if (fb_font_init() == false) die("Unable to initialise the font system"); fbtk = fbtk_init(nsfb); fbtk_enable_oskb(fbtk); urldb_load_cookies(nsoption_charp(cookie_file)); /* create an initial browser window */ LOG(("calling browser_window_create")); ret = nsurl_create(feurl, &url); if (ret == NSERROR_OK) { ret = browser_window_create(BW_CREATE_HISTORY, url, NULL, NULL, &bw); nsurl_unref(url); } if (ret != NSERROR_OK) { warn_user(messages_get_errorcode(ret), 0); } else { netsurf_main_loop(); browser_window_destroy(bw); } netsurf_exit(); if (fb_font_finalise() == false) LOG(("Font finalisation failed.")); /* finalise options */ nsoption_finalise(nsoptions, nsoptions_default); return 0; } /* * Local Variables: * c-basic-offset:8 * End: */