badwolf.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. // BadWolf: Minimalist and privacy-oriented WebKitGTK+ browser
  2. // Copyright © 2019 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
  3. // SPDX-License-Identifier: BSD-3-Clause
  4. #include "config.h"
  5. #include <glib/gi18n.h> /* _() and other internationalization/localization helpers */
  6. #include <glib/gprintf.h> /* g_fprintf() */
  7. #include <gtk/gtk.h>
  8. #include <locale.h> /* LC_* */
  9. #include <webkit2/webkit2.h>
  10. const gchar *homepage = "https://hacktivis.me/projects/badwolf";
  11. const gchar *version = "0.1.1";
  12. struct Window
  13. {
  14. GtkWidget *main_window;
  15. GtkWidget *notebook;
  16. GtkWidget *new_tab;
  17. };
  18. struct Client
  19. {
  20. GtkWidget *box;
  21. GtkWidget *toolbar;
  22. GtkWidget *javascript;
  23. GtkWidget *location;
  24. WebKitWebView *webView;
  25. struct Window *window;
  26. GtkWidget *statusbar;
  27. GtkWidget *statuslabel;
  28. GtkWidget *search;
  29. };
  30. static gboolean WebViewCb_close(WebKitWebView *webView, gpointer user_data);
  31. static gboolean
  32. commonCb_key_press_event(struct Window *window, GdkEvent *event, struct Client *browser);
  33. static gboolean
  34. WebViewCb_key_press_event(WebKitWebView *webView, GdkEvent *event, gpointer user_data);
  35. static gboolean
  36. main_windowCb_key_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data);
  37. static gboolean boxCb_key_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data);
  38. static gboolean WebViewCb_web_process_terminated(WebKitWebView *webView,
  39. WebKitWebProcessTerminationReason reason,
  40. gpointer user_data);
  41. static gboolean
  42. WebViewCb_notify__uri(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  43. GtkWidget *badwolf_new_tab_box(const gchar *title, struct Client *browser);
  44. static gboolean
  45. WebViewCb_notify__title(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  46. static gboolean
  47. WebViewCb_notify__is__playing__audio(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data);
  48. void webView_tab_label_change(struct Client *browser, const gchar *title);
  49. static gboolean WebViewCb_notify__estimated_load_progress(WebKitWebView *webView,
  50. GParamSpec *pspec,
  51. gpointer user_data);
  52. static gboolean WebViewCb_mouse_target_changed(WebKitWebView *webView,
  53. WebKitHitTestResult *hit,
  54. guint modifiers,
  55. gpointer user_data);
  56. static WebKitWebView *WebViewCb_create(WebKitWebView *related_web_view,
  57. WebKitNavigationAction *navigation_action,
  58. gpointer user_data);
  59. static gboolean locationCb_activate(GtkEntry *location, gpointer user_data);
  60. static gboolean javascriptCb_toggled(GtkButton *javascript, gpointer user_data);
  61. static gboolean SearchEntryCb_next__match(GtkSearchEntry *search, gpointer user_data);
  62. static gboolean SearchEntryCb_previous__match(GtkSearchEntry *search, gpointer user_data);
  63. static gboolean SearchEntryCb_search__changed(GtkSearchEntry *search, gpointer user_data);
  64. static gboolean SearchEntryCb_stop__search(GtkSearchEntry *search, gpointer user_data);
  65. struct Client *
  66. new_browser(struct Window *window, gchar *target_url, WebKitWebView *related_web_view);
  67. int badwolf_new_tab(GtkNotebook *notebook, struct Client *browser);
  68. static void new_tabCb_clicked(GtkButton *new_tab, gpointer user_data);
  69. static void closeCb_clicked(GtkButton *close, gpointer user_data);
  70. static void
  71. notebookCb_switch__page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data);
  72. gint get_tab_position(GtkContainer *notebook, GtkWidget *child);
  73. void move_tab_position(GtkNotebook *notebook, GtkWidget *child, int delta);
  74. static void
  75. badwolf_about_dialog(GtkWindow *main_window)
  76. {
  77. // clang-format off
  78. gtk_show_about_dialog(
  79. main_window,
  80. "license", "SPDX-License-Identifier: BSD-3-Clause",
  81. "copyright", "2019 Haelwenn (lanodan) Monnier <contact+badwolf@hacktivis.me>",
  82. "website", homepage,
  83. "comments", "Minimalist and privacy-oriented WebKitGTK+ browser",
  84. "version", version,
  85. //FIXME: "logo-icon-name", g_get_application_name(),
  86. NULL
  87. );
  88. // clang-format on
  89. }
  90. static gboolean
  91. WebViewCb_close(WebKitWebView *webView, gpointer user_data)
  92. {
  93. (void)webView;
  94. struct Client *browser = (struct Client *)user_data;
  95. GtkNotebook *notebook = GTK_NOTEBOOK(browser->window->notebook);
  96. gtk_notebook_remove_page(notebook, get_tab_position(GTK_CONTAINER(notebook), browser->box));
  97. gtk_widget_destroy(browser->box);
  98. free(browser);
  99. return TRUE;
  100. }
  101. /* commonCb_key_press_event: Global callback for keybindings
  102. *
  103. * Theses shortcuts should be avoided as much as possible:
  104. * - Single key shortcuts (ie. backspace and space)
  105. * - Triple key shortcuts (except for Ctrl+Shift)
  106. * - Unix Terminal shortcuts (specially Ctrl-W)
  107. *
  108. * loosely follows https://developer.gnome.org/hig/stable/keyboard-input.html
  109. */
  110. static gboolean
  111. commonCb_key_press_event(struct Window *window, GdkEvent *event, struct Client *browser)
  112. {
  113. GtkNotebook *notebook = GTK_NOTEBOOK(window->notebook);
  114. if((((GdkEventKey *)event)->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && (browser != NULL))
  115. {
  116. switch(((GdkEventKey *)event)->keyval)
  117. {
  118. case GDK_KEY_Page_Down: move_tab_position(notebook, browser->box, 1); return TRUE;
  119. case GDK_KEY_Page_Up: move_tab_position(notebook, browser->box, -1); return TRUE;
  120. }
  121. }
  122. if(((GdkEventKey *)event)->state & GDK_CONTROL_MASK)
  123. {
  124. if(browser != NULL)
  125. {
  126. switch(((GdkEventKey *)event)->keyval)
  127. {
  128. case GDK_KEY_F4: webkit_web_view_try_close(browser->webView); return TRUE;
  129. case GDK_KEY_r:
  130. if(((GdkEventKey *)event)->state & GDK_SHIFT_MASK)
  131. webkit_web_view_reload_bypass_cache(browser->webView);
  132. else
  133. webkit_web_view_reload(browser->webView);
  134. return TRUE;
  135. case GDK_KEY_f: gtk_widget_grab_focus(browser->search); return TRUE;
  136. case GDK_KEY_l: gtk_widget_grab_focus(browser->location); return TRUE;
  137. case GDK_KEY_bracketleft: webkit_web_view_go_back(browser->webView); return TRUE;
  138. case GDK_KEY_bracketright: webkit_web_view_go_forward(browser->webView); return TRUE;
  139. case GDK_KEY_0:
  140. webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(browser->webView), 1);
  141. return TRUE;
  142. }
  143. }
  144. switch(((GdkEventKey *)event)->keyval)
  145. {
  146. case GDK_KEY_Page_Down: gtk_notebook_next_page(notebook); return TRUE;
  147. case GDK_KEY_Page_Up: gtk_notebook_prev_page(notebook); return TRUE;
  148. case GDK_KEY_t: badwolf_new_tab(notebook, new_browser(window, NULL, NULL)); return TRUE;
  149. }
  150. }
  151. if((((GdkEventKey *)event)->state & GDK_MOD1_MASK) && browser != NULL)
  152. {
  153. switch(((GdkEventKey *)event)->keyval)
  154. {
  155. case GDK_KEY_Left: gtk_notebook_prev_page(notebook); return TRUE;
  156. case GDK_KEY_Right: gtk_notebook_next_page(notebook); return TRUE;
  157. }
  158. }
  159. if(browser != NULL)
  160. {
  161. switch(((GdkEventKey *)event)->keyval)
  162. {
  163. case GDK_KEY_F5: webkit_web_view_reload(browser->webView); return TRUE;
  164. }
  165. }
  166. else
  167. {
  168. switch(((GdkEventKey *)event)->keyval)
  169. {
  170. case GDK_KEY_F1: badwolf_about_dialog(GTK_WINDOW(window->main_window)); return TRUE;
  171. }
  172. }
  173. return FALSE;
  174. }
  175. static gboolean
  176. WebViewCb_key_press_event(WebKitWebView *webView, GdkEvent *event, gpointer user_data)
  177. {
  178. (void)webView;
  179. struct Client *browser = (struct Client *)user_data;
  180. if(commonCb_key_press_event(browser->window, event, browser)) return TRUE;
  181. return FALSE;
  182. }
  183. static gboolean
  184. boxCb_key_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
  185. {
  186. (void)widget;
  187. struct Client *browser = (struct Client *)user_data;
  188. if(commonCb_key_press_event(browser->window, event, browser)) return TRUE;
  189. return FALSE;
  190. }
  191. static gboolean
  192. main_windowCb_key_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
  193. {
  194. (void)widget;
  195. struct Window *window = (struct Window *)user_data;
  196. if(commonCb_key_press_event(window, event, NULL)) return TRUE;
  197. return FALSE;
  198. }
  199. static gboolean
  200. WebViewCb_web_process_terminated(WebKitWebView *webView,
  201. WebKitWebProcessTerminationReason reason,
  202. gpointer user_data)
  203. {
  204. (void)webView;
  205. struct Client *browser = (struct Client *)user_data;
  206. switch(reason)
  207. {
  208. case WEBKIT_WEB_PROCESS_CRASHED:
  209. g_fprintf(stderr, _("the web process crashed.\n"));
  210. webView_tab_label_change(browser, _("title|Crashed"));
  211. break;
  212. case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT:
  213. g_fprintf(stderr, _("the web process exceeded the memory limit.\n"));
  214. webView_tab_label_change(browser, _("title|Out of Memory"));
  215. break;
  216. default:
  217. g_fprintf(stderr, _("the web process terminated for an unknown reason.\n"));
  218. webView_tab_label_change(browser, _("title|Unknown Crash"));
  219. }
  220. return FALSE;
  221. }
  222. static gboolean
  223. WebViewCb_notify__uri(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  224. {
  225. (void)webView;
  226. (void)pspec;
  227. const gchar *location_uri;
  228. struct Client *browser = (struct Client *)user_data;
  229. location_uri = webkit_web_view_get_uri(browser->webView);
  230. gtk_entry_set_text(GTK_ENTRY(browser->location), location_uri);
  231. if(webkit_uri_for_display(location_uri) != location_uri)
  232. gtk_widget_set_tooltip_text(browser->location, webkit_uri_for_display(location_uri));
  233. else
  234. gtk_widget_set_has_tooltip(browser->location, false);
  235. return TRUE;
  236. }
  237. GtkWidget *
  238. badwolf_new_tab_box(const gchar *title, struct Client *browser)
  239. {
  240. (void)browser;
  241. GtkWidget *tab_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  242. GtkWidget *close =
  243. gtk_button_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  244. GtkWidget *label = gtk_label_new(title);
  245. GtkWidget *playing =
  246. gtk_image_new_from_icon_name("audio-volume-high-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  247. #ifdef BADWOLF_TAB_BOX_WIDTH
  248. gtk_widget_set_size_request(label, BADWOLF_TAB_BOX_WIDTH, -1);
  249. #endif
  250. #ifdef BADWOLF_TAB_LABEL_CHARWIDTH
  251. gtk_label_set_width_chars(GTK_LABEL(label), BADWOLF_TAB_LABEL_CHARWIDTH);
  252. #endif
  253. gtk_widget_set_hexpand(tab_box, BADWOLF_TAB_HEXPAND);
  254. gtk_label_set_ellipsize(GTK_LABEL(label), BADWOLF_TAB_LABEL_ELLIPSIZE);
  255. gtk_label_set_single_line_mode(GTK_LABEL(label), TRUE);
  256. gtk_box_pack_start(GTK_BOX(tab_box), playing, FALSE, FALSE, 0);
  257. gtk_box_pack_start(GTK_BOX(tab_box), label, TRUE, TRUE, 0);
  258. gtk_box_pack_start(GTK_BOX(tab_box), close, FALSE, FALSE, 0);
  259. gtk_button_set_relief(GTK_BUTTON(close), GTK_RELIEF_NONE);
  260. g_signal_connect(close, "clicked", G_CALLBACK(closeCb_clicked), browser);
  261. gtk_widget_set_tooltip_text(tab_box, title);
  262. gtk_widget_show_all(tab_box);
  263. gtk_widget_set_visible(playing, webkit_web_view_is_playing_audio(browser->webView));
  264. return tab_box;
  265. }
  266. static gboolean
  267. WebViewCb_notify__title(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  268. {
  269. (void)webView;
  270. (void)pspec;
  271. struct Client *browser = (struct Client *)user_data;
  272. webView_tab_label_change(browser, NULL);
  273. return TRUE;
  274. }
  275. static gboolean
  276. WebViewCb_notify__is__playing__audio(WebKitWebView *webView, GParamSpec *pspec, gpointer user_data)
  277. {
  278. (void)webView;
  279. (void)pspec;
  280. struct Client *browser = (struct Client *)user_data;
  281. webView_tab_label_change(browser, NULL);
  282. return TRUE;
  283. }
  284. void
  285. webView_tab_label_change(struct Client *browser, const gchar *title)
  286. {
  287. GtkWidget *notebook = browser->window->notebook;
  288. #define title_IS_EMPTY (title == NULL) || (title == (const gchar *)"")
  289. if(title_IS_EMPTY) title = webkit_web_view_get_title(browser->webView);
  290. if(title_IS_EMPTY) title = webkit_web_view_get_uri(browser->webView);
  291. if(title_IS_EMPTY) title = "BadWolf";
  292. gtk_notebook_set_tab_label(
  293. GTK_NOTEBOOK(notebook), browser->box, badwolf_new_tab_box(title, browser));
  294. if(get_tab_position(GTK_CONTAINER(notebook), browser->box) ==
  295. gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)))
  296. gtk_window_set_title(GTK_WINDOW(browser->window->main_window), title);
  297. }
  298. static gboolean
  299. WebViewCb_notify__estimated_load_progress(WebKitWebView *webView,
  300. GParamSpec *pspec,
  301. gpointer user_data)
  302. {
  303. (void)webView;
  304. (void)pspec;
  305. struct Client *browser = (struct Client *)user_data;
  306. gdouble progress;
  307. progress = webkit_web_view_get_estimated_load_progress(browser->webView);
  308. if(progress == 1)
  309. {
  310. progress = 0;
  311. }
  312. gtk_entry_set_progress_fraction(GTK_ENTRY(browser->location), progress);
  313. return TRUE;
  314. }
  315. static gboolean
  316. WebViewCb_mouse_target_changed(WebKitWebView *webView,
  317. WebKitHitTestResult *hit,
  318. guint modifiers,
  319. gpointer user_data)
  320. {
  321. (void)webView;
  322. (void)modifiers;
  323. struct Client *browser = (struct Client *)user_data;
  324. const gchar *link_uri = webkit_hit_test_result_get_link_uri(hit);
  325. gtk_label_set_text(GTK_LABEL(browser->statuslabel), webkit_uri_for_display(link_uri));
  326. return TRUE;
  327. }
  328. static gboolean
  329. WebViewCb_scroll_event(GtkWidget *widget, GdkEvent *event, gpointer data)
  330. {
  331. (void)widget;
  332. struct Client *browser = (struct Client *)data;
  333. gdouble delta_x, delta_y;
  334. gfloat zoom;
  335. if(((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
  336. {
  337. gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
  338. zoom = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(browser->webView));
  339. zoom -= delta_y * 0.1;
  340. webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(browser->webView), zoom);
  341. return TRUE;
  342. }
  343. return FALSE;
  344. }
  345. static WebKitWebView *
  346. WebViewCb_create(WebKitWebView *related_web_view,
  347. WebKitNavigationAction *navigation_action,
  348. gpointer user_data)
  349. {
  350. (void)navigation_action;
  351. struct Window *window = (struct Window *)user_data;
  352. struct Client *browser = new_browser(window, NULL, related_web_view);
  353. if(badwolf_new_tab(GTK_NOTEBOOK(window->notebook), browser) < 0)
  354. {
  355. return NULL;
  356. }
  357. else
  358. {
  359. return browser->webView;
  360. }
  361. }
  362. static gboolean
  363. locationCb_activate(GtkEntry *location, gpointer user_data)
  364. {
  365. const char *target_url;
  366. struct Client *browser = (struct Client *)user_data;
  367. target_url = gtk_entry_get_text(location);
  368. if(target_url != NULL) webkit_web_view_load_uri(browser->webView, target_url);
  369. return TRUE;
  370. }
  371. static gboolean
  372. javascriptCb_toggled(GtkButton *javascript, gpointer user_data)
  373. {
  374. struct Client *browser = (struct Client *)user_data;
  375. WebKitSettings *settings = webkit_web_view_get_settings(browser->webView);
  376. webkit_settings_set_enable_javascript(
  377. settings, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(javascript)));
  378. webkit_web_view_set_settings(browser->webView, settings);
  379. return TRUE;
  380. }
  381. static gboolean
  382. SearchEntryCb_next__match(GtkSearchEntry *search, gpointer user_data)
  383. {
  384. (void)search;
  385. struct Client *browser = (struct Client *)user_data;
  386. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  387. webkit_find_controller_search_next(findController);
  388. return TRUE;
  389. }
  390. static gboolean
  391. SearchEntryCb_previous__match(GtkSearchEntry *search, gpointer user_data)
  392. {
  393. (void)search;
  394. struct Client *browser = (struct Client *)user_data;
  395. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  396. webkit_find_controller_search_previous(findController);
  397. return TRUE;
  398. }
  399. static gboolean
  400. SearchEntryCb_search__changed(GtkSearchEntry *search, gpointer user_data)
  401. {
  402. struct Client *browser = (struct Client *)user_data;
  403. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  404. const gchar *search_text = gtk_entry_get_text(GTK_ENTRY(search));
  405. webkit_find_controller_search(findController, search_text, 0, 0);
  406. return TRUE;
  407. }
  408. static gboolean
  409. SearchEntryCb_stop__search(GtkSearchEntry *search, gpointer user_data)
  410. {
  411. (void)search;
  412. struct Client *browser = (struct Client *)user_data;
  413. WebKitFindController *findController = webkit_web_view_get_find_controller(browser->webView);
  414. webkit_find_controller_search_finish(findController);
  415. return TRUE;
  416. }
  417. struct Client *
  418. new_browser(struct Window *window, gchar *target_url, WebKitWebView *related_web_view)
  419. {
  420. struct Client *browser = g_malloc(sizeof(struct Client));
  421. if(target_url == NULL) target_url = "about:blank";
  422. browser->window = window;
  423. browser->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  424. browser->toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  425. browser->location = gtk_entry_new();
  426. browser->javascript = gtk_check_button_new();
  427. browser->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  428. browser->statuslabel = gtk_label_new(NULL);
  429. browser->search = gtk_search_entry_new();
  430. WebKitWebContext *web_context = webkit_web_context_new_ephemeral();
  431. webkit_web_context_set_process_model(web_context,
  432. WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
  433. WebKitSettings *settings = webkit_settings_new_with_settings(BADWOLF_WEBKIT_SETTINGS);
  434. browser->webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
  435. "web-context",
  436. webkit_web_context_new_ephemeral(),
  437. "related-view",
  438. related_web_view,
  439. "settings",
  440. settings,
  441. NULL));
  442. gtk_widget_set_tooltip_text(browser->javascript, _("Toggle javascript"));
  443. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  444. GTK_WIDGET(browser->javascript),
  445. FALSE,
  446. FALSE,
  447. BADWOLF_TOOLBAR_PADDING);
  448. gtk_box_pack_start(GTK_BOX(browser->toolbar),
  449. GTK_WIDGET(browser->location),
  450. TRUE,
  451. TRUE,
  452. BADWOLF_TOOLBAR_PADDING);
  453. gtk_box_pack_start(
  454. GTK_BOX(browser->box), GTK_WIDGET(browser->toolbar), FALSE, FALSE, BADWOLF_BOX_PADDING);
  455. gtk_box_pack_start(
  456. GTK_BOX(browser->box), GTK_WIDGET(browser->webView), TRUE, TRUE, BADWOLF_BOX_PADDING);
  457. gtk_box_pack_start(
  458. GTK_BOX(browser->box), GTK_WIDGET(browser->statusbar), FALSE, FALSE, BADWOLF_BOX_PADDING);
  459. gtk_box_pack_start(GTK_BOX(browser->statusbar),
  460. GTK_WIDGET(browser->search),
  461. FALSE,
  462. FALSE,
  463. BADWOLF_STATUSBAR_PADDING);
  464. gtk_box_pack_start(GTK_BOX(browser->statusbar),
  465. GTK_WIDGET(browser->statuslabel),
  466. FALSE,
  467. FALSE,
  468. BADWOLF_STATUSBAR_PADDING);
  469. gtk_widget_set_halign(browser->statusbar, GTK_ALIGN_START);
  470. gtk_label_set_single_line_mode(GTK_LABEL(browser->statuslabel), TRUE);
  471. gtk_entry_set_text(GTK_ENTRY(browser->location), target_url);
  472. gtk_entry_set_has_frame(GTK_ENTRY(browser->location), FALSE);
  473. gtk_entry_set_input_purpose(GTK_ENTRY(browser->location), GTK_INPUT_PURPOSE_URL);
  474. gtk_entry_set_placeholder_text(GTK_ENTRY(browser->search), _("search in current page"));
  475. gtk_entry_set_has_frame(GTK_ENTRY(browser->search), FALSE);
  476. g_signal_connect(browser->location, "activate", G_CALLBACK(locationCb_activate), browser);
  477. g_signal_connect(browser->javascript, "toggled", G_CALLBACK(javascriptCb_toggled), browser);
  478. g_signal_connect(browser->webView,
  479. "web-process-terminated",
  480. G_CALLBACK(WebViewCb_web_process_terminated),
  481. browser);
  482. g_signal_connect(browser->webView, "notify::uri", G_CALLBACK(WebViewCb_notify__uri), browser);
  483. g_signal_connect(browser->webView, "notify::title", G_CALLBACK(WebViewCb_notify__title), browser);
  484. g_signal_connect(browser->webView,
  485. "notify::is-playing-audio",
  486. G_CALLBACK(WebViewCb_notify__is__playing__audio),
  487. browser);
  488. g_signal_connect(browser->webView,
  489. "mouse-target-changed",
  490. G_CALLBACK(WebViewCb_mouse_target_changed),
  491. browser);
  492. g_signal_connect(browser->webView,
  493. "notify::estimated-load-progress",
  494. G_CALLBACK(WebViewCb_notify__estimated_load_progress),
  495. browser);
  496. g_signal_connect(browser->webView, "create", G_CALLBACK(WebViewCb_create), window);
  497. g_signal_connect(browser->webView, "close", G_CALLBACK(WebViewCb_close), browser);
  498. g_signal_connect(
  499. browser->webView, "key-press-event", G_CALLBACK(WebViewCb_key_press_event), browser);
  500. g_signal_connect(browser->webView, "scroll-event", G_CALLBACK(WebViewCb_scroll_event), browser);
  501. g_signal_connect(browser->search, "next-match", G_CALLBACK(SearchEntryCb_next__match), browser);
  502. g_signal_connect(
  503. browser->search, "previous-match", G_CALLBACK(SearchEntryCb_previous__match), browser);
  504. g_signal_connect(
  505. browser->search, "search-changed", G_CALLBACK(SearchEntryCb_search__changed), browser);
  506. g_signal_connect(browser->search, "stop-search", G_CALLBACK(SearchEntryCb_stop__search), browser);
  507. g_signal_connect(browser->box, "key-press-event", G_CALLBACK(boxCb_key_press_event), browser);
  508. if(related_web_view == NULL) webkit_web_view_load_uri(browser->webView, target_url);
  509. return browser;
  510. }
  511. int
  512. badwolf_new_tab(GtkNotebook *notebook, struct Client *browser)
  513. {
  514. gint current_page = gtk_notebook_get_current_page(notebook);
  515. gchar *title = _("New tab");
  516. gtk_widget_show_all(browser->box);
  517. if(gtk_notebook_insert_page(notebook, browser->box, NULL, (current_page + 2)) == -1)
  518. {
  519. return -1;
  520. }
  521. gtk_notebook_set_tab_reorderable(notebook, browser->box, TRUE);
  522. gtk_notebook_set_tab_label(notebook, browser->box, badwolf_new_tab_box(title, browser));
  523. gtk_widget_queue_draw(GTK_WIDGET(notebook));
  524. return 0;
  525. }
  526. static void
  527. new_tabCb_clicked(GtkButton *new_tab, gpointer user_data)
  528. {
  529. (void)new_tab;
  530. struct Window *window = (struct Window *)user_data;
  531. struct Client *browser = new_browser(window, NULL, NULL);
  532. badwolf_new_tab(GTK_NOTEBOOK(window->notebook), browser);
  533. }
  534. static void
  535. closeCb_clicked(GtkButton *close, gpointer user_data)
  536. {
  537. (void)close;
  538. struct Client *browser = (struct Client *)user_data;
  539. webkit_web_view_try_close(browser->webView);
  540. }
  541. static void
  542. notebookCb_switch__page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
  543. {
  544. (void)page_num;
  545. struct Window *window = (struct Window *)user_data;
  546. GtkWidget *label = gtk_notebook_get_tab_label(notebook, page);
  547. // TODO: Maybe find a better way to store the title
  548. gtk_window_set_title(GTK_WINDOW(window->main_window), gtk_widget_get_tooltip_text(label));
  549. }
  550. gint
  551. get_tab_position(GtkContainer *notebook, GtkWidget *child)
  552. {
  553. GValue position = G_VALUE_INIT;
  554. g_value_init(&position, G_TYPE_INT);
  555. gtk_container_child_get_property(notebook, child, "position", &position);
  556. return g_value_get_int(&position);
  557. }
  558. void
  559. move_tab_position(GtkNotebook *notebook, GtkWidget *child, int delta)
  560. {
  561. if(notebook == NULL) return;
  562. if(child == NULL) return;
  563. printf("Moving tab: %d\n", delta);
  564. gint position = get_tab_position(GTK_CONTAINER(notebook), child) + delta;
  565. if(position < 0) position = 0;
  566. gtk_notebook_reorder_child(notebook, child, position);
  567. }
  568. int
  569. main(int argc, char *argv[])
  570. {
  571. struct Window *window = g_malloc(sizeof(struct Client));
  572. gchar *target_url = NULL;
  573. setlocale(LC_ALL, "");
  574. bindtextdomain(PACKAGE, DATADIR "/locale");
  575. bind_textdomain_codeset(PACKAGE, "UTF-8");
  576. textdomain(PACKAGE);
  577. g_fprintf(stderr, _("Running Badwolf version: %s\n"), version);
  578. g_fprintf(stderr,
  579. _("Buildtime WebKit version: %d.%d.%d\n"),
  580. WEBKIT_MAJOR_VERSION,
  581. WEBKIT_MINOR_VERSION,
  582. WEBKIT_MICRO_VERSION);
  583. g_fprintf(stderr,
  584. _("Runtime WebKit version: %d.%d.%d\n"),
  585. webkit_get_major_version(),
  586. webkit_get_minor_version(),
  587. webkit_get_micro_version());
  588. gtk_init(&argc, &argv);
  589. if(argv[1]) target_url = argv[1];
  590. window->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  591. window->notebook = gtk_notebook_new();
  592. window->new_tab = gtk_button_new_from_icon_name("tab-new-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
  593. gtk_window_set_default_size(
  594. GTK_WINDOW(window->main_window), BADWOLF_DEFAULT_WIDTH, BADWOLF_DEFAULT_HEIGHT);
  595. gtk_window_set_role(GTK_WINDOW(window->main_window), "browser");
  596. gtk_widget_set_tooltip_text(window->new_tab, _("Open new tab"));
  597. gtk_notebook_set_action_widget(GTK_NOTEBOOK(window->notebook), window->new_tab, GTK_PACK_END);
  598. gtk_notebook_set_scrollable(GTK_NOTEBOOK(window->notebook), TRUE);
  599. gtk_notebook_set_tab_pos(GTK_NOTEBOOK(window->notebook), BADWOLF_TAB_POSITION);
  600. gtk_notebook_popup_enable(GTK_NOTEBOOK(window->notebook));
  601. gtk_container_add(GTK_CONTAINER(window->main_window), window->notebook);
  602. badwolf_new_tab(GTK_NOTEBOOK(window->notebook), new_browser(window, target_url, NULL));
  603. g_signal_connect(
  604. window->main_window, "key-press-event", G_CALLBACK(main_windowCb_key_press_event), window);
  605. g_signal_connect(window->main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
  606. g_signal_connect(window->new_tab, "clicked", G_CALLBACK(new_tabCb_clicked), window);
  607. g_signal_connect(window->notebook, "switch-page", G_CALLBACK(notebookCb_switch__page), window);
  608. gtk_widget_show(window->new_tab);
  609. gtk_widget_show_all(window->main_window);
  610. gtk_main();
  611. return 0;
  612. }