surf.c 43 KB

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