Add P{B,F,N,S}_CASE & LUAKIT_WIDGET_{,BIN_}INDEX_COMMON macros
[luakit.git] / widgets / webview.c
blob8ef78487a9784565377bfcbcad4013fcd164a7ba
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;
369 gint ret;
370 gint top;
371 WebKitWebView *view = NULL;
372 widget_t *new;
374 lua_State *L = globalconf.L;
375 luaH_object_push(L, w->ref);
376 top = lua_gettop(L);
377 ret = luaH_object_emit_signal(L, top, "create-web-view", 0, 1);
378 if (ret && (new = luaH_checkudata(L, top + 1, &widget_class)))
379 view = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(new->widget), "webview"));
380 lua_pop(L, ret + 1);
381 return view;
384 static gboolean
385 download_request_cb(WebKitWebView *v, GObject *dl, widget_t *w)
387 (void) v;
388 const gchar *uri = webkit_download_get_uri((WebKitDownload *) dl);
389 const gchar *filename = webkit_download_get_suggested_filename((WebKitDownload *) dl);
391 lua_State *L = globalconf.L;
392 luaH_object_push(L, w->ref);
393 lua_pushstring(L, uri);
394 lua_pushstring(L, filename);
395 luaH_object_emit_signal(L, -3, "download-request", 2, 0);
396 lua_pop(L, 1);
398 return FALSE;
401 static void
402 link_hover_cb(WebKitWebView *view, const char *t, const gchar *link, widget_t *w)
404 (void) t;
405 lua_State *L = globalconf.L;
406 GObject *ws = G_OBJECT(view);
407 gchar *last_hover = g_object_get_data(ws, "hovered-uri");
409 /* links are identical, do nothing */
410 if (last_hover && !g_strcmp0(last_hover, link))
411 return;
413 luaH_object_push(L, w->ref);
415 if (last_hover) {
416 lua_pushstring(L, last_hover);
417 g_object_set_data(ws, "hovered-uri", NULL);
418 luaH_object_emit_signal(L, -2, "link-unhover", 1, 0);
421 if (link) {
422 lua_pushstring(L, link);
423 g_object_set_data_full(ws, "hovered-uri", g_strdup(link), g_free);
424 luaH_object_emit_signal(L, -2, "link-hover", 1, 0);
427 luaH_object_emit_signal(L, -1, "property::hovered_uri", 0, 0);
428 lua_pop(L, 1);
431 /* Raises the "navigation-request" signal on a webkit navigation policy
432 * decision request. The default action is to load the requested uri.
434 * The signal handler is able to:
435 * - return true for the handler execution to stop and the request to continue
436 * - return false for the handler execution to stop and the request to hault
437 * - do nothing and give the navigation decision to the next signal handler
439 * This signal is also where you would attach custom scheme handlers to take
440 * over the navigation request by launching an external application.
442 static gboolean
443 navigation_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
444 WebKitNetworkRequest *r, WebKitWebNavigationAction *a,
445 WebKitWebPolicyDecision *p, widget_t *w)
447 (void) v;
448 (void) f;
449 (void) a;
451 lua_State *L = globalconf.L;
452 const gchar *uri = webkit_network_request_get_uri(r);
453 gint ret;
455 luaH_object_push(L, w->ref);
456 lua_pushstring(L, uri);
457 ret = luaH_object_emit_signal(L, -2, "navigation-request", 1, 1);
459 if (ret && !luaH_checkboolean(L, -1))
460 /* User responded with false, do not continue navigation request */
461 webkit_web_policy_decision_ignore(p);
462 else
463 webkit_web_policy_decision_use(p);
465 lua_pop(L, ret + 1);
466 return TRUE;
469 inline static gint
470 luaH_adjustment_push_values(lua_State *L, GtkAdjustment *a)
472 gdouble view_size = gtk_adjustment_get_page_size(a);
473 gdouble value = gtk_adjustment_get_value(a);
474 gdouble max = gtk_adjustment_get_upper(a) - view_size;
475 lua_pushnumber(L, value);
476 lua_pushnumber(L, (max < 0 ? 0 : max));
477 lua_pushnumber(L, view_size);
478 return 3;
481 static gint
482 luaH_webview_get_vscroll(lua_State *L)
484 widget_t *w = luaH_checkudata(L, 1, &widget_class);
485 GtkAdjustment *a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
486 return luaH_adjustment_push_values(L, a);
489 static gint
490 luaH_webview_get_hscroll(lua_State *L)
492 widget_t *w = luaH_checkudata(L, 1, &widget_class);
493 GtkAdjustment *a = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
494 return luaH_adjustment_push_values(L, a);
497 inline static void
498 adjustment_set(GtkAdjustment *a, gdouble new)
500 gdouble view_size = gtk_adjustment_get_page_size(a);
501 gdouble max = gtk_adjustment_get_upper(a) - view_size;
502 gtk_adjustment_set_value(a, ((new < 0 ? 0 : new) > max ? max : new));
505 static gint
506 luaH_webview_set_scroll_vert(lua_State *L)
508 widget_t *w = luaH_checkudata(L, 1, &widget_class);
509 gdouble value = (gdouble) luaL_checknumber(L, 2);
510 GtkAdjustment *a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
511 adjustment_set(a, value);
512 return 0;
515 static gint
516 luaH_webview_set_scroll_horiz(lua_State *L)
518 widget_t *w = luaH_checkudata(L, 1, &widget_class);
519 gdouble value = (gdouble) luaL_checknumber(L, 2);
520 GtkAdjustment *a = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
521 adjustment_set(a, value);
522 return 0;
525 static gint
526 luaH_webview_go_back(lua_State *L)
528 widget_t *w = luaH_checkudata(L, 1, &widget_class);
529 gint steps = (gint) luaL_checknumber(L, 2);
530 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
531 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps * -1);
532 return 0;
535 static gint
536 luaH_webview_go_forward(lua_State *L)
538 widget_t *w = luaH_checkudata(L, 1, &widget_class);
539 gint steps = (gint) luaL_checknumber(L, 2);
540 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
541 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps);
542 return 0;
545 static gint
546 luaH_webview_get_view_source(lua_State *L)
548 widget_t *w = luaH_checkudata(L, 1, &widget_class);
549 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
550 lua_pushboolean(L, webkit_web_view_get_view_source_mode(WEBKIT_WEB_VIEW(view)));
551 return 1;
554 static gint
555 luaH_webview_set_view_source(lua_State *L)
557 const gchar *uri;
558 widget_t *w = luaH_checkudata(L, 1, &widget_class);
559 gboolean show = luaH_checkboolean(L, 2);
560 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
561 webkit_web_view_set_view_source_mode(WEBKIT_WEB_VIEW(view), show);
562 if ((uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(view))))
563 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(view), uri);
564 return 0;
567 static gint
568 luaH_webview_reload(lua_State *L)
570 widget_t *w = luaH_checkudata(L, 1, &widget_class);
571 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
572 webkit_web_view_reload(WEBKIT_WEB_VIEW(view));
573 return 0;
576 static gint
577 luaH_webview_search(lua_State *L)
579 widget_t *w = luaH_checkudata(L, 1, &widget_class);
580 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
581 const gchar *text = luaL_checkstring(L, 2);
582 gboolean case_sensitive = luaH_checkboolean(L, 3);
583 gboolean forward = luaH_checkboolean(L, 4);
584 gboolean wrap = luaH_checkboolean(L, 5);
586 webkit_web_view_unmark_text_matches(view);
587 webkit_web_view_search_text(view, text, case_sensitive, forward, wrap);
588 webkit_web_view_mark_text_matches(view, text, case_sensitive, 0);
589 webkit_web_view_set_highlight_text_matches(view, TRUE);
590 return 0;
593 static gint
594 luaH_webview_clear_search(lua_State *L)
596 widget_t *w = luaH_checkudata(L, 1, &widget_class);
597 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
598 webkit_web_view_unmark_text_matches(view);
599 return 0;
602 inline static GObject*
603 get_settings_object(GtkWidget *view, property_t *p)
605 switch (p->scope) {
606 case SETTINGS:
607 return G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(view)));
608 case WEBKITVIEW:
609 return G_OBJECT(view);
610 case SOUPSESSION:
611 return G_OBJECT(Soup.session);
612 default:
613 break;
615 warn("programmer error: unknown settings scope for property: %s", p->name);
616 return NULL;
619 static gint
620 luaH_webview_get_prop(lua_State *L)
622 GObject *so;
623 SoupURI *u;
624 property_t *p;
625 temp_value_t tmp;
627 /* get webview widget */
628 widget_t *w = luaH_checkudata(L, 1, &widget_class);
629 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
631 /* get property struct */
632 const gchar *name = luaL_checkstring(L, 2);
633 if ((p = g_hash_table_lookup(properties, name))) {
634 so = get_settings_object(view, p);
636 switch(p->type) {
637 case BOOL:
638 g_object_get(so, p->name, &tmp.b, NULL);
639 lua_pushboolean(L, tmp.b);
640 return 1;
642 case INT:
643 g_object_get(so, p->name, &tmp.i, NULL);
644 lua_pushnumber(L, tmp.i);
645 return 1;
647 case FLOAT:
648 g_object_get(so, p->name, &tmp.f, NULL);
649 lua_pushnumber(L, tmp.f);
650 return 1;
652 case DOUBLE:
653 g_object_get(so, p->name, &tmp.d, NULL);
654 lua_pushnumber(L, tmp.d);
655 return 1;
657 case CHAR:
658 g_object_get(so, p->name, &tmp.c, NULL);
659 lua_pushstring(L, tmp.c);
660 g_free(tmp.c);
661 return 1;
663 case URI:
664 g_object_get(so, p->name, &u, NULL);
665 tmp.c = soup_uri_to_string(u, 0);
666 lua_pushstring(L, tmp.c);
667 soup_uri_free(u);
668 g_free(tmp.c);
669 return 1;
671 default:
672 warn("programmer error: unknown property type for: %s", p->name);
673 break;
676 warn("unknown property: %s", name);
677 return 0;
680 static gint
681 luaH_webview_set_prop(lua_State *L)
683 size_t len;
684 GObject *so;
685 SoupURI *u;
686 property_t *p;
687 temp_value_t tmp;
689 /* get webview widget */
690 widget_t *w = luaH_checkudata(L, 1, &widget_class);
691 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
693 /* get property struct */
694 const gchar *name = luaL_checkstring(L, 2);
695 if ((p = g_hash_table_lookup(properties, name))) {
696 if (!p->writable) {
697 warn("attempt to set read-only property: %s", p->name);
698 return 0;
701 so = get_settings_object(view, p);
702 switch(p->type) {
703 case BOOL:
704 tmp.b = luaH_checkboolean(L, 3);
705 g_object_set(so, p->name, tmp.b, NULL);
706 return 0;
708 case INT:
709 tmp.i = (gint) luaL_checknumber(L, 3);
710 g_object_set(so, p->name, tmp.i, NULL);
711 return 0;
713 case FLOAT:
714 tmp.f = (gfloat) luaL_checknumber(L, 3);
715 g_object_set(so, p->name, tmp.f, NULL);
716 return 0;
718 case DOUBLE:
719 tmp.d = (gdouble) luaL_checknumber(L, 3);
720 g_object_set(so, p->name, tmp.d, NULL);
721 return 0;
723 case CHAR:
724 tmp.c = (gchar*) luaL_checkstring(L, 3);
725 g_object_set(so, p->name, tmp.c, NULL);
726 return 0;
728 case URI:
729 tmp.c = (gchar*) luaL_checklstring(L, 3, &len);
730 if (!len || g_strrstr(tmp.c, "://"))
731 tmp.c = g_strdup(tmp.c);
732 else
733 tmp.c = g_strdup_printf("http://%s", tmp.c);
734 u = soup_uri_new(tmp.c);
735 if (!u || SOUP_URI_VALID_FOR_HTTP(u))
736 g_object_set(so, p->name, u, NULL);
737 else
738 luaL_error(L, "cannot parse uri: %s", tmp.c);
739 if (u) soup_uri_free(u);
740 g_free(tmp.c);
741 return 0;
743 default:
744 warn("programmer error: unknown property type for: %s", p->name);
745 break;
748 warn("unknown property: %s", name);
749 return 0;
752 static gint
753 luaH_webview_loading(lua_State *L)
755 widget_t *w = luaH_checkudata(L, 1, &widget_class);
756 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
757 WebKitLoadStatus s;
758 g_object_get(G_OBJECT(view), "load-status", &s, NULL);
759 switch (s) {
760 case WEBKIT_LOAD_PROVISIONAL:
761 case WEBKIT_LOAD_COMMITTED:
762 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
763 lua_pushboolean(L, TRUE);
764 break;
766 default:
767 lua_pushboolean(L, FALSE);
768 break;
770 return 1;
773 void
774 show_scrollbars(widget_t *w, gboolean show)
776 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
777 WebKitWebFrame *mf = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(view));
778 gulong id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(view), "hide_handler_id"));
780 if (show) {
781 if (id)
782 g_signal_handler_disconnect((gpointer) mf, id);
783 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
784 id = 0;
785 } else if (!id) {
786 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
787 id = g_signal_connect(G_OBJECT(mf), "scrollbars-policy-changed", G_CALLBACK(true_cb), NULL);
789 g_object_set_data(G_OBJECT(view), "hide_handler_id", GINT_TO_POINTER(id));
792 static gint
793 luaH_webview_index(lua_State *L, luakit_token_t token)
795 widget_t *w = luaH_checkudata(L, 1, &widget_class);
796 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
797 temp_value_t tmp;
799 switch(token)
801 LUAKIT_WIDGET_INDEX_COMMON
803 /* push property methods */
804 PF_CASE(GET_PROP, luaH_webview_get_prop)
805 PF_CASE(SET_PROP, luaH_webview_set_prop)
806 /* push scroll adjustment methods */
807 PF_CASE(GET_SCROLL_HORIZ, luaH_webview_get_hscroll)
808 PF_CASE(GET_SCROLL_VERT, luaH_webview_get_vscroll)
809 PF_CASE(SET_SCROLL_HORIZ, luaH_webview_set_scroll_horiz)
810 PF_CASE(SET_SCROLL_VERT, luaH_webview_set_scroll_vert)
811 /* push search methods */
812 PF_CASE(CLEAR_SEARCH, luaH_webview_clear_search)
813 PF_CASE(SEARCH, luaH_webview_search)
814 /* push history navigation methods */
815 PF_CASE(GO_BACK, luaH_webview_go_back)
816 PF_CASE(GO_FORWARD, luaH_webview_go_forward)
817 /* push misc webview methods */
818 PF_CASE(EVAL_JS, luaH_webview_eval_js)
819 PF_CASE(LOADING, luaH_webview_loading)
820 PF_CASE(RELOAD, luaH_webview_reload)
821 /* push source viewing methods */
822 PF_CASE(GET_VIEW_SOURCE, luaH_webview_get_view_source)
823 PF_CASE(SET_VIEW_SOURCE, luaH_webview_set_view_source)
825 /* push string properties */
826 PS_CASE(HOVERED_URI, g_object_get_data(G_OBJECT(view), "hovered-uri"))
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 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
915 gtk_widget_destroy(GTK_WIDGET(view));
916 gtk_widget_destroy(GTK_WIDGET(w->widget));
919 widget_t *
920 widget_webview(widget_t *w)
922 w->index = luaH_webview_index;
923 w->newindex = luaH_webview_newindex;
924 w->destructor = webview_destructor;
926 /* init properties hash table */
927 if (!properties)
928 webview_init_properties();
930 /* init soup session & cookies handling */
931 if (!Soup.session) {
932 Soup.session = webkit_get_default_session();
933 gchar *cookie_file = g_build_filename(globalconf.data_dir, "cookies.txt", NULL);
934 Soup.cookiejar = soup_cookie_jar_text_new(cookie_file, FALSE);
935 soup_session_add_feature(Soup.session, (SoupSessionFeature*) Soup.cookiejar);
936 g_free(cookie_file);
939 GtkWidget *view = webkit_web_view_new();
940 w->widget = gtk_scrolled_window_new(NULL, NULL);
941 g_object_set_data(G_OBJECT(w->widget), "widget", w);
942 g_object_set_data(G_OBJECT(w->widget), "webview", view);
943 gtk_container_add(GTK_CONTAINER(w->widget), view);
945 /* set initial scrollbars state */
946 show_scrollbars(w, TRUE);
948 /* connect webview signals */
949 g_object_connect((GObject*)view,
950 "signal::button-press-event", (GCallback)wv_button_press_cb, w,
951 "signal::button-release-event", (GCallback)button_release_cb, w,
952 "signal::create-web-view", (GCallback)create_web_view_cb, w,
953 "signal::download-requested", (GCallback)download_request_cb, w,
954 "signal::expose-event", (GCallback)expose_cb, w,
955 "signal::focus-in-event", (GCallback)focus_cb, w,
956 "signal::focus-out-event", (GCallback)focus_cb, w,
957 "signal::hovering-over-link", (GCallback)link_hover_cb, w,
958 "signal::key-press-event", (GCallback)key_press_cb, w,
959 "signal::mime-type-policy-decision-requested", (GCallback)mime_type_decision_cb, w,
960 "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb, w,
961 "signal::new-window-policy-decision-requested", (GCallback)new_window_decision_cb, w,
962 "signal::notify", (GCallback)notify_cb, w,
963 "signal::notify::load-status", (GCallback)notify_load_status_cb, w,
964 "signal::parent-set", (GCallback)parent_set_cb, w,
965 NULL);
967 /* show widgets */
968 gtk_widget_show(view);
969 gtk_widget_show(w->widget);
971 return w;
974 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80