You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1778 lines
43 KiB

  1. /* See LICENSE file for copyright and license details.
  2. *
  3. * To understand surf, start reading main().
  4. */
  5. #include <sys/file.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. #include <libgen.h>
  9. #include <limits.h>
  10. #include <pwd.h>
  11. #include <regex.h>
  12. #include <signal.h>
  13. #include <stdarg.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include <gdk/gdk.h>
  19. #include <gdk/gdkkeysyms.h>
  20. #include <gdk/gdkx.h>
  21. #include <glib/gstdio.h>
  22. #include <gtk/gtk.h>
  23. #include <gtk/gtkx.h>
  24. #include <JavaScriptCore/JavaScript.h>
  25. #include <webkit2/webkit2.h>
  26. #include <X11/X.h>
  27. #include <X11/Xatom.h>
  28. #include "arg.h"
  29. #define LENGTH(x) (sizeof(x) / sizeof(x[0]))
  30. #define CLEANMASK(mask) (mask & (MODKEY|GDK_SHIFT_MASK))
  31. #define SETB(p, s) [p] = { { .b = s }, }
  32. #define SETI(p, s) [p] = { { .i = s }, }
  33. #define SETV(p, s) [p] = { { .v = s }, }
  34. #define SETF(p, s) [p] = { { .f = s }, }
  35. #define FSETB(p, s) [p] = { { .b = s }, 1 }
  36. #define FSETI(p, s) [p] = { { .i = s }, 1 }
  37. #define FSETV(p, s) [p] = { { .v = s }, 1 }
  38. #define FSETF(p, s) [p] = { { .f = s }, 1 }
  39. #define CSETB(p, s) [p] = (Parameter){ { .b = s }, 1 }
  40. #define CSETI(p, s) [p] = (Parameter){ { .i = s }, 1 }
  41. #define CSETV(p, s) [p] = (Parameter){ { .v = s }, 1 }
  42. #define CSETF(p, s) [p] = (Parameter){ { .f = s }, 1 }
  43. enum { AtomFind, AtomGo, AtomUri, AtomLast };
  44. enum {
  45. OnDoc = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
  46. OnLink = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
  47. OnImg = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
  48. OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
  49. OnEdit = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
  50. OnBar = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
  51. OnSel = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
  52. OnAny = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
  53. };
  54. typedef enum {
  55. AcceleratedCanvas,
  56. CaretBrowsing,
  57. CookiePolicies,
  58. DiskCache,
  59. DNSPrefetch,
  60. FontSize,
  61. FrameFlattening,
  62. Geolocation,
  63. HideBackground,
  64. Inspector,
  65. JavaScript,
  66. KioskMode,
  67. LoadImages,
  68. MediaManualPlay,
  69. Plugins,
  70. PreferredLanguages,
  71. RunInFullscreen,
  72. ScrollBars,
  73. ShowIndicators,
  74. SiteQuirks,
  75. SpellChecking,
  76. SpellLanguages,
  77. StrictTLS,
  78. Style,
  79. ZoomLevel,
  80. ParameterLast,
  81. } ParamName;
  82. typedef union {
  83. int b;
  84. int i;
  85. float f;
  86. const void *v;
  87. } Arg;
  88. typedef struct {
  89. Arg val;
  90. int force;
  91. } Parameter;
  92. typedef struct Client {
  93. GtkWidget *win;
  94. WebKitWebView *view;
  95. WebKitWebInspector *inspector;
  96. WebKitFindController *finder;
  97. WebKitHitTestResult *mousepos;
  98. GTlsCertificateFlags tlserr;
  99. Window xid;
  100. int progress, fullscreen, https, insecure;
  101. const char *title, *overtitle, *targeturi;
  102. const char *needle;
  103. struct Client *next;
  104. } Client;
  105. typedef struct {
  106. guint mod;
  107. guint keyval;
  108. void (*func)(Client *c, const Arg *a);
  109. const Arg arg;
  110. } Key;
  111. typedef struct {
  112. unsigned int target;
  113. unsigned int mask;
  114. guint button;
  115. void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
  116. const Arg arg;
  117. unsigned int stopevent;
  118. } Button;
  119. typedef struct {
  120. const char *uri;
  121. Parameter config[ParameterLast];
  122. regex_t re;
  123. } UriParameters;
  124. typedef struct {
  125. char *regex;
  126. char *style;
  127. regex_t re;
  128. } SiteStyle;
  129. /* Surf */
  130. static void usage(void);
  131. static void die(const char *errstr, ...);
  132. static void setup(void);
  133. static void sigchld(int unused);
  134. static void sighup(int unused);
  135. static char *buildfile(const char *path);
  136. static char *buildpath(const char *path);
  137. static const char *getuserhomedir(const char *user);
  138. static const char *getcurrentuserhomedir(void);
  139. static Client *newclient(Client *c);
  140. static void loaduri(Client *c, const Arg *a);
  141. static const char *geturi(Client *c);
  142. static void setatom(Client *c, int a, const char *v);
  143. static const char *getatom(Client *c, int a);
  144. static void updatetitle(Client *c);
  145. static void gettogglestats(Client *c);
  146. static void getpagestats(Client *c);
  147. static WebKitCookieAcceptPolicy cookiepolicy_get(void);
  148. static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
  149. static void seturiparameters(Client *c, const char *uri);
  150. static void setparameter(Client *c, int refresh, ParamName p, const Arg *a);
  151. static const char *getstyle(const char *uri);
  152. static void setstyle(Client *c, const char *stylefile);
  153. static void runscript(Client *c);
  154. static void evalscript(Client *c, const char *jsstr, ...);
  155. static void updatewinid(Client *c);
  156. static void handleplumb(Client *c, const char *uri);
  157. static void newwindow(Client *c, const Arg *a, int noembed);
  158. static void spawn(Client *c, const Arg *a);
  159. static void destroyclient(Client *c);
  160. static void cleanup(void);
  161. /* GTK/WebKit */
  162. static WebKitWebView *newview(Client *c, WebKitWebView *rv);
  163. static void initwebextensions(WebKitWebContext *wc, Client *c);
  164. static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
  165. Client *c);
  166. static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c);
  167. static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
  168. gpointer d);
  169. static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
  170. static void showview(WebKitWebView *v, Client *c);
  171. static GtkWidget *createwindow(Client *c);
  172. static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
  173. static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
  174. static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
  175. static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
  176. guint modifiers, Client *c);
  177. static gboolean permissionrequested(WebKitWebView *v,
  178. WebKitPermissionRequest *r, Client *c);
  179. static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
  180. WebKitPolicyDecisionType dt, Client *c);
  181. static void decidenavigation(WebKitPolicyDecision *d, Client *c);
  182. static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
  183. static void decideresource(WebKitPolicyDecision *d, Client *c);
  184. static void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e,
  185. Client *c);
  186. static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
  187. Client *c);
  188. static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
  189. static void download(Client *c, WebKitURIResponse *r);
  190. static void closeview(WebKitWebView *v, Client *c);
  191. static void destroywin(GtkWidget* w, Client *c);
  192. /* Hotkeys */
  193. static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
  194. static void reload(Client *c, const Arg *a);
  195. static void print(Client *c, const Arg *a);
  196. static void clipboard(Client *c, const Arg *a);
  197. static void zoom(Client *c, const Arg *a);
  198. static void scroll(Client *c, const Arg *a);
  199. static void navigate(Client *c, const Arg *a);
  200. static void stop(Client *c, const Arg *a);
  201. static void toggle(Client *c, const Arg *a);
  202. static void togglefullscreen(Client *c, const Arg *a);
  203. static void togglecookiepolicy(Client *c, const Arg *a);
  204. static void toggleinspector(Client *c, const Arg *a);
  205. static void find(Client *c, const Arg *a);
  206. /* Buttons */
  207. static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h);
  208. static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h);
  209. static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h);
  210. static char winid[64];
  211. static char togglestats[11];
  212. static char pagestats[2];
  213. static Atom atoms[AtomLast];
  214. static Window embed;
  215. static int showxid;
  216. static int cookiepolicy;
  217. static Display *dpy;
  218. static Client *clients;
  219. static GdkDevice *gdkkb;
  220. static char *stylefile;
  221. static const char *useragent;
  222. static Parameter *curconfig;
  223. char *argv0;
  224. /* configuration, allows nested code to access above variables */
  225. #include "config.h"
  226. void
  227. usage(void)
  228. {
  229. die("usage: %s [-bBdDfFgGiIkKmMnNpPsSvx] [-a cookiepolicies ] "
  230. "[-c cookiefile] [-e xid] [-r scriptfile] [-t stylefile] "
  231. "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
  232. }
  233. void
  234. die(const char *errstr, ...)
  235. {
  236. va_list ap;
  237. va_start(ap, errstr);
  238. vfprintf(stderr, errstr, ap);
  239. va_end(ap);
  240. exit(1);
  241. }
  242. void
  243. setup(void)
  244. {
  245. GdkDisplay *gdpy;
  246. int i, j;
  247. /* clean up any zombies immediately */
  248. sigchld(0);
  249. if (signal(SIGHUP, sighup) == SIG_ERR)
  250. die("Can't install SIGHUP handler");
  251. if (!(dpy = XOpenDisplay(NULL)))
  252. die("Can't open default display");
  253. /* atoms */
  254. atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
  255. atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
  256. atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
  257. gtk_init(NULL, NULL);
  258. gdpy = gdk_display_get_default();
  259. curconfig = defconfig;
  260. /* dirs and files */
  261. cookiefile = buildfile(cookiefile);
  262. scriptfile = buildfile(scriptfile);
  263. cachedir = buildpath(cachedir);
  264. gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy));
  265. if (!stylefile) {
  266. styledir = buildpath(styledir);
  267. for (i = 0; i < LENGTH(styles); ++i) {
  268. if (regcomp(&(styles[i].re), styles[i].regex,
  269. REG_EXTENDED)) {
  270. fprintf(stderr,
  271. "Could not compile regex: %s\n",
  272. styles[i].regex);
  273. styles[i].regex = NULL;
  274. }
  275. styles[i].style = g_strconcat(styledir, "/",
  276. styles[i].style, NULL);
  277. }
  278. g_free(styledir);
  279. } else {
  280. stylefile = buildfile(stylefile);
  281. }
  282. for (i = 0; i < LENGTH(uriparams); ++i) {
  283. if (!regcomp(&(uriparams[i].re), uriparams[i].uri,
  284. REG_EXTENDED)) {
  285. /* copy default parameters if they are not already set
  286. * or if they are forced */
  287. for (j = 0; j < ParameterLast; ++j) {
  288. if (!uriparams[i].config[j].force ||
  289. defconfig[j].force)
  290. uriparams[i].config[j] = defconfig[j];
  291. }
  292. } else {
  293. fprintf(stderr,
  294. "Could not compile regex: %s\n",
  295. uriparams[i].uri);
  296. uriparams[i].uri = NULL;
  297. }
  298. }
  299. }
  300. void
  301. sigchld(int unused)
  302. {
  303. if (signal(SIGCHLD, sigchld) == SIG_ERR)
  304. die("Can't install SIGCHLD handler");
  305. while (waitpid(-1, NULL, WNOHANG) > 0)
  306. ;
  307. }
  308. void
  309. sighup(int unused)
  310. {
  311. Arg a = { .b = 0 };
  312. Client *c;
  313. for (c = clients; c; c = c->next)
  314. reload(c, &a);
  315. }
  316. char *
  317. buildfile(const char *path)
  318. {
  319. char *dname, *bname, *bpath, *fpath;
  320. FILE *f;
  321. dname = g_path_get_dirname(path);
  322. bname = g_path_get_basename(path);
  323. bpath = buildpath(dname);
  324. g_free(dname);
  325. fpath = g_build_filename(bpath, bname, NULL);
  326. g_free(bpath);
  327. g_free(bname);
  328. if (!(f = fopen(fpath, "a")))
  329. die("Could not open file: %s\n", fpath);
  330. g_chmod(fpath, 0600); /* always */
  331. fclose(f);
  332. return fpath;
  333. }
  334. static const char*
  335. getuserhomedir(const char *user)
  336. {
  337. struct passwd *pw = getpwnam(user);
  338. if (!pw)
  339. die("Can't get user %s login information.\n", user);
  340. return pw->pw_dir;
  341. }
  342. static const char*
  343. getcurrentuserhomedir(void)
  344. {
  345. const char *homedir;
  346. const char *user;
  347. struct passwd *pw;
  348. homedir = getenv("HOME");
  349. if (homedir)
  350. return homedir;
  351. user = getenv("USER");
  352. if (user)
  353. return getuserhomedir(user);
  354. pw = getpwuid(getuid());
  355. if (!pw)
  356. die("Can't get current user home directory\n");
  357. return pw->pw_dir;
  358. }
  359. char *
  360. buildpath(const char *path)
  361. {
  362. char *apath, *name, *p, *fpath;
  363. const char *homedir;
  364. if (path[0] == '~') {
  365. if (path[1] == '/' || path[1] == '\0') {
  366. p = (char *)&path[1];
  367. homedir = getcurrentuserhomedir();
  368. } else {
  369. if ((p = strchr(path, '/')))
  370. name = g_strndup(&path[1], --p - path);
  371. else
  372. name = g_strdup(&path[1]);
  373. homedir = getuserhomedir(name);
  374. g_free(name);
  375. }
  376. apath = g_build_filename(homedir, p, NULL);
  377. } else {
  378. apath = g_strdup(path);
  379. }
  380. /* creating directory */
  381. if (g_mkdir_with_parents(apath, 0700) < 0)
  382. die("Could not access directory: %s\n", apath);
  383. fpath = realpath(apath, NULL);
  384. g_free(apath);
  385. return fpath;
  386. }
  387. Client *
  388. newclient(Client *rc)
  389. {
  390. Client *c;
  391. if (!(c = calloc(1, sizeof(Client))))
  392. die("Cannot malloc!\n");
  393. c->next = clients;
  394. clients = c;
  395. c->progress = 100;
  396. c->view = newview(c, rc ? rc->view : NULL);
  397. return c;
  398. }
  399. void
  400. loaduri(Client *c, const Arg *a)
  401. {
  402. struct stat st;
  403. char *url, *path;
  404. const char *uri = a->v;
  405. if (g_strcmp0(uri, "") == 0)
  406. return;
  407. if (g_str_has_prefix(uri, "http://") ||
  408. g_str_has_prefix(uri, "https://") ||
  409. g_str_has_prefix(uri, "file://") ||
  410. g_str_has_prefix(uri, "about:")) {
  411. url = g_strdup(uri);
  412. } else if (!stat(uri, &st) && (path = realpath(uri, NULL))) {
  413. url = g_strdup_printf("file://%s", path);
  414. free(path);
  415. } else {
  416. url = g_strdup_printf("http://%s", uri);
  417. }
  418. setatom(c, AtomUri, url);
  419. if (strcmp(url, geturi(c)) == 0) {
  420. reload(c, a);
  421. } else {
  422. webkit_web_view_load_uri(c->view, url);
  423. updatetitle(c);
  424. }
  425. g_free(url);
  426. }
  427. const char *
  428. geturi(Client *c)
  429. {
  430. const char *uri;
  431. if (!(uri = webkit_web_view_get_uri(c->view)))
  432. uri = "about:blank";
  433. return uri;
  434. }
  435. void
  436. setatom(Client *c, int a, const char *v)
  437. {
  438. XSync(dpy, False);
  439. XChangeProperty(dpy, c->xid,
  440. atoms[a], XA_STRING, 8, PropModeReplace,
  441. (unsigned char *)v, strlen(v) + 1);
  442. }
  443. const char *
  444. getatom(Client *c, int a)
  445. {
  446. static char buf[BUFSIZ];
  447. Atom adummy;
  448. int idummy;
  449. unsigned long ldummy;
  450. unsigned char *p = NULL;
  451. XGetWindowProperty(dpy, c->xid, atoms[a], 0L, BUFSIZ, False, XA_STRING,
  452. &adummy, &idummy, &ldummy, &ldummy, &p);
  453. if (p)
  454. strncpy(buf, (char *)p, LENGTH(buf) - 1);
  455. else
  456. buf[0] = '\0';
  457. XFree(p);
  458. return buf;
  459. }
  460. void
  461. updatetitle(Client *c)
  462. {
  463. char *title;
  464. const char *name = c->overtitle ? c->overtitle :
  465. c->title ? c->title : "";
  466. if (curconfig[ShowIndicators].val.b) {
  467. gettogglestats(c);
  468. getpagestats(c);
  469. if (c->progress != 100)
  470. title = g_strdup_printf("[%i%%] %s:%s | %s",
  471. c->progress, togglestats, pagestats, name);
  472. else
  473. title = g_strdup_printf("%s:%s | %s",
  474. togglestats, pagestats, name);
  475. gtk_window_set_title(GTK_WINDOW(c->win), title);
  476. g_free(title);
  477. } else {
  478. gtk_window_set_title(GTK_WINDOW(c->win), name);
  479. }
  480. }
  481. void
  482. gettogglestats(Client *c)
  483. {
  484. togglestats[0] = cookiepolicy_set(cookiepolicy_get());
  485. togglestats[1] = curconfig[CaretBrowsing].val.b ? 'C' : 'c';
  486. togglestats[2] = curconfig[Geolocation].val.b ? 'G' : 'g';
  487. togglestats[3] = curconfig[DiskCache].val.b ? 'D' : 'd';
  488. togglestats[4] = curconfig[LoadImages].val.b ? 'I' : 'i';
  489. togglestats[5] = curconfig[JavaScript].val.b ? 'S' : 's';
  490. togglestats[6] = curconfig[Plugins].val.b ? 'V' : 'v';
  491. togglestats[7] = curconfig[Style].val.b ? 'M' : 'm';
  492. togglestats[8] = curconfig[FrameFlattening].val.b ? 'F' : 'f';
  493. togglestats[9] = curconfig[StrictTLS].val.b ? 'T' : 't';
  494. togglestats[10] = '\0';
  495. }
  496. void
  497. getpagestats(Client *c)
  498. {
  499. if (c->https)
  500. pagestats[0] = (c->tlserr || c->insecure) ? 'U' : 'T';
  501. else
  502. pagestats[0] = '-';
  503. pagestats[1] = '\0';
  504. }
  505. WebKitCookieAcceptPolicy
  506. cookiepolicy_get(void)
  507. {
  508. switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy]) {
  509. case 'a':
  510. return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
  511. case '@':
  512. return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
  513. default: /* fallthrough */
  514. case 'A':
  515. return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
  516. }
  517. }
  518. char
  519. cookiepolicy_set(const WebKitCookieAcceptPolicy p)
  520. {
  521. switch (p) {
  522. case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
  523. return 'a';
  524. case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
  525. return '@';
  526. default: /* fallthrough */
  527. case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
  528. return 'A';
  529. }
  530. }
  531. void
  532. seturiparameters(Client *c, const char *uri)
  533. {
  534. int i;
  535. for (i = 0; i < LENGTH(uriparams); ++i) {
  536. if (uriparams[i].uri &&
  537. !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) {
  538. curconfig = uriparams[i].config;
  539. break;
  540. }
  541. }
  542. for (i = 0; i < ParameterLast; ++i)
  543. setparameter(c, 0, i, &curconfig[i].val);
  544. }
  545. void
  546. setparameter(Client *c, int refresh, ParamName p, const Arg *a)
  547. {
  548. GdkRGBA bgcolor = { 0 };
  549. WebKitSettings *s = webkit_web_view_get_settings(c->view);
  550. switch (p) {
  551. case AcceleratedCanvas:
  552. webkit_settings_set_enable_accelerated_2d_canvas(s, a->b);
  553. break;
  554. case CaretBrowsing:
  555. webkit_settings_set_enable_caret_browsing(s, a->b);
  556. refresh = 0;
  557. break;
  558. case CookiePolicies:
  559. webkit_cookie_manager_set_accept_policy(
  560. webkit_web_context_get_cookie_manager(
  561. webkit_web_view_get_context(c->view)),
  562. cookiepolicy_get());
  563. refresh = 0;
  564. break;
  565. case DiskCache:
  566. webkit_web_context_set_cache_model(
  567. webkit_web_view_get_context(c->view), a->b ?
  568. WEBKIT_CACHE_MODEL_WEB_BROWSER :
  569. WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
  570. return; /* do not update */
  571. case DNSPrefetch:
  572. webkit_settings_set_enable_dns_prefetching(s, a->b);
  573. return; /* do not update */
  574. case FontSize:
  575. webkit_settings_set_default_font_size(s, a->i);
  576. return; /* do not update */
  577. case FrameFlattening:
  578. webkit_settings_set_enable_frame_flattening(s, a->b);
  579. break;
  580. case Geolocation:
  581. refresh = 0;
  582. break;
  583. case HideBackground:
  584. if (a->b)
  585. webkit_web_view_set_background_color(c->view, &bgcolor);
  586. return; /* do not update */
  587. case Inspector:
  588. webkit_settings_set_enable_developer_extras(s, a->b);
  589. return; /* do not update */
  590. case JavaScript:
  591. webkit_settings_set_enable_javascript(s, a->b);
  592. break;
  593. case KioskMode:
  594. return; /* do nothing */
  595. case LoadImages:
  596. webkit_settings_set_auto_load_images(s, a->b);
  597. break;
  598. case MediaManualPlay:
  599. webkit_settings_set_media_playback_requires_user_gesture(s, a->b);
  600. break;
  601. case Plugins:
  602. webkit_settings_set_enable_plugins(s, a->b);
  603. break;
  604. case PreferredLanguages:
  605. return; /* do nothing */
  606. case RunInFullscreen:
  607. return; /* do nothing */
  608. case ScrollBars:
  609. /* Disabled until we write some WebKitWebExtension for
  610. * manipulating the DOM directly.
  611. enablescrollbars = !enablescrollbars;
  612. evalscript(c, "document.documentElement.style.overflow = '%s'",
  613. enablescrollbars ? "auto" : "hidden");
  614. */
  615. return; /* do not update */
  616. case ShowIndicators:
  617. break;
  618. case SiteQuirks:
  619. webkit_settings_set_enable_site_specific_quirks(s, a->b);
  620. break;
  621. case SpellChecking:
  622. webkit_web_context_set_spell_checking_enabled(
  623. webkit_web_view_get_context(c->view), a->b);
  624. return; /* do not update */
  625. case SpellLanguages:
  626. return; /* do nothing */
  627. case StrictTLS:
  628. webkit_web_context_set_tls_errors_policy(
  629. webkit_web_view_get_context(c->view), a->b ?
  630. WEBKIT_TLS_ERRORS_POLICY_FAIL :
  631. WEBKIT_TLS_ERRORS_POLICY_IGNORE);
  632. break;
  633. case Style:
  634. if (a->b)
  635. setstyle(c, getstyle(geturi(c)));
  636. else
  637. webkit_user_content_manager_remove_all_style_sheets(
  638. webkit_web_view_get_user_content_manager(c->view));
  639. refresh = 0;
  640. break;
  641. case ZoomLevel:
  642. webkit_web_view_set_zoom_level(c->view, a->f);
  643. return; /* do not update */
  644. default:
  645. return; /* do nothing */
  646. }
  647. updatetitle(c);
  648. if (refresh)
  649. reload(c, a);
  650. }
  651. const char *
  652. getstyle(const char *uri)
  653. {
  654. int i;
  655. if (stylefile)
  656. return stylefile;
  657. for (i = 0; i < LENGTH(styles); ++i) {
  658. if (styles[i].regex &&
  659. !regexec(&(styles[i].re), uri, 0, NULL, 0))
  660. return styles[i].style;
  661. }
  662. return "";
  663. }
  664. void
  665. setstyle(Client *c, const char *stylefile)
  666. {
  667. gchar *style;
  668. if (!g_file_get_contents(stylefile, &style, NULL, NULL)) {
  669. fprintf(stderr, "Could not read style file: %s\n", stylefile);
  670. return;
  671. }
  672. webkit_user_content_manager_add_style_sheet(
  673. webkit_web_view_get_user_content_manager(c->view),
  674. webkit_user_style_sheet_new(style,
  675. WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
  676. WEBKIT_USER_STYLE_LEVEL_USER,
  677. NULL, NULL));
  678. g_free(style);
  679. }
  680. void
  681. runscript(Client *c)
  682. {
  683. gchar *script;
  684. gsize l;
  685. if (g_file_get_contents(scriptfile, &script, &l, NULL) && l)
  686. evalscript(c, script);
  687. g_free(script);
  688. }
  689. void
  690. evalscript(Client *c, const char *jsstr, ...)
  691. {
  692. va_list ap;
  693. gchar *script;
  694. va_start(ap, jsstr);
  695. script = g_strdup_vprintf(jsstr, ap);
  696. va_end(ap);
  697. webkit_web_view_run_javascript(c->view, script, NULL, NULL, NULL);
  698. g_free(script);
  699. }
  700. void
  701. updatewinid(Client *c)
  702. {
  703. snprintf(winid, LENGTH(winid), "%lu", c->xid);
  704. }
  705. void
  706. handleplumb(Client *c, const char *uri)
  707. {
  708. Arg a = (Arg)PLUMB(uri);
  709. spawn(c, &a);
  710. }
  711. void
  712. newwindow(Client *c, const Arg *a, int noembed)
  713. {
  714. int i = 0;
  715. char tmp[64];
  716. const char *cmd[26], *uri;
  717. const Arg arg = { .v = cmd };
  718. cmd[i++] = argv0;
  719. cmd[i++] = "-a";
  720. cmd[i++] = curconfig[CookiePolicies].val.v;
  721. cmd[i++] = curconfig[ScrollBars].val.b ? "-B" : "-b";
  722. if (cookiefile && g_strcmp0(cookiefile, "")) {
  723. cmd[i++] = "-c";
  724. cmd[i++] = cookiefile;
  725. }
  726. cmd[i++] = curconfig[DiskCache].val.b ? "-D" : "-d";
  727. if (embed && !noembed) {
  728. cmd[i++] = "-e";
  729. snprintf(tmp, LENGTH(tmp), "%lu", embed);
  730. cmd[i++] = tmp;
  731. }
  732. cmd[i++] = curconfig[RunInFullscreen].val.b ? "-F" : "-f" ;
  733. cmd[i++] = curconfig[Geolocation].val.b ? "-G" : "-g" ;
  734. cmd[i++] = curconfig[LoadImages].val.b ? "-I" : "-i" ;
  735. cmd[i++] = curconfig[KioskMode].val.b ? "-K" : "-k" ;
  736. cmd[i++] = curconfig[Style].val.b ? "-M" : "-m" ;
  737. cmd[i++] = curconfig[Inspector].val.b ? "-N" : "-n" ;
  738. cmd[i++] = curconfig[Plugins].val.b ? "-P" : "-p" ;
  739. if (scriptfile && g_strcmp0(scriptfile, "")) {
  740. cmd[i++] = "-r";
  741. cmd[i++] = scriptfile;
  742. }
  743. cmd[i++] = curconfig[JavaScript].val.b ? "-S" : "-s";
  744. if (stylefile && g_strcmp0(stylefile, "")) {
  745. cmd[i++] = "-t";
  746. cmd[i++] = stylefile;
  747. }
  748. if (fulluseragent && g_strcmp0(fulluseragent, "")) {
  749. cmd[i++] = "-u";
  750. cmd[i++] = fulluseragent;
  751. }
  752. if (showxid)
  753. cmd[i++] = "-x";
  754. /* do not keep zoom level */
  755. cmd[i++] = "--";
  756. if ((uri = a->v))
  757. cmd[i++] = uri;
  758. cmd[i] = NULL;
  759. spawn(c, &arg);
  760. }
  761. void
  762. spawn(Client *c, const Arg *a)
  763. {
  764. if (fork() == 0) {
  765. if (dpy)
  766. close(ConnectionNumber(dpy));
  767. setsid();
  768. execvp(((char **)a->v)[0], (char **)a->v);
  769. fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
  770. perror(" failed");
  771. exit(1);
  772. }
  773. }
  774. void
  775. destroyclient(Client *c)
  776. {
  777. Client *p;
  778. webkit_web_view_stop_loading(c->view);
  779. /* Not needed, has already been called
  780. gtk_widget_destroy(c->win);
  781. */
  782. for (p = clients; p && p->next != c; p = p->next)
  783. ;
  784. if (p)
  785. p->next = c->next;
  786. else
  787. clients = c->next;
  788. free(c);
  789. }
  790. void
  791. cleanup(void)
  792. {
  793. while (clients)
  794. destroyclient(clients);
  795. g_free(cookiefile);
  796. g_free(scriptfile);
  797. g_free(stylefile);
  798. g_free(cachedir);
  799. XCloseDisplay(dpy);
  800. }
  801. WebKitWebView *
  802. newview(Client *c, WebKitWebView *rv)
  803. {
  804. WebKitWebView *v;
  805. WebKitSettings *settings;
  806. WebKitUserContentManager *contentmanager;
  807. WebKitWebContext *context;
  808. /* Webview */
  809. if (rv) {
  810. v = WEBKIT_WEB_VIEW(
  811. webkit_web_view_new_with_related_view(rv));
  812. } else {
  813. settings = webkit_settings_new_with_settings(
  814. "auto-load-images", curconfig[LoadImages].val.b,
  815. "default-font-size", curconfig[FontSize].val.i,
  816. "enable-caret-browsing", curconfig[CaretBrowsing].val.b,
  817. "enable-developer-extras", curconfig[Inspector].val.b,
  818. "enable-dns-prefetching", curconfig[DNSPrefetch].val.b,
  819. "enable-frame-flattening", curconfig[FrameFlattening].val.b,
  820. "enable-html5-database", curconfig[DiskCache].val.b,
  821. "enable-html5-local-storage", curconfig[DiskCache].val.b,
  822. "enable-javascript", curconfig[JavaScript].val.b,
  823. "enable-plugins", curconfig[Plugins].val.b,
  824. "enable-accelerated-2d-canvas", curconfig[AcceleratedCanvas].val.b,
  825. "enable-site-specific-quirks", curconfig[SiteQuirks].val.b,
  826. "media-playback-requires-user-gesture", curconfig[MediaManualPlay].val.b,
  827. NULL);
  828. /* For mor interesting settings, have a look at
  829. * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html */
  830. if (strcmp(fulluseragent, "")) {
  831. webkit_settings_set_user_agent(settings, fulluseragent);
  832. } else if (surfuseragent) {
  833. webkit_settings_set_user_agent_with_application_details(
  834. settings, "Surf", VERSION);
  835. }
  836. useragent = webkit_settings_get_user_agent(settings);
  837. contentmanager = webkit_user_content_manager_new();
  838. context = webkit_web_context_new_with_website_data_manager(
  839. webkit_website_data_manager_new(
  840. "base-cache-directory", cachedir,
  841. "base-data-directory", cachedir,
  842. NULL));
  843. /* rendering process model, can be a shared unique one
  844. * or one for each view */
  845. webkit_web_context_set_process_model(context,
  846. WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
  847. /* TLS */
  848. webkit_web_context_set_tls_errors_policy(context,
  849. curconfig[StrictTLS].val.b ? WEBKIT_TLS_ERRORS_POLICY_FAIL :
  850. WEBKIT_TLS_ERRORS_POLICY_IGNORE);
  851. /* disk cache */
  852. webkit_web_context_set_cache_model(context,
  853. curconfig[DiskCache].val.b ? WEBKIT_CACHE_MODEL_WEB_BROWSER :
  854. WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
  855. /* Currently only works with text file to be compatible with curl */
  856. webkit_cookie_manager_set_persistent_storage(
  857. webkit_web_context_get_cookie_manager(context), cookiefile,
  858. WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
  859. /* cookie policy */
  860. webkit_cookie_manager_set_accept_policy(
  861. webkit_web_context_get_cookie_manager(context),
  862. cookiepolicy_get());
  863. /* languages */
  864. webkit_web_context_set_preferred_languages(context,
  865. curconfig[PreferredLanguages].val.v);
  866. webkit_web_context_set_spell_checking_languages(context,
  867. curconfig[SpellLanguages].val.v);
  868. webkit_web_context_set_spell_checking_enabled(context,
  869. curconfig[SpellChecking].val.b);
  870. g_signal_connect(G_OBJECT(context), "download-started",
  871. G_CALLBACK(downloadstarted), c);
  872. g_signal_connect(G_OBJECT(context), "initialize-web-extensions",
  873. G_CALLBACK(initwebextensions), c);
  874. v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
  875. "settings", settings,
  876. "user-content-manager", contentmanager,
  877. "web-context", context,
  878. NULL);
  879. }
  880. g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
  881. G_CALLBACK(progresschanged), c);
  882. g_signal_connect(G_OBJECT(v), "notify::title",
  883. G_CALLBACK(titlechanged), c);
  884. g_signal_connect(G_OBJECT(v), "button-release-event",
  885. G_CALLBACK(buttonreleased), c);
  886. g_signal_connect(G_OBJECT(v), "close",
  887. G_CALLBACK(closeview), c);
  888. g_signal_connect(G_OBJECT(v), "create",
  889. G_CALLBACK(createview), c);
  890. g_signal_connect(G_OBJECT(v), "decide-policy",
  891. G_CALLBACK(decidepolicy), c);
  892. g_signal_connect(G_OBJECT(v), "insecure-content-detected",
  893. G_CALLBACK(insecurecontent), c);
  894. g_signal_connect(G_OBJECT(v), "load-changed",
  895. G_CALLBACK(loadchanged), c);
  896. g_signal_connect(G_OBJECT(v), "mouse-target-changed",
  897. G_CALLBACK(mousetargetchanged), c);
  898. g_signal_connect(G_OBJECT(v), "permission-request",
  899. G_CALLBACK(permissionrequested), c);
  900. g_signal_connect(G_OBJECT(v), "ready-to-show",
  901. G_CALLBACK(showview), c);
  902. return v;
  903. }
  904. void
  905. initwebextensions(WebKitWebContext *wc, Client *c)
  906. {
  907. webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
  908. }
  909. GtkWidget *
  910. createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
  911. {
  912. Client *n;
  913. switch (webkit_navigation_action_get_navigation_type(a)) {
  914. case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
  915. /*
  916. * popup windows of type “other” are almost always triggered
  917. * by user gesture, so inverse the logic here
  918. */
  919. /* instead of this, compare destination uri to mouse-over uri for validating window */
  920. if (webkit_navigation_action_is_user_gesture(a))
  921. return NULL;
  922. case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
  923. case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
  924. case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
  925. case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
  926. case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
  927. n = newclient(c);
  928. break;
  929. default:
  930. return NULL;
  931. }
  932. return GTK_WIDGET(n->view);
  933. }
  934. gboolean
  935. buttonreleased(GtkWidget *w, GdkEvent *e, Client *c)
  936. {
  937. WebKitHitTestResultContext element;
  938. int i;
  939. element = webkit_hit_test_result_get_context(c->mousepos);
  940. for (i = 0; i < LENGTH(buttons); ++i) {
  941. if (element & buttons[i].target &&
  942. e->button.button == buttons[i].button &&
  943. CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) &&
  944. buttons[i].func) {
  945. buttons[i].func(c, &buttons[i].arg, c->mousepos);
  946. return buttons[i].stopevent;
  947. }
  948. }
  949. return FALSE;
  950. }
  951. GdkFilterReturn
  952. processx(GdkXEvent *e, GdkEvent *event, gpointer d)
  953. {
  954. Client *c = (Client *)d;
  955. XPropertyEvent *ev;
  956. Arg a;
  957. if (((XEvent *)e)->type == PropertyNotify) {
  958. ev = &((XEvent *)e)->xproperty;
  959. if (ev->state == PropertyNewValue) {
  960. if (ev->atom == atoms[AtomFind]) {
  961. find(c, NULL);
  962. return GDK_FILTER_REMOVE;
  963. } else if (ev->atom == atoms[AtomGo]) {
  964. a.v = getatom(c, AtomGo);
  965. loaduri(c, &a);
  966. return GDK_FILTER_REMOVE;
  967. }
  968. }
  969. }
  970. return GDK_FILTER_CONTINUE;
  971. }
  972. gboolean
  973. winevent(GtkWidget *w, GdkEvent *e, Client *c)
  974. {
  975. int i;
  976. switch (e->type) {
  977. case GDK_ENTER_NOTIFY:
  978. c->overtitle = c->targeturi;
  979. updatetitle(c);
  980. break;
  981. case GDK_KEY_PRESS:
  982. if (!curconfig[KioskMode].val.b) {
  983. for (i = 0; i < LENGTH(keys); ++i) {
  984. if (gdk_keyval_to_lower(e->key.keyval) ==
  985. keys[i].keyval &&
  986. CLEANMASK(e->key.state) == keys[i].mod &&
  987. keys[i].func) {
  988. updatewinid(c);
  989. keys[i].func(c, &(keys[i].arg));
  990. return TRUE;
  991. }
  992. }
  993. }
  994. case GDK_LEAVE_NOTIFY:
  995. c->overtitle = NULL;
  996. updatetitle(c);
  997. break;
  998. case GDK_WINDOW_STATE:
  999. if (e->window_state.changed_mask ==
  1000. GDK_WINDOW_STATE_FULLSCREEN)
  1001. c->fullscreen = e->window_state.new_window_state &
  1002. GDK_WINDOW_STATE_FULLSCREEN;
  1003. break;
  1004. default:
  1005. break;
  1006. }
  1007. return FALSE;
  1008. }
  1009. void
  1010. showview(WebKitWebView *v, Client *c)
  1011. {
  1012. GdkRGBA bgcolor = { 0 };
  1013. GdkWindow *gwin;
  1014. c->finder = webkit_web_view_get_find_controller(c->view);
  1015. c->inspector = webkit_web_view_get_inspector(c->view);
  1016. c->win = createwindow(c);
  1017. gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
  1018. gtk_widget_show_all(c->win);
  1019. gtk_widget_grab_focus(GTK_WIDGET(c->view));
  1020. gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
  1021. c->xid = gdk_x11_window_get_xid(gwin);
  1022. updatewinid(c);
  1023. if (showxid) {
  1024. gdk_display_sync(gtk_widget_get_display(c->win));
  1025. puts(winid);
  1026. }
  1027. if (curconfig[HideBackground].val.b)
  1028. webkit_web_view_set_background_color(c->view, &bgcolor);
  1029. if (!curconfig[KioskMode].val.b) {
  1030. gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
  1031. gdk_window_add_filter(gwin, processx, c);
  1032. }
  1033. if (curconfig[RunInFullscreen].val.b)
  1034. togglefullscreen(c, NULL);
  1035. if (curconfig[ZoomLevel].val.f != 1.0)
  1036. webkit_web_view_set_zoom_level(c->view,
  1037. curconfig[ZoomLevel].val.f);
  1038. setatom(c, AtomFind, "");
  1039. setatom(c, AtomUri, "about:blank");
  1040. }
  1041. GtkWidget *
  1042. createwindow(Client *c)
  1043. {
  1044. char *wmstr;
  1045. GtkWidget *w;
  1046. if (embed) {
  1047. w = gtk_plug_new(embed);
  1048. } else {
  1049. w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  1050. wmstr = g_path_get_basename(argv0);
  1051. gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
  1052. g_free(wmstr);
  1053. wmstr = g_strdup_printf("%s[%lu]", "Surf",
  1054. webkit_web_view_get_page_id(c->view));
  1055. gtk_window_set_role(GTK_WINDOW(w), wmstr);
  1056. g_free(wmstr);
  1057. gtk_window_set_default_size(GTK_WINDOW(w), winsize[0], winsize[1]);
  1058. }
  1059. g_signal_connect(G_OBJECT(w), "destroy",
  1060. G_CALLBACK(destroywin), c);
  1061. g_signal_connect(G_OBJECT(w), "enter-notify-event",
  1062. G_CALLBACK(winevent), c);
  1063. g_signal_connect(G_OBJECT(w), "key-press-event",
  1064. G_CALLBACK(winevent), c);
  1065. g_signal_connect(G_OBJECT(w), "leave-notify-event",
  1066. G_CALLBACK(winevent), c);
  1067. g_signal_connect(G_OBJECT(w), "window-state-event",
  1068. G_CALLBACK(winevent), c);
  1069. return w;
  1070. }
  1071. void
  1072. loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
  1073. {
  1074. const char *title = geturi(c);
  1075. switch (e) {
  1076. case WEBKIT_LOAD_STARTED:
  1077. curconfig = defconfig;
  1078. setatom(c, AtomUri, title);
  1079. c->title = title;
  1080. c->https = c->insecure = 0;
  1081. seturiparameters(c, geturi(c));
  1082. break;
  1083. case WEBKIT_LOAD_REDIRECTED:
  1084. setatom(c, AtomUri, title);
  1085. c->title = title;
  1086. seturiparameters(c, geturi(c));
  1087. break;
  1088. case WEBKIT_LOAD_COMMITTED:
  1089. c->https = webkit_web_view_get_tls_info(c->view, NULL,
  1090. &c->tlserr);
  1091. break;
  1092. case WEBKIT_LOAD_FINISHED:
  1093. /* Disabled until we write some WebKitWebExtension for
  1094. * manipulating the DOM directly.
  1095. evalscript(c, "document.documentElement.style.overflow = '%s'",
  1096. enablescrollbars ? "auto" : "hidden");
  1097. */
  1098. runscript(c);
  1099. break;
  1100. }
  1101. updatetitle(c);
  1102. }
  1103. void
  1104. progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
  1105. {
  1106. c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
  1107. 100;
  1108. updatetitle(c);
  1109. }
  1110. void
  1111. titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
  1112. {
  1113. c->title = webkit_web_view_get_title(c->view);
  1114. updatetitle(c);
  1115. }
  1116. void
  1117. mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
  1118. Client *c)
  1119. {
  1120. WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h);
  1121. /* Keep the hit test to know where is the pointer on the next click */
  1122. c->mousepos = h;
  1123. if (hc & OnLink)
  1124. c->targeturi = webkit_hit_test_result_get_link_uri(h);
  1125. else if (hc & OnImg)
  1126. c->targeturi = webkit_hit_test_result_get_image_uri(h);
  1127. else if (hc & OnMedia)
  1128. c->targeturi = webkit_hit_test_result_get_media_uri(h);
  1129. else
  1130. c->targeturi = NULL;
  1131. c->overtitle = c->targeturi;
  1132. updatetitle(c);
  1133. }
  1134. gboolean
  1135. permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
  1136. {
  1137. if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
  1138. if (curconfig[Geolocation].val.b)
  1139. webkit_permission_request_allow(r);
  1140. else
  1141. webkit_permission_request_deny(r);
  1142. return TRUE;
  1143. }
  1144. return FALSE;
  1145. }
  1146. gboolean
  1147. decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
  1148. WebKitPolicyDecisionType dt, Client *c)
  1149. {
  1150. switch (dt) {
  1151. case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
  1152. decidenavigation(d, c);
  1153. break;
  1154. case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
  1155. decidenewwindow(d, c);
  1156. break;
  1157. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  1158. decideresource(d, c);
  1159. break;
  1160. default:
  1161. webkit_policy_decision_ignore(d);
  1162. break;
  1163. }
  1164. return TRUE;
  1165. }
  1166. void
  1167. decidenavigation(WebKitPolicyDecision *d, Client *c)
  1168. {
  1169. WebKitNavigationAction *a =
  1170. webkit_navigation_policy_decision_get_navigation_action(
  1171. WEBKIT_NAVIGATION_POLICY_DECISION(d));
  1172. switch (webkit_navigation_action_get_navigation_type(a)) {
  1173. case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
  1174. case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
  1175. case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
  1176. case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
  1177. case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
  1178. case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
  1179. default:
  1180. /* Do not navigate to links with a "_blank" target (popup) */
  1181. if (webkit_navigation_policy_decision_get_frame_name(
  1182. WEBKIT_NAVIGATION_POLICY_DECISION(d))) {
  1183. webkit_policy_decision_ignore(d);
  1184. } else {
  1185. /* Filter out navigation to different domain ? */
  1186. /* get action→urirequest, copy and load in new window+view
  1187. * on Ctrl+Click ? */
  1188. webkit_policy_decision_use(d);
  1189. }
  1190. break;
  1191. }
  1192. }
  1193. void
  1194. decidenewwindow(WebKitPolicyDecision *d, Client *c)
  1195. {
  1196. Arg arg;
  1197. WebKitNavigationAction *a =
  1198. webkit_navigation_policy_decision_get_navigation_action(
  1199. WEBKIT_NAVIGATION_POLICY_DECISION(d));
  1200. switch (webkit_navigation_action_get_navigation_type(a)) {
  1201. case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
  1202. case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
  1203. case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
  1204. case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
  1205. case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
  1206. /* Filter domains here */
  1207. /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
  1208. * test for link clicked but no button ? */
  1209. arg.v = webkit_uri_request_get_uri(
  1210. webkit_navigation_action_get_request(a));
  1211. newwindow(c, &arg, 0);
  1212. break;
  1213. case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
  1214. default:
  1215. break;
  1216. }
  1217. webkit_policy_decision_ignore(d);
  1218. }
  1219. void
  1220. decideresource(WebKitPolicyDecision *d, Client *c)
  1221. {
  1222. int i, isascii = 1;
  1223. WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
  1224. WebKitURIResponse *res =
  1225. webkit_response_policy_decision_get_response(r);
  1226. const gchar *uri = webkit_uri_response_get_uri(res);
  1227. if (g_str_has_suffix(uri, "/favicon.ico")) {
  1228. webkit_policy_decision_ignore(d);
  1229. return;
  1230. }
  1231. if (!g_str_has_prefix(uri, "http://")
  1232. && !g_str_has_prefix(uri, "https://")
  1233. && !g_str_has_prefix(uri, "about:")
  1234. && !g_str_has_prefix(uri, "file://")
  1235. && !g_str_has_prefix(uri, "data:")
  1236. && !g_str_has_prefix(uri, "blob:")
  1237. && strlen(uri) > 0) {
  1238. for (i = 0; i < strlen(uri); i++) {
  1239. if (!g_ascii_isprint(uri[i])) {
  1240. isascii = 0;
  1241. break;
  1242. }
  1243. }
  1244. if (isascii) {
  1245. handleplumb(c, uri);
  1246. webkit_policy_decision_ignore(d);
  1247. return;
  1248. }
  1249. }
  1250. if (webkit_response_policy_decision_is_mime_type_supported(r)) {
  1251. webkit_policy_decision_use(d);
  1252. } else {
  1253. webkit_policy_decision_ignore(d);
  1254. download(c, res);
  1255. }
  1256. }
  1257. void
  1258. insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, Client *c)
  1259. {
  1260. c->insecure = 1;
  1261. }
  1262. void
  1263. downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
  1264. {
  1265. g_signal_connect(G_OBJECT(d), "notify::response",
  1266. G_CALLBACK(responsereceived), c);
  1267. }
  1268. void
  1269. responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
  1270. {
  1271. download(c, webkit_download_get_response(d));
  1272. webkit_download_cancel(d);
  1273. }
  1274. void
  1275. download(Client *c, WebKitURIResponse *r)
  1276. {
  1277. Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
  1278. spawn(c, &a);
  1279. }
  1280. void
  1281. closeview(WebKitWebView *v, Client *c)
  1282. {
  1283. gtk_widget_destroy(c->win);
  1284. }
  1285. void
  1286. destroywin(GtkWidget* w, Client *c)
  1287. {
  1288. destroyclient(c);
  1289. if (!clients)
  1290. gtk_main_quit();
  1291. }
  1292. void
  1293. pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
  1294. {
  1295. Arg a = {.v = text };
  1296. if (text)
  1297. loaduri((Client *) d, &a);
  1298. }
  1299. void
  1300. reload(Client *c, const Arg *a)
  1301. {
  1302. if (a->b)
  1303. webkit_web_view_reload_bypass_cache(c->view);
  1304. else
  1305. webkit_web_view_reload(c->view);
  1306. }
  1307. void
  1308. print(Client *c, const Arg *a)
  1309. {
  1310. webkit_print_operation_run_dialog(webkit_print_operation_new(c->view),
  1311. GTK_WINDOW(c->win));
  1312. }
  1313. void
  1314. clipboard(Client *c, const Arg *a)
  1315. {
  1316. if (a->b) { /* load clipboard uri */
  1317. gtk_clipboard_request_text(gtk_clipboard_get(
  1318. GDK_SELECTION_PRIMARY),
  1319. pasteuri, c);
  1320. } else { /* copy uri */
  1321. gtk_clipboard_set_text(gtk_clipboard_get(
  1322. GDK_SELECTION_PRIMARY), c->targeturi
  1323. ? c->targeturi : geturi(c), -1);
  1324. }
  1325. }
  1326. void
  1327. zoom(Client *c, const Arg *a)
  1328. {
  1329. if (a->i > 0)
  1330. webkit_web_view_set_zoom_level(c->view,
  1331. curconfig[ZoomLevel].val.f + 0.1);
  1332. else if (a->i < 0)
  1333. webkit_web_view_set_zoom_level(c->view,
  1334. curconfig[ZoomLevel].val.f - 0.1);
  1335. else
  1336. webkit_web_view_set_zoom_level(c->view, 1.0);
  1337. curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view);
  1338. }
  1339. void
  1340. scroll(Client *c, const Arg *a)
  1341. {
  1342. GdkEvent *ev = gdk_event_new(GDK_KEY_PRESS);
  1343. gdk_event_set_device(ev, gdkkb);
  1344. ev->key.window = gtk_widget_get_window(GTK_WIDGET(c->win));
  1345. ev->key.state = GDK_CONTROL_MASK;
  1346. ev->key.time = GDK_CURRENT_TIME;
  1347. switch (a->i) {
  1348. case 'd':
  1349. ev->key.keyval = GDK_KEY_Down;
  1350. break;
  1351. case 'D':
  1352. ev->key.keyval = GDK_KEY_Page_Down;
  1353. break;
  1354. case 'l':
  1355. ev->key.keyval = GDK_KEY_Left;
  1356. break;
  1357. case 'r':
  1358. ev->key.keyval = GDK_KEY_Right;
  1359. break;
  1360. case 'U':
  1361. ev->key.keyval = GDK_KEY_Page_Up;
  1362. break;
  1363. case 'u':
  1364. ev->key.keyval = GDK_KEY_Up;
  1365. break;
  1366. }
  1367. gdk_event_put(ev);
  1368. }
  1369. void
  1370. navigate(Client *c, const Arg *a)
  1371. {
  1372. if (a->i < 0)
  1373. webkit_web_view_go_back(c->view);
  1374. else if (a->i > 0)
  1375. webkit_web_view_go_forward(c->view);
  1376. }
  1377. void
  1378. stop(Client *c, const Arg *a)
  1379. {
  1380. webkit_web_view_stop_loading(c->view);
  1381. }
  1382. void
  1383. toggle(Client *c, const Arg *a)
  1384. {
  1385. curconfig[a->i].val.b ^= 1;
  1386. setparameter(c, 1, (ParamName)a->i, &curconfig[a->i].val);
  1387. }
  1388. void
  1389. togglefullscreen(Client *c, const Arg *a)
  1390. {
  1391. /* toggling value is handled in winevent() */
  1392. if (c->fullscreen)
  1393. gtk_window_unfullscreen(GTK_WINDOW(c->win));
  1394. else
  1395. gtk_window_fullscreen(GTK_WINDOW(c->win));
  1396. }
  1397. void
  1398. togglecookiepolicy(Client *c, const Arg *a)
  1399. {
  1400. ++cookiepolicy;
  1401. cookiepolicy %= strlen(curconfig[CookiePolicies].val.v);
  1402. setparameter(c, 0, CookiePolicies, NULL);
  1403. }
  1404. void
  1405. toggleinspector(Client *c, const Arg *a)
  1406. {
  1407. if (webkit_web_inspector_is_attached(c->inspector))
  1408. webkit_web_inspector_close(c->inspector);
  1409. else if (curconfig[Inspector].val.b)
  1410. webkit_web_inspector_show(c->inspector);
  1411. }
  1412. void
  1413. find(Client *c, const Arg *a)
  1414. {
  1415. const char *s, *f;
  1416. if (a && a->i) {
  1417. if (a->i > 0)
  1418. webkit_find_controller_search_next(c->finder);
  1419. else
  1420. webkit_find_controller_search_previous(c->finder);
  1421. } else {
  1422. s = getatom(c, AtomFind);
  1423. f = webkit_find_controller_get_search_text(c->finder);
  1424. if (g_strcmp0(f, s) == 0) /* reset search */
  1425. webkit_find_controller_search(c->finder, "", findopts,
  1426. G_MAXUINT);
  1427. webkit_find_controller_search(c->finder, s, findopts,
  1428. G_MAXUINT);
  1429. if (strcmp(s, "") == 0)
  1430. webkit_find_controller_search_finish(c->finder);
  1431. }
  1432. }
  1433. void
  1434. clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h)
  1435. {
  1436. navigate(c, a);
  1437. }
  1438. void
  1439. clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h)
  1440. {
  1441. Arg arg;
  1442. arg.v = webkit_hit_test_result_get_link_uri(h);
  1443. newwindow(c, &arg, a->b);
  1444. }
  1445. void
  1446. clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h)
  1447. {
  1448. Arg arg;
  1449. arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
  1450. spawn(c, &arg);
  1451. }
  1452. int
  1453. main(int argc, char *argv[])
  1454. {
  1455. Arg arg;
  1456. Client *c;
  1457. memset(&arg, 0, sizeof(arg));
  1458. /* command line args */
  1459. ARGBEGIN {
  1460. case 'a':
  1461. defconfig CSETV(CookiePolicies, EARGF(usage()));
  1462. break;
  1463. case 'b':
  1464. defconfig CSETB(ScrollBars, 0);
  1465. break;
  1466. case 'B':
  1467. defconfig CSETB(ScrollBars, 1);
  1468. break;
  1469. case 'c':
  1470. cookiefile = EARGF(usage());
  1471. break;
  1472. case 'C':
  1473. stylefile = EARGF(usage());
  1474. break;
  1475. case 'd':
  1476. defconfig CSETB(DiskCache, 0);
  1477. break;
  1478. case 'D':
  1479. defconfig CSETB(DiskCache, 1);
  1480. break;
  1481. case 'e':
  1482. embed = strtol(EARGF(usage()), NULL, 0);
  1483. break;
  1484. case 'f':
  1485. defconfig CSETB(RunInFullscreen, 0);
  1486. break;
  1487. case 'F':
  1488. defconfig CSETB(RunInFullscreen, 1);
  1489. break;
  1490. case 'g':
  1491. defconfig CSETB(Geolocation, 0);
  1492. break;
  1493. case 'G':
  1494. defconfig CSETB(Geolocation, 1);
  1495. break;
  1496. case 'i':
  1497. defconfig CSETB(LoadImages, 0);
  1498. break;
  1499. case 'I':
  1500. defconfig CSETB(LoadImages, 1);
  1501. break;
  1502. case 'k':
  1503. defconfig CSETB(KioskMode, 0);
  1504. break;
  1505. case 'K':
  1506. defconfig CSETB(KioskMode, 1);
  1507. break;
  1508. case 'm':
  1509. defconfig CSETB(Style, 0);
  1510. break;
  1511. case 'M':
  1512. defconfig CSETB(Style, 1);
  1513. break;
  1514. case 'n':
  1515. defconfig CSETB(Inspector, 0);
  1516. break;
  1517. case 'N':
  1518. defconfig CSETB(Inspector, 1);
  1519. break;
  1520. case 'p':
  1521. defconfig CSETB(Plugins, 0);
  1522. break;
  1523. case 'P':
  1524. defconfig CSETB(Plugins, 1);
  1525. break;
  1526. case 'r':
  1527. scriptfile = EARGF(usage());
  1528. break;
  1529. case 's':
  1530. defconfig CSETB(JavaScript, 0);
  1531. break;
  1532. case 'S':
  1533. defconfig CSETB(JavaScript, 1);
  1534. break;
  1535. case 'u':
  1536. fulluseragent = EARGF(usage());
  1537. break;
  1538. case 'v':
  1539. die("surf-"VERSION", ©2009-2017 surf engineers, "
  1540. "see LICENSE for details\n");
  1541. case 'x':
  1542. showxid = 1;
  1543. break;
  1544. case 'z':
  1545. defconfig CSETF(ZoomLevel, strtof(EARGF(usage()), NULL));
  1546. break;
  1547. default:
  1548. usage();
  1549. } ARGEND;
  1550. if (argc > 0)
  1551. arg.v = argv[0];
  1552. else
  1553. arg.v = "about:blank";
  1554. setup();
  1555. c = newclient(NULL);
  1556. showview(NULL, c);
  1557. loaduri(c, &arg);
  1558. updatetitle(c);
  1559. gtk_main();
  1560. cleanup();
  1561. return 0;
  1562. }