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 }