diff --git a/.gitignore b/.gitignore index 00a41de6..711ddb0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,5 @@ *.[oad] *.lo +*.so *.tar.gz -src/hints.js.h -src/config.h -src/vimb -src/libvimb.so -tests/* -!tests/Makefile -!tests/*.c -!tests/manual/ +sandbox diff --git a/CHANGELOG.md b/CHANGELOG.md index d4fb3d42..50d64663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changes in vimb +## [unreleased] + +### Changed + +* completely rebuild of vimb on webkit2 api. +* Syntax for the font related gui settings has be changed. + Fonts have to be given as `[ font-style | font-variant | font-weight | font-stretch ]? font-size font-family` + Example `set input-font-normal=bold 10pt "DejaVu Sans Mono"` instead of + previous `set input-fg-normal=DejaVu Sans Mono Bold 10` +* Renames some settings to consequently use dashed setting names. Following + settings where changed. + ``` + previous setting - new setting name + -------------------------------------- + cursivfont - cursiv-font + defaultfont - default-font + fontsize - font-size + hintkeys - hint-keys + minimumfontsize - minimum-font-size + monofont - monospace-font + monofontsize - monospace-font-size + offlinecache - offline-cache + useragent - user-agent + sansfont - sans-serif-font + scrollstep - scroll-step + seriffont - serif-font + statusbar - status-bar + userscripts - user-scripts + xssauditor - xss-auditor + ``` + +### Removed + +* There where many features removed during the webkit2 migration. + TODO list removed features and if they will be added again + +--- + ## [2.12] - 2017-04-11 ### Added @@ -61,5 +99,6 @@ cookie file * Fixed none POSIX `echo -n` call +[unreleased]: https://github.com/fanglingsu/vimb/compare/2.12...webkit2 [2.12]: https://github.com/fanglingsu/vimb/compare/2.11...2.12 [2.11]: https://github.com/fanglingsu/vimb/compare/2.10...2.11 diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 00000000..e88449e1 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,79 @@ +# Contribute + +This document contains guidelines for contributing to vimb, as well as useful +hints when doing so. + +## Communication + +If you wan't to discuss some feature or provide some suggestion that can't be +done very well with the github issues. You should use the [mailing list][mail] +for this purpose. Else it's a good decision to use the features provided by +github for that. + +## Patching and Coding style + +### File Layout + +- Comment with LICENSE and possibly short explanation of file/tool +- Headers +- Macros +- Types +- Function declarations + - Include variable names + - For short files these can be left out + - Group/order in logical manner +- Global variables +- Function definitions in same order as declarations +- main + +### C Features + +- Do not mix declarations and code +- Do not use for loop initial declarations +- Use `/* */` for comments, not `//` + +### Headers + +- Place system/libc headers first in alphabetical order + - If headers must be included in a specific order comment to explain +- Place local headers after an empty line + +### Variables + +- Global variables not used outside translation unit should be declared static +- In declaration of pointers the `*` is adjacent to variable name, not type + +### Indentation + +- the code is indented by 4 spaces - if you use vim to code you can set + `:set expandtab ts=4 sts=4 sw=4` +- it's a good advice to orientate on the already available code +- if you are using `indent`, following options describe best the code style + - `--k-and-r-style` + - `--case-indentation4` + - `--dont-break-function-decl-args` + - `--dont-break-procedure-type` + - `--dont-line-up-parentheses` + - `--no-tabs` + +## directories + + ├── doc documentation like manual page + └── src all sources to build vimb + ├── scripts JavaScripts that are compiled in for various purposes + └── webextension Source files for the webextension + +## compile and run + +To inform vimb during compile time where the webextension should be loaded +from, the `RUNPREFIX` option can be set to a full qualified path to the +directory where the extension should be stored in. + +To run vimb without installation you could run as a sandbox like this + + make runsandbox + +This will compile and install vimb into the local _sandbox_ folder in the +project directory. + +[mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list" diff --git a/Makefile b/Makefile index c019d57b..e56e528d 100644 --- a/Makefile +++ b/Makefile @@ -1,52 +1,46 @@ include config.mk -SRCDIR=src -DOCDIR=doc +all: $(SRCDIR).subdir-all -all: $(TARGET) options: - @echo "$(PROJECT) build options:" - @echo "LIBS = $(LIBS)" - @echo "CFLAGS = $(CFLAGS)" - @echo "LDFLAGS = $(LDFLAGS)" - @echo "CC = $(CC)" - -test: $(LIBTARGET) - @$(MAKE) $(MFLAGS) -s -C tests - -clean: - @$(MAKE) $(MFLAGS) -C src clean - @$(MAKE) $(MFLAGS) -C tests clean - -install: $(TARGET) $(DOCDIR)/$(MAN1) - install -d $(DESTDIR)$(BINDIR) - install -m 755 $(SRCDIR)/$(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) - install -d $(DESTDIR)$(DATADIR)/applications - cp $(PROJECT).desktop $(DESTDIR)$(DATADIR)/applications - install -d $(DESTDIR)$(EXAMPLEDIR) - cp -r examples/* $(DESTDIR)$(EXAMPLEDIR) - install -d $(DESTDIR)$(MANDIR)/man1 + @echo "vimb build options:" + @echo "LIBS = $(LIBS)" + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "EXTCFLAGS = $(EXTCFLAGS)" + @echo "CC = $(CC)" + +install: $(SRCDIR).subdir-all + @# binary + install -d $(BINPREFIX) + install -m 755 $(SRCDIR)/vimb $(BINPREFIX)/vimb + @# extension + install -d $(LIBDIR) + install -m 644 $(SRCDIR)/webextension/$(EXTTARGET) $(LIBDIR)/$(EXTTARGET) + @# man page + install -d $(MANPREFIX)/man1 @sed -e "s!VERSION!$(VERSION)!g" \ -e "s!PREFIX!$(PREFIX)!g" \ - -e "s!DATE!`date +'%m %Y'`!g" $(DOCDIR)/$(MAN1) > $(DESTDIR)$(MANDIR)/man1/$(MAN1) + -e "s!DATE!`date +'%m %Y'`!g" $(DOCDIR)/vimb.1 > $(MANPREFIX)/man1/vimb.1 + @# .desktop file + install -d $(DOTDESKTOPPREFIX) + install -m 644 vimb.desktop $(DOTDESKTOPPREFIX)/vimb.desktop uninstall: - $(RM) $(DESTDIR)$(BINDIR)/$(TARGET) - $(RM) $(DESTDIR)$(DATADIR)/applications/$(PROJECT).desktop - $(RM) $(DESTDIR)$(MANDIR)/man1/$(MAN1) - $(RM) -r $(DESTDIR)$(EXAMPLEDIR) + $(RM) $(BINPREFIX)/vimb $(DESTDIR)$(MANDIR)/man1/vimb.1 $(LIBDIR)/$(EXTTARGET) -dist: dist-clean - @echo "Creating tarball." - @git archive --format tar -o $(DIST_FILE) HEAD +clean: $(SRCDIR).subdir-clean -dist-clean: - $(RM) $(DIST_FILE) +sandbox: + $(Q)$(MAKE) RUNPREFIX=$(CURDIR)/sandbox/usr PREFIX=/usr DESTDIR=./sandbox install -$(TARGET): - @$(MAKE) $(MFLAGS) -C src $(TARGET) +runsandbox: sandbox + sandbox/usr/bin/vimb -$(LIBTARGET): - @$(MAKE) $(MFLAGS) -C src $(LIBTARGET) +%.subdir-all: + $(Q)$(MAKE) -C $* -.PHONY: clean all install uninstall options dist dist-clean test +%.subdir-clean: + $(Q)$(MAKE) -C $* clean + +.PHONY: all options install uninstall clean sandbox runsandbox diff --git a/README.md b/README.md index 24818764..32a87446 100644 --- a/README.md +++ b/README.md @@ -11,37 +11,25 @@ the project page of [Vimb][]. ## Features - it's modal like Vim -- Vim like [keybindings][] - assignable for each browser mode -- nearly every configuration can be changed at runtime with Vim like [set syntax][set] +- Vim like keybindings - assignable for each browser mode +- nearly every configuration can be changed at runtime with Vim like set syntax - history for `ex` commands, search queries, URLs - completions for: commands, URLs, bookmarked URLs, variable names of settings, search-queries -- [hinting][hints] - marks links, form fields and other clickable elements to +- hinting - marks links, form fields and other clickable elements to be clicked, opened or inspected - SSL validation against ca-certificate file -- HTTP Strict Transport Security (HSTS) -- open input or textarea with configurable external editor - user defined URL-shortcuts with placeholders -- custom [protocol handlers][handlers] -- read it later [queue][] to collect URIs for later use +- read it later queue to collect URIs for later use - multiple yank/paste registers -- Vim like [autocmd][] ## Packages -- Arch Linux [vimb-git][arch-git], [vimb][arch] -- [OpenBSD][] -- [NetBSD][] -- [FreeBSD][] -- [Void Linux][] +- Arch Linux [arch-git][] +- Gentoo [gentoo-git][], [gentoo][] -## Dependencies +## dependencies -- libwebkit >=1.5.0 -- libgtk+-2.0 -- libsoup >=2.38 - -On Ubuntu these dependencies can be installed by -`sudo apt-get install libsoup2.4-dev libwebkit-dev libgtk-3-dev libwebkitgtk-3.0-dev`. +- webkit2gtk-4.0 >= 2.16.x ## Install @@ -58,31 +46,24 @@ Therefore, you should always compare your customised `config.h` with Run the following commands to compile and install Vimb (if necessary, the last one as root). - make clean - make // or make GTK=3 to compile against gtk3 + make make install -To build Vimb against GTK3 you can use `make GTK=3`. - -# License +To run vimb without installation for testing it out use the 'runsandbox' make +target. -Information about the license is found in the file: LICENSE. + make runsandbox -# Mailing list +## Mailing list - feature requests, issues and patches can be discussed on the [mailing list][mail] -[vimb]: http://fanglingsu.github.io/vimb/ "Vimb - Vim like browser project page" -[keybindings]: https://fanglingsu.github.io/vimb/man.html#NORMAL_MODE "vimb keybindings" -[hints]: https://fanglingsu.github.io/vimb/man.html#Hinting "vimb hinting" -[queue]: http://fanglingsu.github.io/vimb/commands.html#queue "vimb read it later queue feature" -[handlers]: http://fanglingsu.github.io/vimb/commands.html#handlers "vimb custom protocol handlers" +## license + +Information about the license are found in the file LICENSE. + +[arch-git]: https://github.com/fanglingsu/dotfiles/tree/master/build/vimb-git +[gentoo-git]: https://github.com/tharvik/overlay/tree/master/www-client/vimb +[gentoo]: https://github.com/hsoft/portage-overlay/tree/master/www-client/vimb +[vimb]: https://fanglingsu.github.io/vimb/ "Vimb - Vim like browser project page" [mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list" -[OpenBSD]: http://openports.se/www/vimb "vimb - OpenBSD port" -[NetBSD]: http://pkgsrc.se/www/vimb "vimb - NetBSD package" -[autocmd]: http://fanglingsu.github.io/vimb/commands.html#autocmd "Vim like autocmd and augroup feature" -[set]: http://fanglingsu.github.io/vimb/commands.html#settings "Vim like set syntax" -[Arch-git]: https://aur.archlinux.org/packages/vimb-git/ "vimb - archlinux package" -[Arch]: https://aur.archlinux.org/packages/vimb/ "vimb - archlinux package" -[FreeBSD]: http://www.freshports.org/www/vimb/ "vimb - FreeBSD port" -[Void Linux]: https://github.com/voidlinux/void-packages/blob/master/srcpkgs/vimb/template "vimb - Void Linux package" diff --git a/config.mk b/config.mk index 007180fe..bf78a8b6 100644 --- a/config.mk +++ b/config.mk @@ -1,64 +1,43 @@ -#----------------user/install options---------------- -VERSION = 2.12 +VERSION = dev-3.0 -PROJECT = vimb -PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin -DATADIR ?= $(PREFIX)/share -MANDIR ?= $(DATADIR)/man -EXAMPLEDIR ?= $(DATADIR)/$(PROJECT)/examples - -#----------------compile options--------------------- - -VERBOSE ?= 0 - -LIBS = libsoup-2.4 - -GTK3LIBS=gtk+-3.0 webkitgtk-3.0 -GTK2LIBS=gtk+-2.0 webkit-1.0 - -ifeq (${GTK}, 3) -ifeq ($(shell pkg-config --exists $(GTK3LIBS) && echo 1), 1) #has gtk3 libs -LIBS += $(GTK3LIBS) -USEGTK3 = 1 -else -LIBS += $(GTK2LIBS) -$(warning Cannot find gtk3-libs, falling back to gtk2) +ifneq ($(V),1) +Q := @ endif -else -LIBS += $(GTK2LIBS) -endif - -# generate a first char upper case project name -PROJECT_UCFIRST = $(shell echo '${PROJECT}' | awk '{for(i=1;i<=NF;i++){$$i=toupper(substr($$i,1,1))substr($$i,2)}}1') -CPPFLAGS = -DVERSION=\"${VERSION}\" -CPPFLAGS += -DPROJECT=\"${PROJECT}\" -DPROJECT_UCFIRST=\"${PROJECT_UCFIRST}\" +PREFIX ?= /usr/local +BINPREFIX := $(DESTDIR)$(PREFIX)/bin +MANPREFIX := $(DESTDIR)$(PREFIX)/share/man +EXAMPLEPREFIX := $(DESTDIR)$(PREFIX)/share/vimb/example +DOTDESKTOPPREFIX := $(DESTDIR)$(PREFIX)/share/applications +LIBDIR := $(DESTDIR)$(PREFIX)/lib/vimb +RUNPREFIX := $(PREFIX) +EXTENSIONDIR := $(RUNPREFIX)/lib/vimb + +# define some directories +SRCDIR = src +DOCDIR = doc + +# used libs +LIBS = gtk+-3.0 'webkit2gtk-4.0 >= 2.3.5' + +# setup general used CFLAGS +CFLAGS += -std=c99 -pipe -Wall +#CPPFLAGS += -pedantic +CPPFLAGS += -DVERSION=\"${VERSION}\" -DEXTENSIONDIR=\"${EXTENSIONDIR}\" +CPPFLAGS += -DPROJECT=\"vimb\" -DPROJECT_UCFIRST=\"Vimb\" CPPFLAGS += -D_XOPEN_SOURCE=500 -CPPFLAGS += -D_POSIX_SOURCE -ifeq ($(USEGTK3), 1) -CPPFLAGS += -DHAS_GTK3 +CPPFLAGS += -D__BSD_VISIBLE CPPFLAGS += -DGSEAL_ENABLE CPPFLAGS += -DGTK_DISABLE_SINGLE_INCLUDES -CPPFLAGS += -DGTK_DISABLE_DEPRECATED CPPFLAGS += -DGDK_DISABLE_DEPRECATED -endif - -# prepare the lib flags used for the linker -LIBFLAGS = $(shell pkg-config --libs $(LIBS)) - -# some compiler flags in case CFLAGS is not set by user -# -Wno-typedef-redefinition to avoid redifinition warnings caused by glib -CFLAGS ?= -Wall -pipe -Wno-overlength-strings -Werror=format-security -Wno-typedef-redefinition -# normal compiler flags -CFLAGS += $(shell pkg-config --cflags $(LIBS)) -CFLAGS += -std=c99 -CFLAGS += ${CPPFLAGS} -LDFLAGS += ${LIBFLAGS} -TARGET = $(PROJECT) -LIBTARGET = lib$(PROJECT).so -DIST_FILE = $(PROJECT)_$(VERSION).tar.gz -MAN1 = $(PROJECT).1 +# flags used to build webextension +EXTTARGET = webext_main.so +EXTCFLAGS = ${CFLAGS} -fPIC $(shell pkg-config --cflags webkit2gtk-4.0) +EXTCFLAGS += $(CPPFLAGS) +EXTLDFLAGS = $(shell pkg-config --libs webkit2gtk-4.0) -shared -MFLAGS ?= --no-print-directory +# flags used for the main application +CFLAGS += $(shell pkg-config --cflags $(LIBS)) +CFLAGS += ${CPPFLAGS} +LDFLAGS += $(shell pkg-config --libs $(LIBS)) diff --git a/doc/vimb.1 b/doc/vimb.1 index 35aebf35..ef1b81cf 100644 --- a/doc/vimb.1 +++ b/doc/vimb.1 @@ -1,4 +1,5 @@ .\" vim: ft=groff +.ss 12 0 .ad l .TH VIMB 1 "DATE" "vimb/VERSION" "Vimb Manual" .de EX @@ -28,48 +29,21 @@ If \fIURI\fP is '-', Vimb reads the HTML to display from stdin. .PP Mandatory arguments to long options are mandatory for short options too. .TP -.BI "\-C, \-\-cmd " "CMD" -Run \fICMD\fP as ex command line right before the first page is loaded. -If the flag is used more than one time, the ordering is preserved when -\fICMD\fP are executed. -You could also pass several ex commands in one \fICMD\fP, -if they are separated by "|". -.sp -Example: -.EX -vimb --cmd "set cookie-accept=origin|set header=Referer,DNT=1" -.EE -.TP -.BI "\-c, \-\-config " "CONFIG-FILE" -Use custom configuration given as \fICONFIG-FILE\fP. +.BI "\-c, \-\-config " "FILE" +Use custom configuration given as \fIFILE\fP. This will also be applied on new spawned instances. .TP -.BI "\-p, \-\-profile " "PROFILE-NAME" -Create or open specified configuration profile. Configuration data for the profile is stored in a directory named \fIPROFILE-NAME\fP under default directory for configuration data. -.TP .BI "\-e, \-\-embed " "WINID" .I WINID of an XEmbed-aware application, that Vimb will use as its parent. .TP -.B \-d, \-\-dump -Dump the current used socket path to stdout in case Vimb is started with \-s -option. -.sp -Example: -.EX -sh -c "./vimb -s -d > ~/vimb.socket" & -echo ":o github.com" | socat - unix-connect:$(< ~/vimb.socket) -.EE -.TP .B "\-h, \-\-help" Show help options. .TP -.B \-k, \-\-kiosk -Run Vimb in kiosk mode with nearly no keybindings, not inputbox and no context -menu. -.TP -.B \-s, \-\-socket -If given Vimb will create a control socket in the user runtime directory. +.BI "\-p, \-\-profile " "PROFILE-NAME" +Create or open specified configuration profile. +Configuration data for the profile is stored in a directory named +\fIPROFILE-NAME\fP under default directory for configuration data. .TP .B "\-v, \-\-version" Print build and version information. @@ -107,9 +81,6 @@ Mode. .B CTRL\-Z Switch Vimb into Pass-Through Mode. .TP -.B gf -Toggle show HTML source of the current page. -.TP .B gF Open the Web Inspector for the current page. .TP @@ -145,8 +116,8 @@ Open the last closed page. Open the last closed page in a new window. .TP .B CTRL\-P -Open the oldest entry from the Read It Later queue in the current browser window (only if -Vimb has been compiled with QUEUE feature). +Open the oldest entry from the read it later queue in the current browser +window. .TP .BI [ \(dqx ]p Open the URI out of the register \fIx\fP or, if not given, from the clipboard. @@ -167,14 +138,6 @@ Go to the \fIN\fPth descendent directory of the current opened URI. .B gU Go to the domain of the current opened page. .TP -.BI [ N ]CTRL\-A -Increments the last number in URL by 1, or by \fIN\fP if given. -.TP -.BI [ N ]CTRL\-X -Decrements the last number in URL by 1, or by \fIN\fP if given. -Negative numbers are not supported as trailing numbers in URLs -are often preceded by hyphens. -.TP .B r Reload the website. .TP @@ -223,29 +186,11 @@ Scroll page \fIN\fP steps down. .TP .BI [ N ]k Scroll page \fIN\fP steps up. -.TP -.BI [ N ]]] -Follow the last \fIN\fPth link matching `nextpattern'. -.TP -.BI [ N ][[ -Follow the last \fIN\fPth link matching `previouspattern'. -.TP -.BI m{ a-z } -Set a page mark {\fIa-z\fP} at the current position on the page. -Such set marks are only available on the current page; -if the page is left, all marks will be removed. -.TP -.BI '{ a-z } -Jump to the mark {\fIa-z\fP} on the current page. -.TP -.B '' -Jumps to the position before the latest jump, or where the last "m'" command -was given. .SS Hinting Hinting in Vimb is how you accomplish the tasks that you would do with the mouse in common mouse-driven browsers: open a URI, yank a URI, save a page and so on. When hinting is started, the relevant elements on the page will -be marked by labels generated from configured `hintkeys'. +be marked by labels generated from configured `hint-keys'. Hints can be selected by using , or , , by typing the chars of the label, or filtering the elements by some text that is part of the hinted element (like URI, link text, button label) @@ -307,9 +252,9 @@ Generate an `:open' prompt with hint's URI. Generate an `:tabopen' prompt with hint's URI. .TP .B ;e -Open the configured $EDITOR (`editor-command') with the hinted form element's +Open the configured editor (`editor-command') with the hinted form element's content. -If the file in $EDITOR is saved and the $EDITOR is closed, the file +If the file in editor is saved and the editor is closed, the file content will be put back in the form field. .TP .B ;i @@ -370,10 +315,10 @@ direction. .TP .BI [ N ]N Search for \fIN\fPnth previous search result depending on current search -direction. .TP .B Perform a click on element containing the current highlighted search result. +direction. .SS Zooming .TP .BI [ N ]zi @@ -396,7 +341,6 @@ Reset Zoom. Yank the URI or current page into register \fIx\fP and clipboard. .TP .BI [ \(dqx ]Y -Yank the current selection into register \fIx\fP and clipboard. .SH COMMAND MODE Commands that are listed below are ex-commands like in Vim, that are typed into the inputbox (the command line of vimb). @@ -411,8 +355,8 @@ command. At the moment the count parts [N] of commands is parsed, but currently there is no command that uses the count. .sp -Commands that are typed interactivly (from the inputbox or from socket) are -normally recorded into command history and register. +Commands that are typed interactivly are normally recorded into command +history and register. To avoid this, the commands can be prefixed by one or more additional `:' or whitespace. .PP @@ -427,8 +371,6 @@ cannot be followed by another command. .PP .PD 0 .IP - 2 -autocmd -.IP - cmap, cnoremap, imap, inoremap, nmap, nnoremap .IP - eval @@ -460,7 +402,7 @@ Remove everything between cursor and prompt. Moves the cursor directly behind the prompt `:'. .TP .B CTRL\-E -Moves the cursor after the char in inputbox. +Moves the cursor after the prompt in inputbox. .TP .B CTRL\-V Pass the next key press directly to GTK. @@ -481,6 +423,7 @@ Step backward in the command history. .TP .B Step forward in the command history. +Yank the current selection into register \fIx\fP and clipboard. .SS Open .TP .BI ":o[pen] [" URI ] @@ -545,8 +488,8 @@ that could be used as ":save ^Gh". .IP ":nmap :set scripts=on:open !glib" This will enable scripts and lookup the first bookmarked URI with the tag `glib' and open it immediately if F1 key is pressed. -.IP ":nmap \\\\\ \\\\\ 50G;o" -Example which maps two spaces to go to 50% of the page, and start hinting mode. +.IP ":nmap \\\\\ \\\\\ 50G" +Example which maps two spaces to go to 50% of the page. .PD .RE .TP @@ -585,12 +528,12 @@ full URI given when the command is called. .P Examples: .PD 0 +.IP ":handler-add mailto=urxvt -e mutt %s" +to start email client for mailto links. .IP ":handler-add magnet=xdg-open %s" to open magnet links with xdg-open. -.IP ":handler-add magnet=transmission-gtk %s" -to open magnet links directly with Transmission. -.IP ":handler-add irc=irc-handler.sh %s" -to direct irc://:/ links to a wrapper for your IRC client. +.IP ":handler-add ftp=urxvt -e wget %s -P ~/ftp-downloads" +to handle ftp downloads via wget. .PD .RE .TP @@ -635,9 +578,10 @@ contain spaces like `:open map "city hall, London" railway station, London' Remove the search engine to the given \fIshortcut\fP. .TP .BI ":shortcut-default " "shortcut" -Set the shortcut for given \fIshortcut\fP as the default. -It doesn't matter if the \fIshortcut\fP is already in use or not -to be able to set it. +Set the shortcut for given \fIshortcut\fP as the default, that is the shortcut +to be used if no shortcut is given and the string to open is not an URI. It +doesn't matter if the \fIshortcut\fP is already in use or not to be able to set +it. .SS Settings .TP .BI ":se[t] " var = value @@ -670,10 +614,8 @@ Show the current set value of variable. .BI ":se[t] " var ! Toggle the value of boolean variable \fIvar\fP and display the new set value. .SS Queue -The queue allows the marking of URIs for later reading (something like a Read It Later -list). +The queue allows the marking of URIs for later reading. This list is shared between the single instances of Vimb. -Only available if Vimb has been compiled with the QUEUE feature. .TP .BI ":qpu[sh] [" URI ] Push \fIURI\fP or, if not given, the current URI to the end of the queue. @@ -687,137 +629,12 @@ queue. .TP .B :qc[lear] Removes all entries from queue. -.SS Automatic commands -An autocommand is a command that is executed automatically in response to some -event, such as a URI being opened. -Autocommands are very powerful. -Use them with care and they will help you avoid typing many commands. -.PP -Autocommands are built with following properties. -.TP -.I group -When the [\fIgroup\fP] argument is not given, Vimb uses the current group as -defined with ':augroup', otherwise, Vimb uses the group defined with -[\fIgroup\fP]. -Groups are useful to remove multiple grouped autocommands. -.TP -.I event -You can specify a comma separated list of event names. -No white space can be used in this list. -.RS -.PP -.PD 0 -Events: -.TP -.B LoadProvisional -Fired if a new page is going to be opened. -No data has been received yet, the load may still fail for transport issues. -.TP -.B LoadCommited -Fired if first data chunk has arrived, meaning that the necessary transport -requirements are established, and the load is being performed. -This is the right event to toggle content related setting -like 'scripts', 'plugins' and such things. -.TP -.B LoadFirstLayout -fired if the first layout with actual visible content is shown. -.TP -.B LoadFinished -Fires when everything that was required to display on the page has been loaded. -.TP -.B LoadFailed -Fired when some error occurred during the page load that prevented it from -being completed. -.TP -.B DownloadStart -Fired right before a download is started. -This is fired for Vimb downloads as well as external downloads -if 'download-use-external' is enabled. -.TP -.B DownloadFinished -Fired if a Vimb managed download is finished. -For external download this event is not available. -.TP -.B DownloadFailed -Fired if a Vimb managed download failed. -For external download this event is not available. -.PD -.RE -.TP -.I pat -Comma separated list of patterns, matches in order to check if a autocommand -applies to the URI associated to an event. -To use ',' within the single patterns this must be escaped as '\e,'. -.RS -.PP -.PD 0 -Patterns: -.IP "\fB*\fP" -Matches any sequence of characters. -This includes also '/' in contrast to shell patterns. -.IP "\fB?\fP" -Matches any single character except of '/'. -.IP "\fB{one,two}\fP" -Matches 'one' or 'two'. -Any '{', ',' and '}' within this pattern must be escaped by a '\\'. -\&'*' and '?' have no special meaning within the curly braces. -.IP "\fB\e\fP" -Use backslash to escape the special meaning of '?*{},' in the pattern or -pattern list. -.PD -.RE -.TP -.I cmd -Any `ex` command vimb understands. -The leading ':' is not required. -Multiple commands can be separated by '|'. -.TP -.BI ":au[tocmd] [" group "] {" event "} {" pat "} {" cmd "}" -Add \fIcmd\fP to the list of commands that Vimb will execute automatically on -\fIevent\fP for a URI matching \fIpat\fP autocmd-patterns. -Vimb always adds the \fIcmd\fP after existing autocommands, so that the -autocommands are executed in the order in which they were given. -.TP -.BI ":au[tocmd]! [" group "] {" event "} {" pat "} {" cmd "}" -Remove all autocommands associated with \fIevent\fP and which pattern match -\fIpat\fP, and add the command \fIcmd\fP. -Note that the pattern is not matches literally to find autocommands -to remove, like Vim does. -Vimb matches the autocommand pattern with \fIpat\fP. -If [\fIgroup\fP] is not given, deletes autocommands in current group, -as noted above. -.TP -.BI ":au[tocmd]! [" group "] {" event "} {" pat "}" -Remove all autocommands associated with \fIevent\fP and which pattern matches -\fIpat\fP in given group (current group by default). -.TP -.BI ":au[tocmd]! [" group "] * {" pat "}" -Remove all autocommands with patterns matching \fIpat\fP for all events -in given group (current group by default). -.TP -.BI ":au[tocmd]! [" group "] {" event "}" -Remove all autocommands for \fIevent\fP in given group (current group -by default). -.TP -.BI ":au[tocmd]! [" group "]" -Remove all autocommands in given group (current group by default). -.TP -.BI ":aug[roup] {" name "}" -Define the autocmd group \fIname\fP for the following ":autocmd" commands. -The name "end" selects the default group. -.TP -.BI ":aug[roup]! {" name "}" -Delete the autocmd group \fIname\fP. -.PP -Example: -.EX -:aug github -: au LoadCommited * set scripts=off|set cookie-accept=never -: au LoadCommited http{s,}://github.com/* set scripts=on -:aug end -.EE .SS Misc .TP +.B :cl[earcache] +Clears all resources currently cached by webkit. +Note that this effects all running instances of vimb. +.TP .BI ":sh[ellcmd] " cmd Runs the given shell \fIcmd\fP syncron and print the output into inputbox. The following patterns in \fIcmd\fP are expanded: '~username', '~/', '$VAR' @@ -839,18 +656,9 @@ Contains the title of the current opened page. .B VIMB_PID Contains the pid of the running Vimb instance. .TP -.B VIMB_SOCKET -Holds the full path to the control socket, if Vimb is compiled with SOCKET -feature and started with `--socket' option. -.TP .B VIMB_XID Holds the X-Window id of the Vimb window or of the embedding window if Vimb is -compiled with XEMBED and started with the -e option. -.PD -.PP -Example: -.EX -:sh ls -l $HOME +started with the -e option. .EE .RE .TP @@ -903,7 +711,6 @@ Last search phrase. .TP .B \(dq; Contains the last hinted URL. -This can be used in `x-hint-command' to get the URL of the hint. .PD .RE .TP @@ -941,10 +748,10 @@ Switch back to normal mode. Executes the next command as normal mode command and return to input mode. .TP .B CTRL\-T -Open configured $EDITOR with content of current form field. +Open configured editor with content of current form field. .TP .B CTRL\-V -Pass the next key press directly to GTK. +Pass the next key press directly to WebKit. .TP .B CTRL\-Z Enter the pass-through mode. @@ -1006,55 +813,65 @@ This completion starts by `/` or `?` in inputbox and performs a prefix comparison for further typed chars. .SH SETTINGS All settings listed below can be set with the `:set' command. -.SS WebKit-Settings -.TP -.B accelerated-compositing (bool) -Enable or disable support for accelerated compositing on pages. -Accelerated compositing uses the GPU to render animations on pages -smoothly and also allows proper rendering of 3D CSS transforms. -.TP -.B auto-load-images (bool) -Load images automatically. .TP -.B auto-resize-window (bool) -Indicates if Vimb will honor size and position changes of the window by various -DOM methods. -.TP -.B auto-shrink-images (bool) -Automatically shrink standalone images to fit. +.B accelerated-2d-canvas (bool) +Enable or disable accelerated 2D canvas. +When accelerated 2D canvas is enabled, WebKit may render some 2D canvas +content using hardware accelerated drawing operations. .TP .B caret (bool) Whether to enable accessibility enhanced keyboard navigation. .TP +.B cookie-accept (string) +Cookie accept policy {`always', `never', `origin' (accept all non-third-party +cookies)}. +.TP .B closed-max-items (int) -Maximum number of stored last closed browser windows. If closed-max-items is -set to 0, closed browser windows will not be stored. +Maximum number of stored last closed URLs. +If closed-max-items is set to 0, closed URLs will not be stored. +.TP +.B completion-css (string) +CSS style applied to the inputbox completion list items. +.TP +.B completion-hover-css (string) +CSS style applied to the inputbox completion list item that is currently +hovered by the mouse. .TP -.B cursivfont (string) +.B completion-selected-css (string) +CSS style applied to the inputbox completion list item that is currently +selected. +.TP +.B cursiv-font (string) The font family used as the default for content using cursive font. .TP -.B defaultencoding (string) +.B default-charset (string) The default text charset used when interpreting content with an unspecified charset. .TP -.B defaultfont (string) +.B default-font (string) The font family to use as the default for content that does not specify a font. .TP +.B default-zoom (int) +Default Full-Content zoom level in percent. Default is 100. +.TP .B dns-prefetching (bool) Indicates if Vimb prefetches domain names. .TP -.B dom-paste (bool) -Whether to enable DOM paste. -If set to TRUE, document.execCommand("Paste") -will correctly execute and paste content of the clipboard. +.B download-path (string) +Path to the default download directory. +If no download directory is set, download will be written into current +directory. +The following pattern will be expanded if the download is +started '~/', '~user', '$VAR' and '${VAR}'. .TP -.B file-access-from-file-uris (bool) -Boolean property to control file access for file:// URIs. -If this option is enabled every file:// will have its own security -unique domain. +.B editor-command (string) +Command with placeholder '%s' called if form field is opened with $EDITOR to +spawn the editor-like `x-terminal-emulator -e vim %s'. +To use Gvim as the editor, it's necessary to call it with `-f' to run it in +the foreground. .TP -.B fontsize (int) +.B font-size (int) The default font size used to display text. .TP .B frame-flattening (bool) @@ -1062,6 +879,82 @@ Whether to enable the Frame Flattening. With this setting each subframe is expanded to its contents, which will flatten all the frames to become one scrollable page. .TP +.B fullscreen (bool) +Show the current window full-screen. +.TP +.B hardware-acceleration-policy (string) +This setting decides how to enable and disable hardware acceleration. +.PD 0 +.RS +.IP - 2 +`ondemand' enables the hardware acceleration when the web contents request it, disabling it again when no +longer needed. +.IP - 2 +`always' enforce hardware acceleration to be enabled. +.IP - 2 +`never' disables it completely. +Note that disabling hardware acceleration might cause some websites to not +render correctly or consume more CPU. +.RE +.PD +.TP +.B header (list) +Comma separated list of headers that replaces default header sent by WebKit or +new headers. +The format for the header list elements is `name[=[value]]'. +.sp +Note that these headers will replace already existing headers. +If there is no '=' after the header name, then the complete header +will be removed from the request, if the '=' is present means that +the header value is set to empty value. +.sp +To use '=' within a header value the value must be quoted like shown in +Example for the Cookie header. +.RS +.PP +Example: +.PD 0 +.IP ":set header=DNT=1,User-Agent,Cookie='name=value'" +Send the 'Do Not Track' header with each request and remove the User-Agent +Header completely from request. +.PD +.RE +.TP +.B hint-follow-last (bool) +If on, vimb automatically follows the last remaining hint on the page. +If off hints are fired only if enter is pressed. +.TP +.B hint-number-same-length (bool) +If on, all hint numbers will have the same length, so no hints will be +ambiguous. +.TP +.B hint-timeout (int) +Timeout before automatically following a non-unique numerical hint. +To disable auto fire of hints, set this value to 0. +.TP +.B hint-keys (string) +The keys used to label and select hints. +With its default value, each hint has a unique number which can be typed +to select it, while all other characters are used to filter hints based +on their text. +With a value such as asdfg;lkjh, +each hint is `numbered' based on the characters of the home row. +Note that the hint matching by label built of hint-keys is case sensitive. +In this vimb differs from some other browsers that show hint labels in upper +case, but match them lowercase. +.RS +To have upper case hint labels, it's possible to add following css to the +`style.css' file in vimb's configuration directory. +.IP "._hintLabel {text-transform: uppercase !important;}" +.RE +.TP +.B history-max-items (int) +Maximum number of unique items stored in search-, command or URI history. +If history-max-items is set to 0, the history file will not be changed. +.TP +.B home-page (string) +Homepage that vimb opens if started without a URI. +.TP .B html5-database (bool) Whether to enable HTML5 client-side SQL database support. Client-side SQL database allows web pages to store structured data @@ -1077,27 +970,24 @@ Enable or disable support for . .B images (bool) Determines whether images should be automatically loaded or not. .TP -.B insecure-content-show (bool) -Whether pages loaded via HTTPS should load subresources such as images and -frames from non-HTTPS URIs. -Only for webkit>=2.0. +.B incsearch (bool) +While typing a search command, show where the pattern typed so far matches. .TP -.B insecure-content-run (bool) -Whether pages loaded via HTTPS should run subresources such as CSS, scripts, -and plugins from non-HTTPS URIs. -Only for webkit>=2.0. +.B input-autohide (bool) +If enabled the inputbox will be hidden whenever it contains no text. .TP -.B java-applet (bool) -Enable or disable support for the Java tag. -Keep in mind that Java content can be still shown in the page -through or , which are the preferred tags for this task. +.B input-css (string) +CSS style applied to the inputbox in normal state. +.TP +.B input-error-css (string) +CSS style applied to the inputbox in case of displayed error. .TP .B javascript-can-access-clipboard (bool) Whether JavaScript can access the clipboard. .TP .B javascript-can-open-windows-automatically (bool) Whether JavaScript can open popup windows automatically without user -intervention. +interaction. .TP .B media-playback-allows-inline (bool) Whether media playback is full-screen only or inline playback is allowed. @@ -1120,28 +1010,22 @@ Enable or disable support for MediaSource on pages. MediaSource is an experimental proposal which extends HTMLMediaElement to allow JavaScript to generate media streams for playback. .TP -.B minimumfontsize (int) +.B minimum-font-size (int) The minimum font size used to display text. .TP -.B monofont (string) +.B monospace-font (string) The font family used as the default for content using monospace font. .TP -.B monofontsize (int) +.B monospace-font-size (int) Default font size for the monospace font. .TP -.B offlinecache (bool) +.B offline-cache (bool) Whether to enable HTML5 offline web application cache support. Offline web application cache allows web applications to run even when the user is not connected to the network. .TP -.B pagecache (bool) -Enable or disable the page cache. -Disabling the page cache is generally only useful for special -circumstances like low-memory scenarios or special purpose -applications like static HTML viewers. -.TP .B print-backgrounds (bool) -Whether background images should be printed. +Whether background images should be drawn during printing. .TP .B private-browsing (bool) Whether to enable private browsing mode. @@ -1152,22 +1036,16 @@ not allow a page to store data in the windows sessionStorage. .B plugins (bool) Determines whether or not plugins on the page are enabled. .TP -.B print-backgrounds (bool) -Whether background images should be drawn during printing. -.TP -.B resizable-text-areas (bool) -Whether text areas are resizable. -.TP -.B respect-image-orientation (bool) -Whether Vimb should respect image orientation. -.TP -.B sansfont (string) +.B sans-serif-font (string) The font family used as the default for content using sans-serif font. .TP .B scripts (bool) Determines whether or not JavaScript executes within a page. .TP -.B seriffont (string) +.B scroll-step (int) +Number of pixel vimb scrolls if 'j' or 'k' is used. +.TP +.B serif-font (string) The font family used as the default for content using serif font. .TP .B site-specific-quirks (bool) @@ -1181,25 +1059,41 @@ Whether to enable the Spatial Navigation. This feature consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. -For example, if -a user presses the Right key, heuristics determine whether there is an -element they might be trying to reach towards the right, and if there are -multiple elements, which element they probably want. +For example, if a user presses the Right key, heuristics determine whether +there is an element they might be trying to reach towards the right, and if +there are multiple elements, which element they probably want. .TP .B spell-checking (bool) -Whether to enable spell checking while typing. +Enable or disable the spell checking feature. .TP .B spell-checking-languages (string) -The languages to be used for spell checking, separated by commas. -.sp +Set comma separated list of spell checking languages to be used for spell +checking. +.br The locale string typically is in the form lang_COUNTRY, where lang is an -ISO-639 language code, and COUNTRY is an ISO-3166 country code. -For instance, sv_FI for Swedish as written in Finland or pt_BR -for Portuguese as written in Brazil. -.sp -If no value is specified the default value for GTK is used. +ISO-639 language code, and COUNTRY is an ISO-3166 country code. For instance, +sv_FI for Swedish as written in Finland or pt_BR for Portuguese as written in +Brazil. +.TP +.B status-bar (bool) +Indicates if the status bar should be shown. +.TP +.B status-css (string) +CSS style applied to the status bar on none https pages. +.TP +.B status-ssl-css (string) +CSS style applied to the status bar on https pages with trusted certificate. +.TP +.B status-ssl-invalid-css (string) +CSS style applied to the status bar on https pages with untrusted certificate. .TP -.B tab-key-cycles-through-elements (bool) +.B strict-ssl (bool) +If 'on', vimb will not load a untrusted https site. +.TP +.B stylesheet (bool) +If 'on' the user defined styles-sheet is used. +.TP +.B tabs-to-links (bool) Whether the Tab key cycles through elements on the page. .sp If true, pressing the Tab key will focus the next element in the web view. @@ -1207,13 +1101,16 @@ Otherwise, the web view will interpret Tab key presses as normal key presses. If the selected element is editable, the Tab key will cause the insertion of a Tab character. .TP -.B universal-access-from-file-uris (bool) -Whether to allow files loaded through file:// URIs universal access to all -pages. +.B timeoutlen (int) +The time in milliseconds that is waited for a key code or mapped key sequence +to complete. .TP -.B useragent (string) +.B user-agent (string) The user-agent string used by WebKit. .TP +.B user-scripts (bool) +Indicates if user style sheet file $XDG_CONFIG_HOME/vimb/style.css is sourced. +.TP .B webaudio (bool) Enable or disable support for WebAudio on pages. WebAudio is an experimental proposal for allowing web pages @@ -1226,297 +1123,6 @@ Enable or disable support for WebGL on pages. Determines whether or not developer tools, such as the Web Inspector, are enabled. .TP -.B xssauditor (bool) -Whether to enable the XSS auditor. -This feature filters some kinds of reflective XSS attacks -on vulnerable web sites. -.SS Vimb-Settings -.TP -.B auto-response-header (list) -Prepend HTTP-Header to responses received from server, based on pattern -matching. -The purpose of this setting is to enforce some security setting in the client. -For example, you could set Content-Security-Policy (see -`http://www.w3.org/TR/CSP/') for implement a whitelist policy, or set -Strict-Transport-Security for server that don't provide this header whereas -they propose https website. -.sp -Note that this setting will not replace existing headers, but add a new one. -If multiple patterns match a requested URI, the last matched rule will be -applied. -You could also specified differents headers for the same pattern. -.sp -The format is a list of `pattern header-list`. -If `header-list` has not than one element, enclosing with QUOTE -is mandatory: `"pattern header-list"`. -The header-list format is the same as `header` setting. -.RS -.PP -Example: -.PD 0 -.IP ":set auto-response-header=* Content-security-policy=default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'none'" -.IP ":set auto-response-header+=https://example.com/* Content-security-policy=default-src 'self' https://*.example.com/" -.IP ":set auto-response-header+=https://example.com/* Strict-Transport-Security=max-age=31536000" -.IP ":set auto-response-header+=""https://*.example.org/sub/* Content-security-policy,X-Test=ok""" -.PD -.RE -.TP -.B ca-bundle (string) -The path to the .crt file for the certificate validation. -The given path is expanded with standard file expansion. -.TP -.B completion-bg-active (color) -Background color for selected completion item. -.TP -.B completion-bg-normal (color) -Background color for non-selected completion items. -.TP -.B completion-fg-active (color) -Foreground color for the selected completion item. -.TP -.B completion-fg-normal (color) -Foreground color for the non-selected completion items. -.TP -.B completion-font (string) -Font used for the completion items. -.TP -.B cookie-accept (string) -Cookie accept policy {`always', `never', `origin' (accept all non-third-party -cookies)}. -.TP -.B cookie-timeout (int) -Cookie timeout in seconds. -.TP -.B cookie-expire-time (int) -Enforce expire-time on cookies. -The default value `-1' keep expire-time as defined by server side. -The value `0' convert all cookies as session-only cookies -(`cookie-timeout' setting is used as for any other session-cookie). -Any other value enforce the expire-time (the expire-time value will be the -lower between server-side request and time defined with `cookie-expire-time'). -.TP -.B default-zoom (int) -Initial Full-Content Zoom in percent. Default is 100. -.TP -.B download-command (string) -A command with placeholder '%s' that will be invoked to download a URI. -.RS -.TP -The following additional environment variable are available: -.PD 0 -.TP -.B $VIMB_URI -The URI of the current opened page, normally the page where the download was -started from, also known as referer. -.TP -.B $VIMB_FILE -The target file that is calculated by Vimb according to the `download-path'. -Note that this file might already exists, so it's strongly recommended to -check the path in this variable before usage. -.TP -.B $VIMB_COOKIES -Path to the cookie file Vimb uses. -This is only available if Vimb is compiled with the COOKIE feature. -.TP -.B $VIMB_USER_AGENT -Holds the user agent string that Vimb uses. -.TP -.B $VIMB_MIME_TYPE -The mime-type of the download. -This variable is only available when the server sent the mime-type header -with the response and only if the download was not start by the `:save' -command or the `;s' hinting. -.TP -.B $VIMB_USE_PROXY -Indicates if the proxy is enabled in Vimb. -If enable this variable is `1', otherwise `0'. -Note that this variable gives no hint if the proxy settings -apply to the URL to be downloaded, only if proxy is enabled in general. -.PD -.PP -Example: -.PD 0 -.IP ":set download-command=/bin/sh -c\ - ""wget -c %s -O $VIMB_FILE --load-cookies $VIMB_COOKIES""" -.PD -.RE -.TP -.B download-path (string) -Path to the default download directory. -If the directory is not set download will be written into current directory. -The following pattern will be expanded if the download -is started '~/', '~user', '$VAR' and '${VAR}'. -.TP -.B download-use-external (bool) -Indicates if the external download tool set as `download-command' should be -used to handle downloads. -If this is disabled Vimb will handle the download. -.TP -.B editor-command (string) -Command with placeholder '%s' called if form filler is opened with $EDITOR to -spawn the editor-like `x-terminal-emulator -e vi %s'. -To use Gvim as the editor, it's necessary to call it with `-f' to run it -in the foreground. -.TP -.B fullscreen (bool) -Show the current window full-screen. -.TP -.B header (list) -Comma separated list of headers that replaces default header sent by WebKit or -new headers. -The format for the header list elements is `name[=[value]]'. -.sp -Note that these headers will replace already existing headers. -If there is no '=' after the header name, then the complete header -will be removed from the request, if the '=' is present means that -the header value is set to empty value. -.sp -To use '=' within a header value the value must be quoted like shown in -Example for the Cookie header. -.RS -.PP -Example: -.PD 0 -.IP ":set header=DNT=1,User-Agent,Cookie='name=value'" -Send the 'Do Not Track' header with each request and remove the User-Agent -Header completely from request. -.PD -.RE -.TP -.B hint-follow-last (bool) -If on, vimb automatically follows the last remaining hint on the page. -If off hints are fired only if enter is pressed. -.TP -.B hint-number-same-length (bool) -If on, all hint numbers will have the same length, so no hints will be -ambiguous. -.TP -.B hint-timeout (int) -Timeout before automatically following a non-unique numerical hint. -To disable auto fire of hints, set this value to 0. -.TP -.B hintkeys (string) -The keys used to label and select hints. -With its default value, each hint has a unique number which can be typed -to select it, while all other characters are used to filter hints based -on their text. -With a value such as asdfg;lkjh, -each hint is `numbered' based on the characters of the home row. -Note that the hint matching by label built of hintkeys is case sensitive. -In this vimb differs from some other browsers that show hint labels in upper -case, but match them lowercase. -.RS -To have upper case hint labels, it's possible to add following css to the -`style.css' file in vimb's configuration directory. -.IP "._hintLabel {text-transform: uppercase !important;}" -.RE -.TP -.B history-max-items (int) -Maximum number of unique items stored in search-, command or URI history. -If history-max-items is set to 0, the history file will not be changed. -.TP -.B home-page (string) -Homepage that vimb opens if started without a URI. -.TP -.B hsts (bool) -Enable or disables the HSTS (HTTP Strict Transport Security) feature. -.TP -.B input-autohide (bool) -If enabled the inputbox will be hidden whenever it contains no text. -.TP -.B input-bg-error (color) -Background color for the inputbox if error is shown. -.TP -.B input-bg-normal (color) -Background color of the inputbox. -.TP -.B input-fg-error (color) -Foreground color of inputbox if error is shown. -.TP -.B input-fg-normal (color) -Foreground color of inputbox. -.TP -.B input-font-error (string) -Font user in inputbox if error is shown. -.TP -.B input-font-normal (string) -Font used for inputbox. -.TP -.B nextpattern (list) -Patterns to use when guessing the next page in a document. -Each pattern is successively tested against each link in the page -beginning from the last link. -Default -"/\\bnext\\b/i,/^(>|>>|»)$/,/^(>|>>|»)/,/(>|>>|»)$/,/\\bmore\\b/i". -Note that you have to escape the '|' as '\\|' else the '|' will terminate -the :set command and start a new command. -.TP -.B maximum-cache-size (int) -Size in kB used to cache various page data. -This caching is independent from `pagecache' or `offlinecache'. -To disable caching, the size could be set to '0'. -.TP -.B previouspattern (list) -Patterns to use when guessing the previous page in a document. -Each pattern is successively tested against each link in the page -beginning from the last link. -Default "/\\bnext\\b/i,/^(>|>>|»)$/,/^(>|>>|»)/,/(>|>>|»)$/,/\\bmore\\b/i" -.TP -.B proxy (bool) -Indicates if the environment variable `http_proxy' is evaluated. -.TP -.B scrollstep (int) -Number of pixel vimb scrolls if 'j' or 'k' is used. -.TP -.B statusbar (bool) -Indicates if the statusbar should be shown. -.TP -.B status-color-bg (color) -Background color of the statusbar. -.TP -.B status-color-fg (color) -Foreground color of the statusbar. -.TP -.B status-font (string) -Font used in statusbar. -.TP -.B status-ssl-color-bg (color) -Background color of statusbar if current page uses trusted https certificate. -.TP -.B status-ssl-color-fg (color) -Foreground color for statusbar for https pages. -.TP -.B status-ssl-font (string) -Statusbar font for https pages. -.TP -.B status-sslinvalid-color-bg (color) -Background color of the statusbar if the certificate if the https page isn't -trusted. -.TP -.B status-sslinvalid-color-fg (color) -Foreground of statusbar for untrusted https pages. -.TP -.B status-sslinvalid-font (string) -Statusbar font for untrusted https pages. -.TP -.B strict-focus (bool) -Vimb checks if an editable element is focused and switch to input mode. -If strict-focus is enabled, this isn't done for focused element on page load -(without user interaction), instead the focus is removed from the focused -element. -Focus changed that appear after the page was completely loaded are -not affected by this setting. -.TP -.B strict-ssl (bool) -If 'on', vimb will not load a untrusted https site. -.TP -.B stylesheet (bool) -If 'on' the user defined styles-sheet is used. -.TP -.B timeoutlen (int) -The time in milliseconds that is waited for a key code or mapped key sequence -to complete. -.TP .B x-hint-command (string) Command used if hint mode ;x is fired. The command can be any vimb command string. @@ -1525,18 +1131,22 @@ it might change the behaviour by adding or changing mappings. .RS .P .PD 0 -.IP ":set x-hint-command=50G" -Not really useful. -If the hint is fired, scroll to the middle of the page. .IP ":set x-hint-command=:sh! curl -e % ;" This fills the inputbox with the prefilled download command and replaces `%' with the current URI and `;' with the URI of the hinted element. .PD .RE +.TP +.B xss-auditor (bool) +Whether to enable the XSS auditor. +This feature filters some kinds of reflective XSS attacks on vulnerable web +sites. .SH FILES .TP -.I $XDG_CONFIG_HOME/vimb/ -Default directory for configuration data. +.IR $XDG_CONFIG_HOME/vimb[/PROFILE] +Directory for configuration data. +If executed with \fB-p \fIPROFILE\fR parameter, configuration is read from +this subdirectory. .RS .PD 0 .TP @@ -1547,7 +1157,7 @@ Configuration file to set WebKit setting, some GUI styles and keybindings. Cookie store file. .TP .I closed -Holds the URI of last closed browser windows. +Holds the URIs of last closed browser windows. .TP .I history This file holds the history of unique opened URIs. @@ -1556,24 +1166,11 @@ This file holds the history of unique opened URIs. This file holds the history of commands and search queries performed via input box. .TP -.I search -This file holds the history of search queries. -.TP -.I bookmark -Holds the bookmarks saved with command `bma'. -The records are stored there as -.br -`URItitlespace separated tags' or as -.br -`URItags` if there is no title. -.TP .I queue -Holds the Read It Later queue filled by `qpush' if -Vimb has been compiled with QUEUE feature. +Holds the read it later queue filled by `qpush'. .TP -.I hsts -Holds the known hosts hosts if Vimb is compiled with HTTP strict transport -security feature. +.I search +This file holds the history of search queries. .TP .I scripts.js This file can be used to run user scripts, that are injected into every paged @@ -1585,21 +1182,6 @@ These file is used if the config variable `stylesheet' is enabled. .PD .RE .TP -.I $XDG_CONFIG_HOME/vimb/PROFILE-NAME -Directory for configuration data if executed with \fB-p \fIPROFILE-NAME\fR parameter. It has same structure as default directory for configuration data. -.TP -.I $XDG_CACHE_HOME/vimb/ -Default directory for cache data. -.TP -.I $XDG_CACHE_HOME/vimb/PROFILE-NAME -Directory for cache data if executed with \fB-p \fIPROFILE-NAME\fR parameter. -.TP -.I $XDG_RUNTIME_DIR/vimb/socket/ -Directory where the control sockets are placed. -.TP -.I $XDG_RUNTIME_DIR/vimb/PROFILE-NAME/socket/ -Directory where the control sockets are placed if executed with \fB-p \fIPROFILE-NAME\fR parameter. -.PP There are also some sample scripts installed together with Vimb under PREFIX/share/vimb/examples. .SH ENVIRONMENT @@ -1608,13 +1190,6 @@ PREFIX/share/vimb/examples. If this variable is set to an non-empty value, and the configuration option `proxy' is enabled, this will be used as HTTP proxy. If the proxy URL has no scheme set, HTTP is assumed. -.TP -.B no_proxy -A comma separated list of domains and/or IPs which should not be proxied. -Note that an IPv6 address must appear in brackets if used with a port, -for example "[::1]:443". -.IP -Example: "localhost,127.0.0.1,::1,fc00::/7,example.com:8080" .SH "REPORTING BUGS" Report bugs to the main project page on https://github.com/fanglingsu/vimb/issues .br diff --git a/examples/formfiller/formfiller b/examples/formfiller/formfiller deleted file mode 100755 index 05edd497..00000000 --- a/examples/formfiller/formfiller +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -# Formfiller that reads credentials from file and let vimb fill execute a -# JavaScript method to fill in the form data. Call this from vimb by ':sh! -# formfiller %' -# -# The form data are stored in $VIMB_KEY_DIR or as fallback -# $XDG_CONFIG_HOME/vimb/keys. The files must be names as -# [prefix]{domain}.gpg or [prefix]{domain}. The files must contain a valid -# JavaScript array that can be used for the _vbform.fill() method. -# -# A unencrypted sample file could look like this: -# ["input[name='user']:daniel", "input[name='password']:p45w0rD"] - -# dmenu command use in case multiple files are found for current domain -DMENU="dmenu -l 7" - -if [[ -z "$XDG_CONFIG_HOME" ]]; then - XDG_CONFIG_HOME=$HOME/.config -fi - -VIMB_KEY_DIR=${VIMB_KEY_DIR:-"$XDG_CONFIG_HOME/vimb/keys"} -uri=$1 - -die() { - echo "$1" >&2 - exit 1 -} - -fillform() { - local path=$1 - local data="" - case "$path" in - *.gpg ) - # this requires the gpg-agent to contains already the key - data=$(gpg --batch -qd "$path") - # abort here if the file could not be decrypted - if [ $? -gt 0 ]; then - exit 1 - fi - ;; - * ) - data=$(cat "$path") - ;; - esac - # make sure we are in normal mode and fill in the form data - # use :: to not save the secrets into vimb command history or into the - # last ex command register ": - echo "::e! _vbform.fill($data);" | socat - UNIX-CONNECT:$VIMB_SOCKET -} - -# check if uri is given -if [ -z "$uri" ]; then - die 'No URI given' -fi -# check if the script is run from vimb with socket support enabled -if [ -z "$VIMB_SOCKET" ] || [ ! -S "$VIMB_SOCKET" ]; then - die 'This script must be run from vimb with socket support' -fi - -# extract the domain part without ports from given uri -domain=$(echo "$uri" | sed -r 's@https?://([^:/]+).*@\1@') - -# find matching data files prefix${domain}{,.gpg} -files=($(find "$VIMB_KEY_DIR" -name "*$domain" -o -name "*${domain}.gpg")) -# strip of the key dir -files=("${files[@]#"$VIMB_KEY_DIR"/}") - -# if only one matchin data file found - use this direct -if [ ${#files[@]} -eq 1 ]; then - fillform "$VIMB_KEY_DIR/${files[0]}" - exit 1 -else - # else allow to select the right one via dmenu - match=$(printf '%s\n' "${files[@]}" | sort | $DMENU) - if [ -n $match ]; then - fillform "$VIMB_KEY_DIR/$match" - fi -fi diff --git a/examples/formfiller/scripts.js b/examples/formfiller/scripts.js deleted file mode 100644 index 83574093..00000000 --- a/examples/formfiller/scripts.js +++ /dev/null @@ -1,50 +0,0 @@ -/* Script to insert various data into form fields. - * Given data is an array of "Selector:Value" tupel. - * ["selector:value","...:..."] - * Example call from within vimb: - * ::e! _vbform.fill(["input[name='login']:daniel","input[name='password']:p45w0rD"]); - * - * Note the double ':' in front, this tells vimb not to write the command into - * history or the last excmd register ":. */ -"use strict" -var _vbform = { - fill: function(data) { - data = data||[]; - var e, i, k, s; - - // iterate over data array and try to find the elements - for (i = 0; i < data.length; i++) { - // split into selector and value part - s = data[i].split(":"); - e = document.querySelectorAll(s[0]); - for (k = 0; k < e.length; k++) { - // fill the form fields - this.set(e[k], s[1]); - } - } - }, - - // set the value for the form element - set: function(e, v) { - var i, t, n = e.nodeName.toLowerCase(); - - if (n == "input") { - t = e.type; - if (t == "checkbox" || t == "radio") { - e.checked = ("1" == v); - } else if (t == "submit") { - e.click(); - } else { - e.value = v; - } - } else if (n == "textarea") { - e.value = v; - } else if (n == "select") { - for (i = 0; i < e.options.length; i++) { - if (e.options[i].value == v) { - e.options[i].selected = "selected"; - } - } - } - } -}; diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..ca46d8a5 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +config.h +vimb diff --git a/src/Makefile b/src/Makefile index b72d7c9e..7f037c63 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,63 +1,43 @@ BASEDIR=.. include $(BASEDIR)/config.mk -OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) -LOBJ = $(patsubst %.c, %.lo, $(wildcard *.c)) +SUBDIRS = webextension +OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) +JSFILES = $(wildcard scripts/*.js) +CSSFILES = $(wildcard scripts/*.css) -all: $(TARGET) +all: vimb $(SUBDIRS:%=%.subdir-all) -clean: clean-lib - $(RM) $(TARGET) *.o *.lo hints.js.h +clean: $(SUBDIRS:%=%.subdir-clean) + $(RM) vimb *.o scripts/scripts.h -clean-lib: - $(RM) $(LIBTARGET) - -hints.o: hints.js.h -hints.lo: hints.js.h - -hints.js.h: hints.js - @echo "minify $<" - @cat $< | ./js2h.sh > $@ - -$(OBJ): config.h $(BASEDIR)/config.mk -$(LOBJ): config.h $(BASEDIR)/config.mk +vimb: $(OBJ) + @echo "${CC} $@" + $(Q)$(CC) $(OBJ) $(LDFLAGS) -o $@ -$(TARGET): $(OBJ) -ifeq ($(VERBOSE),0) - @echo "$(CC) $@" - @$(CC) $(OBJ) -o $@ $(LDFLAGS) -else - $(CC) $(OBJ) -o $@ $(LDFLAGS) -endif +$(OBJ): config.h $(BASEDIR)/config.mk scripts/scripts.h -$(LIBTARGET): $(LOBJ) -ifeq ($(VERBOSE),0) - @echo "$(CC) $@" - @$(CC) -shared ${LOBJ} -o $@ $(LDFLAGS) -else - $(CC) -shared ${LOBJ} -o $@ $(LDFLAGS) -endif +-include $(OBJ:.o=.d) config.h: @echo create $@ from config.def.h - @cp config.def.h $@ + $(Q)cp config.def.h $@ -%.o: %.c %.h -ifeq ($(VERBOSE),0) - @echo "${CC} $@" - @$(CC) $(CFLAGS) -c -o $@ $< -else - $(CC) $(CFLAGS) -c -o $@ $< -endif +scripts/scripts.h: $(JSFILES) $(CSSFILES) + $(Q)$(RM) $@ + @echo "create $@ from *.{css,js}" + $(Q)for file in $(JSFILES) $(CSSFILES); do \ + ./scripts/js2h.sh $$file >> $@; \ + done -%.lo: %.c %.h -ifeq ($(VERBOSE),0) +%.o: %.c @echo "${CC} $@" - @$(CC) -DTESTLIB $(CFLAGS) -fPIC -c -o $@ $< -else - $(CC) -DTESTLIB $(CFLAGS) -fPIC -c -o $@ $< -endif + $(Q)$(CC) $(CFLAGS) -c -o $@ $< --include $(OBJ:.o=.d) +%.subdir-all: config.h + $(Q)$(MAKE) -C $* + +%.subdir-clean: + $(Q)$(MAKE) -C $* clean -.PHONY: all clean clean-lib +.PHONY: all clean diff --git a/src/arh.c b/src/arh.c deleted file mode 100644 index f875efbb..00000000 --- a/src/arh.c +++ /dev/null @@ -1,199 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * Copyright (C) 2014 Sébastien Marie - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_ARH -#include "ascii.h" -#include "arh.h" -#include "util.h" - -typedef struct { - char *pattern; /* pattern the uri is matched against */ - GHashTable *headers; /* header list */ -} MatchARH; - -static void marh_free(MatchARH *); -static char *read_pattern(char **); - - -/** - * parse the data string to a list of MatchARH - * - * pattern name=value[,...] - */ -GSList *arh_parse(const char *data, const char **error) -{ - GSList *parsed = NULL; - GSList *data_list = NULL; - - /* parse data as comma separated list - * of "pattern1 HEADERS-GROUP1","pattern2 HEADERS-GROUP2",... */ - data_list = soup_header_parse_list(data); - - /* iter the list for parsing each elements */ - for (GSList *l = data_list; l; l = g_slist_next(l)) { - char *one_data = (char *)l->data; - char *pattern; - GHashTable *headers; - - /* remove QUOTE around one_data if any */ - if (one_data && *one_data == '"') { - int last = strlen(one_data) - 1; - if (last >= 0 && one_data[last] == '"') { - one_data[last] = '\0'; - one_data++; - } - } - - /* read pattern and parse headers */ - pattern = read_pattern(&one_data); - headers = soup_header_parse_param_list(one_data); - - /* check result (need a pattern and at least one header) */ - if (pattern && g_hash_table_size(headers)) { - MatchARH *marh = g_slice_new0(MatchARH); - marh->pattern = g_strdup(pattern); - marh->headers = headers; - - parsed = g_slist_append(parsed, marh); - -#ifdef DEBUG - PRINT_DEBUG("append pattern='%s' headers[%d]='0x%lx'", - marh->pattern, g_hash_table_size(marh->headers), (long)marh->headers); - GHashTableIter iterHeaders; - const char *name, *value; - g_hash_table_iter_init(&iterHeaders, headers); - while (g_hash_table_iter_next(&iterHeaders, (gpointer)&name, (gpointer)&value)) { - PRINT_DEBUG(" %s=%s", name, value); - } -#endif - } else { - /* an error occurs: cleanup */ - soup_header_free_param_list(headers); - soup_header_free_list(data_list); - arh_free(parsed); - - /* set error if asked */ - if (error != NULL) { - *error = "syntax error"; - } - - return NULL; - } - } - - soup_header_free_list(data_list); - - return parsed; -} - -/** - * free the list of MatchARH - */ -void arh_free(GSList *list) -{ - g_slist_free_full(list, (GDestroyNotify)marh_free); -} - -/** - * append to reponse-header of SoupMessage, - * the header that match the specified uri - */ -void arh_run(GSList *list, const char *uri, SoupMessage *msg) -{ - PRINT_DEBUG("uri: %s", uri); - - /* walk thought the list */ - for (GSList *l=list; l; l = g_slist_next(l)) { - MatchARH *marh = (MatchARH *)l->data; - - if (util_wildmatch(marh->pattern, uri)) { - GHashTableIter iter; - const char *name = NULL; - const char *value = NULL; - - g_hash_table_iter_init(&iter, marh->headers); - while (g_hash_table_iter_next(&iter, (gpointer)&name, (gpointer)&value)) { - if (value) { - /* the pattern match, so replace headers - * as side-effect, for one header the last matched will be keeped */ - soup_message_headers_replace(msg->response_headers, name, value); - - PRINT_DEBUG(" header added: %s: %s", name, value); - } else { - /* remove a previously setted auto-reponse-header */ - soup_message_headers_remove(msg->response_headers, name); - - PRINT_DEBUG(" header removed: %s", name); - } - } - } - } -} - -/** - * free a MatchARH - */ -static void marh_free(MatchARH *marh) -{ - if (marh) { - g_free(marh->pattern); - soup_header_free_param_list(marh->headers); - - g_slice_free(MatchARH, marh); - } -} - -/** - * read until ' ' (or any other SPACE) - */ -static char *read_pattern(char **line) -{ - char *pattern; - - if (!*line || !**line) { - return NULL; - } - - /* remember where the pattern starts */ - pattern = *line; - - /* move pointer to the end of the pattern (or end-of-line) */ - while (**line && !VB_IS_SPACE(**line)) { - (*line)++; - } - - if (**line) { - /* end the pattern */ - *(*line)++ = '\0'; - - /* skip trailing whitespace */ - while (VB_IS_SPACE(**line)) { - (*line)++; - } - - return pattern; - - } else { - /* end-of-line was encounter */ - return NULL; - } -} -#endif diff --git a/src/ascii.h b/src/ascii.h index ff1e2139..93027881 100644 --- a/src/ascii.h +++ b/src/ascii.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -60,25 +60,24 @@ static const unsigned char chartable[256] = { #undef C #undef SC -#define VB_IS_UPPER(c) ((chartable[(unsigned char)c] & VB_UPPER) != 0) -#define VB_IS_LOWER(c) ((chartable[(unsigned char)c] & VB_LOWER) != 0) -#define VB_IS_DIGIT(c) ((chartable[(unsigned char)c] & VB_DIGIT) != 0) -#define VB_IS_PUNKT(c) ((chartable[(unsigned char)c] & VB_PUNKT) != 0) -#define VB_IS_SPACE(c) ((chartable[(unsigned char)c] & VB_SPACE) != 0) -#define VB_IS_CTRL(c) ((chartable[(unsigned char)c] & VB_CTRL) != 0) -#define VB_IS_SEPARATOR(c) (VB_IS_SPACE(c) || c == '"' || c == '\'') -#define VB_IS_ALPHA(c) (VB_IS_LOWER(c) || VB_IS_UPPER(c)) -#define VB_IS_ALNUM(c) (VB_IS_ALPHA(c) || VB_IS_DIGIT(c)) -#define VB_IS_IDENT(c) (VB_IS_ALNUM(c) || c == '_') -#define VB_IS_USER_IDENT(c) (VB_IS_IDENT(c) || c == '-' || c == '.') +#define VB_IS_UPPER(c) ((chartable[(unsigned char)c] & VB_UPPER) != 0) +#define VB_IS_LOWER(c) ((chartable[(unsigned char)c] & VB_LOWER) != 0) +#define VB_IS_DIGIT(c) ((chartable[(unsigned char)c] & VB_DIGIT) != 0) +#define VB_IS_PUNKT(c) ((chartable[(unsigned char)c] & VB_PUNKT) != 0) +#define VB_IS_SPACE(c) ((chartable[(unsigned char)c] & VB_SPACE) != 0) +#define VB_IS_CTRL(c) ((chartable[(unsigned char)c] & VB_CTRL) != 0) +#define VB_IS_SEPARATOR(c) (VB_IS_SPACE(c) || c == '"' || c == '\'') +#define VB_IS_ALPHA(c) (VB_IS_LOWER(c) || VB_IS_UPPER(c)) +#define VB_IS_ALNUM(c) (VB_IS_ALPHA(c) || VB_IS_DIGIT(c)) +#define VB_IS_IDENT(c) (VB_IS_ALNUM(c) || c == '_') /* CSI (control sequence introducer) is the first byte of a control sequence * and is always followed by two bytes. */ -#define CSI 0x80 -#define CSI_STR "\x80" +#define CSI 0x80 +#define CSI_STR "\x80" /* get internal representation for conrol character ^C */ -#define CTRL(c) ((c) ^ 0x40) +#define CTRL(c) ((c) ^ 0x40) #define IS_SPECIAL(c) (c < 0) @@ -96,7 +95,6 @@ static const unsigned char chartable[256] = { #define KEY_DOWN TERMCAP2KEY('k', 'd') #define KEY_LEFT TERMCAP2KEY('k', 'l') #define KEY_RIGHT TERMCAP2KEY('k', 'r') - #define KEY_F1 TERMCAP2KEY('k', '1') #define KEY_F2 TERMCAP2KEY('k', '2') #define KEY_F3 TERMCAP2KEY('k', '3') diff --git a/src/autocmd.c b/src/autocmd.c deleted file mode 100644 index 6fc5a46d..00000000 --- a/src/autocmd.c +++ /dev/null @@ -1,488 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_AUTOCMD -#include "autocmd.h" -#include "ascii.h" -#include "ex.h" -#include "util.h" -#include "completion.h" - -typedef struct { - guint bits; /* the bits identify the events the command applies to */ - char *excmd; /* ex command string to be run on matches event */ - char *pattern; /* list of patterns the uri is matched agains */ -} AutoCmd; - -typedef struct { - char *name; - GSList *cmds; -} AuGroup; - -static struct { - const char *name; - guint bits; -} events[] = { - {"*", 0x00ff}, - {"LoadProvisional", 0x0001}, - {"LoadCommited", 0x0002}, - {"LoadFirstLayout", 0x0004}, - {"LoadFinished", 0x0008}, - {"LoadFailed", 0x0010}, - {"DownloadStart", 0x0020}, - {"DownloadFinished", 0x0040}, - {"DownloadFailed", 0x0080}, -}; - -extern VbCore vb; - -static AuGroup *curgroup = NULL; -static GSList *groups = NULL; -static guint usedbits = 0; /* holds all used event bits */ - -static GSList *get_group(const char *name); -static guint get_event_bits(const char *name); -static void rebuild_used_bits(void); -static char *get_next_word(char **line); -static AuGroup *new_group(const char *name); -static void free_group(AuGroup *group); -static AutoCmd *new_autocmd(const char *excmd, const char *pattern); -static void free_autocmd(AutoCmd *cmd); - - -void autocmd_init(void) -{ - curgroup = new_group("end"); - groups = g_slist_prepend(groups, curgroup); -} - -void autocmd_cleanup(void) -{ - if (groups) { - g_slist_free_full(groups, (GDestroyNotify)free_group); - } -} - -/** - * Handle the :augroup {group} ex command. - */ -gboolean autocmd_augroup(char *name, gboolean delete) -{ - GSList *item; - - if (!*name) { - return false; - } - - /* check for group "end" that marks the default group */ - if (!strcmp(name, "end")) { - curgroup = (AuGroup*)groups->data; - return true; - } - - item = get_group(name); - - /* check if the group is going to be removed */ - if (delete) { - /* group does not exist - so do nothing */ - if (!item) { - return true; - } - if (curgroup == (AuGroup*)item->data) { - /* if the group to delete is the current - switch the the default - * group after removing it */ - curgroup = (AuGroup*)groups->data; - } - - /* now remove the group */ - free_group((AuGroup*)item->data); - groups = g_slist_delete_link(groups, item); - - /* there where autocmds remove - so recreate the usedbits */ - rebuild_used_bits(); - - return true; - } - - /* check if the group does already exists */ - if (item) { - /* if the group is found in the known groups use it as current */ - curgroup = (AuGroup*)item->data; - - return true; - } - - /* create a new group and use it as current */ - curgroup = new_group(name); - - /* append it to known groups */ - groups = g_slist_prepend(groups, curgroup); - - return true; -} - -/** - * Add or delete auto commands. - * - * :au[tocmd]! [group] {event} {pat} [nested] {cmd} - * group and nested flag are not supported at the moment. - */ -gboolean autocmd_add(char *name, gboolean delete) -{ - guint bits; - char *parse, *word, *pattern, *excmd; - GSList *item; - AuGroup *grp = NULL; - - parse = name; - - /* parse group name if it's there */ - word = get_next_word(&parse); - if (word) { - /* check if the word is a known group name */ - item = get_group(word); - if (item) { - grp = (AuGroup*)item->data; - - /* group is found - get the next word */ - word = get_next_word(&parse); - } - } - if (!grp) { - /* no group found - use the current one */ - grp = curgroup; - } - - /* parse event name - if none was matched run it for all events */ - if (word) { - bits = get_event_bits(word); - if (!bits) { - return false; - } - word = get_next_word(&parse); - } else { - bits = events[AU_ALL].bits; - } - - /* last word is the pattern - if not found use '*' */ - pattern = word ? word : "*"; - - /* the rest of the line becoes the ex command to run */ - if (parse && !*parse) { - parse = NULL; - } - excmd = parse; - - /* delete the autocmd if bang was given */ - if (delete) { - GSList *lc; - AutoCmd *cmd; - gboolean removed = false; - - /* check if the group does already exists */ - for (lc = grp->cmds; lc; lc = lc->next) { - cmd = (AutoCmd*)lc->data; - /* if not bits match - skip the command */ - if (!(cmd->bits & bits)) { - continue; - } - /* skip if pattern does not match - we check the pattern against - * another pattern */ - if (!util_wildmatch(pattern, cmd->pattern)) { - continue; - } - /* remove the bits from the item */ - cmd->bits &= ~bits; - - /* if the command has no matching events - remove it */ - grp->cmds = g_slist_delete_link(grp->cmds, lc); - free_autocmd(cmd); - - removed = true; - } - - /* if ther was at least one command removed - rebuilt the used bits */ - if (removed) { - rebuild_used_bits(); - } - - return true; - } - - /* add the new autocmd */ - if (excmd && grp) { - AutoCmd *cmd = new_autocmd(excmd, pattern); - cmd->bits = bits; - - /* add the new autocmd to the group */ - grp->cmds = g_slist_append(grp->cmds, cmd); - - /* merge the autocmd bits into the used bits */ - usedbits |= cmd->bits; - } - - return true; -} - -/** - * Run named auto command. - */ -gboolean autocmd_run(AuEvent event, const char *uri, const char *group) -{ - GSList *lg, *lc; - AuGroup *grp; - AutoCmd *cmd; - guint bits = events[event].bits; - - /* if there is no autocmd for this event - skip here */ - if (!(usedbits & bits)) { - return true; - } - - /* loop over the groups and find matching commands */ - for (lg = groups; lg; lg = lg->next) { - grp = lg->data; - /* if a group was given - skip all none matching groupes */ - if (group && strcmp(group, grp->name)) { - continue; - } - /* test each command in group */ - for (lc = grp->cmds; lc; lc = lc->next) { - cmd = lc->data; - /* skip if this dos not match the event bits */ - if (!(bits & cmd->bits)) { - continue; - } - /* check pattern only if uri was given */ - /* skip if pattern does not match */ - if (uri && !util_wildmatch(cmd->pattern, uri)) { - continue; - } - /* run the command */ - /* TODO shoult the result be tested for RESULT_COMPLETE? */ - /* run command and make sure it's not writte to command history */ - ex_run_string(cmd->excmd, false); - } - } - - return true; -} - -gboolean autocmd_fill_group_completion(GtkListStore *store, const char *input) -{ - GSList *lg; - gboolean found = false; - GtkTreeIter iter; - - if (!input || !*input) { - for (lg = groups; lg; lg = lg->next) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, ((AuGroup*)lg->data)->name, -1); - found = true; - } - } else { - for (lg = groups; lg; lg = lg->next) { - char *value = ((AuGroup*)lg->data)->name; - if (g_str_has_prefix(value, input)) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1); - found = true; - } - } - } - - return found; -} - -gboolean autocmd_fill_event_completion(GtkListStore *store, const char *input) -{ - int i; - const char *value; - gboolean found = false; - GtkTreeIter iter; - - if (!input || !*input) { - for (i = 0; i < LENGTH(events); i++) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, events[i].name, -1); - found = true; - } - } else { - for (i = 0; i < LENGTH(events); i++) { - value = events[i].name; - if (g_str_has_prefix(value, input)) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1); - found = true; - } - } - } - - return found; -} - -/** - * Get the augroup by it's name. - */ -static GSList *get_group(const char *name) -{ - GSList *lg; - AuGroup *grp; - - for (lg = groups; lg; lg = lg->next) { - grp = lg->data; - if (!strcmp(grp->name, name)) { - return lg; - } - } - - return NULL; -} - -static guint get_event_bits(const char *name) -{ - int result = 0; - - while (*name) { - int i, len; - for (i = 0; i < LENGTH(events); i++) { - /* we count the chars that given name has in common with the - * current processed event */ - len = 0; - while (name[len] && events[i].name[len] && name[len] == events[i].name[len]) { - len++; - } - - /* check if the matching chars built a full word match */ - if (events[i].name[len] == '\0' - && (name[len] == '\0' || name[len] == ',') - ) { - /* add the bits to the result */ - result |= events[i].bits; - - /* move pointer after the match and skip possible ',' */ - name += len; - if (*name == ',') { - name++; - } - break; - } - } - - /* check if the end was reached without a match */ - if (i >= LENGTH(events)) { - /* is this the right place to write the error */ - vb_echo(VB_MSG_ERROR, true, "Bad autocmd event name: %s", name); - return 0; - } - } - - return result; -} - -/** - * Rebuild the usedbits from scratch. - * Save all used autocmd event bits in the bitmap. - */ -static void rebuild_used_bits(void) -{ - GSList *lc, *lg; - AuGroup *grp; - - /* clear the current set bits */ - usedbits = 0; - /* loop over the groups */ - for (lg = groups; lg; lg = lg->next) { - grp = (AuGroup*)lg->data; - - /* merge the used event bints into the bitmap */ - for (lc = grp->cmds; lc; lc = lc->next) { - usedbits |= ((AutoCmd*)lc->data)->bits; - } - } -} - -/** - * Get the next word from given line. - * Given line pointer is set past the word and and a 0-byte is added there. - */ -static char *get_next_word(char **line) -{ - char *word; - - if (!*line || !**line) { - return NULL; - } - - /* remember where the word starts */ - word = *line; - - /* move pointer to the end of the word or of the line */ - while (**line && !VB_IS_SPACE(**line)) { - (*line)++; - } - - /* end the word */ - if (**line) { - *(*line)++ = '\0'; - } - - /* skip trailing whitespace */ - while (VB_IS_SPACE(**line)) { - (*line)++; - } - - return word; -} - -static AuGroup *new_group(const char *name) -{ - AuGroup *new = g_slice_new(AuGroup); - new->name = g_strdup(name); - new->cmds = NULL; - - return new; -} - -static void free_group(AuGroup *group) -{ - g_free(group->name); - if (group->cmds) { - g_slist_free_full(group->cmds, (GDestroyNotify)free_autocmd); - } - g_slice_free(AuGroup, group); -} - -static AutoCmd *new_autocmd(const char *excmd, const char *pattern) -{ - AutoCmd *new = g_slice_new(AutoCmd); - new->excmd = g_strdup(excmd); - new->pattern = g_strdup(pattern); - return new; -} - -static void free_autocmd(AutoCmd *cmd) -{ - g_free(cmd->excmd); - g_free(cmd->pattern); - g_slice_free(AutoCmd, cmd); -} - -#endif diff --git a/src/autocmd.h b/src/autocmd.h deleted file mode 100644 index 9ab4b840..00000000 --- a/src/autocmd.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_AUTOCMD - -#ifndef _AUTOCMD_H -#define _AUTOCMD_H - -#include "main.h" - -/* this values correspond to indices in events[] array in autocmd.c */ -typedef enum { - AU_ALL, - AU_LOAD_PROVISIONAL, - AU_LOAD_COMMITED, - AU_LOAD_FIRST_LAYOUT, - AU_LOAD_FINISHED, - AU_LOAD_FAILED, - AU_DOWNLOAD_START, - AU_DOWNLOAD_FINISHED, - AU_DOWNLOAD_FAILED, -} AuEvent; - -void autocmd_init(void); -void autocmd_cleanup(void); -gboolean autocmd_augroup(char *name, gboolean delete); -gboolean autocmd_add(char *name, gboolean delete); -gboolean autocmd_run(AuEvent event, const char *uri, const char *group); -gboolean autocmd_fill_group_completion(GtkListStore *store, const char *input); -gboolean autocmd_fill_event_completion(GtkListStore *store, const char *input); - -#endif /* end of include guard: _AUTOCMD_H */ -#endif diff --git a/src/bookmark.c b/src/bookmark.c index 99121af2..110d72d5 100644 --- a/src/bookmark.c +++ b/src/bookmark.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,20 +17,22 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include + #include "config.h" #include "main.h" #include "bookmark.h" #include "util.h" #include "completion.h" -extern VbCore vb; - typedef struct { char *uri; char *title; char *tags; } Bookmark; +extern struct Vimb vb; + static GList *load(const char *file); static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query, unsigned int qlen); @@ -324,3 +326,4 @@ static void free_bookmark(Bookmark *bm) g_free(bm->tags); g_slice_free(Bookmark, bm); } + diff --git a/src/bookmark.h b/src/bookmark.h index 50d1c638..dea45568 100644 --- a/src/bookmark.h +++ b/src/bookmark.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ #ifndef _BOOKMARK_H #define _BOOKMARK_H +#include "main.h" + gboolean bookmark_add(const char *uri, const char *title, const char *tags); gboolean bookmark_remove(const char *uri); gboolean bookmark_fill_completion(GtkListStore *store, const char *input); @@ -32,3 +34,4 @@ gboolean bookmark_queue_clear(void); #endif #endif /* end of include guard: _BOOKMARK_H */ + diff --git a/src/command.c b/src/command.c index 2ba4c625..28e9a12f 100644 --- a/src/command.c +++ b/src/command.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,129 +21,162 @@ * This file contains functions that are used by normal mode and command mode * together. */ +#include + #include "config.h" -#include "main.h" +#ifdef FEATURE_QUEUE +#include "bookmark.h" +#endif #include "command.h" #include "history.h" -#include "bookmark.h" - -extern VbCore vb; +#include "main.h" -gboolean command_search(const Arg *arg) +gboolean command_search(Client *c, const Arg *arg, bool commit) { - static short dir; /* last direction 1 forward, -1 backward*/ + WebKitFindController *fc; const char *query; - static gboolean newsearch = true; - gboolean forward; + guint count; + + fc = webkit_web_view_get_find_controller(c->webview); + + g_assert(c); + g_assert(arg); + g_assert(fc); if (arg->i == 0) { - vb.state.search_matches = 0; - vb_update_statusbar(); -#ifdef FEATURE_SEARCH_HIGHLIGHT - webkit_web_view_unmark_text_matches(vb.gui.webview); -#endif - newsearch = true; - return true; - } + webkit_find_controller_search_finish(fc); - /* copy search query for later use */ - if (arg->s) { - /* set search direction only when the searching is started */ - dir = arg->i > 0 ? 1 : -1; - query = arg->s; - /* add new search query to history and search register */ - vb_register_add('/', query); - history_add(HISTORY_SEARCH, query, NULL); - } else { - /* no search phrase given - continue a previous search */ - query = vb_register_get('/'); + /* Clear the input only if the search is active. */ + if (c->state.search.active) { + vb_echo(c, MSG_NORMAL, FALSE, ""); + } + + c->state.search.active = FALSE; + c->state.search.direction = 0; + c->state.search.matches = 0; + + vb_statusbar_update(c); + + return TRUE; } - forward = (arg->i * dir) > 0; + query = arg->s; + count = abs(arg->i); + + /* Only committed search strings adjust registers and are recorded in + * history, intermediate strings (while using incsearch) don't. */ + if (commit) { + if (query) { + history_add(c, HISTORY_SEARCH, query, NULL); + vb_register_add(c, '/', query); + } else { + /* Committed search without string re-searches last string. */ + query = vb_register_get(c, '/'); + } + } + /* Hand the query string to webkit's find controller. */ if (query) { - unsigned int count = abs(arg->i); - if (newsearch) { - vb.state.search_matches = webkit_web_view_mark_text_matches(vb.gui.webview, query, false, 0); - vb_update_statusbar(); - newsearch = false; - - /* highlight matches if the search is started new or continued - * after switch to normal mode which calls this function with - * COMMAND_SEARCH_OFF */ -#ifdef FEATURE_SEARCH_HIGHLIGHT - webkit_web_view_set_highlight_text_matches(vb.gui.webview, true); -#endif - /* skip first search because this is done during typing in ex - * mode, else the search will mark the next match as active */ - if (count) { - count -= 1; - } + /* Force a fresh start in order to have webkit select the first match + * on the page. Without this workaround the first selected match + * depends on the most recent selection or caret position (even when + * caret browsing is disabled). */ + if(commit) { + webkit_find_controller_search(fc, "", WEBKIT_FIND_OPTIONS_NONE, G_MAXUINT); } - while (count--) { - if (!webkit_web_view_search_text(vb.gui.webview, query, false, forward, true)) { - break; + webkit_find_controller_search(fc, query, + WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + WEBKIT_FIND_OPTIONS_WRAP_AROUND | + (arg->i > 0 ? WEBKIT_FIND_OPTIONS_NONE : WEBKIT_FIND_OPTIONS_BACKWARDS), + G_MAXUINT); + + c->state.search.active = TRUE; + c->state.search.direction = arg->i > 0 ? 1 : -1; + /* TODO get the number of matches */ + + /* Skip first search because the first match is already + * highlighted on search start. */ + count -= 1; + } + + /* Step through searchs result focus according to arg->i. */ + if (c->state.search.active) { + if (arg->i * c->state.search.direction > 0) { + while (count--) { + webkit_find_controller_search_next(fc); } - }; + } else { + while (count--) { + webkit_find_controller_search_previous(fc); + } + } } - return true; + return TRUE; } -gboolean command_yank(const Arg *arg, char buf) +gboolean command_yank(Client *c, const Arg *arg, char buf) { - static char *tmpl = "Yanked: %s"; - - if (arg->i == COMMAND_YANK_SELECTION) { - char *text = NULL; - /* copy current selection to clipboard */ - webkit_web_view_copy_clipboard(vb.gui.webview); - text = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD()); - if (!text) { - text = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD()); - } - if (text) { - /* save in given register and default "" register */ - vb_register_add(buf, text); - vb_register_add('"', text); - vb_echo(VB_MSG_NORMAL, false, tmpl, text); - g_free(text); - - return true; - } + /** + * This implementation is quite 'brute force', same as in vimb2 + * - both X clipboards are always set, PRIMARY and CLIPBOARD + * - the X clipboards are always set, even though a vimb register was given + */ + + const char *uri = NULL; + char *yanked = NULL; + + g_assert(c); + g_assert(arg); + g_assert(c->webview); + g_assert( + arg->i == COMMAND_YANK_URI || + arg->i == COMMAND_YANK_SELECTION || + arg->i == COMMAND_YANK_ARG); - return false; - } - - Arg a = {VB_CLIPBOARD_PRIMARY|VB_CLIPBOARD_SECONDARY}; if (arg->i == COMMAND_YANK_URI) { - /* yank current uri */ - a.s = vb.state.uri; + if ((uri = webkit_web_view_get_uri(c->webview))) { + yanked = g_strdup(uri); + } + } else if (arg->i == COMMAND_YANK_SELECTION) { + /* copy web view selection to clipboard */ + webkit_web_view_execute_editing_command(c->webview, WEBKIT_EDITING_COMMAND_COPY); + /* read back copy from clipboard */ + yanked = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); } else { /* use current arg.s as new clipboard content */ - a.s = arg->s; + yanked = g_strdup(arg->s); } - if (a.s) { - vb_set_clipboard(&a); - /* save in given register and default "" register */ - vb_register_add(buf, a.s); - vb_register_add('"', a.s); - vb_echo(VB_MSG_NORMAL, false, tmpl, a.s); - - return true; + + if(!yanked) { + return FALSE; } - return false; + /* store in vimb default register */ + vb_register_add(c, '"', yanked); + /* store in vimb register buf if buf != 0 */ + vb_register_add(c, buf, yanked); + + /* store in X clipboard primary (selected text copy, middle mouse paste) */ + gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), yanked, -1); + /* store in X "windows style" clipboard */ + gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), yanked, -1); + + vb_echo(c, MSG_NORMAL, false, "Yanked: %s", yanked); + + g_free(yanked); + + return TRUE; } -gboolean command_save(const Arg *arg) +gboolean command_save(Client *c, const Arg *arg) { - WebKitDownload *download; const char *uri, *path = NULL; + WebKitDownload *download; if (arg->i == COMMAND_SAVE_CURRENT) { - uri = vb.state.uri; + uri = c->state.uri; /* given string is the path to save the download to */ if (arg->s && *(arg->s) != '\0') { path = arg->s; @@ -153,48 +186,48 @@ gboolean command_save(const Arg *arg) } if (!uri || !*uri) { - return false; + return FALSE; } - download = webkit_download_new(webkit_network_request_new(uri)); - vb_download(vb.gui.webview, download, path); + /* Start the download to given path. */ + download = webkit_web_view_download_uri(c->webview, uri); - return true; + return vb_download_set_destination(c, download, NULL, path); } #ifdef FEATURE_QUEUE -gboolean command_queue(const Arg *arg) +gboolean command_queue(Client *c, const Arg *arg) { - gboolean res = false; - int count = 0; + gboolean res = FALSE; + int count = 0; char *uri; switch (arg->i) { case COMMAND_QUEUE_POP: if ((uri = bookmark_queue_pop(&count))) { - res = vb_load_uri(&(Arg){VB_TARGET_CURRENT, uri}); + res = vb_load_uri(c, &(Arg){TARGET_CURRENT, uri}); g_free(uri); } - vb_echo(VB_MSG_NORMAL, false, "Queue length %d", count); + vb_echo(c, MSG_NORMAL, FALSE, "Queue length %d", count); break; case COMMAND_QUEUE_PUSH: - res = bookmark_queue_push(arg->s ? arg->s : vb.state.uri); + res = bookmark_queue_push(arg->s ? arg->s : c->state.uri); if (res) { - vb_echo(VB_MSG_NORMAL, false, "Pushed to queue"); + vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue"); } break; case COMMAND_QUEUE_UNSHIFT: - res = bookmark_queue_unshift(arg->s ? arg->s : vb.state.uri); + res = bookmark_queue_unshift(arg->s ? arg->s : c->state.uri); if (res) { - vb_echo(VB_MSG_NORMAL, false, "Pushed to queue"); + vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue"); } break; case COMMAND_QUEUE_CLEAR: if (bookmark_queue_clear()) { - vb_echo(VB_MSG_NORMAL, false, "Queue cleared"); + vb_echo(c, MSG_NORMAL, FALSE, "Queue cleared"); } break; } diff --git a/src/command.h b/src/command.h index 375b280f..d7625251 100644 --- a/src/command.h +++ b/src/command.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,9 @@ #ifndef _COMMAND_H #define _COMMAND_H +#include +#include "main.h" + enum { COMMAND_YANK_ARG, COMMAND_YANK_URI, @@ -40,11 +43,11 @@ enum { }; #endif -gboolean command_search(const Arg *arg); -gboolean command_yank(const Arg *arg, char buf); -gboolean command_save(const Arg *arg); +gboolean command_search(Client *c, const Arg *arg, bool commit); +gboolean command_yank(Client *c, const Arg *arg, char buf); +gboolean command_save(Client *c, const Arg *arg); #ifdef FEATURE_QUEUE -gboolean command_queue(const Arg *arg); +gboolean command_queue(Client *c, const Arg *arg); #endif #endif /* end of include guard: _COMMAND_H */ diff --git a/src/completion.c b/src/completion.c index 0d8d52a1..4042cb8d 100644 --- a/src/completion.c +++ b/src/completion.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,25 +17,54 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include "completion.h" #include "config.h" #include "main.h" -#include "completion.h" - -extern VbCore vb; -static struct { - GtkWidget *win; - GtkWidget *tree; - int active; /* number of the current active tree item */ - CompletionSelectFunc selfunc; -} comp; +typedef struct { + GtkWidget *win, *tree; + int active; /* number of the current active tree item */ + CompletionSelectFunc selfunc; +} Completion; static gboolean tree_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data); +extern struct Vimb vb; + + +/** + * Stop the completion and reset temporary used data. + */ +void completion_clean(Client *c) +{ + Completion *comp = (Completion*)c->comp; + c->mode->flags &= ~FLAG_COMPLETION; + + if (comp->win) { + gtk_widget_destroy(comp->win); + comp->win = NULL; + comp->tree = NULL; + } +} + +/** + * Free the memory of the completion set on the client. + */ +void completion_cleanup(Client *c) +{ + if (c->comp) { + g_slice_free(Completion, c->comp); + c->comp = NULL; + } +} -gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, - gboolean back) +/** + * Start the completion by creating the required widgets and setting a select + * function. + */ +gboolean completion_create(Client *c, GtkTreeModel *model, + CompletionSelectFunc selfunc, gboolean back) { GtkCellRenderer *renderer; GtkTreeSelection *selection; @@ -44,6 +73,7 @@ gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, GtkTreePath *path; GtkTreeIter iter; int height, width; + Completion *comp = (Completion*)c->comp; /* if there is only one match - don't build the tree view */ if (gtk_tree_model_iter_n_children(model, NULL) == 1) { @@ -53,59 +83,55 @@ gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1); /* call the select function */ - selfunc(value); + selfunc(c, value); g_free(value); g_object_unref(model); - return false; + return FALSE; } } - comp.selfunc = selfunc; + comp->selfunc = selfunc; /* prepare the tree view */ - comp.win = gtk_scrolled_window_new(NULL, NULL); - comp.tree = gtk_tree_view_new(); -#ifndef HAS_GTK3 - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(comp.win), GTK_POLICY_NEVER, GTK_POLICY_NEVER); -#endif - gtk_box_pack_end(GTK_BOX(vb.gui.box), comp.win, false, false, 0); - gtk_container_add(GTK_CONTAINER(comp.win), comp.tree); + comp->win = gtk_scrolled_window_new(NULL, NULL); + comp->tree = gtk_tree_view_new(); + + gtk_style_context_add_provider(gtk_widget_get_style_context(comp->tree), + GTK_STYLE_PROVIDER(vb.style_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_widget_set_name(GTK_WIDGET(comp->tree), "completion"); + + gtk_box_pack_end(GTK_BOX(gtk_widget_get_parent(GTK_WIDGET(c->statusbar.box))), comp->win, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(comp->win), comp->tree); - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(comp.tree), false); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(comp->tree), FALSE); /* we have only on line per item so we can use the faster fixed heigh mode */ - gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(comp.tree), true); - gtk_tree_view_set_model(GTK_TREE_VIEW(comp.tree), model); + gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(comp->tree), TRUE); + gtk_tree_view_set_model(GTK_TREE_VIEW(comp->tree), model); g_object_unref(model); - VB_WIDGET_OVERRIDE_TEXT(comp.tree, VB_GTK_STATE_NORMAL, &vb.style.comp_fg[VB_COMP_NORMAL]); - VB_WIDGET_OVERRIDE_BASE(comp.tree, VB_GTK_STATE_NORMAL, &vb.style.comp_bg[VB_COMP_NORMAL]); - VB_WIDGET_OVERRIDE_TEXT(comp.tree, VB_GTK_STATE_SELECTED, &vb.style.comp_fg[VB_COMP_ACTIVE]); - VB_WIDGET_OVERRIDE_BASE(comp.tree, VB_GTK_STATE_SELECTED, &vb.style.comp_bg[VB_COMP_ACTIVE]); - VB_WIDGET_OVERRIDE_TEXT(comp.tree, VB_GTK_STATE_ACTIVE, &vb.style.comp_fg[VB_COMP_ACTIVE]); - VB_WIDGET_OVERRIDE_BASE(comp.tree, VB_GTK_STATE_ACTIVE, &vb.style.comp_bg[VB_COMP_ACTIVE]); - /* prepare the selection */ - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(comp.tree)); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); - gtk_tree_selection_set_select_function(selection, tree_selection_func, NULL, NULL); + gtk_tree_selection_set_select_function(selection, tree_selection_func, c, NULL); /* get window dimension */ - gtk_window_get_size(GTK_WINDOW(vb.gui.window), &width, &height); + gtk_window_get_size(GTK_WINDOW(c->window), &width, &height); /* prepare first column */ column = gtk_tree_view_column_new(); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); - gtk_tree_view_append_column(GTK_TREE_VIEW(comp.tree), column); + gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, - "font-desc", vb.style.comp_font, + "font-desc", c->config.comp_font, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL ); - gtk_tree_view_column_pack_start(column, renderer, true); + gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_FIRST); gtk_tree_view_column_set_min_width(column, 2 * width/3); @@ -113,20 +139,20 @@ gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, #ifdef FEATURE_TITLE_IN_COMPLETION column = gtk_tree_view_column_new(); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); - gtk_tree_view_append_column(GTK_TREE_VIEW(comp.tree), column); + gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, - "font-desc", vb.style.comp_font, + "font-desc", c->config.comp_font, "ellipsize", PANGO_ELLIPSIZE_END, NULL ); - gtk_tree_view_column_pack_start(column, renderer, true); + gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_SECOND); #endif /* to set the height for the treeview the tree must be realized first */ - gtk_widget_show(comp.tree); + gtk_widget_show(comp->tree); /* this prevents the first item to be placed out of view if the completion * is shown */ @@ -135,30 +161,31 @@ gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, } /* use max 1/3 of window height for the completion */ -#ifdef HAS_GTK3 - gtk_widget_get_preferred_size(comp.tree, NULL, &size); + gtk_widget_get_preferred_size(comp->tree, NULL, &size); height /= 3; gtk_scrolled_window_set_min_content_height( - GTK_SCROLLED_WINDOW(comp.win), + GTK_SCROLLED_WINDOW(comp->win), size.height > height ? height : size.height ); -#else - gtk_widget_size_request(comp.tree, &size); - height /= 3; - if (size.height > height) { - gtk_widget_set_size_request(comp.win, -1, height); - } -#endif - vb.mode->flags |= FLAG_COMPLETION; + c->mode->flags |= FLAG_COMPLETION; /* set to -1 to have the cursor on first or last item set in move_cursor */ - comp.active = -1; - completion_next(back); + comp->active = -1; + completion_next(c, back); - gtk_widget_show(comp.win); + gtk_widget_show(comp->win); - return true; + return TRUE; +} + +/** + * Initialize the completion system for given client. + */ +void completion_init(Client *c) +{ + /* Allocate memory for the completion struct and save it on the client. */ + c->comp = g_slice_new0(Completion); } /** @@ -166,52 +193,78 @@ gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, * If the end/beginning is reached return false and start on the opposite end * on the next call. */ -gboolean completion_next(gboolean back) +gboolean completion_next(Client *c, gboolean back) { int rows; GtkTreePath *path; - GtkTreeView *tree = GTK_TREE_VIEW(comp.tree); + GtkTreeView *tree = GTK_TREE_VIEW(((Completion*)c->comp)->tree); + Completion *comp = (Completion*)c->comp; rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(tree), NULL); if (back) { - comp.active--; + comp->active--; /* Step back over the beginning. */ - if (comp.active == -1) { - /* Unselect the current item to show the user that the shown - * content is the initial typed content. */ - gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp.tree))); - - return false; - } else if (comp.active < -1) { - comp.active = rows - 1; + if (comp->active == -1) { + /* Deselect the current item to show the user the initial typed + * content. */ + gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree))); + + return FALSE; + } + if (comp->active < -1) { + comp->active = rows - 1; } } else { - comp.active++; + comp->active++; /* Step over the end. */ - if (comp.active == rows) { - gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp.tree))); + if (comp->active == rows) { + gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree))); - return false; - } else if (comp.active >= rows) { - comp.active = 0; + return FALSE; + } + if (comp->active >= rows) { + comp->active = 0; } } /* get new path and move cursor to it */ - path = gtk_tree_path_new_from_indices(comp.active, -1); - gtk_tree_view_set_cursor(tree, path, NULL, false); + path = gtk_tree_path_new_from_indices(comp->active, -1); + gtk_tree_view_set_cursor(tree, path, NULL, FALSE); gtk_tree_path_free(path); - return true; + return TRUE; } -void completion_clean(void) +/** + * Fills the given list store by matching data of also given src list. + */ +gboolean completion_fill(GtkListStore *store, const char *input, GList *src) { - vb.mode->flags &= ~FLAG_COMPLETION; - if (comp.win) { - gtk_widget_destroy(comp.win); - comp.win = comp.tree = NULL; + gboolean found = FALSE; + GtkTreeIter iter; + + /* If no filter input given - copy all entries into the data store. */ + if (!input || !*input) { + for (GList *l = src; l; l = l->next) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); + found = TRUE; + } + return found; } + + /* If filter input is given - copy matching list entires into data store. + * Strings are compared by prefix matching. */ + for (GList *l = src; l; l = l->next) { + char *value = (char*)l->data; + if (g_str_has_prefix(value, input)) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); + found = TRUE; + } + } + + return found; } static gboolean tree_selection_func(GtkTreeSelection *selection, @@ -219,16 +272,19 @@ static gboolean tree_selection_func(GtkTreeSelection *selection, { char *value; GtkTreeIter iter; + Completion *comp = (Completion*)((Client*)data)->comp; /* if not selected means the item is going to be selected which we are * interested in */ if (!selected && gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1); - comp.selfunc(value); + comp->selfunc((Client*)data, value); g_free(value); + /* TODO update comp->active on select by mouse to continue with + * or from selected item on. */ } - return true; + return TRUE; } diff --git a/src/completion.h b/src/completion.h index f8e481bc..df8eb60a 100644 --- a/src/completion.h +++ b/src/completion.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,8 +20,12 @@ #ifndef _COMPLETION_H #define _COMPLETION_H +#include + #include "main.h" +typedef void (*CompletionSelectFunc) (Client *c, char *match); + enum { COMPLETION_STORE_FIRST, #ifdef FEATURE_TITLE_IN_COMPLETION @@ -30,11 +34,13 @@ enum { COMPLETION_STORE_NUM }; -typedef void (*CompletionSelectFunc) (char *match); -gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, - gboolean back); -void completion_clean(void); -gboolean completion_next(gboolean back); +void completion_clean(Client *c); +void completion_cleanup(Client *c); +gboolean completion_create(Client *c, GtkTreeModel *model, + CompletionSelectFunc selfunc, gboolean back); +void completion_init(Client *c); +gboolean completion_next(Client *c, gboolean back); +gboolean completion_fill(GtkListStore *store, const char *input, GList *src); #endif /* end of include guard: _COMPLETION_H */ diff --git a/src/config.def.h b/src/config.def.h index bd0497e7..48f33924 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,107 +17,41 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#ifndef _CONFIG_H -#define _CONFIG_H - - /* features */ -/* enable cookie support */ -#define FEATURE_COOKIE -/* highlight search results */ -#define FEATURE_SEARCH_HIGHLIGHT -/* disable scrollbars */ -#define FEATURE_NO_SCROLLBARS -/* disable X window embedding */ -/* #define FEATURE_NO_XEMBED */ +/* show wget style progressbar in status bar */ +#define FEATURE_WGET_PROGRESS_BAR +/* show load progress in window title */ +#define FEATURE_TITLE_PROGRESS /* show page title in url completions */ #define FEATURE_TITLE_IN_COMPLETION +/* support gui style settings compatible with vimb2 */ +/* #define FEATURE_GUI_STYLE_VIMB2_COMPAT */ /* enable the read it later queue */ #define FEATURE_QUEUE -/* show load progress in window title */ -#define FEATURE_TITLE_PROGRESS -/* should the history indicator [+-] be shown in status bar after url */ -#define FEATURE_HISTORY_INDICATOR -/* should the profile name be shown before url in url bar */ -#define FEATURE_PROFILE_INDICATOR -/* show wget style progressbar in status bar */ -#define FEATURE_WGET_PROGRESS_BAR -#ifdef HAS_GTK3 -/* enables workaround for high dpi displays */ -/* eventually the environment variable GDK_DPI_SCALE=2.0 must be set */ -/* to get the hack working */ -/* #define FEATURE_HIGH_DPI */ + +#ifdef FEATURE_WGET_PROGRESS_BAR +/* chars to use for the progressbar */ +#define PROGRESS_BAR "=> " +#define PROGRESS_BAR_LEN 20 #endif -/* enable HTTP Strict-Transport-Security*/ -#define FEATURE_HSTS -/* enable soup caching - size can be configure by maximum-cache-size setting */ -#define FEATURE_SOUP_CACHE -/* allow setting default_zoom via config */ -#define FEATURE_DEFAULT_ZOOM -/* enable the :autocmd feature */ -#define FEATURE_AUTOCMD -/* enable the :auto-response-header feature */ -#define FEATURE_ARH -/* allow to use socket to remote control vimb */ -#define FEATURE_SOCKET /* time in seconds after that message will be removed from inputbox if the * message where only temporary */ -#define MESSAGE_TIMEOUT 5 +#define MESSAGE_TIMEOUT 5 -/* number of chars to be shown for ambiguous commands */ +/* number of chars to be shown in statusbar for ambiguous commands */ #define SHOWCMD_LEN 10 +/* css applied to the gui elements regardless of user's settings */ +#define GUI_STYLE_CSS_BASE "#input text{background-color:inherit;color:inherit;caret-color:@color;font:inherit;}" -/* parh to crt file for the certificate validation */ -#define SETTING_CA_BUNDLE "/etc/ssl/certs/ca-certificates.crt" -#define SETTING_MAX_CONNS 25 -#define SETTING_MAX_CONNS_PER_HOST 5 /* default font size for fonts in webview */ -#define SETTING_DEFAULT_FONT_SIZE 10 -#define SETTING_GUI_FONT_NORMAL "monospace normal 10" -#define SETTING_GUI_FONT_EMPH "monospace bold 10" -#define SETTING_HOME_PAGE "http://fanglingsu.github.io/vimb/" +#define SETTING_DEFAULT_FONT_SIZE 16 +#define SETTING_DEFAULT_MONOSPACE_FONT_SIZE 13 +#define SETTING_GUI_FONT_NORMAL "10pt monospace" +#define SETTING_GUI_FONT_EMPH "bold 10pt monospace" +#define SETTING_HOME_PAGE "about:blank" #define MAXIMUM_HINTS 500 - +/* default window dimensions */ #define WIN_WIDTH 800 #define WIN_HEIGHT 600 - -#ifdef FEATURE_WGET_PROGRESS_BAR -/* chars to use for the progressbar */ -#define PROGRESS_BAR "=> " -#define PROGRESS_BAR_LEN 20 -#endif - -/* CSS style use on creating hints. This might also be averrules by css out of - * $XDG_CONFIG_HOME/vimb/style.css file. */ -#define HINT_CSS "#_hintContainer{\ -position:static\ -}\ -._hintLabel{\ --webkit-transform:translate(-4px,-4px);\ -position:absolute;\ -z-index:100000;\ -font:bold .8em monospace;\ -color:#000;\ -background-color:#fff;\ -margin:0;\ -padding:0px 1px;\ -border:1px solid #444;\ -opacity:0.7\ -}\ -._hintElem{\ -background-color:#ff0 !important;\ -color:#000 !important;\ -transition:all 0 !important;\ -transition-delay:all 0 !important\ -}\ -._hintElem._hintFocus{\ -background-color:#8f0 !important\ -}\ -._hintLabel._hintFocus{\ -z-index:100001;\ -opacity:1\ -}" - -#endif /* end of include guard: _CONFIG_H */ diff --git a/src/cookiejar.c b/src/cookiejar.c deleted file mode 100644 index 1a29dde8..00000000 --- a/src/cookiejar.c +++ /dev/null @@ -1,105 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" - -#ifdef FEATURE_COOKIE -#include -#include -#include "main.h" -#include "cookiejar.h" - -G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT) - -static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old, SoupCookie *new); -static void cookiejar_class_init(CookieJarClass *klass); -static void cookiejar_finalize(GObject *self); -static void cookiejar_init(CookieJar *self); -static void cookiejar_set_property(GObject *self, guint prop_id, - const GValue *value, GParamSpec *pspec); - -extern VbCore vb; - - -SoupCookieJar *cookiejar_new(const char *file, gboolean ro) -{ - return g_object_new( - COOKIEJAR_TYPE, - SOUP_COOKIE_JAR_TEXT_FILENAME, file, - SOUP_COOKIE_JAR_READ_ONLY, ro, - NULL - ); -} - -static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, SoupCookie *new_cookie) -{ - flock(COOKIEJAR(self)->lock, LOCK_EX); - SoupDate *expire; - if (new_cookie) { - /* session-expire-time handling */ - if (vb.config.cookie_expire_time == 0) { - soup_cookie_set_expires(new_cookie, NULL); - - } else if (vb.config.cookie_expire_time > 0 && new_cookie->expires) { - expire = soup_date_new_from_now(vb.config.cookie_expire_time); - if (soup_date_to_time_t(expire) < soup_date_to_time_t(new_cookie->expires)) { - soup_cookie_set_expires(new_cookie, expire); - } - soup_date_free(expire); - } - - /* session-cookie handling */ - if (!new_cookie->expires && vb.config.cookie_timeout) { - expire = soup_date_new_from_now(vb.config.cookie_timeout); - soup_cookie_set_expires(new_cookie, expire); - soup_date_free(expire); - } - } - SOUP_COOKIE_JAR_CLASS(cookiejar_parent_class)->changed(self, old_cookie, new_cookie); - flock(COOKIEJAR(self)->lock, LOCK_UN); -} - -static void cookiejar_class_init(CookieJarClass *klass) -{ - SOUP_COOKIE_JAR_CLASS(klass)->changed = cookiejar_changed; - G_OBJECT_CLASS(klass)->get_property = G_OBJECT_CLASS(cookiejar_parent_class)->get_property; - G_OBJECT_CLASS(klass)->set_property = cookiejar_set_property; - G_OBJECT_CLASS(klass)->finalize = cookiejar_finalize; - g_object_class_override_property(G_OBJECT_CLASS(klass), 1, "filename"); -} - -static void cookiejar_finalize(GObject *self) -{ - close(COOKIEJAR(self)->lock); - G_OBJECT_CLASS(cookiejar_parent_class)->finalize(self); -} - -static void cookiejar_init(CookieJar *self) -{ - self->lock = open(vb.files[FILES_COOKIE], O_RDWR); -} - -static void cookiejar_set_property(GObject *self, guint prop_id, const - GValue *value, GParamSpec *pspec) -{ - flock(COOKIEJAR(self)->lock, LOCK_SH); - G_OBJECT_CLASS(cookiejar_parent_class)->set_property(self, prop_id, value, pspec); - flock(COOKIEJAR(self)->lock, LOCK_UN); -} -#endif diff --git a/src/dom.c b/src/dom.c deleted file mode 100644 index cb954559..00000000 --- a/src/dom.c +++ /dev/null @@ -1,330 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#include "main.h" -#include "dom.h" - -extern VbCore vb; - -static gboolean element_is_visible(WebKitDOMDOMWindow* win, WebKitDOMElement* element); -static gboolean auto_insert(Element *element); -static gboolean editable_blur_cb(Element *element, Event *event); -static gboolean editable_focus_cb(Element *element, Event *event); -static Element *get_active_element(Document *doc); - - -void dom_install_focus_blur_callbacks(Document *doc) -{ - HtmlElement *element = webkit_dom_document_get_body(doc); - - if (!element) { - element = WEBKIT_DOM_HTML_ELEMENT(webkit_dom_document_get_document_element(doc)); - } - if (element) { - webkit_dom_event_target_add_event_listener( - WEBKIT_DOM_EVENT_TARGET(element), "blur", G_CALLBACK(editable_blur_cb), true, NULL - ); - webkit_dom_event_target_add_event_listener( - WEBKIT_DOM_EVENT_TARGET(element), "focus", G_CALLBACK(editable_focus_cb), true, NULL - ); - } -} - -void dom_check_auto_insert(Document *doc) -{ - Element *active = webkit_dom_html_document_get_active_element(WEBKIT_DOM_HTML_DOCUMENT(doc)); - - if (active) { - if (!vb.config.strict_focus) { - auto_insert(active); - } else if (vb.mode->id != 'i') { - /* If strict-focus is enabled and the editable element becomes - * focus, we explicitely remove the focus. But only if vimb isn't - * in input mode at the time. This prevents from leaving input - * mode that was started by user interaction like click to - * editable element, or the 'gi' normal mode command. */ - webkit_dom_element_blur(active); - } - } - dom_install_focus_blur_callbacks(doc); -} - -/** - * Remove focus from active and editable elements. - */ -void dom_clear_focus(WebKitWebView *view) -{ - Element *active = dom_get_active_element(view); - if (active) { - webkit_dom_element_blur(active); - } -} - -/** - * Give the focus to element. - */ -void dom_give_focus(Element *element) -{ - webkit_dom_element_focus(element); -} - -/** - * Find the first editable element and set the focus on it and enter input - * mode. - * Returns true if there was an editable element focused. - */ -gboolean dom_focus_input(Document *doc) -{ - WebKitDOMNode *html, *node; - WebKitDOMDOMWindow *win; - WebKitDOMNodeList *list; - WebKitDOMXPathNSResolver *resolver; - WebKitDOMXPathResult* result; - Document *frame_doc; - guint i, len; - - win = webkit_dom_document_get_default_view(doc); - list = webkit_dom_document_get_elements_by_tag_name(doc, "html"); - if (!list) { - return false; - } - - html = webkit_dom_node_list_item(list, 0); - g_object_unref(list); - - resolver = webkit_dom_document_create_ns_resolver(doc, html); - if (!resolver) { - return false; - } - - /* Use translate to match xpath expression case insensitive so that also - * intput filed of type="TEXT" are matched. */ - result = webkit_dom_document_evaluate( - doc, "//input[not(@type) " - "or translate(@type,'ETX','etx')='text' " - "or translate(@type,'ADOPRSW','adoprsw')='password' " - "or translate(@type,'CLOR','clor')='color' " - "or translate(@type,'ADET','adet')='date' " - "or translate(@type,'ADEIMT','adeimt')='datetime' " - "or translate(@type,'ACDEILMOT','acdeilmot')='datetime-local' " - "or translate(@type,'AEILM','aeilm')='email' " - "or translate(@type,'HMNOT','hmnot')='month' " - "or translate(@type,'BEMNRU','bemnru')='number' " - "or translate(@type,'ACEHRS','acehrs')='search' " - "or translate(@type,'ELT','elt')='tel' " - "or translate(@type,'EIMT','eimt')='time' " - "or translate(@type,'LRU','lru')='url' " - "or translate(@type,'EKW','ekw')='week' " - "]|//textarea", - html, resolver, 5, NULL, NULL - ); - if (!result) { - return false; - } - while ((node = webkit_dom_xpath_result_iterate_next(result, NULL))) { - if (element_is_visible(win, WEBKIT_DOM_ELEMENT(node))) { - vb_enter('i'); - webkit_dom_element_focus(WEBKIT_DOM_ELEMENT(node)); - return true; - } - } - - /* Look for editable elements in frames too. */ - list = webkit_dom_document_get_elements_by_tag_name(doc, "iframe"); - len = webkit_dom_node_list_get_length(list); - - for (i = 0; i < len; i++) { - node = webkit_dom_node_list_item(list, i); - frame_doc = webkit_dom_html_iframe_element_get_content_document(WEBKIT_DOM_HTML_IFRAME_ELEMENT(node)); - /* Stop on first frame with focused element. */ - if (dom_focus_input(frame_doc)) { - g_object_unref(list); - return true; - } - } - g_object_unref(list); - - return false; -} - -/** - * Indicates if the given dom element is an editable element like text input, - * password or textarea. - */ -gboolean dom_is_editable(Element *element) -{ - gboolean result = false; - char *tagname, *type, *editable; - - if (!element) { - return result; - } - - tagname = webkit_dom_element_get_tag_name(element); - type = webkit_dom_element_get_attribute(element, "type"); - editable = webkit_dom_element_get_attribute(element, "contenteditable"); - /* element is editable if it's a text area or input with no type, text or - * pasword */ - if (!g_ascii_strcasecmp(tagname, "textarea")) { - result = true; - } else if (!g_ascii_strcasecmp(tagname, "input") - && (!*type - || !g_ascii_strcasecmp(type, "text") - || !g_ascii_strcasecmp(type, "password") - || !g_ascii_strcasecmp(type, "color") - || !g_ascii_strcasecmp(type, "date") - || !g_ascii_strcasecmp(type, "datetime") - || !g_ascii_strcasecmp(type, "datetime-local") - || !g_ascii_strcasecmp(type, "email") - || !g_ascii_strcasecmp(type, "month") - || !g_ascii_strcasecmp(type, "number") - || !g_ascii_strcasecmp(type, "search") - || !g_ascii_strcasecmp(type, "tel") - || !g_ascii_strcasecmp(type, "time") - || !g_ascii_strcasecmp(type, "url") - || !g_ascii_strcasecmp(type, "week")) - ) { - result = true; - } else if (!g_ascii_strcasecmp(editable, "true")) { - result = true; - } else { - result = false; - } - g_free(tagname); - g_free(type); - g_free(editable); - - return result; -} - -Element *dom_get_active_element(WebKitWebView *view) -{ - return get_active_element(webkit_web_view_get_dom_document(view)); -} - -const char *dom_editable_element_get_value(Element *element) -{ - const char *value = NULL; - - if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT((HtmlInputElement*)element)) { - value = webkit_dom_html_input_element_get_value((HtmlInputElement*)element); - } else { - /* we should check WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT, but this - * seems to return alway false */ - value = webkit_dom_html_text_area_element_get_value((HtmlTextareaElement*)element); - } - - return value; -} - -void dom_editable_element_set_value(Element *element, const char *value) -{ - if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) { - webkit_dom_html_input_element_set_value((HtmlInputElement*)element, value); - } else { - webkit_dom_html_text_area_element_set_value((HtmlTextareaElement*)element, value); - } -} - -void dom_editable_element_set_disable(Element *element, gboolean value) -{ - if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) { - webkit_dom_html_input_element_set_disabled((HtmlInputElement*)element, value); - } else { - webkit_dom_html_text_area_element_set_disabled((HtmlTextareaElement*)element, value); - } -} - -static gboolean element_is_visible(WebKitDOMDOMWindow* win, WebKitDOMElement* element) -{ - gchar* value = NULL; - - WebKitDOMCSSStyleDeclaration* style = webkit_dom_dom_window_get_computed_style(win, element, ""); - value = webkit_dom_css_style_declaration_get_property_value(style, "visibility"); - if (value && g_ascii_strcasecmp(value, "hidden") == 0) { - return false; - } - value = webkit_dom_css_style_declaration_get_property_value(style, "display"); - if (value && g_ascii_strcasecmp(value, "none") == 0) { - return false; - } - - return true; -} - -static gboolean auto_insert(Element *element) -{ - /* Change only the mode if we are in normal mode - passthrough should not - * be disturbed by this and if the user iy typing into inputbox we don't - * want to remove the content and force a switch to input mode. And to - * switch to input mode when this is already active makes no sense. */ - if (vb.mode->id == 'n' && dom_is_editable(element)) { - vb_enter('i'); - - return true; - } - return false; -} - -static gboolean editable_blur_cb(Element *element, Event *event) -{ - if (vb.state.window_has_focus && vb.mode->id == 'i') { - vb_enter('n'); - } - - return false; -} - -static gboolean editable_focus_cb(Element *element, Event *event) -{ - if (vb.state.done_loading_page || !vb.config.strict_focus) { - auto_insert((Element*)webkit_dom_event_get_target(event)); - } - - return false; -} - -static Element *get_active_element(Document *doc) -{ - char *tagname; - Document *d = NULL; - Element *active, *result = NULL; - - active = webkit_dom_html_document_get_active_element((void*)doc); - if (!active) { - return result; - } - tagname = webkit_dom_element_get_tag_name(active); - - if (!g_strcmp0(tagname, "FRAME")) { - d = webkit_dom_html_frame_element_get_content_document(WEBKIT_DOM_HTML_FRAME_ELEMENT(active)); - result = get_active_element(d); - } else if (!g_strcmp0(tagname, "IFRAME")) { - d = webkit_dom_html_iframe_element_get_content_document(WEBKIT_DOM_HTML_IFRAME_ELEMENT(active)); - result = get_active_element(d); - } - g_free(tagname); - - if (result) { - return result; - } - - return active; -} diff --git a/src/dom.h b/src/dom.h deleted file mode 100644 index c6a13e36..00000000 --- a/src/dom.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#ifndef _DOM_H -#define _DOM_H - -#include - -/* Types */ -#define Document WebKitDOMDocument -#define HtmlElement WebKitDOMHTMLElement -#define Element WebKitDOMElement -#define Node WebKitDOMNode -#define Event WebKitDOMEvent -#define EventTarget WebKitDOMEventTarget -#define HtmlInputElement WebKitDOMHTMLInputElement -#define HtmlTextareaElement WebKitDOMHTMLTextAreaElement - -void dom_install_focus_blur_callbacks(Document *doc); -void dom_check_auto_insert(Document *doc); -void dom_clear_focus(WebKitWebView *view); -void dom_give_focus(Element *element); -gboolean dom_focus_input(Document *doc); -gboolean dom_is_editable(Element *element); -Element *dom_get_active_element(WebKitWebView *view); -const char *dom_editable_element_get_value(Element *element); -void dom_editable_element_set_value(Element *element, const char *value); -void dom_editable_element_set_disable(Element *element, gboolean value); - -#endif /* end of include guard: _DOM_H */ diff --git a/src/events.c b/src/events.c deleted file mode 100644 index 156c35f8..00000000 --- a/src/events.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "events.h" - -/* this is only to queue GDK key events, in order to later send them if the map didn't match */ -static struct { - GSList *list; /* queue holding submitted events */ - bool processing; /* whether or not events are processing */ -} events; - -extern VbCore vb; - -static void process_event(GdkEventKey *event); - -/** - * Append an event into the queue. - */ -void queue_event(GdkEventKey *e) -{ - GdkEventKey *copy = g_memdup(e, sizeof(GdkEventKey)); - /* copy memory (otherwise event gets cleared by gdk) */ - events.list = g_slist_append(events.list, copy); -} - -/** - * Process events in the queue, sending the key events to GDK. - */ -void process_events() -{ - for (GSList *l = events.list; l != NULL; l = l->next) { - process_event((GdkEventKey*)l->data); - g_free(l->data); - events.list = g_slist_delete_link(events.list, l); - /* TODO take into account qk mapped key? */ - } -} - -static void process_event(GdkEventKey *event) -{ - if (!event) { - return; - } - - /* signal not to queue other events */ - events.processing = true; - gtk_main_do_event((GdkEvent*)event); - events.processing = false; -} - -/** - * Check if the events are currently processing (i.e. being sent to GDK - * unhandled). Provided in order to encapsulate the "events" global struct. - */ -bool is_processing_events() -{ - return events.processing; -} - -/** - * Clear the event queue by resetting the length. Provided in order to - * encapsulate the "events" global struct. - */ -void free_events() -{ - if (events.list) { - g_slist_free_full(events.list, (GDestroyNotify)g_free); - events.list = NULL; - } -} diff --git a/src/events.h b/src/events.h deleted file mode 100644 index 8ec64809..00000000 --- a/src/events.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _EVENTS_H -#define _EVENTS_H - -#include -#include -#include "main.h" -#include "map.h" - -void queue_event(GdkEventKey *e); -void process_events(); -bool is_processing_events(); -void free_events(); - -#endif /* end of include guard: _MAP_H */ diff --git a/src/ex.c b/src/ex.c index f0b00f33..b0a4480f 100644 --- a/src/ex.c +++ b/src/ex.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,36 +21,34 @@ * This file contains function to handle input editing, parsing of called ex * commands from inputbox and the ex commands. */ -#include "config.h" + +#include +#include #include -#include "main.h" -#include "ex.h" + #include "ascii.h" +#include "bookmark.h" +#include "command.h" #include "completion.h" +#include "config.h" +#include "ex.h" +#include "handler.h" #include "hints.h" -#include "command.h" #include "history.h" -#include "dom.h" +#include "main.h" +#include "map.h" #include "setting.h" -#include "util.h" -#include "bookmark.h" #include "shortcut.h" -#include "handlers.h" -#include "map.h" -#include "js.h" -#ifdef FEATURE_AUTOCMD -#include "autocmd.h" -#endif +#include "util.h" +#include "ext-proxy.h" typedef enum { -#ifdef FEATURE_AUTOCMD - EX_AUTOCMD, - EX_AUGROUP, -#endif + /* TODO add feature autocmd */ EX_BMA, EX_BMR, EX_EVAL, EX_HARDCOPY, + EX_CLEARCACHE, EX_CMAP, EX_CNOREMAP, EX_HANDADD, @@ -98,7 +96,7 @@ typedef struct { int flags; /* flags for the already parsed command */ } ExArg; -typedef VbCmdResult (*ExFunc)(const ExArg *arg); +typedef VbCmdResult (*ExFunc)(Client *c, const ExArg *arg); typedef struct { const char *name; /* full name of the command even if called abbreviated */ @@ -118,43 +116,41 @@ static struct { Phase phase; /* current parsing phase */ } info = {'\0', PHASE_START}; -static void input_activate(void); -static gboolean parse(const char **input, ExArg *arg, gboolean *nohist); +static void input_activate(Client *c); +static gboolean parse(Client *c, const char **input, ExArg *arg, gboolean *nohist); static gboolean parse_count(const char **input, ExArg *arg); -static gboolean parse_command_name(const char **input, ExArg *arg); +static gboolean parse_command_name(Client *c, const char **input, ExArg *arg); static gboolean parse_bang(const char **input, ExArg *arg); static gboolean parse_lhs(const char **input, ExArg *arg); -static gboolean parse_rhs(const char **input, ExArg *arg); +static gboolean parse_rhs(Client *c, const char **input, ExArg *arg); static void skip_whitespace(const char **input); static void free_cmdarg(ExArg *arg); -static VbCmdResult execute(const ExArg *arg); - -#ifdef FEATURE_AUTOCMD -static VbCmdResult ex_augroup(const ExArg *arg); -static VbCmdResult ex_autocmd(const ExArg *arg); -#endif -static VbCmdResult ex_bookmark(const ExArg *arg); -static VbCmdResult ex_eval(const ExArg *arg); -static VbCmdResult ex_hardcopy(const ExArg *arg); -static VbCmdResult ex_map(const ExArg *arg); -static VbCmdResult ex_unmap(const ExArg *arg); -static VbCmdResult ex_normal(const ExArg *arg); -static VbCmdResult ex_open(const ExArg *arg); +static VbCmdResult execute(Client *c, const ExArg *arg); + +static VbCmdResult ex_bookmark(Client *c, const ExArg *arg); +static VbCmdResult ex_eval(Client *c, const ExArg *arg); +static void on_eval_script_finished(GDBusProxy *proxy, GAsyncResult *result, Client *c); +static VbCmdResult ex_clearcache(Client *c, const ExArg *arg); +static VbCmdResult ex_hardcopy(Client *c, const ExArg *arg); +static VbCmdResult ex_map(Client *c, const ExArg *arg); +static VbCmdResult ex_unmap(Client *c, const ExArg *arg); +static VbCmdResult ex_normal(Client *c, const ExArg *arg); +static VbCmdResult ex_open(Client *c, const ExArg *arg); #ifdef FEATURE_QUEUE -static VbCmdResult ex_queue(const ExArg *arg); +static VbCmdResult ex_queue(Client *c, const ExArg *arg); #endif -static VbCmdResult ex_register(const ExArg *arg); -static VbCmdResult ex_quit(const ExArg *arg); -static VbCmdResult ex_save(const ExArg *arg); -static VbCmdResult ex_set(const ExArg *arg); -static VbCmdResult ex_shellcmd(const ExArg *arg); -static VbCmdResult ex_shortcut(const ExArg *arg); -static VbCmdResult ex_source(const ExArg *arg); -static VbCmdResult ex_handlers(const ExArg *arg); - -static gboolean complete(short direction); -static void completion_select(char *match); -static gboolean history(gboolean prev); +static VbCmdResult ex_register(Client *c, const ExArg *arg); +static VbCmdResult ex_quit(Client *c, const ExArg *arg); +static VbCmdResult ex_save(Client *c, const ExArg *arg); +static VbCmdResult ex_set(Client *c, const ExArg *arg); +static VbCmdResult ex_shellcmd(Client *c, const ExArg *arg); +static VbCmdResult ex_shortcut(Client *c, const ExArg *arg); +static VbCmdResult ex_source(Client *c, const ExArg *arg); +static VbCmdResult ex_handlers(Client *c, const ExArg *arg); + +static gboolean complete(Client *c, short direction); +static void completion_select(Client *c, char *match); +static gboolean history(Client *c, gboolean prev); static void history_rewind(void); /* The order of following command names is significant. If there exists @@ -165,15 +161,12 @@ static void history_rewind(void); * match. */ static ExInfo commands[] = { /* command code func flags */ -#ifdef FEATURE_AUTOCMD - {"autocmd", EX_AUTOCMD, ex_autocmd, EX_FLAG_CMD|EX_FLAG_BANG}, - {"augroup", EX_AUGROUP, ex_augroup, EX_FLAG_LHS|EX_FLAG_BANG}, -#endif {"bma", EX_BMA, ex_bookmark, EX_FLAG_RHS}, {"bmr", EX_BMR, ex_bookmark, EX_FLAG_RHS}, {"cmap", EX_CMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD}, {"cnoremap", EX_CNOREMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD}, {"cunmap", EX_CUNMAP, ex_unmap, EX_FLAG_LHS}, + {"clearcache", EX_CLEARCACHE, ex_clearcache, EX_FLAG_NONE}, {"hardcopy", EX_HARDCOPY, ex_hardcopy, EX_FLAG_NONE}, {"handler-add", EX_HANDADD, ex_handlers, EX_FLAG_RHS}, {"handler-remove", EX_HANDREM, ex_handlers, EX_FLAG_RHS}, @@ -217,42 +210,48 @@ static struct { GList *active; } exhist; -extern VbCore vb; - +extern struct Vimb vb; /** * Function called when vimb enters the command mode. */ -void ex_enter(void) +void ex_enter(Client *c) { - gtk_widget_grab_focus(GTK_WIDGET(vb.gui.input)); - dom_clear_focus(vb.gui.webview); + gtk_widget_grab_focus(GTK_WIDGET(c->input)); +#if 0 + dom_clear_focus(c->webview); +#endif } /** * Called when the command mode is left. */ -void ex_leave(void) +void ex_leave(Client *c) { - completion_clean(); - hints_clear(); + completion_clean(c); + hints_clear(c); } /** * Handles the keypress events from webview and inputbox. */ -VbResult ex_keypress(int key) +VbResult ex_keypress(Client *c, int key) { GtkTextIter start, end; - gboolean check_empty = false; - GtkTextBuffer *buffer = vb.gui.buffer; + gboolean check_empty = FALSE; + GtkTextBuffer *buffer = c->buffer; GtkTextMark *mark; VbResult res; const char *text; + if (key == CTRL('C')) { + vb_enter(c, 'n'); + return RESULT_COMPLETE; + } + /* delegate call to hint mode if this is active */ - if (vb.mode->flags & FLAG_HINTING - && RESULT_COMPLETE == hints_keypress(key)) { + if (c->mode->flags & FLAG_HINTING + && RESULT_COMPLETE == hints_keypress(c, key)) { return RESULT_COMPLETE; } @@ -263,7 +262,7 @@ VbResult ex_keypress(int key) info.phase = PHASE_REG; /* insert the register text at cursor position */ - text = vb_register_get((char)key); + text = vb_register_get(c, (char)key); if (text) { gtk_text_buffer_insert_at_cursor(buffer, text, strlen(text)); } @@ -273,29 +272,30 @@ VbResult ex_keypress(int key) res = RESULT_COMPLETE; switch (key) { case KEY_TAB: - complete(1); + complete(c, 1); break; case KEY_SHIFT_TAB: - complete(-1); + complete(c, -1); break; - case CTRL('['): - case CTRL('C'): - vb_enter('n'); - vb_set_input_text(""); + case KEY_UP: /* fall through */ + case CTRL('P'): + history(c, TRUE); break; - case KEY_CR: - input_activate(); + case KEY_DOWN: /* fall through */ + case CTRL('N'): + history(c, FALSE); break; - case KEY_UP: - history(true); + case KEY_CR: + input_activate(c); break; - case KEY_DOWN: - history(false); + case CTRL('['): + vb_enter(c, 'n'); + vb_input_set_text(c, ""); break; /* basic command line editing */ @@ -303,8 +303,8 @@ VbResult ex_keypress(int key) /* delete the last char before the cursor */ mark = gtk_text_buffer_get_insert(buffer); gtk_text_buffer_get_iter_at_mark(buffer, &start, mark); - gtk_text_buffer_backspace(buffer, &start, true, true); - check_empty = true; + gtk_text_buffer_backspace(buffer, &start, TRUE, TRUE); + check_empty = TRUE; break; case CTRL('W'): @@ -319,12 +319,12 @@ VbResult ex_keypress(int key) if (gtk_text_iter_backward_word_start(&start)) { gtk_text_buffer_delete(buffer, &start, &end); } - check_empty = true; + check_empty = TRUE; break; case CTRL('B'): /* move the cursor direct behind the prompt */ - gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(vb.state.prompt)); + gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(c->state.prompt)); gtk_text_buffer_place_cursor(buffer, &start); break; @@ -338,13 +338,13 @@ VbResult ex_keypress(int key) /* remove everything between cursor and prompt */ mark = gtk_text_buffer_get_insert(buffer); gtk_text_buffer_get_iter_at_mark(buffer, &end, mark); - gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(vb.state.prompt)); + gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(c->state.prompt)); gtk_text_buffer_delete(buffer, &start, &end); break; case CTRL('R'): info.phase = PHASE_REG; - vb.mode->flags |= FLAG_NOMAP; + c->mode->flags |= FLAG_NOMAP; res = RESULT_MORE; break; @@ -354,7 +354,7 @@ VbResult ex_keypress(int key) if (key >= 0x20 && key <= 0x7e) { gtk_text_buffer_insert_at_cursor(buffer, (char[2]){key, 0}, 1); } else { - vb.state.processed_key = false; + c->state.processed_key = FALSE; } } } @@ -364,8 +364,8 @@ VbResult ex_keypress(int key) if (check_empty) { gtk_text_buffer_get_bounds(buffer, &start, &end); if (gtk_text_iter_equal(&start, &end)) { - vb_enter('n'); - vb_set_input_text(""); + vb_enter(c, 'n'); + vb_input_set_text(c, ""); } } @@ -380,11 +380,10 @@ VbResult ex_keypress(int key) /** * Handles changes in the inputbox. */ -void ex_input_changed(const char *text) +void ex_input_changed(Client *c, const char *text) { - gboolean forward = false; GtkTextIter start, end; - GtkTextBuffer *buffer = vb.gui.buffer; + GtkTextBuffer *buffer = c->buffer; /* don't add line breaks if content is pasted from clipboard into inputbox */ if (gtk_text_buffer_get_line_count(buffer) > 1) { @@ -392,6 +391,16 @@ void ex_input_changed(const char *text) gtk_text_buffer_get_iter_at_line(buffer, &start, 0); if (gtk_text_iter_forward_to_line_end(&start)) { gtk_text_buffer_get_end_iter(buffer, &end); + + /* TODO the following line creates a GTK warning. + * ex_input_changed() is called from the "changed" event handler of + * GtkTextBuffer. Apparently it's not supported to change a text + * buffer in the changed handler!? + * + * Gtk-WARNING **: Invalid text buffer iterator: either the + * iterator is uninitialized, or the characters/pixbufs/widgets in + * the buffer have been modified since the iterator was created. + */ gtk_text_buffer_delete(buffer, &start, &end); } } @@ -399,15 +408,13 @@ void ex_input_changed(const char *text) switch (*text) { case ';': /* fall through - the modes are handled by hints_create */ case 'g': - hints_create(text); + hints_create(c, text); break; - - case '/': forward = true; /* fall through */ + case '/': /* fall through */ case '?': -#ifdef FEATURE_SEARCH_HIGHLIGHT - webkit_web_view_unmark_text_matches(vb.gui.webview); -#endif - webkit_web_view_search_text(vb.gui.webview, &text[1], false, forward, false); + if (c->config.incsearch) { + command_search(c, &((Arg){*text == '/' ? 1 : -1, (char*)text + 1}), FALSE); + } break; } } @@ -416,14 +423,14 @@ gboolean ex_fill_completion(GtkListStore *store, const char *input) { GtkTreeIter iter; ExInfo *cmd; - gboolean found = false; + gboolean found = FALSE; if (!input || *input == '\0') { for (int i = 0; i < LENGTH(commands); i++) { cmd = &commands[i]; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1); - found = true; + found = TRUE; } } else { for (int i = 0; i < LENGTH(commands); i++) { @@ -431,7 +438,7 @@ gboolean ex_fill_completion(GtkListStore *store, const char *input) if (g_str_has_prefix(cmd->name, input)) { gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1); - found = true; + found = TRUE; } } } @@ -440,61 +447,55 @@ gboolean ex_fill_completion(GtkListStore *store, const char *input) } /** - * This is called if the user typed or into the inputbox. + * Run all ex commands from a file. */ -static void input_activate(void) +VbCmdResult ex_run_file(Client *c, const char *filename) { - int count = -1; - char *text, *cmd; - VbCmdResult res; - text = vb_get_input_text(); - - /* skip leading prompt char like ':' or '/' */ - cmd = text + 1; - switch (*text) { - case '/': count = 1; /* fall through */ - case '?': - vb_enter('n'); - command_search(&((Arg){count, cmd})); - break; - - case ';': /* fall through */ - case 'g': - hints_fire(); - break; + int length, i; + char *line, **lines; + VbCmdResult res = CMD_SUCCESS; - case ':': - vb_enter('n'); - res = ex_run_string(cmd, true); - if (!(res & VB_CMD_KEEPINPUT)) { - /* clear input on success if this is not explicit ommited */ - vb_set_input_text(""); - } - break; + lines = util_get_lines(filename); + if (!lines) { + return res; + } + length = g_strv_length(lines) - 1; + for (i = 0; i < length; i++) { + line = lines[i]; + /* skip commented or empty lines */ + if (*line == '#' || !*line) { + continue; + } + if ((ex_run_string(c, line, false) & ~CMD_KEEPINPUT) == CMD_ERROR) { + res = CMD_ERROR | CMD_KEEPINPUT; + g_warning("Invalid command in %s: '%s'", filename, line); + } } - g_free(text); + g_strfreev(lines); + + return res; } -VbCmdResult ex_run_string(const char *input, gboolean enable_history) +VbCmdResult ex_run_string(Client *c, const char *input, gboolean enable_history) { /* copy to have original command for history */ const char *in = input; - gboolean nohist = false; - VbCmdResult res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; + gboolean nohist = FALSE; + VbCmdResult res = CMD_ERROR | CMD_KEEPINPUT; ExArg *arg = g_slice_new0(ExArg); arg->lhs = g_string_new(""); arg->rhs = g_string_new(""); while (in && *in) { - if (!parse(&in, arg, &nohist) || !(res = execute(arg))) { + if (!parse(c, &in, arg, &nohist) || !(res = execute(c, arg))) { break; } } if (enable_history && !nohist) { - history_add(HISTORY_COMMAND, input, NULL); - vb_register_add(':', input); + history_add(c, HISTORY_COMMAND, input, NULL); + vb_register_add(c, ':', input); } free_cmdarg(arg); @@ -503,43 +504,51 @@ VbCmdResult ex_run_string(const char *input, gboolean enable_history) } /** - * Run all ex commands in a file. + * This is called if the user typed or into the inputbox. */ -VbCmdResult ex_run_file(const char *filename) +static void input_activate(Client *c) { - char *line, **lines; - VbCmdResult res = VB_CMD_SUCCESS; + int count = -1; + char *text, *cmd; + VbCmdResult res; - lines = util_get_lines(filename); + text = vb_input_get_text(c); - if (!lines) { - return res; - } + /* skip leading prompt char like ':' or '/' */ + cmd = text + 1; + switch (*text) { + case '/': count = 1; /* fall through */ + case '?': + vb_enter(c, 'n'); - int length = g_strv_length(lines) - 1; - for (int i = 0; i < length; i++) { - line = lines[i]; - /* skip commented or empty lines */ - if (*line == '#' || !*line) { - continue; - } - if ((ex_run_string(line, false) & ~VB_CMD_KEEPINPUT) == VB_CMD_ERROR) { - res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; - g_warning("Invalid command in %s: '%s'", filename, line); - } - } - g_strfreev(lines); + command_search(c, &((Arg){count, strlen(cmd) ? cmd : NULL}), TRUE); + break; - return res; + case ';': /* fall through */ + case 'g': + /* TODO fire hints */ + break; + + case ':': + vb_enter(c, 'n'); + res = ex_run_string(c, cmd, TRUE); + if (!(res & CMD_KEEPINPUT)) { + /* clear input on success if this is not explicit ommited */ + vb_input_set_text(c, ""); + } + break; + + } + g_free(text); } /** * Parses given input string into given ExArg pointer. */ -static gboolean parse(const char **input, ExArg *arg, gboolean *nohist) +static gboolean parse(Client *c, const char **input, ExArg *arg, gboolean *nohist) { if (!*input || !**input) { - return false; + return FALSE; } /* truncate string from potentially previous run */ @@ -551,13 +560,13 @@ static gboolean parse(const char **input, ExArg *arg, gboolean *nohist) (*input)++; /* Command started with additional ':' or whitespce - don't record it * in history or registry. */ - *nohist = true; + *nohist = TRUE; } parse_count(input, arg); skip_whitespace(input); - if (!parse_command_name(input, arg)) { - return false; + if (!parse_command_name(c, input, arg)) { + return FALSE; } /* parse the bang if this is allowed */ @@ -572,13 +581,13 @@ static gboolean parse(const char **input, ExArg *arg, gboolean *nohist) } /* parse the rhs if this is available */ skip_whitespace(input); - parse_rhs(input, arg); + parse_rhs(c, input, arg); if (**input) { (*input)++; } - return true; + return TRUE; } /** @@ -594,13 +603,13 @@ static gboolean parse_count(const char **input, ExArg *arg) (*input)++; } while (VB_IS_DIGIT(**input)); } - return true; + return TRUE; } /** * Parse the command name from given input. */ -static gboolean parse_command_name(const char **input, ExArg *arg) +static gboolean parse_command_name(Client *c, const char **input, ExArg *arg) { int len = 0; int first = 0; /* number of first found command */ @@ -638,8 +647,8 @@ static gboolean parse_command_name(const char **input, ExArg *arg) } cmd[len] = '\0'; - vb_echo(VB_MSG_ERROR, true, "Unknown command: %s", cmd); - return false; + vb_echo(c, MSG_ERROR, TRUE, "Unknown command: %s", cmd); + return FALSE; } arg->idx = first; @@ -647,7 +656,7 @@ static gboolean parse_command_name(const char **input, ExArg *arg) arg->name = commands[first].name; arg->flags = commands[first].flags; - return true; + return TRUE; } /** @@ -656,10 +665,10 @@ static gboolean parse_command_name(const char **input, ExArg *arg) static gboolean parse_bang(const char **input, ExArg *arg) { if (*input && **input == '!') { - arg->bang = true; + arg->bang = TRUE; (*input)++; } - return true; + return TRUE; } /** @@ -670,7 +679,7 @@ static gboolean parse_lhs(const char **input, ExArg *arg) char quote = '\\'; if (!*input || !**input) { - return false; + return FALSE; } /* get the char until the next none escaped whitespace and save it into @@ -683,9 +692,8 @@ static gboolean parse_lhs(const char **input, ExArg *arg) if (!*input) { /* if input ends here - use only the backslash */ g_string_append_c(arg->lhs, quote); - } else if (**input == ' ' || **input == quote) { - /* Escaped whitespace becomes only whitespace and escaped '\' - * becomes '\' */ + } else if (**input == ' ') { + /* escaped whitespace becomes only whitespace */ g_string_append_c(arg->lhs, **input); } else { /* put escape char and next char into the result string */ @@ -698,7 +706,7 @@ static gboolean parse_lhs(const char **input, ExArg *arg) } (*input)++; } - return true; + return TRUE; } /** @@ -706,7 +714,7 @@ static gboolean parse_lhs(const char **input, ExArg *arg) * command can contain any char accept of the newline, else the right hand * side end on the first none escaped | or newline. */ -static gboolean parse_rhs(const char **input, ExArg *arg) +static gboolean parse_rhs(Client *c, const char **input, ExArg *arg) { int expflags, flags; gboolean cmdlist; @@ -716,7 +724,7 @@ static gboolean parse_rhs(const char **input, ExArg *arg) if ((arg->flags & (EX_FLAG_RHS|EX_FLAG_CMD)) == 0 || !*input || !**input ) { - return false; + return FALSE; } cmdlist = (arg->flags & EX_FLAG_CMD) != 0; @@ -729,7 +737,7 @@ static gboolean parse_rhs(const char **input, ExArg *arg) * EX_FLAG_CMD is not set also on | */ while (**input && **input != '\n' && (cmdlist || **input != '|')) { /* check for expansion placeholder */ - util_parse_expansion(input, arg->rhs, flags, "|\\"); + util_parse_expansion(c, input, arg->rhs, flags, "|\\"); if (VB_IS_SEPARATOR(**input)) { /* add tilde expansion for next loop needs to be first char or to @@ -741,15 +749,15 @@ static gboolean parse_rhs(const char **input, ExArg *arg) } (*input)++; } - return true; + return TRUE; } /** * Executes the command given by ExArg. */ -static VbCmdResult execute(const ExArg *arg) +static VbCmdResult execute(Client *c, const ExArg *arg) { - return (commands[arg->idx].func)(arg); + return (commands[arg->idx].func)(c, arg); } static void skip_whitespace(const char **input) @@ -762,129 +770,133 @@ static void skip_whitespace(const char **input) static void free_cmdarg(ExArg *arg) { if (arg->lhs) { - g_string_free(arg->lhs, true); + g_string_free(arg->lhs, TRUE); } if (arg->rhs) { - g_string_free(arg->rhs, true); + g_string_free(arg->rhs, TRUE); } g_slice_free(ExArg, arg); } -#ifdef FEATURE_AUTOCMD -static VbCmdResult ex_augroup(const ExArg *arg) -{ - return autocmd_augroup(arg->lhs->str, arg->bang) ? VB_CMD_SUCCESS : VB_CMD_ERROR; -} - -static VbCmdResult ex_autocmd(const ExArg *arg) -{ - return autocmd_add(arg->rhs->str, arg->bang) ? VB_CMD_SUCCESS : VB_CMD_ERROR; -} -#endif - -static VbCmdResult ex_bookmark(const ExArg *arg) +static VbCmdResult ex_bookmark(Client *c, const ExArg *arg) { if (arg->code == EX_BMR) { - if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : vb.state.uri)) { - vb_echo_force(VB_MSG_NORMAL, true, " Bookmark removed"); + if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : c->state.uri)) { + vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark removed"); - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + return CMD_SUCCESS | CMD_KEEPINPUT; } - } else if (bookmark_add(vb.state.uri, webkit_web_view_get_title(vb.gui.webview), arg->rhs->str)) { - vb_echo_force(VB_MSG_NORMAL, true, " Bookmark added"); + } else if (bookmark_add(c->state.uri, c->state.title, arg->rhs->str)) { + vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark added"); - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + return CMD_SUCCESS | CMD_KEEPINPUT; } - return VB_CMD_ERROR; + return CMD_ERROR; } -static VbCmdResult ex_eval(const ExArg *arg) +static VbCmdResult ex_eval(Client *c, const ExArg *arg) { - gboolean success; - char *value = NULL; - VbCmdResult res = VB_CMD_SUCCESS; - - if (!arg->rhs->len) { - return false; + /* Called as :eval! - don't print to inputbox. */ + if (arg->bang) { + ext_proxy_eval_script(c, arg->rhs->str, NULL); + } else { + ext_proxy_eval_script(c, arg->rhs->str, (GAsyncReadyCallback)on_eval_script_finished); } - success = js_eval( - webkit_web_frame_get_global_context(webkit_web_view_get_main_frame(vb.gui.webview)), - arg->rhs->str, NULL, &value - ); - if (!arg->bang) { + return CMD_SUCCESS; +} + +static void on_eval_script_finished(GDBusProxy *proxy, GAsyncResult *result, Client *c) +{ + gboolean success = FALSE; + char *string = NULL; + + GVariant *return_value = g_dbus_proxy_call_finish(proxy, result, NULL); + if (return_value) { + g_variant_get(return_value, "(bs)", &success, &string); if (success) { - vb_echo(VB_MSG_NORMAL, false, "%s", value); - res = VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + vb_echo(c, MSG_NORMAL, FALSE, "%s", string); } else { - vb_echo(VB_MSG_ERROR, true, "%s", value); - res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; + vb_echo(c, MSG_ERROR, TRUE, "%s", string); } + } else { + vb_echo(c, MSG_ERROR, TRUE, ""); } - g_free(value); +} - return res; +static VbCmdResult ex_clearcache(Client *c, const ExArg *arg) +{ + webkit_web_context_clear_cache(webkit_web_context_get_default()); + return CMD_SUCCESS; } -static VbCmdResult ex_hardcopy(const ExArg *arg) +static VbCmdResult ex_hardcopy(Client *c, const ExArg *arg) { - webkit_web_frame_print(webkit_web_view_get_main_frame(vb.gui.webview)); - return VB_CMD_SUCCESS; + WebKitPrintOperation *op = webkit_print_operation_new(c->webview); + GtkPrintSettings *settings = gtk_print_settings_new(); + + gtk_print_settings_set(settings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, c->state.title); + webkit_print_operation_set_print_settings(op, settings); + webkit_print_operation_run_dialog(op, NULL); + g_object_unref(op); + g_object_unref(settings); + + return CMD_SUCCESS; } -static VbCmdResult ex_map(const ExArg *arg) +static VbCmdResult ex_map(Client *c, const ExArg *arg) { if (!arg->lhs->len || !arg->rhs->len) { - return VB_CMD_ERROR; + return CMD_ERROR; } /* instead of using the EX_XMAP constants we use the first char of the * command name as mode and the second to determine if noremap is used */ - map_insert(arg->lhs->str, arg->rhs->str, arg->name[0], arg->name[1] != 'n'); + map_insert(c, arg->lhs->str, arg->rhs->str, arg->name[0], arg->name[1] != 'n'); - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -static VbCmdResult ex_unmap(const ExArg *arg) +static VbCmdResult ex_unmap(Client *c, const ExArg *arg) { char *lhs; if (!arg->lhs->len) { - return VB_CMD_ERROR; + return CMD_ERROR; } lhs = arg->lhs->str; if (arg->code == EX_NUNMAP) { - map_delete(lhs, 'n'); + map_delete(c, lhs, 'n'); } else if (arg->code == EX_CUNMAP) { - map_delete(lhs, 'c'); + map_delete(c, lhs, 'c'); } else { - map_delete(lhs, 'i'); + map_delete(c, lhs, 'i'); } - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -static VbCmdResult ex_normal(const ExArg *arg) +static VbCmdResult ex_normal(Client *c, const ExArg *arg) { - vb_enter('n'); + vb_enter(c, 'n'); /* if called with bang - don't apply mapping */ - map_handle_string(arg->rhs->str, !arg->bang); + map_handle_string(c, arg->rhs->str, !arg->bang); - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + return CMD_SUCCESS | CMD_KEEPINPUT; } -static VbCmdResult ex_open(const ExArg *arg) +static VbCmdResult ex_open(Client *c, const ExArg *arg) { if (arg->code == EX_TABOPEN) { - return vb_load_uri(&((Arg){VB_TARGET_NEW, arg->rhs->str})) ? VB_CMD_SUCCESS : VB_CMD_ERROR; + return vb_load_uri(c, &((Arg){TARGET_NEW, arg->rhs->str})) ? CMD_SUCCESS : CMD_ERROR; } - return vb_load_uri(&((Arg){VB_TARGET_CURRENT, arg->rhs->str})) ? VB_CMD_SUCCESS :VB_CMD_ERROR; + return vb_load_uri(c, &((Arg){TARGET_CURRENT, arg->rhs->str})) ? CMD_SUCCESS :CMD_ERROR; } #ifdef FEATURE_QUEUE -static VbCmdResult ex_queue(const ExArg *arg) +static VbCmdResult ex_queue(Client *c, const ExArg *arg) { Arg a = {0}; @@ -906,7 +918,7 @@ static VbCmdResult ex_queue(const ExArg *arg) break; default: - return VB_CMD_ERROR; + return CMD_ERROR; } /* if no argument is found in rhs, keep the uri in arg null to force @@ -915,57 +927,57 @@ static VbCmdResult ex_queue(const ExArg *arg) a.s = arg->rhs->str; } - return command_queue(&a) - ? VB_CMD_SUCCESS | VB_CMD_KEEPINPUT - : VB_CMD_ERROR | VB_CMD_KEEPINPUT; + return command_queue(c, &a) + ? CMD_SUCCESS | CMD_KEEPINPUT + : CMD_ERROR | CMD_KEEPINPUT; } #endif /** * Show the contents of the registers :reg. */ -static VbCmdResult ex_register(const ExArg *arg) +static VbCmdResult ex_register(Client *c, const ExArg *arg) { int idx; char *reg; - const char *regchars = VB_REG_CHARS; + const char *regchars = REG_CHARS; GString *str = g_string_new("-- Register --"); - for (idx = 0; idx < VB_REG_SIZE; idx++) { + for (idx = 0; idx < REG_SIZE; idx++) { /* show only filled registers */ - if (vb.state.reg[idx]) { + if (c->state.reg[idx]) { /* replace all newlines */ - reg = util_str_replace("\n", "^J", vb.state.reg[idx]); + reg = util_str_replace("\n", "^J", c->state.reg[idx]); g_string_append_printf(str, "\n\"%c %s", regchars[idx], reg); g_free(reg); } } - vb_echo(VB_MSG_NORMAL, false, "%s", str->str); - g_string_free(str, true); + vb_echo(c, MSG_NORMAL, FALSE, "%s", str->str); + g_string_free(str, TRUE); - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + return CMD_SUCCESS | CMD_KEEPINPUT; } -static VbCmdResult ex_quit(const ExArg *arg) +static VbCmdResult ex_quit(Client *c, const ExArg *arg) { - vb_quit(arg->bang); - return VB_CMD_SUCCESS; + vb_quit(c, arg->bang); + return CMD_SUCCESS; } -static VbCmdResult ex_save(const ExArg *arg) +static VbCmdResult ex_save(Client *c, const ExArg *arg) { - return command_save(&((Arg){COMMAND_SAVE_CURRENT, arg->rhs->str})) - ? VB_CMD_SUCCESS | VB_CMD_KEEPINPUT - : VB_CMD_ERROR | VB_CMD_KEEPINPUT; + return command_save(c, &((Arg){COMMAND_SAVE_CURRENT, arg->rhs->str})) + ? CMD_SUCCESS | CMD_KEEPINPUT + : CMD_ERROR | CMD_KEEPINPUT; } -static VbCmdResult ex_set(const ExArg *arg) +static VbCmdResult ex_set(Client *c, const ExArg *arg) { char *param = NULL; if (!arg->rhs->len) { - return false; + return FALSE; } /* split the input string into parameter and value part */ @@ -974,13 +986,13 @@ static VbCmdResult ex_set(const ExArg *arg) g_strstrip(arg->rhs->str); g_strstrip(param); - return setting_run(arg->rhs->str, param); + return setting_run(c, arg->rhs->str, param); } - return setting_run(arg->rhs->str, NULL); + return setting_run(c, arg->rhs->str, NULL); } -static VbCmdResult ex_shellcmd(const ExArg *arg) +static VbCmdResult ex_shellcmd(Client *c, const ExArg *arg) { int status; char *stdOut = NULL, *stdErr = NULL; @@ -988,134 +1000,145 @@ static VbCmdResult ex_shellcmd(const ExArg *arg) GError *error = NULL; if (!*arg->rhs->str) { - return VB_CMD_ERROR; + return CMD_ERROR; } if (arg->bang) { if (!g_spawn_command_line_async(arg->rhs->str, &error)) { g_warning("Can't run '%s': %s", arg->rhs->str, error->message); g_clear_error(&error); - res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; + res = CMD_ERROR | CMD_KEEPINPUT; } else { - res = VB_CMD_SUCCESS; + res = CMD_SUCCESS; } } else { if (!g_spawn_command_line_sync(arg->rhs->str, &stdOut, &stdErr, &status, &error)) { g_warning("Can't run '%s': %s", arg->rhs->str, error->message); g_clear_error(&error); - res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; + res = CMD_ERROR | CMD_KEEPINPUT; } else { /* the commands success depends not on the return code of the * called shell command, so we know the result already here */ - res = VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + res = CMD_SUCCESS | CMD_KEEPINPUT; } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { - vb_echo(VB_MSG_NORMAL, false, "%s", stdOut); + vb_echo(c, MSG_NORMAL, FALSE, "%s", stdOut); } else { - vb_echo(VB_MSG_ERROR, true, "[%d] %s", WEXITSTATUS(status), stdErr); + vb_echo(c, MSG_ERROR, TRUE, "[%d] %s", WEXITSTATUS(status), stdErr); } } return res; } -static VbCmdResult ex_source(const ExArg *arg) -{ - return ex_run_file(arg->rhs->str); -} - -static VbCmdResult ex_handlers(const ExArg *arg) +static VbCmdResult ex_handlers(Client *c, const ExArg *arg) { char *p; - gboolean success = false; + gboolean res = FALSE; switch (arg->code) { case EX_HANDADD: if (arg->rhs->len && (p = strchr(arg->rhs->str, '='))) { *p++ = '\0'; - success = handler_add(arg->rhs->str, p); + res = handler_add(c, arg->rhs->str, p); } break; case EX_HANDREM: - success = handler_remove(arg->rhs->str); + res = handler_remove(c, arg->rhs->str); break; default: break; } - return success ? VB_CMD_SUCCESS : VB_CMD_ERROR; + return res ? CMD_SUCCESS : CMD_ERROR; } -static VbCmdResult ex_shortcut(const ExArg *arg) +static VbCmdResult ex_shortcut(Client *c, const ExArg *arg) { - char *p; + gchar *uri; gboolean success = false; - /* TODO allow to set shortcuts with set command like ':set - * shortcut[name]=http://donain.tld/?q=$0' */ - switch (arg->code) { + if (!c) { + return CMD_ERROR; + } + + if (!arg->rhs || !arg->rhs->str || !*arg->rhs->str) { + return CMD_ERROR; + } + + switch(arg->code) { case EX_SCA: - if (arg->rhs->len && (p = strchr(arg->rhs->str, '='))) { - *p++ = '\0'; - success = shortcut_add(arg->rhs->str, p); + if ((uri = strchr(arg->rhs->str, '='))) { + *uri++ = '\0'; /* devide key and uri */ + g_strstrip(arg->rhs->str); + g_strstrip(uri); + success = shortcut_add(c, arg->rhs->str, uri); } break; case EX_SCR: - success = shortcut_remove(arg->rhs->str); + g_strstrip(arg->rhs->str); + success = shortcut_remove(c, arg->rhs->str); break; case EX_SCD: - success = shortcut_set_default(arg->rhs->str); + g_strstrip(arg->rhs->str); + success = shortcut_set_default(c, arg->rhs->str); break; default: break; } - return success ? VB_CMD_SUCCESS : VB_CMD_ERROR; + + return success ? CMD_SUCCESS : CMD_ERROR; +} + +static VbCmdResult ex_source(Client *c, const ExArg *arg) +{ + return ex_run_file(c, arg->rhs->str); } /** * Manage the generation and stepping through completions. * This function prepared some prefix and suffix string that are required to - * put hte matched data back to inputbox, and prepares the tree list store + * put the matched data back to inputbox, and prepares the tree list store * model containing matched values. */ -static gboolean complete(short direction) +static gboolean complete(Client *c, short direction) { char *input; /* input read from inputbox */ const char *in; /* pointer to input that we move */ - gboolean found = false; - gboolean sort = true; + gboolean found = FALSE; + gboolean sort = TRUE; GtkListStore *store; /* if direction is 0 stop the completion */ if (!direction) { - completion_clean(); + completion_clean(c); - return true; + return TRUE; } - input = vb_get_input_text(); + input = vb_input_get_text(c); /* if completion was already started move to the next/prev item */ - if (vb.mode->flags & FLAG_COMPLETION) { + if (c->mode->flags & FLAG_COMPLETION) { if (excomp.current && !strcmp(input, excomp.current)) { /* Step through the next/prev completion item. */ - if (!completion_next(direction < 0)) { + if (!completion_next(c, direction < 0)) { /* If we stepped over the last/first item - put the initial content in */ - completion_select(excomp.token); + completion_select(c, excomp.token); } g_free(input); - return true; + return TRUE; } /* if current input isn't the content of the completion item, stop * completion and start it after that again */ - completion_clean(); + completion_clean(c); } store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING); @@ -1123,21 +1146,22 @@ static gboolean complete(short direction) in = (const char*)input; if (*in == ':') { const char *before_cmdname; - /* skipt the first : */ - in++; + /* skip leading ':' and whitespace */ + while (*in && (*in == ':' || VB_IS_SPACE(*in))) { + in++; + } ExArg *arg = g_slice_new0(ExArg); - skip_whitespace(&in); parse_count(&in, arg); /* Backup the current pointer so that we can restore the input pointer - * if tha command name parsing fails. */ + * if the command name parsing fails. */ before_cmdname = in; - /* Do ex command specific completion if the comman is recognized and + /* Do ex command specific completion if the command is recognized and * there is a space after the command and the optional '!' bang. */ - if (parse_command_name(&in, arg) && parse_bang(&in, arg) && VB_IS_SPACE(*in)) { + if (parse_command_name(c, &in, arg) && parse_bang(&in, arg) && VB_IS_SPACE(*in)) { const char *token; /* Get only the last word of input string for the completion for * bookmark tag completion. */ @@ -1165,43 +1189,34 @@ static gboolean complete(short direction) switch (arg->code) { case EX_OPEN: case EX_TABOPEN: + sort = FALSE; if (*token == '!') { found = bookmark_fill_completion(store, token + 1); } else { found = history_fill_completion(store, HISTORY_URL, token); } - sort = false; break; case EX_SET: - found = setting_fill_completion(store, token); + found = setting_fill_completion(c, store, token); break; case EX_BMA: found = bookmark_fill_tag_completion(store, token); break; - case EX_SCR: - found = shortcut_fill_completion(store, token); + case EX_SCR: /* Fallthrough */ + case EX_SCD: + found = shortcut_fill_completion(c, store, token); break; case EX_HANDREM: - found = handler_fill_completion(store, token); - break; - -#ifdef FEATURE_AUTOCMD - case EX_AUTOCMD: - found = autocmd_fill_event_completion(store, token); + found = handler_fill_completion(c, store, token); break; - case EX_AUGROUP: - found = autocmd_fill_group_completion(store, token); - break; -#endif - - case EX_SAVE: + case EX_SAVE: /* Fallthrough */ case EX_SOURCE: - found = util_filename_fill_completion(store, token); + found = util_filename_fill_completion(c, store, token); break; default: @@ -1217,9 +1232,9 @@ static gboolean complete(short direction) excomp.count = arg->count; if (ex_fill_completion(store, in)) { - OVERWRITE_STRING(excomp.prefix, ":"); - found = true; - sort = false; + /* Use all the input before the command as prefix. */ + OVERWRITE_NSTRING(excomp.prefix, input, in - input); + found = TRUE; } } free_cmdarg(arg); @@ -1227,7 +1242,8 @@ static gboolean complete(short direction) if (history_fill_completion(store, HISTORY_SEARCH, in + 1)) { OVERWRITE_STRING(excomp.token, in + 1); OVERWRITE_NSTRING(excomp.prefix, in, 1); - found = true; + found = TRUE; + sort = FALSE; } } @@ -1239,11 +1255,11 @@ static gboolean complete(short direction) } if (found) { - completion_create(GTK_TREE_MODEL(store), completion_select, direction < 0); + completion_create(c, GTK_TREE_MODEL(store), completion_select, direction < 0); } g_free(input); - return true; + return TRUE; } /** @@ -1251,7 +1267,7 @@ static gboolean complete(short direction) * matched item according with previously saved prefix and command name to the * inputbox. */ -static void completion_select(char *match) +static void completion_select(Client *c, char *match) { OVERWRITE_STRING(excomp.current, NULL); @@ -1260,15 +1276,15 @@ static void completion_select(char *match) } else { excomp.current = g_strconcat(excomp.prefix, match, NULL); } - vb_set_input_text(excomp.current); + vb_input_set_text(c, excomp.current); } -static gboolean history(gboolean prev) +static gboolean history(Client *c, gboolean prev) { char *input; GList *new = NULL; - input = vb_get_input_text(); + input = vb_input_get_text(c); if (exhist.active) { /* calculate the actual content of the inpubox from history data, if * the theoretical content and the actual given input are different @@ -1293,11 +1309,11 @@ static gboolean history(gboolean prev) /* check which type of history we should use */ if (*in == ':') { - type = VB_INPUT_COMMAND; + type = INPUT_COMMAND; } else if (*in == '/' || *in == '?') { /* the history does not distinguish between forward and backward * search, so we don't need the backward search here too */ - type = VB_INPUT_SEARCH_FORWARD; + type = INPUT_SEARCH_FORWARD; } else { goto failed; } @@ -1317,14 +1333,18 @@ static gboolean history(gboolean prev) exhist.active = new; } - vb_echo_force(VB_MSG_NORMAL, false, "%s%s", exhist.prefix, (char*)exhist.active->data); + if (!exhist.active) { + goto failed; + } + + vb_echo_force(c, MSG_NORMAL, FALSE, "%s%s", exhist.prefix, (char*)exhist.active->data); g_free(input); - return true; + return TRUE; failed: g_free(input); - return false; + return FALSE; } static void history_rewind(void) diff --git a/src/ex.h b/src/ex.h index b18c6df9..44ac11f1 100644 --- a/src/ex.h +++ b/src/ex.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,12 +23,12 @@ #include "config.h" #include "main.h" -void ex_enter(void); -void ex_leave(void); -VbResult ex_keypress(int key); -void ex_input_changed(const char *text); +void ex_enter(Client *c); +void ex_leave(Client *c); +VbResult ex_keypress(Client *c, int key); +void ex_input_changed(Client *c, const char *text); gboolean ex_fill_completion(GtkListStore *store, const char *input); -VbCmdResult ex_run_string(const char *input, gboolean enable_history); -VbCmdResult ex_run_file(const char *filename); +VbCmdResult ex_run_file(Client *c, const char *filename); +VbCmdResult ex_run_string(Client *c, const char *input, gboolean enable_history); #endif /* end of include guard: _EX_H */ diff --git a/src/ext-proxy.c b/src/ext-proxy.c new file mode 100644 index 00000000..6b9c9578 --- /dev/null +++ b/src/ext-proxy.c @@ -0,0 +1,293 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include + +#include "ext-proxy.h" +#include "main.h" +#include "webextension/ext-main.h" + +static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, + GIOStream *stream, GCredentials *credentials, gpointer data); +static gboolean on_new_connection(GDBusServer *server, + GDBusConnection *connection, gpointer data); +static void on_connection_close(GDBusConnection *connection, gboolean + remote_peer_vanished, GError *error, gpointer data); +static void on_proxy_created (GDBusProxy *proxy, GAsyncResult *result, + gpointer data); +static void on_vertical_scroll(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data); +static void dbus_call(Client *c, const char *method, GVariant *param, + GAsyncReadyCallback callback); +static GVariant *dbus_call_sync(Client *c, const char *method, GVariant + *param); +static void on_web_extension_page_created(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data); + +/* TODO we need potentially multiple proxies. Because a single instance of + * vimb may hold multiple clients which may use more than one webprocess and + * therefore multiple webextension instances. */ +extern struct Vimb vb; +static GDBusServer *dbusserver; + + +/** + * Initialize the dbus proxy by watching for appearing dbus name. + */ +const char *ext_proxy_init(void) +{ + char *address, *guid; + GDBusAuthObserver *observer; + GError *error = NULL; + + address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir()); + guid = g_dbus_generate_guid(); + observer = g_dbus_auth_observer_new(); + + g_signal_connect(observer, "authorize-authenticated-peer", + G_CALLBACK(on_authorize_authenticated_peer), NULL); + + /* Use sync call because server must be starte before the web extension + * attempt to connect */ + dbusserver = g_dbus_server_new_sync(address, G_DBUS_SERVER_FLAGS_NONE, + guid, observer, NULL, &error); + + if (error) { + g_warning("Failed to start web extension server on %s: %s", address, error->message); + g_error_free(error); + goto out; + } + + g_signal_connect(dbusserver, "new-connection", G_CALLBACK(on_new_connection), NULL); + g_dbus_server_start(dbusserver); + +out: + g_free(address); + g_free(guid); + g_object_unref(observer); + + return g_dbus_server_get_client_address(dbusserver); +} + +/* TODO move this to a lib or somthing that can be used from ui and web + * process together */ +static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, + GIOStream *stream, GCredentials *credentials, gpointer data) +{ + static GCredentials *own_credentials = NULL; + GError *error = NULL; + + if (!own_credentials) { + own_credentials = g_credentials_new(); + } + + if (credentials && g_credentials_is_same_user(credentials, own_credentials, &error)) { + return TRUE; + } + + if (error) { + g_warning("Failed to authorize web extension connection: %s", error->message); + g_error_free(error); + } + + return FALSE; +} + +static gboolean on_new_connection(GDBusServer *server, + GDBusConnection *connection, gpointer data) +{ + /* Create dbus proxy. */ + g_return_val_if_fail(G_IS_DBUS_CONNECTION(connection), FALSE); + + g_signal_connect(connection, "closed", G_CALLBACK(on_connection_close), NULL); + + g_dbus_proxy_new(connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES|G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + NULL, + VB_WEBEXTENSION_OBJECT_PATH, + VB_WEBEXTENSION_INTERFACE, + NULL, + (GAsyncReadyCallback)on_proxy_created, + NULL); + + return TRUE; +} + +static void on_connection_close(GDBusConnection *connection, gboolean + remote_peer_vanished, GError *error, gpointer data) +{ + if (error && !remote_peer_vanished) { + g_warning("Unexpected lost connection to web extension: %s", error->message); + } +} + +static void on_proxy_created(GDBusProxy *new_proxy, GAsyncResult *result, + gpointer data) +{ + GError *error = NULL; + GDBusProxy *proxy; + GDBusConnection *connection; + + proxy = g_dbus_proxy_new_finish(result, &error); + if (!proxy) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Error creating web extension proxy: %s", error->message); + } + g_error_free(error); + + /* TODO cancel the dbus connection - use cancelable */ + return; + } + + connection = g_dbus_proxy_get_connection(proxy); + g_dbus_connection_signal_subscribe(connection, NULL, + VB_WEBEXTENSION_INTERFACE, "PageCreated", + VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)on_web_extension_page_created, proxy, + NULL); +} + +/** + * Listen to the VerticalScroll signal of the webextension and set the scroll + * percent value on the client to update the statusbar. + */ +static void on_vertical_scroll(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data) +{ + glong max; + guint percent; + guint64 pageid; + Client *c; + + g_variant_get(parameters, "(ttq)", &pageid, &max, &percent); + c = vb_get_client_for_page_id(pageid); + if (c) { + c->state.scroll_max = max; + c->state.scroll_percent = percent; + } + + vb_statusbar_update(c); +} + +void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback) +{ + if (callback) { + dbus_call(c, "EvalJs", g_variant_new("(ts)", c->page_id, js), callback); + } else { + dbus_call(c, "EvalJsNoResult", g_variant_new("(ts)", c->page_id, js), NULL); + } +} + +GVariant *ext_proxy_eval_script_sync(Client *c, char *js) +{ + return dbus_call_sync(c, "EvalJs", g_variant_new("(ts)", c->page_id, js)); +} + +/** + * Request the web extension to focus first editable element. + * Returns whether an focusable element was found or not. + */ +void ext_proxy_focus_input(Client *c) +{ + dbus_call(c, "FocusInput", g_variant_new("(t)", c->page_id), NULL); +} + +/** + * Send the headers string to the webextension. + */ +void ext_proxy_set_header(Client *c, const char *headers) +{ + dbus_call(c, "SetHeaderSetting", g_variant_new("(s)", headers), NULL); +} + +/** + * Call a dbus method. + */ +static void dbus_call(Client *c, const char *method, GVariant *param, + GAsyncReadyCallback callback) +{ + /* TODO add function to queue calls until the proxy connection is + * established */ + if (!c->dbusproxy) { + return; + } + g_dbus_proxy_call(c->dbusproxy, method, param, G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, c); +} + +/** + * Call a dbus method syncron. + */ +static GVariant *dbus_call_sync(Client *c, const char *method, GVariant *param) +{ + GVariant *result = NULL; + GError *error = NULL; + + if (!c->dbusproxy) { + return NULL; + } + + result = g_dbus_proxy_call_sync(c->dbusproxy, method, param, + G_DBUS_CALL_FLAGS_NONE, 500, NULL, &error); + + if (error) { + g_warning("Failed dbus method %s: %s", method, error->message); + g_error_free(error); + } + + return result; +} + +/** + * Called when the web context created the page. + * + * Find the right client to the page id returned from the webextension. + * Add the proxy connection to the client for later calls. + */ +static void on_web_extension_page_created(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data) +{ + Client *c; + guint64 pageid; + + g_variant_get(parameters, "(t)", &pageid); + + /* Search for the client with the same page id as returned by the + * webextension. */ + c = vb_get_client_for_page_id(pageid); + if (c) { + /* Set the dbus proxy on the right client based on page id. */ + c->dbusproxy = (GDBusProxy*)data; + + /* Subscribe to dbus signals here. */ + g_dbus_connection_signal_subscribe(connection, NULL, + VB_WEBEXTENSION_INTERFACE, "VerticalScroll", + VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)on_vertical_scroll, NULL, NULL); + } +} diff --git a/src/cookiejar.h b/src/ext-proxy.h similarity index 55% rename from src/cookiejar.h rename to src/ext-proxy.h index 250ba703..c5f41d1e 100644 --- a/src/cookiejar.h +++ b/src/ext-proxy.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,27 +17,15 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#ifdef FEATURE_COOKIE +#ifndef _EXT_PROXY_H +#define _EXT_PROXY_H -#ifndef _COOKIEJAR_H -#define _COOKIEJAR_H +#include "main.h" -#define COOKIEJAR_TYPE (cookiejar_get_type()) -#define COOKIEJAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), COOKIEJAR_TYPE, CookieJar)) +const char *ext_proxy_init(void); +void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback); +GVariant *ext_proxy_eval_script_sync(Client *c, char *js); +void ext_proxy_focus_input(Client *c); +void ext_proxy_set_header(Client *c, const char *headers); - -typedef struct { - SoupCookieJarText parent_instance; - int lock; -} CookieJar; - -typedef struct { - SoupCookieJarTextClass parent_class; -} CookieJarClass; - -GType cookiejar_get_type(void); -SoupCookieJar *cookiejar_new(const char *file, gboolean ro); - -#endif /* end of include guard: _COOKIEJAR_H */ -#endif +#endif /* end of include guard: _EXT_PROXY_H */ diff --git a/src/handlers.c b/src/handler.c similarity index 54% rename from src/handlers.c rename to src/handler.c index 12da9b72..6cde8888 100644 --- a/src/handlers.c +++ b/src/handler.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,80 +17,83 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include + #include "main.h" -#include "handlers.h" +#include "handler.h" #include "util.h" -static GHashTable *handlers = NULL; - -static char *handler_lookup(const char *uri); +extern struct Vimb vb; +static char *handler_lookup(Client *c, const char *uri); -void handlers_init(void) +void handler_init(Client *c) { - handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + c->handlers.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } -void handlers_cleanup(void) +void handler_cleanup(Client *c) { - if (handlers) { - g_hash_table_destroy(handlers); + if (c->handlers.table) { + g_hash_table_destroy(c->handlers.table); + c->handlers.table = NULL; } } -gboolean handler_add(const char *key, const char *cmd) +gboolean handler_add(Client *c, const char *key, const char *cmd) { - g_hash_table_insert(handlers, g_strdup(key), g_strdup(cmd)); + g_hash_table_insert(c->handlers.table, g_strdup(key), g_strdup(cmd)); return true; } -gboolean handler_remove(const char *key) +gboolean handler_remove(Client *c, const char *key) { - return g_hash_table_remove(handlers, key); + return g_hash_table_remove(c->handlers.table, key); } -gboolean handle_uri(const char *uri) +gboolean handler_handle_uri(Client *c, const char *uri) { char *handler, *cmd; GError *error = NULL; - gboolean result; + gboolean res; - if (!(handler = handler_lookup(uri))) { - return false; + if (!(handler = handler_lookup(c, uri))) { + return FALSE; } cmd = g_strdup_printf(handler, uri); if (!g_spawn_command_line_async(cmd, &error)) { g_warning("Can't run '%s': %s", cmd, error->message); g_clear_error(&error); - result = false; + res = FALSE; } else { - result = true; + res = TRUE; } g_free(cmd); - return result; + return res; } -gboolean handler_fill_completion(GtkListStore *store, const char *input) +gboolean handler_fill_completion(Client *c, GtkListStore *store, const char *input) { - GList *src = g_hash_table_get_keys(handlers); + GList *src = g_hash_table_get_keys(c->handlers.table); gboolean found = util_fill_completion(store, input, src); g_list_free(src); return found; } -static char *handler_lookup(const char *uri) +static char *handler_lookup(Client *c, const char *uri) { char *p, *schema, *handler = NULL; if ((p = strchr(uri, ':'))) { schema = g_strndup(uri, p - uri); - handler = g_hash_table_lookup(handlers, schema); + handler = g_hash_table_lookup(c->handlers.table, schema); g_free(schema); } return handler; } + diff --git a/src/handlers.h b/src/handler.h similarity index 68% rename from src/handlers.h rename to src/handler.h index 665ddd5b..ce16bdf4 100644 --- a/src/handlers.h +++ b/src/handler.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +20,12 @@ #ifndef _HANDLERS_H #define _HANDLERS_H -void handlers_init(void); -void handlers_cleanup(void); -gboolean handler_add(const char *key, const char *cmd); -gboolean handler_remove(const char *key); -gboolean handle_uri(const char *uri); -gboolean handler_fill_completion(GtkListStore *store, const char *input); +void handler_init(Client *c); +void handler_cleanup(Client *c); +gboolean handler_add(Client *c, const char *key, const char *cmd); +gboolean handler_remove(Client *c, const char *key); +gboolean handler_handle_uri(Client *c, const char *uri); +gboolean handler_fill_completion(Client *c, GtkListStore *store, const char *input); #endif /* end of include guard: _HANDLERS_H */ + diff --git a/src/hints.c b/src/hints.c index d7a3f750..6046706b 100644 --- a/src/hints.c +++ b/src/hints.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,132 +18,102 @@ */ #include "config.h" +#include +#include +#include #include #include #include "hints.h" #include "main.h" #include "ascii.h" -#include "dom.h" #include "command.h" -#include "hints.js.h" #include "input.h" #include "map.h" -#include "js.h" - -#define HINT_FILE "hints.js" +#include "ext-proxy.h" static struct { - JSObjectRef obj; /* the js object */ char mode; /* mode identifying char - that last char of the hint prompt */ int promptlen; /* length of the hint prompt chars 2 or 3 */ gboolean gmode; /* indicate if the hints 'g' mode is used */ - JSContextRef ctx; -#if WEBKIT_CHECK_VERSION(2, 0, 0) /* holds the setting if JavaScript can open windows automatically that we * have to change to open windows via hinting */ gboolean allow_open_win; -#endif guint timeout_id; } hints; -extern VbCore vb; +extern struct Vimb vb; -static gboolean call_hints_function(const char *func, int count, JSValueRef params[]); -static void fire_timeout(gboolean on); +static gboolean call_hints_function(Client *c, const char *func, const char* args); +static void fire_timeout(Client *c, gboolean on); static gboolean fire_cb(gpointer data); -void hints_init(WebKitWebFrame *frame) -{ - if (hints.obj) { - JSValueUnprotect(hints.ctx, hints.obj); - hints.obj = NULL; - } - if (!hints.obj) { - hints.ctx = webkit_web_frame_get_global_context(frame); - hints.obj = js_create_object(hints.ctx, HINTS_JS); - } -} - -VbResult hints_keypress(int key) +VbResult hints_keypress(Client *c, int key) { - JSValueRef arguments[1]; - if (key == KEY_CR) { - hints_fire(); + hints_fire(c); return RESULT_COMPLETE; - } else if (key == CTRL('H')) { - fire_timeout(false); - arguments[0] = JSValueMakeNull(hints.ctx); - if (call_hints_function("update", 1, arguments)) { + } else if (key == CTRL('H')) { /* backspace */ + fire_timeout(c, false); + if (call_hints_function(c, "update", "null")) { return RESULT_COMPLETE; } } else if (key == KEY_TAB) { - fire_timeout(false); - hints_focus_next(false); + fire_timeout(c, false); + hints_focus_next(c, false); return RESULT_COMPLETE; } else if (key == KEY_SHIFT_TAB) { - fire_timeout(false); - hints_focus_next(true); + fire_timeout(c, false); + hints_focus_next(c, true); return RESULT_COMPLETE; } else { - fire_timeout(true); + fire_timeout(c, true); /* try to handle the key by the javascript */ - arguments[0] = js_string_to_ref(hints.ctx, (char[]){key, '\0'}); - if (call_hints_function("update", 1, arguments)) { + if (call_hints_function(c, "update", (char[]){'"', key, '"', '\0'})) { return RESULT_COMPLETE; } } - fire_timeout(false); + fire_timeout(c, false); return RESULT_ERROR; } -void hints_clear(void) +void hints_clear(Client *c) { - if (vb.mode->flags & FLAG_HINTING) { - vb.mode->flags &= ~FLAG_HINTING; - vb_set_input_text(""); + if (c->mode->flags & FLAG_HINTING) { + c->mode->flags &= ~FLAG_HINTING; + vb_input_set_text(c, ""); - call_hints_function("clear", 0, NULL); + call_hints_function(c, "clear", ""); - g_signal_emit_by_name(vb.gui.webview, "hovering-over-link", NULL, NULL); - -#if WEBKIT_CHECK_VERSION(2, 0, 0) /* if open window was not allowed for JavaScript, restore this */ if (!hints.allow_open_win) { - WebKitWebSettings *setting = webkit_web_view_get_settings(vb.gui.webview); + WebKitSettings *setting = webkit_web_view_get_settings(c->webview); g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", hints.allow_open_win, NULL); } -#endif } } -void hints_create(const char *input) +void hints_create(Client *c, const char *input) { - /* don't start hinting if the hinting object isn't created - for example - * if hinting is started before the first data of page are received */ - if (!hints.obj) { - return; - } + char *jsargs; /* check if the input contains a valid hinting prompt */ if (!hints_parse_prompt(input, &hints.mode, &hints.gmode)) { /* if input is not valid, clear possible previous hint mode */ - if (vb.mode->flags & FLAG_HINTING) { - vb_enter('n'); + if (c->mode->flags & FLAG_HINTING) { + vb_enter(c, 'n'); } return; } - if (!(vb.mode->flags & FLAG_HINTING)) { - vb.mode->flags |= FLAG_HINTING; + if (!(c->mode->flags & FLAG_HINTING)) { + c->mode->flags |= FLAG_HINTING; -#if WEBKIT_CHECK_VERSION(2, 0, 0) - WebKitWebSettings *setting = webkit_web_view_get_settings(vb.gui.webview); + WebKitSettings *setting = webkit_web_view_get_settings(c->webview); /* before we enable JavaScript to open new windows, we save the actual * value to be able restore it after hints where fired */ @@ -153,66 +123,70 @@ void hints_create(const char *input) if (!hints.allow_open_win) { g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", true, NULL); } -#endif hints.promptlen = hints.gmode ? 3 : 2; - JSValueRef arguments[] = { - js_string_to_ref(hints.ctx, (char[]){hints.mode, '\0'}), - JSValueMakeBoolean(hints.ctx, hints.gmode), - JSValueMakeNumber(hints.ctx, MAXIMUM_HINTS), - js_string_to_ref(hints.ctx, GET_CHAR("hintkeys")), - JSValueMakeBoolean(hints.ctx, GET_BOOL("hint-follow-last")), - JSValueMakeBoolean(hints.ctx, GET_BOOL("hint-number-same-length")) - }; - call_hints_function("init", 6, arguments); + jsargs = g_strdup_printf("'%s', %s, %d, '%s', %s, %s", + (char[]){hints.mode, '\0'}, + hints.gmode ? "true" : "false", + MAXIMUM_HINTS, + GET_CHAR(c, "hint-keys"), + GET_BOOL(c, "hint-follow-last") ? "true" : "false", + GET_BOOL(c, "hint-number-same-length") ? "true" : "false" + ); + + call_hints_function(c, "init", jsargs); + g_free(jsargs); /* if hinting is started there won't be any additional filter given and * we can go out of this function */ return; } - JSValueRef arguments[] = {js_string_to_ref(hints.ctx, *(input + hints.promptlen) ? input + hints.promptlen : "")}; - call_hints_function("filter", 1, arguments); + jsargs = g_strdup_printf("'%s'", *(input + hints.promptlen) ? input + hints.promptlen : ""); + call_hints_function(c, "filter", jsargs); + g_free(jsargs); } -void hints_focus_next(const gboolean back) +void hints_focus_next(Client *c, const gboolean back) { - JSValueRef arguments[] = { - JSValueMakeNumber(hints.ctx, back) - }; - call_hints_function("focus", 1, arguments); + call_hints_function(c, "focus", back ? "true" : "false"); } -void hints_fire(void) +void hints_fire(Client *c) { - call_hints_function("fire", 0, NULL); + call_hints_function(c, "fire", ""); } -void hints_follow_link(const gboolean back, int count) +void hints_follow_link(Client *c, const gboolean back, int count) { - char *json = g_strdup_printf( - "[%s]", - back ? vb.config.prevpattern : vb.config.nextpattern - ); - - JSValueRef arguments[] = { - js_string_to_ref(hints.ctx, back ? "prev" : "next"), - js_object_to_ref(hints.ctx, json), - JSValueMakeNumber(hints.ctx, count) - }; - g_free(json); - - call_hints_function("followLink", 3, arguments); + /* TODO implement outside of hints.c */ + /* We would previously "piggyback" on hints.js for the "js" part of this feature + * but this would actually be more elegant in its own JS file. This has nothing + * to do with hints. + */ + /* char *json = g_strdup_printf( */ + /* "[%s]", */ + /* back ? vb.config.prevpattern : vb.config.nextpattern */ + /* ); */ + + /* JSValueRef arguments[] = { */ + /* js_string_to_ref(hints.ctx, back ? "prev" : "next"), */ + /* js_object_to_ref(hints.ctx, json), */ + /* JSValueMakeNumber(hints.ctx, count) */ + /* }; */ + /* g_free(json); */ + + /* call_hints_function(c, "followLink", 3, arguments); */ } -void hints_increment_uri(int count) +void hints_increment_uri(Client *c, int count) { - JSValueRef arguments[] = { - JSValueMakeNumber(hints.ctx, count) - }; + char *jsargs; - call_hints_function("incrementUri", 1, arguments); + jsargs = g_strdup_printf("%d", count); + call_hints_function(c, "incrementUri", jsargs); + g_free(jsargs); } /** @@ -274,86 +248,92 @@ gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode) return res; } -static gboolean call_hints_function(const char *func, int count, JSValueRef params[]) +static gboolean call_hints_function(Client *c, const char *func, const char* args) { - char *value = js_object_call_function(hints.ctx, hints.obj, func, count, params); + GVariant *return_value; + char *jscode, *value = NULL; + gboolean success = FALSE; - g_return_val_if_fail(value != NULL, false); + jscode = g_strdup_printf("hints.%s(%s);", func, args); + return_value = ext_proxy_eval_script_sync(c, jscode); + g_free(jscode); - if (!strncmp(value, "ERROR:", 6)) { - g_free(value); - return false; + if (!return_value) { + return FALSE; } - if (!strncmp(value, "OVER:", 5)) { - g_signal_emit_by_name( - vb.gui.webview, "hovering-over-link", NULL, *(value + 5) == '\0' ? NULL : (value + 5) - ); - g_free(value); - - return true; + g_variant_get(return_value, "(bs)", &success, &value); + if (!success || !strncmp(value, "ERROR:", 6)) { + return FALSE; } /* following return values mark fired hints */ if (!strncmp(value, "DONE:", 5)) { - fire_timeout(false); + fire_timeout(c, false); /* Change to normal mode only if we are currently in command mode and * we are not in g-mode hinting. This is required to not switch to * normal mode when the hinting triggered a click that set focus on * editable element that lead vimb to switch to input mode. */ - if (!hints.gmode && vb.mode->id == 'c') { - vb_enter('n'); + if (!hints.gmode && c->mode->id == 'c') { + vb_enter(c, 'n'); + } + /* If open in new window hinting is use, set a flag on the mode after + * changing to normal mode. This is used in on_webview_decide_policy + * to enforce opening into new instance for the next navigation + * action. */ + if (hints.mode == 't') { + c->mode->flags |= FLAG_NEW_WIN; } } else if (!strncmp(value, "INSERT:", 7)) { - fire_timeout(false); - vb_enter('i'); + fire_timeout(c, false); + vb_enter(c, 'i'); if (hints.mode == 'e') { - input_open_editor(); + input_open_editor(c); } } else if (!strncmp(value, "DATA:", 5)) { - fire_timeout(false); + fire_timeout(c, false); /* switch first to normal mode - else we would clear the inputbox * on switching mode also if we want to show yanked data */ if (!hints.gmode) { - vb_enter('n'); + vb_enter(c, 'n'); } char *v = (value + 5); Arg a = {0}; /* put the hinted value into register "; */ - vb_register_add(';', v); + vb_register_add(c, ';', v); switch (hints.mode) { /* used if images should be opened */ case 'i': case 'I': a.s = v; - a.i = (hints.mode == 'I') ? VB_TARGET_NEW : VB_TARGET_CURRENT; - vb_load_uri(&a); + a.i = (hints.mode == 'I') ? TARGET_NEW : TARGET_CURRENT; + vb_load_uri(c, &a); break; case 'O': case 'T': - vb_echo(VB_MSG_NORMAL, false, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v); + vb_echo(c, MSG_NORMAL, false, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v); if (!hints.gmode) { - vb_enter('c'); + vb_enter(c, 'c'); } break; case 's': a.s = v; a.i = COMMAND_SAVE_URI; - command_save(&a); + command_save(c, &a); break; case 'x': - map_handle_string(GET_CHAR("x-hint-command"), true); + map_handle_string(c, GET_CHAR(c, "x-hint-command"), true); break; case 'y': case 'Y': a.i = COMMAND_YANK_ARG; a.s = v; - command_yank(&a, vb.state.current_register); + command_yank(c, &a, c->state.current_register); break; #ifdef FEATURE_QUEUE @@ -361,16 +341,16 @@ static gboolean call_hints_function(const char *func, int count, JSValueRef para case 'P': a.s = v; a.i = (hints.mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH; - command_queue(&a); + command_queue(c, &a); break; #endif } } - g_free(value); - return true; + + return TRUE; } -static void fire_timeout(gboolean on) +static void fire_timeout(Client *c, gboolean on) { int millis; /* remove possible timeout function */ @@ -380,16 +360,16 @@ static void fire_timeout(gboolean on) } if (on) { - millis = GET_INT("hint-timeout"); + millis = GET_INT(c, "hint-timeout"); if (millis) { - hints.timeout_id = g_timeout_add(millis, (GSourceFunc)fire_cb, NULL); + hints.timeout_id = g_timeout_add(millis, (GSourceFunc)fire_cb, c); } } } static gboolean fire_cb(gpointer data) { - hints_fire(); + hints_fire(data); /* remove timeout id for the timeout that is removed by return value of * false automatic */ diff --git a/src/hints.h b/src/hints.h index 434092c7..a64d689e 100644 --- a/src/hints.h +++ b/src/hints.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,14 +22,13 @@ #include "main.h" -void hints_init(WebKitWebFrame *frame); -VbResult hints_keypress(int key); -void hints_create(const char *input); -void hints_fire(void); -void hints_follow_link(const gboolean back, int count); -void hints_increment_uri(int count); +VbResult hints_keypress(Client *c, int key); +void hints_create(Client *c, const char *input); +void hints_fire(Client *c); +void hints_follow_link(Client *c, gboolean back, int count); +void hints_increment_uri(Client *c, int count); gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode); -void hints_clear(void); -void hints_focus_next(const gboolean back); +void hints_clear(Client *c); +void hints_focus_next(Client *c, const gboolean back); #endif /* end of include guard: _HINTS_H */ diff --git a/src/history.c b/src/history.c index df17c4f2..e6cfc73b 100644 --- a/src/history.c +++ b/src/history.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,78 +17,79 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" #include +#include #include -#include "main.h" + +#include "ascii.h" +#include "completion.h" +#include "config.h" #include "history.h" +#include "main.h" #include "util.h" -#include "completion.h" -#include "ascii.h" - -extern VbCore vb; #define HIST_FILE(t) (vb.files[file_map[t]]) -/* map history types to files */ -static const VbFile file_map[HISTORY_LAST] = { - FILES_COMMAND, - FILES_SEARCH, - FILES_HISTORY -}; - typedef struct { char *first; char *second; } History; +static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen); +static void free_history(History *item); +static History *line_to_history(const char *uri, const char *title); static GList *load(const char *file); static void write_to_file(GList *list, const char *file); -static gboolean history_item_contains_all_tags(History *item, char **query, - unsigned int qlen); -static History *line_to_history(const char *uri, const char *title); -static void free_history(History *item); +/* map history types to files */ +static const int file_map[HISTORY_LAST] = { + FILES_COMMAND, + FILES_SEARCH, + FILES_HISTORY +}; +extern struct Vimb vb; /** - * Makes all history items unique and force them to fit the maximum history - * size and writes all entries of the different history types to file. + * Write a new history entry to the end of history file. */ -void history_cleanup(void) +void history_add(Client *c, HistoryType type, const char *value, const char *additional) { const char *file; - GList *list; - /* don't cleanup the history file if history max size is 0 */ - if (!vb.config.history_max) { +#if 0 + /* Don't write a history entry if the history max size is set to 0. Else + * skip command history in case the command was not typed by the user. */ + if (!vb.config.history_max || (!vb.state.typed && type == HISTORY_COMMAND)) { return; } +#endif - for (HistoryType i = HISTORY_FIRST; i < HISTORY_LAST; i++) { - file = HIST_FILE(i); - list = load(file); - write_to_file(list, file); - g_list_free_full(list, (GDestroyNotify)free_history); + file = HIST_FILE(type); + if (additional) { + util_file_append(file, "%s\t%s\n", value, additional); + } else { + util_file_append(file, "%s\n", value); } } /** - * Write a new history entry to the end of history file. + * Makes all history items unique and force them to fit the maximum history + * size and writes all entries of the different history types to file. */ -void history_add(HistoryType type, const char *value, const char *additional) +void history_cleanup(void) { const char *file; + GList *list; - /* Don't write a history entry if the history max size is set to 0. Else - * skip command history in case the command was not typed by the user. */ - if (!vb.config.history_max || (!vb.state.typed && type == HISTORY_COMMAND)) { + /* don't cleanup the history file if history max size is 0 */ + if (!vb.config.history_max) { return; } - file = HIST_FILE(type); - if (additional) { - util_file_append(file, "%s\t%s\n", value, additional); - } else { - util_file_append(file, "%s\n", value); + for (HistoryType i = HISTORY_FIRST; i < HISTORY_LAST; i++) { + file = HIST_FILE(i); + list = load(file); + write_to_file(list, file); + g_list_free_full(list, (GDestroyNotify)free_history); } } @@ -96,7 +97,7 @@ gboolean history_fill_completion(GtkListStore *store, HistoryType type, const ch { char **parts; unsigned int len; - gboolean found = false; + gboolean found = FALSE; GList *src = NULL; GtkTreeIter iter; History *item; @@ -116,7 +117,7 @@ gboolean history_fill_completion(GtkListStore *store, HistoryType type, const ch #endif -1 ); - found = true; + found = TRUE; } } else if (HISTORY_URL == type) { parts = g_strsplit(input, " ", 0); @@ -134,7 +135,7 @@ gboolean history_fill_completion(GtkListStore *store, HistoryType type, const ch #endif -1 ); - found = true; + found = TRUE; } } g_strfreev(parts); @@ -151,7 +152,7 @@ gboolean history_fill_completion(GtkListStore *store, HistoryType type, const ch #endif -1 ); - found = true; + found = TRUE; } } } @@ -169,12 +170,12 @@ GList *history_get_list(VbInputType type, const char *query) GList *result = NULL, *src = NULL; switch (type) { - case VB_INPUT_COMMAND: + case INPUT_COMMAND: src = load(HIST_FILE(HISTORY_COMMAND)); break; - case VB_INPUT_SEARCH_FORWARD: - case VB_INPUT_SEARCH_BACKWARD: + case INPUT_SEARCH_FORWARD: + case INPUT_SEARCH_BACKWARD: src = load(HIST_FILE(HISTORY_SEARCH)); break; @@ -200,7 +201,46 @@ GList *history_get_list(VbInputType type, const char *query) } /** - * Loads history items form file but eleminate duplicates in FIFO order. + * Checks if the given array of tags are all found in history item. + */ +static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen) +{ + unsigned int i; + if (!qlen) { + return TRUE; + } + + /* iterate over all query parts */ + for (i = 0; i < qlen; i++) { + if (!(util_strcasestr(item->first, query[i]) + || (item->second && util_strcasestr(item->second, query[i]))) + ) { + return FALSE; + } + } + + return TRUE; +} + +static void free_history(History *item) +{ + g_free(item->first); + g_free(item->second); + g_slice_free(History, item); +} + +static History *line_to_history(const char *uri, const char *title) +{ + History *item = g_slice_new0(History); + + item->first = g_strdup(uri); + item->second = g_strdup(title); + + return item; +} + +/** + * Loads history items form file but eliminate duplicates in FIFO order. * * Returned list must be freed with (GDestroyNotify)free_history. */ @@ -234,43 +274,3 @@ static void write_to_file(GList *list, const char *file) fclose(f); } } - -/** - * Checks if the given array of tags are all found in history item. - */ -static gboolean history_item_contains_all_tags(History *item, char **query, - unsigned int qlen) -{ - unsigned int i; - if (!qlen) { - return true; - } - - /* iterate over all query parts */ - for (i = 0; i < qlen; i++) { - if (!(util_strcasestr(item->first, query[i]) - || (item->second && util_strcasestr(item->second, query[i]))) - ) { - return false; - } - } - - return true; -} - -static History *line_to_history(const char *uri, const char *title) -{ - History *item = g_slice_new0(History); - - item->first = g_strdup(uri); - item->second = g_strdup(title); - - return item; -} - -static void free_history(History *item) -{ - g_free(item->first); - g_free(item->second); - g_slice_free(History, item); -} diff --git a/src/history.h b/src/history.h index edbbc483..46df5328 100644 --- a/src/history.h +++ b/src/history.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,10 @@ #ifndef _HISTORY_H #define _HISTORY_H +#include + +#include "main.h" + typedef enum { HISTORY_FIRST = 0, HISTORY_COMMAND = 0, @@ -28,8 +32,8 @@ typedef enum { HISTORY_LAST } HistoryType; +void history_add(Client *c, HistoryType type, const char *value, const char *additional); void history_cleanup(void); -void history_add(HistoryType type, const char *value, const char *additional); gboolean history_fill_completion(GtkListStore *store, HistoryType type, const char *input); GList *history_get_list(VbInputType type, const char *query); diff --git a/src/hsts.c b/src/hsts.c deleted file mode 100644 index 9ee32a5d..00000000 --- a/src/hsts.c +++ /dev/null @@ -1,458 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_HSTS -#include "hsts.h" -#include "util.h" -#include "main.h" -#include -#include -#include -#include -#include - -#define HSTS_HEADER_NAME "Strict-Transport-Security" -#define HSTS_FILE_FORMAT "%s\t%s\t%c\n" -#define HSTS_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), HSTS_TYPE_PROVIDER, HSTSProviderPrivate)) - -extern VbCore vb; - -/* private interface of the provider */ -typedef struct _HSTSProviderPrivate { - GHashTable* whitelist; -} HSTSProviderPrivate; - -typedef struct { - SoupDate *expires_at; - gboolean include_sub_domains; -} HSTSEntry; - -static void hsts_provider_class_init(HSTSProviderClass *klass); -static void hsts_provider_init(HSTSProvider *self); -static void hsts_provider_finalize(GObject* obj); -static inline gboolean should_secure_host(HSTSProvider *provider, - const char *host); -static void process_hsts_header(SoupMessage *msg, gpointer data); -static void parse_hsts_header(HSTSProvider *provider, - const char *host, const char *header); -static void free_entry(HSTSEntry *entry); -static void add_host_entry(HSTSProvider *provider, const char *host, - HSTSEntry *entry); -static void add_host_entry_to_file(HSTSProvider *provider, const char *host, - HSTSEntry *entry); -static void remove_host_entry(HSTSProvider *provider, const char *host); -/* session feature related functions */ -static void session_feature_init( - SoupSessionFeatureInterface *inteface, gpointer data); -static void request_queued(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg); -static void request_started(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg, SoupSocket *socket); -static void request_unqueued(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg); -/* caching related functions */ -static void load_entries(HSTSProvider *provider, const char *file); -static void save_entries(HSTSProvider *provider, const char *file); - -/** - * Change scheme and port of soup messages uri if the host is a known and - * valid hsts host. - * - * This logic should be implemented in request_queued function but the changes - * that are done there to the uri do not appear in webkit_web_view_get_uri(). - * If a valid hsts host is requested via http and the url is changed to https - * vimb would still show the http uri in url bar. This seems to be a - * missbehaviour in webkit, but for now we provide this function to put in the - * logic in the scope of the navigation-policy-decision-requested event of the - * webview. - * - * Returns newly allocated string with new URI if the URI was change to - * fullfill HSTS, else NULL. - */ -char *hsts_get_changed_uri(SoupSession* session, SoupMessage *msg) -{ - SoupSessionFeature *feature; - HSTSProvider *provider; - SoupURI *uri; - - if (!msg) { - return NULL; - } - - feature = soup_session_get_feature_for_message(session, HSTS_TYPE_PROVIDER, msg); - uri = soup_message_get_uri(msg); - if (!feature || !uri) { - return NULL; - } - - provider = HSTS_PROVIDER(feature); - /* if URI uses still https we don't nee to rewrite it */ - if (uri->scheme != SOUP_URI_SCHEME_HTTPS - && should_secure_host(provider, uri->host) - ) { - /* the ports is set by soup uri if scheme is changed */ - soup_uri_set_scheme(uri, SOUP_URI_SCHEME_HTTPS); - return soup_uri_to_string(uri, false); - } - return NULL; -} - -/** - * Generates a new hsts provider instance. - * Unref the instance with g_object_unref if no more used. - */ -HSTSProvider *hsts_provider_new(void) -{ - return g_object_new(HSTS_TYPE_PROVIDER, NULL); -} - -G_DEFINE_TYPE_WITH_CODE( - HSTSProvider, hsts_provider, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE, session_feature_init) -) - -static void hsts_provider_class_init(HSTSProviderClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS(klass); - hsts_provider_parent_class = g_type_class_peek_parent(klass); - g_type_class_add_private(klass, sizeof(HSTSProviderPrivate)); - object_class->finalize = hsts_provider_finalize; -} - -static void hsts_provider_init(HSTSProvider *self) -{ - /* initialize private fields */ - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(self); - priv->whitelist = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_entry); - - /* load entries from hsts file */ - load_entries(self, vb.files[FILES_HSTS]); -} - -static void hsts_provider_finalize(GObject* obj) -{ - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE (obj); - - /* save all the entries in hsts file */ - save_entries(HSTS_PROVIDER(obj), vb.files[FILES_HSTS]); - - g_hash_table_destroy(priv->whitelist); - G_OBJECT_CLASS(hsts_provider_parent_class)->finalize(obj); -} - -/** - * Checks if given host is a known https host according to RFC 6797 8.2f - */ -static inline gboolean should_secure_host(HSTSProvider *provider, - const char *host) -{ - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); - HSTSEntry *entry; - char *canonical, *p; - gboolean result = false, is_subdomain = false; - - /* ip is not allowed for hsts */ - if (g_hostname_is_ip_address(host)) { - return false; - } - - canonical = g_hostname_to_ascii(host); - /* don't match empty host */ - if (*canonical) { - p = canonical; - /* Try to find the whole congruent matching host in hash table - if - * not found strip of the first label and try to find a superdomain - * match. Specified is a from right to left comparison 8.3, but in the - * end this should be lead to the same result. */ - while (p != NULL) { - entry = g_hash_table_lookup(priv->whitelist, p); - if (entry != NULL) { - /* remove expired entries RFC 6797 8.1.1 */ - if (soup_date_is_past(entry->expires_at)) { - remove_host_entry(provider, p); - } else if(!is_subdomain || entry->include_sub_domains) { - result = true; - break; - } - } - - is_subdomain = true; - /* test without the first domain part */ - if ((p = strchr(p, '.'))) { - p++; - } - } - } - g_free(canonical); - - return result; -} - -static void process_hsts_header(SoupMessage *msg, gpointer data) -{ - HSTSProvider *provider = (HSTSProvider*)data; - SoupURI *uri = soup_message_get_uri(msg); - SoupMessageHeaders *hdrs; - const char *header; - - if (!g_hostname_is_ip_address(uri->host) - && (soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED) - ){ - g_object_get(G_OBJECT(msg), SOUP_MESSAGE_RESPONSE_HEADERS, &hdrs, NULL); - - /* TODO according to RFC 6797 8.1 we must only use the first header */ - header = soup_message_headers_get_one(hdrs, HSTS_HEADER_NAME); - if (header) { - parse_hsts_header(provider, uri->host, header); - } - } -} - -/** - * Parses the hsts directives from given header like specified in RFC 6797 6.1 - */ -static void parse_hsts_header(HSTSProvider *provider, - const char *host, const char *header) -{ - GHashTable *directives = soup_header_parse_semi_param_list(header); - HSTSEntry *entry; - int max_age = G_MAXINT; - gboolean include_sub_domains = false; - GHashTableIter iter; - gpointer key, value; - gboolean success = true; - - HSTSProviderClass *klass = g_type_class_ref(HSTS_TYPE_PROVIDER); - - g_hash_table_iter_init(&iter, directives); - while (g_hash_table_iter_next(&iter, &key, &value)) { - /* parse the max-age directive */ - if (!g_ascii_strncasecmp(key, "max-age", 7)) { - /* max age needs a value */ - if (value) { - max_age = g_ascii_strtoll(value, NULL, 10); - if (max_age < 0) { - success = false; - break; - } - } else { - success = false; - break; - } - } else if (g_ascii_strncasecmp(key, "includeSubDomains", 17)) { - /* includeSubDomains must not have a value */ - if (!value) { - include_sub_domains = true; - } else { - success = false; - break; - } - } - } - soup_header_free_param_list(directives); - g_type_class_unref(klass); - - if (success) { - /* remove host if max-age = 0 RFC 6797 6.1.1 */ - if (max_age == 0) { - remove_host_entry(provider, host); - } else { - entry = g_slice_new(HSTSEntry); - entry->expires_at = soup_date_new_from_now(max_age); - entry->include_sub_domains = include_sub_domains; - - add_host_entry(provider, host, entry); - add_host_entry_to_file(provider, host, entry); - } - } -} - -static void free_entry(HSTSEntry *entry) -{ - soup_date_free(entry->expires_at); - g_slice_free(HSTSEntry, entry); -} - -/** - * Adds the host to the known host, if it already exists it replaces it with - * the information contained in entry according to RFC 6797 8.1. - */ -static void add_host_entry(HSTSProvider *provider, const char *host, - HSTSEntry *entry) -{ - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); - g_hash_table_replace(priv->whitelist, g_hostname_to_unicode(host), entry); -} - -static void add_host_entry_to_file(HSTSProvider *provider, const char *host, - HSTSEntry *entry) -{ - char *date = soup_date_to_string(entry->expires_at, SOUP_DATE_ISO8601_FULL); - - util_file_append( - vb.files[FILES_HSTS], HSTS_FILE_FORMAT, host, date, entry->include_sub_domains ? 'y' : 'n' - ); - g_free(date); -} - -/** - * Removes stored entry for given host. - */ -static void remove_host_entry(HSTSProvider *provider, const char *host) -{ - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); - char *canonical = g_hostname_to_unicode(host); - - g_hash_table_remove(priv->whitelist, canonical); - g_free(canonical); -} - -/** - * Initialise the SoupSessionFeature interface. - */ -static void session_feature_init( - SoupSessionFeatureInterface *inteface, gpointer data) -{ - inteface->request_queued = request_queued; - inteface->request_started = request_started; - inteface->request_unqueued = request_unqueued; -} - -/** - * Check if the host is known and switch the URI scheme to https. - */ -static void request_queued(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg) -{ - SoupURI *uri = soup_message_get_uri(msg); - HSTSProvider *provider = HSTS_PROVIDER(feature); - - /* only look for HSTS headers sent over https RFC 6797 7.2*/ - if (uri->scheme == SOUP_URI_SCHEME_HTTPS) { - soup_message_add_header_handler( - msg, "got-headers", HSTS_HEADER_NAME, G_CALLBACK(process_hsts_header), feature - ); - } else if (should_secure_host(provider, uri->host)) { - /* the ports is set by soup uri if scheme is changed */ - soup_uri_set_scheme(uri, SOUP_URI_SCHEME_HTTPS); - soup_session_requeue_message(session, msg); - } -} - -static void request_started(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg, SoupSocket *socket) -{ - HSTSProvider *provider = HSTS_PROVIDER(feature); - SoupURI *uri = soup_message_get_uri(msg); - GTlsCertificate *certificate; - GTlsCertificateFlags errors; - - if (should_secure_host(provider, uri->host)) { - if (uri->scheme != SOUP_URI_SCHEME_HTTPS - || (soup_message_get_https_status(msg, &certificate, &errors) && errors) - ) { - soup_session_cancel_message(session, msg, SOUP_STATUS_SSL_FAILED); - g_warning("cancel invalid hsts request to %s://%s", uri->scheme, uri->host); - } - } -} - -static void request_unqueued(SoupSessionFeature *feature, - SoupSession *session, SoupMessage *msg) -{ - g_signal_handlers_disconnect_by_func(msg, process_hsts_header, feature); -} - -/** - * Reads the entries save in given file and store them in the whitelist. - */ -static void load_entries(HSTSProvider *provider, const char *file) -{ - char **lines, **parts, *host, *line; - int i, len, partlen; - gboolean include_sub_domains; - SoupDate *date; - HSTSEntry *entry; - - lines = util_get_lines(file); - if (!(len = g_strv_length(lines))) { - return; - } - - for (i = len - 1; i >= 0; i--) { - line = lines[i]; - /* skip empty or commented lines */ - if (!*line || *line == '#') { - continue; - } - - parts = g_strsplit(line, "\t", 3); - partlen = g_strv_length(parts); - if (partlen == 3) { - host = parts[0]; - if (g_hostname_is_ip_address(host)) { - continue; - } - date = soup_date_new_from_string(parts[1]); - if (!date) { - continue; - } - include_sub_domains = (*parts[2] == 'y') ? true : false; - - /* built the new entry to add */ - entry = g_slice_new(HSTSEntry); - entry->expires_at = soup_date_new_from_string(parts[1]); - entry->include_sub_domains = include_sub_domains; - - add_host_entry(provider, host, entry); - } else { - g_warning("could not parse hsts line '%s'", line); - } - g_strfreev(parts); - } - g_strfreev(lines); -} - -/** - * Saves all entries of given provider in given file. - */ -static void save_entries(HSTSProvider *provider, const char *file) -{ - GHashTableIter iter; - char *host, *date; - HSTSEntry *entry; - FILE *f; - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); - - if ((f = fopen(file, "w"))) { - flock(fileno(f), LOCK_EX); - - g_hash_table_iter_init(&iter, priv->whitelist); - while (g_hash_table_iter_next (&iter, (gpointer)&host, (gpointer)&entry)) { - date = soup_date_to_string(entry->expires_at, SOUP_DATE_ISO8601_FULL); - fprintf(f, HSTS_FILE_FORMAT, host, date, entry->include_sub_domains ? 'y' : 'n'); - g_free(date); - } - - flock(fileno(f), LOCK_UN); - fclose(f); - } -} -#endif diff --git a/src/hsts.h b/src/hsts.h deleted file mode 100644 index 05e8be53..00000000 --- a/src/hsts.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_HSTS - -#ifndef _HSTS_H -#define _HSTS_H - -#include -#include - -#define HSTS_TYPE_PROVIDER (hsts_provider_get_type()) -#define HSTS_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), HSTS_TYPE_PROVIDER, HSTSProvider)) -#define HSTS_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), HSTS_TYPE_PROVIDER, HSTSProviderClass)) -#define HSTS_IS_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), HSTS_TYPE_PROVIDER)) -#define HSTS_IS_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), HSTS_TYPE_PROVIDER)) -#define HSTS_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), HSTS_TYPE_PROVIDER, HSTSProviderClass)) - -/* public interface of the provider */ -typedef struct { - GObject parent_instance; -} HSTSProvider; - -/* class members of the provider */ -typedef struct { - GObjectClass parent_class; -} HSTSProviderClass; - - -char *hsts_get_changed_uri(SoupSession* session, SoupMessage *msg); -GType hsts_provider_get_type(void); -HSTSProvider *hsts_provider_new(void); - -#endif /* end of include guard: _HSTS_H */ -#endif diff --git a/src/input.c b/src/input.c index d167bc92..7bd8face 100644 --- a/src/input.c +++ b/src/input.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,62 +17,64 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include #include -#include "main.h" -#include "input.h" -#include "dom.h" -#include "util.h" + #include "ascii.h" +#include "config.h" +#include "input.h" +#include "main.h" #include "normal.h" +#include "util.h" +#include "ext-proxy.h" typedef struct { - char *file; - Element *element; + Client *c; + char *file; } EditorData; static void resume_editor(GPid pid, int status, EditorData *data); -extern VbCore vb; - /** * Function called when vimb enters the input mode. */ -void input_enter(void) +void input_enter(Client *c) { /* switch focus first to make sure we can write to the inputbox without * disturbing the user */ - gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview)); - vb_update_mode_label("-- INPUT --"); + gtk_widget_grab_focus(GTK_WIDGET(c->webview)); + vb_modelabel_update(c, "-- INPUT --"); + ext_proxy_eval_script(c, "var vimb_input_mode_element = document.activeElement;", NULL); } /** * Called when the input mode is left. */ -void input_leave(void) +void input_leave(Client *c) { - vb_update_mode_label(""); + ext_proxy_eval_script(c, "vimb_input_mode_element.blur();", NULL); + vb_modelabel_update(c, ""); } /** * Handles the keypress events from webview and inputbox. */ -VbResult input_keypress(int key) +VbResult input_keypress(Client *c, int key) { - static gboolean ctrlo = false; + static gboolean ctrlo = FALSE; if (ctrlo) { /* if we are in ctrl-O mode perform the next keys as normal mode * commands until the command is complete or error */ - VbResult res = normal_keypress(key); + VbResult res = normal_keypress(c, key); if (res != RESULT_MORE) { - ctrlo = false; + ctrlo = FALSE; /* Don't overwrite the mode label in case we landed in another * mode. This might occurre by CTRL-0 CTRL-Z or after running ex * command, where we mainly end up in normal mode. */ - if (vb.mode->id == 'i') { + if (c->mode->id == 'i') { /* reenter the input mode */ - input_enter(); + input_enter(c); } } return res; @@ -80,53 +82,55 @@ VbResult input_keypress(int key) switch (key) { case CTRL('['): /* esc */ - vb_enter('n'); + vb_enter(c, 'n'); return RESULT_COMPLETE; case CTRL('O'): /* enter CTRL-0 mode to execute next command in normal mode */ - ctrlo = true; - vb.mode->flags |= FLAG_NOMAP; - vb_update_mode_label("-- (input) --"); + ctrlo = TRUE; + c->mode->flags |= FLAG_NOMAP; + vb_modelabel_update(c, "-- (input) --"); return RESULT_MORE; case CTRL('T'): - return input_open_editor(); + return input_open_editor(c); case CTRL('Z'): - vb_enter('p'); + vb_enter(c, 'p'); return RESULT_COMPLETE; } - vb.state.processed_key = false; + c->state.processed_key = FALSE; return RESULT_ERROR; } -VbResult input_open_editor(void) +VbResult input_open_editor(Client *c) { char **argv, *file_path = NULL; - const char *text, *editor_command; + const char *text = NULL, *editor_command; int argc; GPid pid; gboolean success; + GVariant *jsreturn; + + g_assert(c); - editor_command = GET_CHAR("editor-command"); + /* get the editor command */ + editor_command = GET_CHAR(c, "editor-command"); if (!editor_command || !*editor_command) { - vb_echo(VB_MSG_ERROR, true, "No editor-command configured"); + vb_echo(c, MSG_ERROR, true, "No editor-command configured"); return RESULT_ERROR; } - Element* active = dom_get_active_element(vb.gui.webview); - /* check if element is suitable for editing */ - if (!active || !dom_is_editable(active)) { - return RESULT_ERROR; - } + /* get the selected input element */ + jsreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.value"); + g_variant_get(jsreturn, "(bs)", &success, &text); - text = dom_editable_element_get_value(active); - if (!text) { + if (!success || !text) { return RESULT_ERROR; } + /* create a temp file to pass text to and from editor */ if (!util_create_tmp_file(text, &file_path)) { return RESULT_ERROR; } @@ -154,12 +158,12 @@ VbResult input_open_editor(void) } /* disable the active element */ - dom_editable_element_set_disable(active, true); + ext_proxy_eval_script(c, "vimb_input_mode_element.disabled=true", NULL); + /* watch the editor process */ EditorData *data = g_slice_new0(EditorData); - data->file = file_path; - data->element = active; - + data->file = file_path; + data->c = c; g_child_watch_add(pid, (GChildWatchFunc)resume_editor, data); return RESULT_COMPLETE; @@ -167,16 +171,79 @@ VbResult input_open_editor(void) static void resume_editor(GPid pid, int status, EditorData *data) { - char *text; + char *text, *tmp; + char *jscode; + + g_assert(pid); + g_assert(data); + g_assert(data->c); + g_assert(data->file); + if (status == 0) { + /* get the text the editor stored */ text = util_get_file_contents(data->file, NULL); + if (text) { - dom_editable_element_set_value(data->element, text); + /* escape the text to form a valid JS string */ + /* TODO: could go into util.c maybe */ + struct search_replace { + const char* search; + const char* replace; + } escapes[] = { + {"\x01", ""}, + {"\x02", ""}, + {"\x03", ""}, + {"\x04", ""}, + {"\x05", ""}, + {"\x06", ""}, + {"\a", ""}, + {"\b", ""}, + {"\t", "\\t"}, + {"\n", "\\n"}, + {"\v", ""}, + {"\f", ""}, + {"\r", ""}, + {"\x0E", ""}, + {"\x0F", ""}, + {"\x10", ""}, + {"\x11", ""}, + {"\x12", ""}, + {"\x13", ""}, + {"\x14", ""}, + {"\x15", ""}, + {"\x16", ""}, + {"\x17", ""}, + {"\x18", ""}, + {"\x19", ""}, + {"\x1A", ""}, + {"\x1B", ""}, + {"\x1C", ""}, + {"\x1D", ""}, + {"\x1E", ""}, + {"\x1F", ""}, + {"\"", "\\\""}, + {"\x7F", ""}, + {NULL, NULL}, + }; + + for(struct search_replace *i = escapes; i->search; i++) { + tmp = util_str_replace(i->search, i->replace, text); + g_free(text); + text = tmp; + } + + /* put the text back into the element */ + jscode = g_strdup_printf("vimb_input_mode_element.value=\"%s\"", text); + ext_proxy_eval_script(data->c, jscode, NULL); + + g_free(jscode); + g_free(text); } - g_free(text); } - dom_editable_element_set_disable(data->element, false); - dom_give_focus(data->element); + + ext_proxy_eval_script(data->c, + "vimb_input_mode_element.disabled=false;" + "vimb_input_mode_element.focus()", NULL); g_unlink(data->file); g_free(data->file); diff --git a/src/input.h b/src/input.h index 1e5e5bc9..eaa0169f 100644 --- a/src/input.h +++ b/src/input.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,9 +23,9 @@ #include "config.h" #include "main.h" -void input_enter(void); -void input_leave(void); -VbResult input_keypress(int key); -VbResult input_open_editor(void); +void input_enter(Client *c); +void input_leave(Client *c); +VbResult input_keypress(Client *c, int key); +VbResult input_open_editor(Client *c); #endif /* end of include guard: _INPUT_H */ diff --git a/src/io.c b/src/io.c deleted file mode 100644 index 7a3285e6..00000000 --- a/src/io.c +++ /dev/null @@ -1,170 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2016 Daniel Carl - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -#include "config.h" -#ifdef FEATURE_SOCKET -#include "io.h" -#include "main.h" -#include "map.h" -#include "util.h" -#include -#include -#include -#include -#include -#include - -extern VbCore vb; - -static gboolean socket_accept(GIOChannel *chan); -static gboolean socket_watch(GIOChannel *chan); - - -gboolean io_init_socket(const char *name) -{ - char *dir, *path; - int sock; - struct sockaddr_un local; - - /* create socket in runtime directory */ - dir = g_build_filename(util_get_runtime_dir(vb.config.profile), "socket", NULL); - util_create_dir_if_not_exists(dir); - path = g_build_filename(dir, name, NULL); - g_free(dir); - - /* if the file already exists - remove this first */ - if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) { - unlink(path); - } - - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - g_warning("Can't create socket %s", path); - } - - /* prepare socket address */ - local.sun_family = AF_UNIX; - strcpy(local.sun_path, path); - - if (bind(sock, (struct sockaddr*)&local, sizeof(local)) != -1 - && listen(sock, 5) >= 0 - ) { - GIOChannel *chan = g_io_channel_unix_new(sock); - if (chan) { - g_io_channel_set_encoding(chan, NULL, NULL); - g_io_channel_set_buffered(chan, false); - g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc)socket_accept, chan); - /* don't free path - because we want to keep the value in - * vb.state.socket_path still accessible */ - vb.state.socket_path = path; - g_setenv("VIMB_SOCKET", path, true); - - return true; - } - } else { - g_warning("no bind"); - } - - g_warning("Could not listen on %s: %s", path, strerror(errno)); - g_free(path); - - return false; -} - -void io_cleanup(void) -{ - if (vb.state.socket_path) { - if (unlink(vb.state.socket_path) == -1) { - g_warning("Can't remove socket %s", vb.state.socket_path); - } - g_free(vb.state.socket_path); - vb.state.socket_path = NULL; - } -} - -static gboolean socket_accept(GIOChannel *chan) -{ - struct sockaddr_un remote; - guint size = sizeof(remote); - GIOChannel *iochan; - int clientsock; - - clientsock = accept(g_io_channel_unix_get_fd(chan), (struct sockaddr *)&remote, &size); - - if ((iochan = g_io_channel_unix_new(clientsock))) { - g_io_channel_set_encoding(iochan, NULL, NULL); - g_io_add_watch(iochan, G_IO_IN|G_IO_HUP, (GIOFunc)socket_watch, iochan); - } - return true; -} - -static gboolean socket_watch(GIOChannel *chan) -{ - GIOStatus ret; - GError *error = NULL; - char *line, *inputtext; - gsize len; - - ret = g_io_channel_read_line(chan, &line, &len, NULL, &error); - if (ret == G_IO_STATUS_ERROR || ret == G_IO_STATUS_EOF) { - if (ret == G_IO_STATUS_ERROR) { - g_warning("Error reading: %s", error->message); - g_error_free(error); - } - - /* shutdown and remove the client channel */ - ret = g_io_channel_shutdown(chan, true, &error); - g_io_channel_unref(chan); - - if (ret == G_IO_STATUS_ERROR) { - g_warning("Error closing: %s", error->message); - g_error_free(error); - } - return false; - } - - /* simulate the typed flag to allow to record the commands in history */ - vb.state.typed = true; - - /* run the commands */ - map_handle_string(line, true); - g_free(line); - - /* unset typed flag */ - vb.state.typed = false; - - /* We assume that the commands result is still available in the inputbox, - * so the whole inputbox content is written to the socket. */ - inputtext = vb_get_input_text(); - ret = g_io_channel_write_chars(chan, inputtext, -1, &len, &error); - if (ret == G_IO_STATUS_ERROR) { - g_warning("Error writing: %s", error->message); - g_error_free(error); - } - if (g_io_channel_flush(chan, &error) == G_IO_STATUS_ERROR) { - g_warning("Error flushing: %s", error->message); - g_error_free(error); - } - - g_free(inputtext); - - return true; -} - -#endif diff --git a/src/js.c b/src/js.c index bd2d77d9..420a0597 100644 --- a/src/js.c +++ b/src/js.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,175 +17,36 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#include "js.h" - - -static gboolean evaluate_string(JSContextRef ctx, const char *script, - const char *file, JSValueRef *result); - -/** - * Run scripts out of given file in the given frame. - */ -gboolean js_eval_file(JSContextRef ctx, const char *file) -{ - char *js = NULL, *value = NULL; - - if (g_file_test(file, G_FILE_TEST_IS_REGULAR) - && g_file_get_contents(file, &js, NULL, NULL) - ) { - gboolean success = js_eval(ctx, js, file, &value); - if (!success) { - g_warning("JavaScript error in %s: %s", file, value); - } - g_free(value); - g_free(js); - - return success; - } - - return false; -} - -/** - * Evaluates given string as script and return if this call succeed or not. - * On success the given **value pointer is filled with the returned string, - * else with the exception message. In both cases this must be freed by the - * caller if no longer used. - */ -gboolean js_eval(JSContextRef ctx, const char *script, const char *file, - char **value) -{ - gboolean success; - JSValueRef result = NULL; - - success = evaluate_string(ctx, script, file, &result); - *value = js_ref_to_string(ctx, result); - - return success; -} - -/** - * Creates a JavaScript object in context of given frame. - */ -JSObjectRef js_create_object(JSContextRef ctx, const char *script) -{ - JSValueRef result = NULL, exc = NULL; - JSObjectRef object; - if (!evaluate_string(ctx, script, NULL, &result)) { - return NULL; - } - - object = JSValueToObject(ctx, result, &exc); - if (exc) { - return NULL; - } - JSValueProtect(ctx, result); - - return object; -} - -/** - * Calls a function on object and returns the result as newly allocates - * string. - * Returned string must be freed after use. - */ -char* js_object_call_function(JSContextRef ctx, JSObjectRef obj, - const char *func, int count, JSValueRef params[]) -{ - JSValueRef js_ret, function; - JSObjectRef function_object; - JSStringRef js_func = NULL; - char *value; - - g_return_val_if_fail(obj != NULL, NULL); - - js_func = JSStringCreateWithUTF8CString(func); - if (!JSObjectHasProperty(ctx, obj, js_func)) { - JSStringRelease(js_func); - - return NULL; - } - - function = JSObjectGetProperty(ctx, obj, js_func, NULL); - function_object = JSValueToObject(ctx, function, NULL); - js_ret = JSObjectCallAsFunction(ctx, function_object, NULL, count, params, NULL); - JSStringRelease(js_func); - - value = js_ref_to_string(ctx, js_ret); +#include +#include +#include - return value; -} +#include "js.h" +#include "config.h" /** - * Returns a new allocates string for given value reference. + * Returns a new allocates string for given value javascript result. * String must be freed if not used anymore. */ -char* js_ref_to_string(JSContextRef ctx, JSValueRef ref) +char *js_result_as_string(WebKitJavascriptResult *res) { - char *string; - size_t len; - JSStringRef str_ref; - - g_return_val_if_fail(ref != NULL, NULL); + JSGlobalContextRef cr; + JSStringRef jsstring; + JSValueRef jsvalue; + gsize max; - str_ref = JSValueToStringCopy(ctx, ref, NULL); - len = JSStringGetMaximumUTF8CStringSize(str_ref); + g_return_val_if_fail(res != NULL, NULL); - string = g_new0(char, len); - JSStringGetUTF8CString(str_ref, string, len); - JSStringRelease(str_ref); + jsvalue = webkit_javascript_result_get_value(res); + cr = webkit_javascript_result_get_global_context(res); + jsstring = JSValueToStringCopy(cr, jsvalue, NULL); + max = JSStringGetMaximumUTF8CStringSize(jsstring); + if (max > 0) { + char *string = g_new(char, max); + JSStringGetUTF8CString(jsstring, string, max); - return string; -} - -/** - * Retrieves a values reference for given string. - */ -JSValueRef js_string_to_ref(JSContextRef ctx, const char *string) -{ - JSStringRef js = JSStringCreateWithUTF8CString(string); - JSValueRef ref = JSValueMakeString(ctx, js); - JSStringRelease(js); - return ref; -} - -/** - * Retrieves a values reference for given json or array string string. - */ -JSValueRef js_object_to_ref(JSContextRef ctx, const char *json) -{ - JSValueRef ref = NULL; - if (evaluate_string(ctx, json, NULL, &ref)) { - return ref; + return string; } - g_warning("Could not parse %s", json); return NULL; } -/** - * Runs a string as JavaScript and returns if the call succeed. - * In case the call succeed, the given *result is filled with the result - * value, else with the value reference of the exception. - */ -static gboolean evaluate_string(JSContextRef ctx, const char *script, - const char *file, JSValueRef *result) -{ - JSStringRef js_str, js_file; - JSValueRef exc = NULL, res = NULL; - - js_str = JSStringCreateWithUTF8CString(script); - js_file = JSStringCreateWithUTF8CString(file); - - res = JSEvaluateScript(ctx, js_str, JSContextGetGlobalObject(ctx), js_file, 0, &exc); - JSStringRelease(js_file); - JSStringRelease(js_str); - - if (exc) { - *result = exc; - return false; - } - - *result = res; - return true; -} diff --git a/src/js.h b/src/js.h index 0983957a..76e73e50 100644 --- a/src/js.h +++ b/src/js.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,8 @@ #ifndef _JS_H #define _JS_H -#include "main.h" +#include -gboolean js_eval_file(JSContextRef ctx, const char *file); -gboolean js_eval(JSContextRef ctx, const char *script, const char *file, - char **value); -JSObjectRef js_create_object(JSContextRef ctx, const char *script); -char* js_object_call_function(JSContextRef ctx, JSObjectRef obj, - const char *func, int count, JSValueRef params[]); -char *js_ref_to_string(JSContextRef ctx, JSValueRef ref); -JSValueRef js_string_to_ref(JSContextRef ctx, const char *string); -JSValueRef js_object_to_ref(JSContextRef ctx, const char *json); +char *js_result_as_string(WebKitJavascriptResult *res); -#endif /* end of include guard: _JS_H */ +#endif diff --git a/src/js2h.sh b/src/js2h.sh deleted file mode 100755 index f849e858..00000000 --- a/src/js2h.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -echo '#define HINTS_JS "' | tr -d '\n' -cat $1 | \ - tr '\n\r\t' ' ' | \ - sed -e 's:/\*[^*]*\*/::g' \ - -e 's|[ ]\{2,\}| |g' \ - -e "s|[ ]\{0,\}\([-!?<>:=(){};+\&\"',\|]\)[ ]\{0,\}|\1|g" \ - -e 's|"+"||g' \ - -e 's|\\x20| |g' \ - -e 's|\\|\\\\|g' \ - -e 's|"|\\"|g' \ - -e 's|HINT_CSS|" HINT_CSS "|' \ - -e '$s/$/"/' -echo "" diff --git a/src/main.c b/src/main.c index 9c63baca..5fc4e203 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,138 +17,177 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include "main.h" -#include "util.h" +#include + +#include "ascii.h" #include "command.h" -#include "setting.h" #include "completion.h" -#include "dom.h" -#include "hints.h" -#include "shortcut.h" -#include "handlers.h" -#include "history.h" -#include "cookiejar.h" -#include "hsts.h" -#include "normal.h" +#include "config.h" #include "ex.h" +#include "ext-proxy.h" +#include "handler.h" +#include "history.h" #include "input.h" -#include "map.h" -#include "bookmark.h" #include "js.h" -#include "autocmd.h" -#include "arh.h" -#include "io.h" -#include "ascii.h" - -/* variables */ -static char *argv0; -VbCore vb; - -/* callbacks */ +#include "main.h" +#include "map.h" +#include "normal.h" +#include "setting.h" +#include "shortcut.h" +#include "util.h" -static void buffer_changed_cb(GtkTextBuffer* buffer, gpointer data); -#if WEBKIT_CHECK_VERSION(1, 10, 0) -static gboolean context_menu_cb(WebKitWebView *view, GtkWidget *menu, - WebKitHitTestResult *hitTestResult, gboolean keyboard, gpointer data); -#else -static void context_menu_cb(WebKitWebView *view, GtkMenu *menu, gpointer data); -#endif -static void context_menu_activate_cb(GtkMenuItem *item, gpointer data); -static void uri_change_cb(WebKitWebView *view, GParamSpec param_spec); -static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec); -static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec); -static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec); -static void webview_request_starting_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitWebResource *res, WebKitNetworkRequest *req, - WebKitNetworkResponse *resp, gpointer data); -static gboolean focus_out_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data); -static gboolean focus_in_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data); -static void destroy_window_cb(GtkWidget *widget); -static void scroll_cb(GtkAdjustment *adjustment); -static gboolean input_focus_in_cb(GtkWidget *widget, GdkEventFocus *event, - gpointer data); -static WebKitWebView *inspector_new(WebKitWebInspector *inspector, WebKitWebView *webview); -static gboolean inspector_show(WebKitWebInspector *inspector); -static gboolean inspector_close(WebKitWebInspector *inspector); -static void inspector_finished(WebKitWebInspector *inspector); -static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event); -static gboolean new_window_policy_cb( - WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *navig, WebKitWebPolicyDecision *policy); -static WebKitWebView *create_web_view_cb(WebKitWebView *view, WebKitWebFrame *frame); -static gboolean create_web_view_received_uri_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data); -static gboolean navigation_decision_requested_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data); -static void onload_event_cb(WebKitWebView *view, WebKitWebFrame *frame, - gpointer user_data); -static void hover_link_cb(WebKitWebView *webview, const char *title, const char *link); -static void title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, const char *title); -static gboolean mimetype_decision_cb(WebKitWebView *webview, - WebKitWebFrame *frame, WebKitNetworkRequest *request, char* - mime_type, WebKitWebPolicyDecision *decision); -gboolean vb_download(WebKitWebView *view, WebKitDownload *download, const char *path); -void vb_download_internal(WebKitWebView *view, WebKitDownload *download, const char *file); -void vb_download_external(WebKitWebView *view, WebKitDownload *download, const char *file); -static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec); -static void read_from_stdin(void); -#ifdef FEATURE_ARH -static void session_request_queued_cb(SoupSession *session, SoupMessage *msg, gpointer data); +static void client_destroy(Client *c); +static Client *client_new(WebKitWebView *webview, gboolean); +static gboolean input_clear(Client *c); +static void input_print(Client *c, gboolean force, MessageType type, + gboolean hide, const char *message); +static void marks_clear(Client *c); +static void mode_free(Mode *mode); +static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data); +static void on_webctx_download_started(WebKitWebContext *webctx, + WebKitDownload *download, Client *c); +static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data); +static gboolean on_webdownload_decide_destination(WebKitDownload *download, + gchar *suggested_filename, Client *c); +static void on_webdownload_failed(WebKitDownload *download, + GError *error, Client *c); +static void on_webdownload_finished(WebKitDownload *download, Client *c); +static void on_webdownload_received_data(WebKitDownload *download, + guint64 data_length, Client *c); +static void on_webview_close(WebKitWebView *webview, Client *c); +static WebKitWebView *on_webview_create(WebKitWebView *webview, + WebKitNavigationAction *navact, Client *c); +static gboolean on_webview_decide_policy(WebKitWebView *webview, + WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c); +static void on_webview_load_changed(WebKitWebView *webview, + WebKitLoadEvent event, Client *c); +static void on_webview_mouse_target_changed(WebKitWebView *webview, + WebKitHitTestResult *result, guint modifiers, Client *c); +static void on_webview_notify_estimated_load_progress(WebKitWebView *webview, + GParamSpec *spec, Client *c); +static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec, + Client *c); +static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec, + Client *c); +static void on_webview_ready_to_show(WebKitWebView *webview, Client *c); +static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c); +static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c); +static void on_window_destroy(GtkWidget *window, Client *c); +static gboolean quit(Client *c); +static void read_from_stdin(Client *c); +static void register_cleanup(Client *c); +static void update_title(Client *c); +static void update_urlbar(Client *c); +static void set_statusbar_style(Client *c, StatusType type); +static void set_title(Client *c, const char *title); +static void spawn_new_instance(const char *uri, gboolean embed); +#ifdef FREE_ON_QUIT +static void vimb_cleanup(void); #endif +static void vimb_setup(void); +static WebKitWebView *webview_new(Client *c, WebKitWebView *webview); +static void on_script_message_focus(WebKitUserContentManager *manager, + WebKitJavascriptResult *res, gpointer data); +static gboolean profileOptionArgFunc(const gchar *option_name, + const gchar *value, gpointer data, GError **error); -/* functions */ -#ifdef FEATURE_WGET_PROGRESS_BAR -static void wget_bar(int len, int progress, char *string); -#endif -static void update_title(void); -static void set_uri(const char *uri); -static void set_title(const char *title); -static void init_core(void); -static void marks_clear(void); -static void setup_signals(); -static void init_files(void); -static void session_init(void); -static void session_cleanup(void); -static void register_init(void); -static void register_cleanup(void); -static gboolean hide_message(); -static void set_status(const StatusType status); -static void input_print(gboolean force, const MessageType type, gboolean hide, const char *message); -static void vb_cleanup(void); -static void cleanup_modes(void); -static void free_mode(Mode *mode); +struct Vimb vb; /** - * Creates a new mode with given callback functions. + * Set the destination for a download according to suggested file name and + * possible given path. */ -void vb_add_mode(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, - ModeKeyFunc keypress, ModeInputChangedFunc input_changed) +gboolean vb_download_set_destination(Client *c, WebKitDownload *download, + char *suggested_filename, const char *path) { - Mode *new = g_slice_new(Mode); - new->id = id; - new->enter = enter; - new->leave = leave; - new->keypress = keypress; - new->input_changed = input_changed; - new->flags = 0; + char *download_path, *dir, *file, *uri; + download_path = GET_CHAR(c, "download-path"); - /* Initialize the hashmap if this was not done before */ - if (!vb.modes) { - vb.modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)free_mode); + /* For unnamed downloads set default filename. */ + if (!suggested_filename || !*suggested_filename) { + suggested_filename = "vimb-download"; } - g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new); + + /* Prepare the path to save the download. */ + if (path && *path) { + file = util_build_path(c, path, download_path); + + /* if file is an directory append a file name */ + if (g_file_test(file, (G_FILE_TEST_IS_DIR))) { + dir = file; + file = g_build_filename(dir, suggested_filename, NULL); + g_free(dir); + } + } else { + file = util_build_path(c, suggested_filename, download_path); + } + + if (!file) { + return FALSE; + } + + /* If the filepath exists already insert numerical suffix before file + * extension. */ + if (g_file_test(file, G_FILE_TEST_EXISTS)) { + const char *dot_pos; + char *num = NULL; + GString *tmp; + gssize suffix; + int i = 1; + + /* position on .tar. (special case, extension with two dots), + * position on last dot (if any) otherwise */ + if (!(dot_pos = strstr(file, ".tar."))) { + dot_pos = strrchr(file, '.'); + } + + /* the position to insert the suffix at */ + if (dot_pos) { + suffix = dot_pos - file; + } else { + suffix = strlen(file); + } + + tmp = g_string_new(NULL); + + /* Construct a new complete download filepath with suffix before the + * file extension. */ + do { + num = g_strdup_printf("_%d", i++); + g_string_assign(tmp, file); + g_string_insert(tmp, suffix, num); + g_free(num); + } while (g_file_test(tmp->str, G_FILE_TEST_EXISTS)); + + file = g_strdup(tmp->str); + g_string_free(tmp, TRUE); + } + + /* Build URI from filepath. */ + uri = g_filename_to_uri(file, NULL, NULL); + g_free(file); + + /* configure download */ + g_assert(uri); + webkit_download_set_allow_overwrite(download, FALSE); + webkit_download_set_destination(download, uri); + g_free(uri); + + return TRUE; } -void vb_echo_force(const MessageType type, gboolean hide, const char *error, ...) +/** + * Write text to the inpubox if this isn't focused. + */ +void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...) { char *buffer; va_list args; @@ -157,11 +196,15 @@ void vb_echo_force(const MessageType type, gboolean hide, const char *error, ... buffer = g_strdup_vprintf(error, args); va_end(args); - input_print(true, type, hide, buffer); + input_print(c, FALSE, type, hide, buffer); g_free(buffer); } -void vb_echo(const MessageType type, gboolean hide, const char *error, ...) +/** + * Write text to the inpubox independent if this is focused or not. + * Note that this could disturb the user during typing into inputbox. + */ +void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...) { char *buffer; va_list args; @@ -170,28 +213,28 @@ void vb_echo(const MessageType type, gboolean hide, const char *error, ...) buffer = g_strdup_vprintf(error, args); va_end(args); - input_print(false, type, hide, buffer); + input_print(c, TRUE, type, hide, buffer); g_free(buffer); } /** * Enter into the new given mode and leave possible active current mode. */ -void vb_enter(char id) +void vb_enter(Client *c, char id) { Mode *new = g_hash_table_lookup(vb.modes, GINT_TO_POINTER(id)); g_return_if_fail(new != NULL); - if (vb.mode) { + if (c->mode) { /* don't do anything if the mode isn't a new one */ - if (vb.mode == new) { + if (c->mode == new) { return; } /* if there is a active mode, leave this first */ - if (vb.mode->leave) { - vb.mode->leave(); + if (c->mode->leave) { + c->mode->leave(c); } } @@ -199,14 +242,14 @@ void vb_enter(char id) new->flags = 0; /* set the new mode so that it is available also in enter function */ - vb.mode = new; + c->mode = new; /* call enter only if the new mode isn't the current mode */ if (new->enter) { - new->enter(); + new->enter(c); } #ifndef TESTLIB - vb_update_statusbar(); + vb_statusbar_update(c); #endif } @@ -218,111 +261,84 @@ void vb_enter(char id) * @print_prompt: Indicates if the new set prompt should be put into inputbox * after switching the mode. */ -void vb_enter_prompt(char id, const char *prompt, gboolean print_prompt) +void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt) { /* set the prompt to be accessible in vb_enter */ - strncpy(vb.state.prompt, prompt, PROMPT_SIZE - 1); - vb.state.prompt[PROMPT_SIZE - 1] = '\0'; + strncpy(c->state.prompt, prompt, PROMPT_SIZE - 1); + c->state.prompt[PROMPT_SIZE - 1] = '\0'; - vb_enter(id); + vb_enter(c, id); if (print_prompt) { /* set it after the mode was entered so that the modes input change * event listener could grep the new prompt */ - vb_echo_force(VB_MSG_NORMAL, false, vb.state.prompt); + vb_echo_force(c, MSG_NORMAL, FALSE, c->state.prompt); } } -VbResult vb_handle_key(int key) +/** + * Returns the client for given page id. + */ +Client *vb_get_client_for_page_id(guint64 pageid) { - VbResult res; - static gboolean ctrl_v = false; + Client *c; + /* Search for the client with the same page id. */ + for (c = vb.clients; c && c->page_id != pageid; c = c->next); - if (ctrl_v) { - vb.state.processed_key = false; - ctrl_v = false; - - return RESULT_COMPLETE; - } - if (vb.mode->id != 'p' && key == CTRL('V')) { - vb.mode->flags |= FLAG_NOMAP; - ctrl_v = true; - - return RESULT_MORE; - } - - if (vb.mode && vb.mode->keypress) { -#ifdef DEBUG - int flags = vb.mode->flags; - int id = vb.mode->id; - res = vb.mode->keypress(key); - if (vb.mode) { - PRINT_DEBUG( - "%c[%d]: %#.2x '%c' -> %c[%d]", - id - ' ', flags, key, (key >= 0x20 && key <= 0x7e) ? key : ' ', - vb.mode->id - ' ', vb.mode->flags - ); - } -#else - res = vb.mode->keypress(key); -#endif - return res; + if (c) { + return c; } - return RESULT_ERROR; + return NULL; } -static void input_print(gboolean force, const MessageType type, gboolean hide, - const char *message) +/** + * Retrieves the content of the command line. + * Returned string must be freed with g_free. + */ +char *vb_input_get_text(Client *c) { - static guint timer = 0; - - /* don't print message if the input is focussed */ - if (!force && gtk_widget_is_focus(GTK_WIDGET(vb.gui.input))) { - return; - } + GtkTextIter start, end; - /* apply input style only if the message type was changed */ - if (type != vb.state.input_type) { - vb.state.input_type = type; - vb_update_input_style(); - } - vb_set_input_text(message); - if (hide) { - /* add timeout function */ - timer = g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)hide_message, NULL); - } else if (timer > 0) { - /* If there is already a timeout function but the input box content is - * changed - remove the timeout. Seems the user started another - * command or typed into inputbox. */ - g_source_remove(timer); - timer = 0; - } + gtk_text_buffer_get_bounds(c->buffer, &start, &end); + return gtk_text_buffer_get_text(c->buffer, &start, &end, FALSE); } /** * Writes given text into the command line. */ -void vb_set_input_text(const char *text) +void vb_input_set_text(Client *c, const char *text) { - gtk_text_buffer_set_text(vb.gui.buffer, text, -1); - if (vb.config.input_autohide) { - gtk_widget_set_visible(GTK_WIDGET(vb.gui.input), *text != '\0'); + gtk_text_buffer_set_text(c->buffer, text, -1); + if (c->config.input_autohide) { + gtk_widget_set_visible(GTK_WIDGET(c->input), *text != '\0'); } } /** - * Retrieves the content of the command line. - * Returned string must be freed with g_free. + * Set the style of the inputbox according to current input type (normal or + * error). */ -char *vb_get_input_text(void) +void vb_input_update_style(Client *c) { - GtkTextIter start, end; + MessageType type = c->state.input_type; - gtk_text_buffer_get_bounds(vb.gui.buffer, &start, &end); - return gtk_text_buffer_get_text(vb.gui.buffer, &start, &end, false); + if (type == MSG_ERROR) { + gtk_style_context_add_class(gtk_widget_get_style_context(c->input), "error"); + } else { + gtk_style_context_remove_class(gtk_widget_get_style_context(c->input), "error"); + } } -gboolean vb_load_uri(const Arg *arg) +/** + * Load the a uri given in Arg. This function handles also shortcuts and local + * file paths. + * + * If arg.i = TARGET_CURRENT, the url is opened into the current webview. + * TARGET_RELATED causes the generation of a new window within the current + * instance of vimb with a own, but related webview. And TARGET_NEW spawns a + * new instance of vimb with the given uri. + */ +gboolean vb_load_uri(Client *c, const Arg *arg) { char *uri = NULL, *rp, *path = NULL; struct stat st; @@ -331,7 +347,7 @@ gboolean vb_load_uri(const Arg *arg) path = g_strstrip(arg->s); } if (!path || !*path) { - path = GET_CHAR("home-page"); + path = GET_CHAR(c, "home-page"); } /* If path contains :// but no space we open it direct. This is required @@ -345,1591 +361,1451 @@ gboolean vb_load_uri(const Arg *arg) free(rp); } else if (strchr(path, ' ') || !strchr(path, '.')) { /* use a shortcut if path contains spaces or no dot */ - uri = shortcut_get_uri(path); + uri = shortcut_get_uri(c, path); } if (!uri) { uri = g_strconcat("http://", path, NULL); } - if (arg->i == VB_TARGET_NEW) { - guint i = 0; + if (arg->i == TARGET_CURRENT) { + /* Load the uri into the browser instance. */ + webkit_web_view_load_uri(c->webview, uri); + set_title(c, uri); + } else if (arg->i == TARGET_NEW) { + spawn_new_instance(uri, TRUE); + } else { /* TARGET_RELATET */ + Client *newclient = client_new(c->webview, FALSE); + /* Load the uri into the new client. */ + webkit_web_view_load_uri(newclient->webview, uri); + set_title(c, uri); + } + g_free(uri); - /* memory allocation */ - char **cmd = g_malloc_n( - 3 /* basename + uri + ending NULL */ -#ifndef FEATURE_NO_XEMBED - + (vb.embed ? 2 : 0) -#endif - + (vb.config.file ? 2 : 0) - + (vb.config.profile ? 2 : 0) - + (vb.config.kioskmode ? 1 : 0) -#ifdef FEATURE_SOCKET - + (vb.config.socket ? 1 : 0) -#endif - + g_slist_length(vb.config.cmdargs) * 2, - sizeof(char *) - ); + return TRUE; +} - /* build commandline */ - cmd[i++] = argv0; -#ifndef FEATURE_NO_XEMBED - if (vb.embed) { - char xid[64]; - snprintf(xid, LENGTH(xid), "%u", (int)vb.embed); - cmd[i++] = "-e"; - cmd[i++] = xid; - } -#endif - if (vb.config.file) { - cmd[i++] = "-c"; - cmd[i++] = vb.config.file; - } - if (vb.config.profile) { - cmd[i++] = "-p"; - cmd[i++] = vb.config.profile; - } - for (GSList *l = vb.config.cmdargs; l; l = l->next) { - cmd[i++] = "-C"; - cmd[i++] = l->data; - } - if (vb.config.kioskmode) { - cmd[i++] = "-k"; - } -#ifdef FEATURE_SOCKET - if (vb.config.socket) { - cmd[i++] = "-s"; - } -#endif - cmd[i++] = uri; - cmd[i++] = NULL; +/** + * Creates and add a new mode with given callback functions. + */ +void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, + ModeKeyFunc keypress, ModeInputChangedFunc input_changed) +{ + Mode *new = g_slice_new(Mode); + new->id = id; + new->enter = enter; + new->leave = leave; + new->keypress = keypress; + new->input_changed = input_changed; + new->flags = 0; + + /* Initialize the hashmap if this was not done before */ + if (!vb.modes) { + vb.modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)mode_free); + } + g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new); +} + +VbResult vb_mode_handle_key(Client *c, int key) +{ + VbResult res; - /* spawn a new browser instance */ - g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); + if (c->state.ctrlv) { + c->state.processed_key = FALSE; + c->state.ctrlv = FALSE; - /* free commandline */ - g_free(cmd); - } else { - /* Load a web page into the browser instance */ - webkit_web_view_load_uri(vb.gui.webview, uri); - /* show the url to be opened in the window title until we receive the - * page title */ - set_title(uri); + return RESULT_COMPLETE; + } + if (c->mode->id != 'p' && key == CTRL('V')) { + c->mode->flags |= FLAG_NOMAP; + c->state.ctrlv = TRUE; + + return RESULT_MORE; } - g_free(uri); - return true; + if (c->mode && c->mode->keypress) { +#ifdef DEBUGDISABLED + int flags = c->mode->flags; + int id = c->mode->id; + res = c->mode->keypress(c, key); + if (c->mode) { + PRINT_DEBUG( + "%c[%d]: %#.2x '%c' -> %c[%d]", + id - ' ', flags, key, (key >= 0x20 && key <= 0x7e) ? key : ' ', + c->mode->id - ' ', c->mode->flags + ); + } +#else + res = c->mode->keypress(c, key); +#endif + return res; + } + return RESULT_ERROR; } -gboolean vb_set_clipboard(const Arg *arg) +/** + * Change the label for the current mode in inputbox or on the left of + * statusbar if inputbox is in autohide mode. + */ +void vb_modelabel_update(Client *c, const char *label) { - gboolean result = false; - if (!arg->s) { - return result; + if (c->config.input_autohide) { + /* if the inputbox is potentially not shown write mode into statusbar */ + gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label); + } else { + vb_echo(c, MSG_NORMAL, FALSE, "%s", label); } +} - if (arg->i & VB_CLIPBOARD_PRIMARY) { - gtk_clipboard_set_text(PRIMARY_CLIPBOARD(), arg->s, -1); - result = true; - } - if (arg->i & VB_CLIPBOARD_SECONDARY) { - gtk_clipboard_set_text(SECONDARY_CLIPBOARD(), arg->s, -1); - result = true; +/** + * Close the given client instances window. + */ +gboolean vb_quit(Client *c, gboolean force) +{ + /* if not forced quit - don't quit if there are still running downloads */ + if (!force && c->state.downloads) { + vb_echo_force(c, MSG_ERROR, TRUE, "Can't quit: there are running downloads. Use :q! to force quit"); + return FALSE; } - return result; + /* Don't run the quit synchronously, because this could lead to access of + * no more existing widget where some command response is written. */ + g_idle_add((GSourceFunc)quit, c); + + return TRUE; } -void vb_set_widget_font(GtkWidget *widget, const VbColor *fg, const VbColor *bg, PangoFontDescription *font) +/** + * Adds content to a named register. + */ +void vb_register_add(Client *c, char buf, const char *value) { - VB_WIDGET_OVERRIDE_FONT(widget, font); - VB_WIDGET_OVERRIDE_TEXT(widget, VB_GTK_STATE_NORMAL, fg); - VB_WIDGET_OVERRIDE_COLOR(widget, VB_GTK_STATE_NORMAL, fg); - VB_WIDGET_OVERRIDE_BASE(widget, VB_GTK_STATE_NORMAL, bg); - VB_WIDGET_OVERRIDE_BACKGROUND(widget, VB_GTK_STATE_NORMAL, bg); + char *mark; + int idx; + + if (!c->state.enable_register || !buf) { + return; + } + + /* make sure the mark is a valid mark char */ + if ((mark = strchr(REG_CHARS, buf))) { + /* get the index of the mark char */ + idx = mark - REG_CHARS; + + OVERWRITE_STRING(c->state.reg[idx], value); + } } -#ifdef FEATURE_WGET_PROGRESS_BAR -static void wget_bar(int len, int progress, char *string) +/** + * Lookup register entry by it's name. + */ +const char *vb_register_get(Client *c, char buf) { - int i, state; + char *mark; + int idx; + + /* make sure the mark is a valid mark char */ + if ((mark = strchr(REG_CHARS, buf))) { + /* get the index of the mark char */ + idx = mark - REG_CHARS; - state = progress * len / 100; - for (i = 0; i < state; i++) { - string[i] = PROGRESS_BAR[0]; + return c->state.reg[idx]; } - string[i++] = PROGRESS_BAR[1]; - for (; i < len; i++) { - string[i] = PROGRESS_BAR[2]; + + return NULL; +} + +static void statusbar_update_downloads(Client *c, GString *status) +{ + GList *list; + guint list_length, remaining_max = 0; + gdouble progress, elapsed, total, remaining; + WebKitDownload *download; + + g_assert(c); + g_assert(status); + + if (c->state.downloads) { + list_length = g_list_length(c->state.downloads); + g_assert(list_length); + + /* get highest ETA value of all downloads based on each download's + * current progress fraction and time elapsed */ + for (list = c->state.downloads; list != NULL; list = list->next) { + download = (WebKitDownload *)list->data; + g_assert(download); + + progress = webkit_download_get_estimated_progress(download); + + /* avoid dividing by zero */ + if (progress == 0.0) { + continue; + } + + elapsed = webkit_download_get_elapsed_time(download); + total = (1.0 / progress) * elapsed; + remaining = total - elapsed; + + remaining_max = MAX(remaining, remaining_max); + } + + g_string_append_printf(status, " %d %s (ETA %us)", + list_length, list_length == 1? "dnld" : "dnlds", remaining_max); } - string[i] = '\0'; } -#endif -void vb_update_statusbar() +void vb_statusbar_update(Client *c) { - int max, val, num; GString *status; - if (!gtk_widget_get_visible(GTK_WIDGET(vb.gui.statusbar.box))) { + if (!gtk_widget_get_visible(GTK_WIDGET(c->statusbar.box))) { return; } status = g_string_new(""); - /* show the active downloads */ - if (vb.state.downloads) { - num = g_list_length(vb.state.downloads); - g_string_append_printf(status, " %d %s", num, num == 1 ? "download" : "downloads"); - } /* show the number of matches search results */ - if (vb.state.search_matches) { - g_string_append_printf(status, " (%d)", vb.state.search_matches); + if (c->state.search.matches) { + g_string_append_printf(status, " (%d)", c->state.search.matches); } /* show load status of page or the downloads */ - if (vb.state.progress != 100) { + if (c->state.progress != 100) { #ifdef FEATURE_WGET_PROGRESS_BAR char bar[PROGRESS_BAR_LEN + 1]; - wget_bar(PROGRESS_BAR_LEN, vb.state.progress, bar); + int i, state; + + state = c->state.progress * PROGRESS_BAR_LEN / 100; + for (i = 0; i < state; i++) { + bar[i] = PROGRESS_BAR[0]; + } + bar[i++] = PROGRESS_BAR[1]; + for (; i < PROGRESS_BAR_LEN; i++) { + bar[i] = PROGRESS_BAR[2]; + } + bar[i] = '\0'; g_string_append_printf(status, " [%s]", bar); #else - g_string_append_printf(status, " [%i%%]", vb.state.progress); + g_string_append_printf(status, " [%i%%]", c->state.progress); #endif } - /* show the scroll status */ - max = gtk_adjustment_get_upper(vb.gui.adjust_v) - gtk_adjustment_get_page_size(vb.gui.adjust_v); - val = (int)(0.5 + (gtk_adjustment_get_value(vb.gui.adjust_v) / max * 100)); + statusbar_update_downloads(c, status); - if (max == 0) { + /* show the scroll status */ + if (c->state.scroll_max == 0) { g_string_append(status, " All"); - } else if (val == 0) { + } else if (c->state.scroll_percent == 0) { g_string_append(status, " Top"); - } else if (val >= 100) { + } else if (c->state.scroll_percent == 100) { g_string_append(status, " Bot"); } else { - g_string_append_printf(status, " %d%%", val); + g_string_append_printf(status, " %d%%", c->state.scroll_percent); } - gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.right), status->str); - g_string_free(status, true); -} - -void vb_update_status_style(void) -{ - StatusType type = vb.state.status_type; - vb_set_widget_font( - vb.gui.eventbox, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type] - ); -#ifndef HAS_GTK3 - vb_set_widget_font( - vb.gui.statusbar.mode, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type] - ); - vb_set_widget_font( - vb.gui.statusbar.left, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type] - ); - vb_set_widget_font( - vb.gui.statusbar.right, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type] - ); - vb_set_widget_font( - vb.gui.statusbar.cmd, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type] - ); -#endif + gtk_label_set_text(GTK_LABEL(c->statusbar.right), status->str); + g_string_free(status, TRUE); } -void vb_update_input_style(void) +/** + * Destroys given client and removed it from client queue. If no client is + * there in queue, quit the gtk main loop. + */ +static void client_destroy(Client *c) { - MessageType type = vb.state.input_type; - vb_set_widget_font( - vb.gui.input, &vb.style.input_fg[type], &vb.style.input_bg[type], vb.style.input_font[type] - ); -} + Client *p; + webkit_web_view_stop_loading(c->webview); -void vb_update_urlbar(const char *uri) -{ - Gui *gui = &vb.gui; -#if !defined(FEATURE_HISTORY_INDICATOR) && !defined(FEATURE_PROFILE_INDICATOR) - /* if only the uri is shown - write it like it is on the label */ - gtk_label_set_text(GTK_LABEL(gui->statusbar.left), uri); -#else - GString *str = g_string_new(""); -#ifdef FEATURE_PROFILE_INDICATOR - if (vb.config.profile) { - g_string_append_printf(str, "[%s] ", vb.config.profile); + /* Write last URL into file for recreation. + * The URL is only stored if the closed-max-items is not 0 and the file + * exists. */ + if (c->state.uri && vb.config.closed_max && vb.files[FILES_CLOSED]) { + util_file_prepend_line(vb.files[FILES_CLOSED], c->state.uri, + vb.config.closed_max); } -#endif /* FEATURE_PROFILE_INDICATOR */ - g_string_append_printf(str, "%s", uri); + gtk_widget_destroy(c->window); -#ifdef FEATURE_HISTORY_INDICATOR - gboolean back, fwd; + /* Look for the client in the list, if we searched through the list and + * didn't find it the client must be the first item. */ + for (p = vb.clients; p && p->next != c; p = p->next); + if (p) { + p->next = c->next; + } else { + vb.clients = c->next; + } - back = webkit_web_view_can_go_back(gui->webview); - fwd = webkit_web_view_can_go_forward(gui->webview); + completion_cleanup(c); + map_cleanup(c); + register_cleanup(c); + setting_cleanup(c); + handler_cleanup(c); - /* show history indicator only if there is something to show */ - if (back || fwd) { - g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+"); - } -#endif /* FEATURE_HISTORY_INDICATOR */ + g_slice_free(Client, c); - gtk_label_set_text(GTK_LABEL(gui->statusbar.left), str->str); - g_string_free(str, true); -#endif /* !defined(FEATURE_HISTORY_INDICATOR) && !defined(FEATURE_PROFILE_INDICATOR) */ + /* if there are no clients - quit the main loop */ + if (!vb.clients) { + gtk_main_quit(); + } } -void vb_update_mode_label(const char *label) +/** + * Creates a new client instance with it's own window. + * + * @webview: Related webview or NULL if a client with an independent + * webview shoudl be created. + */ +static Client *client_new(WebKitWebView *webview, gboolean show) { - if (GET_BOOL("input-autohide")) { - /* if the inputbox is potentially not shown write mode into statusbar */ - gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.mode), label); + Client *c; + char *xid; + GtkWidget *box; + + /* create the client */ + c = g_slice_new0(Client); + c->state.progress = 100; + + if (vb.embed) { + c->window = gtk_plug_new(vb.embed); + xid = g_strdup_printf("%d", (int)vb.embed); } else { - vb_echo(VB_MSG_NORMAL, false, "%s", label); - } -} + GtkRequisition req; + c->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_role(GTK_WINDOW(c->window), PROJECT_UCFIRST); + /* We have to call gtk_widget_get_preferred_size before + * gtk_widget_size_allocate otherwise a warning is thrown when the + * widget is realized. */ + gtk_widget_get_preferred_size(GTK_WIDGET(c->window), &req, NULL); + gtk_widget_realize(GTK_WIDGET(c->window)); -void vb_quit(gboolean force) -{ - /* if not forced quit - don't quit if there are still running downloads */ - if (!force && vb.state.downloads) { - vb_echo_force(VB_MSG_ERROR, true, "Can't quit: there are running downloads"); - return; + xid = g_strdup_printf("%d", + (int)GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(c->window)))); } - webkit_web_view_stop_loading(vb.gui.webview); + gtk_window_set_default_size(GTK_WINDOW(c->window), WIN_WIDTH, WIN_HEIGHT); - /* write last URL into file for recreation */ - if (vb.state.uri && vb.config.closed_max) { - char **lines = util_get_lines(vb.files[FILES_CLOSED]); - GString *new = g_string_new(vb.state.uri); - g_string_append(new, "\n"); - if (lines) { - int len = g_strv_length(lines); - int i; - for (i = 0; i < len - 1 && i < vb.config.closed_max - 1; i++) { - g_string_append_printf(new, "%s\n", lines[i]); - } - g_strfreev(lines); - } - g_file_set_contents(vb.files[FILES_CLOSED], new->str, -1, NULL); - g_string_free(new, true); - } + completion_init(c); + map_init(c); - gtk_main_quit(); -} + g_object_connect( + G_OBJECT(c->window), + "signal::destroy", G_CALLBACK(on_window_destroy), c, + "signal::delete-event", G_CALLBACK(on_window_delete_event), c, + "signal::key-press-event", G_CALLBACK(on_map_keypress), c, + NULL); + + /* statusbar */ + c->statusbar.box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); + c->statusbar.mode = gtk_label_new(NULL); + c->statusbar.left = gtk_label_new(NULL); + c->statusbar.right = gtk_label_new(NULL); + c->statusbar.cmd = gtk_label_new(NULL); + gtk_widget_set_name(GTK_WIDGET(c->statusbar.box), "statusbar"); + gtk_label_set_ellipsize(GTK_LABEL(c->statusbar.left), PANGO_ELLIPSIZE_MIDDLE); + gtk_widget_set_halign(c->statusbar.left, GTK_ALIGN_START); + gtk_widget_set_halign(c->statusbar.mode, GTK_ALIGN_START); + + gtk_box_pack_start(c->statusbar.box, c->statusbar.mode, FALSE, TRUE, 0); + gtk_box_pack_start(c->statusbar.box, c->statusbar.left, TRUE, TRUE, 2); + gtk_box_pack_start(c->statusbar.box, c->statusbar.cmd, FALSE, FALSE, 0); + gtk_box_pack_start(c->statusbar.box, c->statusbar.right, FALSE, FALSE, 2); + + /* webview */ + c->webview = webview_new(c, webview); + c->page_id = webkit_web_view_get_page_id(c->webview); + + /* inputbox */ + c->input = gtk_text_view_new(); + gtk_widget_set_name(c->input, "input"); + c->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(c->input)); + g_signal_connect(c->buffer, "changed", G_CALLBACK(on_textbuffer_changed), c); + /* Make sure the user can see the typed text. */ + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(c->input), GTK_WRAP_WORD_CHAR); + + /* pack the parts together */ + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(c->window), box); + gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->webview), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->statusbar.box), FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(c->input), FALSE, FALSE, 0); + + /* Set the default style for statusbar and inputbox. */ + gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)), + GTK_STYLE_PROVIDER(vb.style_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_style_context_add_provider(gtk_widget_get_style_context(c->input), + GTK_STYLE_PROVIDER(vb.style_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + /* set the x window id to env */ + g_setenv("VIMB_XID", xid, TRUE); + g_free(xid); -static gboolean hide_message() -{ - input_print(false, VB_MSG_NORMAL, false, ""); + /* initialize the settings */ + setting_init(c); - return false; -} + /* initialize the url handlers */ + handler_init(c); -/** - * Process input changed event on current active mode. - */ -static void buffer_changed_cb(GtkTextBuffer* buffer, gpointer data) -{ - char *text; - GtkTextIter start, end; - /* don't observe changes in completion mode */ - if (vb.mode->flags & FLAG_COMPLETION) { - return; - } - /* don't process changes not typed by the user */ - if (gtk_widget_is_focus(vb.gui.input) && vb.mode && vb.mode->input_changed) { - gtk_text_buffer_get_bounds(buffer, &start, &end); - text = gtk_text_buffer_get_text(buffer, &start, &end, false); + /* start client in normal mode */ + vb_enter(c, 'n'); - vb.mode->input_changed(text); + c->state.enable_register = TRUE; - g_free(text); + if (show) { + gtk_widget_show_all(c->window); } -} -#if WEBKIT_CHECK_VERSION(1, 10, 0) -static gboolean context_menu_cb(WebKitWebView *view, GtkWidget *menu, - WebKitHitTestResult *hitTestResult, gboolean keyboard, gpointer data) -{ - GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu))); - for (GList *l = items; l; l = l->next) { - g_signal_connect(l->data, "activate", G_CALLBACK(context_menu_activate_cb), NULL); - } - g_list_free(items); + /* read the config file */ + ex_run_file(c, vb.files[FILES_CONFIG]); + + /* Prepend the new client to the queue of clients. */ + c->next = vb.clients; + vb.clients = c; - return false; + return c; } -#else -static void context_menu_cb(WebKitWebView *view, GtkMenu *menu, gpointer data) + +/** + * Callback that clear the input box after a timeout if this was set on + * input_print. + */ +static gboolean input_clear(Client *c) { - GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu))); - for (GList *l = items; l; l = l->next) { - g_signal_connect(l->data, "activate", G_CALLBACK(context_menu_activate_cb), NULL); - } - g_list_free(items); + input_print(c, FALSE, MSG_NORMAL, FALSE, ""); + + return FALSE; } -#endif -static void context_menu_activate_cb(GtkMenuItem *item, gpointer data) +/** + * Print a message to the input box. + * + * @force: If TRUE the message is also written when the inputbox is already + * focused, might be the case when the user types text into it. + * @type: Type of message normal or error + * @hide: If TRUE the inputbox is cleared after a short timeout. + * @message: The message to print. + */ +static void input_print(Client *c, gboolean force, MessageType type, + gboolean hide, const char *message) { -#if WEBKIT_CHECK_VERSION(1, 10, 0) - WebKitContextMenuAction action = webkit_context_menu_item_get_action(item); - if (action == WEBKIT_CONTEXT_MENU_ACTION_COPY_LINK_TO_CLIPBOARD) { - vb_set_clipboard( - &((Arg){VB_CLIPBOARD_PRIMARY|VB_CLIPBOARD_SECONDARY, vb.state.linkhover}) - ); - } -#else - const char *name; - GtkAction *action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item)); - - if (!action) { + /* don't print message if the input is focussed */ + if (!force && gtk_widget_is_focus(GTK_WIDGET(c->input))) { return; } - name = gtk_action_get_name(action); - /* context-menu-action-3 copy link location */ - if (!g_strcmp0(name, "context-menu-action-3")) { - vb_set_clipboard( - &((Arg){VB_CLIPBOARD_PRIMARY|VB_CLIPBOARD_SECONDARY,vb.state.linkhover}) - ); + /* apply input style only if the message type was changed */ + if (type != c->state.input_type) { + c->state.input_type = type; + vb_input_update_style(c); + } + vb_input_set_text(c, message); + if (hide) { + /* add timeout function */ + c->state.input_timer = g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)input_clear, c); + } else if (c->state.input_timer > 0) { + /* If there is already a timeout function but the input box content is + * changed - remove the timeout. Seems the user started another + * command or typed into inputbox. */ + g_source_remove(c->state.input_timer); + c->state.input_timer = 0; } -#endif } -static void uri_change_cb(WebKitWebView *view, GParamSpec param_spec) +/** + * Reinitializes or clears the set page marks. + */ +static void marks_clear(Client *c) { - set_uri(webkit_web_view_get_uri(view)); -} + int i; -static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec) -{ - vb.state.progress = webkit_web_view_get_progress(view) * 100; - vb_update_statusbar(); - update_title(); + /* init empty marks array */ + for (i = 0; i < MARK_SIZE; i++) { + c->state.marks[i] = -1; + } } -static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec) +/** + * Free the memory of given mode. This is used as destroy function of the + * modes hashmap. + */ +static void mode_free(Mode *mode) { - if (vb.state.downloads) { - vb.state.progress = 0; - GList *ptr; - for (ptr = vb.state.downloads; ptr; ptr = g_list_next(ptr)) { - vb.state.progress += 100 * webkit_download_get_progress(ptr->data); - } - vb.state.progress /= g_list_length(vb.state.downloads); - } - vb_update_statusbar(); - update_title(); + g_slice_free(Mode, mode); } -static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) +/** + * The ::changed signal is emitted when the content of a GtkTextBuffer has + * changed. This call back function is connected to the input box' text buffer. + */ +static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data) { - const char *uri; - WebKitWebFrame *frame = webkit_web_view_get_main_frame(view); - - switch (webkit_web_view_get_load_status(view)) { - case WEBKIT_LOAD_PROVISIONAL: -#ifdef FEATURE_AUTOCMD - { - WebKitWebDataSource *src = webkit_web_frame_get_provisional_data_source(frame); - WebKitNetworkRequest *req = webkit_web_data_source_get_initial_request(src); - uri = webkit_network_request_get_uri(req); - autocmd_run(AU_LOAD_PROVISIONAL, uri, NULL); - } -#endif - /* update load progress in statusbar */ - vb.state.progress = 0; - vb_update_statusbar(); - update_title(); - break; - - case WEBKIT_LOAD_COMMITTED: - uri = webkit_web_view_get_uri(view); -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_LOAD_COMMITED, uri, NULL); -#endif - { - JSContextRef ctx; - /* set the status */ - if (g_str_has_prefix(uri, "https://")) { - WebKitWebDataSource *src = webkit_web_frame_get_data_source(frame); - WebKitNetworkRequest *request = webkit_web_data_source_get_request(src); - SoupMessage *msg = webkit_network_request_get_message(request); - SoupMessageFlags flags = soup_message_get_flags(msg); - set_status( - (flags & SOUP_MESSAGE_CERTIFICATE_TRUSTED) ? VB_STATUS_SSL_VALID : VB_STATUS_SSL_INVALID - ); - } else { - set_status(VB_STATUS_NORMAL); - } - - /* inject the hinting javascript */ - hints_init(frame); - - /* run user script file */ - ctx = webkit_web_frame_get_global_context(frame); - js_eval_file(ctx, vb.files[FILES_SCRIPT]); - } - - vb_update_statusbar(); - set_uri(uri); - set_title(uri); - /* save the current URI in register % */ - vb_register_add('%', uri); - - /* clear possible set marks */ - marks_clear(); - - break; - - case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT: -#ifdef FEATURE_AUTOCMD - uri = webkit_web_view_get_uri(view); - autocmd_run(AU_LOAD_FIRST_LAYOUT, uri, NULL); -#endif - /* if we load a page from a submitted form, leave the insert mode */ - if (vb.mode->id == 'i') { - vb_enter('n'); - } + gchar *text; + GtkTextIter start, end; + Client *c = (Client *)user_data; - dom_install_focus_blur_callbacks(webkit_web_frame_get_dom_document(frame)); - vb.state.done_loading_page = false; + g_assert(c); - /* Unset possible last search. */ - command_search(&((Arg){0})); + /* don't observe changes in completion mode */ + if (c->mode->flags & FLAG_COMPLETION) { + return; + } - break; + /* don't process changes not typed by the user */ + if (gtk_widget_is_focus(c->input) && c->mode && c->mode->input_changed) { - case WEBKIT_LOAD_FINISHED: - dom_install_focus_blur_callbacks(webkit_web_frame_get_dom_document(frame)); - uri = webkit_web_view_get_uri(view); -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_LOAD_FINISHED, uri, NULL); -#endif - /* update load progress in statusbar */ - vb.state.progress = 100; - vb_update_statusbar(); - update_title(); + gtk_text_buffer_get_bounds(textbuffer, &start, &end); + text = gtk_text_buffer_get_text(textbuffer, &start, &end, false); - if (strncmp(uri, "about:", 6)) { - history_add(HISTORY_URL, uri, webkit_web_view_get_title(view)); - } -#ifdef FEATURE_SOUP_CACHE - /* Make sure the caches are written to file to be picked up by new - * browser instance. */ - soup_cache_flush(vb.config.soup_cache); - soup_cache_dump(vb.config.soup_cache); -#endif - break; + c->mode->input_changed(c, text); - case WEBKIT_LOAD_FAILED: - { - /* In case the requested uri could not be loaded the Current - * uri of the Webview would still be the PRevious one. So We - * use the provisional uri here. */ - WebKitWebDataSource *src = webkit_web_frame_get_provisional_data_source(frame); - if (src) { - WebKitNetworkRequest *req = webkit_web_data_source_get_initial_request(src); - uri = webkit_network_request_get_uri(req); - /* set the status */ - if (g_str_has_prefix(uri, "https://")) { - SoupMessage *msg = webkit_network_request_get_message(req); - SoupMessageFlags flags = soup_message_get_flags(msg); - set_status( - (flags & SOUP_MESSAGE_CERTIFICATE_TRUSTED) ? VB_STATUS_SSL_VALID : VB_STATUS_SSL_INVALID - ); - } else { - set_status(VB_STATUS_NORMAL); - } - } else { - uri = webkit_web_view_get_uri(view); - } - set_uri(uri); - /* Show the failed uri as title. */ - set_title(uri); -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_LOAD_FAILED, uri, NULL); -#endif - } - break; + g_free(text); } } -static void webview_request_starting_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitWebResource *res, WebKitNetworkRequest *req, - WebKitNetworkResponse *resp, gpointer data) +/** + * Set the style of the statusbar. + */ +static void set_statusbar_style(Client *c, StatusType type) { - char *name, *value; - GHashTableIter iter; - SoupMessage *msg; - - /* don't try to load favicon */ - const char *uri = webkit_network_request_get_uri(req); - if (g_str_has_suffix(uri, "/favicon.ico")) { - webkit_network_request_set_uri(req, "about:blank"); + GtkStyleContext *ctx; + /* Do nothing if the new to set style is the same as the current. */ + if (type == c->state.status_type) { return; } - msg = webkit_network_request_get_message(req); - if (!msg) { - return; - } - - if (!vb.config.headers) { - return; - } + ctx = gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)); - /* set/remove/change user defined headers */ - g_hash_table_iter_init(&iter, vb.config.headers); - while (g_hash_table_iter_next(&iter, (gpointer*)&name, (gpointer*)&value)) { - /* allow to remove header with null value */ - if (value == NULL) { - soup_message_headers_remove(msg->request_headers, name); - } else { - soup_message_headers_replace(msg->request_headers, name, value); - } + if (type == STATUS_SSL_VALID) { + gtk_style_context_remove_class(ctx, "unsecure"); + gtk_style_context_add_class(ctx, "secure"); + } else if (type == STATUS_SSL_INVALID) { + gtk_style_context_remove_class(ctx, "secure"); + gtk_style_context_add_class(ctx, "unsecure"); + } else { + gtk_style_context_remove_class(ctx, "secure"); + gtk_style_context_remove_class(ctx, "unsecure"); } + c->state.status_type = type; } -static gboolean focus_out_event_cb(GtkWidget *widget, GdkEvent *event, - gpointer user_data) -{ - vb.state.window_has_focus = false; - return false; -} - -static gboolean focus_in_event_cb(GtkWidget *widget, GdkEvent *event, - gpointer user_data) +/** + * Update the window title of the main window. + */ +static void set_title(Client *c, const char *title) { - vb.state.window_has_focus = true; - return false; + OVERWRITE_STRING(c->state.title, title); + update_title(c); + g_setenv("VIMB_TITLE", title ? title : "", true); } -static void destroy_window_cb(GtkWidget *widget) +/** + * Spawns a new browser instance for given uri. + * + * @uri: URI used for the new instance. + * @embed: If FALSE, the new instance is not embedded, independent from + * current set -e option. + */ +static void spawn_new_instance(const char *uri, gboolean embed) { - vb_quit(true); -} + guint i = 0; + /* memory allocation */ + char **cmd = g_malloc_n( + 3 /* basename + uri + ending NULL */ + + (vb.configfile ? 2 : 0) +#ifndef FEATURE_NO_XEMBED + + (vb.embed && embed ? 2 : 0) +#endif + + (vb.profile ? 2 : 0), + sizeof(char *) + ); -static void scroll_cb(GtkAdjustment *adjustment) -{ - vb_update_statusbar(); -} + cmd[i++] = vb.argv0; -static gboolean input_focus_in_cb(GtkWidget *widget, GdkEventFocus *event, - gpointer data) -{ - /* enter the command mode if the focus is on inputbox */ - vb_enter('c'); + if (vb.configfile) { + cmd[i++] = "-c"; + cmd[i++] = vb.configfile; + } +#ifndef FEATURE_NO_XEMBED + if (vb.embed && embed) { + char xid[64]; + cmd[i++] = "-e"; + snprintf(xid, LENGTH(xid), "%d", (int)vb.embed); + cmd[i++] = xid; + } +#endif + if (vb.profile) { + cmd[i++] = "-p"; + cmd[i++] = vb.profile; + } + cmd[i++] = (char*)uri; + cmd[i++] = NULL; - return false; -} + /* spawn a new browser instance */ + g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); -static WebKitWebView *inspector_new(WebKitWebInspector *inspector, WebKitWebView *webview) -{ - return WEBKIT_WEB_VIEW(webkit_web_view_new()); + /* free commandline */ + g_free(cmd); } -static gboolean inspector_show(WebKitWebInspector *inspector) +/** + * Callback for the web contexts download-started signal. + */ +static void on_webctx_download_started(WebKitWebContext *webctx, + WebKitDownload *download, Client *c) { - WebKitWebView *webview; - int height; - - if (vb.state.is_inspecting) { - return false; - } - - webview = webkit_web_inspector_get_web_view(inspector); + g_signal_connect(download, "decide-destination", G_CALLBACK(on_webdownload_decide_destination), c); + g_signal_connect(download, "failed", G_CALLBACK(on_webdownload_failed), c); + g_signal_connect(download, "finished", G_CALLBACK(on_webdownload_finished), c); + g_signal_connect(download, "received-data", G_CALLBACK(on_webdownload_received_data), c); - /* use about 1/3 of window height for the inspector */ - gtk_window_get_size(GTK_WINDOW(vb.gui.window), NULL, &height); - gtk_paned_set_position(GTK_PANED(vb.gui.pane), 2 * height / 3); + c->state.downloads = g_list_append(c->state.downloads, download); - gtk_paned_pack2(GTK_PANED(vb.gui.pane), GTK_WIDGET(webview), true, true); - gtk_widget_show(GTK_WIDGET(webview)); - - vb.state.is_inspecting = true; - - return true; + /* to reflect the correct download count */ + vb_statusbar_update(c); } -static gboolean inspector_close(WebKitWebInspector *inspector) +/** + * Callback for the web contexts initialize-web-extensions signal. + */ +static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data) { - WebKitWebView *webview; - - if (!vb.state.is_inspecting) { - return false; - } - webview = webkit_web_inspector_get_web_view(inspector); - gtk_widget_hide(GTK_WIDGET(webview)); - gtk_widget_destroy(GTK_WIDGET(webview)); - - vb.state.is_inspecting = false; + const char *name; + GVariant *vdata; - return true; -} + /* Setup the extension directory. */ + webkit_web_context_set_web_extensions_directory(webctx, EXTENSIONDIR); -static void inspector_finished(WebKitWebInspector *inspector) -{ - g_free(vb.gui.inspector); + name = ext_proxy_init(); + vdata = g_variant_new("(ms)", name); + webkit_web_context_set_web_extensions_initialization_user_data(webctx, vdata); } -static void set_status(const StatusType status) +/** + * Callback for the webkit download decide destination signal. + * This signal is emitted after response is received to decide a destination + * URI for the download. + */ +static gboolean on_webdownload_decide_destination(WebKitDownload *download, + gchar *suggested_filename, Client *c) { - if (vb.state.status_type != status) { - vb.state.status_type = status; - /* update the statusbar style only if the status changed */ - vb_update_status_style(); + if (webkit_download_get_destination(download)) { + return TRUE; } + + return vb_download_set_destination(c, download, suggested_filename, NULL); } -static void init_core(void) +/** + * Callback for the webkit download failed signal. + * This signal is emitted when an error occurs during the download operation. + */ +static void on_webdownload_failed(WebKitDownload *download, + GError *error, Client *c) { - Gui *gui = &vb.gui; -#ifndef FEATURE_NO_XEMBED - char *xid; + gchar *destination = NULL, *filename = NULL, *basename = NULL; - if (vb.embed) { - gui->window = gtk_plug_new(vb.embed); - xid = g_strdup_printf("%u", (int)vb.embed); - } else { - gui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(gui->window), PROJECT_UCFIRST); + g_assert(download); + g_assert(error); + g_assert(c); - gtk_widget_realize(GTK_WIDGET(gui->window)); + /* get the failed download's destination uri */ + g_object_get(download, "destination", &destination, NULL); + g_assert(destination); - /* set the x window id to env */ - xid = g_strdup_printf("%d", (int)GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(gui->window)))); + /* filename from uri */ + if (destination) { + filename = g_filename_from_uri(destination, NULL, NULL); + g_free(destination); } - g_setenv("VIMB_XID", xid, true); - g_free(xid); -#else - gui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(gui->window), PROJECT_UCFIRST); - - gtk_widget_realize(GTK_WIDGET(gui->window)); -#endif - - GdkGeometry hints = {10, 10}; - gtk_window_set_default_size(GTK_WINDOW(gui->window), WIN_WIDTH, WIN_HEIGHT); - gtk_window_set_title(GTK_WINDOW(gui->window), PROJECT "/" VERSION); - gtk_window_set_geometry_hints(GTK_WINDOW(gui->window), NULL, &hints, GDK_HINT_MIN_SIZE); - gtk_window_set_icon(GTK_WINDOW(gui->window), NULL); - gtk_widget_set_name(GTK_WIDGET(gui->window), PROJECT); - - /* Create a browser instance */ - gui->webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); - gui->inspector = webkit_web_view_get_inspector(gui->webview); - - /* Create a scrollable area */ - GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); - gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll)); - gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll)); - -#ifdef FEATURE_NO_SCROLLBARS -#ifdef HAS_GTK3 - /* set the default style for the application - this can be overwritten by - * the users style in gtk-3.0/gtk.css */ - const char *style = "GtkScrollbar{-GtkRange-slider-width:0;-GtkRange-trough-border:0;}\ - GtkScrolledWindow{-GtkScrolledWindow-scrollbar-spacing:0;}"; - GtkCssProvider *provider = gtk_css_provider_get_default(); - gtk_css_provider_load_from_data(provider, style, -1, NULL); - gtk_style_context_add_provider_for_screen( - gdk_screen_get_default(), - GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - ); -#else /* no GTK3 */ - /* GTK_POLICY_NEVER with gtk3 disallows window resizing and scrolling */ - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER); -#endif -#endif - - /* Prepare the command line */ - gui->input = gtk_text_view_new(); - gui->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gui->input)); - -#ifdef HAS_GTK3 - gui->pane = gtk_paned_new(GTK_ORIENTATION_VERTICAL); - gui->box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); - gui->statusbar.box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); -#else - gui->pane = gtk_vpaned_new(); - gui->box = GTK_BOX(gtk_vbox_new(false, 0)); - gui->statusbar.box = GTK_BOX(gtk_hbox_new(false, 0)); -#endif - gui->statusbar.mode = gtk_label_new(NULL); - gui->statusbar.left = gtk_label_new(NULL); - gui->statusbar.right = gtk_label_new(NULL); - gui->statusbar.cmd = gtk_label_new(NULL); - - /* Prepare the event box */ - gui->eventbox = gtk_event_box_new(); - - gtk_paned_pack1(GTK_PANED(gui->pane), GTK_WIDGET(gui->box), true, true); - - /* Put all part together */ - gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(gui->webview)); - gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar.box)); - gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane)); -#ifdef HAS_GTK3 - gtk_widget_set_halign(gui->statusbar.mode, GTK_ALIGN_START); - gtk_widget_set_halign(gui->statusbar.left, GTK_ALIGN_START); -#else - gtk_misc_set_alignment(GTK_MISC(gui->statusbar.mode), 0.0, 0.0); - gtk_misc_set_alignment(GTK_MISC(gui->statusbar.left), 0.0, 0.0); -#endif - gtk_label_set_ellipsize(GTK_LABEL(gui->statusbar.left), PANGO_ELLIPSIZE_MIDDLE); - gtk_box_pack_start(gui->statusbar.box, gui->statusbar.mode, false, true, 0); - gtk_box_pack_start(gui->statusbar.box, gui->statusbar.left, true, true, 2); - gtk_box_pack_start(gui->statusbar.box, gui->statusbar.cmd, false, false, 0); - gtk_box_pack_start(gui->statusbar.box, gui->statusbar.right, false, false, 2); - - gtk_box_pack_start(gui->box, scroll, true, true, 0); - gtk_box_pack_start(gui->box, gui->eventbox, false, false, 0); - -#ifdef HAS_GTK3 - /* use a scrolled window to hide overflowing text in inputbox like GTK2 */ - GtkWidget *inputscroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(inputscroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); - gtk_container_add(GTK_CONTAINER(inputscroll), gui->input); - - gtk_box_pack_end(gui->box, inputscroll, false, false, 0); -#else - gtk_box_pack_end(gui->box, gui->input, false, false, 0); -#endif + /* basename from filename */ + if (filename) { + basename = g_path_get_basename(filename); + g_free(filename); + } - /* initialize the modes */ - vb_add_mode('n', normal_enter, normal_leave, normal_keypress, NULL); - vb_add_mode('c', ex_enter, ex_leave, ex_keypress, ex_input_changed); - vb_add_mode('i', input_enter, input_leave, input_keypress, NULL); - vb_add_mode('p', pass_enter, pass_leave, pass_keypress, NULL); - - /* initialize the marks with empty values */ - marks_clear(); - - init_files(); - session_init(); - setting_init(); - register_init(); -#ifdef FEATURE_AUTOCMD - autocmd_init(); -#endif - map_init(); + /* report the error to the user */ + if (basename) { + vb_echo(c, MSG_ERROR, FALSE, "Download of %s failed (%s)", basename, error->message); + g_free(basename); + } +} - setup_signals(); +/** + * Callback for the webkit download finished signal. + * This signal is emitted when download finishes successfully or due to an + * error. In case of errors “failed” signal is emitted before this one. + */ +static void on_webdownload_finished(WebKitDownload *download, Client *c) +{ + gchar *destination = NULL, *filename = NULL, *basename = NULL; - /* enter normal mode */ - vb_enter('n'); + g_assert(download); + g_assert(c); - /* make sure the main window and all its contents are visible */ - gtk_widget_show_all(gui->window); + c->state.downloads = g_list_remove(c->state.downloads, download); - /* read the config file */ - ex_run_file(vb.files[FILES_CONFIG]); + /* to reflect the correct download count */ + vb_statusbar_update(c); - /* initially apply input style */ - vb_update_input_style(); + /* get the finished downloads destination uri */ + g_object_get(download, "destination", &destination, NULL); + g_assert(destination); - if (vb.config.kioskmode) { - WebKitWebSettings *setting = webkit_web_view_get_settings(gui->webview); + /* filename from uri */ + if (destination) { + filename = g_filename_from_uri(destination, NULL, NULL); + g_free(destination); + } - /* hide input box - to not create it would be better, but this needs a - * lot of changes in the code where the input is used */ - gtk_widget_hide(vb.gui.input); + if (filename) { + /* basename from filename */ + basename = g_path_get_basename(filename); - /* disable context menu */ - g_object_set(G_OBJECT(setting), "enable-default-context-menu", false, NULL); - } + if (basename) { + /* Only report to the user if the downloaded file exists, so the + * download was successful. Otherwise, this is a failed download + * finished signal and it was reported to the user in + * on_webdownload_failed() already. */ + if (g_file_test(filename, G_FILE_TEST_EXISTS)) { + vb_echo(c, MSG_NORMAL, FALSE, "Download of %s finished", basename); + } -#ifdef FEATURE_HIGH_DPI -#ifdef FEATURE_DEFAULT_ZOOM - /* if default_zoom was not changed via config */ - if (vb.config.default_zoom == 1.0) { -#endif - /* fix for high dpi displays */ - GdkScreen *screen = gdk_window_get_screen(gtk_widget_get_window(vb.gui.window)); - gdouble dpi = gdk_screen_get_resolution(screen); - if (dpi != -1) { - WebKitWebSettings *setting = webkit_web_view_get_settings(gui->webview); - webkit_web_view_set_full_content_zoom(gui->webview, true); - g_object_set(G_OBJECT(setting), "enforce-96-dpi", true, NULL); - - /* calculate the zoom level based on 96 dpi */ - vb.config.default_zoom = dpi/96; - - webkit_web_view_set_zoom_level(gui->webview, vb.config.default_zoom); + g_free(basename); } -#ifdef FEATURE_DEFAULT_ZOOM + + g_free(filename); } -#endif -#endif } -static void marks_clear(void) +/** + * Callback for the webkit download received-data signal. + * This signal is emitted after response is received, every time new data has + * been written to the destination. It's useful to know the progress of the + * download operation. + */ +static void on_webdownload_received_data(WebKitDownload *download, + guint64 data_length, Client *c) { - int i; + /* rate limit statusbar updates */ + static gint64 statusbar_update_next = 0; - /* init empty marks array */ - for (i = 0; i < VB_MARK_SIZE; i++) { - vb.state.marks[i] = -1; + if (g_get_monotonic_time() > statusbar_update_next) { + statusbar_update_next = g_get_monotonic_time() + 1000000; /* 1 second */ + + vb_statusbar_update(c); } } -static void setup_signals() +/** + * Callback for the webview close signal. + */ +static void on_webview_close(WebKitWebView *webview, Client *c) { - /* Set up callbacks so that if either the main window or the browser - * instance is closed, the program will exit */ - g_signal_connect(vb.gui.window, "destroy", G_CALLBACK(destroy_window_cb), NULL); - g_object_connect( - G_OBJECT(vb.gui.webview), -#if WEBKIT_CHECK_VERSION(1, 10, 0) - "signal::context-menu", G_CALLBACK(context_menu_cb), NULL, -#else - "signal::populate-popup", G_CALLBACK(context_menu_cb), NULL, -#endif - "signal::notify::uri", G_CALLBACK(uri_change_cb), NULL, - "signal::notify::progress", G_CALLBACK(webview_progress_cb), NULL, - "signal::notify::load-status", G_CALLBACK(webview_load_status_cb), NULL, - "signal::button-release-event", G_CALLBACK(button_relase_cb), NULL, - "signal::new-window-policy-decision-requested", G_CALLBACK(new_window_policy_cb), NULL, - "signal::create-web-view", G_CALLBACK(create_web_view_cb), NULL, - "signal::hovering-over-link", G_CALLBACK(hover_link_cb), NULL, - "signal::title-changed", G_CALLBACK(title_changed_cb), NULL, - "signal::mime-type-policy-decision-requested", G_CALLBACK(mimetype_decision_cb), NULL, - "signal::download-requested", G_CALLBACK(vb_download), NULL, - "signal::should-show-delete-interface-for-element", G_CALLBACK(gtk_false), NULL, - "signal::resource-request-starting", G_CALLBACK(webview_request_starting_cb), NULL, - "signal::navigation-policy-decision-requested", G_CALLBACK(navigation_decision_requested_cb), NULL, - "signal::onload-event", G_CALLBACK(onload_event_cb), NULL, - NULL - ); - g_signal_connect(vb.gui.window, "focus-in-event", G_CALLBACK(focus_in_event_cb), NULL); - g_signal_connect(vb.gui.window, "focus-out-event", G_CALLBACK(focus_out_event_cb), NULL); -#ifdef FEATURE_ARH - g_signal_connect(vb.session, "request-queued", G_CALLBACK(session_request_queued_cb), NULL); -#endif - -#ifdef FEATURE_NO_SCROLLBARS - WebKitWebFrame *frame = webkit_web_view_get_main_frame(vb.gui.webview); - g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed", G_CALLBACK(gtk_true), NULL); -#endif - - if (!vb.config.kioskmode) { - g_signal_connect( - G_OBJECT(vb.gui.window), "key-press-event", G_CALLBACK(map_keypress), NULL - ); - g_signal_connect( - G_OBJECT(vb.gui.input), "focus-in-event", G_CALLBACK(input_focus_in_cb), NULL - ); - - /* inspector */ - g_object_connect( - G_OBJECT(vb.gui.inspector), - "signal::inspect-web-view", G_CALLBACK(inspector_new), NULL, - "signal::show-window", G_CALLBACK(inspector_show), NULL, - "signal::close-window", G_CALLBACK(inspector_close), NULL, - "signal::finished", G_CALLBACK(inspector_finished), NULL, - NULL - ); - } - /* There is no inputbox in kioskmode - but the contents may be changed in - * case vimb is controlled via socket. To track inputbox changes is - * required for the hinting to work. */ - g_signal_connect(G_OBJECT(vb.gui.buffer), "changed", G_CALLBACK(buffer_changed_cb), NULL); - - /* webview adjustment */ - g_signal_connect(G_OBJECT(vb.gui.adjust_v), "value-changed", G_CALLBACK(scroll_cb), NULL); + client_destroy(c); } -static void init_files(void) +/** + * Callback for the webview create signal. + * This creates a new client - with it's own window with a related webview. + */ +static WebKitWebView *on_webview_create(WebKitWebView *webview, + WebKitNavigationAction *navact, Client *c) { - char *path = util_get_config_dir(vb.config.profile); - - if (vb.config.file) { - char *rp = realpath(vb.config.file, NULL); - vb.files[FILES_CONFIG] = g_strdup(rp); - free(rp); - } else { - vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL); - util_create_file_if_not_exists(vb.files[FILES_CONFIG]); - } + Client *new = client_new(webview, FALSE); -#ifdef FEATURE_COOKIE - vb.files[FILES_COOKIE] = g_build_filename(path, "cookies", NULL); - util_create_file_if_not_exists(vb.files[FILES_COOKIE]); -#endif + return new->webview; +} - vb.files[FILES_CLOSED] = g_build_filename(path, "closed", NULL); - util_create_file_if_not_exists(vb.files[FILES_CLOSED]); +/** + * Callback for the webview decide-policy signal. + * Checks the reasons for some navigation actions and decides if the action is + * allowed, or should go into a new instance of vimb. + */ +static gboolean on_webview_decide_policy(WebKitWebView *webview, + WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c) +{ + guint status, button, mod; + WebKitNavigationAction *a; + WebKitURIRequest *req; + WebKitURIResponse *res; + + switch (type) { + case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: + a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec)); + req = webkit_navigation_action_get_request(a); + button = webkit_navigation_action_get_mouse_button(a); + mod = webkit_navigation_action_get_modifiers(a); + + /* Try to handle with specific protocol handler. */ + if (handler_handle_uri(c, webkit_uri_request_get_uri(req))) { + webkit_policy_decision_ignore(dec); + return TRUE; + } + /* Spawn new instance if the new win flag is set on the mode, or + * the navigation was triggered by CTRL-LeftMouse or MiddleMouse. */ + if ((c->mode->flags & FLAG_NEW_WIN) + || (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED + && (button == 2 || (button == 1 && mod & GDK_CONTROL_MASK)))) { + + /* Remove the FLAG_NEW_WIN after the first use. */ + c->mode->flags &= ~FLAG_NEW_WIN; + + webkit_policy_decision_ignore(dec); + spawn_new_instance(webkit_uri_request_get_uri(req), TRUE); + return TRUE; + } + return FALSE; - vb.files[FILES_HISTORY] = g_build_filename(path, "history", NULL); - util_create_file_if_not_exists(vb.files[FILES_HISTORY]); + case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: + a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec)); - vb.files[FILES_COMMAND] = g_build_filename(path, "command", NULL); - util_create_file_if_not_exists(vb.files[FILES_COMMAND]); + /* Ignore opening new window if this was started without user gesture. */ + if (!webkit_navigation_action_is_user_gesture(a)) { + webkit_policy_decision_ignore(dec); + return TRUE; + } - vb.files[FILES_SEARCH] = g_build_filename(path, "search", NULL); - util_create_file_if_not_exists(vb.files[FILES_SEARCH]); + if (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) { + webkit_policy_decision_ignore(dec); + /* This is triggered on link click for links with * + * target="_blank". Maybe it should be configurable if the + * page is opened as tabe or a new instance. */ + req = webkit_navigation_action_get_request(a); + spawn_new_instance(webkit_uri_request_get_uri(req), TRUE); + return TRUE; + } + return FALSE; - vb.files[FILES_BOOKMARK] = g_build_filename(path, "bookmark", NULL); - util_create_file_if_not_exists(vb.files[FILES_BOOKMARK]); + case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: + res = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(dec)); + status = webkit_uri_response_get_status_code(res); -#ifdef FEATURE_QUEUE - vb.files[FILES_QUEUE] = g_build_filename(path, "queue", NULL); - util_create_file_if_not_exists(vb.files[FILES_QUEUE]); -#endif -#ifdef FEATURE_HSTS - vb.files[FILES_HSTS] = g_build_filename(path, "hsts", NULL); - util_create_file_if_not_exists(vb.files[FILES_HSTS]); -#endif + if (!webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(dec)) + && (SOUP_STATUS_IS_SUCCESSFUL(status) || status == SOUP_STATUS_NONE)) { - vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL); + webkit_policy_decision_download(dec); - vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL); + return TRUE; + } + return FALSE; - g_free(path); + default: + return FALSE; + } } -static void session_init(void) +static void on_webview_load_changed(WebKitWebView *webview, + WebKitLoadEvent event, Client *c) { - /* init soup session */ - vb.session = webkit_get_default_session(); - g_object_set(vb.session, "max-conns", SETTING_MAX_CONNS , NULL); - g_object_set(vb.session, "max-conns-per-host", SETTING_MAX_CONNS_PER_HOST, NULL); - g_object_set(vb.session, "accept-language-auto", true, NULL); - -#ifdef FEATURE_COOKIE - SoupCookieJar *cookie = cookiejar_new(vb.files[FILES_COOKIE], false); - soup_session_add_feature(vb.session, SOUP_SESSION_FEATURE(cookie)); - g_object_unref(cookie); -#endif -#ifdef FEATURE_HSTS - /* create only the session feature - the feature is added in setting.c - * when the setting hsts=on */ - vb.config.hsts_provider = hsts_provider_new(); -#endif -#ifdef FEATURE_SOUP_CACHE - /* setup the soup cache but without setting the cache size - this is done in setting.c */ - char *cache_dir = util_get_cache_dir(vb.config.profile); - vb.config.soup_cache = soup_cache_new(cache_dir, SOUP_CACHE_SINGLE_USER); - soup_session_add_feature(vb.session, SOUP_SESSION_FEATURE(vb.config.soup_cache)); - soup_cache_load(vb.config.soup_cache); - g_free(cache_dir); -#endif -} + GTlsCertificateFlags tlsflags; + const char *uri; -static void session_cleanup(void) -{ -#ifdef FEATURE_HSTS - /* remove feature from session and unref the feature to make sure the - * feature is finalized */ - g_object_unref(vb.config.hsts_provider); - soup_session_remove_feature_by_type(vb.session, HSTS_TYPE_PROVIDER); -#endif -#ifdef FEATURE_SOUP_CACHE - /* commit all cache writes */ - soup_cache_flush(vb.config.soup_cache); - /* make sure that the cache will be kept on next browser start */ - soup_cache_dump(vb.config.soup_cache); -#endif -} + switch (event) { + case WEBKIT_LOAD_STARTED: + /* update load progress in statusbar */ + c->state.progress = 0; + vb_statusbar_update(c); + set_title(c, webkit_web_view_get_uri(webview)); + /* Make sure hinting is cleared before the new page is loaded. + * Without that vimb would still be in hinting mode after hinting + * was started and some links was clicked my mouse. Even if there + * could not hints be shown. */ + if (c->mode->flags & FLAG_HINTING) { + vb_enter(c, 'n'); + } + break; -static void register_init(void) -{ - memset(vb.state.reg, 0, sizeof(char*)); -} + case WEBKIT_LOAD_REDIRECTED: + break; -void vb_register_add(char buf, const char *value) -{ - char *mark; - int idx; + case WEBKIT_LOAD_COMMITTED: + uri = webkit_web_view_get_uri(webview); + /* save the current URI in register % */ + vb_register_add(c, '%', uri); + /* check if tls is on and the page is trusted */ + if (g_str_has_prefix(uri, "https://")) { + if (webkit_web_view_get_tls_info(webview, NULL, &tlsflags) && tlsflags) { + set_statusbar_style(c, STATUS_SSL_INVALID); + } else { + set_statusbar_style(c, STATUS_SSL_VALID); + } + } else { + set_statusbar_style(c, STATUS_NORMAL); + } - if (!vb.state.enable_register || !buf) { - return; - } + /* clear possible set marks */ + marks_clear(c); - /* make sure the mark is a valid mark char */ - if ((mark = strchr(VB_REG_CHARS, buf))) { - /* get the index of the mark char */ - idx = mark - VB_REG_CHARS; + /* Unset possible last search. */ + command_search(c, &(Arg){0, NULL}, FALSE); - OVERWRITE_STRING(vb.state.reg[idx], value); + break; + + case WEBKIT_LOAD_FINISHED: + uri = webkit_web_view_get_uri(webview); + c->state.progress = 100; + if (strncmp(uri, "about:", 6)) { + history_add(c, HISTORY_URL, uri, webkit_web_view_get_title(webview)); + } + break; } } -const char *vb_register_get(char buf) +/** + * Callback for the webview mouse-target-changed signal. + * This is used to print the uri too statusbar if the user hovers over links + * or images. + */ +static void on_webview_mouse_target_changed(WebKitWebView *webview, + WebKitHitTestResult *result, guint modifiers, Client *c) { - char *mark; - int idx; - - /* make sure the mark is a valid mark char */ - if ((mark = strchr(VB_REG_CHARS, buf))) { - /* get the index of the mark char */ - idx = mark - VB_REG_CHARS; + char *msg; + const char *uri; - return vb.state.reg[idx]; + /* Save the hitTestResult to have this later available for events that + * don't support this. */ + if (c->state.hit_test_result) { + g_object_unref(c->state.hit_test_result); + } + c->state.hit_test_result = g_object_ref(result); + + if (webkit_hit_test_result_context_is_link(result)) { + uri = webkit_hit_test_result_get_link_uri(result); + msg = g_strconcat("Link: ", uri, NULL); + gtk_label_set_text(GTK_LABEL(c->statusbar.left), msg); + g_free(msg); + } else if (webkit_hit_test_result_context_is_image(result)) { + uri = webkit_hit_test_result_get_image_uri(result); + msg = g_strconcat("Image: ", uri, NULL); + gtk_label_set_text(GTK_LABEL(c->statusbar.left), msg); + g_free(msg); + } else { + /* No link under cursor - show the current URI. */ + update_urlbar(c); } - - return NULL; } -static void register_cleanup(void) +/** + * Called on webviews notify::estimated-load-progress event. This writes the + * esitamted load progress in percent in a variable and updates the statusbar + * to make the changes visible. + */ +static void on_webview_notify_estimated_load_progress(WebKitWebView *webview, + GParamSpec *spec, Client *c) { - int i; - for (i = 0; i < VB_REG_SIZE; i++) { - if (vb.state.reg[i]) { - g_free(vb.state.reg[i]); - } - } + c->state.progress = webkit_web_view_get_estimated_load_progress(webview) * 100; + vb_statusbar_update(c); + update_title(c); } -static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event) +/** + * Callback for the webview notify::title signal. + * Changes the window title according to the title of the current page. + */ +static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec, Client *c) { - /* let webkit handle the click - for example on a link */ - gboolean nopropagate = false; - WebKitHitTestResultContext context; - - WebKitHitTestResult *result = webkit_web_view_get_hit_test_result(webview, event); + const char *title = webkit_web_view_get_title(webview); - g_object_get(result, "context", &context, NULL); - /* ctrl click or middle mouse click onto link */ - if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK - && (event->button == 2 || (event->button == 1 && event->state & GDK_CONTROL_MASK)) - ) { - Arg a = {VB_TARGET_NEW}; - g_object_get(result, "link-uri", &a.s, NULL); - vb_load_uri(&a); - - nopropagate = true; + if (*title) { + set_title(c, title); } - g_object_unref(result); - - return nopropagate; } -static gboolean new_window_policy_cb( - WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *navig, WebKitWebPolicyDecision *policy) +/** + * Callback for the webview notify::uri signal. + * Changes the current uri shown on left of statusbar. + */ +static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec, Client *c) { - if (webkit_web_navigation_action_get_reason(navig) == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) { - webkit_web_policy_decision_ignore(policy); - /* open in a new window */ - Arg a = {VB_TARGET_NEW, (char*)webkit_network_request_get_uri(request)}; - vb_load_uri(&a); - return true; - } - return false; + OVERWRITE_STRING(c->state.uri, webkit_web_view_get_uri(c->webview)); + update_urlbar(c); + g_setenv("VIMB_URI", c->state.uri, TRUE); } -static WebKitWebView *create_web_view_cb(WebKitWebView *view, WebKitWebFrame *frame) +/** + * Callback for the webview ready-to-show signal. + * Show the webview only if it's ready to be shown. + */ +static void on_webview_ready_to_show(WebKitWebView *webview, Client *c) { - WebKitWebView *new = WEBKIT_WEB_VIEW(webkit_web_view_new()); - - /* wait until the new webview receives its new URI */ - g_signal_connect(new, "navigation-policy-decision-requested", G_CALLBACK(create_web_view_received_uri_cb), NULL); - - return new; + gtk_widget_show_all(GTK_WIDGET(c->window)); } -static gboolean create_web_view_received_uri_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data) +/** + * Callback for the webview web-process-crashed signal. + */ +static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c) { - Arg a = {VB_TARGET_NEW, (char*)webkit_network_request_get_uri(request)}; - vb_load_uri(&a); + vb_echo(c, MSG_ERROR, FALSE, "Webview Crashed on %s", webkit_web_view_get_uri(webview)); - /* destroy temporary webview */ - gtk_widget_destroy(GTK_WIDGET(view)); - - /* mark that we handled the signal */ - return true; + return TRUE; } -static gboolean navigation_decision_requested_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data) +/** + * Callback for window ::delete-event signal which is emitted if a user + * requests that a toplevel window is closed. The default handler for this + * signal destroys the window. Returns TRUE to stop other handlers from being + * invoked for the event. FALSE to propagate the event further. + */ +static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c) { -#ifdef FEATURE_HSTS - char *uri; - SoupMessage *msg = webkit_network_request_get_message(request); - - /* manually reload the page for HSTS only when it occurs in - * the main-frame. the others cases are covered by requeueing. */ - if (webkit_web_view_get_main_frame(view) == frame) { - uri = hsts_get_changed_uri(vb.session, msg); - if (uri) { - webkit_web_frame_load_uri(frame, uri); - webkit_web_policy_decision_ignore(policy); - - g_free(uri); - /* mark the request as handled */ - return true; - } - } -#endif - - /* try to find a protocol handler to open the uri */ - if (handle_uri(webkit_network_request_get_uri(request))) { - webkit_web_policy_decision_ignore(policy); + /* if vb_quit fails, do not propagate event further, keep window open */ + return !vb_quit(c, FALSE); +} - return true; - } - return false; +/** + * Callback for the window destroy signal. + * Destroys the client that is associated to the window. + */ +static void on_window_destroy(GtkWidget *window, Client *c) +{ + client_destroy(c); } -static void onload_event_cb(WebKitWebView *view, WebKitWebFrame *frame, - gpointer user_data) +/** + * Callback for to quit given client as idle event source. + */ +static gboolean quit(Client *c) { - Document *doc = webkit_web_frame_get_dom_document(frame); - dom_check_auto_insert(doc); - vb.state.done_loading_page = true; + /* Destroy the main window to tirgger the destruction of the client. */ + gtk_widget_destroy(c->window); + + /* Remove this from the list of event sources. */ + return FALSE; } -static void hover_link_cb(WebKitWebView *webview, const char *title, const char *link) +/** + * Read string from stdin and pass it to webkit for html interpretation. + */ +static void read_from_stdin(Client *c) { - char *message; - if (link) { - /* save the uri to have this if the user want's to copy the link - * location via context menu */ - OVERWRITE_STRING(vb.state.linkhover, link); - - message = g_strconcat("Link: ", link, NULL); - gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.left), message); - g_free(message); - } else if (vb.state.uri) { - /* Use previous url in case of hover out of a link. */ - vb_update_urlbar(vb.state.uri); + GIOChannel *ch; + gchar *buf = NULL; + GError *err = NULL; + gsize len = 0; + + g_assert(c); + + ch = g_io_channel_unix_new(fileno(stdin)); + g_io_channel_read_to_end(ch, &buf, &len, &err); + g_io_channel_unref(ch); + + if (err) { + g_warning("Error loading from stdin: %s", err->message); + g_error_free(err); } else { - /* If there is no previous uri use the current uri from webview. */ - set_uri(webkit_web_view_get_uri(webview)); + webkit_web_view_load_html(c->webview, buf, NULL); } + g_free(buf); } -static void title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, const char *title) +/** + * Free the register contents memory. + */ +static void register_cleanup(Client *c) { - set_title(title); + int i; + for (i = 0; i < REG_SIZE; i++) { + if (c->state.reg[i]) { + g_free(c->state.reg[i]); + } + } } -static void update_title(void) +static void update_title(Client *c) { #ifdef FEATURE_TITLE_PROGRESS - /* show load status of page or the downloads */ - if (vb.state.progress != 100) { + /* Show load status of page or the downloads. */ + if (c->state.progress != 100) { char *title = g_strdup_printf( - "[%i%%] %s", - vb.state.progress, - vb.state.title ? vb.state.title : "" - ); - gtk_window_set_title(GTK_WINDOW(vb.gui.window), title); + "[%i%%] %s", + c->state.progress, + c->state.title ? c->state.title : ""); + gtk_window_set_title(GTK_WINDOW(c->window), title); g_free(title); + return; } #endif - if (vb.state.title) { - gtk_window_set_title(GTK_WINDOW(vb.gui.window), vb.state.title); + if (c->state.title) { + gtk_window_set_title(GTK_WINDOW(c->window), c->state.title); } } -static void set_uri(const char *uri) +/** + * Update the contents of the url bar on the left of the statu bar according + * to current opened url and position in back forward history. + */ +static void update_urlbar(Client *c) { - OVERWRITE_STRING(vb.state.uri, uri); - g_setenv("VIMB_URI", uri, true); - vb_update_urlbar(uri); -} + GString *str; + gboolean back, fwd; -static void set_title(const char *title) -{ - OVERWRITE_STRING(vb.state.title, title); - update_title(); - g_setenv("VIMB_TITLE", title ? title : "", true); -} + str = g_string_new(""); + /* show profile name */ + if (vb.profile) { + g_string_append_printf(str, "[%s] ", vb.profile); + } -static gboolean mimetype_decision_cb(WebKitWebView *webview, - WebKitWebFrame *frame, WebKitNetworkRequest *request, char *mime_type, - WebKitWebPolicyDecision *decision) -{ - SoupMessage *msg; - /* don't start download if request failed or stopped by proxy or can be - * displayed in the webview */ - if (!mime_type || *mime_type == '\0' - || webkit_web_view_can_show_mime_type(webview, mime_type)) { - - return false; - } - - /* Don't start a download when the response has no 2xx status code. Or the - * message was not sent before - this seems to be the case when the server - * responds with a Accept-Ranges header. */ - msg = webkit_network_request_get_message(request); - if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code) - || msg->status_code == SOUP_STATUS_NONE - ) { - webkit_web_policy_decision_download(decision); - return true; - } - return false; + /* show current url */ + g_string_append_printf(str, "%s", c->state.uri); + + /* show history indicator only if there is something to show */ + back = webkit_web_view_can_go_back(c->webview); + fwd = webkit_web_view_can_go_forward(c->webview); + if (back || fwd) { + g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+"); + } + + gtk_label_set_text(GTK_LABEL(c->statusbar.left), str->str); + g_string_free(str, TRUE); } -gboolean vb_download(WebKitWebView *view, WebKitDownload *download, const char *path) +#ifdef FREE_ON_QUIT +/** + * Free memory of the whole application. + */ +static void vimb_cleanup(void) { - char *file, *dir; - const char *download_cmd = GET_CHAR("download-command"); - gboolean use_external = GET_BOOL("download-use-external"); - - /* prepare the path to save the download */ - if (path) { - file = util_build_path(path, vb.config.download_dir); + int i; - /* if file is an directory append a file name */ - if (g_file_test(file, (G_FILE_TEST_IS_DIR))) { - dir = file; - file = g_build_filename(dir, PROJECT "-download", NULL); - g_free(dir); - } - } else { - /* if there was no path given where to download the file, used - * suggested file name or a static one */ - path = webkit_download_get_suggested_filename(download); - if (!path || *path == '\0') { - path = PROJECT "-download"; - } - file = util_build_path(path, vb.config.download_dir); + while (vb.clients) { + client_destroy(vb.clients); } -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_DOWNLOAD_START, webkit_download_get_uri(download), NULL); -#endif - if (use_external && *download_cmd) { - /* run download with external program */ - vb_download_external(view, download, file); - g_free(file); - - /* signalize that we handle the download ourself */ - return false; - } else { - /* use webkit download helpr to download the uri */ - vb_download_internal(view, download, file); - g_free(file); + /* free memory of other components */ + util_cleanup(); - return true; + for (i = 0; i < FILES_LAST; i++) { + if (vb.files[i]) { + g_free(vb.files[i]); + } } + g_free(vb.profile); } +#endif -void vb_download_internal(WebKitWebView *view, WebKitDownload *download, const char *file) +/** + * Setup resources used on application scope. + */ +static void vimb_setup(void) { - char *uri; - guint64 size; - WebKitDownloadStatus status; + WebKitWebContext *ctx; + WebKitCookieManager *cm; + char *path; - /* build the file uri from file path */ - uri = g_filename_to_uri(file, NULL, NULL); - webkit_download_set_destination_uri(download, uri); - g_free(uri); + /* prepare the file pathes */ + path = util_get_config_dir(); - size = webkit_download_get_total_size(download); - if (size > 0) { - vb_echo(VB_MSG_NORMAL, false, "Download %s [%uB] started ...", file, size); + if (vb.configfile) { + char *rp = realpath(vb.configfile, NULL); + vb.files[FILES_CONFIG] = g_strdup(rp); + free(rp); } else { - vb_echo(VB_MSG_NORMAL, false, "Download %s started ...", file); - } - - status = webkit_download_get_status(download); - if (status == WEBKIT_DOWNLOAD_STATUS_CREATED) { - webkit_download_start(download); - } - - /* prepend the download to the download list */ - vb.state.downloads = g_list_prepend(vb.state.downloads, download); + vb.files[FILES_CONFIG] = util_get_filepath(path, "config", FALSE); + } + + /* Setup those files that are use multiple time during runtime */ + vb.files[FILES_CLOSED] = util_get_filepath(path, "closed", TRUE); + vb.files[FILES_COOKIE] = util_get_filepath(path, "cookies", TRUE); + vb.files[FILES_USER_STYLE] = util_get_filepath(path, "style.css", FALSE); + vb.files[FILES_SCRIPT] = util_get_filepath(path, "scripts.js", FALSE); + vb.files[FILES_HISTORY] = util_get_filepath(path, "history", TRUE); + vb.files[FILES_COMMAND] = util_get_filepath(path, "command", TRUE); + vb.files[FILES_BOOKMARK] = util_get_filepath(path, "bookmark", TRUE); + vb.files[FILES_QUEUE] = util_get_filepath(path, "queue", TRUE); + vb.files[FILES_SEARCH] = util_get_filepath(path, "search", TRUE); + g_free(path); - /* connect signal handler to check if the download is done */ - g_signal_connect(download, "notify::status", G_CALLBACK(download_progress_cp), NULL); - g_signal_connect(download, "notify::progress", G_CALLBACK(webview_download_progress_cb), NULL); + /* Use seperate rendering processed for the webview of the clients in the + * current instance. This must be called as soon as possible according to + * the documentation. */ + ctx = webkit_web_context_get_default(); + webkit_web_context_set_process_model(ctx, WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES); + webkit_web_context_set_web_process_count_limit(ctx, 1); + webkit_web_context_set_cache_model(ctx, WEBKIT_CACHE_MODEL_WEB_BROWSER); - vb_update_statusbar(); -} + g_signal_connect(ctx, "initialize-web-extensions", G_CALLBACK(on_webctx_init_web_extension), NULL); -void vb_download_external(WebKitWebView *view, WebKitDownload *download, const char *file) -{ - const char *user_agent = NULL, *mimetype = NULL, *download_cmd; - char **argv, **envp; - char *cmd; - int argc; - guint64 size; - SoupMessage *msg; - WebKitNetworkRequest *request; - GError *error = NULL; - - request = webkit_download_get_network_request(download); - msg = webkit_network_request_get_message(request); - /* if the download is started by the :save command or hinting we get no - * message here */ - if (msg) { - user_agent = soup_message_headers_get_one(msg->request_headers, "User-Agent"); - mimetype = soup_message_headers_get_one(msg->request_headers, "Content-Type"); - } - - /* set the required download information as environment */ - envp = g_get_environ(); - envp = g_environ_setenv(envp, "VIMB_FILE", file, true); - envp = g_environ_setenv(envp, "VIMB_USE_PROXY", GET_BOOL("proxy") ? "1" : "0", true); -#ifdef FEATURE_COOKIE - envp = g_environ_setenv(envp, "VIMB_COOKIES", vb.files[FILES_COOKIE], true); -#endif - if (mimetype) { - envp = g_environ_setenv(envp, "VIMB_MIME_TYPE", mimetype, true); - } - - if (!user_agent) { - WebKitWebSettings *setting = webkit_web_view_get_settings(view); - g_object_get(G_OBJECT(setting), "user-agent", &user_agent, NULL); + /* Add cookie support only if the cookie file exists. */ + if (vb.files[FILES_COOKIE]) { + cm = webkit_web_context_get_cookie_manager(ctx); + webkit_cookie_manager_set_persistent_storage( + cm, + vb.files[FILES_COOKIE], + WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT); } - envp = g_environ_setenv(envp, "VIMB_USER_AGENT", user_agent, true); - download_cmd = GET_CHAR("download-command"); - cmd = g_strdup_printf(download_cmd, webkit_download_get_uri(download)); - if (!g_shell_parse_argv(cmd, &argc, &argv, &error)) { - g_warning("Could not parse download-command '%s': %s", download_cmd, error->message); - g_error_free(error); - g_free(cmd); - - return; - } - g_free(cmd); + /* initialize the modes */ + vb_mode_add('n', normal_enter, normal_leave, normal_keypress, NULL); + vb_mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed); + vb_mode_add('i', input_enter, input_leave, input_keypress, NULL); + vb_mode_add('p', pass_enter, pass_leave, pass_keypress, NULL); - if (g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { - size = webkit_download_get_total_size(download); - if (size > 0) { - vb_echo(VB_MSG_NORMAL, true, "Download of %uB started", size); - } else { - vb_echo(VB_MSG_NORMAL, true, "Download started"); - } - } else { - g_warning("%s", error->message); - g_clear_error(&error); - vb_echo(VB_MSG_ERROR, true, "Could not start download"); - } - g_strfreev(argv); - g_strfreev(envp); + /* Prepare the style provider to be used for the clients and completion. */ + vb.style_provider = gtk_css_provider_get_default(); } -static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec) -{ - WebKitDownloadStatus status = webkit_download_get_status(download); +/** + * Update the gui style settings for client c, given a style setting name and a + * style setting value to be updated. The complete style sheet document will be + * regenerated and re-fed into gtk css provider. + */ +void vb_gui_style_update(Client *c, const char *setting_name_new, const char *setting_value_new) +{ + g_assert(c); + g_assert(setting_name_new); + g_assert(setting_value_new); + + /* The css style sheet document being composed in this function */ + GString *style_sheet = g_string_new(GUI_STYLE_CSS_BASE); + size_t i; + + /* Mapping from vimb config setting name to css style sheet string */ + static const char *setting_style_map[][2] = { +#ifdef FEATURE_GUI_STYLE_VIMB2_COMPAT + {"completion-bg-active", " #completion:selected{background-color:%s}"}, + {"completion-bg-normal", " #completion{background-color:%s}"}, + {"completion-fg-active", " #completion:selected{color:%s}"}, + {"completion-fg-normal", " #completion{color:%s}"}, + {"completion-font", " #completion{font:%s}"}, + {"input-bg-error", " #input.error{background-color:%s}"}, + {"input-bg-normal", " #input{background-color:%s}"}, + {"input-fg-error", " #input.error{color:%s}"}, + {"input-fg-normal", " #input{color:%s}"}, + {"input-font-error", " #input.error{font:%s}"}, + {"input-font-normal", " #input{font:%s}"}, + {"status-color-bg", " #statusbar{background-color:%s}"}, + {"status-color-fg", " #statusbar{color:%s}"}, + {"status-font", " #statusbar{font:%s}"}, + {"status-ssl-color-bg", " #statusbar.secure{background-color:%s}"}, + {"status-ssl-color-fg", " #statusbar.secure{color:%s}"}, + {"status-ssl-font", " #statusbar.secure{font:%s}"}, + {"status-sslinvalid-color-bg", " #statusbar.unsecure{background-color:%s}"}, + {"status-sslinvalid-color-fg", " #statusbar.unsecure{color:%s}"}, + {"status-sslinvalid-font", " #statusbar.unsecure{font:%s}"}, +#else /* vimb3 gui style settings */ + {"completion-css", " #completion{%s}"}, + {"completion-hover-css", " #completion:hover{%s}"}, + {"completion-selected-css", " #completion:selected{%s}"}, + {"input-css", " #input{%s}"}, + {"input-error-css", " #input.error{%s}"}, + {"status-css", " #statusbar{%s}"}, + {"status-ssl-css", " #statusbar.secure{%s}"}, + {"status-ssl-invalid-css", " #statusbar.unsecure{%s}"}, +#endif /* FEATURE_GUI_STYLE_VIMB2_COMPAT */ + + {0, 0}, + }; - if (status == WEBKIT_DOWNLOAD_STATUS_STARTED || status == WEBKIT_DOWNLOAD_STATUS_CREATED) { - return; - } + /* For each supported style setting name */ + for (i = 0; setting_style_map[i][0]; i++) { + const char *setting_name = setting_style_map[i][0]; + const char *style_string = setting_style_map[i][1]; + + /* If the current style setting name is the one to be updated, + * append the given value with appropriate css wrapping to the + * style sheet document. */ + if (strcmp(setting_name, setting_name_new) == 0) { + if (strlen(setting_value_new)) { + g_string_append_printf(style_sheet, style_string, setting_value_new); + } + } + /* If the current style setting name is NOT the one being updated, + * append the css string based on the current config setting. */ + else { + Setting* setting_value = (Setting*)g_hash_table_lookup(c->config.settings, setting_name); + + /* If the current style setting name is not available via settings + * yet - this happens during setting_init() - cleanup and return. + * We are going to be called again. With the last setting_add(), + * all style setting names are available. */ + if(!setting_value) { + goto cleanup; + } - const char *file = webkit_download_get_destination_uri(download); - /* skip the file protocol for the display */ - if (!strncmp(file, "file://", 7)) { - file += 7; - } - if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) { -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_DOWNLOAD_FAILED, webkit_download_get_uri(download), NULL); -#endif - vb_echo(VB_MSG_ERROR, false, "Error downloading %s", file); - } else { -#ifdef FEATURE_AUTOCMD - autocmd_run(AU_DOWNLOAD_FINISHED, webkit_download_get_uri(download), NULL); -#endif - vb_echo(VB_MSG_NORMAL, false, "Download %s finished", file); + if (strlen(setting_value->value.s)) { + g_string_append_printf(style_sheet, style_string, setting_value->value.s); + } + } } - /* remove the download from the list */ - vb.state.downloads = g_list_remove(vb.state.downloads, download); - - vb_update_statusbar(); + /* Feed style sheet document to gtk */ + gtk_css_provider_load_from_data(vb.style_provider, style_sheet->str, -1, NULL); + + /* WORKAROUND to always ensure correct size of input field + * + * The following line is required to apply the style defined font size on + * the GtkTextView c->input. Without the call, the font size is updated on + * first user input, leading to a sudden unpleasant widget size and layout + * change. According to the GTK+ docs, this call should not be required as + * style context invalidation is automatic. + * + * "gtk_style_context_invalidate has been deprecated since version 3.12 + * and should not be used in newly-written code. Style contexts are + * invalidated automatically." + * https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-style-context-invalidate + * + * Required settings in vimb config file: + * set input-autohide=true + * set input-font-normal=20pt monospace + * + * A bug has been filed at GTK+ + * https://bugzilla.gnome.org/show_bug.cgi?id=781158 + * + * Tested on ARCH linux with gtk3 3.22.10-1 + */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + gtk_style_context_invalidate(gtk_widget_get_style_context(c->input)); + G_GNUC_END_IGNORE_DEPRECATIONS; + +cleanup: + g_string_free(style_sheet, TRUE); } -static void read_from_stdin(void) +/** + * Factory to create a new webview. + * + * @webview: Relates webview or NULL. If given a related webview is + * generated. + */ +static WebKitWebView *webview_new(Client *c, WebKitWebView *webview) { - /* read content from stdin */ - GIOChannel *ch = g_io_channel_unix_new(fileno(stdin)); - gchar *buf = NULL; - GError *err = NULL; - gsize len; + WebKitWebView *new; + WebKitUserContentManager *ucm; - g_io_channel_read_to_end(ch, &buf, &len, &err); - g_io_channel_unref(ch); - if (err) { - g_warning("Error loading from stdin: %s", err->message); - g_error_free(err); + /* create a new webview */ + if (webview) { + new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webview)); + ucm = webkit_web_view_get_user_content_manager(webview); } else { - webkit_web_view_load_string(vb.gui.webview, buf, "text/html", NULL, "(stdin)"); + ucm = webkit_user_content_manager_new(); + new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(ucm)); } - g_free(buf); -} -#ifdef FEATURE_ARH -static void session_request_queued_cb(SoupSession *session, SoupMessage *msg, gpointer data) -{ - SoupURI *suri = soup_message_get_uri(msg); - char *uri = soup_uri_to_string(suri, false); + g_object_connect( + G_OBJECT(new), + "signal::close", G_CALLBACK(on_webview_close), c, + "signal::create", G_CALLBACK(on_webview_create), c, + "signal::decide-policy", G_CALLBACK(on_webview_decide_policy), c, + "signal::load-changed", G_CALLBACK(on_webview_load_changed), c, + "signal::mouse-target-changed", G_CALLBACK(on_webview_mouse_target_changed), c, + "signal::notify::estimated-load-progress", G_CALLBACK(on_webview_notify_estimated_load_progress), c, + "signal::notify::title", G_CALLBACK(on_webview_notify_title), c, + "signal::notify::uri", G_CALLBACK(on_webview_notify_uri), c, + "signal::ready-to-show", G_CALLBACK(on_webview_ready_to_show), c, + "signal::web-process-crashed", G_CALLBACK(on_webview_web_process_crashed), c, + NULL + ); - arh_run(vb.config.autoresponseheader, uri, msg); + g_signal_connect(webkit_web_context_get_default(), "download-started", G_CALLBACK(on_webctx_download_started), c); - g_free(uri); + /* Setup script message handlers. */ + webkit_user_content_manager_register_script_message_handler(ucm, "focus"); + g_signal_connect(ucm, "script-message-received::focus", G_CALLBACK(on_script_message_focus), NULL); + + return new; } -#endif -/** - * Free some memory when vimb is quit. - */ -static void vb_cleanup(void) +static void on_script_message_focus(WebKitUserContentManager *manager, + WebKitJavascriptResult *res, gpointer data) { + char *message; + GVariant *variant; + guint64 pageid; + gboolean is_focused; + Client *c; - completion_clean(); - map_cleanup(); - cleanup_modes(); - setting_cleanup(); - history_cleanup(); - session_cleanup(); - register_cleanup(); -#ifdef FEATURE_AUTOCMD - autocmd_cleanup(); -#endif -#ifdef FEATURE_ARH - arh_free(vb.config.autoresponseheader); -#endif -#ifdef FEATURE_SOCKET - io_cleanup(); -#endif - g_free(vb.state.pid_str); - g_free(vb.state.uri); + message = util_js_result_as_string(res); + variant = g_variant_parse(G_VARIANT_TYPE("(tb)"), message, NULL, NULL, NULL); + g_free(message); - g_slist_free_full(vb.config.cmdargs, g_free); + g_variant_get(variant, "(tb)", &pageid, &is_focused); + g_variant_unref(variant); - for (int i = 0; i < FILES_LAST; i++) { - g_free(vb.files[i]); - vb.files[i] = NULL; + c = vb_get_client_for_page_id(pageid); + if (!c) { + return; } -} -static void cleanup_modes(void) -{ - if (vb.modes) { - g_hash_table_destroy(vb.modes); - vb.modes = NULL; - vb.mode = NULL; + /* Don't change the mode if we are in pass through mode. */ + if (c->mode->id == 'n' && is_focused) { + vb_enter(c, 'i'); + } else if (c->mode->id == 'i' && !is_focused) { + vb_enter(c, 'n'); } } -static void free_mode(Mode *mode) +static gboolean profileOptionArgFunc(const gchar *option_name, + const gchar *value, gpointer data, GError **error) { - g_slice_free(Mode, mode); -} + vb.profile = util_sanitize_filename(g_strdup(value)); -static gboolean autocmdOptionArgFunc(const gchar *option_name, const gchar *value, gpointer data, GError **error) -{ - vb.config.cmdargs = g_slist_append(vb.config.cmdargs, g_strdup(value)); return TRUE; } -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { -#ifndef FEATURE_NO_XEMBED - static char *winid = NULL; -#endif - static gboolean ver = false; -#ifdef FEATURE_SOCKET - static gboolean dump = false; -#endif - static GError *err; + Client *c; + GError *err = NULL; + char *pidstr, *winid = NULL; + gboolean ver = FALSE; - static GOptionEntry opts[] = { - {"cmd", 'C', 0, G_OPTION_ARG_CALLBACK, autocmdOptionArgFunc, "Ex command run before first page is loaded", NULL}, - {"config", 'c', 0, G_OPTION_ARG_FILENAME, &vb.config.file, "Custom configuration file", NULL}, - {"profile", 'p', 0, G_OPTION_ARG_STRING, &vb.config.profile, "Profile name", NULL}, -#ifndef FEATURE_NO_XEMBED - {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL}, -#endif -#ifdef FEATURE_SOCKET - {"dump", 'd', 0, G_OPTION_ARG_NONE, &dump, "Dump the socket path to stdout", NULL}, - {"socket", 's', 0, G_OPTION_ARG_NONE, &vb.config.socket, "Create control socket", NULL}, -#endif - {"kiosk", 'k', 0, G_OPTION_ARG_NONE, &vb.config.kioskmode, "Run in kiosk mode", NULL}, + GOptionEntry opts[] = { + {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL}, + {"config", 'c', 0, G_OPTION_ARG_FILENAME, &vb.configfile, "Custom configuration file", NULL}, + {"profile", 'p', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)profileOptionArgFunc, "Profile name", NULL}, {"version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Print version", NULL}, {NULL} }; - /* Initialize GTK+ */ + + /* initialize GTK+ */ if (!gtk_init_with_args(&argc, &argv, "[URI]", opts, NULL, &err)) { - g_printerr("can't init gtk: %s\n", err->message); + fprintf(stderr, "can't init gtk: %s\n", err->message); g_error_free(err); return EXIT_FAILURE; } if (ver) { - fprintf(stdout, "%s, version %s\n", PROJECT, VERSION); - fprintf(stdout, "Copyright © 2012 - 2016 Daniel Carl \n"); + fprintf(stdout, "%s, version %s\n\n", PROJECT, VERSION); + fprintf(stdout, "Copyright © 2012 - 2017 Daniel Carl \n"); fprintf(stdout, "License GPLv3+: GNU GPL version 3 or later \n"); fprintf(stdout, "This is free software; you are free to change and redistribute it.\n"); fprintf(stdout, "There is NO WARRANTY, to the extent permitted by law.\n"); - return EXIT_SUCCESS; - } - - /* save vimb basename */ - argv0 = argv[0]; -#ifndef FEATURE_NO_XEMBED - if (winid) { - vb.embed = strtol(winid, NULL, 0); + return EXIT_SUCCESS; } -#endif - vb.state.pid_str = g_strdup_printf("%d", (int)getpid()); - g_setenv("VIMB_PID", vb.state.pid_str, true); + /* Save the base name for spawning new instances. */ + vb.argv0 = argv[0]; - /* init some state variable */ - vb.state.enable_register = false; - vb.state.uri = g_strdup(""); + /* set the current pid in env */ + pidstr = g_strdup_printf("%d", (int)getpid()); + g_setenv("VIMB_PID", pidstr, TRUE); - init_core(); + vimb_setup(); - /* process the --cmd if this was given */ - for (GSList *l = vb.config.cmdargs; l; l = l->next) { - ex_run_string(l->data, false); + if (winid) { + vb.embed = strtol(winid, NULL, 0); } - /* active the registers and writing of command history */ - vb.state.enable_register = true; - - /* open uri given as last argument */ + c = client_new(NULL, TRUE); if (argc <= 1) { - /* open configured home page if no uri was given */ - vb_load_uri(&(Arg){VB_TARGET_CURRENT, NULL}); + vb_load_uri(c, &(Arg){TARGET_CURRENT, NULL}); } else if (!strcmp(argv[argc - 1], "-")) { /* read from stdin if uri is - */ - read_from_stdin(); + read_from_stdin(c); } else { - vb_load_uri(&(Arg){VB_TARGET_CURRENT, argv[argc - 1]}); + vb_load_uri(c, &(Arg){TARGET_CURRENT, argv[argc - 1]}); } -#ifdef FEATURE_SOCKET - /* setup the control socket - quit vimb if this failed */ - if (vb.config.socket && !io_init_socket(vb.state.pid_str)) { - /* cleanup memory */ - vb_cleanup(); - return EXIT_FAILURE; - } - if (dump && vb.state.socket_path) { - printf("%s\n", vb.state.socket_path); - fflush(NULL); - } -#endif - - /* Run the main GTK+ event loop */ gtk_main(); - - /* cleanup memory */ - vb_cleanup(); +#ifdef FREE_ON_QUIT + vimb_cleanup(); +#endif return EXIT_SUCCESS; } diff --git a/src/main.h b/src/main.h index 3fd761ef..ac565d4e 100644 --- a/src/main.h +++ b/src/main.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,33 +20,24 @@ #ifndef _MAIN_H #define _MAIN_H -#include -#include -#include -#include -#include #include -#include -#ifndef FEATURE_NO_XEMBED -#ifdef HAS_GTK3 -#include #include -#endif -#endif -#include "config.h" -#ifdef FEATURE_HSTS -#include "hsts.h" -#endif +#include +#include -/* size of some I/O buffer */ -#define BUF_SIZE 512 +#include "config.h" #define LENGTH(x) (sizeof x / sizeof x[0]) +#define OVERWRITE_STRING(t, s) {if (t) g_free(t); t = g_strdup(s);} +#define OVERWRITE_NSTRING(t, s, l) {if (t) {g_free(t); t = NULL;} t = g_strndup(s, l);} +#define GET_CHAR(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.s) +#define GET_INT(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.i) +#define GET_BOOL(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.b) #ifdef DEBUG #define PRINT_DEBUG(...) { \ - fprintf(stderr, "\n\033[31;1mDEBUG:\033[0m %s:%d:%s()\t", __FILE__, __LINE__, __func__); \ + fprintf(stderr, "\n\033[31;1mDEBUG: \033[32;1m%s +%d %s()\033[0m\t", __FILE__, __LINE__, __func__); \ fprintf(stderr, __VA_ARGS__);\ } #define TIMER_START GTimer *__timer; {__timer = g_timer_new(); g_timer_start(__timer);} @@ -60,174 +51,87 @@ #define TIMER_END #endif -#define PRIMARY_CLIPBOARD() gtk_clipboard_get(GDK_SELECTION_PRIMARY) -#define SECONDARY_CLIPBOARD() gtk_clipboard_get(GDK_NONE) - -#define OVERWRITE_STRING(t, s) {if (t) {g_free(t); t = NULL;} t = g_strdup(s);} -#define OVERWRITE_NSTRING(t, s, l) {if (t) {g_free(t); t = NULL;} t = g_strndup(s, l);} - -#define GET_CHAR(n) (((Setting*)g_hash_table_lookup(vb.config.settings, n))->value.s) -#define GET_INT(n) (((Setting*)g_hash_table_lookup(vb.config.settings, n))->value.i) -#define GET_BOOL(n) (((Setting*)g_hash_table_lookup(vb.config.settings, n))->value.b) - -#ifdef HAS_GTK3 -#define VbColor GdkRGBA -#define VB_COLOR_PARSE(color, string) (gdk_rgba_parse(color, string)) -#define VB_COLOR_TO_STRING(color) (gdk_rgba_to_string(color)) -#define VB_WIDGET_OVERRIDE_BACKGROUND(w, s, c) -#define VB_WIDGET_OVERRIDE_BASE(w, s, c) (gtk_widget_override_background_color(w, s, c)) -#define VB_WIDGET_OVERRIDE_COLOR(w, s, c) -#define VB_WIDGET_OVERRIDE_TEXT(w, s, c) (gtk_widget_override_color(w, s, c)) -#define VB_WIDGET_OVERRIDE_FONT(w, f) (gtk_widget_override_font(w, f)) - -#define VB_GTK_STATE_NORMAL GTK_STATE_FLAG_NORMAL -#define VB_GTK_STATE_ACTIVE GTK_STATE_FLAG_ACTIVE -#define VB_GTK_STATE_SELECTED GTK_STATE_FLAG_SELECTED -#define VB_WIDGET_SET_STATE(w, s) (gtk_widget_set_state_flags(w, s, true)) - -#else - -#define VbColor GdkColor -#define VB_COLOR_PARSE(color, string) (gdk_color_parse(string, color)) -#define VB_COLOR_TO_STRING(color) (gdk_color_to_string(color)) -#define VB_WIDGET_OVERRIDE_BACKGROUND(w, s, c) (gtk_widget_modify_bg(w, s, c)) -#define VB_WIDGET_OVERRIDE_BASE(w, s, c) (gtk_widget_modify_base(w, s, c)) -#define VB_WIDGET_OVERRIDE_COLOR(w, s, c) (gtk_widget_modify_fg(w, s, c)) -#define VB_WIDGET_OVERRIDE_TEXT(w, s, c) (gtk_widget_modify_text(w, s, c)) -#define VB_WIDGET_OVERRIDE_FONT(w, f) (gtk_widget_modify_font(w, f)) - -#define VB_GTK_STATE_NORMAL GTK_STATE_NORMAL -#define VB_GTK_STATE_ACTIVE GTK_STATE_ACTIVE -#define VB_GTK_STATE_SELECTED GTK_STATE_SELECTED -#define VB_WIDGET_SET_STATE(w, s) (gtk_widget_set_state(w, s)) -#endif - -#ifndef SOUP_CHECK_VERSION -#define SOUP_CHECK_VERSION(major, minor, micro) (0) -#endif - /* the special mark ' must be the first in the list for easiest lookup */ -#define VB_MARK_CHARS "'abcdefghijklmnopqrstuvwxyz" -#define VB_MARK_TICK 0 -#define VB_MARK_SIZE (sizeof(VB_MARK_CHARS) - 1) +#define MARK_CHARS "'abcdefghijklmnopqrstuvwxyz" +#define MARK_TICK 0 +#define MARK_SIZE (sizeof(MARK_CHARS) - 1) -#define VB_USER_REG "abcdefghijklmnopqrstuvwxyz" +#define USER_REG "abcdefghijklmnopqrstuvwxyz" /* registers in order displayed for :register command */ -#define VB_REG_CHARS "\"" VB_USER_REG ":%/;" -#define VB_REG_SIZE (sizeof(VB_REG_CHARS) - 1) +#define REG_CHARS "\"" USER_REG ":%/;" +#define REG_SIZE (sizeof(REG_CHARS) - 1) + +#define FILE_CLOSED "closed" +#define FILE_COOKIES "cookies" + +enum { TARGET_CURRENT, TARGET_RELATED, TARGET_NEW }; -/* enums */ typedef enum { - RESULT_COMPLETE, - RESULT_MORE, - RESULT_ERROR + RESULT_COMPLETE, RESULT_MORE, RESULT_ERROR } VbResult; typedef enum { - VB_INPUT_UNKNOWN, - VB_INPUT_SET = 0x01, - VB_INPUT_OPEN = 0x02, - VB_INPUT_TABOPEN = 0x04, - VB_INPUT_COMMAND = 0x08, - VB_INPUT_SEARCH_FORWARD = 0x10, - VB_INPUT_SEARCH_BACKWARD = 0x20, - VB_INPUT_BOOKMARK_ADD = 0x40, - VB_INPUT_ALL = 0xff, /* map to match all input types */ -} VbInputType; - -enum { - VB_NAVIG_BACK, - VB_NAVIG_FORWARD, - VB_NAVIG_RELOAD, - VB_NAVIG_RELOAD_FORCE, - VB_NAVIG_STOP_LOADING -}; - -enum { - VB_TARGET_CURRENT, - VB_TARGET_NEW -}; - -enum { - VB_INPUT_CURRENT_URI = 1 -}; + CMD_ERROR, /* command could not be parses or executed */ + CMD_SUCCESS = 0x01, /* command runned successfully */ + CMD_KEEPINPUT = 0x02, /* don't clear inputbox after command run */ +} VbCmdResult; -/* -1 << 0: 0 = jump, 1 = scroll -1 << 1: 0 = vertical, 1 = horizontal -1 << 2: 0 = top/left, 1 = down/right -1 << 3: 0 = paging/halfpage, 1 = line -1 << 4: 0 = paging, 1 = halfpage -*/ -enum {VB_SCROLL_TYPE_JUMP, VB_SCROLL_TYPE_SCROLL}; -enum { - VB_SCROLL_AXIS_V, - VB_SCROLL_AXIS_H = (1 << 1) -}; -enum { - VB_SCROLL_DIRECTION_TOP, - VB_SCROLL_DIRECTION_DOWN = (1 << 2), - VB_SCROLL_DIRECTION_LEFT = VB_SCROLL_AXIS_H, - VB_SCROLL_DIRECTION_RIGHT = VB_SCROLL_AXIS_H | (1 << 2) -}; -enum { - VB_SCROLL_UNIT_PAGE, - VB_SCROLL_UNIT_LINE = (1 << 3), - VB_SCROLL_UNIT_HALFPAGE = (1 << 4) -}; +typedef enum { + TYPE_BOOLEAN, TYPE_INTEGER, TYPE_CHAR, TYPE_COLOR, TYPE_FONT +} DataType; typedef enum { - VB_MSG_NORMAL, - VB_MSG_ERROR, - VB_MSG_LAST + MSG_NORMAL, MSG_ERROR } MessageType; typedef enum { - VB_STATUS_NORMAL, - VB_STATUS_SSL_VALID, - VB_STATUS_SSL_INVALID, - VB_STATUS_LAST + STATUS_NORMAL, STATUS_SSL_VALID, STATUS_SSL_INVALID } StatusType; typedef enum { - VB_CMD_ERROR, /* command could not be parses or executed */ - VB_CMD_SUCCESS = 0x01, /* command runned successfully */ - VB_CMD_KEEPINPUT = 0x02, /* don't clear inputbox after command run */ -} VbCmdResult; + INPUT_UNKNOWN, + INPUT_SET = 0x01, + INPUT_OPEN = 0x02, + INPUT_TABOPEN = 0x04, + INPUT_COMMAND = 0x08, + INPUT_SEARCH_FORWARD = 0x10, + INPUT_SEARCH_BACKWARD = 0x20, + INPUT_BOOKMARK_ADD = 0x40, + INPUT_ALL = 0xff, /* map to match all input types */ +} VbInputType; -typedef enum { - VB_COMP_NORMAL, - VB_COMP_ACTIVE, - VB_COMP_LAST -} CompletionStyle; +enum { + COMP_NORMAL, COMP_ACTIVE, COMP_LAST +}; -typedef enum { +enum { + FILES_BOOKMARK, + FILES_CLOSED, + FILES_COMMAND, FILES_CONFIG, -#ifdef FEATURE_COOKIE FILES_COOKIE, -#endif - FILES_CLOSED, - FILES_SCRIPT, FILES_HISTORY, - FILES_COMMAND, - FILES_SEARCH, - FILES_BOOKMARK, -#ifdef FEATURE_QUEUE FILES_QUEUE, -#endif + FILES_SCRIPT, + FILES_SEARCH, FILES_USER_STYLE, -#ifdef FEATURE_HSTS - FILES_HSTS, -#endif FILES_LAST -} VbFile; +}; -enum { - VB_CLIPBOARD_PRIMARY = (1<<1), - VB_CLIPBOARD_SECONDARY = (1<<2) +typedef struct Client Client; +typedef struct Map Map; +typedef struct Mode Mode; +typedef struct Arg Arg; +typedef void (*ModeTransitionFunc)(Client*); +typedef VbResult (*ModeKeyFunc)(Client*, int); +typedef void (*ModeInputChangedFunc)(Client*, const char*); + +struct Arg { + int i; + char *s; }; -typedef int (*SettingFunction)(const char *name, int type, void *value, void *data); +typedef int (*SettingFunction)(Client *c, const char *name, DataType type, void *value, void *data); typedef union { gboolean b; int i; @@ -243,16 +147,48 @@ typedef struct { void *data; /* data given to the setter */ } Setting; -/* structs */ -typedef struct { - int i; - char *s; -} Arg; +struct State { + char *uri; + gboolean typed; /* indicates if the user typed the keys */ + gboolean processed_key; /* indicates if a key press was handled and should not bubbled up */ + gboolean ctrlv; /* indicates if the CTRL-V temorary submode is on */ -typedef void (*ModeTransitionFunc) (void); -typedef VbResult (*ModeKeyFunc) (int); -typedef void (*ModeInputChangedFunc) (const char*); -typedef struct { +#define PROMPT_SIZE 4 + char prompt[PROMPT_SIZE];/* current prompt ':', 'g;t', '/' including nul */ + gdouble marks[MARK_SIZE]; /* holds marks set to page with 'm{markchar}' */ + guint input_timer; + MessageType input_type; + StatusType status_type; + glong scroll_max; /* Maxmimum scrollable height of the document. */ + guint scroll_percent; /* Current position of the viewport in document. */ + char *title; /* Window title of the client. */ + + char *reg[REG_SIZE]; /* holds the yank buffers */ + /* TODO rename to reg_{enabled,current} */ + gboolean enable_register; /* indicates if registers are filled */ + char current_register; /* holds char for current register to be used */ + + GList *downloads; + guint progress; + WebKitHitTestResult *hit_test_result; + + struct { + gboolean active; /* indicate if there is a active search */ + short direction; /* last direction 1 forward, -1 backward */ + int matches; /* number of matching search results */ + } search; +}; + +struct Map { + char *in; /* input keys */ + int inlen; /* length of the input keys */ + char *mapped; /* mapped keys */ + int mappedlen; /* length of the mapped keys string */ + char mode; /* mode for which the map is available */ + gboolean remap; /* if FALSE do not remap the {rhs} of this map */ +}; + +struct Mode { char id; ModeTransitionFunc enter; /* is called if the mode is entered */ ModeTransitionFunc leave; /* is called if the mode is left */ @@ -262,154 +198,93 @@ typedef struct { #define FLAG_HINTING 0x0002 /* marks active hinting submode */ #define FLAG_COMPLETION 0x0004 /* marks active completion submode */ #define FLAG_PASSTHROUGH 0x0008 /* don't handle any other keybind than */ +#define FLAG_NEW_WIN 0x0010 /* enforce opening of pages into new window */ unsigned int flags; -} Mode; +}; -/* statusbar */ -typedef struct { +struct Statusbar { GtkBox *box; - GtkWidget *mode; - GtkWidget *left; - GtkWidget *right; - GtkWidget *cmd; -} StatusBar; - -/* gui */ -typedef struct { - GtkWidget *window; - WebKitWebView *webview; - WebKitWebInspector *inspector; - GtkBox *box; - GtkWidget *eventbox; - GtkWidget *input; - GtkTextBuffer *buffer; /* text buffer associated with the input for fast access */ - GtkWidget *pane; - StatusBar statusbar; - GtkAdjustment *adjust_h; - GtkAdjustment *adjust_v; -} Gui; - -/* state */ -typedef struct { - char *uri; /* holds current uri or the new to open uri */ - guint progress; - StatusType status_type; - MessageType input_type; - gboolean is_inspecting; - GList *downloads; - gboolean processed_key; - char *title; /* holds the window title */ -#define PROMPT_SIZE 4 - char prompt[PROMPT_SIZE]; /* current prompt ':', 'g;t', '/' including nul */ - gdouble marks[VB_MARK_SIZE]; /* holds marks set to page with 'm{markchar}' */ - char *linkhover; /* the uri of the curret hovered link */ - char *reg[VB_REG_SIZE]; /* holds the yank buffer */ - gboolean enable_register; /* indicates if registers are filled */ - char current_register; /* holds char for current register to be used */ - gboolean typed; /* indicates if th euser type the keys processed as command */ - int search_matches; /* number of matches search results */ - char *fifo_path; /* holds the path to the control fifo */ - char *socket_path; /* holds the path to the control socket */ - char *pid_str; /* holds the pid as string */ - gboolean done_loading_page; - gboolean window_has_focus; -} State; - -typedef struct { -#ifdef FEATURE_COOKIE - time_t cookie_timeout; - int cookie_expire_time; -#endif - int scrollstep; - char *download_dir; - guint history_max; - guint closed_max; - guint timeoutlen; /* timeout for ambiguous mappings */ - gboolean strict_focus; - GHashTable *headers; /* holds user defined header appended to requests */ -#ifdef FEATURE_ARH - GSList *autoresponseheader; /* holds user defined list of auto-response-header */ -#endif - char *nextpattern; /* regex patter nfor prev link matching */ - char *prevpattern; /* regex patter nfor next link matching */ - char *file; /* path to the custome config file */ - char *profile; /* profile name */ - GSList *cmdargs; /* list of commands given by --cmd option */ - char *cafile; /* path to the ca file */ - GTlsDatabase *tls_db; /* tls database */ - float default_zoom; /* default zoomlevel that is applied on zz zoom reset */ - gboolean kioskmode; - gboolean input_autohide; /* indicates if the inputbox should be hidden if it's empty */ -#ifdef FEATURE_SOCKET - gboolean socket; /* indicates if the socket is used */ -#endif -#ifdef FEATURE_HSTS - HSTSProvider *hsts_provider; /* the hsts session feature that is added to soup session */ -#endif -#ifdef FEATURE_SOUP_CACHE - SoupCache *soup_cache; /* soup caching feature model */ -#endif - GHashTable *settings; -} Config; - -typedef struct { - VbColor input_fg[VB_MSG_LAST]; - VbColor input_bg[VB_MSG_LAST]; - PangoFontDescription *input_font[VB_MSG_LAST]; - /* completion */ - VbColor comp_fg[VB_COMP_LAST]; - VbColor comp_bg[VB_COMP_LAST]; - PangoFontDescription *comp_font; - /* status bar */ - VbColor status_bg[VB_STATUS_LAST]; - VbColor status_fg[VB_STATUS_LAST]; - PangoFontDescription *status_font[VB_STATUS_LAST]; -} VbStyle; + GtkWidget *mode, *left, *right, *cmd; +}; -typedef struct { - Gui gui; - State state; - - char *files[FILES_LAST]; - Mode *mode; - Config config; - VbStyle style; - SoupSession *session; -#ifndef FEATURE_NO_XEMBED -#ifdef HAS_GTK3 - Window embed; -#else - GdkNativeWindow embed; -#endif -#endif - GHashTable *modes; /* all available browser main modes */ -} VbCore; +struct Client { + struct Client *next; + struct State state; + struct Statusbar statusbar; + void *comp; /* pointer to data used in completion.c */ + Mode *mode; /* current active browser mode */ + /* WebKitWebContext *webctx; */ /* not used atm, use webkit_web_context_get_default() instead */ + GtkWidget *window, *input; + WebKitWebView *webview; + guint64 page_id; /* page id of the webview */ + GtkTextBuffer *buffer; + GDBusProxy *dbusproxy; + GDBusServer *dbusserver; + struct { + /* TODO split in global setting definitions and set values on a per + * client base. */ + GHashTable *settings; + guint scrollstep; + gboolean input_autohide; + gboolean incsearch; + /* completion */ + GdkRGBA comp_fg[COMP_LAST]; + GdkRGBA comp_bg[COMP_LAST]; + PangoFontDescription *comp_font; + guint default_zoom; /* default zoom level in percent */ + } config; + struct { + GSList *list; + GString *queue; /* queue holding typed keys */ + int qlen; /* pointer to last char in queue */ + int resolved; /* number of resolved keys (no mapping required) */ + guint timout_id; /* source id of the timeout function */ + char showcmd[SHOWCMD_LEN + 1]; /* buffer to show ambiguous key sequence */ + guint timeoutlen; /* timeout for ambiguous mappings */ + } map; + struct { + GHashTable *table; + char *fallback; /* default shortcut to use if none given in request */ + } shortcut; + struct { + GHashTable *table; /* holds the protocol handlers */ + } handlers; +}; -/* main object */ -extern VbCore core; +struct Vimb { + char *argv0; + Client *clients; + Window embed; + GHashTable *modes; /* all available browser main modes */ + char *configfile; /* config file given as option on startup */ + char *files[FILES_LAST]; + char *profile; /* profile name */ + struct { + guint history_max; + guint closed_max; + } config; + GtkCssProvider *style_provider; +}; -/* functions */ -void vb_add_mode(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, +gboolean vb_download_set_destination(Client *c, WebKitDownload *download, + char *suggested_filename, const char *path); +void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...); +void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...); +void vb_enter(Client *c, char id); +void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt); +Client *vb_get_client_for_page_id(guint64 pageid); +char *vb_input_get_text(Client *c); +void vb_input_set_text(Client *c, const char *text); +void vb_input_update_style(Client *c); +gboolean vb_load_uri(Client *c, const Arg *arg); +void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, ModeKeyFunc keypress, ModeInputChangedFunc input_changed); -void vb_echo_force(const MessageType type,gboolean hide, const char *error, ...); -void vb_echo(const MessageType type, gboolean hide, const char *error, ...); -void vb_enter(char id); -void vb_enter_prompt(char id, const char *prompt, gboolean print_prompt); -VbResult vb_handle_key(int key); -void vb_set_input_text(const char *text); -char *vb_get_input_text(void); -void vb_input_activate(void); -gboolean vb_load_uri(const Arg *arg); -gboolean vb_set_clipboard(const Arg *arg); -void vb_set_widget_font(GtkWidget *widget, const VbColor *fg, const VbColor *bg, PangoFontDescription *font); -void vb_update_statusbar(void); -void vb_update_status_style(void); -void vb_update_input_style(void); -void vb_update_urlbar(const char *uri); -void vb_update_mode_label(const char *label); -void vb_register_add(char buf, const char *value); -const char *vb_register_get(char buf); -gboolean vb_download(WebKitWebView *view, WebKitDownload *download, const char *path); -void vb_quit(gboolean force); +VbResult vb_mode_handle_key(Client *c, int key); +void vb_modelabel_update(Client *c, const char *label); +gboolean vb_quit(Client *c, gboolean force); +void vb_register_add(Client *c, char buf, const char *value); +const char *vb_register_get(Client *c, char buf); +void vb_statusbar_update(Client *c); +void vb_gui_style_update(Client *c, const char *name, const char *value); #endif /* end of include guard: _MAIN_H */ diff --git a/src/map.c b/src/map.c index 960ccfcb..9f2a5ea7 100644 --- a/src/map.c +++ b/src/map.c @@ -1,5 +1,7 @@ /** - * Copyright (C) 2012-2016 Daniel Carl + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,48 +17,37 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include #include +#include +#include + +#include "ascii.h" #include "config.h" -#include "events.h" #include "main.h" #include "map.h" -#include "normal.h" -#include "ascii.h" - -/* convert the lower 4 bits of byte n to its hex character */ -#define NR2HEX(n) (n & 0xf) <= 9 ? (n & 0xf) + '0' : (c & 0xf) - 10 + 'a' - -typedef struct { - char *in; /* input keys */ - int inlen; /* length of the input keys */ - char *mapped; /* mapped keys */ - int mappedlen; /* length of the mapped keys string */ - char mode; /* mode for which the map is available */ - gboolean remap; /* if false do not remap the {rhs} of this map */ -} Map; - -/* this is only to keep the variables together */ -static struct { - GSList *list; - GString *queue; /* queue holding typed keys */ - int qlen; /* pointer to last char in queue */ - int resolved; /* number of resolved keys (no mapping required) */ - guint timout_id; /* source id of the timeout function */ - char showcmd[SHOWCMD_LEN + 1]; /* buffer to show ambiguous key sequence */ -} map; - -extern VbCore vb; +#include "util.h" + +struct MapInfo { + GSList *list; + GString *queue; /* queue holding typed keys */ + int qlen; /* pointer to last char in queue */ + int resolved; /* number of resolved keys (no mapping required) */ + guint timout_id; /* source id of the timeout function */ + char showcmd[SHOWCMD_LEN + 1]; /* buffer to show ambiguous key sequence */ + guint timeoutlen; /* timeout for ambiguous mappings */ +}; -static void showcmd(int c); -static char *transchar(int c); -static gboolean map_delete_by_lhs(const char *lhs, int len, char mode); -static int keyval_to_string(guint keyval, guint state, guchar *string); -static int utf_char2bytes(guint c, guchar *buf); -static char *convert_keys(const char *in, int inlen, int *len); static char *convert_keylabel(const char *in, int inlen, int *len); -static gboolean do_timeout(gpointer data); +static char *convert_keys(const char *in, int inlen, int *len); +static gboolean do_timeout(Client *c); static void free_map(Map *map); +static int keyval_to_string(guint keyval, guint state, guchar *string); +static gboolean map_delete_by_lhs(Client *c, const char *lhs, int len, char mode); +static void showcmd(Client *c, int ch); +static char *transchar(int c); +static int utf_char2bytes(guint c, guchar *buf); + +extern struct Vimb vb; static struct { guint state; @@ -64,6 +55,9 @@ static struct { char one; char two; } special_keys[] = { + /* TODO: In GTK3, keysyms changed to have a KEY_ prefix. + * See gdkkeysyms.h and gdkkeysyms-compat.h + */ {GDK_SHIFT_MASK, GDK_Tab, 'k', 'B'}, {0, GDK_Up, 'k', 'u'}, {0, GDK_Down, 'k', 'd'}, @@ -113,87 +107,21 @@ static struct { {"", 5, CSI_STR "F2", 3}, }; -void map_init(void) -{ - map.queue = g_string_sized_new(50); -} - -void map_cleanup(void) +void map_init(Client *c) { - if (map.list) { - g_slist_free_full(map.list, (GDestroyNotify)free_map); - } - g_string_free(map.queue, true); + c->map.queue = g_string_sized_new(50); + /* TODO move this to settings */ + c->map.timeoutlen = 1000; } -/** - * Handle all key events, convert the key event into the internal used ASCII - * representation and put this into the key queue to be mapped. - */ -gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data) +void map_cleanup(Client *c) { - if (is_processing_events()) { - /* events are processing, pass all keys unmodified */ - return false; - } - - guint state = event->state; - guint keyval = event->keyval; - guchar string[32]; - int len; - - len = keyval_to_string(keyval, state, string); - - /* translate iso left tab to shift tab */ - if (keyval == GDK_ISO_Left_Tab) { - keyval = GDK_Tab; - state |= GDK_SHIFT_MASK; - } - - if (len == 0 || len == 1) { - for (int i = 0; i < LENGTH(special_keys); i++) { - if (special_keys[i].keyval == keyval - && (special_keys[i].state == 0 || state & special_keys[i].state) - ) { - state &= ~special_keys[i].state; - string[0] = CSI; - string[1] = special_keys[i].one; - string[2] = special_keys[i].two; - len = 3; - break; - } - } + if (c->map.list) { + g_slist_free_full(c->map.list, (GDestroyNotify)free_map); } - - if (len == 0) { - /* mark all unknown key events as unhandled to not break some gtk features - * like to copy clipboard content into inputbox */ - return false; - } - - /* set flag to notify that the key was typed by the user */ - vb.state.typed = true; - vb.state.processed_key = true; - - queue_event(event); - - MapState res = map_handle_keys(string, len, true); - - if (res != MAP_AMBIGUOUS) { - if (!vb.state.processed_key) { - /* events ready to be consumed */ - process_events(); - } else { - /* no ambiguous - key processed elsewhere */ - free_events(); - } + if (c->map.queue) { + g_string_free(c->map.queue, TRUE); } - - /* reset the typed flag */ - vb.state.typed = false; - - /* prevent input from going to GDK - input is sent via process_events(); */ - return true; } /** @@ -201,7 +129,7 @@ gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data) * chars. The key sequence do not need to be NUL terminated. * Keylen of 0 signalized a key timeout. */ -MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) +MapState map_handle_keys(Client *c, const guchar *keys, int keylen, gboolean use_map) { int ambiguous; Map *match = NULL; @@ -209,99 +137,99 @@ MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) static int showlen = 0; /* track the number of keys in showcmd of status bar */ /* if a previous timeout function was set remove this */ - if (map.timout_id) { - g_source_remove(map.timout_id); - map.timout_id = 0; + if (c->map.timout_id) { + g_source_remove(c->map.timout_id); + c->map.timout_id = 0; } /* don't set the timeout function if the timeout is processed now */ if (!timeout) { - map.timout_id = g_timeout_add(vb.config.timeoutlen, (GSourceFunc)do_timeout, NULL); + c->map.timout_id = g_timeout_add(c->map.timeoutlen, (GSourceFunc)do_timeout, c); } /* copy the keys onto the end of queue */ if (keylen > 0) { - g_string_overwrite_len(map.queue, map.qlen, (char*)keys, keylen); - map.qlen += keylen; + g_string_overwrite_len(c->map.queue, c->map.qlen, (char*)keys, keylen); + c->map.qlen += keylen; } /* try to resolve keys against the map */ - while (true) { + while (TRUE) { /* send any resolved key to the parser */ - while (map.resolved > 0) { + while (c->map.resolved > 0) { int qk; /* skip csi indicator and the next 2 chars - if the csi sequence * isn't part of a mapped command we let gtk handle the key - this * is required allow to move cursor in inputbox with and * keys */ - if ((map.queue->str[0] & 0xff) == CSI && map.qlen >= 3) { + if ((c->map.queue->str[0] & 0xff) == CSI && c->map.qlen >= 3) { /* get next 2 chars to build the termcap key */ - qk = TERMCAP2KEY(map.queue->str[1], map.queue->str[2]); + qk = TERMCAP2KEY(c->map.queue->str[1], c->map.queue->str[2]); - map.resolved -= 3; - map.qlen -= 3; + c->map.resolved -= 3; + c->map.qlen -= 3; /* move all other queue entries three steps to the left */ - memmove(map.queue->str, map.queue->str + 3, map.qlen); + memmove(c->map.queue->str, c->map.queue->str + 3, c->map.qlen); } else { /* get first char of queue */ - qk = map.queue->str[0]; + qk = c->map.queue->str[0]; - map.resolved--; - map.qlen--; + c->map.resolved--; + c->map.qlen--; /* move all other queue entries one step to the left */ - memmove(map.queue->str, map.queue->str + 1, map.qlen); + memmove(c->map.queue->str, c->map.queue->str + 1, c->map.qlen); } /* remove the no-map flag */ - vb.mode->flags &= ~FLAG_NOMAP; + c->mode->flags &= ~FLAG_NOMAP; /* send the key to the parser */ - if (RESULT_MORE != vb_handle_key((int)qk)) { - showcmd(0); + if (RESULT_MORE != vb_mode_handle_key(c, (int)qk)) { + showcmd(c, 0); showlen = 0; } else if (showlen > 0) { showlen--; } else { - showcmd(qk); + showcmd(c, qk); } } /* if all keys where processed return MAP_DONE */ - if (map.qlen == 0) { - map.resolved = 0; + if (c->map.qlen == 0) { + c->map.resolved = 0; return match ? MAP_DONE : MAP_NOMATCH; } /* try to find matching maps */ match = NULL; ambiguous = 0; - if (use_map && !(vb.mode->flags & FLAG_NOMAP)) { - for (GSList *l = map.list; l != NULL; l = l->next) { + if (use_map && !(c->mode->flags & FLAG_NOMAP)) { + for (GSList *l = c->map.list; l != NULL; l = l->next) { Map *m = (Map*)l->data; /* ignore maps for other modes */ - if (m->mode != vb.mode->id) { + if (m->mode != c->mode->id) { continue; } /* find ambiguous matches */ - if (!timeout && m->inlen > map.qlen && !strncmp(m->in, map.queue->str, map.qlen)) { + if (!timeout && m->inlen > c->map.qlen && !strncmp(m->in, c->map.queue->str, c->map.qlen)) { if (ambiguous == 0) { /* show command chars for the ambiguous commands */ - int i = map.qlen > SHOWCMD_LEN ? map.qlen - SHOWCMD_LEN : 0; + int i = c->map.qlen > SHOWCMD_LEN ? c->map.qlen - SHOWCMD_LEN : 0; /* appen only those chars that are not already in showcmd */ i += showlen; - while (i < map.qlen) { - showcmd(map.queue->str[i++]); + while (i < c->map.qlen) { + showcmd(c, c->map.queue->str[i++]); showlen++; } } ambiguous++; } /* complete match or better/longer match than previous found */ - if (m->inlen <= map.qlen - && !strncmp(m->in, map.queue->str, m->inlen) + if (m->inlen <= c->map.qlen + && !strncmp(m->in, c->map.queue->str, m->inlen) && (!match || match->inlen < m->inlen) ) { /* backup this found possible match */ @@ -309,7 +237,7 @@ MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) } } - /* if there are ambiguous matches return MAP_KEY and flush queue + /* if there are ambiguous matches return MAP_AMBIGUOUS and flush queue * after a timeout if the user do not type more keys */ if (ambiguous) { return MAP_AMBIGUOUS; @@ -322,7 +250,7 @@ MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) /* Flush the show command to make room for possible mapped command * chars to show. For example if :nmap foo 12g is use we want to * display the incomplete 12g command. */ - showcmd(0); + showcmd(c, 0); showlen = 0; /* Replace the matching input chars by the mapped chars. */ @@ -331,30 +259,30 @@ MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) * chars with the mapped chars. This case could also be * handled by the later string erase and prepend, but handling * it special avoids unneded function call. */ - g_string_overwrite_len(map.queue, 0, match->mapped, match->mappedlen); + g_string_overwrite_len(c->map.queue, 0, match->mapped, match->mappedlen); } else { /* Remove all the chars that where matched and prepend the * mapped chars to the queue. */ - g_string_erase(map.queue, 0, match->inlen); - g_string_prepend_len(map.queue, match->mapped, match->mappedlen); + g_string_erase(c->map.queue, 0, match->inlen); + g_string_prepend_len(c->map.queue, match->mapped, match->mappedlen); } - map.qlen += match->mappedlen - match->inlen; + c->map.qlen += match->mappedlen - match->inlen; /* without remap the mapped chars are resolved now */ if (!match->remap) { - map.resolved = match->mappedlen; + c->map.resolved = match->mappedlen; } else if (match->inlen <= match->mappedlen && !strncmp(match->in, match->mapped, match->inlen) ) { - map.resolved = match->inlen; + c->map.resolved = match->inlen; } /* Unset the typed flag - if there where keys replaced by a * mapping the resulting key string is considered as not typed by * the user. */ - vb.state.typed = false; + c->state.typed = FALSE; } else { /* first char is not mapped but resolved */ - map.resolved = 1; + c->map.resolved = 1; } } @@ -365,21 +293,21 @@ MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map) * Like map_handle_keys but use a null terminates string with untranslated * keys like that are converted here before calling map_handle_keys. */ -void map_handle_string(const char *str, gboolean use_map) +void map_handle_string(Client *c, const char *str, gboolean use_map) { int len; char *keys = convert_keys(str, strlen(str), &len); - map_handle_keys((guchar*)keys, len, use_map); + map_handle_keys(c, (guchar*)keys, len, use_map); } -void map_insert(const char *in, const char *mapped, char mode, gboolean remap) +void map_insert(Client *c, const char *in, const char *mapped, char mode, gboolean remap) { int inlen, mappedlen; char *lhs = convert_keys(in, strlen(in), &inlen); char *rhs = convert_keys(mapped, strlen(mapped), &mappedlen); /* if lhs was already mapped, remove this first */ - map_delete_by_lhs(lhs, inlen, mode); + map_delete_by_lhs(c, lhs, inlen, mode); Map *new = g_slice_new(Map); new->in = lhs; @@ -389,81 +317,186 @@ void map_insert(const char *in, const char *mapped, char mode, gboolean remap) new->mode = mode; new->remap = remap; - map.list = g_slist_prepend(map.list, new); + c->map.list = g_slist_prepend(c->map.list, new); } -gboolean map_delete(const char *in, char mode) +gboolean map_delete(Client *c, const char *in, char mode) { int len; char *lhs = convert_keys(in, strlen(in), &len); - return map_delete_by_lhs(lhs, len, mode); + return map_delete_by_lhs(c, lhs, len, mode); } /** - * Put the given char onto the show command buffer. + * Handle all key events, convert the key event into the internal used ASCII + * representation and put this into the key queue to be mapped. */ -static void showcmd(int c) +gboolean on_map_keypress(GtkWidget *widget, GdkEventKey* event, Client *c) { - char *translated; - int old, extra, overflow; + guint state = event->state; + guint keyval = event->keyval; + guchar string[32]; + int len; - if (c) { - translated = transchar(c); - old = strlen(map.showcmd); - extra = strlen(translated); - overflow = old + extra - SHOWCMD_LEN; - if (overflow > 0) { - memmove(map.showcmd, map.showcmd + overflow, old - overflow + 1); + len = keyval_to_string(keyval, state, string); + + /* translate iso left tab to shift tab */ + if (keyval == GDK_ISO_Left_Tab) { + keyval = GDK_Tab; + state |= GDK_SHIFT_MASK; + } + + if (len == 0 || len == 1) { + for (int i = 0; i < LENGTH(special_keys); i++) { + if (special_keys[i].keyval == keyval + && (special_keys[i].state == 0 || state & special_keys[i].state) + ) { + state &= ~special_keys[i].state; + string[0] = CSI; + string[1] = special_keys[i].one; + string[2] = special_keys[i].two; + len = 3; + break; + } } - strcat(map.showcmd, translated); - } else { - map.showcmd[0] = '\0'; } -#ifndef TESTLIB - /* show the typed keys */ - gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.cmd), map.showcmd); -#endif + + if (len == 0) { + /* mark all unknown key events as unhandled to not break some gtk features + * like to copy clipboard content into inputbox */ + return FALSE; + } + + /* set flag to notify that the key was typed by the user */ + c->state.typed = TRUE; + c->state.processed_key = TRUE; + + map_handle_keys(c, string, len, TRUE); + + /* reset the typed flag */ + c->state.typed = FALSE; + + return c->state.processed_key; } /** - * Translate a singe char into a readable representation to be show to the - * user in status bar. + * Translate given key string into a internal representation -> \n. + * The len of the translated key sequence is put into given *len pointer. */ -static char *transchar(int c) +static char *convert_keylabel(const char *in, int inlen, int *len) { - static char trans[5]; - int i = 0; - if (VB_IS_CTRL(c)) { - trans[i++] = '^'; - trans[i++] = CTRL(c); - } else if ((unsigned)c >= 0x80) { - trans[i++] = '<'; - trans[i++] = NR2HEX((unsigned)c >> 4); - trans[i++] = NR2HEX((unsigned)c); - trans[i++] = '>'; - } else { - trans[i++] = c; + for (int i = 0; i < LENGTH(key_labels); i++) { + if (inlen == key_labels[i].len + && !strncmp(key_labels[i].label, in, inlen) + ) { + *len = key_labels[i].chlen; + return key_labels[i].ch; + } } - trans[i++] = '\0'; + *len = 0; - return trans; + return NULL; } -static gboolean map_delete_by_lhs(const char *lhs, int len, char mode) +/** + * Converts a keysequence into a internal raw keysequence. + * Returned keyseqence must be freed if not used anymore. + */ +static char *convert_keys(const char *in, int inlen, int *len) { - for (GSList *l = map.list; l != NULL; l = l->next) { - Map *m = (Map*)l->data; + int symlen, rawlen; + char *dest; + char ch[1]; + const char *p, *raw; + GString *str = g_string_new(""); - /* remove only if the map's lhs matches the given key sequence */ - if (m->mode == mode && m->inlen == len && !strcmp(m->in, lhs)) { - /* remove the found list item */ - map.list = g_slist_delete_link(map.list, l); - free_map(m); - return true; + *len = 0; + for (p = in; p < &in[inlen]; p++) { + /* if it starts not with < we can add it literally */ + if (*p != '<') { + g_string_append_len(str, p, 1); + *len += 1; + continue; + } + + /* search matching > of symbolic name */ + symlen = 1; + do { + if (&p[symlen] == &in[inlen] + || p[symlen] == '<' + || p[symlen] == ' ' + ) { + break; + } + } while (p[symlen++] != '>'); + + raw = NULL; + rawlen = 0; + /* check if we found a real keylabel */ + if (p[symlen - 1] == '>') { + if (symlen == 5 && p[2] == '-') { + /* is it a */ + if (p[1] == 'C') { + if (VB_IS_UPPER(p[3])) { + ch[0] = p[3] - 0x40; + raw = ch; + rawlen = 1; + } else if (VB_IS_LOWER(p[3])) { + ch[0] = p[3] - 0x60; + raw = ch; + rawlen = 1; + } + } + } + + /* if we could not convert it jet - try to translate the label */ + if (!rawlen) { + raw = convert_keylabel(p, symlen, &rawlen); + } } + + /* we found no known keylabel - so use the chars literally */ + if (!rawlen) { + rawlen = symlen; + raw = p; + } + + /* write the converted keylabel into the buffer */ + g_string_append_len(str, raw, rawlen); + + /* move p after the keylabel */ + p += symlen - 1; + + *len += rawlen; } - return false; + dest = str->str; + + /* don't free the character data of the GString */ + g_string_free(str, FALSE); + + return dest; +} + +/** + * Timeout function to signalize a key timeout to the map. + */ +static gboolean do_timeout(Client *c) +{ + /* signalize the timeout to the key handler */ + map_handle_keys(c, (guchar*)"", 0, TRUE); + + /* we return TRUE to not automatically remove the resource - this is + * required to prevent critical error when we remove the source in + * map_handle_keys where we don't know if the timeout was called or not */ + return TRUE; +} + +static void free_map(Map *map) +{ + g_free(map->in); + g_free(map->mapped); + g_slice_free(Map, map); } /** @@ -524,6 +557,76 @@ static int keyval_to_string(guint keyval, guint state, guchar *string) return len; } +static gboolean map_delete_by_lhs(Client *c, const char *lhs, int len, char mode) +{ + for (GSList *l = c->map.list; l != NULL; l = l->next) { + Map *m = (Map*)l->data; + + /* remove only if the map's lhs matches the given key sequence */ + if (m->mode == mode && m->inlen == len && !strcmp(m->in, lhs)) { + /* remove the found list item */ + c->map.list = g_slist_delete_link(c->map.list, l); + free_map(m); + return TRUE; + } + } + return FALSE; +} + +/** + * Put the given char onto the show command buffer. + */ +static void showcmd(Client *c, int ch) +{ + char *translated; + int old, extra, overflow; + + if (ch) { + translated = transchar(ch); + old = strlen(c->map.showcmd); + extra = strlen(translated); + overflow = old + extra - SHOWCMD_LEN; + if (overflow > 0) { + memmove(c->map.showcmd, c->map.showcmd + overflow, old - overflow + 1); + } + strcat(c->map.showcmd, translated); + } else { + c->map.showcmd[0] = '\0'; + } +#ifndef TESTLIB + /* show the typed keys */ + gtk_label_set_text(GTK_LABEL(c->statusbar.cmd), c->map.showcmd); +#endif +} + +/** + * Translate a singe char into a readable representation to be show to the + * user in status bar. + */ +static char *transchar(int c) +{ + static char trans[5]; + int i = 0; + + if (VB_IS_CTRL(c)) { + trans[i++] = '^'; + trans[i++] = CTRL(c); + } else if ((unsigned)c >= 0x80) { +/* convert the lower 4 bits of byte n to its hex character */ +#define NR2HEX(n) (n & 0xf) <= 9 ? (n & 0xf) + '0' : (c & 0xf) - 10 + 'a' + trans[i++] = '<'; + trans[i++] = NR2HEX((unsigned)c >> 4); + trans[i++] = NR2HEX((unsigned)c); + trans[i++] = '>'; +#undef NR2HEX + } else { + trans[i++] = c; + } + trans[i++] = '\0'; + + return trans; +} + static int utf_char2bytes(guint c, guchar *buf) { if (c < 0x80) { @@ -564,125 +667,3 @@ static int utf_char2bytes(guint c, guchar *buf) buf[5] = 0x80 + (c & 0x3f); return 6; } - -/** - * Converts a keysequence into a internal raw keysequence. - * Returned keyseqence must be freed if not used anymore. - */ -static char *convert_keys(const char *in, int inlen, int *len) -{ - int symlen, rawlen; - char *dest; - char ch[1]; - const char *p, *raw; - GString *str = g_string_new(""); - - *len = 0; - for (p = in; p < &in[inlen]; p++) { - /* if it starts not with < we can add it literally */ - if (*p != '<') { - g_string_append_len(str, p, 1); - *len += 1; - continue; - } - - /* search matching > of symbolic name */ - symlen = 1; - do { - if (&p[symlen] == &in[inlen] - || p[symlen] == '<' - || p[symlen] == ' ' - ) { - break; - } - } while (p[symlen++] != '>'); - - raw = NULL; - rawlen = 0; - /* check if we found a real keylabel */ - if (p[symlen - 1] == '>') { - if (symlen == 5 && p[2] == '-') { - /* is it a */ - if (p[1] == 'C') { - if (VB_IS_UPPER(p[3])) { - ch[0] = p[3] - 0x40; - raw = ch; - rawlen = 1; - } else if (VB_IS_LOWER(p[3])) { - ch[0] = p[3] - 0x60; - raw = ch; - rawlen = 1; - } - } - } - - /* if we could not convert it jet - try to translate the label */ - if (!rawlen) { - raw = convert_keylabel(p, symlen, &rawlen); - } - } - - /* we found no known keylabel - so use the chars literally */ - if (!rawlen) { - rawlen = symlen; - raw = p; - } - - /* write the converted keylabel into the buffer */ - g_string_append_len(str, raw, rawlen); - - /* move p after the keylabel */ - p += symlen - 1; - - *len += rawlen; - } - dest = str->str; - - /* don't free the character data of the GString */ - g_string_free(str, false); - - return dest; -} - -/** - * Translate given key string into a internal representation -> \n. - * The len of the translated key sequence is put into given *len pointer. - */ -static char *convert_keylabel(const char *in, int inlen, int *len) -{ - for (int i = 0; i < LENGTH(key_labels); i++) { - if (inlen == key_labels[i].len - && !strncmp(key_labels[i].label, in, inlen) - ) { - *len = key_labels[i].chlen; - return key_labels[i].ch; - } - } - *len = 0; - - return NULL; -} - -/** - * Timeout function to signalize a key timeout to the map. - */ -static gboolean do_timeout(gpointer data) -{ - /* signalize the timeout to the key handler */ - map_handle_keys((guchar*)"", 0, true); - - /* consume any unprocessed events */ - process_events(); - - /* we return true to not automatically remove the resource - this is - * required to prevent critical error when we remove the source in - * map_handle_keys where we don't know if the timeout was called or not */ - return true; -} - -static void free_map(Map *map) -{ - g_free(map->in); - g_free(map->mapped); - g_slice_free(Map, map); -} diff --git a/src/map.h b/src/map.h index c2c2bfb1..5faf9092 100644 --- a/src/map.h +++ b/src/map.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,12 +26,12 @@ typedef enum { MAP_NOMATCH } MapState; -void map_init(void); -void map_cleanup(void); -gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data); -MapState map_handle_keys(const guchar *keys, int keylen, gboolean use_map); -void map_handle_string(const char *str, gboolean use_map); -void map_insert(const char *in, const char *mapped, char mode, gboolean remap); -gboolean map_delete(const char *in, char mode); +void map_init(Client *c); +void map_cleanup(Client *c); +MapState map_handle_keys(Client *c, const guchar *keys, int keylen, gboolean use_map); +void map_handle_string(Client *c, const char *str, gboolean use_map); +void map_insert(Client *c, const char *in, const char *mapped, char mode, gboolean remap); +gboolean map_delete(Client *c, const char *in, char mode); +gboolean on_map_keypress(GtkWidget *widget, GdkEventKey* event, Client *c); #endif /* end of include guard: _MAP_H */ diff --git a/src/normal.c b/src/normal.c index 10322163..0dc06ee3 100644 --- a/src/normal.c +++ b/src/normal.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,16 +18,18 @@ */ #include -#include "config.h" -#include "main.h" -#include "normal.h" +#include + #include "ascii.h" #include "command.h" +#include "config.h" #include "hints.h" -#include "js.h" -#include "dom.h" -#include "history.h" +#include "ext-proxy.h" +#include "main.h" +#include "normal.h" +#include "scripts/scripts.h" #include "util.h" +#include "ext-proxy.h" typedef enum { PHASE_START, @@ -48,33 +50,32 @@ typedef struct NormalCmdInfo_s { static NormalCmdInfo info = {0, '\0', '\0', PHASE_START}; -typedef VbResult (*NormalCommand)(const NormalCmdInfo *info); - -static VbResult normal_clear_input(const NormalCmdInfo *info); -static VbResult normal_descent(const NormalCmdInfo *info); -static VbResult normal_ex(const NormalCmdInfo *info); -static VbResult normal_focus_input(const NormalCmdInfo *info); -static VbResult normal_g_cmd(const NormalCmdInfo *info); -static VbResult normal_hint(const NormalCmdInfo *info); -static VbResult normal_do_hint(const char *prompt); -static VbResult normal_fire(const NormalCmdInfo *info); -static VbResult normal_increment_decrement(const NormalCmdInfo *info); -static VbResult normal_input_open(const NormalCmdInfo *info); -static VbResult normal_mark(const NormalCmdInfo *info); -static VbResult normal_navigate(const NormalCmdInfo *info); -static VbResult normal_open_clipboard(const NormalCmdInfo *info); -static VbResult normal_open(const NormalCmdInfo *info); -static VbResult normal_pass(const NormalCmdInfo *info); -static VbResult normal_prevnext(const NormalCmdInfo *info); -static VbResult normal_queue(const NormalCmdInfo *info); -static VbResult normal_quit(const NormalCmdInfo *info); -static VbResult normal_scroll(const NormalCmdInfo *info); -static VbResult normal_search(const NormalCmdInfo *info); -static VbResult normal_search_selection(const NormalCmdInfo *info); -static VbResult normal_view_inspector(const NormalCmdInfo *info); -static VbResult normal_view_source(const NormalCmdInfo *info); -static VbResult normal_yank(const NormalCmdInfo *info); -static VbResult normal_zoom(const NormalCmdInfo *info); +typedef VbResult (*NormalCommand)(Client *c, const NormalCmdInfo *info); + +static VbResult normal_clear_input(Client *c, const NormalCmdInfo *info); +static VbResult normal_descent(Client *c, const NormalCmdInfo *info); +static VbResult normal_ex(Client *c, const NormalCmdInfo *info); +static VbResult normal_fire(Client *c, const NormalCmdInfo *info); +static VbResult normal_g_cmd(Client *c, const NormalCmdInfo *info); +static VbResult normal_hint(Client *c, const NormalCmdInfo *info); +static VbResult normal_do_hint(Client *c, const char *prompt); +static VbResult normal_increment_decrement(Client *c, const NormalCmdInfo *info); +static VbResult normal_input_open(Client *c, const NormalCmdInfo *info); +static VbResult normal_mark(Client *c, const NormalCmdInfo *info); +static VbResult normal_navigate(Client *c, const NormalCmdInfo *info); +static VbResult normal_open_clipboard(Client *c, const NormalCmdInfo *info); +static VbResult normal_open(Client *c, const NormalCmdInfo *info); +static VbResult normal_pass(Client *c, const NormalCmdInfo *info); +static VbResult normal_prevnext(Client *c, const NormalCmdInfo *info); +static VbResult normal_queue(Client *c, const NormalCmdInfo *info); +static VbResult normal_quit(Client *c, const NormalCmdInfo *info); +static VbResult normal_scroll(Client *c, const NormalCmdInfo *info); +static VbResult normal_search(Client *c, const NormalCmdInfo *info); +static VbResult normal_search_selection(Client *c, const NormalCmdInfo *info); +static VbResult normal_view_inspector(Client *c, const NormalCmdInfo *info); +static VbResult normal_view_source(Client *c, const NormalCmdInfo *info); +static VbResult normal_yank(Client *c, const NormalCmdInfo *info); +static VbResult normal_zoom(Client *c, const NormalCmdInfo *info); static struct { NormalCommand func; @@ -173,7 +174,7 @@ static struct { /* [ 0x5b */ {normal_prevnext}, /* \ 0x5c */ {NULL}, /* ] 0x5d */ {normal_prevnext}, -/* ^ 0x5e */ {normal_scroll}, +/* ^ 0x5e */ {NULL}, /* _ 0x5f */ {NULL}, /* ` 0x60 */ {NULL}, /* a 0x61 */ {NULL}, @@ -209,34 +210,32 @@ static struct { /* DEL 0x7f */ {NULL}, }; -extern VbCore vb; +extern struct Vimb vb; /** * Function called when vimb enters the normal mode. */ -void normal_enter(void) +void normal_enter(Client *c) { - dom_clear_focus(vb.gui.webview); /* Make sure that when the browser area becomes visible, it will get mouse * and keyboard events */ - gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview)); - hints_clear(); + gtk_widget_grab_focus(GTK_WIDGET(c->webview)); + hints_clear(c); } /** * Called when the normal mode is left. */ -void normal_leave(void) +void normal_leave(Client *c) { - command_search(&((Arg){0})); + command_search(c, &((Arg){0, NULL}), FALSE); } /** * Handles the keypress events from webview and inputbox. */ -VbResult normal_keypress(int key) +VbResult normal_keypress(Client *c, int key) { - State *s = &vb.state; VbResult res; switch (info.phase) { @@ -250,10 +249,10 @@ VbResult normal_keypress(int key) /* handle commands that needs additional char */ info.phase = PHASE_KEY2; info.key = key; - vb.mode->flags |= FLAG_NOMAP; + c->mode->flags |= FLAG_NOMAP; } else if (key == '"') { info.phase = PHASE_REG; - vb.mode->flags |= FLAG_NOMAP; + c->mode->flags |= FLAG_NOMAP; } else { info.key = key; info.phase = PHASE_COMPLETE; @@ -266,7 +265,7 @@ VbResult normal_keypress(int key) /* hinting g; mode requires a third key */ if (info.key == 'g' && info.key2 == ';') { info.phase = PHASE_KEY3; - vb.mode->flags |= FLAG_NOMAP; + c->mode->flags |= FLAG_NOMAP; } else { info.phase = PHASE_COMPLETE; } @@ -278,7 +277,7 @@ VbResult normal_keypress(int key) break; case PHASE_REG: - if (strchr(VB_REG_CHARS, key)) { + if (strchr(REG_CHARS, key)) { info.reg = key; info.phase = PHASE_START; } else { @@ -294,10 +293,10 @@ VbResult normal_keypress(int key) /* TODO allow more commands - some that are looked up via command key * direct and those that are searched via binary search */ if ((guchar)info.key <= LENGTH(commands) && commands[(guchar)info.key].func) { - res = commands[(guchar)info.key].func(&info); + res = commands[(guchar)info.key].func(c, &info); } else { /* let gtk handle the keyevent if we have no command attached to it */ - s->processed_key = false; + c->state.processed_key = FALSE; res = RESULT_COMPLETE; } @@ -314,51 +313,49 @@ VbResult normal_keypress(int key) /** * Function called when vimb enters the passthrough mode. */ -void pass_enter(void) +void pass_enter(Client *c) { - vb_update_mode_label("-- PASS THROUGH --"); + vb_modelabel_update(c, "-- PASS THROUGH --"); } /** * Called when passthrough mode is left. */ -void pass_leave(void) +void pass_leave(Client *c) { - vb_update_mode_label(""); + ext_proxy_eval_script(c, "document.activeElement.blur();", NULL); + vb_modelabel_update(c, ""); } -VbResult pass_keypress(int key) +VbResult pass_keypress(Client *c, int key) { if (key == CTRL('[')) { /* esc */ - vb_enter('n'); + vb_enter(c, 'n'); } - vb.state.processed_key = false; + c->state.processed_key = FALSE; return RESULT_COMPLETE; } -static VbResult normal_clear_input(const NormalCmdInfo *info) +static VbResult normal_clear_input(Client *c, const NormalCmdInfo *info) { - /* if there's a text selection, deselect it */ - char *clipboard_text = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD()); - gtk_clipboard_clear(PRIMARY_CLIPBOARD()); - if (clipboard_text) { - gtk_clipboard_set_text(PRIMARY_CLIPBOARD(), clipboard_text, -1); - } - g_free(clipboard_text); + gtk_widget_grab_focus(GTK_WIDGET(c->webview)); - gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview)); - vb_echo(VB_MSG_NORMAL, false, ""); - command_search(&((Arg){0})); + /* Clear the inputbox and change the style to normal to reset also the + * possible colored error background. */ + vb_echo(c, MSG_NORMAL, FALSE, ""); + + /* Unset search highlightning. */ + command_search(c, &((Arg){0, NULL}), FALSE); return RESULT_COMPLETE; } -static VbResult normal_descent(const NormalCmdInfo *info) +static VbResult normal_descent(Client *c, const NormalCmdInfo *info) { int count = info->count ? info->count : 1; const char *uri, *p = NULL, *domain = NULL; - uri = vb.state.uri; + uri = c->state.uri; /* get domain part */ if (!uri || !*uri @@ -398,73 +395,82 @@ static VbResult normal_descent(const NormalCmdInfo *info) p = domain; } - Arg a = {VB_TARGET_CURRENT}; + Arg a = {TARGET_CURRENT}; a.s = g_strndup(uri, p - uri + 1); - vb_load_uri(&a); + vb_load_uri(c, &a); g_free(a.s); return RESULT_COMPLETE; } -static VbResult normal_ex(const NormalCmdInfo *info) +static VbResult normal_ex(Client *c, const NormalCmdInfo *info) { if (info->key == 'F') { - vb_enter_prompt('c', ";t", true); + vb_enter_prompt(c, 'c', ";t", TRUE); } else if (info->key == 'f') { - vb_enter_prompt('c', ";o", true); + vb_enter_prompt(c, 'c', ";o", TRUE); } else { char prompt[2] = {info->key, '\0'}; - vb_enter_prompt('c', prompt, true); + vb_enter_prompt(c, 'c', prompt, TRUE); } return RESULT_COMPLETE; } -static VbResult normal_focus_input(const NormalCmdInfo *info) +static VbResult normal_fire(Client *c, const NormalCmdInfo *info) { - dom_focus_input(webkit_web_view_get_dom_document(vb.gui.webview)); - return RESULT_COMPLETE; + /* If searching is currently active - click link containing current search + * highlight. We use the search_matches as indicator that the searching is + * active. */ + if (c->state.search.active) { + ext_proxy_eval_script(c, "getSelection().anchorNode.parentNode.click();", NULL); + + return RESULT_COMPLETE; + } + + return RESULT_ERROR; } -static VbResult normal_g_cmd(const NormalCmdInfo *info) +static VbResult normal_g_cmd(Client *c, const NormalCmdInfo *info) { Arg a; switch (info->key2) { case ';': { const char prompt[4] = {'g', ';', info->key3, 0}; - return normal_do_hint(prompt); + return normal_do_hint(c, prompt); } case 'F': - return normal_view_inspector(info); + return normal_view_inspector(c, info); case 'f': - normal_view_source(info); + normal_view_source(c, info); case 'g': - return normal_scroll(info); + return normal_scroll(c, info); case 'H': case 'h': - a.i = info->key2 == 'H' ? VB_TARGET_NEW : VB_TARGET_CURRENT; + a.i = info->key2 == 'H' ? TARGET_NEW : TARGET_CURRENT; a.s = NULL; - vb_load_uri(&a); + vb_load_uri(c, &a); return RESULT_COMPLETE; case 'i': - return normal_focus_input(info); + ext_proxy_focus_input(c); + return RESULT_COMPLETE; case 'U': case 'u': - return normal_descent(info); + return normal_descent(c, info); } return RESULT_ERROR; } -static VbResult normal_hint(const NormalCmdInfo *info) +static VbResult normal_hint(Client *c, const NormalCmdInfo *info) { const char prompt[3] = {info->key, info->key2, 0}; @@ -473,110 +479,76 @@ static VbResult normal_hint(const NormalCmdInfo *info) * somewhere else - it's only use is for hinting. It might be better to * allow to set various data to the mode itself to avoid toggling * variables in global skope. */ - vb.state.current_register = info->reg; - return normal_do_hint(prompt); + c->state.current_register = info->reg; + return normal_do_hint(c, prompt); } -static VbResult normal_do_hint(const char *prompt) +static VbResult normal_do_hint(Client *c, const char *prompt) { - /* check if this is a valid hint mode */ - if (!hints_parse_prompt(prompt, NULL, NULL)) { - return RESULT_ERROR; - } + /* TODO check if the prompt is of a valid hint mode */ - vb_enter_prompt('c', prompt, true); + vb_enter_prompt(c, 'c', prompt, TRUE); return RESULT_COMPLETE; } -static VbResult normal_fire(const NormalCmdInfo *info) -{ - char *value = NULL; - /* If searching is currently active - click link containing current search - * highlight. We use the search_matches as indicator that the searching is - * active. */ - if (vb.state.search_matches) { - js_eval( - webkit_web_frame_get_global_context(webkit_web_view_get_main_frame(vb.gui.webview)), - "getSelection().anchorNode.parentNode.click();", NULL, &value - ); - g_free(value); - - return RESULT_COMPLETE; - } - return RESULT_ERROR; -} - -static VbResult normal_increment_decrement(const NormalCmdInfo *info) +static VbResult normal_increment_decrement(Client *c, const NormalCmdInfo *info) { + char *js; int count = info->count ? info->count : 1; - hints_increment_uri(info->key == CTRL('A') ? count : -count); + + js = g_strdup_printf(JS_INCREMENT_URI_NUMBER, info->key == CTRL('A') ? count : -count); + ext_proxy_eval_script(c, js, NULL); + g_free(js); return RESULT_COMPLETE; } -static VbResult normal_input_open(const NormalCmdInfo *info) +static VbResult normal_input_open(Client *c, const NormalCmdInfo *info) { if (strchr("ot", info->key)) { - vb_set_input_text(info->key == 't' ? ":tabopen " : ":open "); + vb_echo(c, MSG_NORMAL, FALSE, + ":%s ", info->key == 't' ? "tabopen" : "open"); } else { - vb_echo( - VB_MSG_NORMAL, false, - ":%s %s", info->key == 'T' ? "tabopen" : "open", vb.state.uri - ); + vb_echo(c, MSG_NORMAL, FALSE, + ":%s %s", info->key == 'T' ? "tabopen" : "open", c->state.uri); } /* switch mode after setting the input text to not trigger the * commands modes input change handler */ - vb_enter_prompt('c', ":", false); + vb_enter_prompt(c, 'c', ":", FALSE); return RESULT_COMPLETE; } -static VbResult normal_mark(const NormalCmdInfo *info) +static VbResult normal_mark(Client *c, const NormalCmdInfo *info) { - gdouble current; - char *mark; - int idx; - - /* check if the second char is a valid mark char */ - if (!(mark = strchr(VB_MARK_CHARS, info->key2))) { - return RESULT_ERROR; - } - - /* get the index of the mark char */ - idx = mark - VB_MARK_CHARS; - - if ('m' == info->key) { - vb.state.marks[idx] = gtk_adjustment_get_value(vb.gui.adjust_v); - } else { - /* check if the mark was set */ - if ((int)(vb.state.marks[idx] - .5) < 0) { - return RESULT_ERROR; - } - - current = gtk_adjustment_get_value(vb.gui.adjust_v); - - /* jump to the location */ - gtk_adjustment_set_value(vb.gui.adjust_v, vb.state.marks[idx]); - - /* save previous adjust as last position */ - vb.state.marks[VB_MARK_TICK] = current; - } + /* TODO implement setting of marks - we need to get the position in the pagee from the Webextension */ return RESULT_COMPLETE; } -static VbResult normal_navigate(const NormalCmdInfo *info) +static VbResult normal_navigate(Client *c, const NormalCmdInfo *info) { int count; + WebKitBackForwardList *list; + WebKitBackForwardListItem *item; - WebKitWebView *view = vb.gui.webview; + WebKitWebView *view = c->webview; switch (info->key) { case CTRL('I'): /* fall through */ case CTRL('O'): count = info->count ? info->count : 1; if (info->key == CTRL('O')) { - count *= -1; + if (webkit_web_view_can_go_back(view)) { + list = webkit_web_view_get_back_forward_list(view); + item = webkit_back_forward_list_get_nth_item(list, -1 * count); + webkit_web_view_go_to_back_forward_list_item(view, item); + } + } else { + if (webkit_web_view_can_go_forward(view)) { + list = webkit_web_view_get_back_forward_list(view); + item = webkit_back_forward_list_get_nth_item(list, count); + webkit_web_view_go_to_back_forward_list_item(view, item); + } } - webkit_web_view_go_back_or_forward(view, count); break; case 'r': @@ -595,23 +567,23 @@ static VbResult normal_navigate(const NormalCmdInfo *info) return RESULT_COMPLETE; } -static VbResult normal_open_clipboard(const NormalCmdInfo *info) +static VbResult normal_open_clipboard(Client *c, const NormalCmdInfo *info) { - Arg a = {info->key == 'P' ? VB_TARGET_NEW : VB_TARGET_CURRENT}; + Arg a = {info->key == 'P' ? TARGET_NEW : TARGET_CURRENT}; /* if register is not the default - read out of the internal register */ if (info->reg) { - a.s = g_strdup(vb_register_get(info->reg)); + a.s = g_strdup(vb_register_get(c, info->reg)); } else { /* if no register is given use the system clipboard */ - a.s = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD()); + a.s = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); if (!a.s) { - a.s = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD()); + a.s = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_NONE)); } } if (a.s) { - vb_load_uri(&a); + vb_load_uri(c, &a); g_free(a.s); return RESULT_COMPLETE; @@ -620,208 +592,144 @@ static VbResult normal_open_clipboard(const NormalCmdInfo *info) return RESULT_ERROR; } -static VbResult normal_open(const NormalCmdInfo *info) +/** + * Open the last closed page. + */ +static VbResult normal_open(Client *c, const NormalCmdInfo *info) { - char *uri; Arg a; + if (!vb.files[FILES_CLOSED]) { + return RESULT_ERROR; + } - uri = util_file_pop_line(vb.files[FILES_CLOSED], NULL); - if (!uri) { + a.i = info->key == 'U' ? TARGET_NEW : TARGET_CURRENT; + a.s = util_file_pop_line(vb.files[FILES_CLOSED], NULL); + if (!a.s) { return RESULT_ERROR; } - /* open last closed */ - a.i = info->key == 'U' ? VB_TARGET_NEW : VB_TARGET_CURRENT; - a.s = uri; - vb_load_uri(&a); - g_free(uri); + vb_load_uri(c, &a); + g_free(a.s); return RESULT_COMPLETE; } -static VbResult normal_pass(const NormalCmdInfo *info) +static VbResult normal_pass(Client *c, const NormalCmdInfo *info) { - vb_enter('p'); + vb_enter(c, 'p'); return RESULT_COMPLETE; } -static VbResult normal_prevnext(const NormalCmdInfo *info) +static VbResult normal_prevnext(Client *c, const NormalCmdInfo *info) { +#if 0 /* TODO implement outside of hints.js */ int count = info->count ? info->count : 1; if (info->key2 == ']') { - hints_follow_link(false, count); + hints_follow_link(FALSE, count); } else if (info->key2 == '[') { - hints_follow_link(true, count); + hints_follow_link(TRUE, count); } else { return RESULT_ERROR; } +#endif return RESULT_COMPLETE; } -static VbResult normal_queue(const NormalCmdInfo *info) +static VbResult normal_queue(Client *c, const NormalCmdInfo *info) { #ifdef FEATURE_QUEUE - command_queue(&((Arg){COMMAND_QUEUE_POP})); + command_queue(c, &((Arg){COMMAND_QUEUE_POP})); #endif return RESULT_COMPLETE; } -static VbResult normal_quit(const NormalCmdInfo *info) +static VbResult normal_quit(Client *c, const NormalCmdInfo *info) { - vb_quit(false); + vb_quit(c, FALSE); return RESULT_COMPLETE; } -static VbResult normal_scroll(const NormalCmdInfo *info) +static VbResult normal_scroll(Client *c, const NormalCmdInfo *info) { - GtkAdjustment *adjust; - gdouble value, max, new; - int count = info->count ? info->count : 1; - - /* TODO split this into more functions - reduce similar code */ - switch (info->key) { - case 'h': - adjust = vb.gui.adjust_h; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) - value * count; - break; - case 'j': - adjust = vb.gui.adjust_v; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) + value * count; - break; - case 'k': - adjust = vb.gui.adjust_v; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) - value * count; - break; - case 'l': - adjust = vb.gui.adjust_h; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) + value * count; - break; - case CTRL('D'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust) / 2; - new = gtk_adjustment_get_value(adjust) + value * count; - break; - case CTRL('U'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust) / 2; - new = gtk_adjustment_get_value(adjust) - value * count; - break; - case CTRL('F'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust); - new = gtk_adjustment_get_value(adjust) + value * count; - break; - case CTRL('B'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust); - new = gtk_adjustment_get_value(adjust) - value * count; - break; - case 'G': - adjust = vb.gui.adjust_v; - max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust); - new = info->count ? (max * info->count / 100) : gtk_adjustment_get_upper(adjust); - /* save the position to mark ' */ - vb.state.marks[VB_MARK_TICK] = gtk_adjustment_get_value(adjust); - break; - case '0': /* fall through */ - case '^': - adjust = vb.gui.adjust_h; - new = gtk_adjustment_get_lower(adjust); - break; - case '$': - adjust = vb.gui.adjust_h; - new = gtk_adjustment_get_upper(adjust); - break; + char *js; - default: - if (info->key2 == 'g') { - adjust = vb.gui.adjust_v; - max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust); - new = info->count ? (max * info->count / 100) : gtk_adjustment_get_lower(adjust); - break; - } - return RESULT_ERROR; - } - max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust); - gtk_adjustment_set_value(adjust, new > max ? max : new); + js = g_strdup_printf("vbscroll('%c',%d,%d);", info->key, c->config.scrollstep, info->count); + ext_proxy_eval_script(c, js, NULL); + g_free(js); return RESULT_COMPLETE; } -static VbResult normal_search(const NormalCmdInfo *info) +static VbResult normal_search(Client *c, const NormalCmdInfo *info) { int count = (info->count > 0) ? info->count : 1; - command_search(&((Arg){info->key == 'n' ? count : -count})); + command_search(c, &((Arg){info->key == 'n' ? count : -count, NULL}), FALSE); + return RESULT_COMPLETE; } -static VbResult normal_search_selection(const NormalCmdInfo *info) +static VbResult normal_search_selection(Client *c, const NormalCmdInfo *info) { int count; char *query; /* there is no function to get the selected text so we copy current * selection to clipboard */ - webkit_web_view_copy_clipboard(vb.gui.webview); - query = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD()); + webkit_web_view_execute_editing_command(c->webview, WEBKIT_EDITING_COMMAND_COPY); + query = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); if (!query) { return RESULT_ERROR; } count = (info->count > 0) ? info->count : 1; - /* stopp possible existing search and the search highlights before - * starting the new search query */ - command_search(&((Arg){0})); - command_search(&((Arg){info->key == '*' ? count : -count, query})); + command_search(c, &((Arg){info->key == '*' ? count : -count, query}), TRUE); g_free(query); return RESULT_COMPLETE; } -static VbResult normal_view_inspector(const NormalCmdInfo *info) +static VbResult normal_view_inspector(Client *c, const NormalCmdInfo *info) { - gboolean enabled; - WebKitWebSettings *settings = webkit_web_view_get_settings(vb.gui.webview); + WebKitWebInspector *inspector; + WebKitSettings *settings; + + settings = webkit_web_view_get_settings(c->webview); + inspector = webkit_web_view_get_inspector(c->webview); + + /* Try to get the inspected uri to identify if the inspector is shown at + * the time or not. */ + if (webkit_web_inspector_is_attached(inspector)) { + webkit_web_inspector_close(inspector); + } else if (webkit_settings_get_enable_developer_extras(settings)) { + webkit_web_inspector_show(inspector); + } else { + /* Inform the user on attempt to enable webinspector when the + * developer extra are not enabled. */ + vb_echo(c, MSG_ERROR, TRUE, "webinspector is not enabled"); - g_object_get(G_OBJECT(settings), "enable-developer-extras", &enabled, NULL); - if (enabled) { - if (vb.state.is_inspecting) { - webkit_web_inspector_close(vb.gui.inspector); - } else { - webkit_web_inspector_show(vb.gui.inspector); - } - return RESULT_COMPLETE; + return RESULT_ERROR; } - - vb_echo(VB_MSG_ERROR, true, "webinspector is not enabled"); - return RESULT_ERROR; + return RESULT_COMPLETE; } -static VbResult normal_view_source(const NormalCmdInfo *info) +static VbResult normal_view_source(Client *c, const NormalCmdInfo *info) { - gboolean mode = webkit_web_view_get_view_source_mode(vb.gui.webview); - webkit_web_view_set_view_source_mode(vb.gui.webview, !mode); - webkit_web_view_reload(vb.gui.webview); + /* TODO the source mode isn't supported anymore use external editor for this */ return RESULT_COMPLETE; } -static VbResult normal_yank(const NormalCmdInfo *info) +static VbResult normal_yank(Client *c, const NormalCmdInfo *info) { Arg a = {info->key == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI}; - return command_yank(&a, info->reg) ? RESULT_COMPLETE : RESULT_ERROR; + return command_yank(c, &a, info->reg) ? RESULT_COMPLETE : RESULT_ERROR; } -static VbResult normal_zoom(const NormalCmdInfo *info) +static VbResult normal_zoom(Client *c, const NormalCmdInfo *info) { - float step, level, count; - WebKitWebSettings *setting; - WebKitWebView *view = vb.gui.webview; + float step = 0.1, level, count; + WebKitWebView *view = c->webview; /* check if the second key is allowed */ if (!strchr("iIoOz", info->key2)) { @@ -830,21 +738,15 @@ static VbResult normal_zoom(const NormalCmdInfo *info) count = info->count ? (float)info->count : 1.0; - if (info->key2 == 'z') { /* zz reset zoom */ -#ifdef FEATURE_HIGH_DPI - /* to set the zoom for high dpi displays we need full content zoom */ - webkit_web_view_set_full_content_zoom(view, true); -#endif - webkit_web_view_set_zoom_level(view, vb.config.default_zoom); + /* zz reset zoom to it's default zoom level */ + if (info->key2 == 'z') { + webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(view), FALSE); + webkit_web_view_set_zoom_level(view, c->config.default_zoom / 100.0); return RESULT_COMPLETE; } - level = webkit_web_view_get_zoom_level(view); - setting = webkit_web_view_get_settings(view); - g_object_get(G_OBJECT(setting), "zoom-step", &step, NULL); - - webkit_web_view_set_full_content_zoom(view, VB_IS_UPPER(info->key2)); + level= webkit_web_view_get_zoom_level(view); /* calculate the new zoom level */ if (info->key2 == 'i' || info->key2 == 'I') { @@ -854,6 +756,7 @@ static VbResult normal_zoom(const NormalCmdInfo *info) } /* apply the new zoom level */ + webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(view), VB_IS_LOWER(info->key2)); webkit_web_view_set_zoom_level(view, level); return RESULT_COMPLETE; diff --git a/src/normal.h b/src/normal.h index fb54e7a2..426ecb45 100644 --- a/src/normal.h +++ b/src/normal.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,11 +23,11 @@ #include "config.h" #include "main.h" -void normal_enter(void); -void normal_leave(void); -VbResult normal_keypress(int key); -void pass_enter(void); -void pass_leave(void); -VbResult pass_keypress(int key); +void normal_enter(Client *c); +void normal_leave(Client *c); +VbResult normal_keypress(Client *c, int key); +void pass_enter(Client *c); +void pass_leave(Client *c); +VbResult pass_keypress(Client *c, int key); #endif /* end of include guard: _NORMAL_H */ diff --git a/src/scripts/.gitignore b/src/scripts/.gitignore new file mode 100644 index 00000000..763376e4 --- /dev/null +++ b/src/scripts/.gitignore @@ -0,0 +1 @@ +scripts.h diff --git a/src/scripts/hints.css b/src/scripts/hints.css new file mode 100644 index 00000000..acbe28f5 --- /dev/null +++ b/src/scripts/hints.css @@ -0,0 +1,29 @@ +#_hintContainer{ + position:static +} +._hintLabel{ + -webkit-transform:translate(-4px,-4px); + background-color:#fff; + border:1px solid #444; + color:#000; + font:bold .8em monospace; + margin:0; + opacity:0.7; + padding:0px 1px; + position:absolute; + z-index:225000 +} +._hintElem{ + background-color:#ff0 !important; + color:#000 !important; + transition-delay:all 0 !important; + transition:all 0 !important; + opacity:1 !important +} +._hintElem._hintFocus{ + background-color:#8f0 !important +} +._hintLabel._hintFocus{ + opacity:1; + z-index:225000 +} diff --git a/src/hints.js b/src/scripts/hints.js similarity index 80% rename from src/hints.js rename to src/scripts/hints.js index ee3ddf3f..724490e9 100644 --- a/src/hints.js +++ b/src/scripts/hints.js @@ -1,4 +1,4 @@ -Object.freeze((function(){ +var hints = Object.freeze((function(){ 'use strict'; var hints = [], /* holds all hint data (hinted element, label, number) in view port */ @@ -197,9 +197,6 @@ Object.freeze((function(){ if (doc.body) { doc.body.appendChild(hDiv); } - /* create the default style sheet */ - createStyle(doc); - docs.push({ doc: doc, start: start, @@ -209,9 +206,11 @@ Object.freeze((function(){ /* recurse into any iframe or frame element */ for (i = 0; i < win.frames.length; i++) { - var rect, - f = win.frames[i], - e = f.frameElement; + try { + var rect, f = win.frames[i], e = f.frameElement; + } catch (ex) { + continue; + } if (isVisible(e)) { rect = e.getBoundingClientRect(); @@ -292,7 +291,7 @@ Object.freeze((function(){ }; } - /* Retrun the hint string for a given number based on configured hintkeys */ + /* Return the hint string for a given number based on configured hintkeys */ function getHintString(n) { var res = [], len = config.hintKeys.length; @@ -316,18 +315,6 @@ Object.freeze((function(){ return [doc.defaultView.scrollX, doc.defaultView.scrollY]; } - function createStyle(doc) { - if (doc.hasStyle) { - return; - } - var e = doc.createElement("style"); - /* HINT_CSS is replaces by the contents of the HINT_CSS constant from config.h */ - e.innerHTML = "HINT_CSS"; - doc.head.appendChild(e); - /* prevent us from adding the style multiple times */ - doc.hasStyle = true; - } - function focus(back) { var idx = validHints.indexOf(activeHint); /* previous active hint not found */ @@ -379,11 +366,11 @@ Object.freeze((function(){ if (tag === "input" || tag === "textarea" || tag === "select") { if (type === "radio" || type === "checkbox") { e.focus(); - click(e); + e.click(); return "DONE:"; } if (type === "submit" || type === "reset" || type === "button" || type === "image") { - click(e); + e.click(); return "DONE:"; } e.focus(); @@ -396,19 +383,16 @@ Object.freeze((function(){ } /* internal used methods */ - function open(e, newWin) { - var oldTarget = e.target; - if (newWin) { - /* set target to open in new window */ - e.target = "_blank"; - } else if (e.target === "_blank") { - e.removeAttribute("target"); + function open(e) { + var href; + if ((href = e.getAttribute('href')) && href != '#') { + window.location.href = href; + } else { + /* We call click() in async mode to return as fast as possible. If + * we don't return immediately, the EvalJS dbus call will probably + * timeout and cause errors. */ + window.setTimeout(function() {e.click();}, 0); } - /* to open links in new window the mouse events are fired with ctrl */ - /* key - otherwise some ugly pages will ignore this attribute in their */ - /* mouse event observers like duckduckgo */ - click(e, newWin); - e.target = oldTarget; } /* set focus on hint with given index valid hints array */ @@ -424,24 +408,15 @@ Object.freeze((function(){ activeHint.e.classList.add(fClass); activeHint.label.classList.add(fClass); mouseEvent(activeHint.e, "mouseover"); - - return "OVER:" + getSrc(activeHint.e);; } } - function click(e, ctrl) { - mouseEvent(e, "mouseover", ctrl); - mouseEvent(e, "mousedown", ctrl); - mouseEvent(e, "mouseup", ctrl); - mouseEvent(e, "click", ctrl); - } - - function mouseEvent(e, name, ctrl) { + function mouseEvent(e, name) { var evObj = e.ownerDocument.createEvent("MouseEvents"); evObj.initMouseEvent( name, true, true, e.ownerDocument.defaultView, 0, 0, 0, 0, 0, - (typeof ctrl != "undefined") ? ctrl : false, false, false, false, 0, null + false, false, false, false, 0, null ); e.dispatchEvent(evObj); } @@ -458,74 +433,6 @@ Object.freeze((function(){ ); } - /* follow the count last link on pagematching the given regex list */ - function followLink(rel, patterns, count) { - /* returns array of matching elements */ - function followFrame(frame) { - var i, p, reg, res = [], - doc = frame.document, - elems = [], - all = doc.getElementsByTagName("a"); - - /* first match links by rel attribute */ - for (i = all.length - 1; i >= 0; i--) { - /* collect visible elements */ - var s = doc.defaultView.getComputedStyle(all[i], null); - if (s.display !== "none" && s.visibility === "visible") { - /* if there are rel attributes elements, put them in the result */ - if (all[i].rel.toLowerCase() === rel) { - res.push(all[i]); - } else { - /* save to match them later */ - elems.push(all[i]); - } - } - } - /* match each pattern successively against each link in the page */ - for (p = 0; p < patterns.length; p++) { - reg = patterns[p]; - /* begin with the last link on page */ - for (i = elems.length - 1; i >= 0; i--) { - if (elems[i].innerText.match(reg)) { - res.push(elems[i]); - } - } - } - return res; - } - var i, j, elems, frames = allFrames(window); - for (i = 0; i < frames.length; i++) { - elems = followFrame(frames[i]); - for (j = 0; j < elems.length; j++) { - if (--count == 0) { - open(elems[j], false); - return "DONE:"; - } - } - } - return "ERROR:"; - } - - function incrementUri(count) { - var oldnum, newnum, matches = location.href.match(/(.*?)(\d+)(\D*)$/); - if (matches) { - oldnum = matches[2]; - newnum = String(Math.max(parseInt(oldnum) + count, 0)); - /* keep prepending zeros */ - if (/^0/.test(oldnum)) { - while (newnum.length < oldnum.length) { - newnum = "0" + newnum; - } - } - matches[2] = newnum; - - location.href = matches.slice(1).join(""); - - return "DONE:"; - } - return "ERROR:"; - } - function allFrames(win) { var i, f, frames = [win]; for (i = 0; i < win.frames.length; i++) { @@ -547,8 +454,7 @@ Object.freeze((function(){ }, /* holds the actions to perform on hint fire */ actionmap = { - o: function(e) {open(e, false); return "DONE:";}, - t: function(e) {open(e, true); return "DONE:";}, + ot: function(e) {open(e); return "DONE:";}, eiIOpPsTxy: function(e) {return "DATA:" + getSrc(e);}, Y: function(e) {return "DATA:" + (e.textContent || "");} }; @@ -604,8 +510,5 @@ Object.freeze((function(){ clear: clear, fire: fire, focus: focus, - /* not really hintings but uses similar logic */ - followLink: followLink, - incrementUri: incrementUri, }; })()); diff --git a/src/scripts/increment_uri_number.js b/src/scripts/increment_uri_number.js new file mode 100644 index 00000000..d90adfb9 --- /dev/null +++ b/src/scripts/increment_uri_number.js @@ -0,0 +1,16 @@ +/* TODO maybe it's better to inject this in the webview as method */ +/* and to call it if needed */ +var c = %d, on, nn, m = location.href.match(/(.*?)(\d+)(\D*)$/); +if (m) { + on = m[2]; + nn = String(Math.max(parseInt(on) + c, 0)); + /* keep prepending zeros */ + if (/^0/.test(on)) { + while (nn.length < on.length) { + nn = "0" + nn; + } + } + m[2] = nn; + + location.href = m.slice(1).join(""); +} diff --git a/src/scripts/js2h.sh b/src/scripts/js2h.sh new file mode 100755 index 00000000..0efeaea9 --- /dev/null +++ b/src/scripts/js2h.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Defined a constant for given JavaScript file. The file name is used to get +# the constant name and the contents are minifed and escaped as the value. +# ./js.h do_fancy_stuff.js creates somethings like +# #define DO_FANCY_STUFF "Escaped JavaScriptCode" + +FILE="$1" + +# Check if the file exists. +if [ ! -r "$FILE" ]; then + echo "File $FILE does not exist or is not readable" >&2 + exit 1 +fi + +# Put file extension and _ before file name, turn all to upper case to get the +# constant name. +CONSTANT=$(echo "$FILE" | sed -e 's:.*/::g' -e 's/.*\.css$/CSS_&/g' -e 's/.*\.js$/JS_&/g' -e 's/\.css$//' -e 's/\.js$//' | tr a-z A-Z) + +# minify the script +cat $FILE | \ +# remove single line comments +sed -e 's|^//.*$||g' | \ +# remove linebreaks +tr '\n\r' ' ' | \ +# remove unneeded whitespace +sed -e 's:/\*[^*]*\*/::g' \ + -e 's|[ ]\{2,\}| |g' \ + -e 's|^ ||g' \ + -e "s|[ ]\{0,\}\([-*[%/!?<>:=(){};+\&\"',\|]\)[ ]\{0,\}|\1|g" \ + -e 's|"+"||g' | \ +# ecaspe +sed -e 's|\\x20| |g' \ + -e 's|\\|\\\\|g' \ + -e 's|"|\\"|g' | \ +# write opener with the starting and ending quote char +sed -e "1s/^/#define $CONSTANT \"/" \ + -e '$s/$/"/' +echo "" diff --git a/src/scripts/scroll.js b/src/scripts/scroll.js new file mode 100644 index 00000000..83e30f89 --- /dev/null +++ b/src/scripts/scroll.js @@ -0,0 +1,61 @@ +function vbscroll(mode, scrollStep, count) { + var w = window, + d = document, + x = y = 0, + ph = w.innerHeight, + c = count||1, + rel = true; + switch (mode) { + case 'j': + y = c * scrollStep; + break; + case 'h': + x = -c * scrollStep; + break; + case 'k': + y = -c * scrollStep; + break; + case 'l': + x = c * scrollStep; + break; + case '\x04': /* ^D */ + y = c * ph / 2; + break; + case '\x15': /* ^U */ + y = -c * ph / 2; + break; + case '\x06': /* ^F */ + y = c * ph; + break; + case '\x02': /* ^B */ + y = -c * ph; + break; + case 'G': /* fall through - gg and G differ only in y value when no count is given */ + case 'g': + x = w.scrollX; + if (count) { + y = c * ((d.documentElement.scrollHeight - w.innerHeight) / 100); + } else { + y = 'G' == mode ? d.body.scrollHeight : 0; + } + rel = false; + break; + case '0': + y = w.scrollY; + rel = false; + break; + case '$': + x = d.body.scrollWidth; + y = w.scrollY; + rel = false; + break; + default: + return 1; + } + if (rel) { + w.scrollBy(x, y); + } else { + w.scroll(x, y); + } + return 0; +} diff --git a/src/setting.c b/src/setting.c index 6c5f2f69..1224ae95 100644 --- a/src/setting.c +++ b/src/setting.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,27 +17,16 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include #include + +#include "completion.h" +#include "config.h" +#include "ext-proxy.h" #include "main.h" #include "setting.h" +#include "scripts/scripts.h" #include "shortcut.h" -#include "handlers.h" -#include "util.h" -#include "completion.h" -#include "js.h" -#ifdef FEATURE_HSTS -#include "hsts.h" -#endif -#include "arh.h" - -typedef enum { - TYPE_BOOLEAN, - TYPE_INTEGER, - TYPE_CHAR, - TYPE_COLOR, - TYPE_FONT, -} Type; typedef enum { SETTING_SET, /* :set option=value */ @@ -53,202 +42,165 @@ enum { FLAG_NODUP = (1<<2), /* don't allow duplicate strings within list values */ }; -extern VbCore vb; - -static int setting_set_value(Setting *prop, void *value, SettingType type); +static int setting_set_value(Client *c, Setting *prop, void *value, SettingType type); static gboolean prepare_setting_value(Setting *prop, void *value, SettingType type, void **newvalue); -static gboolean setting_add(const char *name, int type, void *value, +static gboolean setting_add(Client *c, const char *name, DataType type, void *value, SettingFunction setter, int flags, void *data); -static void setting_print(Setting *s); +static void setting_print(Client *c, Setting *s); static void setting_free(Setting *s); -static int webkit(const char *name, int type, void *value, void *data); -static int pagecache(const char *name, int type, void *value, void *data); -static int soup(const char *name, int type, void *value, void *data); -static int internal(const char *name, int type, void *value, void *data); -static int input_autohide(const char *name, int type, void *value, void *data); -static int input_color(const char *name, int type, void *value, void *data); -static int statusbar(const char *name, int type, void *value, void *data); -static int status_color(const char *name, int type, void *value, void *data); -static int input_font(const char *name, int type, void *value, void *data); -static int status_font(const char *name, int type, void *value, void *data); -gboolean setting_fill_completion(GtkListStore *store, const char *input); -#ifdef FEATURE_COOKIE -static int cookie_accept(const char *name, int type, void *value, void *data); -#endif -static int ca_bundle(const char *name, int type, void *value, void *data); -static int proxy(const char *name, int type, void *value, void *data); -static int user_style(const char *name, int type, void *value, void *data); -static int headers(const char *name, int type, void *value, void *data); -#ifdef FEATURE_ARH -static int autoresponseheader(const char *name, int type, void *value, void *data); -#endif -static int prevnext(const char *name, int type, void *value, void *data); -static int fullscreen(const char *name, int type, void *value, void *data); -#ifdef FEATURE_HSTS -static int hsts(const char *name, int type, void *value, void *data); -#endif -#ifdef FEATURE_SOUP_CACHE -static int soup_cache(const char *name, int type, void *value, void *data); -#endif -#ifdef FEATURE_DEFAULT_ZOOM -static int default_zoom(const char *name, int type, void *value, void *data); -#endif -static gboolean validate_js_regexp_list(const char *pattern); -void setting_init() +static int cookie_accept(Client *c, const char *name, DataType type, void *value, void *data); +static int default_zoom(Client *c, const char *name, DataType type, void *value, void *data); +static int fullscreen(Client *c, const char *name, DataType type, void *value, void *data); +static int gui_style(Client *c, const char *name, DataType type, void *value, void *data); +static int hardware_acceleration_policy(Client *c, const char *name, DataType type, void *value, void *data); +static int input_autohide(Client *c, const char *name, DataType type, void *value, void *data); +static int internal(Client *c, const char *name, DataType type, void *value, void *data); +static int headers(Client *c, const char *name, DataType type, void *value, void *data); +static int user_scripts(Client *c, const char *name, DataType type, void *value, void *data); +static int user_style(Client *c, const char *name, DataType type, void *value, void *data); +static int statusbar(Client *c, const char *name, DataType type, void *value, void *data); +static int tls_policy(Client *c, const char *name, DataType type, void *value, void *data); +static int webkit(Client *c, const char *name, DataType type, void *value, void *data); +static int webkit_spell_checking(Client *c, const char *name, DataType type, void *value, void *data); +static int webkit_spell_checking_language(Client *c, const char *name, DataType type, void *value, void *data); + +extern struct Vimb vb; + + +void setting_init(Client *c) { int i; - gboolean on = true, off = false; - - vb.config.settings = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)setting_free); -#if WEBKIT_CHECK_VERSION(1, 7, 5) - setting_add("accelerated-compositing", TYPE_BOOLEAN, &off, webkit, 0, "enable-accelerated-compositing"); -#endif - setting_add("auto-resize-window", TYPE_BOOLEAN, &off, webkit, 0, "auto-resize-window"); - setting_add("auto-shrink-images", TYPE_BOOLEAN, &on, webkit, 0, "auto-shrink-images"); - setting_add("caret", TYPE_BOOLEAN, &off, webkit, 0, "enable-caret-browsing"); - setting_add("cursivfont", TYPE_CHAR, &"serif", webkit, 0, "cursive-font-family"); - setting_add("defaultencoding", TYPE_CHAR, &"utf-8", webkit, 0, "default-encoding"); - setting_add("defaultfont", TYPE_CHAR, &"sans-serif", webkit, 0, "default-font-family"); - setting_add("dns-prefetching", TYPE_BOOLEAN, &on, webkit, 0, "enable-dns-prefetching"); - setting_add("dom-paste", TYPE_BOOLEAN, &off, webkit, 0, "enable-dom-paste"); - setting_add("file-access-from-file-uris", TYPE_BOOLEAN, &off, webkit, 0, "enable-file-access-from-file-uris"); + gboolean on = TRUE, off = FALSE; + + /* TODO put the setting definitions into config.def.h and save them on vb + * struct. Use a hash table for fast name to key processing on the client. + * Separate the setting definition from the data. + * Don't set the webkit settings if they are the default on startup. */ + c->config.settings = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)setting_free); + setting_add(c, "user-agent", TYPE_CHAR, &"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/604.1 (KHTML, like Gecko) Version/11.0 Safari/604.1 " PROJECT "/" VERSION, webkit, 0, "user-agent"); + /* TODO use the real names for webkit settings */ + i = 14; + setting_add(c, "accelerated-2d-canvas", TYPE_BOOLEAN, &off, webkit, 0, "enable-accelerated-2d-canvas"); + setting_add(c, "caret", TYPE_BOOLEAN, &off, webkit, 0, "enable-caret-browsing"); + setting_add(c, "cursiv-font", TYPE_CHAR, &"serif", webkit, 0, "cursive-font-family"); + setting_add(c, "default-charset", TYPE_CHAR, &"utf-8", webkit, 0, "default-charset"); + setting_add(c, "default-font", TYPE_CHAR, &"sans-serif", webkit, 0, "default-font-family"); + setting_add(c, "dns-prefetching", TYPE_BOOLEAN, &on, webkit, 0, "enable-dns-prefetching"); i = SETTING_DEFAULT_FONT_SIZE; - setting_add("fontsize", TYPE_INTEGER, &i, webkit, 0, "default-font-size"); - setting_add("frame-flattening", TYPE_BOOLEAN, &off, webkit, 0, "enable-frame-flattening"); - setting_add("html5-database", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-database"); - setting_add("html5-local-storage", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-local-storage"); - setting_add("hyperlink-auditing", TYPE_BOOLEAN, &off, webkit, 0, "enable-hyperlink-auditing"); - setting_add("images", TYPE_BOOLEAN, &on, webkit, 0, "auto-load-images"); -#if WEBKIT_CHECK_VERSION(2, 0, 0) - setting_add("insecure-content-show", TYPE_BOOLEAN, &off, webkit, 0, "enable-display-of-insecure-content"); - setting_add("insecure-content-run", TYPE_BOOLEAN, &off, webkit, 0, "enable-running-of-insecure-content"); -#endif - setting_add("java-applet", TYPE_BOOLEAN, &on, webkit, 0, "enable-java-applet"); - setting_add("javascript-can-access-clipboard", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-access-clipboard"); - setting_add("javascript-can-open-windows-automatically", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-open-windows-automatically"); - setting_add("media-playback-allows-inline", TYPE_BOOLEAN, &on, webkit, 0, "media-playback-allows-inline"); - setting_add("media-playback-requires-user-gesture", TYPE_BOOLEAN, &off, webkit, 0, "media-playback-requires-user-gesture"); + setting_add(c, "font-size", TYPE_INTEGER, &i, webkit, 0, "default-font-size"); + setting_add(c, "frame-flattening", TYPE_BOOLEAN, &off, webkit, 0, "enable-frame-flattening"); + setting_add(c, "hardware-acceleration-policy", TYPE_CHAR, &"ondemand", hardware_acceleration_policy, FLAG_NODUP, NULL); + setting_add(c, "header", TYPE_CHAR, &"", headers, FLAG_LIST|FLAG_NODUP, "header"); + i = 1000; + setting_add(c, "hint-timeout", TYPE_INTEGER, &i, NULL, 0, NULL); + setting_add(c, "hint-keys", TYPE_CHAR, &"0123456789", NULL, 0, NULL); + setting_add(c, "hint-follow-last", TYPE_BOOLEAN, &on, NULL, 0, NULL); + setting_add(c, "hint-number-same-length", TYPE_BOOLEAN, &off, NULL, 0, NULL); + setting_add(c, "html5-database", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-database"); + setting_add(c, "html5-local-storage", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-local-storage"); + setting_add(c, "hyperlink-auditing", TYPE_BOOLEAN, &off, webkit, 0, "enable-hyperlink-auditing"); + setting_add(c, "images", TYPE_BOOLEAN, &on, webkit, 0, "auto-load-images"); + setting_add(c, "javascript-can-access-clipboard", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-access-clipboard"); + setting_add(c, "javascript-can-open-windows-automatically", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-open-windows-automatically"); + setting_add(c, "media-playback-allows-inline", TYPE_BOOLEAN, &on, webkit, 0, "media-playback-allows-inline"); + setting_add(c, "media-playback-requires-user-gesture", TYPE_BOOLEAN, &off, webkit, 0, "media-playback-requires-user-gesture"); #if WEBKIT_CHECK_VERSION(2, 4, 0) - setting_add("media-stream", TYPE_BOOLEAN, &off, webkit, 0, "enable-media-stream"); - setting_add("mediasource", TYPE_BOOLEAN, &off, webkit, 0, "enable-mediasource"); + setting_add(c, "media-stream", TYPE_BOOLEAN, &off, webkit, 0, "enable-media-stream"); + setting_add(c, "mediasource", TYPE_BOOLEAN, &off, webkit, 0, "enable-mediasource"); #endif i = 5; - setting_add("minimumfontsize", TYPE_INTEGER, &i, webkit, 0, "minimum-font-size"); - setting_add("monofont", TYPE_CHAR, &"monospace", webkit, 0, "monospace-font-family"); - i = SETTING_DEFAULT_FONT_SIZE; - setting_add("monofontsize", TYPE_INTEGER, &i, webkit, 0, "default-monospace-font-size"); - setting_add("offlinecache", TYPE_BOOLEAN, &on, webkit, 0, "enable-offline-web-application-cache"); - setting_add("pagecache", TYPE_BOOLEAN, &on, pagecache, 0, "enable-page-cache"); - setting_add("plugins", TYPE_BOOLEAN, &on, webkit, 0, "enable-plugins"); - setting_add("print-backgrounds", TYPE_BOOLEAN, &on, webkit, 0, "print-backgrounds"); - setting_add("private-browsing", TYPE_BOOLEAN, &off, webkit, 0, "enable-private-browsing"); - setting_add("resizable-text-areas", TYPE_BOOLEAN, &on, webkit, 0, "resizable-text-areas"); - setting_add("respect-image-orientation", TYPE_BOOLEAN, &off, webkit, 0, "respect-image-orientation"); - setting_add("sansfont", TYPE_CHAR, &"sans-serif", webkit, 0, "sans-serif-font-family"); - setting_add("scripts", TYPE_BOOLEAN, &on, webkit, 0, "enable-scripts"); - setting_add("seriffont", TYPE_CHAR, &"serif", webkit, 0, "serif-font-family"); - setting_add("site-specific-quirks", TYPE_BOOLEAN, &off, webkit, 0, "enable-site-specific-quirks"); -#if WEBKIT_CHECK_VERSION(1, 9, 0) - setting_add("smooth-scrolling", TYPE_BOOLEAN, &off, webkit, 0, "enable-smooth-scrolling"); -#endif - setting_add("spacial-navigation", TYPE_BOOLEAN, &off, webkit, 0, "enable-spatial-navigation"); - setting_add("spell-checking", TYPE_BOOLEAN, &off, webkit, 0, "enable-spell-checking"); - setting_add("spell-checking-languages", TYPE_CHAR, NULL, webkit, 0, "spell-checking-languages"); - setting_add("tab-key-cycles-through-elements", TYPE_BOOLEAN, &on, webkit, 0, "tab-key-cycles-through-elements"); - setting_add("universal-access-from-file-uris", TYPE_BOOLEAN, &off, webkit, 0, "enable-universal-access-from-file-uris"); - setting_add("useragent", TYPE_CHAR, &"Mozilla/5.0 (X11; Linux i686) AppleWebKit/538.15+ (KHTML, like Gecko) " PROJECT "/" VERSION " Version/8.0 Safari/538.15", webkit, 0, "user-agent"); - setting_add("webaudio", TYPE_BOOLEAN, &off, webkit, 0, "enable-webaudio"); - setting_add("webgl", TYPE_BOOLEAN, &off, webkit, 0, "enable-webgl"); - setting_add("webinspector", TYPE_BOOLEAN, &off, webkit, 0, "enable-developer-extras"); - setting_add("xssauditor", TYPE_BOOLEAN, &on, webkit, 0, "enable-xss-auditor"); + setting_add(c, "minimum-font-size", TYPE_INTEGER, &i, webkit, 0, "minimum-font-size"); + setting_add(c, "monospace-font", TYPE_CHAR, &"monospace", webkit, 0, "monospace-font-family"); + i = SETTING_DEFAULT_MONOSPACE_FONT_SIZE; + setting_add(c, "monospace-font-size", TYPE_INTEGER, &i, webkit, 0, "default-monospace-font-size"); + setting_add(c, "offline-cache", TYPE_BOOLEAN, &on, webkit, 0, "enable-offline-web-application-cache"); + setting_add(c, "plugins", TYPE_BOOLEAN, &on, webkit, 0, "enable-plugins"); + setting_add(c, "print-backgrounds", TYPE_BOOLEAN, &on, webkit, 0, "print-backgrounds"); + setting_add(c, "private-browsing", TYPE_BOOLEAN, &off, webkit, 0, "enable-private-browsing"); + setting_add(c, "sans-serif-font", TYPE_CHAR, &"sans-serif", webkit, 0, "sans-serif-font-family"); + setting_add(c, "scripts", TYPE_BOOLEAN, &on, webkit, 0, "enable-javascript"); + setting_add(c, "serif-font", TYPE_CHAR, &"serif", webkit, 0, "serif-font-family"); + setting_add(c, "site-specific-quirks", TYPE_BOOLEAN, &off, webkit, 0, "enable-site-specific-quirks"); + setting_add(c, "smooth-scrolling", TYPE_BOOLEAN, &off, webkit, 0, "enable-smooth-scrolling"); + setting_add(c, "spacial-navigation", TYPE_BOOLEAN, &off, webkit, 0, "enable-spatial-navigation"); + setting_add(c, "tabs-to-links", TYPE_BOOLEAN, &on, webkit, 0, "enable-tabs-to-links"); + setting_add(c, "webaudio", TYPE_BOOLEAN, &off, webkit, 0, "enable-webaudio"); + setting_add(c, "webgl", TYPE_BOOLEAN, &off, webkit, 0, "enable-webgl"); + setting_add(c, "webinspector", TYPE_BOOLEAN, &on, webkit, 0, "enable-developer-extras"); + setting_add(c, "xss-auditor", TYPE_BOOLEAN, &on, webkit, 0, "enable-xss-auditor"); /* internal variables */ - setting_add("stylesheet", TYPE_BOOLEAN, &on, user_style, 0, NULL); - setting_add("proxy", TYPE_BOOLEAN, &on, proxy, 0, NULL); -#ifdef FEATURE_COOKIE - setting_add("cookie-accept", TYPE_CHAR, &"always", cookie_accept, 0, NULL); - i = 4800; - setting_add("cookie-timeout", TYPE_INTEGER, &i, internal, 0, &vb.config.cookie_timeout); - i = -1; - setting_add("cookie-expire-time", TYPE_INTEGER, &i, internal, 0, &vb.config.cookie_expire_time); -#endif - setting_add("strict-ssl", TYPE_BOOLEAN, &on, soup, 0, "ssl-strict"); - setting_add("strict-focus", TYPE_BOOLEAN, &off, internal, 0, &vb.config.strict_focus); + setting_add(c, "stylesheet", TYPE_BOOLEAN, &on, user_style, 0, NULL); + setting_add(c, "user-scripts", TYPE_BOOLEAN, &on, user_scripts, 0, NULL); + setting_add(c, "cookie-accept", TYPE_CHAR, &"always", cookie_accept, 0, NULL); i = 40; - setting_add("scrollstep", TYPE_INTEGER, &i, internal, 0, &vb.config.scrollstep); - setting_add("statusbar", TYPE_BOOLEAN, &on, statusbar, 0, NULL); - setting_add("status-color-bg", TYPE_COLOR, &"#000000", status_color, 0, &vb.style.status_bg[VB_STATUS_NORMAL]); - setting_add("status-color-fg", TYPE_COLOR, &"#ffffff", status_color, 0, &vb.style.status_fg[VB_STATUS_NORMAL]); - setting_add("status-font", TYPE_FONT, &SETTING_GUI_FONT_EMPH, status_font, 0, &vb.style.status_font[VB_STATUS_NORMAL]); - setting_add("status-ssl-color-bg", TYPE_COLOR, &"#95e454", status_color, 0, &vb.style.status_bg[VB_STATUS_SSL_VALID]); - setting_add("status-ssl-color-fg", TYPE_COLOR, &"#000000", status_color, 0, &vb.style.status_fg[VB_STATUS_SSL_VALID]); - setting_add("status-ssl-font", TYPE_FONT, &SETTING_GUI_FONT_EMPH, status_font, 0, &vb.style.status_font[VB_STATUS_SSL_VALID]); - setting_add("status-sslinvalid-color-bg", TYPE_COLOR, &"#ff7777", status_color, 0, &vb.style.status_bg[VB_STATUS_SSL_INVALID]); - setting_add("status-sslinvalid-color-fg", TYPE_COLOR, &"#000000", status_color, 0, &vb.style.status_fg[VB_STATUS_SSL_INVALID]); - setting_add("status-sslinvalid-font", TYPE_FONT, &SETTING_GUI_FONT_EMPH, status_font, 0, &vb.style.status_font[VB_STATUS_SSL_INVALID]); - i = 1000; - setting_add("timeoutlen", TYPE_INTEGER, &i, internal, 0, &vb.config.timeoutlen); - setting_add("input-autohide", TYPE_BOOLEAN, &off, input_autohide, 0, &vb.config.input_autohide); - setting_add("input-bg-normal", TYPE_COLOR, &"#ffffff", input_color, 0, &vb.style.input_bg[VB_MSG_NORMAL]); - setting_add("input-bg-error", TYPE_COLOR, &"#ff7777", input_color, 0, &vb.style.input_bg[VB_MSG_ERROR]); - setting_add("input-fg-normal", TYPE_COLOR, &"#000000", input_color, 0, &vb.style.input_fg[VB_MSG_NORMAL]); - setting_add("input-fg-error", TYPE_COLOR, &"#000000", input_color, 0, &vb.style.input_fg[VB_MSG_ERROR]); - setting_add("input-font-normal", TYPE_FONT, &SETTING_GUI_FONT_NORMAL, input_font, 0, &vb.style.input_font[VB_MSG_NORMAL]); - setting_add("input-font-error", TYPE_FONT, &SETTING_GUI_FONT_EMPH, input_font, 0, &vb.style.input_font[VB_MSG_ERROR]); - setting_add("completion-font", TYPE_FONT, &SETTING_GUI_FONT_NORMAL, input_font, 0, &vb.style.comp_font); - setting_add("completion-fg-normal", TYPE_COLOR, &"#f6f3e8", input_color, 0, &vb.style.comp_fg[VB_COMP_NORMAL]); - setting_add("completion-fg-active", TYPE_COLOR, &"#ffffff", input_color, 0, &vb.style.comp_fg[VB_COMP_ACTIVE]); - setting_add("completion-bg-normal", TYPE_COLOR, &"#656565", input_color, 0, &vb.style.comp_bg[VB_COMP_NORMAL]); - setting_add("completion-bg-active", TYPE_COLOR, &"#777777", input_color, 0, &vb.style.comp_bg[VB_COMP_ACTIVE]); - setting_add("ca-bundle", TYPE_CHAR, &SETTING_CA_BUNDLE, ca_bundle, 0, NULL); - setting_add("home-page", TYPE_CHAR, &SETTING_HOME_PAGE, NULL, 0, NULL); - i = 1000; - setting_add("hint-timeout", TYPE_INTEGER, &i, NULL, 0, NULL); - setting_add("hintkeys", TYPE_CHAR, &"0123456789", NULL, 0, NULL); - setting_add("hint-follow-last", TYPE_BOOLEAN, &on, NULL, 0, NULL); - setting_add("hint-number-same-length", TYPE_BOOLEAN, &off, NULL, 0, NULL); - setting_add("download-path", TYPE_CHAR, &"", internal, 0, &vb.config.download_dir); - i = 2000; - setting_add("history-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.history_max); - i = 10; - setting_add("closed-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.closed_max); - setting_add("editor-command", TYPE_CHAR, &"x-terminal-emulator -e -vi '%s'", NULL, 0, NULL); - setting_add("header", TYPE_CHAR, &"", headers, FLAG_LIST|FLAG_NODUP, NULL); -#ifdef FEATURE_ARH - setting_add("auto-response-header", TYPE_CHAR, &"", autoresponseheader, FLAG_LIST|FLAG_NODUP, NULL); -#endif - setting_add("nextpattern", TYPE_CHAR, &"/\\bnext\\b/i,/^(>\\|>>\\|»)$/,/^(>\\|>>\\|»)/,/(>\\|>>\\|»)$/,/\\bmore\\b/i", prevnext, FLAG_LIST|FLAG_NODUP, NULL); - setting_add("previouspattern", TYPE_CHAR, &"/\\bprev\\b|previous\\b/i,/^(<\\|<<\\|«)$/,/^(<\\|<<\\|«)/,/(<\\|<<\\|«)$/", prevnext, FLAG_LIST|FLAG_NODUP, NULL); - setting_add("fullscreen", TYPE_BOOLEAN, &off, fullscreen, 0, NULL); - setting_add("download-command", TYPE_CHAR, &"/bin/sh -c \"curl -sLJOC - -A '$VIMB_USER_AGENT' -e '$VIMB_URI' -b '$VIMB_COOKIES' '%s'\"", NULL, 0, NULL); - setting_add("download-use-external", TYPE_BOOLEAN, &off, NULL, 0, NULL); -#ifdef FEATURE_HSTS - setting_add("hsts", TYPE_BOOLEAN, &on, hsts, 0, NULL); -#endif -#ifdef FEATURE_SOUP_CACHE + setting_add(c, "scroll-step", TYPE_INTEGER, &i, internal, 0, &c->config.scrollstep); + setting_add(c, "home-page", TYPE_CHAR, &SETTING_HOME_PAGE, NULL, 0, NULL); i = 2000; - setting_add("maximum-cache-size", TYPE_INTEGER, &i, soup_cache, 0, NULL); -#endif - setting_add("x-hint-command", TYPE_CHAR, &":o ;", NULL, 0, NULL); - -#ifdef FEATURE_DEFAULT_ZOOM + /* TODO should be global and not overwritten by a new client */ + setting_add(c, "history-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.history_max); + setting_add(c, "editor-command", TYPE_CHAR, &"x-terminal-emulator -e -vi '%s'", NULL, 0, NULL); + setting_add(c, "strict-ssl", TYPE_BOOLEAN, &on, tls_policy, 0, NULL); + setting_add(c, "status-bar", TYPE_BOOLEAN, &on, statusbar, 0, NULL); + i = 1000; + setting_add(c, "timeoutlen", TYPE_INTEGER, &i, internal, 0, &c->map.timeoutlen); + setting_add(c, "input-autohide", TYPE_BOOLEAN, &off, input_autohide, 0, &c->config.input_autohide); + setting_add(c, "fullscreen", TYPE_BOOLEAN, &off, fullscreen, 0, NULL); i = 100; - setting_add("default-zoom", TYPE_INTEGER, &i, default_zoom, 0, &vb.config.default_zoom); -#endif + setting_add(c, "default-zoom", TYPE_INTEGER, &i, default_zoom, 0, NULL); + setting_add(c, "download-path", TYPE_CHAR, &"~/", NULL, 0, NULL); + setting_add(c, "incsearch", TYPE_BOOLEAN, &off, internal, 0, &c->config.incsearch); + i = 10; + /* TODO should be global and not overwritten by a new client */ + setting_add(c, "closed-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.closed_max); + setting_add(c, "x-hint-command", TYPE_CHAR, &":o ;", NULL, 0, NULL); + setting_add(c, "spell-checking", TYPE_BOOLEAN, &off, webkit_spell_checking, 0, NULL); + setting_add(c, "spell-checking-languages", TYPE_CHAR, &"en_US", webkit_spell_checking_language, FLAG_LIST|FLAG_NODUP, NULL); + + +#ifdef FEATURE_GUI_STYLE_VIMB2_COMPAT + /* gui style settings vimb2 compatibility */ + setting_add(c, "completion-bg-active", TYPE_CHAR, &"#888", gui_style, 0, NULL); + setting_add(c, "completion-bg-normal", TYPE_CHAR, &"#656565", gui_style, 0, NULL); + setting_add(c, "completion-fg-active", TYPE_CHAR, &"#f6f3e8", gui_style, 0, NULL); + setting_add(c, "completion-fg-normal", TYPE_CHAR, &"#fff", gui_style, 0, NULL); + setting_add(c, "completion-font", TYPE_CHAR, &"" SETTING_GUI_FONT_NORMAL, gui_style, 0, NULL); + setting_add(c, "input-bg-error", TYPE_CHAR, &"#f77", gui_style, 0, NULL); + setting_add(c, "input-bg-normal", TYPE_CHAR, &"#fff", gui_style, 0, NULL); + setting_add(c, "input-fg-error", TYPE_CHAR, &"#000", gui_style, 0, NULL); + setting_add(c, "input-fg-normal", TYPE_CHAR, &"#000", gui_style, 0, NULL); + setting_add(c, "input-font-error", TYPE_CHAR, &"" SETTING_GUI_FONT_EMPH, gui_style, 0, NULL); + setting_add(c, "input-font-normal", TYPE_CHAR, &"" SETTING_GUI_FONT_NORMAL, gui_style, 0, NULL); + setting_add(c, "status-color-bg", TYPE_CHAR, &"#000", gui_style, 0, NULL); + setting_add(c, "status-color-fg", TYPE_CHAR, &"#fff", gui_style, 0, NULL); + setting_add(c, "status-font", TYPE_CHAR, &"" SETTING_GUI_FONT_EMPH, gui_style, 0, NULL); + setting_add(c, "status-ssl-color-bg", TYPE_CHAR, &"#95e454", gui_style, 0, NULL); + setting_add(c, "status-ssl-color-fg", TYPE_CHAR, &"#000", gui_style, 0, NULL); + setting_add(c, "status-ssl-font", TYPE_CHAR, &"", gui_style, 0, NULL); + setting_add(c, "status-sslinvalid-color-bg", TYPE_CHAR, &"#f77", gui_style, 0, NULL); + setting_add(c, "status-sslinvalid-color-fg", TYPE_CHAR, &"#000", gui_style, 0, NULL); + setting_add(c, "status-sslinvalid-font", TYPE_CHAR, &"" SETTING_GUI_FONT_EMPH, gui_style, 0, NULL); +#else + /* gui style settings vimb3 */ + setting_add(c, "completion-css", TYPE_CHAR, &"color:#fff;background-color:#656565;font:" SETTING_GUI_FONT_NORMAL, gui_style, 0, NULL); + setting_add(c, "completion-hover-css", TYPE_CHAR, &"background-color:#777;", gui_style, 0, NULL); + setting_add(c, "completion-selected-css", TYPE_CHAR, &"color:#f6f3e8;background-color:#888;", gui_style, 0, NULL); + setting_add(c, "input-css", TYPE_CHAR, &"background-color:#fff;color:#000;font:" SETTING_GUI_FONT_NORMAL, gui_style, 0, NULL); + setting_add(c, "input-error-css", TYPE_CHAR, &"background-color:#f77;font:" SETTING_GUI_FONT_EMPH, gui_style, 0, NULL); + setting_add(c, "status-css", TYPE_CHAR, &"color:#fff;background-color:#000;font:" SETTING_GUI_FONT_EMPH, gui_style, 0, NULL); + setting_add(c, "status-ssl-css", TYPE_CHAR, &"background-color:#95e454;color:#000;", gui_style, 0, NULL); + setting_add(c, "status-ssl-invalid-css", TYPE_CHAR, &"background-color:#f77;color:#000;", gui_style, 0, NULL); +#endif /* FEATURE_GUI_STYLE_VIMB2_COMPAT */ /* initialize the shortcuts and set the default shortcuts */ - shortcut_init(); - shortcut_add("dl", "https://duckduckgo.com/html/?q=$0"); - shortcut_add("dd", "https://duckduckgo.com/?q=$0"); - shortcut_set_default("dl"); - - /* initialize the handlers */ - handlers_init(); - handler_add("magnet", "xdg-open '%s'"); + shortcut_init(c); + shortcut_add(c, "dl", "https://duckduckgo.com/html/?q=$0"); + shortcut_add(c, "dd", "https://duckduckgo.com/?q=$0"); + shortcut_set_default(c, "dl"); } -VbCmdResult setting_run(char *name, const char *param) +VbCmdResult setting_run(Client *c, char *name, const char *param) { SettingType type = SETTING_SET; char modifier; @@ -277,34 +229,34 @@ VbCmdResult setting_run(char *name, const char *param) } /* lookup a matching setting */ - Setting *s = g_hash_table_lookup(vb.config.settings, name); + Setting *s = g_hash_table_lookup(c->config.settings, name); if (!s) { - vb_echo(VB_MSG_ERROR, true, "Config '%s' not found", name); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; + vb_echo(c, MSG_ERROR, TRUE, "Config '%s' not found", name); + return CMD_ERROR | CMD_KEEPINPUT; } if (type == SETTING_GET) { - setting_print(s); - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; + setting_print(c, s); + return CMD_SUCCESS | CMD_KEEPINPUT; } if (type == SETTING_TOGGLE) { if (s->type != TYPE_BOOLEAN) { - vb_echo(VB_MSG_ERROR, true, "Could not toggle none boolean %s", s->name); + vb_echo(c, MSG_ERROR, TRUE, "Could not toggle none boolean %s", s->name); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; + return CMD_ERROR | CMD_KEEPINPUT; } gboolean value = !s->value.b; - res = setting_set_value(s, &value, SETTING_SET); - setting_print(s); + res = setting_set_value(c, s, &value, SETTING_SET); + setting_print(c, s); /* make sure the new value set by the toggle keep visible */ - res |= VB_CMD_KEEPINPUT; + res |= CMD_KEEPINPUT; } else { if (!param) { - vb_echo(VB_MSG_ERROR, true, "No valid value"); + vb_echo(c, MSG_ERROR, TRUE, "No valid value"); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; + return CMD_ERROR | CMD_KEEPINPUT; } /* convert sting value into internal used data type */ @@ -314,49 +266,49 @@ VbCmdResult setting_run(char *name, const char *param) case TYPE_BOOLEAN: boolvar = g_ascii_strncasecmp(param, "true", 4) == 0 || g_ascii_strncasecmp(param, "on", 2) == 0; - res = setting_set_value(s, &boolvar, type); + res = setting_set_value(c, s, &boolvar, type); break; case TYPE_INTEGER: intvar = g_ascii_strtoull(param, (char**)NULL, 10); - res = setting_set_value(s, &intvar, type); + res = setting_set_value(c, s, &intvar, type); break; default: - res = setting_set_value(s, (void*)param, type); + res = setting_set_value(c, s, (void*)param, type); break; } } - if (res & (VB_CMD_SUCCESS | VB_CMD_KEEPINPUT)) { + if (res & (CMD_SUCCESS | CMD_KEEPINPUT)) { return res; } - vb_echo(VB_MSG_ERROR, true, "Could not set %s", s->name); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; + vb_echo(c, MSG_ERROR, TRUE, "Could not set %s", s->name); + return CMD_ERROR | CMD_KEEPINPUT; } -gboolean setting_fill_completion(GtkListStore *store, const char *input) +gboolean setting_fill_completion(Client *c, GtkListStore *store, const char *input) { - GList *src = g_hash_table_get_keys(vb.config.settings); - gboolean found = util_fill_completion(store, input, src); + GList *src = g_hash_table_get_keys(c->config.settings); + gboolean found = completion_fill(store, input, src); g_list_free(src); return found; } -void setting_cleanup(void) +void setting_cleanup(Client *c) { - if (vb.config.settings) { - g_hash_table_destroy(vb.config.settings); + if (c->config.settings) { + g_hash_table_destroy(c->config.settings); + c->config.settings = NULL; } - shortcut_cleanup(); - handlers_cleanup(); + shortcut_cleanup(c); } -static int setting_set_value(Setting *prop, void *value, SettingType type) +static int setting_set_value(Client *c, Setting *prop, void *value, SettingType type) { - int res = VB_CMD_SUCCESS; + int res = CMD_SUCCESS; /* by default given value is also the new value */ void *newvalue = NULL; gboolean free_newvalue; @@ -367,9 +319,9 @@ static int setting_set_value(Setting *prop, void *value, SettingType type) /* if there is a setter defined - call this first to check if the value is * accepted */ if (prop->setter) { - res = prop->setter(prop->name, prop->type, newvalue, prop->data); + res = prop->setter(c, prop->name, prop->type, newvalue, prop->data); /* break here on error and don't change the setting */ - if (res & VB_CMD_ERROR) { + if (res & CMD_ERROR) { goto free; } } @@ -398,12 +350,12 @@ static int setting_set_value(Setting *prop, void *value, SettingType type) /** * Prepares the value for the setting for the different setting types. - * Return value true indicates that the memory of newvalue must be freed by + * Return value TRUE indicates that the memory of newvalue must be freed by * the caller. */ static gboolean prepare_setting_value(Setting *prop, void *value, SettingType type, void **newvalue) { - gboolean islist, res = false; + gboolean islist, res = FALSE; int vlen, i = 0; char *p = NULL; @@ -418,7 +370,7 @@ static gboolean prepare_setting_value(Setting *prop, void *value, SettingType ty /* perform arithmetic operation for integer values */ if (prop->type == TYPE_INTEGER) { int *newint = g_malloc(sizeof(int)); - res = true; + res = TRUE; if (type == SETTING_APPEND) { *newint = prop->value.i + *((int*)value); } else if (type == SETTING_PREPEND) { @@ -481,7 +433,7 @@ static gboolean prepare_setting_value(Setting *prop, void *value, SettingType ty } else { *newvalue = g_strconcat(prop->value.s, value, NULL); } - res = true; + res = TRUE; } else if (type == SETTING_PREPEND) { if (islist && *(char*)value) { /* don't prepend a comma if the value is empty */ @@ -489,7 +441,7 @@ static gboolean prepare_setting_value(Setting *prop, void *value, SettingType ty } else { *newvalue = g_strconcat(value, prop->value.s, NULL); } - res = true; + res = TRUE; } else if (type == SETTING_REMOVE && p) { char *copy = g_strdup(prop->value.s); /* make p to point to the same position in the copy */ @@ -497,14 +449,14 @@ static gboolean prepare_setting_value(Setting *prop, void *value, SettingType ty memmove(p, p + i, 1 + strlen(p + vlen)); *newvalue = copy; - res = true; + res = TRUE; } return res; } -static gboolean setting_add(const char *name, int type, void *value, - SettingFunction setter, int flags, void *data) +static gboolean setting_add(Client *c, const char *name, DataType type, void *value, + SettingFunction setter, int flags, void *data) { Setting *prop = g_slice_new0(Setting); prop->name = name; @@ -513,25 +465,25 @@ static gboolean setting_add(const char *name, int type, void *value, prop->flags = flags; prop->data = data; - setting_set_value(prop, value, SETTING_SET); + setting_set_value(c, prop, value, SETTING_SET); - g_hash_table_insert(vb.config.settings, (char*)name, prop); - return true; + g_hash_table_insert(c->config.settings, (char*)name, prop); + return TRUE; } -static void setting_print(Setting *s) +static void setting_print(Client *c, Setting *s) { switch (s->type) { case TYPE_BOOLEAN: - vb_echo(VB_MSG_NORMAL, false, " %s=%s", s->name, s->value.b ? "true" : "false"); + vb_echo(c, MSG_NORMAL, FALSE, " %s=%s", s->name, s->value.b ? "true" : "false"); break; case TYPE_INTEGER: - vb_echo(VB_MSG_NORMAL, false, " %s=%d", s->name, s->value.i); + vb_echo(c, MSG_NORMAL, FALSE, " %s=%d", s->name, s->value.i); break; default: - vb_echo(VB_MSG_NORMAL, false, " %s=%s", s->name, s->value.s); + vb_echo(c, MSG_NORMAL, FALSE, " %s=%s", s->name, s->value.s); break; } } @@ -544,404 +496,268 @@ static void setting_free(Setting *s) g_slice_free(Setting, s); } -static int webkit(const char *name, int type, void *value, void *data) +static int cookie_accept(Client *c, const char *name, DataType type, void *value, void *data) { - const char *property = (const char*)data; - WebKitWebSettings *web_setting = webkit_web_view_get_settings(vb.gui.webview); - - switch (type) { - case TYPE_BOOLEAN: - g_object_set(G_OBJECT(web_setting), property, *((gboolean*)value), NULL); - break; + WebKitWebContext *ctx; + WebKitCookieManager *cm; + char *policy = (char*)value; - case TYPE_INTEGER: - g_object_set(G_OBJECT(web_setting), property, *((int*)value), NULL); - break; + ctx = webkit_web_view_get_context(c->webview); + cm = webkit_web_context_get_cookie_manager(ctx); + if (strcmp("always", policy) == 0) { + webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); + } else if (strcmp("origin", policy) == 0) { + webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY); + } else if (strcmp("never", policy) == 0) { + webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER); + } else { + vb_echo(c, MSG_ERROR, TRUE, "%s must be in [always, origin, never]", name); - default: - g_object_set(G_OBJECT(web_setting), property, (char*)value, NULL); - break; + return CMD_ERROR | CMD_KEEPINPUT; } - return VB_CMD_SUCCESS; + + return CMD_SUCCESS; } -static int pagecache(const char *name, int type, void *value, void *data) +static int default_zoom(Client *c, const char *name, DataType type, void *value, void *data) { - int res; - gboolean on = *((gboolean*)value); + /* Store the percent value in the client config. */ + c->config.default_zoom = *(int*)value; + + /* Apply the default zoom to the webview. */ + webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(c->webview), FALSE); + webkit_web_view_set_zoom_level(c->webview, c->config.default_zoom / 100.0); - /* first set the setting on the web settings */ - res = webkit(name, type, value, data); + return CMD_SUCCESS; +} - if (res == VB_CMD_SUCCESS && on) { - webkit_set_cache_model(WEBKIT_CACHE_MODEL_WEB_BROWSER); +static int fullscreen(Client *c, const char *name, DataType type, void *value, void *data) +{ + if (*(gboolean*)value) { + gtk_window_fullscreen(GTK_WINDOW(c->window)); } else { - /* reduce memory usage if caching is not used */ - webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); + gtk_window_unfullscreen(GTK_WINDOW(c->window)); } - return res; + return CMD_SUCCESS; } -static int soup(const char *name, int type, void *value, void *data) +static int hardware_acceleration_policy(Client *c, const char *name, DataType type, void *value, void *data) { - const char *property = (const char*)data; - switch (type) { - case TYPE_BOOLEAN: - g_object_set(G_OBJECT(vb.session), property, *((gboolean*)value), NULL); - break; - - case TYPE_INTEGER: - g_object_set(G_OBJECT(vb.session), property, *((int*)value), NULL); - break; - - default: - g_object_set(G_OBJECT(vb.session), property, (char*)value, NULL); - break; + WebKitSettings *settings = webkit_web_view_get_settings(c->webview); + + if (g_str_equal(value, "ondemand")) { + webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND); + } else if (g_str_equal(value, "always")) { + webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS); + } else if (g_str_equal(value, "never")) { + webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); + } else { + vb_echo(c, MSG_ERROR, TRUE, "%s must be in [ondemand, always, never]", name); + return CMD_ERROR|CMD_KEEPINPUT; } - return VB_CMD_SUCCESS; + + return CMD_SUCCESS; } -static int internal(const char *name, int type, void *value, void *data) +/** + * Allow to set user defined http headers. + * + * :set header=NAME1=VALUE!,NAME2=,NAME3 + * + * Note that these headers will replace already existing headers. If there is + * no '=' after the header name, than the complete header will be removed from + * the request (NAME3), if the '=' is present means that the header value is + * set to empty value. + */ +static int headers(Client *c, const char *name, DataType type, void *value, void *data) { - char **str; - switch (type) { - case TYPE_BOOLEAN: - *(gboolean*)data = *(gboolean*)value; - break; - - case TYPE_INTEGER: - *(int*)data = *(int*)value; - break; + ext_proxy_set_header(c, (char*)value); - default: - str = (char**)data; - OVERWRITE_STRING(*str, (char*)value); - break; - } - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -static int input_autohide(const char *name, int type, void *value, void *data) +static int input_autohide(Client *c, const char *name, DataType type, void *value, void *data) { char *text; + /* save selected value in internal variable */ *(gboolean*)data = *(gboolean*)value; /* if autohide is on and inputbox contains no text - hide it now */ if (*(gboolean*)value) { - text = vb_get_input_text(); + text = vb_input_get_text(c); if (!*text) { - gtk_widget_set_visible(GTK_WIDGET(vb.gui.input), false); + gtk_widget_set_visible(GTK_WIDGET(c->input), FALSE); } g_free(text); } else { /* autohide is off - make sure the input box is shown */ - gtk_widget_set_visible(GTK_WIDGET(vb.gui.input), true); + gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE); } - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -static int input_color(const char *name, int type, void *value, void *data) +static int internal(Client *c, const char *name, DataType type, void *value, void *data) { - VB_COLOR_PARSE((VbColor*)data, (char*)value); - vb_update_input_style(); - - return VB_CMD_SUCCESS; -} + char **str; + switch (type) { + case TYPE_BOOLEAN: + *(gboolean*)data = *(gboolean*)value; + break; -static int statusbar(const char *name, int type, void *value, void *data) -{ - gtk_widget_set_visible(GTK_WIDGET(vb.gui.statusbar.box), *(gboolean*)value); + case TYPE_INTEGER: + *(int*)data = *(int*)value; + break; - return VB_CMD_SUCCESS; + default: + str = (char**)data; + OVERWRITE_STRING(*str, (char*)value); + break; + } + return CMD_SUCCESS; } -static int status_color(const char *name, int type, void *value, void *data) +static int user_scripts(Client *c, const char *name, DataType type, void *value, void *data) { - VB_COLOR_PARSE((VbColor*)data, (char*)value); - vb_update_status_style(); + WebKitUserContentManager *ucm; + WebKitUserScript *script; + gchar *source; - return VB_CMD_SUCCESS; -} - -static int input_font(const char *name, int type, void *value, void *data) -{ - PangoFontDescription **font = (PangoFontDescription**)data; - if (*font) { - /* free previous font description */ - pango_font_description_free(*font); - } - *font = pango_font_description_from_string((char*)value); - vb_update_input_style(); + gboolean enabled = *(gboolean*)value; - return VB_CMD_SUCCESS; -} + ucm = webkit_web_view_get_user_content_manager(c->webview); + webkit_user_content_manager_remove_all_scripts(ucm); -static int status_font(const char *name, int type, void *value, void *data) -{ - PangoFontDescription **font = (PangoFontDescription**)data; - if (*font) { - /* free previous font description */ - pango_font_description_free(*font); - } - *font = pango_font_description_from_string((char*)value); - vb_update_status_style(); + if (enabled) { + if (vb.files[FILES_SCRIPT] + && g_file_get_contents(vb.files[FILES_SCRIPT], &source, NULL, NULL)) { - return VB_CMD_SUCCESS; -} + script = webkit_user_script_new( + source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL + ); -#ifdef FEATURE_COOKIE -static int cookie_accept(const char *name, int type, void *value, void *data) -{ - char *policy = (char*)value; - int i; - SoupCookieJar *jar; - static struct { - SoupCookieJarAcceptPolicy policy; - char* name; - } map[] = { - {SOUP_COOKIE_JAR_ACCEPT_ALWAYS, "always"}, - {SOUP_COOKIE_JAR_ACCEPT_NEVER, "never"}, - {SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, "origin"}, - }; - - jar = (SoupCookieJar*)soup_session_get_feature(vb.session, SOUP_TYPE_COOKIE_JAR); - - for (i = 0; i < LENGTH(map); i++) { - if (!strcmp(map[i].name, policy)) { - g_object_set(jar, SOUP_COOKIE_JAR_ACCEPT_POLICY, map[i].policy, NULL); - - return VB_CMD_SUCCESS; + webkit_user_content_manager_add_script(ucm, script); + webkit_user_script_unref(script); + g_free(source); } } - vb_echo(VB_MSG_ERROR, true, "%s must be in [always, origin, never]", name); - - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; -} -#endif -static int ca_bundle(const char *name, int type, void *value, void *data) -{ - char *expanded; - GError *error = NULL; - /* expand the given file and set it to the file database */ - expanded = util_expand((char*)value, UTIL_EXP_TILDE|UTIL_EXP_DOLLAR); - vb.config.tls_db = g_tls_file_database_new(expanded, &error); - g_free(expanded); - if (error) { - g_warning("Could not load ssl database '%s': %s", (char*)value, error->message); - g_error_free(error); - - return VB_CMD_ERROR; - } + /* Inject the global scripts. */ + script = webkit_user_script_new(JS_HINTS " " JS_SCROLL, + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL); + webkit_user_content_manager_add_script(ucm, script); + webkit_user_script_unref(script); - /* there is no function to get the file back from tls file database so - * it's saved as separate configuration */ - g_object_set(vb.session, "tls-database", vb.config.tls_db, NULL); - - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } - -static int proxy(const char *name, int type, void *value, void *data) +static int user_style(Client *c, const char *name, DataType type, void *value, void *data) { + WebKitUserContentManager *ucm; + WebKitUserStyleSheet *style; + gchar *source; + gboolean enabled = *(gboolean*)value; -#if SOUP_CHECK_VERSION(2, 42, 2) - GProxyResolver *proxy = NULL; -#else - SoupURI *proxy = NULL; -#endif - if (enabled) { - const char *http_proxy = g_getenv("http_proxy"); - - if (http_proxy != NULL && *http_proxy != '\0') { - char *proxy_new = strstr(http_proxy, "://") - ? g_strdup(http_proxy) - : g_strconcat("http://", http_proxy, NULL); - -#if SOUP_CHECK_VERSION(2, 42, 2) - const char *no_proxy; - char **ignored_hosts = NULL; - /* check for no_proxy environment variable that contains comma - * separated domains or ip addresses to skip from proxy */ - if ((no_proxy = g_getenv("no_proxy"))) { - ignored_hosts = g_strsplit(no_proxy, ",", 0); - } + ucm = webkit_web_view_get_user_content_manager(c->webview); - proxy = g_simple_proxy_resolver_new(proxy_new, ignored_hosts); - if (proxy) { - g_object_set(vb.session, "proxy-resolver", proxy, NULL); - } - g_strfreev(ignored_hosts); - g_object_unref(proxy); -#else - proxy = soup_uri_new(proxy_new); - if (proxy && SOUP_URI_VALID_FOR_HTTP(proxy)) { - g_object_set(vb.session, "proxy-uri", proxy, NULL); - } - soup_uri_free(proxy); -#endif - g_free(proxy_new); + if (enabled && vb.files[FILES_USER_STYLE]) { + if (g_file_get_contents(vb.files[FILES_USER_STYLE], &source, NULL, NULL)) { + style = webkit_user_style_sheet_new( + source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL + ); + + webkit_user_content_manager_add_style_sheet(ucm, style); + webkit_user_style_sheet_unref(style); + g_free(source); + } else { + g_warning("Could not reed style file: %s", vb.files[FILES_USER_STYLE]); } } else { - /* disable the proxy */ -#if SOUP_CHECK_VERSION(2, 42, 2) - g_object_set(vb.session, "proxy-resolver", NULL, NULL); -#else - g_object_set(vb.session, "proxy-uri", NULL, NULL); -#endif + webkit_user_content_manager_remove_all_style_sheets(ucm); } - return VB_CMD_SUCCESS; -} - -static int user_style(const char *name, int type, void *value, void *data) -{ - gboolean enabled = *(gboolean*)value; - WebKitWebSettings *web_setting = webkit_web_view_get_settings(vb.gui.webview); - - if (enabled) { - char *uri = g_strconcat("file://", vb.files[FILES_USER_STYLE], NULL); - g_object_set(web_setting, "user-stylesheet-uri", uri, NULL); - g_free(uri); - } else { - g_object_set(web_setting, "user-stylesheet-uri", NULL, NULL); - } + /* Inject the global styles with author level to allow restyling by user + * style sheets. */ + style = webkit_user_style_sheet_new(CSS_HINTS, + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_STYLE_LEVEL_AUTHOR, NULL, NULL); + webkit_user_content_manager_add_style_sheet(ucm, style); + webkit_user_style_sheet_unref(style); - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } - -/** - * Allow to set user defined http headers. - * - * :set header=NAME1=VALUE!,NAME2=,NAME3 - * - * Note that these headers will replace already existing headers. If there is - * no '=' after the header name, than the complete header will be removed from - * the request (NAME3), if the '=' is present means that the header value is - * set to empty value. - */ -static int headers(const char *name, int type, void *value, void *data) +static int statusbar(Client *c, const char *name, DataType type, void *value, void *data) { - /* remove previous parsed headers */ - if (vb.config.headers) { - soup_header_free_param_list(vb.config.headers); - vb.config.headers = NULL; - } - vb.config.headers = soup_header_parse_param_list((char*)value); + gtk_widget_set_visible(GTK_WIDGET(c->statusbar.box), *(gboolean*)value); - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -#ifdef FEATURE_ARH -static int autoresponseheader(const char *name, int type, void *value, void *data) +static int gui_style(Client *c, const char *name, DataType type, void *value, void *data) { - const char *error = NULL; - - GSList *new = arh_parse((char *)value, &error); - - if (! error) { - /* remove previous parsed headers */ - arh_free(vb.config.autoresponseheader); + vb_gui_style_update(c, name, (const char*)value); - /* add the new one */ - vb.config.autoresponseheader = new; - - return VB_CMD_SUCCESS; - - } else { - vb_echo(VB_MSG_ERROR, true, "auto-response-header: %s", error); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; - } + return CMD_SUCCESS; } -#endif -static int prevnext(const char *name, int type, void *value, void *data) +static int tls_policy(Client *c, const char *name, DataType type, void *value, void *data) { - if (validate_js_regexp_list((char*)value)) { - if (*name == 'n') { - OVERWRITE_STRING(vb.config.nextpattern, (char*)value); - } else { - OVERWRITE_STRING(vb.config.prevpattern, (char*)value); - } - return VB_CMD_SUCCESS; - } + gboolean strict = *((gboolean*)value); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; -} + webkit_web_context_set_tls_errors_policy( + webkit_web_context_get_default(), + strict ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE); -static int fullscreen(const char *name, int type, void *value, void *data) -{ - if (*(gboolean*)value) { - gtk_window_fullscreen(GTK_WINDOW(vb.gui.window)); - } else { - gtk_window_unfullscreen(GTK_WINDOW(vb.gui.window)); - } - - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -#ifdef FEATURE_HSTS -static int hsts(const char *name, int type, void *value, void *data) +static int webkit(Client *c, const char *name, DataType type, void *value, void *data) { - if (*(gboolean*)value) { - soup_session_add_feature(vb.session, SOUP_SESSION_FEATURE(vb.config.hsts_provider)); - } else { - soup_session_remove_feature(vb.session, SOUP_SESSION_FEATURE(vb.config.hsts_provider)); - } - return VB_CMD_SUCCESS; -} -#endif + const char *property = (const char*)data; + WebKitSettings *web_setting = webkit_web_view_get_settings(c->webview); -#ifdef FEATURE_SOUP_CACHE -static int soup_cache(const char *name, int type, void *value, void *data) -{ - int kilobytes = *(int*)value; + switch (type) { + case TYPE_BOOLEAN: + g_object_set(G_OBJECT(web_setting), property, *((gboolean*)value), NULL); + break; - soup_cache_set_max_size(vb.config.soup_cache, kilobytes * 1000); + case TYPE_INTEGER: + g_object_set(G_OBJECT(web_setting), property, *((int*)value), NULL); + break; - /* clear the cache if maximum-cache-size is set to zero - note that this - * will also effect other vimb instances */ - if (!kilobytes) { - soup_cache_clear(vb.config.soup_cache); + default: + g_object_set(G_OBJECT(web_setting), property, (char*)value, NULL); + break; } - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -#endif -#ifdef FEATURE_DEFAULT_ZOOM -static int default_zoom(const char *name, int type, void *value, void *data) +static int webkit_spell_checking(Client *c, const char *name, DataType type, void *value, void *data) { - *(float*)data = (float)*(int*)value / 100.0; + gboolean enabled = *((gboolean*)value); - webkit_web_view_set_full_content_zoom(vb.gui.webview, true); - webkit_web_view_set_zoom_level(vb.gui.webview, vb.config.default_zoom); + webkit_web_context_set_spell_checking_enabled( + webkit_web_context_get_default(), + enabled); - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -#endif -/** - * Validated syntax given list of JavaScript RegExp patterns. - * If validation fails, the error is shown to the user. - */ -static gboolean validate_js_regexp_list(const char *pattern) +static int webkit_spell_checking_language(Client *c, const char *name, DataType type, void *value, void *data) { - gboolean result; - char *js, *value = NULL; - WebKitWebFrame *frame = webkit_web_view_get_main_frame(vb.gui.webview); + char **languages = g_strsplit((char*)value, ",", -1); - js = g_strdup_printf("var i;for(i=0;i<[%s].length;i++);", pattern); - result = js_eval(webkit_web_frame_get_global_context(frame), js, NULL, &value); - g_free(js); + webkit_web_context_set_spell_checking_languages( + webkit_web_context_get_default(), + (const char * const *)languages); + g_strfreev(languages); - if (!result) { - vb_echo(VB_MSG_ERROR, true, "%s", value); - } - g_free(value); - return result; + return CMD_SUCCESS; } diff --git a/src/setting.h b/src/setting.h index bf46288b..584e307f 100644 --- a/src/setting.h +++ b/src/setting.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +20,13 @@ #ifndef _SETTING_H #define _SETTING_H +#include + #include "main.h" -void setting_init(void); -void setting_cleanup(void); -VbCmdResult setting_run(char* name, const char* param); -gboolean setting_fill_completion(GtkListStore *store, const char *input); +void setting_init(Client *c); +void setting_cleanup(Client *c); +VbCmdResult setting_run(Client *c, char *name, const char *param); +gboolean setting_fill_completion(Client *c, GtkListStore *store, const char *input); #endif /* end of include guard: _SETTING_H */ diff --git a/src/shortcut.c b/src/shortcut.c index 94351087..a4205022 100644 --- a/src/shortcut.c +++ b/src/shortcut.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,65 +17,66 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include +#include + +#include "ascii.h" #include "main.h" #include "shortcut.h" #include "util.h" -#include "ascii.h" - -extern VbCore vb; -static GHashTable *shortcuts = NULL; -static char *default_key = NULL; +extern struct Vimb vb; static int get_max_placeholder(const char *str); -static const char *shortcut_lookup(const char *string, const char **query); +static const char *shortcut_lookup(Client *c, const char *string, const char **query); -void shortcut_init(void) +void shortcut_init(Client *c) { - shortcuts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + c->shortcut.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + c->shortcut.fallback = NULL; } -void shortcut_cleanup(void) +void shortcut_cleanup(Client *c) { - if (shortcuts) { - g_hash_table_destroy(shortcuts); + if (c->shortcut.table) { + g_hash_table_destroy(c->shortcut.table); } } -gboolean shortcut_add(const char *key, const char *uri) +gboolean shortcut_add(Client *c, const char *key, const char *uri) { - g_hash_table_insert(shortcuts, g_strdup(key), g_strdup(uri)); + g_hash_table_insert(c->shortcut.table, g_strdup(key), g_strdup(uri)); - return true; + return TRUE; } -gboolean shortcut_remove(const char *key) +gboolean shortcut_remove(Client *c, const char *key) { - return g_hash_table_remove(shortcuts, key); + return g_hash_table_remove(c->shortcut.table, key); } -gboolean shortcut_set_default(const char *key) +gboolean shortcut_set_default(Client *c, const char *key) { /* do not check if the shortcut exists to be able to set the default * before defining the shortcut */ - OVERWRITE_STRING(default_key, key); + OVERWRITE_STRING(c->shortcut.fallback, key); - return true; + return TRUE; } /** * Retrieves the uri for given query string. Not that the memory of the * returned uri must be freed. */ -char *shortcut_get_uri(const char *string) +char *shortcut_get_uri(Client *c, const char *string) { const char *tmpl, *query = NULL; char *uri, *quoted_param; int max_num, current_num; GString *token; - tmpl = shortcut_lookup(string, &query); + tmpl = shortcut_lookup(c, string, &query); if (!tmpl) { return NULL; } @@ -151,14 +152,14 @@ char *shortcut_get_uri(const char *string) } current_num++; } - g_string_free(token, true); + g_string_free(token, TRUE); return uri; } -gboolean shortcut_fill_completion(GtkListStore *store, const char *input) +gboolean shortcut_fill_completion(Client *c, GtkListStore *store, const char *input) { - GList *src = g_hash_table_get_keys(shortcuts); + GList *src = g_hash_table_get_keys(c->shortcut.table); gboolean found = util_fill_completion(store, input, src); g_list_free(src); @@ -190,22 +191,23 @@ static int get_max_placeholder(const char *str) * pointer with the query part of the given string (everything except of the * shortcut identifier). */ -static const char *shortcut_lookup(const char *string, const char **query) +static const char *shortcut_lookup(Client *c, const char *string, const char **query) { char *p, *uri = NULL; if ((p = strchr(string, ' '))) { char *key = g_strndup(string, p - string); /* is the first word might be a shortcut */ - if ((uri = g_hash_table_lookup(shortcuts, key))) { + if ((uri = g_hash_table_lookup(c->shortcut.table, key))) { *query = p + 1; } g_free(key); } else { - uri = g_hash_table_lookup(shortcuts, string); + uri = g_hash_table_lookup(c->shortcut.table, string); } - if (!uri && default_key && (uri = g_hash_table_lookup(shortcuts, default_key))) { + if (!uri && c->shortcut.fallback + && (uri = g_hash_table_lookup(c->shortcut.table, c->shortcut.fallback))) { *query = string; } diff --git a/src/shortcut.h b/src/shortcut.h index 871ecc13..0b4cabe5 100644 --- a/src/shortcut.h +++ b/src/shortcut.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,13 @@ #ifndef _SHORTCUT_H #define _SHORTCUT_H -void shortcut_init(void); -void shortcut_cleanup(void); -gboolean shortcut_add(const char *key, const char *uri); -gboolean shortcut_remove(const char *key); -gboolean shortcut_set_default(const char *key); -char *shortcut_get_uri(const char *key); -gboolean shortcut_fill_completion(GtkListStore *store, const char *input); +void shortcut_init(Client *c); +void shortcut_cleanup(Client *c); +gboolean shortcut_add(Client *c, const char *key, const char *uri); +gboolean shortcut_remove(Client *c, const char *key); +gboolean shortcut_set_default(Client *c, const char *key); +char *shortcut_get_uri(Client *c, const char *key); +gboolean shortcut_fill_completion(Client *c, GtkListStore *store, const char *input); #endif /* end of include guard: _SHORTCUT_H */ + diff --git a/src/util.c b/src/util.c index 34b6c817..e5a813b5 100644 --- a/src/util.c +++ b/src/util.c @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,199 +17,165 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include +#include #include -#include -#include +#include +#include #include -#include -#include "main.h" -#include "util.h" +#include +#include +#include + #include "ascii.h" #include "completion.h" +#include "util.h" -extern VbCore vb; - -static gboolean match(const char *pattern, int patlen, const char *subject); -static gboolean match_list(const char *pattern, int patlen, const char *subject); +static struct { + char *config_dir; +} util; -/** - * Retrieves newly allocated string with vimb config directory with profilename. - * If profilename is NULL, path to default directory is returned. - * Returned string must be freed. - */ -char *util_get_config_dir(const char *profilename) -{ - char *path = g_build_filename(g_get_user_config_dir(), PROJECT, G_DIR_SEPARATOR_S, profilename, NULL); - util_create_dir_if_not_exists(path); +extern struct Vimb vb; - return path; -} +static void create_dir_if_not_exists(const char *dirpath); /** - * Retrieves the path to the cache dir with profilename - * If profilename is NULL, path to default directory is returned. - * Returned string must be freed. + * Build the absolute file path of given path and possible given directory. + * + * Returned path must be freed. */ -char *util_get_cache_dir(const char *profilename) +char *util_build_path(Client *c, const char *path, const char *dir) { - char *path = g_build_filename(g_get_user_cache_dir(), PROJECT, G_DIR_SEPARATOR_S, profilename, NULL); - util_create_dir_if_not_exists(path); + char *fullPath = NULL, *fexp, *dexp, *p; + int expflags = UTIL_EXP_TILDE|UTIL_EXP_DOLLAR; - return path; -} + /* if the path could be expanded */ + if ((fexp = util_expand(c, path, expflags))) { + if (*fexp == '/') { + /* path is already absolute, no need to use given dir - there is + * no need to free fexp, because this should be done by the caller + * on fullPath later */ + fullPath = fexp; + } else if (dir && *dir) { + /* try to expand also the dir given - this may be ~/path */ + if ((dexp = util_expand(c, dir, expflags))) { + /* use expanded dir and append expanded path */ + fullPath = g_build_filename(dexp, fexp, NULL); + g_free(dexp); + } + g_free(fexp); + } + } -/** - * Retrieves the path to the socket dir with profilename - * If profilename is NULL, path to default directory is returned. - * Returned string must be freed. - */ -char *util_get_runtime_dir(const char *profilename) -{ - char *path = g_build_filename(g_get_user_runtime_dir(), PROJECT, G_DIR_SEPARATOR_S, profilename, NULL); - util_create_dir_if_not_exists(path); + /* if full path not found use current dir */ + if (!fullPath) { + fullPath = g_build_filename(g_get_current_dir(), path, NULL); + } - return path; -} + /* Create the directory part of the path if it does not exists. */ + if ((p = strrchr(fullPath, '/'))) { + gboolean res; + *p = '\0'; + res = util_create_dir_if_not_exists(fullPath); + *p = '/'; -/** - * Retrieves the users home directory. - */ -const char *util_get_home_dir(void) -{ - const char *dir = g_getenv("HOME"); + if (!res) { + g_free(fullPath); - if (!dir) { - dir = g_get_home_dir(); + return NULL; + } } - return dir; + return fullPath; } -void util_create_dir_if_not_exists(const char *dirpath) +/** + * Free memory for allocated path strings. + */ +void util_cleanup(void) { - if (!g_file_test(dirpath, G_FILE_TEST_IS_DIR)) { - g_mkdir_with_parents(dirpath, 0755); + if (util.config_dir) { + g_free(util.config_dir); } } -void util_create_file_if_not_exists(const char *filename) +gboolean util_create_dir_if_not_exists(const char *dirpath) { - if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { - FILE *f = fopen(filename, "a"); - fclose(f); + if (g_mkdir_with_parents(dirpath, 0755) == -1) { + g_critical("Could not create directory '%s': %s", dirpath, strerror(errno)); + + return FALSE; } + + return TRUE; } /** - * Retrieves the length bytes from given file. + * Creates a temporary file with given content. * - * The memory of returned string have to be freed! + * Upon success, and if file is non-NULL, the actual file path used is + * returned in file. This string should be freed with g_free() when not + * needed any longer. */ -char *util_get_file_contents(const char *filename, gsize *length) +gboolean util_create_tmp_file(const char *content, char **file) { - GError *error = NULL; - char *content = NULL; - if (!(g_file_test(filename, G_FILE_TEST_IS_REGULAR) - && g_file_get_contents(filename, &content, length, &error)) - ) { - g_warning("Cannot open %s: %s", filename, error ? error->message : "file not found"); - g_clear_error(&error); + int fp; + ssize_t bytes, len; + + fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL); + if (fp == -1) { + g_critical("Could not create temp file %s", *file); + g_free(*file); + return false; } - return content; -} -/** - * Retrieves the file content as lines. - * - * The result have to be freed by g_strfreev(). - */ -char **util_get_lines(const char *filename) -{ - char *content = util_get_file_contents(filename, NULL); - char **lines = NULL; - if (content) { - /* split the file content into lines */ - lines = g_strsplit(content, "\n", -1); - g_free(content); + len = strlen(content); + + /* write content into temporary file */ + bytes = write(fp, content, len); + if (bytes < len) { + close(fp); + unlink(*file); + g_critical("Could not write temp file %s", *file); + g_free(*file); + + return false; } - return lines; + close(fp); + + return true; } /** - * Retrieves a list with unique items from file. The uniqueness is calculated - * based on the lines comparing all chars until the next char or end of - * line. + * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated + * string. * - * @filename: file to read items from - * @func: Function to parse a single line to item. - * @max_items: maximum number of items that are returned, use 0 for - * unlimited items + * Returned path must be g_freed. */ -GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, - guint max_items) +char *util_expand(Client *c, const char *src, int expflags) { - char *line, **lines; - int i, len; - GList *gl = NULL; - GHashTable *ht; - - lines = util_get_lines(filename); - if (!lines) { - return NULL; - } - - /* Use the hashtable to check for duplicates in a faster way than by - * iterating over the generated list itself. So it's enough to store the - * the keys only. */ - ht = g_hash_table_new(g_str_hash, g_str_equal); - - /* Begin with the last line of the file to make unique check easier - - * every already existing item in the table is the latest, so we don't need - * to do anything if an item already exists in the hash table. */ - len = g_strv_length(lines); - for (i = len - 1; i >= 0; i--) { - char *key, *data; - void *item; - - line = lines[i]; - g_strstrip(line); - if (!*line) { - continue; - } + const char **input = &src; + char *result; + GString *dst = g_string_new(""); + int flags = expflags; - /* if line contains tab char - separate the line at this */ - if ((data = strchr(line, '\t'))) { - *data = '\0'; - key = line; - data++; + while (**input) { + util_parse_expansion(c, input, dst, flags, "\\"); + if (VB_IS_SEPARATOR(**input)) { + /* after space the tilde expansion is allowed */ + flags = expflags; } else { - key = line; - data = NULL; - } - - /* Check if the entry is already in the result list. */ - if (g_hash_table_lookup_extended(ht, key, NULL, NULL)) { - continue; - } - - /* Item is new - prepend it to the list. Because the record are read - * in reverse order the prepend generates a list in the right order. */ - if ((item = func(key, data))) { - g_hash_table_insert(ht, key, NULL); - gl = g_list_prepend(gl, item); - - /* Don't put more entries into the list than requested. */ - if (max_items && g_hash_table_size(ht) >= max_items) { - break; - } + /* remove tile expansion for next loop */ + flags &= ~UTIL_EXP_TILDE; } + /* move pointer to the next char */ + (*input)++; } - g_strfreev(lines); - g_hash_table_destroy(ht); + result = dst->str; + g_string_free(dst, FALSE); - return gl; + return result; } /** @@ -223,7 +189,7 @@ gboolean util_file_append(const char *file, const char *format, ...) va_list args; FILE *f; - if ((f = fopen(file, "a+"))) { + if (file && (f = fopen(file, "a+"))) { flock(fileno(f), LOCK_EX); va_start(args, format); @@ -233,9 +199,9 @@ gboolean util_file_append(const char *file, const char *format, ...) flock(fileno(f), LOCK_UN); fclose(f); - return true; + return TRUE; } - return false; + return FALSE; } /** @@ -250,6 +216,9 @@ gboolean util_file_prepend(const char *file, const char *format, ...) va_list args; char *content; FILE *f; + if (!file) { + return FALSE; + } content = util_get_file_contents(file, NULL); if ((f = fopen(file, "w"))) { @@ -273,6 +242,41 @@ gboolean util_file_prepend(const char *file, const char *format, ...) return res; } +/** + * Prepend a new line to the file and make sure there are not more than + * max_lines in the file. + * + * @file: File to prepend the data + * @line: Line to be written as new first line into the file. + * The line ending is inserted automatic. + * @max_lines Maximum number of lines in file after the operation. + */ +void util_file_prepend_line(const char *file, const char *line, + unsigned int max_lines) +{ + char **lines; + GString *new_content; + + g_assert(file); + g_assert(line); + + lines = util_get_lines(file); + /* Write first the new line into the string and append the new line. */ + new_content = g_string_new(line); + g_string_append(new_content, "\n"); + if (lines) { + int len, i; + + len = g_strv_length(lines); + for (i = 0; i < len - 1 && i < max_lines - 1; i++) { + g_string_append_printf(new_content, "%s\n", lines[i]); + } + g_strfreev(lines); + } + g_file_set_contents(file, new_content->str, -1, NULL); + g_string_free(new_content, TRUE); +} + /** * Retrieves the first line from file and delete it from file. * @@ -284,17 +288,24 @@ gboolean util_file_prepend(const char *file, const char *format, ...) */ char *util_file_pop_line(const char *file, int *item_count) { - char **lines = util_get_lines(file); - char *line = NULL; - int count = 0; + char **lines; + char *new, + *line = NULL; + int len, + count = 0; + + if (!file) { + return NULL; + } + lines = util_get_lines(file); if (lines) { - int len = g_strv_length(lines); + len = g_strv_length(lines); if (len) { line = g_strdup(lines[0]); /* minus one for last empty item and one for popped item */ count = len - 2; - char *new = g_strjoinv("\n", lines + 1); + new = g_strjoinv("\n", lines + 1); g_file_set_contents(file, new, -1, NULL); g_free(new); } @@ -304,156 +315,283 @@ char *util_file_pop_line(const char *file, int *item_count) if (item_count) { *item_count = count; } + return line; } -char *util_strcasestr(const char *haystack, const char *needle) +/** + * Retrieves the config directory path according to current used profile. + * Returned string must be freed. + */ +char *util_get_config_dir(void) { - guchar c1, c2; - int i, j; - int nlen = strlen(needle); - int hlen = strlen(haystack) - nlen + 1; + char *path = g_build_filename(g_get_user_config_dir(), PROJECT, vb.profile, NULL); + create_dir_if_not_exists(path); - for (i = 0; i < hlen; i++) { - for (j = 0; j < nlen; j++) { - c1 = haystack[i + j]; - c2 = needle[j]; - if (toupper(c1) != toupper(c2)) { - goto next; - } - } - return (char*)haystack + i; -next: - ; + return path; +} + +/** + * Retrieves the length bytes from given file. + * + * The memory of returned string have to be freed with g_free(). + */ +char *util_get_file_contents(const char *filename, gsize *length) +{ + GError *error = NULL; + char *content = NULL; + + if (filename && !g_file_get_contents(filename, &content, length, &error)) { + g_warning("Cannot open %s: %s", filename, error->message); + g_error_free(error); } + return content; +} + +/** + * Buil the path from given directory and filename and checks if the file + * exists. If the file does not exists and the create option is not set, this + * function returns NULL. + * If the file exists or the create option was given the full generated path + * is returned as newly allocated string. + * + * The return value must be freed with g_free. + * + * @dir: Directory in which the file is searched. + * @filename: Filename to built the absolute path with. + * @create: If TRUE, the file is created if it does not already exist. + */ +char *util_get_filepath(const char *dir, const char *filename, gboolean create) +{ + char *fullpath; + + /* Built the full path out of config dir and given file name. */ + fullpath = g_build_filename(util_get_config_dir(), filename, NULL); + + if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR)) { + return fullpath; + } else if (create) { + /* If create option was given - create the file. */ + fclose(fopen(fullpath, "a")); + return fullpath; + } + + g_free(fullpath); return NULL; } + /** - * Replaces appearances of search in string by given replace. - * Returns a new allocated string if search was found. + * Retrieves the file content as lines. + * + * The result have to be freed by g_strfreev(). */ -char *util_str_replace(const char* search, const char* replace, const char* string) +char **util_get_lines(const char *filename) { - if (!string) { + char *content; + char **lines = NULL; + + if (!filename) { return NULL; } - char **buf = g_strsplit(string, search, -1); - char *ret = g_strjoinv(replace, buf); - g_strfreev(buf); + if ((content = util_get_file_contents(filename, NULL))) { + /* split the file content into lines */ + lines = g_strsplit(content, "\n", -1); + g_free(content); + } + return lines; +} + +/** + * Retrieves a list with unique items from file. The uniqueness is calculated + * based on the lines comparing all chars until the next char or end of + * line. + * + * @filename: file to read items from + * @func: Function to parse a single line to item. + * @max_items: maximum number of items that are returned, use 0 for + * unlimited items + */ +GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, + guint max_items) +{ + char *line, **lines; + int i, len; + GList *gl = NULL; + GHashTable *ht; + + lines = util_get_lines(filename); + if (!lines) { + return NULL; + } + + /* Use the hashtable to check for duplicates in a faster way than by + * iterating over the generated list itself. So it's enough to store the + * the keys only. */ + ht = g_hash_table_new(g_str_hash, g_str_equal); + + /* Begin with the last line of the file to make unique check easier - + * every already existing item in the table is the latest, so we don't need + * to do anything if an item already exists in the hash table. */ + len = g_strv_length(lines); + for (i = len - 1; i >= 0; i--) { + char *key, *data; + void *item; + + line = lines[i]; + g_strstrip(line); + if (!*line) { + continue; + } + + /* if line contains tab char - separate the line at this */ + if ((data = strchr(line, '\t'))) { + *data = '\0'; + key = line; + data++; + } else { + key = line; + data = NULL; + } + + /* Check if the entry is already in the result list. */ + if (g_hash_table_lookup_extended(ht, key, NULL, NULL)) { + continue; + } + + /* Item is new - prepend it to the list. Because the record are read + * in reverse order the prepend generates a list in the right order. */ + if ((item = func(key, data))) { + g_hash_table_insert(ht, key, NULL); + gl = g_list_prepend(gl, item); + + /* Don't put more entries into the list than requested. */ + if (max_items && g_hash_table_size(ht) >= max_items) { + break; + } + } + } - return ret; + g_strfreev(lines); + g_hash_table_destroy(ht); + + return gl; } /** - * Creates a temporary file with given content. - * - * Upon success, and if file is non-NULL, the actual file path used is - * returned in file. This string should be freed with g_free() when not - * needed any longer. + * Fills the given list store by matching data of also given src list. */ -gboolean util_create_tmp_file(const char *content, char **file) +gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src) { - int fp; - ssize_t bytes, len; - - fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL); - if (fp == -1) { - g_critical("Could not create temp file %s", *file); - g_free(*file); - return false; - } - - len = strlen(content); - - /* write content into temporary file */ - bytes = write(fp, content, len); - if (bytes < len) { - close(fp); - unlink(*file); - g_critical("Could not write temp file %s", *file); - g_free(*file); + gboolean found = false; + GtkTreeIter iter; - return false; + if (!input || !*input) { + for (GList *l = src; l; l = l->next) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); + found = true; + } + } else { + for (GList *l = src; l; l = l->next) { + char *value = (char*)l->data; + if (g_str_has_prefix(value, input)) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); + found = true; + } + } } - close(fp); - return true; + return found; } /** - * Build the absolute file path of given path and possible given directory. - * - * Returned path must be freed. + * Fills file path completion entries into given list store for also given + * input. */ -char *util_build_path(const char *path, const char *dir) +gboolean util_filename_fill_completion(Client *c, GtkListStore *store, const char *input) { - char *fullPath = NULL, *fexp, *dexp, *p; - int expflags = UTIL_EXP_TILDE|UTIL_EXP_DOLLAR; + gboolean found = FALSE; + GError *error = NULL; + char *input_dirname, *real_dirname; + const char *last_slash, *input_basename; + GDir *dir; + + last_slash = strrchr(input, '/'); + input_basename = last_slash ? last_slash + 1 : input; + input_dirname = g_strndup(input, input_basename - input); + real_dirname = util_expand( + c, + *input_dirname ? input_dirname : ".", + UTIL_EXP_TILDE|UTIL_EXP_DOLLAR|UTIL_EXP_SPECIAL + ); - /* if the path could be expanded */ - if ((fexp = util_expand(path, expflags))) { - if (*fexp == '/') { - /* path is already absolute, no need to use given dir - there is - * no need to free fexp, bacuse this should be done by the caller - * on fullPath later */ - fullPath = fexp; - } else if (dir && *dir) { - /* try to expand also the dir given - this may be ~/path */ - if ((dexp = util_expand(dir, expflags))) { - /* use expanded dir and append expanded path */ - fullPath = g_build_filename(dexp, fexp, NULL); - g_free(dexp); + dir = g_dir_open(real_dirname, 0, &error); + if (error) { + /* Can't open directory, likely bad user input */ + g_error_free(error); + } else { + GtkTreeIter iter; + const char *filename; + char *fullpath, *result; + + while ((filename = g_dir_read_name(dir))) { + if (g_str_has_prefix(filename, input_basename)) { + fullpath = g_build_filename(real_dirname, filename, NULL); + if (g_file_test(fullpath, G_FILE_TEST_IS_DIR)) { + result = g_strconcat(input_dirname, filename, "/", NULL); + } else { + result = g_strconcat(input_dirname, filename, NULL); + } + g_free(fullpath); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, result, -1); + g_free(result); + found = TRUE; } - g_free(fexp); } + g_dir_close(dir); } - /* if full path not found use current dir */ - if (!fullPath) { - fullPath = g_build_filename(g_get_current_dir(), path, NULL); - } - - if ((p = strrchr(fullPath, '/'))) { - *p = '\0'; - util_create_dir_if_not_exists(fullPath); - *p = '/'; - } + g_free(input_dirname); + g_free(real_dirname); - return fullPath; + return found; } /** - * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated - * string. - * - * Returned path must be g_freed. + * Returns the script result as string. + * Returned string must be freed by g_free. */ -char *util_expand(const char *src, int expflags) +char *util_js_result_as_string(WebKitJavascriptResult *result) { - const char **input = &src; - char *result; - GString *dst = g_string_new(""); - int flags = expflags; - - while (**input) { - util_parse_expansion(input, dst, flags, "\\"); - if (VB_IS_SEPARATOR(**input)) { - /* after space the tilde expansion is allowed */ - flags = expflags; - } else { - /* remove tile expansion for next loop */ - flags &= ~UTIL_EXP_TILDE; - } - /* move pointer to the next char */ - (*input)++; + JSValueRef value; + JSStringRef string; + size_t len; + char *retval = NULL; + + value = webkit_javascript_result_get_value(result); + string = JSValueToStringCopy(webkit_javascript_result_get_global_context(result), + value, NULL); + + len = JSStringGetMaximumUTF8CStringSize(string); + if (len) { + retval = g_malloc(len); + JSStringGetUTF8CString(string, retval, len); } + JSStringRelease(string); - result = dst->str; - g_string_free(dst, false); + return retval; +} - return result; +double util_js_result_as_number(WebKitJavascriptResult *result) +{ + JSValueRef value = webkit_javascript_result_get_value(result); + + return JSValueToNumber(webkit_javascript_result_get_global_context(result), value, + NULL); } /** @@ -462,19 +600,19 @@ char *util_expand(const char *src, int expflags) * not expanded char. If no expansion pattern was found, the first char is * appended to given GString. * - * @input: String pointer with the content to be parsed. - * @str: GString that will be filled with expanded content. - * @flags Flags that determine which expansion are processed. - * @quoteable String of chars that are additionally escapable by \. - * Returns true if input started with expandable pattern. + * @input: String pointer with the content to be parsed. + * @str: GString that will be filled with expanded content. + * @flags Flags that determine which expansion are processed. + * @quoteable: String of chars that are additionally escapable by \. + * Returns TRUE if input started with expandable pattern. */ -gboolean util_parse_expansion(const char **input, GString *str, int flags, - const char *quoteable) +gboolean util_parse_expansion(Client *c, const char **input, GString *str, + int flags, const char *quoteable) { GString *name; const char *env, *prev, quote = '\\'; struct passwd *pwd; - gboolean expanded = false; + gboolean expanded = FALSE; prev = *input; if (flags & UTIL_EXP_TILDE && **input == '~') { @@ -482,28 +620,28 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags, (*input)++; if (**input == '/') { - g_string_append(str, util_get_home_dir()); - expanded = true; + g_string_append(str, g_get_home_dir()); + expanded = TRUE; /* if there is no char or space after ~/ skip the / to get * /home/user instead of /home/user/ */ if (!*(*input + 1) || VB_IS_SPACE(*(*input + 1))) { (*input)++; } - } else if (**input != '-') { + } else { /* look ahead to / space or end of string to get a possible * username for ~user pattern */ name = g_string_new(""); /* current char is ~ that is skipped to get the user name */ - while (VB_IS_USER_IDENT(**input)) { + while (VB_IS_IDENT(**input)) { g_string_append_c(name, **input); (*input)++; } /* append the name to the destination string */ if ((pwd = getpwnam(name->str))) { g_string_append(str, pwd->pw_dir); - expanded = true; + expanded = TRUE; } - g_string_free(name, true); + g_string_free(name, TRUE); } /* move pointer back to last expanded char */ (*input)--; @@ -540,13 +678,13 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags, /* move pointer back to last expanded char */ (*input)--; /* variable are expanded even if they do not exists */ - expanded = true; - g_string_free(name, true); + expanded = TRUE; + g_string_free(name, TRUE); } else if (flags & UTIL_EXP_SPECIAL && **input == '%') { - if (*vb.state.uri) { + if (*c->state.uri) { /* TODO check for modifiers like :h:t:r:e */ - g_string_append(str, vb.state.uri); - expanded = true; + g_string_append(str, c->state.uri); + expanded = TRUE; } } @@ -584,274 +722,57 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags, } /** - * Compares given string against also given list of patterns. + * Sanituze filename by removeing directory separator by underscore. * - * * Matches any sequence of characters. - * ? Matches any single character except of '/'. - * {foo,bar} Matches foo or bar - '{', ',' and '}' within this pattern must be - * escaped by '\'. '*' and '?' have no special meaning within the - * curly braces. - * *?{} these chars must always be escaped by '\' to match them literally + * The string is modified in place. */ -gboolean util_wildmatch(const char *pattern, const char *subject) +char *util_sanitize_filename(char *filename) { - const char *end; - int braces, patlen, count; - - /* loop through all pattens */ - for (count = 0; *pattern; pattern = (*end == ',' ? end + 1 : end), count++) { - /* find end of the pattern - but be careful with comma in curly braces */ - braces = 0; - for (end = pattern; *end && (*end != ',' || braces || *(end - 1) == '\\'); ++end) { - if (*end == '{') { - braces++; - } else if (*end == '}') { - braces--; - } - } - /* ignore single comma */ - if (*pattern == *end) { - continue; - } - /* calculate the length of the pattern */ - patlen = end - pattern; - - /* if this pattern matches - return */ - if (match(pattern, patlen, subject)) { - return true; - } - } - - if (!count) { - /* empty pattern matches only on empty subject */ - return !*subject; - } - /* there where one or more patterns but none of them matched */ - return false; + return g_strdelimit(filename, G_DIR_SEPARATOR_S, '_'); } -/** - * Compares given subject string against the given pattern. - * The pattern needs not to bee NUL terminated. - */ -static gboolean match(const char *pattern, int patlen, const char *subject) +char *util_strcasestr(const char *haystack, const char *needle) { - int i; - char sl, pl; - - while (patlen > 0) { - switch (*pattern) { - case '?': - /* '?' matches a single char except of / and subject end */ - if (*subject == '/' || !*subject) { - return false; - } - break; - - case '*': - /* easiest case - the '*' ist the last char in pattern - this - * will always match */ - if (patlen == 1) { - return true; - } - /* Try to match as much as possible. Try to match the complete - * uri, if that fails move forward in uri and check for a - * match. */ - i = strlen(subject); - while (i >= 0 && !match(pattern + 1, patlen - 1, subject + i)) { - i--; - } - return i >= 0; - - case '}': - /* spurious '}' in pattern */ - return false; - - case '{': - /* possible {foo,bar} pattern */ - return match_list(pattern, patlen, subject); - - case '\\': - /* '\' escapes next special char */ - if (strchr("*?{}", pattern[1])) { - pattern++; - patlen--; - if (*pattern != *subject) { - return false; - } - } - break; + guchar c1, c2; + int i, j; + int nlen = strlen(needle); + int hlen = strlen(haystack) - nlen + 1; - default: - /* compare case insensitive */ - sl = *subject; - if (VB_IS_UPPER(sl)) { - sl += 'a' - 'A'; - } - pl = *pattern; - if (VB_IS_UPPER(pl)) { - pl += 'a' - 'A'; - } - if (sl != pl) { - return false; - } - break; + for (i = 0; i < hlen; i++) { + for (j = 0; j < nlen; j++) { + c1 = haystack[i + j]; + c2 = needle[j]; + if (toupper(c1) != toupper(c2)) { + goto next; + } } - /* do another loop run with next pattern and subject char */ - pattern++; - patlen--; - subject++; + return (char*)haystack + i; +next: + ; } - - /* on end of pattern only a also ended subject is a match */ - return !*subject; + return NULL; } /** - * Matches pattern starting with '{'. - * This function can process also on none null terminated pattern. + * Replaces appearances of search in string by given replace. + * Returns a new allocated string if search was found. */ -static gboolean match_list(const char *pattern, int patlen, const char *subject) +char *util_str_replace(const char *search, const char *replace, const char *string) { - int endlen; - const char *end, *s; - - /* finde the next none escaped '}' */ - for (end = pattern, endlen = patlen; endlen > 0 && *end != '}'; end++, endlen--) { - /* if escape char - move pointer one additional step */ - if (*end == '\\') { - end++; - endlen--; - } - } - - if (!*end) { - /* unterminated '{' in pattern */ - return false; - } - - s = subject; - end++; /* skip over } */ - endlen--; - pattern++; /* skip over { */ - patlen--; - while (true) { - switch (*pattern) { - case ',': - if (match(end, endlen, s)) { - return true; - } - s = subject; - pattern++; - patlen--; - break; - - case '}': - return match(end, endlen, s); - - case '\\': - if (pattern[1] == ',' || pattern[1] == '}' || pattern[1] == '{') { - pattern++; - patlen--; - } - /* fall through */ - - default: - if (*pattern == *s) { - pattern++; - patlen--; - s++; - } else { - /* this item of the list does not match - move forward to - * the next none escaped ',' or '}' */ - s = subject; - for (s = subject; *pattern != ',' && *pattern != '}'; pattern++, patlen--) { - /* if escape char is found - skip next char */ - if (*pattern == '\\') { - pattern++; - patlen--; - } - } - /* found ',' skip over it to check the next list item */ - if (*pattern == ',') { - pattern++; - patlen--; - } - } - } + if (!string) { + return NULL; } -} - -/** - * Fills the given list store by matching data of also given src list. - */ -gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src) -{ - gboolean found = false; - GtkTreeIter iter; - - if (!input || !*input) { - for (GList *l = src; l; l = l->next) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); - found = true; - } - } else { - for (GList *l = src; l; l = l->next) { - char *value = (char*)l->data; - if (g_str_has_prefix(value, input)) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1); - found = true; - } - } - } + char **buf = g_strsplit(string, search, -1); + char *ret = g_strjoinv(replace, buf); + g_strfreev(buf); - return found; + return ret; } -gboolean util_filename_fill_completion(GtkListStore *store, const char *input) +static void create_dir_if_not_exists(const char *dirpath) { - gboolean found = false; - - const char *last_slash = strrchr(input, '/'); - const char *input_basename = last_slash ? last_slash + 1 : input; - char *input_dirname = g_strndup(input, input_basename - input); - char *real_dirname = util_expand( - *input_dirname ? input_dirname : ".", - UTIL_EXP_TILDE|UTIL_EXP_DOLLAR|UTIL_EXP_SPECIAL - ); - - GError *error = NULL; - GDir *dir = g_dir_open(real_dirname, 0, &error); - if (error) { - /* Can't open directory, likely bad user input */ - g_error_free(error); - } else { - const char *filename; - GtkTreeIter iter; - while ((filename = g_dir_read_name(dir))) { - if (g_str_has_prefix(filename, input_basename)) { - char *fullpath = g_build_filename(real_dirname, filename, NULL); - char *result; - if (g_file_test(fullpath, G_FILE_TEST_IS_DIR)) { - result = g_strconcat(input_dirname, filename, "/", NULL); - } else { - result = g_strconcat(input_dirname, filename, NULL); - } - g_free(fullpath); - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, result, -1); - g_free(result); - found = true; - } - } - g_dir_close(dir); + if (!g_file_test(dirpath, G_FILE_TEST_IS_DIR)) { + g_mkdir_with_parents(dirpath, 0755); } - - g_free(input_dirname); - g_free(real_dirname); - - return found; } diff --git a/src/util.h b/src/util.h index 5227e092..7e802e46 100644 --- a/src/util.h +++ b/src/util.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ #ifndef _UTIL_H #define _UTIL_H +#include #include "main.h" enum { @@ -27,31 +28,32 @@ enum { UTIL_EXP_DOLLAR = 0x02, /* $ENV and ${ENV} expansion */ UTIL_EXP_SPECIAL = 0x04, /* expand % to current URI */ }; - typedef void *(*Util_Content_Func)(const char*, const char*); -char* util_get_config_dir(const char* profilename); -char* util_get_cache_dir(const char* profilename); -char* util_get_runtime_dir(const char* profilename); -const char* util_get_home_dir(void); -void util_create_dir_if_not_exists(const char* dirpath); -void util_create_file_if_not_exists(const char* filename); -char* util_get_file_contents(const char* filename, gsize* length); -char** util_get_lines(const char* filename); -GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, - guint max_items); +char *util_build_path(Client *c, const char *path, const char *dir); +void util_cleanup(void); +gboolean util_create_dir_if_not_exists(const char *dirpath); +gboolean util_create_tmp_file(const char *content, char **file); +char *util_expand(Client *c, const char *src, int expflags); gboolean util_file_append(const char *file, const char *format, ...); gboolean util_file_prepend(const char *file, const char *format, ...); +void util_file_prepend_line(const char *file, const char *line, + unsigned int max_lines); char *util_file_pop_line(const char *file, int *item_count); -char* util_strcasestr(const char* haystack, const char* needle); -char *util_str_replace(const char* search, const char* replace, const char* string); -gboolean util_create_tmp_file(const char *content, char **file); -char *util_build_path(const char *path, const char *dir); -char *util_expand(const char *src, int expflags); -gboolean util_parse_expansion(const char **input, GString *str, int flags, - const char *quoteable); -gboolean util_wildmatch(const char *pattern, const char *subject); +char *util_get_config_dir(void); +char *util_get_file_contents(const char *filename, gsize *length); +char *util_get_filepath(const char *dir, const char *filename, gboolean create); +char **util_get_lines(const char *filename); +GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, + guint max_items); gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src); -gboolean util_filename_fill_completion(GtkListStore *store, const char *input); +gboolean util_filename_fill_completion(Client *c, GtkListStore *store, const char *input); +char *util_js_result_as_string(WebKitJavascriptResult *result); +double util_js_result_as_number(WebKitJavascriptResult *result); +gboolean util_parse_expansion(Client *c, const char **input, GString *str, + int flags, const char *quoteable); +char *util_sanitize_filename(char *filename); +char *util_strcasestr(const char *haystack, const char *needle); +char *util_str_replace(const char* search, const char* replace, const char* string); #endif /* end of include guard: _UTIL_H */ diff --git a/src/webextension/Makefile b/src/webextension/Makefile new file mode 100644 index 00000000..215629cb --- /dev/null +++ b/src/webextension/Makefile @@ -0,0 +1,19 @@ +BASEDIR=../.. +include $(BASEDIR)/config.mk + +OBJ = $(patsubst %.c, %.lo, $(wildcard *.c)) + +all: $(EXTTARGET) + +clean: + $(RM) $(EXTTARGET) *.lo + +$(EXTTARGET): $(OBJ) + @echo "$(CC) $@" + $(Q)$(CC) $(OBJ) $(EXTLDFLAGS) -o $@ + +%.lo: %.c + @echo "${CC} $@" + $(Q)$(CC) $(EXTCFLAGS) -fPIC -c -o $@ $< + +.PHONY: all clean diff --git a/src/webextension/ext-dom.c b/src/webextension/ext-dom.c new file mode 100644 index 00000000..6f9030f0 --- /dev/null +++ b/src/webextension/ext-dom.c @@ -0,0 +1,179 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include + +#include "ext-main.h" +#include "ext-dom.h" + +static gboolean is_element_visible(WebKitDOMHTMLElement *element); + + +/** + * Checks if given dom element is an editable element. + */ +gboolean ext_dom_is_editable(WebKitDOMElement *element) +{ + char *type; + gboolean result = FALSE; + + if (!element) { + return FALSE; + } + + /* element is editable if it's a text area or input with no type, text or + * password */ + if (webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element)) + || WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(element)) { + return TRUE; + } + + if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) { + type = webkit_dom_html_input_element_get_input_type(WEBKIT_DOM_HTML_INPUT_ELEMENT(element)); + /* Input element without type attribute are rendered and behave like + * type = text and there are a lot of pages in the wild using input + * field without type attribute. */ + if (!*type + || !g_ascii_strcasecmp(type, "text") + || !g_ascii_strcasecmp(type, "password") + || !g_ascii_strcasecmp(type, "color") + || !g_ascii_strcasecmp(type, "date") + || !g_ascii_strcasecmp(type, "datetime") + || !g_ascii_strcasecmp(type, "datetime-local") + || !g_ascii_strcasecmp(type, "email") + || !g_ascii_strcasecmp(type, "month") + || !g_ascii_strcasecmp(type, "number") + || !g_ascii_strcasecmp(type, "search") + || !g_ascii_strcasecmp(type, "tel") + || !g_ascii_strcasecmp(type, "time") + || !g_ascii_strcasecmp(type, "url") + || !g_ascii_strcasecmp(type, "week")) + { + result = TRUE; + } + + g_free(type); + } + + return result; +} + +/** + * Find the first editable element and set the focus on it and enter input + * mode. + * Returns true if there was an editable element focused. + */ +gboolean ext_dom_focus_input(WebKitDOMDocument *doc) +{ + WebKitDOMNode *html, *node; + WebKitDOMHTMLCollection *collection; + WebKitDOMXPathNSResolver *resolver; + WebKitDOMXPathResult* result; + WebKitDOMDocument *frame_doc; + guint i, len; + + collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "html"); + if (!collection) { + return FALSE; + } + + html = webkit_dom_html_collection_item(collection, 0); + g_object_unref(collection); + + resolver = webkit_dom_document_create_ns_resolver(doc, html); + if (!resolver) { + return FALSE; + } + + /* Use translate to match xpath expression case insensitive so that also + * intput filed of type="TEXT" are matched. */ + result = webkit_dom_document_evaluate( + doc, "//input[not(@type) " + "or translate(@type,'ETX','etx')='text' " + "or translate(@type,'ADOPRSW','adoprsw')='password' " + "or translate(@type,'CLOR','clor')='color' " + "or translate(@type,'ADET','adet')='date' " + "or translate(@type,'ADEIMT','adeimt')='datetime' " + "or translate(@type,'ACDEILMOT','acdeilmot')='datetime-local' " + "or translate(@type,'AEILM','aeilm')='email' " + "or translate(@type,'HMNOT','hmnot')='month' " + "or translate(@type,'BEMNRU','bemnru')='number' " + "or translate(@type,'ACEHRS','acehrs')='search' " + "or translate(@type,'ELT','elt')='tel' " + "or translate(@type,'EIMT','eimt')='time' " + "or translate(@type,'LRU','lru')='url' " + "or translate(@type,'EKW','ekw')='week' " + "]|//textarea", + html, resolver, 5, NULL, NULL + ); + if (!result) { + return FALSE; + } + while ((node = webkit_dom_xpath_result_iterate_next(result, NULL))) { + if (is_element_visible(WEBKIT_DOM_HTML_ELEMENT(node))) { + webkit_dom_element_focus(WEBKIT_DOM_ELEMENT(node)); + return TRUE; + } + } + + /* Look for editable elements in frames too. */ + collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "iframe"); + len = webkit_dom_html_collection_get_length(collection); + + for (i = 0; i < len; i++) { + node = webkit_dom_html_collection_item(collection, i); + frame_doc = webkit_dom_html_iframe_element_get_content_document(WEBKIT_DOM_HTML_IFRAME_ELEMENT(node)); + /* Stop on first frame with focused element. */ + if (ext_dom_focus_input(frame_doc)) { + g_object_unref(collection); + return TRUE; + } + } + g_object_unref(collection); + + return FALSE; +} + +/** + * Retrieves the content of given editable element. + * Not that the returned value must be freed. + */ +char *ext_dom_editable_get_value(WebKitDOMElement *element) +{ + char *value = NULL; + + if ((webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element)))) { + value = webkit_dom_html_element_get_inner_text(WEBKIT_DOM_HTML_ELEMENT(element)); + } else if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(WEBKIT_DOM_HTML_INPUT_ELEMENT(element))) { + value = webkit_dom_html_input_element_get_value(WEBKIT_DOM_HTML_INPUT_ELEMENT(element)); + } else { + value = webkit_dom_html_text_area_element_get_value(WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT(element)); + } + + return value; +} + +/** + * Indicates if the give nelement is visible. + */ +static gboolean is_element_visible(WebKitDOMHTMLElement *element) +{ + return TRUE; +} diff --git a/src/io.h b/src/webextension/ext-dom.h similarity index 68% rename from src/io.h rename to src/webextension/ext-dom.h index 0fa0f876..0dbc0b96 100644 --- a/src/io.h +++ b/src/webextension/ext-dom.h @@ -1,7 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,16 +17,14 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#ifdef FEATURE_SOCKET - -#ifndef _IO_H -#define _IO_H +#ifndef _EXT_DOM_H +#define _EXT_DOM_H #include +#include -gboolean io_init_socket(const char *name); -void io_cleanup(void); +gboolean ext_dom_is_editable(WebKitDOMElement *element); +gboolean ext_dom_focus_input(WebKitDOMDocument *doc); +char *ext_dom_editable_get_value(WebKitDOMElement *element); -#endif /* end of include guard: _IO_H */ -#endif +#endif /* end of include guard: _EXT-DOM_H */ diff --git a/src/webextension/ext-main.c b/src/webextension/ext-main.c new file mode 100644 index 00000000..d70db003 --- /dev/null +++ b/src/webextension/ext-main.c @@ -0,0 +1,542 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include +#include +#include +#include + +#include "ext-main.h" +#include "ext-dom.h" +#include "ext-util.h" + +static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, + GIOStream *stream, GCredentials *credentials, gpointer extension); +static void on_dbus_connection_created(GObject *source_object, + GAsyncResult *result, gpointer data); +static void add_onload_event_observers(WebKitDOMDocument *doc, + WebKitWebPage *page); +static void on_document_scroll(WebKitDOMEventTarget *target, WebKitDOMEvent *event, + WebKitWebPage *page); +static void emit_page_created(GDBusConnection *connection, guint64 pageid); +static void emit_page_created_pending(GDBusConnection *connection); +static void queue_page_created_signal(guint64 pageid); +static void dbus_emit_signal(const char *name, GVariant *data); +static WebKitWebPage *get_web_page_or_return_dbus_error(GDBusMethodInvocation *invocation, + WebKitWebExtension *extension, guint64 pageid); +static void dbus_handle_method_call(GDBusConnection *conn, const char *sender, + const char *object_path, const char *interface_name, const char *method, + GVariant *parameters, GDBusMethodInvocation *invocation, gpointer data); +static void on_editable_change_focus(WebKitDOMEventTarget *target, + WebKitDOMEvent *event, WebKitWebPage *page); +static void on_page_created(WebKitWebExtension *ext, WebKitWebPage *webpage, gpointer data); +static void on_web_page_document_loaded(WebKitWebPage *webpage, gpointer extension); +static gboolean on_web_page_send_request(WebKitWebPage *webpage, WebKitURIRequest *request, + WebKitURIResponse *response, gpointer extension); + +static const GDBusInterfaceVTable interface_vtable = { + dbus_handle_method_call, + NULL, + NULL +}; + +static const char introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +/* Global struct to hold internal used variables. */ +struct Ext { + guint regid; + GDBusConnection *connection; + GHashTable *headers; + GHashTable *documents; + GArray *page_created_signals; +}; +struct Ext ext = {0}; + + +/** + * Webextension entry point. + */ +G_MODULE_EXPORT +void webkit_web_extension_initialize_with_user_data(WebKitWebExtension *extension, GVariant *data) +{ + char *server_address; + GDBusAuthObserver *observer; + + g_variant_get(data, "(ms)", &server_address); + if (!server_address) { + g_warning("UI process did not start D-Bus server"); + return; + } + + g_signal_connect(extension, "page-created", G_CALLBACK(on_page_created), NULL); + + observer = g_dbus_auth_observer_new(); + g_signal_connect(observer, "authorize-authenticated-peer", + G_CALLBACK(on_authorize_authenticated_peer), extension); + + g_dbus_connection_new_for_address(server_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, observer, NULL, + (GAsyncReadyCallback)on_dbus_connection_created, extension); + g_object_unref(observer); +} + +static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, + GIOStream *stream, GCredentials *credentials, gpointer extension) +{ + static GCredentials *own_credentials = NULL; + GError *error = NULL; + + if (!own_credentials) { + own_credentials = g_credentials_new(); + } + + if (credentials && g_credentials_is_same_user(credentials, own_credentials, &error)) { + return TRUE; + } + + if (error) { + g_warning("Failed to authorize connection to ui: %s", error->message); + g_error_free(error); + } + + return FALSE; +} + +static void on_dbus_connection_created(GObject *source_object, + GAsyncResult *result, gpointer data) +{ + static GDBusNodeInfo *node_info = NULL; + GDBusConnection *connection; + GError *error = NULL; + + if (!node_info) { + node_info = g_dbus_node_info_new_for_xml(introspection_xml, NULL); + } + + connection = g_dbus_connection_new_for_address_finish(result, &error); + if (error) { + g_warning("Failed to connect to UI process: %s", error->message); + g_error_free(error); + return; + } + + /* register the webextension object */ + ext.regid = g_dbus_connection_register_object( + connection, + VB_WEBEXTENSION_OBJECT_PATH, + node_info->interfaces[0], + &interface_vtable, + WEBKIT_WEB_EXTENSION(data), + NULL, + &error); + + if (!ext.regid) { + g_warning("Failed to register web extension object: %s", error->message); + g_error_free(error); + g_object_unref(connection); + return; + } + + emit_page_created_pending(connection); + ext.connection = connection; +} + +/** + * Add observers to doc event for given document and all the contained iframes + * too. + */ +static void add_onload_event_observers(WebKitDOMDocument *doc, + WebKitWebPage *page) +{ + WebKitDOMEventTarget *target; + + /* Add the document to the table of known documents or if already exists + * return to not apply observers multiple times. */ + if (!g_hash_table_add(ext.documents, doc)) { + return; + } + + /* We have to use default view instead of the document itself in case this + * function is called with content document of an iframe. Else the event + * observing does not work. */ + target = WEBKIT_DOM_EVENT_TARGET(webkit_dom_document_get_default_view(doc)); + + webkit_dom_event_target_add_event_listener(target, "focus", + G_CALLBACK(on_editable_change_focus), TRUE, page); + webkit_dom_event_target_add_event_listener(target, "blur", + G_CALLBACK(on_editable_change_focus), TRUE, page); + /* Check for focused editable elements also if they where focused before + * the event observer where set up. */ + /* TODO this is not needed for strict-focus=on */ + on_editable_change_focus(target, NULL, page); + + /* Observe scroll events to get current position in the document. */ + webkit_dom_event_target_add_event_listener(target, "scroll", + G_CALLBACK(on_document_scroll), false, page); + /* Call the callback explicitly to make sure we have the right position + * shown in statusbar also in cases the user does not scroll. */ + on_document_scroll(target, NULL, page); +} + +/** + * Callback called when the document is scrolled. + */ +static void on_document_scroll(WebKitDOMEventTarget *target, WebKitDOMEvent *event, + WebKitWebPage *page) +{ + WebKitDOMDocument *doc; + + if (WEBKIT_DOM_IS_DOM_WINDOW(target)) { + g_object_get(target, "document", &doc, NULL); + } else { + /* target is a doc document */ + doc = WEBKIT_DOM_DOCUMENT(target); + } + + if (doc) { + WebKitDOMElement *b, *de; + glong max, scrollTop, scrollHeight, clientHeight; + guint percent = 0; + + de = webkit_dom_document_get_document_element(doc); + if (!de) { + return; + } + /* Get the clientHeight. */ + clientHeight = webkit_dom_element_get_client_height(WEBKIT_DOM_ELEMENT(de)); + + b = WEBKIT_DOM_ELEMENT(webkit_dom_document_get_body(doc)); + /* Get the scrollTop of the document or the body. */ + if (!(scrollTop = webkit_dom_element_get_scroll_top(de)) && b) { + scrollTop = webkit_dom_element_get_scroll_top(b); + } + /* Get the scrollHeight of the document or the body. */ + if (!(scrollHeight = webkit_dom_element_get_scroll_height(de)) && b) { + scrollHeight = webkit_dom_element_get_scroll_height(b); + } + + /* Get the maximum scrollable page size. This is the size of the whole + * document - height of the viewport. */ + max = scrollHeight - clientHeight ; + + if (scrollTop && max) { + percent = (guint)(0.5 + (scrollTop * 100 / max)); + } + dbus_emit_signal("VerticalScroll", g_variant_new("(ttq)", + webkit_web_page_get_id(page), max, percent)); + } +} + +/** + * Emit the page created signal that is used in the UI process to finish the + * dbus proxy connection. + */ +static void emit_page_created(GDBusConnection *connection, guint64 pageid) +{ + GError *error = NULL; + + /* propagate the signal over dbus */ + g_dbus_connection_emit_signal(G_DBUS_CONNECTION(connection), NULL, + VB_WEBEXTENSION_OBJECT_PATH, VB_WEBEXTENSION_INTERFACE, + "PageCreated", g_variant_new("(t)", pageid), &error); + + if (error) { + g_warning("Failed to emit signal PageCreated: %s", error->message); + g_error_free(error); + } +} + +/** + * Emit queued page created signals. + */ +static void emit_page_created_pending(GDBusConnection *connection) +{ + int i; + guint64 pageid; + + if (!ext.page_created_signals) { + return; + } + + for (i = 0; i < ext.page_created_signals->len; i++) { + pageid = g_array_index(ext.page_created_signals, guint64, i); + emit_page_created(connection, pageid); + } + + g_array_free(ext.page_created_signals, TRUE); + ext.page_created_signals = NULL; +} + +/** + * Write the page id of the created page to a queue to send them to the ui + * process when the dbus connection is established. + */ +static void queue_page_created_signal(guint64 pageid) +{ + if (!ext.page_created_signals) { + ext.page_created_signals = g_array_new(FALSE, FALSE, sizeof(guint64)); + } + ext.page_created_signals = g_array_append_val(ext.page_created_signals, pageid); +} + +/** + * Emits a signal over dbus. + * + * @name: Signal name to emit. + * @data: GVariant value used as value for the signal or NULL. + */ +static void dbus_emit_signal(const char *name, GVariant *data) +{ + GError *error = NULL; + + if (!ext.connection) { + return; + } + + /* propagate the signal over dbus */ + g_dbus_connection_emit_signal(ext.connection, NULL, + VB_WEBEXTENSION_OBJECT_PATH, VB_WEBEXTENSION_INTERFACE, name, + data, &error); + if (error) { + g_warning("Failed to emit signal '%s': %s", name, error->message); + g_error_free(error); + } +} + +static WebKitWebPage *get_web_page_or_return_dbus_error(GDBusMethodInvocation *invocation, + WebKitWebExtension *extension, guint64 pageid) +{ + WebKitWebPage *page = webkit_web_extension_get_page(extension, pageid); + if (!page) { + g_warning("invalid page id %lu", pageid); + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, "Invalid page ID: %"G_GUINT64_FORMAT, pageid); + } + + return page; +} + +/** + * Handle dbus method calls. + */ +static void dbus_handle_method_call(GDBusConnection *conn, const char *sender, + const char *object_path, const char *interface_name, const char *method, + GVariant *parameters, GDBusMethodInvocation *invocation, gpointer extension) +{ + char *value; + guint64 pageid; + WebKitWebPage *page; + + if (g_str_has_prefix(method, "EvalJs")) { + char *result = NULL; + gboolean success; + gboolean no_result; + JSValueRef ref = NULL; + JSGlobalContextRef jsContext; + + g_variant_get(parameters, "(ts)", &pageid, &value); + page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid); + if (!page) { + return; + } + + no_result = !g_strcmp0(method, "EvalJsNoResult"); + jsContext = webkit_frame_get_javascript_context_for_script_world( + webkit_web_page_get_main_frame(page), + webkit_script_world_get_default() + ); + + success = ext_util_js_eval(jsContext, value, &ref); + + if (no_result) { + g_dbus_method_invocation_return_value(invocation, NULL); + } else { + result = ext_util_js_ref_to_string(jsContext, ref); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(bs)", success, result)); + g_free(result); + } + } else if (!g_strcmp0(method, "FocusInput")) { + g_variant_get(parameters, "(t)", &pageid); + page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid); + if (!page) { + return; + } + ext_dom_focus_input(webkit_web_page_get_dom_document(page)); + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (!g_strcmp0(method, "SetHeaderSetting")) { + g_variant_get(parameters, "(s)", &value); + + if (ext.headers) { + soup_header_free_param_list(ext.headers); + ext.headers = NULL; + } + ext.headers = soup_header_parse_param_list(value); + g_dbus_method_invocation_return_value(invocation, NULL); + } +} + +/** + * Callback called if a editable element changes it focus state. + * Event target may be a WebKitDOMDocument (in case of iframe) or a + * WebKitDOMDOMWindow. + */ +static void on_editable_change_focus(WebKitDOMEventTarget *target, + WebKitDOMEvent *event, WebKitWebPage *page) +{ + WebKitDOMDocument *doc; + WebKitDOMDOMWindow *dom_window; + WebKitDOMElement *active; + GVariant *variant; + char *message; + + if (WEBKIT_DOM_IS_DOM_WINDOW(target)) { + g_object_get(target, "document", &doc, NULL); + } else { + /* target is a doc document */ + doc = WEBKIT_DOM_DOCUMENT(target); + } + + dom_window = webkit_dom_document_get_default_view(doc); + if (!dom_window) { + return; + } + + active = webkit_dom_document_get_active_element(doc); + /* Don't do anything if there is no active element */ + if (!active) { + return; + } + if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT(active)) { + WebKitDOMHTMLIFrameElement *iframe; + WebKitDOMDocument *subdoc; + + iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT(active); + subdoc = webkit_dom_html_iframe_element_get_content_document(iframe); + add_onload_event_observers(subdoc, page); + return; + } + + /* Check if the active element is an editable element. */ + variant = g_variant_new("(tb)", webkit_web_page_get_id(page), + ext_dom_is_editable(active)); + message = g_variant_print(variant, FALSE); + g_variant_unref(variant); + if (!webkit_dom_dom_window_webkit_message_handlers_post_message(dom_window, "focus", message)) { + g_warning("Error sending focus message"); + } + g_free(message); + g_object_unref(dom_window); +} + +/** + * Callback for web extensions page-created signal. + */ +static void on_page_created(WebKitWebExtension *extension, WebKitWebPage *webpage, gpointer data) +{ + guint64 pageid = webkit_web_page_get_id(webpage); + + if (ext.connection) { + emit_page_created(ext.connection, pageid); + } else { + queue_page_created_signal(pageid); + } + + g_object_connect(webpage, + "signal::send-request", G_CALLBACK(on_web_page_send_request), extension, + "signal::document-loaded", G_CALLBACK(on_web_page_document_loaded), extension, + NULL); +} + +/** + * Callback for web pages document-loaded signal. + */ +static void on_web_page_document_loaded(WebKitWebPage *webpage, gpointer extension) +{ + /* If there is a hashtable of known document - detroy this and create a + * new hashtable. */ + if (ext.documents) { + g_hash_table_unref(ext.documents); + } + ext.documents = g_hash_table_new(g_direct_hash, g_direct_equal); + + add_onload_event_observers(webkit_web_page_get_dom_document(webpage), webpage); +} + +/** + * Callback for web pages send-request signal. + */ +static gboolean on_web_page_send_request(WebKitWebPage *webpage, WebKitURIRequest *request, + WebKitURIResponse *response, gpointer extension) +{ + char *name, *value; + SoupMessageHeaders *headers; + GHashTableIter iter; + + if (!ext.headers) { + return FALSE; + } + + /* Change request headers according to the users preferences. */ + headers = webkit_uri_request_get_http_headers(request); + if (!headers) { + return FALSE; + } + + g_hash_table_iter_init(&iter, ext.headers); + while (g_hash_table_iter_next(&iter, (gpointer*)&name, (gpointer*)&value)) { + /* Null value is used to indicate that the header should be + * removed completely. */ + if (value == NULL) { + soup_message_headers_remove(headers, name); + } else { + soup_message_headers_replace(headers, name, value); + } + } + + return FALSE; +} diff --git a/src/arh.h b/src/webextension/ext-main.h similarity index 66% rename from src/arh.h rename to src/webextension/ext-main.h index a7f0b099..5f829e88 100644 --- a/src/arh.h +++ b/src/webextension/ext-main.h @@ -1,8 +1,7 @@ /** * vimb - a webkit based vim like browser. * - * Copyright (C) 2012-2016 Daniel Carl - * Copyright (C) 2014 Sébastien Marie + * Copyright (C) 2012-2017 Daniel Carl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,17 +17,11 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#ifdef FEATURE_ARH +#ifndef _EXT_MAIN_H +#define _EXT_MAIN_H -#ifndef _ARH_H -#define _ARH_H +#define VB_WEBEXTENSION_SERVICE_NAME "org.vimb.browser.WebExtension" +#define VB_WEBEXTENSION_OBJECT_PATH "/org/vimb/browser/WebExtension" +#define VB_WEBEXTENSION_INTERFACE "org.vimb.browser.WebExtension" -#include "main.h" - -GSList *arh_parse(const char *, const char **); -void arh_free(GSList *); -void arh_run(GSList *, const char *, SoupMessage *); - -#endif /* end of include guard: _ARH_H */ -#endif +#endif /* end of include guard: _EXT_MAIN_H */ diff --git a/src/webextension/ext-util.c b/src/webextension/ext-util.c new file mode 100644 index 00000000..c15b6318 --- /dev/null +++ b/src/webextension/ext-util.c @@ -0,0 +1,103 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include +#include +#include "../config.h" +#include "ext-util.h" + +/** + * Evaluates given string as script and return if this call succeed or not. + */ +gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result) +{ + JSStringRef js_str; + JSValueRef exc = NULL, res = NULL; + + js_str = JSStringCreateWithUTF8CString(script); + res = JSEvaluateScript(ctx, js_str, JSContextGetGlobalObject(ctx), NULL, 0, &exc); + JSStringRelease(js_str); + + if (exc) { + *result = exc; + return FALSE; + } + + *result = res; + return TRUE; +} + +/** + * Creates a temporary file with given content. + * + * Upon success, and if file is non-NULL, the actual file path used is + * returned in file. This string should be freed with g_free() when not + * needed any longer. + */ +gboolean ext_util_create_tmp_file(const char *content, char **file) +{ + int fp; + ssize_t bytes, len; + + fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL); + if (fp == -1) { + g_critical("Could not create temp file %s", *file); + g_free(*file); + return FALSE; + } + + len = strlen(content); + + /* write content into temporary file */ + bytes = write(fp, content, len); + if (bytes < len) { + close(fp); + unlink(*file); + g_critical("Could not write temp file %s", *file); + g_free(*file); + + return FALSE; + } + close(fp); + + return TRUE; +} + +/** + * Returns a new allocates string for given value reference. + * String must be freed if not used anymore. + */ +char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref) +{ + char *string; + size_t len; + JSStringRef str_ref; + + g_return_val_if_fail(ref != NULL, NULL); + + str_ref = JSValueToStringCopy(ctx, ref, NULL); + len = JSStringGetMaximumUTF8CStringSize(str_ref); + + string = g_new0(char, len); + JSStringGetUTF8CString(str_ref, string, len); + JSStringRelease(str_ref); + + return string; +} diff --git a/src/webextension/ext-util.h b/src/webextension/ext-util.h new file mode 100644 index 00000000..d65677c6 --- /dev/null +++ b/src/webextension/ext-util.h @@ -0,0 +1,30 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2017 Daniel Carl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#ifndef _EXT_UTIL_H +#define _EXT_UTIL_H + +#include +#include + +gboolean ext_util_create_tmp_file(const char *content, char **file); +gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result); +char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref); + +#endif /* end of include guard: _EXT_UTIL_H */ diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index 766f66ed..00000000 --- a/tests/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -BASEDIR=.. -SRCDIR=$(BASEDIR)/src -include $(BASEDIR)/config.mk - -CPPFLAGS += -I $(BASEDIR)/ -CFLAGS += -fPIC -pedantic - -TEST_PROGS = test-handlers \ - test-map \ - test-shortcut \ - test-util - -all: $(TEST_PROGS) - LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):." gtester --verbose $(TEST_PROGS) - -${TEST_PROGS}: $(SRCDIR)/$(LIBTARGET) - -clean: - $(RM) -f $(TEST_PROGS) diff --git a/tests/manual/146-hsts-iframe.html b/tests/manual/146-hsts-iframe.html deleted file mode 100644 index 7775b753..00000000 --- a/tests/manual/146-hsts-iframe.html +++ /dev/null @@ -1,14 +0,0 @@ - - -iFrame URI change by HSTS (#146) - - -

- The hsts domain used in iFrame with http must not lead to load the - iframe content as page. -

- - - diff --git a/tests/manual/201-editable-focus-in-iframes.html b/tests/manual/editable-focus-in-iframe.html similarity index 77% rename from tests/manual/201-editable-focus-in-iframes.html rename to tests/manual/editable-focus-in-iframe.html index d02c0fb5..46fffe0b 100644 --- a/tests/manual/201-editable-focus-in-iframes.html +++ b/tests/manual/editable-focus-in-iframe.html @@ -3,7 +3,7 @@ Track Focu/Blur also within iFrames - + diff --git a/tests/manual/112-editable-focus.html b/tests/manual/editable-focus.html similarity index 88% rename from tests/manual/112-editable-focus.html rename to tests/manual/editable-focus.html index 3accc379..5c0d123c 100644 --- a/tests/manual/112-editable-focus.html +++ b/tests/manual/editable-focus.html @@ -1,6 +1,6 @@ -Input mode Switching (#112 #237) +Input mode Switching