browse

browse.git
git clone git://git.lenczewski.org/browse.git
Log | Files | Refs | README | LICENSE

webview.c (8653B)


      1 #include "browse.h"
      2 
      3 struct browse_download_slot {
      4 	char uri[BROWSE_WINDOW_URL_MAX];
      5 	uint64_t bytes;
      6 	float progress;
      7 };
      8 
      9 struct browse_client {
     10 	GtkWindow *window;
     11 	GtkEventController *event_handler;
     12 
     13 	GdkClipboard *clipboard;
     14 
     15 	WebKitWebView *webview;
     16 	WebKitNetworkSession *webnetsession;
     17 
     18 	union browse_prop props[_BROWSE_PROP_TYPE_COUNT];
     19 
     20 	char title[BROWSE_WINDOW_TITLE_MAX];
     21 	char url[BROWSE_WINDOW_URL_MAX];
     22 
     23 	struct {
     24 		struct browse_download_slot buf[BROWSE_WINDOW_DOWNLOADS_MAX];
     25 		size_t ptr;
     26 	} downloads;
     27 };
     28 
     29 static gboolean
     30 event_controller_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode,
     31 				GdkModifierType state, struct browse_client *client);
     32 
     33 static void
     34 window_on_destroy(GtkWindow *window, struct browse_client *client);
     35 
     36 static void
     37 webview_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_client *client);
     38 
     39 static gboolean
     40 webview_on_decide_policy(WebKitWebView *webview, WebKitPolicyDecision *decision,
     41 			 WebKitPolicyDecisionType type, struct browse_client *client);
     42 
     43 struct browse_client *
     44 browse_new(char const *uri, struct browse_client *root)
     45 {
     46 	assert(uri);
     47 
     48 	struct browse_client *self = calloc(1, sizeof *self);
     49 	if (!self) return NULL;
     50 
     51 	self->window = GTK_WINDOW(gtk_window_new());
     52 
     53 	g_signal_connect(self->window, "destroy", G_CALLBACK(window_on_destroy), self);
     54 
     55 	self->event_handler = gtk_event_controller_key_new();
     56 
     57 	self->clipboard = gtk_widget_get_primary_clipboard(GTK_WIDGET(self->window));
     58 
     59 	if (root) {
     60 		self->webview = g_object_new(WEBKIT_TYPE_WEB_VIEW, "related-view", root->webview, NULL);
     61 	} else {
     62 		self->webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
     63 	}
     64 
     65 	g_signal_connect(self->event_handler, "key-pressed", G_CALLBACK(event_controller_on_key_pressed), self);
     66 
     67 	gtk_widget_add_controller(GTK_WIDGET(self->webview), self->event_handler);
     68 
     69 	self->webnetsession = webkit_web_view_get_network_session(self->webview);
     70 
     71 	g_signal_connect(self->webview, "load-changed", G_CALLBACK(webview_on_load_changed), self);
     72 	g_signal_connect(self->webview, "decide-policy", G_CALLBACK(webview_on_decide_policy), self);
     73 
     74 	webkit_web_view_set_settings(self->webview, browse.webkit_settings);
     75 
     76 	gtk_window_set_child(self->window, GTK_WIDGET(self->webview));
     77 
     78 	memcpy(self->props, window_default_props, sizeof window_default_props);
     79 
     80 	browse_load_uri(self, uri);
     81 
     82 	browse_update_title(self);
     83 
     84 	gtk_window_present(self->window);
     85 
     86 	browse.clients++;
     87 
     88 	return self;
     89 }
     90 
     91 void
     92 browse_update_title(struct browse_client *self)
     93 {
     94 	size_t written = snprintf(self->title, sizeof self->title,
     95 			"@%c | %s |",
     96 			self->props[BROWSE_PROP_STRICT_TLS].b ? 'T' : 't',
     97 			self->url);
     98 
     99 	for (size_t i = 0; i < ARRLEN(self->downloads.buf); i++) {
    100 		struct browse_download_slot *slot = &self->downloads.buf[i];
    101 
    102 		if (!slot->uri[0]) continue;
    103 
    104 		written += snprintf(self->title + written, sizeof self->title - written,
    105 				" %s [%0.3f%%]",
    106 				slot->uri, slot->progress);
    107 	}
    108 
    109 	gtk_window_set_title(self->window, self->title);
    110 }
    111 
    112 void
    113 browse_load_uri(struct browse_client *self, char const *uri)
    114 {
    115 	assert(uri);
    116 
    117 	if (!g_str_has_prefix(uri, "http://") && !g_str_has_prefix(uri, "https://") &&
    118 	    !g_str_has_prefix(uri, "file://") && !g_str_has_prefix(uri, "about:")) {
    119 		snprintf(self->url, sizeof self->url, search_page, uri);
    120 		uri = self->url;
    121 	}
    122 
    123 	webkit_web_view_load_uri(self->webview, uri);
    124 }
    125 
    126 static gboolean
    127 download_on_decide_destination(WebKitDownload *download, gchar const *suggested_filename, struct browse_client *client);
    128 
    129 static void
    130 download_on_received_data(WebKitDownload *download, guint64 length, struct browse_client *client);
    131 
    132 static void
    133 download_on_finished(WebKitDownload *download, struct browse_client *client);
    134 
    135 void
    136 browse_download_uri(struct browse_client *self, char const *uri)
    137 {
    138 	WebKitDownload *download = webkit_web_view_download_uri(self->webview, uri);
    139 
    140 	struct browse_download_slot *slot = &self->downloads.buf[self->downloads.ptr++ % BROWSE_WINDOW_DOWNLOADS_MAX];
    141 
    142 	strncpy(slot->uri, uri, sizeof slot->uri);
    143 	slot->bytes = 0;
    144 	slot->progress = 0;
    145 
    146 	g_signal_connect(download, "decide-destination", G_CALLBACK(download_on_decide_destination), self);
    147 	g_signal_connect(download, "received-data", G_CALLBACK(download_on_received_data), self);
    148 	g_signal_connect(download, "finished", G_CALLBACK(download_on_finished), self);
    149 }
    150 
    151 static void
    152 window_on_destroy(GtkWindow *window, struct browse_client *client)
    153 {
    154 	(void) window;
    155 
    156 	free(client);
    157 
    158 	browse.clients--;
    159 	browse.shutdown = browse.clients == 0;
    160 }
    161 
    162 static gboolean
    163 event_controller_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode,
    164 				GdkModifierType state, struct browse_client *client)
    165 {
    166 	(void) controller;
    167 	(void) keycode;
    168 
    169 	for (size_t i = 0; i < ARRLEN(keybinds); i++) {
    170 		if ((state & GDK_MODIFIER_MASK) == keybinds[i].mod && keyval == keybinds[i].key) {
    171 			keybinds[i].handler(client, &keybinds[i].arg);
    172 			return TRUE;
    173 		}
    174 	}
    175 
    176 	return FALSE;
    177 }
    178 
    179 static void
    180 webview_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_client *client)
    181 {
    182 	(void) ev;
    183 
    184 	char const *uri = webkit_web_view_get_uri(webview);
    185 	strncpy(client->url, uri, sizeof client->url);
    186 
    187 	browse_update_title(client);
    188 }
    189 
    190 static gboolean
    191 webview_on_decide_policy(WebKitWebView *webview, WebKitPolicyDecision *decision,
    192 			 WebKitPolicyDecisionType type, struct browse_client *client)
    193 {
    194 	(void) webview;
    195 
    196 	switch (type) {
    197 	case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
    198 		WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
    199 
    200 		if (webkit_navigation_action_get_frame_name(action)) {
    201 			webkit_policy_decision_ignore(decision);
    202 		} else {
    203 			webkit_policy_decision_use(decision);
    204 		}
    205 	} break;
    206 
    207 	case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: {
    208 		WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
    209 
    210 		switch (webkit_navigation_action_get_navigation_type(action)) {
    211 		case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
    212 		case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
    213 		case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
    214 		case WEBKIT_NAVIGATION_TYPE_RELOAD:
    215 		case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: {
    216 			char const *uri = webkit_uri_request_get_uri(webkit_navigation_action_get_request(action));
    217 			browse_new(uri, client);
    218 		} break;
    219 
    220 		default:
    221 			break;
    222 		}
    223 
    224 		webkit_policy_decision_ignore(decision);
    225 	} break;
    226 
    227 	case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: {
    228 		WebKitResponsePolicyDecision *response_decision = WEBKIT_RESPONSE_POLICY_DECISION(decision);
    229 		WebKitURIResponse *response = webkit_response_policy_decision_get_response(response_decision);
    230 
    231 		char const *uri = webkit_uri_response_get_uri(response);
    232 
    233 		if (g_str_has_suffix(uri, "/favicon.ico")) {
    234 			webkit_policy_decision_ignore(decision);
    235 			break;
    236 		}
    237 
    238 		if (webkit_response_policy_decision_is_mime_type_supported(response_decision)) {
    239 			webkit_policy_decision_use(decision);
    240 		} else {
    241 			webkit_policy_decision_ignore(decision);
    242 
    243 			browse_download_uri(client, uri);
    244 		}
    245 	} break;
    246 
    247 	default:
    248 		fprintf(stderr, "Unknown policy decision type: %d, ignoring\n", type);
    249 		webkit_policy_decision_ignore(decision);
    250 		break;
    251 	}
    252 
    253 	return TRUE;
    254 }
    255 
    256 static struct browse_download_slot *
    257 get_download_slot(struct browse_client *client, char const *uri)
    258 {
    259 	for (size_t i = 0; i < ARRLEN(client->downloads.buf); i++) {
    260 		struct browse_download_slot *slot = &client->downloads.buf[i];
    261 
    262 		if (strncmp(slot->uri, uri, sizeof slot->uri) == 0)
    263 			return slot;
    264 	}
    265 
    266 	return NULL;
    267 }
    268 
    269 static gboolean
    270 download_on_decide_destination(WebKitDownload *download, gchar const *suggested_filename,
    271 			       struct browse_client *client)
    272 {
    273 	(void) client;
    274 
    275 	char fpath[PATH_MAX];
    276 	snprintf(fpath, sizeof fpath, download_fpath_fmt, suggested_filename);
    277 	webkit_download_set_destination(download, fpath);
    278 
    279 	return TRUE;
    280 }
    281 
    282 static void
    283 download_on_received_data(WebKitDownload *download, guint64 length, struct browse_client *client)
    284 {
    285 	char const *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
    286 	struct browse_download_slot *slot = get_download_slot(client, uri);
    287 
    288 	if (!slot) return;
    289 
    290 	slot->progress = 100 * webkit_download_get_estimated_progress(download);
    291 	slot->bytes += length;
    292 
    293 	browse_update_title(client);
    294 }
    295 
    296 static void
    297 download_on_finished(WebKitDownload *download, struct browse_client *client)
    298 {
    299 	char const *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
    300 	struct browse_download_slot *slot = get_download_slot(client, uri);
    301 
    302 	if (!slot) return;
    303 
    304 	slot->uri[0] = '\0';
    305 
    306 	browse_update_title(client);
    307 }