badwolf.c 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. // BadWolf: Minimalist and privacy-oriented WebKitGTK+ browser
  2. // Copyright © 2019-2020 Badwolf Authors <https://hacktivis.me/projects/badwolf>
  3. // SPDX-License-Identifier: BSD-3-Clause
  4. #include "badwolf.h"
  5. #include "config.h"
  6. #include "downloads.h"
  7. #include "keybindings.h"
  8. #include "uri.h"
  9. #include <glib/gi18n.h> /* _() and other internationalization/localization helpers */
  10. #include <libsoup/soup.h> /* soup* */
  11. #include <locale.h> /* LC_* */
  12. #include <stdio.h> /* perror(), fprintf(), snprintf() */
  13. #include <stdlib.h> /* malloc() */
  14. #include <unistd.h> /* access() */
  15. const gchar *homepage = "https://hacktivis.me/projects/badwolf";
  16. const gchar *version = VERSION;
  17. config_t config;
  18. static gchar *web_extensions_directory;
  19. static int context_id_counter = 0;
  20. static gboolean WebViewCb_close(WebKitWebView *webView, gpointer user_data);
  21. static gboolean WebViewCb_web_process_terminated(WebKitWebView *webView,
  22. WebKitWebProcessTerminationReason reason,
  23. gpointer user_data);
  24. static gboolean
  25. WebViewCb_notify__uri(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  26. static gboolean
  27. WebViewCb_notify__title(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  28. static gboolean
  29. WebViewCb_notify__is__playing__audio(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  30. static gboolean WebViewCb_notify__estimated_load_progress(WebKitWebView *webView,
  31. GParamSpec *pspec,
  32. gpointer user_data);
  33. static gboolean WebViewCb_mouse_target_changed(WebKitWebView *webView,
  34. WebKitHitTestResult *hit,
  35. guint modifiers,
  36. gpointer user_data);
  37. static WebKitWebView *WebViewCb_create(WebKitWebView *related_web_view,
  38. WebKitNavigationAction *navigation_action,
  39. gpointer user_data);
  40. static gboolean WebViewCb_permission_request(WebKitWebView *web_view,
  41. WebKitPermissionRequest *request,
  42. gpointer user_data);
  43. static gboolean WebViewCb_decide_policy(WebKitWebView *web_view,
  44. WebKitPolicyDecision *decision,
  45. WebKitPolicyDecisionType decision_type,
  46. gpointer user_data);
  47. static void
  48. WebViewCb_load_changed(WebKitWebView *webView, WebKitLoadEvent load_event, gpointer user_data);
  49. static void web_contextCb_download_started(WebKitWebContext *web_context,
  50. WebKitDownload *download,
  51. gpointer user_data);
  52. static gboolean locationCb_activate(GtkEntry *location, gpointer user_data);
  53. static gboolean javascriptCb_toggled(GtkButton *javascript, gpointer user_data);
  54. static gboolean auto_load_imagesCb_toggled(GtkButton *auto_load_images, gpointer user_data);
  55. static void backCb_clicked(GtkButton *back, gpointer user_data);
  56. static void forwardCb_clicked(GtkButton *forward, gpointer user_data);
  57. static void printCb_clicked(GtkButton *forward, gpointer user_data);
  58. static gboolean SearchEntryCb_next__match(GtkSearchEntry *search, gpointer user_data);
  59. static gboolean SearchEntryCb_previous__match(GtkSearchEntry *search, gpointer user_data);
  60. static gboolean SearchEntryCb_search__changed(GtkSearchEntry *search, gpointer user_data);
  61. static gboolean SearchEntryCb_stop__search(GtkSearchEntry *search, gpointer user_data);
  62. static void new_tabCb_clicked(GtkButton *new_tab, gpointer user_data);
  63. static void closeCb_clicked(GtkButton *close, gpointer user_data);
  64. static void
  65. notebookCb_switch__page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data);
  66. static gboolean
  67. WebViewCb_close(WebKitWebView *webView, gpointer user_data)
  68. {
  69. (void)webView;
  70. struct Client *browser = (struct Client *)user_data;
  71. GtkNotebook *notebook = GTK_NOTEBOOK(browser->window->notebook);
  72. gtk_notebook_remove_page(notebook, gtk_notebook_page_num(GTK_NOTEBOOK(notebook), browser->box));
  73. gtk_widget_destroy(browser->box);
  74. free(browser);
  75. return TRUE;
  76. }
  77. static gboolean
  78. WebViewCb_web_process_terminated(WebKitWebView *webView,
  79. WebKitWebProcessTerminationReason reason,
  80. gpointer user_data)
  81. {
  82. (void)webView;
  83. struct Client *browser = (struct Client *)user_data;
  84. switch(reason)
  85. {
  86. case WEBKIT_WEB_PROCESS_CRASHED:
  87. fprintf(stderr, "%s", _("the web process crashed.\n"));
  88. webView_tab_label_change(browser, _("Crashed"));
  89. break;
  90. case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT:
  91. fprintf(stderr, "%s", _("the web process exceeded the memory limit.\n"));
  92. webView_tab_label_change(browser, _("Out of Memory"));
  93. break;
  94. default:
  95. fprintf(stderr, "%s", _("the web process terminated for an unknown reason.\n"));
  96. webView_tab_label_change(browser, _("Unknown Crash"));
  97. }
  98. return FALSE;
  99. }
  100. static gboolean
  101. WebViewCb_notify__uri(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  102. {
  103. (void)webView;
  104. (void)pspec;
  105. const gchar *location_uri;
  106. struct Client *browser = (struct Client *)user_data;
  107. location_uri = webkit_web_view_get_uri(browser->webView);
  108. gtk_entry_set_text(GTK_ENTRY(browser->location), location_uri);
  109. if(webkit_uri_for_display(location_uri) != location_uri)
  110. gtk_widget_set_tooltip_text(browser->location, webkit_uri_for_display(location_uri));
  111. else
  112. gtk_widget_set_has_tooltip(browser->location, false);
  113. return TRUE;
  114. }
  115. GtkWidget *
  116. badwolf_new_tab_box(const gchar *title, struct Client *browser)
  117. {
  118. (void)browser;
  119. char context_id_str[7];
  120. int tab_box_width;
  121. int tab_label_charwidth = BADWOLF_TAB_LABEL_CHARWIDTH;
  122. int tab_hexpand = false;
  123. snprintf(context_id_str, 7, "%4X: ", browser->context_id);
  124. GtkWidget *tab_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  125. gtk_widget_set_name(tab_box, "browser__tabbox");
  126. GtkWidget *close =
  127. gtk_button_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
  128. gtk_widget_set_name(close, "browser__tabbox__close");
  129. GtkWidget *label = gtk_label_new(title);
  130. gtk_widget_set_name(label, "browser__tabbox__label");
  131. GtkWidget *context_label = gtk_label_new(context_id_str);
  132. gtk_widget_set_name(label, "browser__tabbox__context_label");
  133. GtkWidget *playing =
  134. gtk_image_new_from_icon_name("audio-volume-high-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  135. gtk_widget_set_name(playing, "browser__tabbox__playing");
  136. if(config_lookup_int(&config, "tab.box_width", &tab_box_width) == CONFIG_TRUE)
  137. {
  138. gtk_widget_set_size_request(label, tab_box_width, -1);
  139. }
  140. else
  141. {
  142. config_lookup_int(&config, "tab.label_charwidth", &tab_label_charwidth);
  143. gtk_label_set_width_chars(GTK_LABEL(label), tab_label_charwidth);
  144. }
  145. config_lookup_bool(&config, "tab.hexpand", &tab_hexpand);
  146. gtk_widget_set_hexpand(tab_box, tab_hexpand);
  147. gtk_label_set_ellipsize(GTK_LABEL(label), BADWOLF_TAB_LABEL_ELLIPSIZE);
  148. gtk_label_set_single_line_mode(GTK_LABEL(label), TRUE);
  149. gtk_label_set_single_line_mode(GTK_LABEL(context_label), TRUE);
  150. gtk_box_pack_start(GTK_BOX(tab_box), playing, FALSE, FALSE, 0);
  151. gtk_box_pack_start(GTK_BOX(tab_box), context_label, TRUE, TRUE, 0);
  152. gtk_box_pack_start(GTK_BOX(tab_box), label, TRUE, TRUE, 0);
  153. gtk_box_pack_start(GTK_BOX(tab_box), close, FALSE, FALSE, 0);
  154. gtk_button_set_relief(GTK_BUTTON(close), GTK_RELIEF_NONE);
  155. g_signal_connect(close, "clicked", G_CALLBACK(closeCb_clicked), browser);
  156. gtk_widget_set_tooltip_text(tab_box, title);
  157. gtk_widget_show_all(tab_box);
  158. gtk_widget_set_visible(playing, webkit_web_view_is_playing_audio(browser->webView));
  159. return tab_box;
  160. }
  161. static gboolean
  162. WebViewCb_notify__title(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  163. {
  164. (void)webView;
  165. (void)pspec;
  166. struct Client *browser = (struct Client *)user_data;
  167. webView_tab_label_change(browser, NULL);
  168. return TRUE;
  169. }
  170. static gboolean
  171. WebViewCb_notify__is__playing__audio(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  172. {
  173. (void)webView;
  174. (void)pspec;
  175. struct Client *browser = (struct Client *)user_data;
  176. webView_tab_label_change(browser, NULL);
  177. return TRUE;
  178. }
  179. void
  180. webView_tab_label_change(struct Client *browser, const gchar *title)
  181. {
  182. GtkWidget *notebook = browser->window->notebook;
  183. #define title_IS_EMPTY (title == NULL) || strnlen(title, 2) == 0
  184. if(title_IS_EMPTY) title = webkit_web_view_get_title(browser->webView);
  185. if(title_IS_EMPTY) title = webkit_web_view_get_uri(browser->webView);
  186. if(title_IS_EMPTY) title = "BadWolf";
  187. gtk_notebook_set_tab_label(
  188. GTK_NOTEBOOK(notebook), browser->box, badwolf_new_tab_box(title, browser));
  189. gtk_notebook_set_menu_label_text(GTK_NOTEBOOK(notebook), browser->box, title);
  190. // Set the window title if the title change was on the current tab
  191. if(gtk_notebook_page_num(GTK_NOTEBOOK(notebook), browser->box) ==
  192. gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)))
  193. gtk_window_set_title(GTK_WINDOW(browser->window->main_window), title);
  194. }
  195. static gboolean
  196. WebViewCb_notify__estimated_load_progress(WebKitWebView *webView,
  197. GParamSpec *pspec,
  198. gpointer user_data)
  199. {
  200. (void)webView;
  201. (void)pspec;
  202. struct Client *browser = (struct Client *)user_data;
  203. gdouble progress;
  204. progress = webkit_web_view_get_estimated_load_progress(browser->webView);
  205. if(progress >= 1) progress = 0;
  206. gtk_entry_set_progress_fraction(GTK_ENTRY(browser->location), progress);
  207. return TRUE;
  208. }
  209. static gboolean
  210. WebViewCb_mouse_target_changed(WebKitWebView *webView,
  211. WebKitHitTestResult *hit,
  212. guint modifiers,
  213. gpointer user_data)
  214. {
  215. (void)webView;
  216. (void)modifiers;
  217. struct Client *browser = (struct Client *)user_data;
  218. if(webkit_hit_test_result_context_is_link(hit))
  219. {
  220. const gchar *link_uri = webkit_hit_test_result_get_link_uri(hit);
  221. gtk_label_set_text(GTK_LABEL(browser->statuslabel), webkit_uri_for_display(link_uri));
  222. }
  223. else
  224. gtk_label_set_text(GTK_LABEL(browser->statuslabel), NULL);
  225. return FALSE;
  226. }
  227. static gboolean
  228. WebViewCb_scroll_event(GtkWidget *widget, GdkEvent *event, gpointer data)
  229. {
  230. (void)widget;
  231. struct Client *browser = (struct Client *)data;
  232. gdouble delta_x, delta_y;
  233. gdouble zoom;
  234. if(((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
  235. {
  236. gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
  237. zoom = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(browser->webView));
  238. zoom -= delta_y * 0.1;
  239. webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(browser->webView), zoom);
  240. return TRUE;
  241. }
  242. return FALSE;
  243. }
  244. static WebKitWebView *
  245. WebViewCb_create(WebKitWebView *related_web_view,
  246. WebKitNavigationAction *navigation_action,
  247. gpointer user_data)
  248. {
  249. (void)navigation_action;
  250. struct Client *old_browser = (struct Client *)user_data;
  251. struct Client *browser = NULL;
  252. // shouldn't be needed but better be safe
  253. old_browser->webView = related_web_view;
  254. browser = new_browser(old_browser->window, NULL, old_browser);
  255. if(badwolf_new_tab(GTK_NOTEBOOK(old_browser->window->notebook), browser, FALSE) < 0)
  256. return NULL;
  257. else
  258. return browser->webView;
  259. }
  260. static gboolean
  261. WebViewCb_permission_request(WebKitWebView *web_view,
  262. WebKitPermissionRequest *request,
  263. gpointer user_data)
  264. {
  265. (void)web_view;
  266. (void)user_data;
  267. webkit_permission_request_deny(request);
  268. return TRUE; /* Stop other handlers */
  269. }
  270. static gboolean
  271. WebViewCb_decide_policy(WebKitWebView *web_view,
  272. WebKitPolicyDecision *decision,
  273. WebKitPolicyDecisionType decision_type,
  274. gpointer user_data)
  275. {
  276. WebKitResponsePolicyDecision *r;
  277. (void)web_view;
  278. (void)user_data;
  279. switch(decision_type)
  280. {
  281. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  282. r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
  283. if(!webkit_response_policy_decision_is_mime_type_supported(r))
  284. webkit_policy_decision_download(decision);
  285. else
  286. webkit_policy_decision_use(decision);
  287. break;
  288. default:
  289. /* Use whatever default there is. */
  290. return FALSE;
  291. }
  292. return TRUE;
  293. }
  294. static void
  295. WebViewCb_load_changed(WebKitWebView *webView, WebKitLoadEvent load_event, gpointer user_data)
  296. {
  297. (void)webView;
  298. (void)load_event;
  299. struct Client *browser = (struct Client *)user_data;
  300. gtk_widget_set_sensitive(browser->back, webkit_web_view_can_go_back(browser->webView));
  301. gtk_widget_set_sensitive(browser->forward, webkit_web_view_can_go_forward(browser->webView));
  302. }
  303. static char *
  304. detail_tls_certificate_flags(GTlsCertificateFlags tls_errors)
  305. {
  306. GString *errors = g_string_new(NULL);
  307. g_string_append_printf(errors,
  308. _("Couldn't verify the TLS certificate to ensure a better security of the "
  309. "connection. You might want to verify your machine and network.\n\n"));
  310. if(tls_errors & G_TLS_CERTIFICATE_UNKNOWN_CA)
  311. g_string_append_printf(errors, _("Error: The X509 Certificate Authority is unknown.\n"));
  312. if(tls_errors & G_TLS_CERTIFICATE_BAD_IDENTITY)
  313. g_string_append(errors, _("Error: The given identity doesn't match the expected one.\n"));
  314. if(tls_errors & G_TLS_CERTIFICATE_NOT_ACTIVATED)
  315. g_string_append(errors,
  316. _("Error: The certificate isn't valid yet. Check your system's clock.\n"));
  317. if(tls_errors & G_TLS_CERTIFICATE_EXPIRED)
  318. g_string_append(errors, _("Error: The certificate has expired. Check your system's clock.\n"));
  319. if(tls_errors & G_TLS_CERTIFICATE_REVOKED)
  320. g_string_append(errors, _("Error: The certificate has been revoked.\n"));
  321. if(tls_errors & G_TLS_CERTIFICATE_INSECURE)
  322. g_string_append(errors, _("Error: The certificate is considered to be insecure.\n"));
  323. if(tls_errors & G_TLS_CERTIFICATE_GENERIC_ERROR)
  324. g_string_append(errors, _("Error: Some unknown error occurred validating the certificate.\n"));
  325. return g_string_free(errors, FALSE);
  326. }
  327. static gboolean
  328. WebViewCb_load_failed_with_tls_errors(WebKitWebView *web_view,
  329. gchar *failing_text,
  330. GTlsCertificate *certificate,
  331. GTlsCertificateFlags errors,
  332. gpointer user_data)
  333. {
  334. (void)web_view;
  335. (void)certificate;
  336. (void)errors;
  337. struct Client *browser = (struct Client *)user_data;
  338. gchar *error_details = detail_tls_certificate_flags(errors);
  339. gint dialog_response;
  340. SoupURI *failing_uri = soup_uri_new(failing_text);
  341. GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(browser->window->main_window),
  342. GTK_DIALOG_MODAL & GTK_DIALOG_DESTROY_WITH_PARENT,
  343. GTK_MESSAGE_ERROR,
  344. GTK_BUTTONS_NONE,
  345. _("TLS Error for %s."),
  346. failing_text);
  347. gtk_dialog_add_buttons(
  348. GTK_DIALOG(dialog), _("Temporarily Add Exception"), 1, _("Continue"), 0, NULL);
  349. gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0);
  350. gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s\n", error_details);
  351. dialog_response = gtk_dialog_run(GTK_DIALOG(dialog));
  352. if(dialog_response == 1)
  353. {
  354. webkit_web_context_allow_tls_certificate_for_host(
  355. webkit_web_view_get_context(browser->webView), certificate, failing_uri->host);
  356. webkit_web_view_reload(browser->webView);
  357. }
  358. soup_uri_free(failing_uri);
  359. g_free(error_details);
  360. gtk_widget_destroy(dialog);
  361. return FALSE; /* propagate the event further */
  362. }
  363. static void
  364. web_contextCb_download_started(WebKitWebContext *web_context,
  365. WebKitDownload *webkit_download,
  366. gpointer user_data)
  367. {
  368. (void)web_context;
  369. struct Client *browser = (struct Client *)user_data;
  370. struct Download *download = malloc(sizeof(struct Client));
  371. if(download != NULL)
  372. {
  373. download->window = browser->window;
  374. download_new_entry(webkit_download, download);
  375. g_signal_connect(
  376. G_OBJECT(webkit_download), "received-data", G_CALLBACK(downloadCb_received_data), download);
  377. g_signal_connect(G_OBJECT(webkit_download),
  378. "created-destination",
  379. G_CALLBACK(downloadCb_created_destination),
  380. download);
  381. g_signal_connect(G_OBJECT(webkit_download), "failed", G_CALLBACK(downloadCb_failed), download);
  382. g_signal_connect(
  383. G_OBJECT(webkit_download), "finished", G_CALLBACK(downloadCb_finished), download);
  384. }
  385. g_signal_connect(G_OBJECT(webkit_download),
  386. "decide-destination",
  387. G_CALLBACK(downloadCb_decide_destination),
  388. user_data);
  389. }
  390. static gboolean
  391. locationCb_activate(GtkEntry *location, gpointer user_data)
  392. {
  393. struct Client *browser = (struct Client *)user_data;
  394. webkit_web_view_load_uri(browser->webView,
  395. badwolf_ensure_uri_scheme(gtk_entry_get_text(location), TRUE));
  396. return TRUE;
  397. }
  398. static gboolean
  399. javascriptCb_toggled(GtkButton *javascript, gpointer user_data)
  400. {
  401. struct Client *browser = (struct Client *)user_data;
  402. WebKitSettings *settings = webkit_web_view_get_settings(browser->webView);
  403. webkit_settings_set_enable_javascript_markup(
  404. settings, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(javascript)));
  405. webkit_web_view_set_settings(browser->webView, settings);
  406. return TRUE;
  407. }
  408. static gboolean
  409. auto_load_imagesCb_toggled(GtkButton *auto_load_images, gpointer user_data)
  410. {
  411. struct Client *browser = (struct Client *)user_data;
  412. WebKitSettings *settings = webkit_web_view_get_settings(browser->webView);
  413. webkit_settings_set_auto_load_images(
  414. settings, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(auto_load_images)));
  415. webkit_web_view_set_settings(browser->webView, settings);
  416. return TRUE;
  417. }
  418. static void
  419. backCb_clicked(GtkButton *back, gpointer user_data)
  420. {
  421. (void)back;
  422. struct Client *browser = (struct Client *)user_data;
  423. webkit_web_view_go_back(browser->webView);
  424. }
  425. static void
  426. forwardCb_clicked(GtkButton *forward, gpointer user_data)
  427. {
  428. (void)forward;
  429. struct Client *browser = (struct Client *)user_data;
  430. webkit_web_view_go_forward(browser->webView);
  431. }
  432. static void
  433. printCb_clicked(GtkButton *print, gpointer user_data)
  434. {
  435. (void)print;
  436. struct Client *browser = (struct Client *)user_data;
  437. WebKitPrintOperation *print_operation = webkit_print_operation_new(browser->webView);
  438. webkit_print_operation_run_dialog(print_operation, GTK_WINDOW(browser->window));
  439. }
  440. static gboolean
  441. SearchEntryCb_next__match(GtkSearchEntry *search, gpointer user_data)
  442. {
  443. (void)search;
  444. struct Client *browser = (struct Client *)user_data;
  445. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  446. webkit_find_controller_search_next(findController);
  447. return TRUE;
  448. }
  449. static gboolean
  450. SearchEntryCb_previous__match(GtkSearchEntry *search, gpointer user_data)
  451. {
  452. (void)search;
  453. struct Client *browser = (struct Client *)user_data;
  454. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  455. webkit_find_controller_search_previous(findController);
  456. return TRUE;
  457. }
  458. static gboolean
  459. SearchEntryCb_search__changed(GtkSearchEntry *search, gpointer user_data)
  460. {
  461. struct Client *browser = (struct Client *)user_data;
  462. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  463. const gchar *search_text = gtk_entry_get_text(GTK_ENTRY(search));
  464. webkit_find_controller_search(findController, search_text, 0, 0);
  465. return TRUE;
  466. }
  467. static gboolean
  468. SearchEntryCb_stop__search(GtkSearchEntry *search, gpointer user_data)
  469. {
  470. (void)search;
  471. struct Client *browser = (struct Client *)user_data;
  472. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  473. webkit_find_controller_search_finish(findController);
  474. return TRUE;
  475. }
  476. static gboolean
  477. widgetCb_drop_button3_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
  478. {
  479. (void)widget;
  480. (void)user_data;
  481. // Button3 being right-click on right-handed mode, left-click on left-handed mode
  482. return ((GdkEventButton *)event)->button == 3;
  483. }
  484. struct Client *
  485. new_browser(struct Window *window, const gchar *target_url, struct Client *old_browser)
  486. {
  487. struct Client *browser = malloc(sizeof(struct Client));
  488. target_url = badwolf_ensure_uri_scheme(target_url, (old_browser == NULL));
  489. char *badwolf_l10n = NULL;
  490. if(browser == NULL) return NULL;
  491. browser->window = window;
  492. browser->context_id = old_browser == NULL ? context_id_counter++ : old_browser->context_id;
  493. browser->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  494. gtk_widget_set_name(browser->box, "browser__box");
  495. browser->toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  496. gtk_widget_set_name(browser->toolbar, "browser__toolbar");
  497. browser->back =
  498. gtk_button_new_from_icon_name("go-previous-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
  499. gtk_widget_set_name(browser->back, "browser__back");
  500. browser->forward = gtk_button_new_from_icon_name("go-next-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  501. gtk_widget_set_name(browser->forward, "browser__forward");
  502. GtkWidget *toolbar_separator = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
  503. browser->javascript = gtk_toggle_button_new_with_mnemonic(_("_JS"));
  504. gtk_widget_set_name(browser->javascript, "browser__javascript");
  505. gtk_widget_set_tooltip_text(browser->javascript, _("Toggle javascript"));
  506. gtk_button_set_relief(GTK_BUTTON(browser->javascript), GTK_RELIEF_NONE);
  507. browser->auto_load_images = gtk_toggle_button_new_with_mnemonic(_("_IMG"));
  508. gtk_widget_set_name(browser->auto_load_images, "browser__load_images");
  509. gtk_widget_set_tooltip_text(browser->auto_load_images, _("Toggle loading images automatically"));
  510. gtk_button_set_relief(GTK_BUTTON(browser->auto_load_images), GTK_RELIEF_NONE);
  511. browser->location = gtk_entry_new();
  512. gtk_widget_set_name(browser->location, "browser__location");
  513. GtkWidget *print =
  514. gtk_button_new_from_icon_name("document-print-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  515. gtk_widget_set_name(browser->back, "browser__print");
  516. browser->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  517. gtk_widget_set_name(browser->statusbar, "browser__statusbar");
  518. browser->search = gtk_search_entry_new();
  519. gtk_widget_set_name(browser->search, "browser__search");
  520. browser->statuslabel = gtk_label_new(NULL);
  521. gtk_widget_set_name(browser->statuslabel, "browser__statuslabel");
  522. setenv("GTK_THEME", ":light", 0);
  523. WebKitWebContext *web_context = webkit_web_context_new_ephemeral();
  524. webkit_web_context_set_sandbox_enabled(web_context, TRUE);
  525. webkit_web_context_set_process_model(web_context,
  526. WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
  527. webkit_web_context_set_web_extensions_directory(web_context, web_extensions_directory);
  528. badwolf_l10n = getenv("BADWOLF_L10N");
  529. if(badwolf_l10n != NULL)
  530. {
  531. gchar **languages = g_strsplit(badwolf_l10n, ":", -1);
  532. webkit_web_context_set_spell_checking_languages(web_context, (const gchar *const *)languages);
  533. g_strfreev(languages);
  534. webkit_web_context_set_spell_checking_enabled(web_context, TRUE);
  535. }
  536. WebKitSettings *settings = webkit_settings_new_with_settings(BADWOLF_WEBKIT_SETTINGS);
  537. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(browser->javascript),
  538. webkit_settings_get_enable_javascript_markup(settings));
  539. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(browser->auto_load_images),
  540. webkit_settings_get_auto_load_images(settings));
  541. browser->webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
  542. "web-context",
  543. web_context,
  544. "related-view",
  545. old_browser == NULL ? NULL : old_browser->webView,
  546. "settings",
  547. settings,
  548. NULL));
  549. gtk_widget_set_name(GTK_WIDGET(browser->webView), "browser__webView");
  550. gtk_box_pack_start(
  551. GTK_BOX(browser->toolbar), GTK_WIDGET(browser->back), FALSE, FALSE, BADWOLF_TOOLBAR_PADDING);
  552. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  553. GTK_WIDGET(browser->forward),
  554. FALSE,
  555. FALSE,
  556. BADWOLF_TOOLBAR_PADDING);
  557. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  558. toolbar_separator,
  559. FALSE,
  560. FALSE,
  561. BADWOLF_TOOLBAR_SEPARATOR_PADDING);
  562. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  563. GTK_WIDGET(browser->javascript),
  564. FALSE,
  565. FALSE,
  566. BADWOLF_TOOLBAR_PADDING);
  567. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  568. GTK_WIDGET(browser->auto_load_images),
  569. FALSE,
  570. FALSE,
  571. BADWOLF_TOOLBAR_PADDING);
  572. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  573. GTK_WIDGET(browser->location),
  574. TRUE,
  575. TRUE,
  576. BADWOLF_TOOLBAR_PADDING);
  577. gtk_box_pack_start(GTK_BOX(browser->toolbar), print, FALSE, FALSE, BADWOLF_TOOLBAR_PADDING);
  578. gtk_container_set_focus_child(GTK_CONTAINER(browser->box), browser->toolbar);
  579. gtk_container_set_focus_child(GTK_CONTAINER(browser->toolbar), browser->location);
  580. gtk_box_pack_start(
  581. GTK_BOX(browser->box), GTK_WIDGET(browser->toolbar), FALSE, FALSE, BADWOLF_BOX_PADDING);
  582. gtk_box_pack_start(
  583. GTK_BOX(browser->box), GTK_WIDGET(browser->webView), TRUE, TRUE, BADWOLF_BOX_PADDING);
  584. gtk_box_pack_start(
  585. GTK_BOX(browser->box), GTK_WIDGET(browser->statusbar), FALSE, FALSE, BADWOLF_BOX_PADDING);
  586. gtk_box_pack_start(GTK_BOX(browser->statusbar),
  587. GTK_WIDGET(browser->search),
  588. FALSE,
  589. FALSE,
  590. BADWOLF_STATUSBAR_PADDING);
  591. gtk_box_pack_start(GTK_BOX(browser->statusbar),
  592. GTK_WIDGET(browser->statuslabel),
  593. FALSE,
  594. FALSE,
  595. BADWOLF_STATUSBAR_PADDING);
  596. gtk_widget_set_halign(browser->statusbar, GTK_ALIGN_START);
  597. gtk_label_set_single_line_mode(GTK_LABEL(browser->statuslabel), TRUE);
  598. gtk_label_set_ellipsize(GTK_LABEL(browser->statuslabel), BADWOLF_STATUSLABEL_ELLIPSIZE);
  599. gtk_entry_set_text(GTK_ENTRY(browser->location), target_url);
  600. gtk_entry_set_input_purpose(GTK_ENTRY(browser->location), GTK_INPUT_PURPOSE_URL);
  601. gtk_entry_set_placeholder_text(GTK_ENTRY(browser->search), _("search in current page"));
  602. /* signals for back/forward buttons */
  603. g_signal_connect(browser->back, "clicked", G_CALLBACK(backCb_clicked), browser);
  604. g_signal_connect(browser->forward, "clicked", G_CALLBACK(forwardCb_clicked), browser);
  605. /* prevents GtkNotebook from spawning it's context-menu */
  606. g_signal_connect(
  607. browser->back, "button-press-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  608. g_signal_connect(
  609. browser->back, "button-release-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  610. g_signal_connect(
  611. browser->forward, "button-press-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  612. g_signal_connect(
  613. browser->forward, "button-release-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  614. /* signals for javacript toggle widget */
  615. g_signal_connect(browser->javascript, "toggled", G_CALLBACK(javascriptCb_toggled), browser);
  616. /* prevents GtkNotebook from spawning it's context-menu */
  617. g_signal_connect(
  618. browser->javascript, "button-press-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  619. g_signal_connect(
  620. browser->javascript, "button-release-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  621. /* signals for auto_load_images toggle widget */
  622. g_signal_connect(
  623. browser->auto_load_images, "toggled", G_CALLBACK(auto_load_imagesCb_toggled), browser);
  624. /* prevents GtkNotebook from spawning it's context-menu */
  625. g_signal_connect(browser->auto_load_images,
  626. "button-press-event",
  627. G_CALLBACK(widgetCb_drop_button3_event),
  628. NULL);
  629. g_signal_connect(browser->auto_load_images,
  630. "button-release-event",
  631. G_CALLBACK(widgetCb_drop_button3_event),
  632. NULL);
  633. /* signals for location entry widget */
  634. g_signal_connect(browser->location, "activate", G_CALLBACK(locationCb_activate), browser);
  635. /* signals for print button */
  636. g_signal_connect(print, "clicked", G_CALLBACK(printCb_clicked), browser);
  637. /* prevents GtkNotebook from spawning it's context-menu */
  638. g_signal_connect(print, "button-press-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  639. g_signal_connect(print, "button-release-event", G_CALLBACK(widgetCb_drop_button3_event), NULL);
  640. /* signals for WebView widget */
  641. g_signal_connect(browser->webView,
  642. "web-process-terminated",
  643. G_CALLBACK(WebViewCb_web_process_terminated),
  644. browser);
  645. g_signal_connect(browser->webView, "notify::uri", G_CALLBACK(WebViewCb_notify__uri), browser);
  646. g_signal_connect(browser->webView, "notify::title", G_CALLBACK(WebViewCb_notify__title), browser);
  647. g_signal_connect(browser->webView,
  648. "notify::is-playing-audio",
  649. G_CALLBACK(WebViewCb_notify__is__playing__audio),
  650. browser);
  651. g_signal_connect(browser->webView,
  652. "mouse-target-changed",
  653. G_CALLBACK(WebViewCb_mouse_target_changed),
  654. browser);
  655. g_signal_connect(browser->webView,
  656. "notify::estimated-load-progress",
  657. G_CALLBACK(WebViewCb_notify__estimated_load_progress),
  658. browser);
  659. g_signal_connect(browser->webView, "create", G_CALLBACK(WebViewCb_create), browser);
  660. g_signal_connect(browser->webView, "close", G_CALLBACK(WebViewCb_close), browser);
  661. g_signal_connect(
  662. browser->webView, "key-press-event", G_CALLBACK(WebViewCb_key_press_event), browser);
  663. g_signal_connect(browser->webView, "scroll-event", G_CALLBACK(WebViewCb_scroll_event), browser);
  664. g_signal_connect(
  665. browser->webView, "permission-request", G_CALLBACK(WebViewCb_permission_request), NULL);
  666. g_signal_connect(browser->webView, "decide-policy", G_CALLBACK(WebViewCb_decide_policy), NULL);
  667. g_signal_connect(browser->webView,
  668. "load-failed-with-tls-errors",
  669. G_CALLBACK(WebViewCb_load_failed_with_tls_errors),
  670. browser);
  671. g_signal_connect(browser->webView, "load-changed", G_CALLBACK(WebViewCb_load_changed), browser);
  672. /* signals for WebView's WebContext */
  673. g_signal_connect(G_OBJECT(web_context),
  674. "download-started",
  675. G_CALLBACK(web_contextCb_download_started),
  676. browser);
  677. /* signals for search widget */
  678. g_signal_connect(browser->search, "next-match", G_CALLBACK(SearchEntryCb_next__match), browser);
  679. g_signal_connect(
  680. browser->search, "previous-match", G_CALLBACK(SearchEntryCb_previous__match), browser);
  681. g_signal_connect(
  682. browser->search, "search-changed", G_CALLBACK(SearchEntryCb_search__changed), browser);
  683. g_signal_connect(browser->search, "stop-search", G_CALLBACK(SearchEntryCb_stop__search), browser);
  684. /* signals for box container */
  685. g_signal_connect(browser->box, "key-press-event", G_CALLBACK(boxCb_key_press_event), browser);
  686. if(old_browser == NULL) webkit_web_view_load_uri(browser->webView, target_url);
  687. return browser;
  688. }
  689. /* badwolf_new_tab: Inserts struct Client *browser in GtkNotebook *notebook
  690. * and optionally switches selected tab to it.
  691. *
  692. * returns:
  693. * 0 : Ran successfully
  694. * -1 : Failed to insert a page for browser->box
  695. * -2 : browser is NULL
  696. */
  697. int
  698. badwolf_new_tab(GtkNotebook *notebook, struct Client *browser, bool auto_switch)
  699. {
  700. gint current_page = gtk_notebook_get_current_page(notebook);
  701. gchar *title = _("New tab");
  702. if(browser == NULL) return -2;
  703. gtk_widget_show_all(browser->box);
  704. if(gtk_notebook_insert_page(notebook, browser->box, NULL, (current_page + 1)) == -1) return -1;
  705. gtk_notebook_set_tab_reorderable(notebook, browser->box, TRUE);
  706. gtk_notebook_set_tab_label(notebook, browser->box, badwolf_new_tab_box(title, browser));
  707. gtk_notebook_set_menu_label_text(GTK_NOTEBOOK(notebook), browser->box, title);
  708. gtk_widget_queue_draw(GTK_WIDGET(notebook));
  709. if(auto_switch)
  710. {
  711. gtk_notebook_set_current_page(notebook, gtk_notebook_page_num(notebook, browser->box));
  712. }
  713. return 0;
  714. }
  715. static void
  716. new_tabCb_clicked(GtkButton *new_tab, gpointer user_data)
  717. {
  718. (void)new_tab;
  719. struct Window *window = (struct Window *)user_data;
  720. struct Client *browser = new_browser(window, NULL, NULL);
  721. badwolf_new_tab(GTK_NOTEBOOK(window->notebook), browser, TRUE);
  722. }
  723. static void
  724. closeCb_clicked(GtkButton *close, gpointer user_data)
  725. {
  726. (void)close;
  727. struct Client *browser = (struct Client *)user_data;
  728. webkit_web_view_try_close(browser->webView);
  729. }
  730. static void
  731. notebookCb_switch__page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
  732. {
  733. (void)page_num;
  734. struct Window *window = (struct Window *)user_data;
  735. GtkWidget *label = gtk_notebook_get_tab_label(notebook, page);
  736. // TODO: Maybe find a better way to store the title
  737. gtk_window_set_title(GTK_WINDOW(window->main_window), gtk_widget_get_tooltip_text(label));
  738. }
  739. int
  740. main(int argc, char *argv[])
  741. {
  742. struct Window *window = &(struct Window){NULL, NULL, NULL, NULL};
  743. int tab_position = GTK_POS_TOP;
  744. int window_width = 800;
  745. int window_height = 600;
  746. setlocale(LC_ALL, "");
  747. bindtextdomain(PACKAGE, DATADIR "/locale");
  748. bind_textdomain_codeset(PACKAGE, "UTF-8");
  749. textdomain(PACKAGE);
  750. gtk_init(&argc, &argv);
  751. config_init(&config);
  752. fprintf(stderr, _("Running Badwolf version: %s\n"), version);
  753. fprintf(stderr,
  754. _("Buildtime WebKit version: %d.%d.%d\n"),
  755. WEBKIT_MAJOR_VERSION,
  756. WEBKIT_MINOR_VERSION,
  757. WEBKIT_MICRO_VERSION);
  758. fprintf(stderr,
  759. _("Runtime WebKit version: %d.%d.%d\n"),
  760. webkit_get_major_version(),
  761. webkit_get_minor_version(),
  762. webkit_get_micro_version());
  763. char *config_path = g_build_filename(g_get_user_config_dir(), "badwolf", "badwolf.cfg", NULL);
  764. if(!read_config(&config, config_path))
  765. {
  766. fprintf(stderr, _("Fatal error: Failed to parse config file.\n"));
  767. return 1;
  768. }
  769. config_lookup_int(&config, "tab.position", &tab_position);
  770. config_lookup_int(&config, "window.width", &window_width);
  771. config_lookup_int(&config, "window.height", &window_height);
  772. web_extensions_directory =
  773. g_build_filename(g_get_user_data_dir(), "badwolf", "webkit-web-extension", NULL);
  774. fprintf(stderr, _("webkit-web-extension directory set to: %s\n"), web_extensions_directory);
  775. window->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  776. window->notebook = gtk_notebook_new();
  777. window->new_tab = gtk_button_new_from_icon_name("tab-new-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  778. window->downloads_tab = badwolf_downloads_tab_new();
  779. gtk_window_set_default_size(GTK_WINDOW(window->main_window), window_width, window_height);
  780. gtk_window_set_role(GTK_WINDOW(window->main_window), "browser");
  781. gtk_window_set_icon_name(GTK_WINDOW(window->main_window), "badwolf");
  782. GtkCssProvider *css_provider_app = gtk_css_provider_new();
  783. gchar *provider_path_app = g_build_filename(DATADIR, "interface.css", NULL);
  784. if(access(provider_path_app, R_OK) == 0)
  785. {
  786. gtk_css_provider_load_from_path(css_provider_app, provider_path_app, NULL);
  787. gtk_style_context_add_provider_for_screen(
  788. gtk_widget_get_screen(GTK_WIDGET(window->main_window)),
  789. GTK_STYLE_PROVIDER(css_provider_app),
  790. GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  791. }
  792. g_free(provider_path_app);
  793. GtkCssProvider *css_provider_user = gtk_css_provider_new();
  794. gchar *provider_path_user =
  795. g_build_filename(g_get_user_data_dir(), "badwolf", "interface.css", NULL);
  796. if(access(provider_path_user, R_OK) == 0)
  797. {
  798. gtk_css_provider_load_from_path(css_provider_user, provider_path_user, NULL);
  799. gtk_style_context_add_provider_for_screen(
  800. gtk_widget_get_screen(GTK_WIDGET(window->main_window)),
  801. GTK_STYLE_PROVIDER(css_provider_user),
  802. GTK_STYLE_PROVIDER_PRIORITY_USER);
  803. }
  804. g_free(provider_path_user);
  805. gtk_widget_set_tooltip_text(window->new_tab, _("Open new tab"));
  806. gtk_notebook_set_action_widget(GTK_NOTEBOOK(window->notebook), window->new_tab, GTK_PACK_END);
  807. gtk_notebook_set_scrollable(GTK_NOTEBOOK(window->notebook), TRUE);
  808. gtk_notebook_set_tab_pos(GTK_NOTEBOOK(window->notebook), tab_position);
  809. gtk_notebook_popup_enable(GTK_NOTEBOOK(window->notebook));
  810. gtk_container_add(GTK_CONTAINER(window->main_window), window->notebook);
  811. gtk_widget_queue_draw(window->notebook);
  812. badwolf_downloads_tab_attach(window);
  813. g_signal_connect(
  814. window->main_window, "key-press-event", G_CALLBACK(main_windowCb_key_press_event), window);
  815. g_signal_connect(window->main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
  816. g_signal_connect(window->new_tab, "clicked", G_CALLBACK(new_tabCb_clicked), window);
  817. g_signal_connect(window->notebook, "switch-page", G_CALLBACK(notebookCb_switch__page), window);
  818. gtk_widget_show(window->new_tab);
  819. gtk_widget_show_all(window->main_window);
  820. if(argc == 1)
  821. badwolf_new_tab(GTK_NOTEBOOK(window->notebook), new_browser(window, NULL, NULL), FALSE);
  822. else
  823. for(int i = 1; i < argc; ++i)
  824. badwolf_new_tab(GTK_NOTEBOOK(window->notebook), new_browser(window, argv[i], NULL), FALSE);
  825. gtk_notebook_set_current_page(GTK_NOTEBOOK(window->notebook), 1);
  826. gtk_main();
  827. #if 0
  828. /* TRANSLATOR Ignore this entry. Done for forcing Unicode in xgettext. */
  829. _("ø");
  830. #endif
  831. return 0;
  832. }