Add "create-web-view" callback, fixes "Open in new window" context item
[luakit.git] / widgets / webview.c
blob394c6c72f68ddfd3959359f372f8df0078691d96
1 /*
2 * webview.c - webkit webview widget
4 * Copyright (C) 2010 Mason Larobina <mason.larobina@gmail.com>
5 * Copyright (C) 2007-2009 Julien Danjou <julien@danjou.info>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "luah.h"
23 #include "widgets/common.h"
24 #include <JavaScriptCore/JavaScript.h>
25 #include <webkit/webkit.h>
26 #include <libsoup/soup.h>
27 #include "math.h"
29 static struct {
30 SoupSession *session;
31 SoupCookieJar *cookiejar;
32 } Soup = { NULL, NULL };
34 typedef enum {
35 BOOL,
36 CHAR,
37 INT,
38 FLOAT,
39 DOUBLE,
40 URI,
41 } property_value_t;
43 typedef enum {
44 SETTINGS,
45 WEBKITVIEW,
46 SOUPSESSION,
47 } property_scope;
49 typedef union {
50 gchar *c;
51 gboolean b;
52 gdouble d;
53 gfloat f;
54 gint i;
55 } temp_value_t;
57 GHashTable *properties = NULL;
59 typedef struct {
60 const gchar *name;
61 property_value_t type;
62 property_scope scope;
63 gboolean writable;
64 const gchar *signame;
65 } property_t;
67 property_t properties_table[] = {
68 { "accept-language", CHAR, SOUPSESSION, TRUE, NULL },
69 { "accept-language-auto", BOOL, SOUPSESSION, TRUE, NULL },
70 { "auto-load-images", BOOL, SETTINGS, TRUE, NULL },
71 { "auto-resize-window", BOOL, SETTINGS, TRUE, NULL },
72 { "auto-shrink-images", BOOL, SETTINGS, TRUE, NULL },
73 { "cursive-font-family", CHAR, SETTINGS, TRUE, NULL },
74 { "custom-encoding", CHAR, WEBKITVIEW, TRUE, NULL },
75 { "default-encoding", CHAR, SETTINGS, TRUE, NULL },
76 { "default-font-family", CHAR, SETTINGS, TRUE, NULL },
77 { "default-font-size", INT, SETTINGS, TRUE, NULL },
78 { "default-monospace-font-size", INT, SETTINGS, TRUE, NULL },
79 { "editable", BOOL, WEBKITVIEW, TRUE, NULL },
80 { "enable-caret-browsing", BOOL, SETTINGS, TRUE, NULL },
81 { "enable-default-context-menu", BOOL, SETTINGS, TRUE, NULL },
82 { "enable-developer-extras", BOOL, SETTINGS, TRUE, NULL },
83 { "enable-dom-paste", BOOL, SETTINGS, TRUE, NULL },
84 { "enable-file-access-from-file-uris", BOOL, SETTINGS, TRUE, NULL },
85 { "enable-html5-database", BOOL, SETTINGS, TRUE, NULL },
86 { "enable-html5-local-storage", BOOL, SETTINGS, TRUE, NULL },
87 { "enable-java-applet", BOOL, SETTINGS, TRUE, NULL },
88 { "enable-offline-web-application-cache", BOOL, SETTINGS, TRUE, NULL },
89 { "enable-page-cache", BOOL, SETTINGS, TRUE, NULL },
90 { "enable-plugins", BOOL, SETTINGS, TRUE, NULL },
91 { "enable-private-browsing", BOOL, SETTINGS, TRUE, NULL },
92 { "enable-scripts", BOOL, SETTINGS, TRUE, NULL },
93 { "enable-site-specific-quirks", BOOL, SETTINGS, TRUE, NULL },
94 { "enable-spatial-navigation", BOOL, SETTINGS, TRUE, NULL },
95 { "enable-spell-checking", BOOL, SETTINGS, TRUE, NULL },
96 { "enable-universal-access-from-file-uris", BOOL, SETTINGS, TRUE, NULL },
97 { "enable-xss-auditor", BOOL, SETTINGS, TRUE, NULL },
98 { "encoding", CHAR, WEBKITVIEW, FALSE, NULL },
99 { "enforce-96-dpi", BOOL, SETTINGS, TRUE, NULL },
100 { "fantasy-font-family", CHAR, SETTINGS, TRUE, NULL },
101 { "full-content-zoom", BOOL, WEBKITVIEW, TRUE, NULL },
102 { "icon-uri", CHAR, WEBKITVIEW, FALSE, NULL },
103 { "idle-timeout", INT, SOUPSESSION, TRUE, NULL },
104 { "javascript-can-access-clipboard", BOOL, SETTINGS, TRUE, NULL },
105 { "javascript-can-open-windows-automatically", BOOL, SETTINGS, TRUE, NULL },
106 { "max-conns", INT, SOUPSESSION, TRUE, NULL },
107 { "max-conns-per-host", INT, SOUPSESSION, TRUE, NULL },
108 { "minimum-font-size", INT, SETTINGS, TRUE, NULL },
109 { "minimum-logical-font-size", INT, SETTINGS, TRUE, NULL },
110 { "monospace-font-family", CHAR, SETTINGS, TRUE, NULL },
111 { "print-backgrounds", BOOL, SETTINGS, TRUE, NULL },
112 { "progress", DOUBLE, WEBKITVIEW, FALSE, NULL },
113 { "proxy-uri", URI, SOUPSESSION, TRUE, NULL },
114 { "resizable-text-areas", BOOL, SETTINGS, TRUE, NULL },
115 { "sans-serif-font-family", CHAR, SETTINGS, TRUE, NULL },
116 { "serif-font-family", CHAR, SETTINGS, TRUE, NULL },
117 { "spell-checking-languages", CHAR, SETTINGS, TRUE, NULL },
118 { "ssl-ca-file", CHAR, SOUPSESSION, TRUE, NULL },
119 { "ssl-strict", BOOL, SOUPSESSION, TRUE, NULL },
120 { "tab-key-cycles-through-elements", BOOL, SETTINGS, TRUE, NULL },
121 { "timeout", INT, SOUPSESSION, TRUE, NULL },
122 { "title", CHAR, WEBKITVIEW, FALSE, NULL },
123 { "transparent", BOOL, WEBKITVIEW, TRUE, NULL },
124 { "uri", CHAR, WEBKITVIEW, TRUE, NULL },
125 { "use-ntlm", BOOL, SOUPSESSION, TRUE, NULL },
126 { "user-agent", CHAR, SETTINGS, TRUE, NULL },
127 { "user-stylesheet-uri", CHAR, SETTINGS, TRUE, NULL },
128 { "zoom-level", FLOAT, WEBKITVIEW, TRUE, NULL },
129 { "zoom-step", FLOAT, SETTINGS, TRUE, NULL },
130 { NULL, 0, 0, 0, NULL },
133 static void
134 webview_init_properties() {
135 properties = g_hash_table_new(g_str_hash, g_str_equal);
136 for (property_t *p = properties_table; p->name; p++) {
137 /* pre-compile "property::name" signals for each property */
138 if (!p->signame) p->signame = g_strdup_printf("property::%s", p->name);
139 g_hash_table_insert(properties, (gpointer) p->name, (gpointer) p);
143 static const gchar*
144 webview_eval_js(WebKitWebView *view, const gchar *script, const gchar *file) {
145 WebKitWebFrame *frame;
146 JSGlobalContextRef context;
147 JSObjectRef globalobject;
148 JSStringRef js_file;
149 JSStringRef js_script;
150 JSValueRef js_result;
151 JSValueRef js_exc = NULL;
152 JSStringRef js_result_string;
153 GString *result = g_string_new(NULL);
154 size_t js_result_size;
156 frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(view));
157 context = webkit_web_frame_get_global_context(frame);
158 globalobject = JSContextGetGlobalObject(context);
160 /* evaluate the script and get return value*/
161 js_script = JSStringCreateWithUTF8CString(script);
162 js_file = JSStringCreateWithUTF8CString(file);
163 js_result = JSEvaluateScript(context, js_script, globalobject, js_file, 0, &js_exc);
164 if (js_result && !JSValueIsUndefined(context, js_result)) {
165 js_result_string = JSValueToStringCopy(context, js_result, NULL);
166 js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string);
168 if (js_result_size) {
169 char js_result_utf8[js_result_size];
170 JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size);
171 g_string_assign(result, js_result_utf8);
174 JSStringRelease(js_result_string);
176 else if (js_exc) {
177 size_t size;
178 JSStringRef prop, val;
179 JSObjectRef exc = JSValueToObject(context, js_exc, NULL);
181 printf("Exception occured while executing script:\n");
183 /* Print file */
184 prop = JSStringCreateWithUTF8CString("sourceURL");
185 val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL);
186 size = JSStringGetMaximumUTF8CStringSize(val);
187 if(size) {
188 char cstr[size];
189 JSStringGetUTF8CString(val, cstr, size);
190 printf("At %s", cstr);
192 JSStringRelease(prop);
193 JSStringRelease(val);
195 /* Print line */
196 prop = JSStringCreateWithUTF8CString("line");
197 val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL);
198 size = JSStringGetMaximumUTF8CStringSize(val);
199 if(size) {
200 char cstr[size];
201 JSStringGetUTF8CString(val, cstr, size);
202 printf(":%s: ", cstr);
204 JSStringRelease(prop);
205 JSStringRelease(val);
207 /* Print message */
208 val = JSValueToStringCopy(context, exc, NULL);
209 size = JSStringGetMaximumUTF8CStringSize(val);
210 if(size) {
211 char cstr[size];
212 JSStringGetUTF8CString(val, cstr, size);
213 printf("%s\n", cstr);
215 JSStringRelease(val);
218 /* cleanup */
219 JSStringRelease(js_script);
220 JSStringRelease(js_file);
222 return g_string_free(result, FALSE);
225 static gint
226 luaH_webview_eval_js(lua_State *L)
228 widget_t *w = luaH_checkudata(L, 1, &widget_class);
229 WebKitWebView *view = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(w->widget), "webview"));
230 const gchar *script = luaL_checkstring(L, 2);
231 const gchar *filename = luaL_checkstring(L, 3);
233 /* evaluate javascript script and push return result onto lua stack */
234 const gchar *result = webview_eval_js(view, script, filename);
235 lua_pushstring(L, result);
236 return 1;
239 static void
240 notify_cb(WebKitWebView *v, GParamSpec *ps, widget_t *w)
242 (void) v;
243 property_t *p;
244 /* emit "property::name" signal if found in properties table */
245 if ((p = g_hash_table_lookup(properties, ps->name))) {
246 lua_State *L = globalconf.L;
247 luaH_object_push(L, w->ref);
248 luaH_object_emit_signal(L, -1, p->signame, 0, 0);
249 lua_pop(L, 1);
253 static void
254 notify_load_status_cb(WebKitWebView *v, GParamSpec *ps, widget_t *w)
256 (void) ps;
258 /* Get load status */
259 WebKitLoadStatus status;
260 g_object_get(G_OBJECT(v), "load-status", &status, NULL);
262 /* get load status literal */
263 gchar *name = NULL;
264 switch (status) {
266 #define LT_CASE(a, l) case WEBKIT_LOAD_##a: name = l; break;
267 LT_CASE(PROVISIONAL, "provisional")
268 LT_CASE(COMMITTED, "committed")
269 LT_CASE(FINISHED, "finished")
270 LT_CASE(FIRST_VISUALLY_NON_EMPTY_LAYOUT, "first-visual")
271 LT_CASE(FAILED, "failed")
272 #undef LT_CASE
274 default:
275 warn("programmer error, unable to get load status literal");
276 break;
279 lua_State *L = globalconf.L;
280 luaH_object_push(L, w->ref);
281 lua_pushstring(L, name);
282 luaH_object_emit_signal(L, -2, "load-status", 1, 0);
283 lua_pop(L, 1);
286 static gboolean
287 mime_type_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
288 WebKitNetworkRequest *r, gchar *mime, WebKitWebPolicyDecision *pd,
289 widget_t *w)
291 (void) v;
292 (void) f;
293 lua_State *L = globalconf.L;
294 const gchar *uri = webkit_network_request_get_uri(r);
295 gint ret;
297 luaH_object_push(L, w->ref);
298 lua_pushstring(L, uri);
299 lua_pushstring(L, mime);
300 ret = luaH_object_emit_signal(L, -3, "mime-type-decision", 2, 1);
302 if (ret && !luaH_checkboolean(L, -1))
303 /* User responded with false, ignore request */
304 webkit_web_policy_decision_ignore(pd);
305 else if (!webkit_web_view_can_show_mime_type(v, mime))
306 webkit_web_policy_decision_download(pd);
307 else
308 webkit_web_policy_decision_use(pd);
310 lua_pop(L, ret + 1);
311 return TRUE;
314 static gboolean
315 new_window_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
316 WebKitNetworkRequest *r, WebKitWebNavigationAction *na,
317 WebKitWebPolicyDecision *pd, widget_t *w)
319 (void) v;
320 (void) f;
321 lua_State *L = globalconf.L;
322 const gchar *uri = webkit_network_request_get_uri(r);
323 gchar *reason = NULL;
324 gint ret = 0;
326 luaH_object_push(L, w->ref);
327 lua_pushstring(L, uri);
329 switch (webkit_web_navigation_action_get_reason(na)) {
331 #define NR_CASE(a, l) case WEBKIT_WEB_NAVIGATION_REASON_##a: reason = l; break;
332 NR_CASE(LINK_CLICKED, "link-clicked");
333 NR_CASE(FORM_SUBMITTED, "form-submitted");
334 NR_CASE(BACK_FORWARD, "back-forward");
335 NR_CASE(RELOAD, "reload");
336 NR_CASE(FORM_RESUBMITTED, "form-resubmitted");
337 NR_CASE(OTHER, "other");
338 #undef NR_CASE
340 default:
341 warn("programmer error, unable to get web navigation reason literal");
342 break;
345 lua_pushstring(L, reason);
346 ret = luaH_object_emit_signal(L, -3, "new-window-decision", 2, 1);
348 /* User responded with true, meaning a decision was made
349 * and the signal was handled */
350 if (ret && luaH_checkboolean(L, -1))
352 webkit_web_policy_decision_ignore(pd);
353 lua_pop(L, ret + 1);
355 return TRUE;
358 lua_pop(L, ret + 1);
360 /* proceed with default behaviour */
361 return FALSE;
364 static WebKitWebView*
365 create_web_view_cb(WebKitWebView *v, WebKitWebFrame *f, widget_t *w)
367 (void) v;
368 (void) f;
370 lua_State *L = globalconf.L;
371 luaH_object_push(L, w->ref);
372 luaH_object_emit_signal(L, -1, "create-web-view", 0, 0);
373 lua_pop(L, 1);
375 return NULL;
378 static gboolean
379 download_request_cb(WebKitWebView *v, GObject *dl, widget_t *w)
381 (void) v;
382 const gchar *uri = webkit_download_get_uri((WebKitDownload *) dl);
383 const gchar *filename = webkit_download_get_suggested_filename((WebKitDownload *) dl);
385 lua_State *L = globalconf.L;
386 luaH_object_push(L, w->ref);
387 lua_pushstring(L, uri);
388 lua_pushstring(L, filename);
389 luaH_object_emit_signal(L, -3, "download-request", 2, 0);
390 lua_pop(L, 1);
392 return FALSE;
395 static void
396 link_hover_cb(WebKitWebView *view, const char *t, const gchar *link, widget_t *w)
398 (void) t;
399 lua_State *L = globalconf.L;
400 GObject *ws = G_OBJECT(view);
401 gchar *last_hover = g_object_get_data(ws, "hovered-uri");
403 /* links are identical, do nothing */
404 if (last_hover && !g_strcmp0(last_hover, link))
405 return;
407 luaH_object_push(L, w->ref);
409 if (last_hover) {
410 lua_pushstring(L, last_hover);
411 g_object_set_data(ws, "hovered-uri", NULL);
412 luaH_object_emit_signal(L, -2, "link-unhover", 1, 0);
415 if (link) {
416 lua_pushstring(L, link);
417 g_object_set_data_full(ws, "hovered-uri", g_strdup(link), g_free);
418 luaH_object_emit_signal(L, -2, "link-hover", 1, 0);
421 luaH_object_emit_signal(L, -1, "property::hovered_uri", 0, 0);
422 lua_pop(L, 1);
425 /* Raises the "navigation-request" signal on a webkit navigation policy
426 * decision request. The default action is to load the requested uri.
428 * The signal handler is able to:
429 * - return true for the handler execution to stop and the request to continue
430 * - return false for the handler execution to stop and the request to hault
431 * - do nothing and give the navigation decision to the next signal handler
433 * This signal is also where you would attach custom scheme handlers to take
434 * over the navigation request by launching an external application.
436 static gboolean
437 navigation_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
438 WebKitNetworkRequest *r, WebKitWebNavigationAction *a,
439 WebKitWebPolicyDecision *p, widget_t *w)
441 (void) v;
442 (void) f;
443 (void) a;
445 lua_State *L = globalconf.L;
446 const gchar *uri = webkit_network_request_get_uri(r);
447 gint ret;
449 luaH_object_push(L, w->ref);
450 lua_pushstring(L, uri);
451 ret = luaH_object_emit_signal(L, -2, "navigation-request", 1, 1);
453 if (ret && !luaH_checkboolean(L, -1))
454 /* User responded with false, do not continue navigation request */
455 webkit_web_policy_decision_ignore(p);
456 else
457 webkit_web_policy_decision_use(p);
459 lua_pop(L, ret + 1);
460 return TRUE;
463 inline static gint
464 luaH_adjustment_push_values(lua_State *L, GtkAdjustment *a)
466 gdouble view_size = gtk_adjustment_get_page_size(a);
467 gdouble value = gtk_adjustment_get_value(a);
468 gdouble max = gtk_adjustment_get_upper(a) - view_size;
469 lua_pushnumber(L, value);
470 lua_pushnumber(L, (max < 0 ? 0 : max));
471 lua_pushnumber(L, view_size);
472 return 3;
475 static gint
476 luaH_webview_get_vscroll(lua_State *L)
478 widget_t *w = luaH_checkudata(L, 1, &widget_class);
479 GtkAdjustment *a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
480 return luaH_adjustment_push_values(L, a);
483 static gint
484 luaH_webview_get_hscroll(lua_State *L)
486 widget_t *w = luaH_checkudata(L, 1, &widget_class);
487 GtkAdjustment *a = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
488 return luaH_adjustment_push_values(L, a);
491 inline static void
492 adjustment_set(GtkAdjustment *a, gdouble new)
494 gdouble view_size = gtk_adjustment_get_page_size(a);
495 gdouble max = gtk_adjustment_get_upper(a) - view_size;
496 gtk_adjustment_set_value(a, ((new < 0 ? 0 : new) > max ? max : new));
499 static gint
500 luaH_webview_set_scroll_vert(lua_State *L)
502 widget_t *w = luaH_checkudata(L, 1, &widget_class);
503 gdouble value = (gdouble) luaL_checknumber(L, 2);
504 GtkAdjustment *a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
505 adjustment_set(a, value);
506 return 0;
509 static gint
510 luaH_webview_set_scroll_horiz(lua_State *L)
512 widget_t *w = luaH_checkudata(L, 1, &widget_class);
513 gdouble value = (gdouble) luaL_checknumber(L, 2);
514 GtkAdjustment *a = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
515 adjustment_set(a, value);
516 return 0;
519 static gint
520 luaH_webview_go_back(lua_State *L)
522 widget_t *w = luaH_checkudata(L, 1, &widget_class);
523 gint steps = (gint) luaL_checknumber(L, 2);
524 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
525 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps * -1);
526 return 0;
529 static gint
530 luaH_webview_go_forward(lua_State *L)
532 widget_t *w = luaH_checkudata(L, 1, &widget_class);
533 gint steps = (gint) luaL_checknumber(L, 2);
534 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
535 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps);
536 return 0;
539 static gint
540 luaH_webview_get_view_source(lua_State *L)
542 widget_t *w = luaH_checkudata(L, 1, &widget_class);
543 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
544 lua_pushboolean(L, webkit_web_view_get_view_source_mode(WEBKIT_WEB_VIEW(view)));
545 return 1;
548 static gint
549 luaH_webview_set_view_source(lua_State *L)
551 const gchar *uri;
552 widget_t *w = luaH_checkudata(L, 1, &widget_class);
553 gboolean show = luaH_checkboolean(L, 2);
554 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
555 webkit_web_view_set_view_source_mode(WEBKIT_WEB_VIEW(view), show);
556 if ((uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(view))))
557 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(view), uri);
558 return 0;
561 static gint
562 luaH_webview_reload(lua_State *L)
564 widget_t *w = luaH_checkudata(L, 1, &widget_class);
565 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
566 webkit_web_view_reload(WEBKIT_WEB_VIEW(view));
567 return 0;
570 static gint
571 luaH_webview_search(lua_State *L)
573 widget_t *w = luaH_checkudata(L, 1, &widget_class);
574 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
575 const gchar *text = luaL_checkstring(L, 2);
576 gboolean case_sensitive = luaH_checkboolean(L, 3);
577 gboolean forward = luaH_checkboolean(L, 4);
578 gboolean wrap = luaH_checkboolean(L, 5);
580 webkit_web_view_unmark_text_matches(view);
581 webkit_web_view_search_text(view, text, case_sensitive, forward, wrap);
582 webkit_web_view_mark_text_matches(view, text, case_sensitive, 0);
583 webkit_web_view_set_highlight_text_matches(view, TRUE);
584 return 0;
587 static gint
588 luaH_webview_clear_search(lua_State *L)
590 widget_t *w = luaH_checkudata(L, 1, &widget_class);
591 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
592 webkit_web_view_unmark_text_matches(view);
593 return 0;
596 inline static GObject*
597 get_settings_object(GtkWidget *view, property_t *p)
599 switch (p->scope) {
600 case SETTINGS:
601 return G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(view)));
602 case WEBKITVIEW:
603 return G_OBJECT(view);
604 case SOUPSESSION:
605 return G_OBJECT(Soup.session);
606 default:
607 break;
609 warn("programmer error: unknown settings scope for property: %s", p->name);
610 return NULL;
613 static gint
614 luaH_webview_get_prop(lua_State *L)
616 GObject *so;
617 SoupURI *u;
618 property_t *p;
619 temp_value_t tmp;
621 /* get webview widget */
622 widget_t *w = luaH_checkudata(L, 1, &widget_class);
623 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
625 /* get property struct */
626 const gchar *name = luaL_checkstring(L, 2);
627 if ((p = g_hash_table_lookup(properties, name))) {
628 so = get_settings_object(view, p);
630 switch(p->type) {
631 case BOOL:
632 g_object_get(so, p->name, &tmp.b, NULL);
633 lua_pushboolean(L, tmp.b);
634 return 1;
636 case INT:
637 g_object_get(so, p->name, &tmp.i, NULL);
638 lua_pushnumber(L, tmp.i);
639 return 1;
641 case FLOAT:
642 g_object_get(so, p->name, &tmp.f, NULL);
643 lua_pushnumber(L, tmp.f);
644 return 1;
646 case DOUBLE:
647 g_object_get(so, p->name, &tmp.d, NULL);
648 lua_pushnumber(L, tmp.d);
649 return 1;
651 case CHAR:
652 g_object_get(so, p->name, &tmp.c, NULL);
653 lua_pushstring(L, tmp.c);
654 g_free(tmp.c);
655 return 1;
657 case URI:
658 g_object_get(so, p->name, &u, NULL);
659 tmp.c = soup_uri_to_string(u, 0);
660 lua_pushstring(L, tmp.c);
661 soup_uri_free(u);
662 g_free(tmp.c);
663 return 1;
665 default:
666 warn("programmer error: unknown property type for: %s", p->name);
667 break;
670 warn("unknown property: %s", name);
671 return 0;
674 static gint
675 luaH_webview_set_prop(lua_State *L)
677 size_t len;
678 GObject *so;
679 SoupURI *u;
680 property_t *p;
681 temp_value_t tmp;
683 /* get webview widget */
684 widget_t *w = luaH_checkudata(L, 1, &widget_class);
685 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
687 /* get property struct */
688 const gchar *name = luaL_checkstring(L, 2);
689 if ((p = g_hash_table_lookup(properties, name))) {
690 if (!p->writable) {
691 warn("attempt to set read-only property: %s", p->name);
692 return 0;
695 so = get_settings_object(view, p);
696 switch(p->type) {
697 case BOOL:
698 tmp.b = luaH_checkboolean(L, 3);
699 g_object_set(so, p->name, tmp.b, NULL);
700 return 0;
702 case INT:
703 tmp.i = (gint) luaL_checknumber(L, 3);
704 g_object_set(so, p->name, tmp.i, NULL);
705 return 0;
707 case FLOAT:
708 tmp.f = (gfloat) luaL_checknumber(L, 3);
709 g_object_set(so, p->name, tmp.f, NULL);
710 return 0;
712 case DOUBLE:
713 tmp.d = (gdouble) luaL_checknumber(L, 3);
714 g_object_set(so, p->name, tmp.d, NULL);
715 return 0;
717 case CHAR:
718 tmp.c = (gchar*) luaL_checkstring(L, 3);
719 g_object_set(so, p->name, tmp.c, NULL);
720 return 0;
722 case URI:
723 tmp.c = (gchar*) luaL_checklstring(L, 3, &len);
724 if (!len || g_strrstr(tmp.c, "://"))
725 tmp.c = g_strdup(tmp.c);
726 else
727 tmp.c = g_strdup_printf("http://%s", tmp.c);
728 u = soup_uri_new(tmp.c);
729 if (!u || SOUP_URI_VALID_FOR_HTTP(u))
730 g_object_set(so, p->name, u, NULL);
731 else
732 luaL_error(L, "cannot parse uri: %s", tmp.c);
733 if (u) soup_uri_free(u);
734 g_free(tmp.c);
735 return 0;
737 default:
738 warn("programmer error: unknown property type for: %s", p->name);
739 break;
742 warn("unknown property: %s", name);
743 return 0;
746 static gint
747 luaH_webview_loading(lua_State *L)
749 widget_t *w = luaH_checkudata(L, 1, &widget_class);
750 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
751 WebKitLoadStatus s;
752 g_object_get(G_OBJECT(view), "load-status", &s, NULL);
753 switch (s) {
754 case WEBKIT_LOAD_PROVISIONAL:
755 case WEBKIT_LOAD_COMMITTED:
756 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
757 lua_pushboolean(L, TRUE);
758 break;
760 default:
761 lua_pushboolean(L, FALSE);
762 break;
764 return 1;
767 void
768 show_scrollbars(widget_t *w, gboolean show)
770 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
771 WebKitWebFrame *mf = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(view));
772 gulong id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(view), "hide_handler_id"));
774 if (show) {
775 if (id)
776 g_signal_handler_disconnect((gpointer) mf, id);
777 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
778 id = 0;
779 } else if (!id) {
780 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
781 id = g_signal_connect(G_OBJECT(mf), "scrollbars-policy-changed", G_CALLBACK(true_cb), NULL);
783 g_object_set_data(G_OBJECT(view), "hide_handler_id", GINT_TO_POINTER(id));
786 static gint
787 luaH_webview_index(lua_State *L, luakit_token_t token)
789 widget_t *w = luaH_checkudata(L, 1, &widget_class);
790 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
791 temp_value_t tmp;
793 switch(token) {
795 #define PF_CASE(t, f) case L_TK_##t: lua_pushcfunction(L, f); return 1;
796 /* property methods */
797 PF_CASE(GET_PROP, luaH_webview_get_prop)
798 PF_CASE(SET_PROP, luaH_webview_set_prop)
799 /* scroll adjustment methods */
800 PF_CASE(GET_SCROLL_HORIZ, luaH_webview_get_hscroll)
801 PF_CASE(GET_SCROLL_VERT, luaH_webview_get_vscroll)
802 PF_CASE(SET_SCROLL_HORIZ, luaH_webview_set_scroll_horiz)
803 PF_CASE(SET_SCROLL_VERT, luaH_webview_set_scroll_vert)
804 /* search methods */
805 PF_CASE(CLEAR_SEARCH, luaH_webview_clear_search)
806 PF_CASE(SEARCH, luaH_webview_search)
807 /* history navigation methods */
808 PF_CASE(GO_BACK, luaH_webview_go_back)
809 PF_CASE(GO_FORWARD, luaH_webview_go_forward)
810 /* misc webview methods */
811 PF_CASE(EVAL_JS, luaH_webview_eval_js)
812 PF_CASE(LOADING, luaH_webview_loading)
813 PF_CASE(RELOAD, luaH_webview_reload)
814 /* source viewing methods */
815 PF_CASE(GET_VIEW_SOURCE, luaH_webview_get_view_source)
816 PF_CASE(SET_VIEW_SOURCE, luaH_webview_set_view_source)
817 /* widget methods */
818 PF_CASE(DESTROY, luaH_widget_destroy)
819 PF_CASE(FOCUS, luaH_widget_focus)
820 PF_CASE(HIDE, luaH_widget_hide)
821 PF_CASE(SHOW, luaH_widget_show)
822 #undef PF_CASE
824 case L_TK_HOVERED_URI:
825 lua_pushstring(L, g_object_get_data(G_OBJECT(view), "hovered-uri"));
826 return 1;
828 case L_TK_URI:
829 g_object_get(G_OBJECT(view), "uri", &tmp.c, NULL);
830 lua_pushstring(L, tmp.c);
831 g_free(tmp.c);
832 return 1;
834 default:
835 warn("unknown property: %s", luaL_checkstring(L, 2));
836 break;
839 return 0;
842 /* The __newindex method for the webview object */
843 static gint
844 luaH_webview_newindex(lua_State *L, luakit_token_t token)
846 size_t len;
847 widget_t *w = luaH_checkudata(L, 1, &widget_class);
848 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
849 temp_value_t tmp;
851 switch(token)
853 case L_TK_URI:
854 tmp.c = (gchar*) luaL_checklstring(L, 3, &len);
855 if (g_strrstr(tmp.c, "://") || !g_strcmp0(tmp.c, "about:blank"))
856 tmp.c = g_strdup(tmp.c);
857 else
858 tmp.c = g_strdup_printf("http://%s", tmp.c);
859 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(view), tmp.c);
860 g_free(tmp.c);
861 break;
863 case L_TK_SHOW_SCROLLBARS:
864 show_scrollbars(w, luaH_checkboolean(L, 3));
865 return 0;
867 default:
868 warn("unknown property: %s", luaL_checkstring(L, 2));
869 return 0;
872 return luaH_object_emit_property_signal(L, 1);
875 static gboolean
876 expose_cb(GtkWidget *widget, GdkEventExpose *e, widget_t *w)
878 (void) e;
879 (void) widget;
880 lua_State *L = globalconf.L;
881 luaH_object_push(L, w->ref);
882 luaH_object_emit_signal(L, -1, "expose", 0, 0);
883 lua_pop(L, 1);
884 return FALSE;
887 static gboolean
888 wv_button_press_cb(GtkWidget *view, GdkEventButton *ev, widget_t *w)
890 if((ev->type != GDK_BUTTON_PRESS) || (ev->button != 1))
891 return FALSE;
893 /* get webview hit context */
894 WebKitHitTestResult *ht = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(view), ev);
895 guint c;
896 g_object_get(ht, "context", &c, NULL);
897 gint context = (gint) c;
899 lua_State *L = globalconf.L;
900 luaH_object_push(L, w->ref);
901 /* raise "form-active" when a user clicks on a form field and raise
902 * "root-active" when a user clicks elsewhere */
903 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
904 luaH_object_emit_signal(L, -1, "form-active", 0, 0);
905 else if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)
906 luaH_object_emit_signal(L, -1, "root-active", 0, 0);
907 lua_pop(L, 1);
908 return FALSE;
911 static void
912 webview_destructor(widget_t *w)
914 if (w->widget) {
915 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
916 gtk_widget_destroy(GTK_WIDGET(view));
917 gtk_widget_destroy(GTK_WIDGET(w->widget));
918 } else
919 warn("webview widget already destroyed");
922 widget_t *
923 widget_webview(widget_t *w)
925 w->index = luaH_webview_index;
926 w->newindex = luaH_webview_newindex;
927 w->destructor = webview_destructor;
929 /* init properties hash table */
930 if (!properties)
931 webview_init_properties();
933 /* init soup session & cookies handling */
934 if (!Soup.session) {
935 Soup.session = webkit_get_default_session();
936 gchar *cookie_file = g_build_filename(globalconf.data_dir, "cookies.txt", NULL);
937 Soup.cookiejar = soup_cookie_jar_text_new(cookie_file, FALSE);
938 soup_session_add_feature(Soup.session, (SoupSessionFeature*) Soup.cookiejar);
939 g_free(cookie_file);
942 GtkWidget *view = webkit_web_view_new();
943 w->widget = gtk_scrolled_window_new(NULL, NULL);
944 g_object_set_data(G_OBJECT(w->widget), "widget", w);
945 g_object_set_data(G_OBJECT(w->widget), "webview", view);
946 gtk_container_add(GTK_CONTAINER(w->widget), view);
948 /* set initial scrollbars state */
949 show_scrollbars(w, TRUE);
951 /* connect webview signals */
952 g_object_connect((GObject*)view,
953 "signal::button-press-event", (GCallback)wv_button_press_cb, w,
954 "signal::button-release-event", (GCallback)button_release_cb, w,
955 "signal::create-web-view", (GCallback)create_web_view_cb, w,
956 "signal::download-requested", (GCallback)download_request_cb, w,
957 "signal::expose-event", (GCallback)expose_cb, w,
958 "signal::focus-in-event", (GCallback)focus_cb, w,
959 "signal::focus-out-event", (GCallback)focus_cb, w,
960 "signal::hovering-over-link", (GCallback)link_hover_cb, w,
961 "signal::key-press-event", (GCallback)key_press_cb, w,
962 "signal::mime-type-policy-decision-requested", (GCallback)mime_type_decision_cb, w,
963 "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb, w,
964 "signal::new-window-policy-decision-requested", (GCallback)new_window_decision_cb, w,
965 "signal::notify", (GCallback)notify_cb, w,
966 "signal::notify::load-status", (GCallback)notify_load_status_cb, w,
967 "signal::parent-set", (GCallback)parent_set_cb, w,
968 NULL);
970 /* show widgets */
971 gtk_widget_show(view);
972 gtk_widget_show(w->widget);
974 return w;
977 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80