surf.c 43 KB

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