diff options
author | Daniel Carl <danielcarl@gmx.de> | 2015-09-19 20:47:37 +0200 |
---|---|---|
committer | Daniel Carl <danielcarl@gmx.de> | 2016-03-30 23:32:23 +0200 |
commit | 6608f8fc19ef4b587596c9ed3cb3b3fcc37c1eb6 (patch) | |
tree | 12f055b9e671cb41ea8097add3e3833890a65fe8 | |
parent | e3ea1d3081cc7dbe86f95ee0888660c292c355eb (diff) |
Startup webkit2 branch from the scratch.
74 files changed, 3752 insertions, 10849 deletions
@@ -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 77da6f6..9fb2ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changes in vimb +## [unreleased] + +### Removed + +* `FEATURE_COOKIE` precompiler flag was removed bcause compiling without cookie + support does not bring any real benefit + ## [2.11] - 2015-12-17 ### Added @@ -37,4 +44,5 @@ cookie file * Fixed none POSIX `echo -n` call +[unreleased]: https://github.com/fanglingsu/vimb/compare/2.11...HEAD [2.11]: https://github.com/fanglingsu/vimb/compare/2.10...2.11 @@ -1,49 +1,39 @@ include config.mk -SRCDIR=src -DOCDIR=doc +all: vimb -all: $(TARGET) options: - @echo "$(PROJECT) build options:" + @echo "vimb build options:" @echo "LIBS = $(LIBS)" @echo "CFLAGS = $(CFLAGS)" @echo "LDFLAGS = $(LDFLAGS)" @echo "CC = $(CC)" -test: $(LIBTARGET) - @$(MAKE) $(MFLAGS) -s -C tests +install: vimb + @# binary + install -d $(BINPREFIX) + install -m 755 $(SRCDIR)/vimb $(BINPREFIX)/vimb + @# extension + install -d $(EXTPREFIX) + install -m 644 $(SRCDIR)/webextension/$(EXTTARGET) $(EXTPREFIX)/$(EXTTARGET) -clean: - @$(MAKE) $(MFLAGS) -C src clean - @$(MAKE) $(MFLAGS) -C tests clean +uninstall: + $(RM) $(BINPREFIX)/vimb $(EXTPREFIX)/$(EXTTARGET) -install: $(TARGET) $(DOCDIR)/$(MAN1) - install -d $(DESTDIR)$(BINDIR) - install -m 755 $(SRCDIR)/$(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) - install -d $(DESTDIR)$(EXAMPLEDIR) - cp -r examples/* $(DESTDIR)$(EXAMPLEDIR) - install -d $(DESTDIR)$(MANDIR)/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) +vimb: $(SUBDIRS:%=%.subdir-all) -uninstall: - $(RM) $(DESTDIR)$(BINDIR)/$(TARGET) - $(RM) $(DESTDIR)$(MANDIR)/man1/$(MAN1) - $(RM) -r $(DESTDIR)$(EXAMPLEDIR) +%.subdir-all: + $(MAKE) $(MFLAGS) -C $* -dist: dist-clean - @echo "Creating tarball." - @git archive --format tar -o $(DIST_FILE) HEAD +%.subdir-clean: + $(MAKE) $(MFLAGS) -C $* clean -dist-clean: - $(RM) $(DIST_FILE) +clean: $(SUBDIRS:%=%.subdir-clean) -$(TARGET): - @$(MAKE) $(MFLAGS) -C src $(TARGET) +runsandbox: sandbox + sandbox/usr/bin/vimb -$(LIBTARGET): - @$(MAKE) $(MFLAGS) -C src $(LIBTARGET) +sandbox: + make RUNPREFIX=$(CURDIR)/sandbox/usr PREFIX=/usr DESTDIR=./sandbox install -.PHONY: clean all install uninstall options dist dist-clean test +.PHONY: all options clean install uninstall sandbox @@ -1,89 +1,111 @@ -# Vimb - the Vim-like browser - -Vimb is a Vim-like web browser that is inspired by Pentadactyl and Vimprobable. -The goal of Vimb is to build a completely keyboard-driven, efficient and -pleasurable browsing-experience with low memory and CPU usage that is -intuitive to use for Vim users. - -More information and some screenshots of Vimb browser in action can be found on -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] -- [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 - 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 -- multiple yank/paste [registers][] -- Vim like [autocmd][] - -## Packages - -- Arch Linux [vimb-git][arch-git], [vimb][arch] -- [NetBSD][] -- [FreeBSD][] -- [Void Linux][] - -## 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`. - -## Install - -Edit `config.mk` to match your local setup. - -Edit src/config.h to match your personal preferences. -Edit `src/config.h` to match your personal preferences. - -The default `Makefile` will not overwrite your customised `config.h` with the -contents of `config.def.h`, even if it was updated in the latest git pull. -Therefore, you should always compare your customised `config.h` with -`config.def.h` and make sure you include any changes to the latter in your -`config.h`. - -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 install - -To build Vimb against GTK3 you can use `make GTK=3`. - -# License - -Information about the license is found in the file: LICENSE. - -# 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]: http://fanglingsu.github.io/vimb/keybindings.html "vimb keybindings" -[hints]: http://fanglingsu.github.io/vimb/keybindings.html#hinting "vimb hinting" -[queue]: http://fanglingsu.github.io/vimb/commands.html#queue "vimb read it later queue feature" -[history]: http://fanglingsu.github.io/vimb/keybindings.html#history "vimb keybindings to access history" -[handlers]: http://fanglingsu.github.io/vimb/commands.html#handlers "vimb custom protocol handlers" -[registers]: http://fanglingsu.github.io/vimb/keybindings.html#registers "vimb yank/paste registers" -[mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list" -[NetBSD]: http://pkgsrc.se/wip/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" +# vimb - the vim like browser + +This is the development branch for the new webkit2 port of vimb. This branch +does not work and lags a lot of features of the webkit1 version of vimb. So +this is only meant to be the playground for the developers at the moment. + +If you like to have a working vimb, please use the master branch. + +## Patching and Coding style + +- the code is indented by 4 spaces - if you use vim to code you can set + `:set expandtab ts=4 sts=4 sw=4` +- the functions are sorted alphabetically within the c files +- 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 to various purposes + └── webextension Source files for the webextension + +## dependencies + +- webkit2gtk-4.0 >= 2.3.5 + +## 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 sotred in. + +To run vimb wihtout 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. + +## Tasks + +1. general infrastructure and built + - [x] write make file + - [x] allow to built as sandbox for local testing + - [x] add a way to specify the target of the webextension shared objects + this is now available with the `RUNPREFIX` oprion for the make + - [x] establish communication channel between the vimb instance and the now + required webextension (dbus) +2. migrate as many of the features of the webkit1 vimb + - [ ] starting with custom config file `--config,-c` option + - [ ] running multiple ex-commands during startup `--cmd,-C` options + - [ ] starting with a named profile `--profile,-p` option + - [ ] xembed `--embed,-e` option + - [ ] socket support `--socket,-s` and `--dump,-d` option to print the actual + used socket path to stdout + - [ ] kiosk-mode `--kiosk,-k` + - [ ] allow to start vimb reading html from `stdin` by `vimb -` + - [ ] browser modes normal, input, command, pass-through and hintmode + - [ ] download support + - [ ] editor command + - [ ] external downloader + - [ ] hinting + - [ ] searching and matching of search results + - [ ] navigation j, k, h, l, ... + - [ ] history and history lookup + - [ ] completion + - [ ] HSTS + - [ ] auto-response-header + - [ ] cookies support + - [ ] register support and `:register` command + - [ ] read it later queue + - [ ] show scroll indicator in statusbar as top, x%, bttom or all + how can we get this information from webview easily? + - [ ] find a way to disable the scrollbars on the main frame + - [ ] page marks - maybe we change make them global (shared between + instances and to work also if the page was reloaded or changed like + the marks in vim) + - [x] zooming + - [ ] yanking + - [x] keymapping + - [ ] URL handler + - [x] shortcuts + - [ ] autocommands and augroups +3. documentation +4. testing + - [ ] write automatic test to the essential main features + - [ ] adapt the manual test cases and add some more to avoid regressions + before a release + - [ ] write new test cases for essential things like mode switching of vimb + when clicking form fields or tabbing over them. +5. new features and changed behaviour + - [ ] try to use the webkit2 feature of running multiple pages with related + view instance `webkit_web_view_new_with_related_view` + - [ ] allow setting of different scopes, global and instance (new feature) + - [ ] remove the settings related to the gui like `status-color-bg` this was + only a hack and is not recommended for new gtk3 applications. the + color and font settings should be setup by css instead. + - [ ] webkit2 does not provide the view mode `source` so maybe this is going + to be removed together with the `gf` keybinding or we find a simple + workaround for this + +# license + +Information about the license are found in the file LICENSE. @@ -1,63 +1,37 @@ -#----------------user/install options---------------- -VERSION = 2.11 - -PROJECT = vimb -PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin -MANDIR ?= $(PREFIX)/share/man -EXAMPLEDIR ?= $(PREFIX)/share/$(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) -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}\" +VERSION = dev-3.0 + +PREFIX ?= /usr/local +BINPREFIX := $(DESTDIR)$(PREFIX)/bin +MANPREFIX := $(DESTDIR)$(PREFIX)/share/man +EXAMPLEPREFIX := $(DESTDIR)$(PREFIX)/share/vimb/example +RUNPREFIX := $(PREFIX) +EXTPREFIX := $(RUNPREFIX)/lib/vimb + +# define some directories +SRCDIR = src +DOCDIR = doc +SUBDIRS = $(SRCDIR)/scripts $(SRCDIR)/webextension $(SRCDIR) + +# used libs +LIBS = gtk+-3.0 webkit2gtk-4.0 + +# setup general used CFLAGS +CFLAGS += -std=c99 -pipe -Wall +#CPPFLAGS += -pedantic +CPPFLAGS += -DVERSION=\"${VERSION}\" -DEXTPREFIX=\"${EXTPREFIX}\" +CPPFLAGS += -DPROJECT=\"vimb\" -DPROJECT_UCFIRST=\"Vimb\" CPPFLAGS += -D_XOPEN_SOURCE=500 -CPPFLAGS += -D_POSIX_SOURCE -ifeq ($(USEGTK3), 1) -CPPFLAGS += -DHAS_GTK3 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 deleted file mode 100644 index 7c0e2ea..0000000 --- a/doc/vimb.1 +++ /dev/null @@ -1,1617 +0,0 @@ -.\" vim: ft=groff -.ad l -.TH VIMB 1 "DATE" "vimb/VERSION" "Vimb Manual" -.de EX -.nf -.ft CW -.. -.de EE -.ft R -.fi -.. -.SH NAME -Vimb - Vim Browser - A modal web browser based on WebKit, inspired by Vim: the -great editor. -.SH SYNOPSIS -.B vimb -.OP OPTIONS -.RI [ URI "|" file "|" - ] -.SH DESCRIPTION -Vimb is a WebKit based web browser that behaves like the Vimperator -plugin for Firefox and has usage paradigms from the great editor, Vim. -The goal of Vimb is to build a completely keyboard-driven, efficient -and pleasurable browsing-experience. -.SH OPTIONS -If no \fIURI\fP or \fIfile\fP is given, Vimb will open the configured -home-page. -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. -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<CR>" | 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. -.TP -.B "\-v, \-\-version" -Print build and version information. -.SH MODES -Vimb is modal and has the following main modes: -.TP -.B Normal Mode -The default mode. -Pressing Escape always enter normal mode. -.TP -.B Input Mode -Used for editing text elements in a webpage. -.TP -.B Command Mode -Execute `ex` commands from the builtin inputbox (commandline). -.TP -.B Pass-Through Mode -In Pass-Through mode only the `<Esc>` and `<C-[>` keybindings are interpreted -by Vimb, all other keystrokes are given to the webview to handle them. -This allows the use of a website's configured keybindings, that might otherwise -be swallowed by Vimb. -.SH NORMAL MODE COMMANDS -Some of the Normal Model Commands can have a numeric count to multiply the -effect of the command. -If a command supports the count this is shown as \fB[N]\fP. -.SS General -.TP -.B : -Start Command Mode and print `:' to the input box. -.TP -.B gi -Set cursor to the first editable element in the page and switch to Input -Mode. -.TP -.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 -.B CTRL\-V -Pass the next key press directly to GTK. -.TP -.B CTRL\-Q -Quit the browser if there are no running downloads. -.SS Navigation -.TP -.B o -Start Command Mode and print `:open ' to the input box. -.TP -.B O -Start Command Mode and print `:open URI' to the input box. -.TP -.B t -Start Command Mode and print `:tabopen ' to the input box. -.TP -.B T -Start Command Mode and print `:tabopen URI' to the input box. -.TP -.B gh -Open the configured home-page. -.TP -.B gH -Open the configured home-page in a new window. -.TP -.B u -Open the last closed page. -.TP -.B U -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). -.TP -.BI [ \(dqx ]p -Open the URI out of the register \fIx\fP or, if not given, from the clipboard. -.TP -.BI [ \(dqx ]P -Open the URI out of the register \fIx\fP or, if not given, from the clipboard in a -new window. -.TP -.BI [ N ]CTRL\-O -Go back \fIN\fP steps in the browser history. -.TP -.BI [ N ]CTRL\-I -Go forward \fIN\fP steps in the browser history. -.TP -.BI [ N ]gu -Go to the \fIN\fPth descendent directory of the current opened URI. -.TP -.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 -.B R -Reload the website without using caches. -.TP -.B CTRL\-C -Stop loading the current page. -.SS Motion -.TP -.BI [ N ]CTRL\-F -Scroll \fIN\fP pages down. -.TP -.BI [ N ]CTRL\-B -Scroll \fIN\fP pages up. -.TP -.BI [ N ]CTRL\-D -Scroll \fIN\fP half pages down. -.TP -.BI [ N ]CTRL\-U -Scroll \fIN\fP half pages up. -.TP -.BI [ N ]gg -Scroll to the top of the current page. -Or if \fIN\fP is given to \fIN\fP% of the page. -.TP -.BI [ N ]G -Scroll to the bottom of the current page. -Or if \fIN\fP is given to \fIN\fP% of the page. -.TP -.B 0, ^ -Scroll to the absolute left of the document. -Unlike in Vim, 0 and ^ work exactly the same way. -.TP -.B $ -Scroll to the absolute right of the document. -.TP -.BI [ N ]h -Scroll \fIN\fP steps to the left of page. -.TP -.BI [ N ]l -Scroll \fIN\fP steps to the right of page. -.TP -.BI [ N ]j -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'. -Hints can be selected by using <Tab>, <C-I> or <C-Tab>, <C-O>, -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) -or any combination of these methods. -If <enter> is pressed, the current active hint will be fired. -If only one possible hint remains, this will be fired automatically. -.PP -.BI Syntax: " ;{mode}{hint}" -.PP -Start Hints mode. -Different elements depending on \fImode\fP are highlighted and `numbered'. -Elements can be selected either by typing their label, or by typing part -of their text (\fIhint\fP) to narrow down the result. -When an element has been selected, it is automatically clicked -or used (depending on \fImode\fP) and hint mode ends. -.PP -The filtering of hints by text splits the query at ' ' and use the single parts -as separate queries to filter the hints. -This is useful for hints that have a lot of filterable chars in common -and many chars are required to make a distinct selection. -For example ';over tw' will easily select the second hint out of -{'very long link text one', 'very long link text two'}. -.PP -The following keys have special meanings in Hints modes: -.PD 0 -.IP \fB<CR>\fP -Selects the first highlighted element, or the current focused. -.IP "\fB<Tab>\fP" -Moves the focus to the next hint element. -.IP "\fB<S-Tab>\fP" -Moves the focus to the previous hint element. -.IP "\fB<Esc>, CTRL\-C, CTRL\-[\fP" -Exits Hints mode without selecting an element. -.PD -.TP -.B Hint modes: -.RS -.PD 0 -.TP -.B f -Is an alias for the \fB;o\fP hint mode. -.TP -.B F -Is an alias for the \fB;t\fP hint mode. -.TP -.B ;o -Open hint's location in the current window. -.TP -.B ;t -Open hint's location in a new window. -.TP -.B ;s -Saves the hint's destination under the configured `download-path'. -.TP -.B ;O -Generate an `:open' prompt with hint's URI. -.TP -.B ;T -Generate an `:tabopen' prompt with hint's URI. -.TP -.B ;e -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 -content will be put back in the form field. -.TP -.B ;i -Open hinted image in the current window. -.TP -.B ;I -Open hinted image in a new window. -.TP -.B ;p -Push the hint's URI to the end of the Read It Later queue like the `:qpush' -command. -This is only available if Vimb was compiled with the QUEUE feature. -.TP -.B ;P -Push the hint's URI to the beginning of the Read It Later queue like the -`:qunshift' command. -This is only available if Vimb was compiled with the QUEUE feature. -.TP -.B ;x -Hints like ;o, but instead of opening the hinted URI, the -`x-hint-command' is run in Vimb. -.TP -.BI [ \(dqx ];y -Yank hint's destination location into primary and secondary clipboard and into -the register \fIx\fP. -.TP -.BI [ \(dqx ];Y -Yank hint's text description or form text into primary and secondary clipboard -and into the register \fIx\fP. -.PD -.RE -.TP -.BI Syntax: " g;{mode}{hint}" -Start an extended hints mode and stay there until <Esc> is pressed. -Like normal hinting, except that after a hint is selected, hints -remain visible so that another one can be selected with the same action -as the first. -Note that the extended hint mode can only be combined with the following -hint modes \fII p P s t y Y\fP. -.SS Searching -.TP -.BI / QUERY ", ?" QUERY -Start searching for \fIQUERY\fP in the current page. -\fI/\fP start search forward, \fI?\fP in backward direction. -.TP -.B *, # -Start searching for the current selected text, or if no text is selected for -the content of the primary or secondary clipboard. -\fI*\fP start the search in forward direction and \fI#\fP in backward -direction. -.sp -Note that these commands will yank the text selection into the clipboard and -may remove other content from there! -.TP -.BI [ N ]n -Search for \fIN\fPnth next search result depending on current search -direction. -.TP -.BI [ N ]N -Search for \fIN\fPnth previous search result depending on current search -direction. -.SS Zooming -.TP -.BI [ N ]zi -Zoom-In the text of the page by \fIN\fP steps. -.TP -.BI [ N ]zo -Zoom-Out the text of the page by \fIN\fP steps. -.TP -.BI [ N ]zI -Full-Content Zoom-In the page by \fIN\fP steps. -.TP -.BI [ N ]zO -Full-Content Zoom-Out the page by \fIN\fP steps. -.TP -.B zz -Reset Zoom. -.SS Yank -.TP -.BI [ \(dqx ]y -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). -The commands may vary in their syntax or in the parts they allow, -but in general they follow a simple syntax. -.PP -.BI Syntax: " :[:| ][N]cmd[name][!][ lhs][ rhs]" -.sp -Where \fIlhs\fP (left hand side) must not contain any unescaped space. -The syntax of the rhs (right hand side) if this is available depends on the -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. -To avoid this, the commands can be prefixed by one or more additional `:' or -whitespace. -.PP -Multiple commands, separated by a `|' can be given in a single command line -and will be executed consecutively. -The pipe can be included as an argument to a command by escaping it with a -backslash. -.br -Following commands process the entire command-line string literally. -These commands will include any `|' as part of their argument string and so -cannot be followed by another command. -.PP -.PD 0 -.IP - 2 -autocmd -.IP - -cmap, cnoremap, imap, inoremap, nmap, nnoremap -.IP - -eval -.IP - -normal -.IP - -open, tabopen -.IP - -shellcmd -.PD -.SS Command Line Editing -.TP -.B <Esc>, CTRL\-[, CTRL-C -Ignore all typed content and switch back to normal mode. -.TP -.B <CR> -Submit the entered `ex` command or search query to run it. -.TP -.B CTRL\-H -Deletes the char before the cursor. -.TP -.B CTRL\-W -Deletes the last word before the cursor. -.TP -.B CTRL\-U -Remove everything between cursor and prompt. -.TP -.B CTRL\-B -Moves the cursor directly behind the prompt `:'. -.TP -.B CTRL\-E -Moves the cursor after the char in inputbox. -.TP -.B CTRL\-V -Pass the next key press directly to GTK. -.TP -.B CTRL\-R {a-z"%:/;} -Insert the content of given register at cursor position. -See also section about `:reg[ister]' command. -.SS Command Line History -.TP -.B <Tab> -Start completion of the content in the inputbox in forward direction. -.TP -.B <S-Tab> -Start completion of the content in the inputbox in backward direction. -.TP -.B <Up> -Step backward in the command history. -.TP -.B <Down> -Step forward in the command history. -.SS Open -.TP -.BI ":o[pen] [" URI ] -Open the give \fIURI\fP in the current window. -If \fIURI\fP is empty, the configured 'home-page' is opened. -.TP -.BI ":t[abopen] [" URI ] -Open the give \fIURI\fP in a new window. -If \fIURI\fP is empty, the configured 'home-page' is opened. -.SS Key Mapping -Key mappings allow users to alter the actions of key presses. -Each key mapping is associated with a mode and only has effect -when the mode is active. -The following commands allow the user to substitute one sequence -of key presses by another. -.PP -.BI Syntax: " :{m}map {lhs} {rhs}" -.PP -Note that the \fIlhs\fP ends with the first found space. -If you want to use space also in the {lhs} you have to escape this -with a single `\\', as shown in the examples. -.sp -The \fIrhs\fP starts with the first non-space char. If you want a \fIrhs\fP -that starts with a space, you have to use "<Space>". -.PP -Standard key mapping commands are provided for these modes \fIm\fP: -.PD 0 -.IP \fBn\fP -Normal mode: when browsing normally. -.IP \fBi\fP -Insert mode: when interacting with text fields on a website. -.IP \fBc\fP -Command Line mode: when typing into Vimb's command line. -.PD -.PP -Most keys in key sequences are represented simply by the character that you -see on the screen when you type them. -However, as a number of these characters have special meanings, and a -number of keys have no visual representation, a special notation is required. -.PP -As special key names have the format \fI<...>\fP. -The following special keys can be used: <Left>, <Up>, <Right>, <Down> -for the cursor keys, <Tab>, <Esc>, <CR>, <Space>, <BS>, <F1>-<F12> and <C-A>-<C-Z>. -.TP -.BI ":nm[ap] {" lhs "} {" rhs } -.TP -.BI ":im[ap] {" lhs "} {" rhs } -.TP -.BI ":cm[ap] {" lhs "} {" rhs } -Map the key sequence \fIlhs\fP to \fIrhs\fP for the modes where the map -command applies. -The result, including \fIrhs\fP, is then further scanned for mappings. -This allows for nested and recursive use of mappings. -.RS -.P -Examples: -.PD 0 -.IP ":cmap <C-G>h /home/user/downloads/" -Adds a keybind to insert a file path into the input box. -This could be useful for the `:save' command -that could be used as ":save ^Gh". -.IP ":nmap <F1> :set scripts=on<CR>:open !glib<Tab><CR>" -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. -.PD -.RE -.TP -.BI ":nn[oremap] {" lhs "} {" rhs } -.TP -.BI ":ino[remap] {" lhs "} {" rhs } -.TP -.BI ":cno[remap] {" lhs "} {" rhs } -Map the key sequence \fIlhs\fP to \fIrhs\fP for the mode where the map command -applies. -Disallow mapping of \fIrhs\fP, to avoid nested and recursive mappings. -Often used to redefine a command. -.TP -.BI ":nu[nmap] {" lhs } -.TP -.BI ":iu[nmap] {" lhs } -.TP -.BI ":cu[nmap] {" lhs } -Remove the mapping of \fIlhs\fP for the applicable mode. -.SS Bookmarks -.TP -.BI ":bma [" tags ] -Save the current opened URI with \fItags\fP to the bookmark file. -.TP -.BI ":bmr [" URI ] -Removes all bookmarks for given \fIURI\fP or, if not given, the current opened -page. -.SS Handlers -Handlers allow specifying external scripts to handle alternative URI methods. -.TP -.BI ":handler-add " "handler" "=" "cmd" -Adds a handler to direct \fIhandler\fP links to the external \fIcmd\fP. -The \fIcmd\fP can contain one placeholder `%s` that will be filled by the -full URI given when the command is called. -.RS -.P -Examples: -.PD 0 -.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://<host>:<port>/<channel> links to a wrapper for your IRC client. -.PD -.RE -.TP -.BI ":handler-remove " "handler" -Remove the handler for the given URI \fIhandler\fP. -.SS Shortcuts -Shortcuts allow the opening of an URI built up from a named template with additional -parameters. -If a shortcut named 'dd' is defined, you can use it with `:open dd -list of parameters' to open the generated URI. -.PP -Shortcuts are convenient to use with search engines where the URI is standardised -and a single parameter is user defined. -.TP -.BI ":shortcut-add " "shortcut" "=" "URI" -Adds a shortcut with the \fIshortcut\fP and \fIURI\fP template. -The \fIURI\fP can contain multiple placeholders $0-$9 that will be -filled by the parameters given when the shortcut is called. -The parameters given when the shortcut is called will be split -into as many parameters like the highest used placeholder. -.sp -To use spaces within the parameters, the parameters can be grouped by -surrounding them with single-or double quotes-as shown in example shortcut -`map'. -.RS -.P -Examples: -.PD 0 -.IP ":shortcut-add dl=https://duckduckgo.com/lite/?q=$0" -to setup a search engine. -Can be called by `:open dl my search phrase'. -.IP ":shortcut-add gh=https://github.com/$0/$1" -to build URIs from given parameters. -Can be called `:open gh fanglingsu vimb'. -.IP ":shortcut-add map=https://maps.google.com/maps?saddr=$0&daddr=$1" -to search for a route, all but the last parameter must be quoted if they -contain spaces like `:open map "city hall, London" railway station, London' -.PD -.RE -.TP -.BI ":shortcut-remove " "shortcut" -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. -.SS Settings -.TP -.BI ":se[t] " var = value -Set configuration values named by \fIvar\fP. -To set boolean variable you should use 'on', 'off' or 'true' and 'false'. -Colors are given as hexadecimal value like '#f57700'. -.TP -.BI ":se[t] " var += value -Add the \fIvalue\fP to a number option, or append the \fIvalue\fP to a string -option. -When the option is a comma separated list, a comma is added, unless -the value was empty. -.TP -.BI ":se[t] " var ^= value -Multiply the \fIvalue\fP to a number option, or prepend the \fIvalue\fP to a -string option. -When the option is a comma separated list, a comma is added, -unless the value was empty. -.TP -.BI ":se[t] " var -= value -Subtract the \fIvalue\fP from a number option, or remove the \fIvalue\fP from -a string option, if it is there. -When the option is a comma separated list, a -comma is deleted, unless the option becomes empty. -.TP -.BI ":se[t] " var ? -Show the current set value of variable. -.IR VAR . -.TP -.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). -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. -.TP -.BI ":qun[shift] [" URI ] -Push \fIURI\fP or, if not given, the current URI to the beginning of the queue. -.TP -.B :qp[op] -Open the oldest queue entry in the current browser window and remove it from the -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 -.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' -and '${VAR}'. -A '\\' before these patterns disables the expansion. -.PP -.RS -.PP -.PD 0 -The following environment variables are set for called shell commands. -.TP -.B VIMB_URI -This variable is set by Vimb everytime a new page is opened to the URI of the -page. -.TP -.B VIMB_TITLE -Contains the title of the current opened page. -.TP -.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 -started with the -e option. -.PD -.PP -Example: -.EX -:sh ls -l $HOME -.EE -.RE -.TP -.BI ":sh[ellcmd]! " cmd -Like :sh[ellcmd], but asyncron. -.sp -Example: -.EX -:sh! /bin/sh -c 'echo "`date` $VIMB_URI" >> myhistory.txt' -.EE -.TP -.BI ":s[ave] [" path "]" -Download current opened page into configured download directory. -If \fIpath\fP is given, download under this file name or path. -\fIpath\fP is expanded and can therefore contain '~/', '${ENV}' -and '~user' pattern. -.TP -.BI ":so[urce] [" file "]" -Read ex commands from \fIfile\fP. -.TP -.B :q[uit] -Close the browser. -This will be refused if there are running downloads. -.TP -.B :q[uit]! -Close the browser independent from an running download. -.TP -.B :reg[ister] -Display the contents of all registers. -.RS -.PP -.PD 0 -Registers: -.TP -.BR \(dqa " - " \(dqz -26 named registers "a to "z. -Vimb fills these registers only when you say so. -.TP -.B \(dq: -Last executed `ex` command. -.TP -.B \(dq" -Last yanked content. -.TP -.B \(dq% -Curent opened URI. -.TP -.B \(dq/ -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 -.BI :e[val] " javascript" -Runs the given \fIjavascript\fP in the current page and display the evaluated -value. -.sp -Example: :eval document.cookie -.TP -.BI :e[val]! " javascript" -Like :eval, but there is nothing print to the input box. -.TP -.BI ":no[rmal] [" cmds ] -Execute normal mode commands \fIcmds\fP. -This makes it possible to execute normal mode commands typed on the input box. -.sp -\fIcmds\fP cannot start with a space. -Put a count of 1 (one) before it, "1 " is one space. -.sp -Example: :set scripts!|no! R -.TP -.BI ":no[rmal]! [" cmds ] -Like :normal, but no mapping is applied to \fIcmds\fP. -.TP -.B :ha[rdcopy] -Print current document. -Open a GUI dialog where you can select the printer, -number of copies, orientation, etc. -.SH INPUT MODE -.TP -.B <Esc>, CTRL\-[ -Switch back to normal mode. -.TP -.B CTRL\-O -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. -.TP -.B CTRL\-V -Pass the next key press directly to GTK. -.TP -.B CTRL\-Z -Enter the pass-through mode. -.SH COMPLETIONS -The completions are triggered by pressing `<Tab>` or `<S-Tab>` in the -activated inputbox. -Depending of the current inserted content different completions are started. -The completion takes additional typed chars to filter -the completion list that is shown. -.TP -.B commands -The completion for commands are started when at least `:` is shown in the -inputbox. -If initial chars are passed, the completion will lookup those -commands that begin with the given chars. -.TP -.B settings -The setting name completion is started if at least `:set ` is shown in -inputbox and does also match settings that begins with already typed setting -prefix. -.TP -.B history -The history of URIs is shown for the `:open ` and `:tabopen ` commands. -This completion looks up every given word in the history URI and titles. -Only those history items are shown, where the title or URI contains all tags. -.sp -Example: -.RS -.PD 0 -.IP ":open foo bar<Tab>" -will complete only URIs that contain the words foo and bar. -.PD -.RE -.TP -.B bookmarks -The bookmark completion is similar to the history completion, but does match -only the tags of the bookmarks. -The bookmark completion ist started by `:open \fB!\fP` -or `:tabopen \fB!\fP` and does a prefix search for all given words in -the bookmark tags. -.sp -Example: -.RS -.PD 0 -.IP ":open \fB!\fPfoo ba" -will match all bookmarks that have tags starting with "foo" and "ba". -If the bookmark does not have any tags set, the URL is split on `.' and `/' -into tags. -.PD -.RE -.TP -.B boomark tags -The boomark tag completion allows the insertion of already used bookmarks for the -`:bma ` commands. -.TP -.B search -The search completion allows a filtered list of already done searches. -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. -.TP -.B caret (bool) -Whether to enable accessibility enhanced keyboard navigation. -.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. -.TP -.B cursivfont (string) -The font family used as the default for content using cursive font. -.TP -.B defaultencoding (string) -The default text charset used when interpreting content with an unspecified -charset. -.TP -.B defaultfont (string) -The font family to use as the default for content that does not specify a -font. -.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. -.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. -.TP -.B fontsize (int) -The default font size used to display text. -.TP -.B frame-flattening (bool) -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 html5-database (bool) -Whether to enable HTML5 client-side SQL database support. -Client-side SQL database allows web pages to store structured data -and be able to use SQL to manipulate that data asynchronously. -.TP -.B html5-local-storage (bool) -Whether to enable HTML5 localStorage support. -localStorage provides simple synchronous storage access. -.TP -.B hyperlink-auditing (bool) -Enable or disable support for <a ping>. -.TP -.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. -.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. -.TP -.B java-applet (bool) -Enable or disable support for the Java <applet> tag. -Keep in mind that Java content can be still shown in the page -through <object> or <embed>, which are the preferred tags for this task. -.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. -.TP -.B media-playback-allows-inline (bool) -Whether media playback is full-screen only or inline playback is allowed. -Setting it to false allows specifying that media playback should be always -fullscreen. -.TP -.B media-playback-requires-user-gesture (bool) -Whether a user gesture (such as clicking the play button) would be required to -start media playback or load media. -Setting it on requires a gesture by the -user to start playback, or to load the media. -.TP -.B media-stream (bool) -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 mediasource (bool) -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) -The minimum font size used to display text. -.TP -.B monofont (string) -The font family used as the default for content using monospace font. -.TP -.B monofontsize (int) -Default font size for the monospace font. -.TP -.B offlinecache (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. -.TP -.B private-browsing (bool) -Whether to enable private browsing mode. -This suppresses printing of messages into JavaScript Console. -At the time this is the only way to force WebKit to -not allow a page to store data in the windows sessionStorage. -.TP -.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) -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) -The font family used as the default for content using serif font. -.TP -.B site-specific-quirks (bool) -Enables the site-specific compatibility workarounds. -.TP -.B smooth-scrolling (bool) -Enable or disable support for smooth scrolling. -.TP -.B spacial-navigation (bool) -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. -.TP -.B spell-checking (bool) -Whether to enable spell checking while typing. -.TP -.B spell-checking-languages (string) -The languages to be used for spell checking, separated by commas. -.sp -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. -.TP -.B tab-key-cycles-through-elements (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. -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. -.TP -.B useragent (string) -The user-agent string used by WebKit. -.TP -.B webaudio (bool) -Enable or disable support for WebAudio on pages. -WebAudio is an experimental proposal for allowing web pages -to generate Audio WAVE data from JavaScript. -.TP -.B webgl (bool) -Enable or disable support for WebGL on pages. -.TP -.B webinspector (bool) -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 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. -Note that the command is run through the mapping mechanism of vimb so -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 <C-R>% <C-R>;" -This fills the inputbox with the prefilled download command and replaces -`<C-R>%' with the current URI and `<C-R>;' with the URI of the hinted element. -.PD -.RE -.SH FILES -.TP -.I $XDG_CONFIG_HOME/vimb/ -Default directory for configuration data. -.RS -.PD 0 -.TP -.I config -Configuration file to set WebKit setting, some GUI styles and keybindings. -.TP -.I cookies -Cookie store file. -.TP -.I closed -Holds the URI of last closed browser windows. -.TP -.I history -This file holds the history of unique opened URIs. -.TP -.I command -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 -`URI<tab>title<tab>space separated tags' or as -.br -`URI<tab><tab>tags` 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. -.TP -.I hsts -Holds the known hosts hosts if Vimb is compiled with HTTP strict transport -security feature. -.TP -.I scripts.js -This file can be used to run user scripts, that are injected into every paged -that is opened. -.TP -.I style.css -File for userdefined CSS styles. -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 -.TP -.B http_proxy -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 -or on the mailing list https://lists.sourceforge.net/lists/listinfo/vimb-users. -.SH AUTHOR -Daniel Carl diff --git a/examples/formfiller/formfiller b/examples/formfiller/formfiller deleted file mode 100755 index 05edd49..0000000 --- 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 "<Esc>::e! _vbform.fill($data);<CR>" | 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 8357409..0000000 --- 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 0000000..ca46d8a --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +config.h +vimb diff --git a/src/Makefile b/src/Makefile index b72d7c9..e4e2fae 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,63 +1,27 @@ BASEDIR=.. include $(BASEDIR)/config.mk -OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) -LOBJ = $(patsubst %.c, %.lo, $(wildcard *.c)) +OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) -all: $(TARGET) +all: vimb -clean: clean-lib - $(RM) $(TARGET) *.o *.lo hints.js.h +clean: + $(RM) vimb *.o -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 - -$(TARGET): $(OBJ) -ifeq ($(VERBOSE),0) +vimb: $(OBJ) @echo "$(CC) $@" - @$(CC) $(OBJ) -o $@ $(LDFLAGS) -else - $(CC) $(OBJ) -o $@ $(LDFLAGS) -endif + @$(CC) $(LDFLAGS) $(OBJ) -o $@ -$(LIBTARGET): $(LOBJ) -ifeq ($(VERBOSE),0) - @echo "$(CC) $@" - @$(CC) -shared ${LOBJ} -o $@ $(LDFLAGS) -else - $(CC) -shared ${LOBJ} -o $@ $(LDFLAGS) -endif +$(OBJ): config.h $(BASEDIR)/config.mk + +-include $(OBJ:.o=.d) config.h: @echo create $@ from config.def.h @cp config.def.h $@ -%.o: %.c %.h -ifeq ($(VERBOSE),0) +%.o: %.c @echo "${CC} $@" @$(CC) $(CFLAGS) -c -o $@ $< -else - $(CC) $(CFLAGS) -c -o $@ $< -endif - -%.lo: %.c %.h -ifeq ($(VERBOSE),0) - @echo "${CC} $@" - @$(CC) -DTESTLIB $(CFLAGS) -fPIC -c -o $@ $< -else - $(CC) -DTESTLIB $(CFLAGS) -fPIC -c -o $@ $< -endif - --include $(OBJ:.o=.d) -.PHONY: all clean clean-lib +.PHONY: all clean diff --git a/src/arh.c b/src/arh.c deleted file mode 100644 index 6fd0f8e..0000000 --- a/src/arh.c +++ /dev/null @@ -1,199 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 a5234e1..f375a30 100644 --- a/src/ascii.h +++ b/src/ascii.h @@ -73,11 +73,11 @@ static const unsigned char chartable[256] = { /* 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) @@ -95,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 e856a4c..0000000 --- a/src/autocmd.c +++ /dev/null @@ -1,488 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 aef09a2..0000000 --- a/src/autocmd.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 deleted file mode 100644 index adc2e03..0000000 --- a/src/bookmark.c +++ /dev/null @@ -1,326 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 "bookmark.h" -#include "util.h" -#include "completion.h" - -extern VbCore vb; - -typedef struct { - char *uri; - char *title; - char *tags; -} Bookmark; - -static GList *load(const char *file); -static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query, - unsigned int qlen); -static Bookmark *line_to_bookmark(const char *uri, const char *data); -static void free_bookmark(Bookmark *bm); - -/** - * Write a new bookmark entry to the end of bookmark file. - */ -gboolean bookmark_add(const char *uri, const char *title, const char *tags) -{ - const char *file = vb.files[FILES_BOOKMARK]; - if (tags) { - return util_file_append(file, "%s\t%s\t%s\n", uri, title ? title : "", tags); - } - if (title) { - return util_file_append(file, "%s\t%s\n", uri, title); - } - return util_file_append(file, "%s\n", uri); -} - -gboolean bookmark_remove(const char *uri) -{ - char **lines, *line, *p; - int len, i; - GString *new; - gboolean removed = false; - - if (!uri) { - return false; - } - - lines = util_get_lines(vb.files[FILES_BOOKMARK]); - if (lines) { - new = g_string_new(NULL); - len = g_strv_length(lines) - 1; - for (i = 0; i < len; i++) { - line = lines[i]; - g_strstrip(line); - /* ignore the title or bookmark tags and test only the uri */ - if ((p = strchr(line, '\t'))) { - *p = '\0'; - if (!strcmp(uri, line)) { - removed = true; - continue; - } else { - /* reappend the tags */ - *p = '\t'; - } - } - if (!strcmp(uri, line)) { - removed = true; - continue; - } - g_string_append_printf(new, "%s\n", line); - } - g_strfreev(lines); - g_file_set_contents(vb.files[FILES_BOOKMARK], new->str, -1, NULL); - g_string_free(new, true); - } - - return removed; -} - -gboolean bookmark_fill_completion(GtkListStore *store, const char *input) -{ - gboolean found = false; - char **parts; - unsigned int len; - GtkTreeIter iter; - GList *src = NULL; - Bookmark *bm; - - src = load(vb.files[FILES_BOOKMARK]); - src = g_list_reverse(src); - if (!input || !*input) { - /* without any tags return all bookmarked items */ - for (GList *l = src; l; l = l->next) { - bm = (Bookmark*)l->data; - gtk_list_store_append(store, &iter); - gtk_list_store_set( - store, &iter, - COMPLETION_STORE_FIRST, bm->uri, -#ifdef FEATURE_TITLE_IN_COMPLETION - COMPLETION_STORE_SECOND, bm->title, -#endif - -1 - ); - found = true; - } - } else { - parts = g_strsplit(input, " ", 0); - len = g_strv_length(parts); - - for (GList *l = src; l; l = l->next) { - bm = (Bookmark*)l->data; - if (bookmark_contains_all_tags(bm, parts, len)) { - gtk_list_store_append(store, &iter); - gtk_list_store_set( - store, &iter, - COMPLETION_STORE_FIRST, bm->uri, -#ifdef FEATURE_TITLE_IN_COMPLETION - COMPLETION_STORE_SECOND, bm->title, -#endif - -1 - ); - found = true; - } - } - g_strfreev(parts); - } - - g_list_free_full(src, (GDestroyNotify)free_bookmark); - - return found; -} - -gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input) -{ - gboolean found; - unsigned int len, i; - char **tags, *tag; - GList *src = NULL, *taglist = NULL; - Bookmark *bm; - - /* get all distinct tags from bookmark file */ - src = load(vb.files[FILES_BOOKMARK]); - for (GList *l = src; l; l = l->next) { - bm = (Bookmark*)l->data; - /* if bookmark contains no tags we can go to the next bookmark */ - if (!bm->tags) { - continue; - } - - tags = g_strsplit(bm->tags, " ", -1); - len = g_strv_length(tags); - for (i = 0; i < len; i++) { - tag = tags[i]; - /* add tag only if it isn't already in the list */ - if (!g_list_find_custom(taglist, tag, (GCompareFunc)strcmp)) { - taglist = g_list_prepend(taglist, g_strdup(tag)); - } - } - g_strfreev(tags); - } - - /* generate the completion with the found tags */ - found = util_fill_completion(store, input, taglist); - - g_list_free_full(src, (GDestroyNotify)free_bookmark); - g_list_free_full(taglist, (GDestroyNotify)g_free); - - return found; -} - -#ifdef FEATURE_QUEUE -/** - * Push a uri to the end of the queue. - * - * @uri: URI to put into the queue - */ -gboolean bookmark_queue_push(const char *uri) -{ - return util_file_append(vb.files[FILES_QUEUE], "%s\n", uri); -} - -/** - * Push a uri to the beginning of the queue. - * - * @uri: URI to put into the queue - */ -gboolean bookmark_queue_unshift(const char *uri) -{ - return util_file_prepend(vb.files[FILES_QUEUE], "%s\n", uri); -} - -/** - * Retrieves the oldest entry from queue. - * - * @item_count: will be filled with the number of remaining items in queue. - * Returned uri must be freed with g_free. - */ -char *bookmark_queue_pop(int *item_count) -{ - return util_file_pop_line(vb.files[FILES_QUEUE], item_count); -} - -/** - * Removes all contents from the queue file. - */ -gboolean bookmark_queue_clear(void) -{ - FILE *f; - if ((f = fopen(vb.files[FILES_QUEUE], "w"))) { - fclose(f); - return true; - } - return false; -} -#endif /* FEATURE_QUEUE */ - -static GList *load(const char *file) -{ - return util_file_to_unique_list(file, (Util_Content_Func)line_to_bookmark, 0); -} - -/** - * Checks if the given bookmark matches all given query strings as prefix. If - * the bookmark has no tags, the matching is done on the '/' splited URL. - * - * @bm: bookmark to test if it matches - * @query: char array with tags to search for - * @qlen: length of given query char array - * - * Return: true if the bookmark contained all tags - */ -static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query, - unsigned int qlen) -{ - const char *separators; - char *cursor; - unsigned int i; - gboolean found; - - if (!qlen) { - return true; - } - - if (bm->tags) { - /* If there are tags - use them for matching. */ - separators = " "; - cursor = bm->tags; - } else { - /* No tags available - matching is based on the path parts of the URL. */ - separators = "./"; - cursor = bm->uri; - } - - /* iterate over all query parts */ - for (i = 0; i < qlen; i++) { - found = false; - - /* we want to do a prefix match on all bookmark tags - so we check for - * a match on string begin - if this fails we move the cursor to the - * next space and do the test again */ - while (cursor && *cursor) { - /* match was not found at current cursor position */ - if (g_str_has_prefix(cursor, query[i])) { - found = true; - break; - } - /* If match was not found at the cursor position - move cursor - * behind the next separator char. */ - if ((cursor = strpbrk(cursor, separators))) { - cursor++; - } - } - - if (!found) { - return false; - } - } - - return true; -} - -static Bookmark *line_to_bookmark(const char *uri, const char *data) -{ - char *p; - Bookmark *bm; - - /* data part may consist of title or title<tab>tags */ - bm = g_slice_new(Bookmark); - bm->uri = g_strdup(uri); - if (data && (p = strchr(data, '\t'))) { - *p = '\0'; - bm->title = g_strdup(data); - bm->tags = g_strdup(p + 1); - } else { - bm->title = g_strdup(data); - bm->tags = NULL; - } - - return bm; -} - -static void free_bookmark(Bookmark *bm) -{ - g_free(bm->uri); - g_free(bm->title); - g_free(bm->tags); - g_slice_free(Bookmark, bm); -} diff --git a/src/bookmark.h b/src/bookmark.h deleted file mode 100644 index 32aaba4..0000000 --- a/src/bookmark.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 _BOOKMARK_H -#define _BOOKMARK_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); -gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input); -#ifdef FEATURE_QUEUE -gboolean bookmark_queue_push(const char *uri); -gboolean bookmark_queue_unshift(const char *uri); -char *bookmark_queue_pop(int *item_count); -gboolean bookmark_queue_clear(void); -#endif - -#endif /* end of include guard: _BOOKMARK_H */ diff --git a/src/command.c b/src/command.c index a5bb9f2..c957c46 100644 --- a/src/command.c +++ b/src/command.c @@ -21,128 +21,86 @@ * This file contains functions that are used by normal mode and command mode * together. */ -#include "config.h" -#include "main.h" #include "command.h" +#include "config.h" #include "history.h" -#include "bookmark.h" +#include "main.h" +#include <stdlib.h> -extern VbCore vb; +extern struct Vimb vb; -gboolean command_search(const Arg *arg) +gboolean command_search(Client *c, const Arg *arg) { - static short dir; /* last direction 1 forward, -1 backward*/ const char *query; - static gboolean newsearch = true; gboolean forward; + WebKitFindController *fc; + fc = webkit_web_view_get_find_controller(c->webview); if (arg->i == 0) { -#ifdef FEATURE_SEARCH_HIGHLIGHT - webkit_web_view_unmark_text_matches(vb.gui.webview); - vb.state.search_matches = 0; - vb_update_statusbar(); -#endif - newsearch = true; - return true; + webkit_find_controller_search_finish(fc); + c->state.search.matches = 0; + c->state.search.active = FALSE; + vb_statusbar_update(c); + return TRUE; } /* copy search query for later use */ if (arg->s) { /* set search direction only when the searching is started */ - dir = arg->i > 0 ? 1 : -1; + c->state.search.direction = 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); + vb_register_add(c, '/', query); + history_add(c, HISTORY_SEARCH, query, NULL); } else { /* no search phrase given - continue a previous search */ - query = vb_register_get('/'); + query = vb_register_get(c, '/'); } - forward = (arg->i * dir) > 0; + forward = (arg->i * c->state.search.direction) > 0; if (query) { - unsigned int count = abs(arg->i); - if (newsearch) { -#ifdef FEATURE_SEARCH_HIGHLIGHT - /* highlight matches if the search is started new or continued - * after switch to normal mode which calls this function with - * COMMAND_SEARCH_OFF */ - vb.state.search_matches = webkit_web_view_mark_text_matches(vb.gui.webview, query, false, 0); - webkit_web_view_set_highlight_text_matches(vb.gui.webview, true); - vb_update_statusbar(); -#endif - newsearch = false; - /* 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; - } + guint count = abs(arg->i); + + if (!c->state.search.active) { + webkit_find_controller_search(fc, query, + WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + WEBKIT_FIND_OPTIONS_WRAP_AROUND | + (forward ? WEBKIT_FIND_OPTIONS_NONE : WEBKIT_FIND_OPTIONS_BACKWARDS), + G_MAXUINT); + c->state.search.active = TRUE; + /* Skip first search because the first match is already + * highlighted on search start. */ + count -= 1; } - while (count--) { - if (!webkit_web_view_search_text(vb.gui.webview, query, false, forward, true)) { - break; - } - }; + if (forward) { + 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; - } - - return false; - } - - Arg a = {VB_CLIPBOARD_PRIMARY|VB_CLIPBOARD_SECONDARY}; - if (arg->i == COMMAND_YANK_URI) { - /* yank current uri */ - a.s = vb.state.uri; - } else { - /* use current arg.s as new clipboard content */ - a.s = 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; - } - - return false; + /* TODO no implemented yet */ + return TRUE; } -gboolean command_save(const Arg *arg) +gboolean command_save(Client *c, const Arg *arg) { - WebKitDownload *download; +#if 0 const char *uri, *path = NULL; 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; @@ -152,52 +110,18 @@ gboolean command_save(const Arg *arg) } if (!uri || !*uri) { - return false; + return FALSE; } +#endif - download = webkit_download_new(webkit_network_request_new(uri)); - vb_download(vb.gui.webview, download, path); - - return true; + /* TODO start the download to given path here */ + return TRUE; } #ifdef FEATURE_QUEUE -gboolean command_queue(const Arg *arg) +gboolean command_queue(Client *c, const Arg *arg) { - 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}); - g_free(uri); - } - vb_echo(VB_MSG_NORMAL, false, "Queue length %d", count); - break; - - case COMMAND_QUEUE_PUSH: - res = bookmark_queue_push(arg->s ? arg->s : vb.state.uri); - if (res) { - vb_echo(VB_MSG_NORMAL, false, "Pushed to queue"); - } - break; - - case COMMAND_QUEUE_UNSHIFT: - res = bookmark_queue_unshift(arg->s ? arg->s : vb.state.uri); - if (res) { - vb_echo(VB_MSG_NORMAL, false, "Pushed to queue"); - } - break; - - case COMMAND_QUEUE_CLEAR: - if (bookmark_queue_clear()) { - vb_echo(VB_MSG_NORMAL, false, "Queue cleared"); - } - break; - } - - return res; + /* TODO no implemented yet */ + return TRUE; } #endif diff --git a/src/command.h b/src/command.h index 68c8d85..92cf494 100644 --- a/src/command.h +++ b/src/command.h @@ -20,6 +20,9 @@ #ifndef _COMMAND_H #define _COMMAND_H +#include <gtk/gtk.h> +#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); +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 a15b081..8bfb9b0 100644 --- a/src/completion.c +++ b/src/completion.c @@ -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; -gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc, - gboolean back) + +/** + * 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; + } +} + +/** + * 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,56 @@ 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(); + + GtkCssProvider* provider = gtk_css_provider_get_default(); + gtk_css_provider_load_from_data(provider, GUI_STYLE, -1, NULL); + gtk_style_context_add_provider(gtk_widget_get_style_context(comp->tree), + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(comp.tree), false); + 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); /* 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 +140,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 +162,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 +194,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) { + 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))); + 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; + 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 +273,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 <Tab> + * or <S-Tab> from selected item on. */ } - return true; + return TRUE; } diff --git a/src/completion.h b/src/completion.h index 26b057d..e353ecb 100644 --- a/src/completion.h +++ b/src/completion.h @@ -20,8 +20,12 @@ #ifndef _COMPLETION_H #define _COMPLETION_H +#include <glib.h> + #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 97531bf..da33cc7 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -17,101 +17,35 @@ * 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 -/* show page title in url completions */ -#define FEATURE_TITLE_IN_COMPLETION -/* 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 hight 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 -/* 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 +/* show page title in url completions */ +#define FEATURE_TITLE_IN_COMPLETION /* 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 of the borwser window */ +#define GUI_STYLE "GtkBox#statusbar{color:#fff;background-color:#000;font:monospace bold 10;} \ +GtkBox#statusbar.secure{background-color:#95e454;color:#000;} \ +GtkBox#statusbar.insecure{background-color:#f77;color:#000;} \ +GtkTextView{background-color:#fff;color:#000;font:monospace 10;} \ +GtkTextView.error{background-color:#f77;font-weight:bold;} \ +GtkTreeView{color:#fff;background-color:#656565;font:monospace;} \ +GtkTreeView:hover{background-color:#777;} \ +GtkTreeView:selected{color:#f6f3e8;background-color:#888;}" -/* 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 MAXIMUM_HINTS 500 - -#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\ -}\ -._hintElem._hintFocus{\ -background-color:#8f0 !important\ -}\ -._hintLabel._hintFocus{\ -z-index:100001;\ -opacity:1\ -}" - -#endif /* end of include guard: _CONFIG_H */ +#define SETTING_HOME_PAGE "about:blank" diff --git a/src/cookiejar.c b/src/cookiejar.c deleted file mode 100644 index a16392f..0000000 --- a/src/cookiejar.c +++ /dev/null @@ -1,105 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <fcntl.h> -#include <sys/file.h> -#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/cookiejar.h b/src/cookiejar.h deleted file mode 100644 index bca9758..0000000 --- a/src/cookiejar.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 - -#ifndef _COOKIEJAR_H -#define _COOKIEJAR_H - -#define COOKIEJAR_TYPE (cookiejar_get_type()) -#define COOKIEJAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), COOKIEJAR_TYPE, CookieJar)) - - -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 diff --git a/src/dom.c b/src/dom.c deleted file mode 100644 index 32b6bea..0000000 --- a/src/dom.c +++ /dev/null @@ -1,330 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 03eaf37..0000000 --- a/src/dom.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <webkit/webkit.h> - -/* 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 */ @@ -21,32 +21,23 @@ * This file contains function to handle input editing, parsing of called ex * commands from inputbox and the ex commands. */ -#include "config.h" + +#include <string.h> #include <sys/wait.h> -#include "main.h" -#include "ex.h" + #include "ascii.h" -#include "completion.h" -#include "hints.h" #include "command.h" +#include "completion.h" +#include "config.h" +#include "ex.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 typedef enum { -#ifdef FEATURE_AUTOCMD - EX_AUTOCMD, - EX_AUGROUP, -#endif + /* TODO add feature autocmd */ EX_BMA, EX_BMR, EX_EVAL, @@ -78,7 +69,6 @@ typedef enum { EX_SCR, EX_SET, EX_SHELLCMD, - EX_SOURCE, EX_TABOPEN, } ExCode; @@ -98,7 +88,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 +108,38 @@ 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 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_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,10 +150,6 @@ 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}, @@ -200,7 +181,6 @@ static ExInfo commands[] = { {"shortcut-add", EX_SCA, ex_shortcut, EX_FLAG_RHS}, {"shortcut-default", EX_SCD, ex_shortcut, EX_FLAG_RHS}, {"shortcut-remove", EX_SCR, ex_shortcut, EX_FLAG_RHS}, - {"source", EX_SOURCE, ex_source, EX_FLAG_RHS|EX_FLAG_EXP}, {"tabopen", EX_TABOPEN, ex_open, EX_FLAG_CMD}, }; @@ -217,45 +197,43 @@ 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(); + completion_clean(c); +#if 0 hints_clear(); +#endif } /** * 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; - /* delegate call to hint mode if this is active */ - if (vb.mode->flags & FLAG_HINTING - && RESULT_COMPLETE == hints_keypress(key)) { - - return RESULT_COMPLETE; - } + /* TODO delegate call to hint mode if this is active */ /* process the register */ if (info.phase == PHASE_REG) { @@ -263,7 +241,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 +251,29 @@ 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: + history(c, TRUE); break; - case KEY_CR: - input_activate(); + case KEY_DOWN: + history(c, FALSE); break; - case KEY_UP: - history(true); + case KEY_CR: + input_activate(c); break; - case KEY_DOWN: - history(false); + case CTRL('['): + case CTRL('C'): + vb_enter(c, 'n'); + vb_input_set_text(c, ""); break; /* basic command line editing */ @@ -303,8 +281,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 +297,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 +316,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 +332,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 +342,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 +358,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) { @@ -399,15 +376,7 @@ void ex_input_changed(const char *text) switch (*text) { case ';': /* fall through - the modes are handled by hints_create */ case 'g': - hints_create(text); - break; - - case '/': forward = true; /* 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); + /* TODO create hints */ break; } } @@ -416,14 +385,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 +400,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; } } } @@ -442,33 +411,34 @@ gboolean ex_fill_completion(GtkListStore *store, const char *input) /** * This is called if the user typed <nl> or <cr> into the inputbox. */ -static void input_activate(void) +static void input_activate(Client *c) { int count = -1; char *text, *cmd; VbCmdResult res; - text = vb_get_input_text(); + + text = vb_input_get_text(c); /* 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})); + vb_enter(c, 'n'); + command_search(c, &((Arg){count, cmd})); break; case ';': /* fall through */ case 'g': - hints_fire(); + /* TODO fire hints */ break; case ':': - vb_enter('n'); - res = ex_run_string(cmd, true); - if (!(res & VB_CMD_KEEPINPUT)) { + 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_set_input_text(""); + vb_input_set_text(c, ""); } break; @@ -476,25 +446,25 @@ static void input_activate(void) g_free(text); } -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 +473,12 @@ VbCmdResult ex_run_string(const char *input, gboolean enable_history) } /** - * Run all ex commands in a file. - */ -VbCmdResult ex_run_file(const char *filename) -{ - char *line, **lines; - VbCmdResult res = VB_CMD_SUCCESS; - - lines = util_get_lines(filename); - - if (!lines) { - return res; - } - - 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); - - return res; -} - -/** * 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 +490,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 +511,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 +533,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 +577,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 +586,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 +595,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 +609,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 +622,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 +636,7 @@ static gboolean parse_lhs(const char **input, ExArg *arg) } (*input)++; } - return true; + return TRUE; } /** @@ -706,7 +644,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 +654,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 +667,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 +679,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,210 +700,136 @@ 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"); - - return VB_CMD_SUCCESS | VB_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"); - - return VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; - } - - return VB_CMD_ERROR; + /* TODO no implemented yet */ + return CMD_SUCCESS; } -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; - } - - 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) { - if (success) { - vb_echo(VB_MSG_NORMAL, false, "%s", value); - res = VB_CMD_SUCCESS | VB_CMD_KEEPINPUT; - } else { - vb_echo(VB_MSG_ERROR, true, "%s", value); - res = VB_CMD_ERROR | VB_CMD_KEEPINPUT; - } - } - g_free(value); - - return res; + /* TODO allow to get the return value and possible errors. */ + webkit_web_view_run_javascript(c->webview, arg->rhs->str, NULL, NULL, NULL); + 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; + /* TODO no implemented yet */ + 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}; - - switch (arg->code) { - case EX_QPUSH: - a.i = COMMAND_QUEUE_PUSH; - break; - - case EX_QUNSHIFT: - a.i = COMMAND_QUEUE_UNSHIFT; - break; - - case EX_QPOP: - a.i = COMMAND_QUEUE_POP; - break; - - case EX_QCLEAR: - a.i = COMMAND_QUEUE_CLEAR; - break; - - default: - return VB_CMD_ERROR; - } - - /* if no argument is found in rhs, keep the uri in arg null to force - * command_queue() to use current URI */ - if (arg->rhs->len) { - a.s = arg->rhs->str; - } - - return command_queue(&a) - ? VB_CMD_SUCCESS | VB_CMD_KEEPINPUT - : VB_CMD_ERROR | VB_CMD_KEEPINPUT; + /* TODO no implemented yet */ + return CMD_SUCCESS; } #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 +838,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,94 +852,48 @@ 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) +static VbCmdResult ex_handlers(Client *c, const ExArg *arg) { - return ex_run_file(arg->rhs->str); + /* TODO no implemented yet */ + return CMD_SUCCESS; } -static VbCmdResult ex_handlers(const ExArg *arg) +static VbCmdResult ex_shortcut(Client *c, const ExArg *arg) { - char *p; - gboolean success = 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); - } - break; - - case EX_HANDREM: - success = handler_remove(arg->rhs->str); - break; - - default: - break; - } - - return success ? VB_CMD_SUCCESS : VB_CMD_ERROR; -} - -static VbCmdResult ex_shortcut(const ExArg *arg) -{ - char *p; - gboolean success = false; - - /* TODO allow to set shortcuts with set command like ':set - * shortcut[name]=http://donain.tld/?q=$0' */ - switch (arg->code) { - case EX_SCA: - if (arg->rhs->len && (p = strchr(arg->rhs->str, '='))) { - *p++ = '\0'; - success = shortcut_add(arg->rhs->str, p); - } - break; - - case EX_SCR: - success = shortcut_remove(arg->rhs->str); - break; - - case EX_SCD: - success = shortcut_set_default(arg->rhs->str); - break; - - default: - break; - } - return success ? VB_CMD_SUCCESS : VB_CMD_ERROR; + /* TODO no implemented yet */ + return CMD_SUCCESS; } /** @@ -1084,38 +902,38 @@ static VbCmdResult ex_shortcut(const ExArg *arg) * put hte 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); @@ -1137,7 +955,7 @@ static gboolean complete(short direction) /* Do ex command specific completion if the comman 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 +983,33 @@ static gboolean complete(short direction) switch (arg->code) { case EX_OPEN: case EX_TABOPEN: - if (*token == '!') { - found = bookmark_fill_completion(store, token + 1); - } else { - found = history_fill_completion(store, HISTORY_URL, token); - } - sort = false; + /* TODO add bookmark completion if *token == '!' */ + found = history_fill_completion(store, HISTORY_URL, token); break; case EX_SET: - found = setting_fill_completion(store, token); + sort = TRUE; + found = setting_fill_completion(c, store, token); break; case EX_BMA: - found = bookmark_fill_tag_completion(store, token); + sort = TRUE; + /* TODO fill bookmark completion */ break; case EX_SCR: - found = shortcut_fill_completion(store, token); + sort = TRUE; + /* TODO fill shortcut completion */ break; case EX_HANDREM: - found = handler_fill_completion(store, token); - break; - -#ifdef FEATURE_AUTOCMD - case EX_AUTOCMD: - found = autocmd_fill_event_completion(store, token); + sort = TRUE; + /* TODO fill handler completion */ break; - case EX_AUGROUP: - found = autocmd_fill_group_completion(store, token); - break; -#endif - case EX_SAVE: - case EX_SOURCE: - found = util_filename_fill_completion(store, token); + sort = TRUE; + found = util_filename_fill_completion(c, store, token); break; default: @@ -1218,8 +1026,8 @@ static gboolean complete(short direction) if (ex_fill_completion(store, in)) { OVERWRITE_STRING(excomp.prefix, ":"); - found = true; - sort = false; + found = TRUE; + sort = FALSE; } } free_cmdarg(arg); @@ -1227,7 +1035,7 @@ 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; } } @@ -1239,11 +1047,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 +1059,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 +1068,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 +1101,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 +1125,14 @@ static gboolean history(gboolean prev) exhist.active = new; } - vb_echo_force(VB_MSG_NORMAL, false, "%s%s", exhist.prefix, (char*)exhist.active->data); + 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) @@ -23,12 +23,11 @@ #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_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 0000000..75ccbd7 --- /dev/null +++ b/src/ext-proxy.c @@ -0,0 +1,214 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2015 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 <gio/gio.h> +#include <glib.h> + +#include "ext-proxy.h" +#include "main.h" +#include "webextension/ext-main.h" + +static void dbus_call(Client *c, const char *method, GVariant *param, + GAsyncReadyCallback callback); +static void on_editable_change_focus(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data); +static void on_name_appeared(GDBusConnection *connection, const char *name, + const char *owner, gpointer data); +static void on_proxy_created(GDBusProxy *new_proxy, GAsyncResult *result, + gpointer data); +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; + + +/** + * 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", NULL, NULL); +} + +/** + * Initialize the dbus proxy by watching for appearing dbus name. + */ +void ext_proxy_init(const char *id) +{ + char *service_name; + + service_name = g_strdup_printf("%s-%s", VB_WEBEXTENSION_SERVICE_NAME, id); + g_bus_watch_name( + G_BUS_TYPE_SESSION, + service_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + (GBusNameAppearedCallback)on_name_appeared, + NULL, + NULL, + NULL); + g_free(service_name); +} + +/** + * 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); +} + +/** + * Callback called if a editable element changes it's focus state. + */ +static void on_editable_change_focus(GDBusConnection *connection, + const char *sender_name, const char *object_path, + const char *interface_name, const char *signal_name, + GVariant *parameters, gpointer data) +{ + gboolean is_focused; + Client *c = (Client*)data; + g_variant_get(parameters, "(b)", &is_focused); + + /* 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'); + } + /* TODO allo strict-focus to ignore focus event for initial set focus */ +} + +/** + * Called when the name of the webextension appeared on the dbus session bus. + */ +static void on_name_appeared(GDBusConnection *connection, const char *name, + const char *owner, gpointer data) +{ + int flags = G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START + | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS; + + /* Create the proxy to communicate over dbus. */ + g_dbus_proxy_new(connection, flags, NULL, name, + VB_WEBEXTENSION_OBJECT_PATH, VB_WEBEXTENSION_INTERFACE, NULL, + (GAsyncReadyCallback)on_proxy_created, NULL); +} + +/** + * Callback called when the dbus proxy is created. + */ +static void on_proxy_created(GDBusProxy *new_proxy, GAsyncResult *result, gpointer data) +{ + GDBusConnection *connection; + GDBusProxy *proxy; + GError *error = NULL; + + proxy = g_dbus_proxy_new_finish(result, &error); + connection = g_dbus_proxy_get_connection(proxy); + + if (!proxy) { + g_warning("Error creating web extension proxy: %s", error->message); + g_error_free(error); + return; + } + g_dbus_proxy_set_default_timeout(proxy, 100); + 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) +{ + Client *c = (Client*)data; + g_variant_get(parameters, "(tt)", &c->state.scroll_max, &c->state.scroll_percent); + + vb_statusbar_update(c); +} + +/** + * 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 *p; + guint64 page_id; + + g_variant_get(parameters, "(t)", &page_id); + + /* Search for the client with the same page id as returned by the + * webextension. */ + for (p = vb.clients; p && p->page_id != page_id; p = p->next); + + if (p) { + /* Set the dbus proxy on the right client based on page id. */ + p->dbusproxy = data; + + 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, p, NULL); + g_dbus_connection_signal_subscribe(connection, NULL, + VB_WEBEXTENSION_INTERFACE, "EditableChangeFocus", + VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)on_editable_change_focus, p, NULL); + } +} diff --git a/src/arh.h b/src/ext-proxy.h index d235b36..b5b33a1 100644 --- a/src/arh.h +++ b/src/ext-proxy.h @@ -2,7 +2,6 @@ * vimb - a webkit based vim like browser. * * Copyright (C) 2012-2015 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 @@ -18,17 +17,13 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#ifdef FEATURE_ARH - -#ifndef _ARH_H -#define _ARH_H +#ifndef _EXT_PROXY_H +#define _EXT_PROXY_H #include "main.h" -GSList *arh_parse(const char *, const char **); -void arh_free(GSList *); -void arh_run(GSList *, const char *, SoupMessage *); +void ext_proxy_focus_input(Client *c); +void ext_proxy_init(const char *id); +void ext_proxy_set_header(Client *c, const char *headers); -#endif /* end of include guard: _ARH_H */ -#endif +#endif /* end of include guard: _EXT_PROXY_H */ diff --git a/src/handlers.c b/src/handlers.c deleted file mode 100644 index 66625bf..0000000 --- a/src/handlers.c +++ /dev/null @@ -1,96 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 "main.h" -#include "handlers.h" -#include "util.h" - -static GHashTable *handlers = NULL; - -static char *handler_lookup(const char *uri); - - -void handlers_init(void) -{ - handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); -} - -void handlers_cleanup(void) -{ - if (handlers) { - g_hash_table_destroy(handlers); - } -} - -gboolean handler_add(const char *key, const char *cmd) -{ - g_hash_table_insert(handlers, g_strdup(key), g_strdup(cmd)); - - return true; -} - -gboolean handler_remove(const char *key) -{ - return g_hash_table_remove(handlers, key); -} - -gboolean handle_uri(const char *uri) -{ - char *handler, *cmd; - GError *error = NULL; - gboolean result; - - if (!(handler = handler_lookup(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; - } else { - result = true; - } - - g_free(cmd); - return result; -} - -gboolean handler_fill_completion(GtkListStore *store, const char *input) -{ - GList *src = g_hash_table_get_keys(handlers); - gboolean found = util_fill_completion(store, input, src); - g_list_free(src); - - return found; -} - -static char *handler_lookup(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); - g_free(schema); - } - - return handler; -} diff --git a/src/hints.c b/src/hints.c deleted file mode 100644 index 91bb869..0000000 --- a/src/hints.c +++ /dev/null @@ -1,398 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <gdk/gdkkeysyms.h> -#include <gdk/gdkkeysyms-compat.h> -#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" - -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; - -static gboolean call_hints_function(const char *func, int count, JSValueRef params[]); -static void fire_timeout(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) -{ - JSValueRef arguments[1]; - - if (key == KEY_CR) { - hints_fire(); - - return RESULT_COMPLETE; - } else if (key == CTRL('H')) { - fire_timeout(false); - arguments[0] = JSValueMakeNull(hints.ctx); - if (call_hints_function("update", 1, arguments)) { - return RESULT_COMPLETE; - } - } else if (key == KEY_TAB) { - fire_timeout(false); - hints_focus_next(false); - - return RESULT_COMPLETE; - } else if (key == KEY_SHIFT_TAB) { - fire_timeout(false); - hints_focus_next(true); - - return RESULT_COMPLETE; - } else { - fire_timeout(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)) { - return RESULT_COMPLETE; - } - } - - fire_timeout(false); - return RESULT_ERROR; -} - -void hints_clear(void) -{ - if (vb.mode->flags & FLAG_HINTING) { - vb.mode->flags &= ~FLAG_HINTING; - vb_set_input_text(""); - - call_hints_function("clear", 0, NULL); - - 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); - g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", hints.allow_open_win, NULL); - } -#endif - } -} - -void hints_create(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; - } - - /* 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'); - } - return; - } - - if (!(vb.mode->flags & FLAG_HINTING)) { - vb.mode->flags |= FLAG_HINTING; - -#if WEBKIT_CHECK_VERSION(2, 0, 0) - WebKitWebSettings *setting = webkit_web_view_get_settings(vb.gui.webview); - - /* before we enable JavaScript to open new windows, we save the actual - * value to be able restore it after hints where fired */ - g_object_get(G_OBJECT(setting), "javascript-can-open-windows-automatically", &(hints.allow_open_win), NULL); - - /* if window open is already allowed there's no need to allow it again */ - 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); - - /* 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); -} - -void hints_focus_next(const gboolean back) -{ - JSValueRef arguments[] = { - JSValueMakeNumber(hints.ctx, back) - }; - call_hints_function("focus", 1, arguments); -} - -void hints_fire(void) -{ - call_hints_function("fire", 0, NULL); -} - -void hints_follow_link(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); -} - -void hints_increment_uri(int count) -{ - JSValueRef arguments[] = { - JSValueMakeNumber(hints.ctx, count) - }; - - call_hints_function("incrementUri", 1, arguments); -} - -/** - * Checks if the given hint prompt belong to a known and valid hints mode and - * parses the mode and is_gmode into given pointers. - * - * The given prompt sting may also contain additional chars after the prompt. - * - * @prompt: String to be parsed as prompt. The Prompt can be followed by - * additional characters. - * @mode: Pointer to char that will be filled with mode char if prompt was - * valid and given pointer is not NULL. - * @is_gmode: Pointer to gboolean to be filled with the flag to indicate gmode - * hinting. - */ -gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode) -{ - gboolean res; - char pmode = '\0'; -#ifdef FEATURE_QUEUE - static char *modes = "eiIoOpPstTxyY"; - static char *g_modes = "IpPstyY"; -#else - static char *modes = "eiIoOstTxyY"; - static char *g_modes = "IstyY"; -#endif - - if (!prompt) { - return false; - } - - /* get the mode identifying char from prompt */ - if (*prompt == ';') { - pmode = prompt[1]; - } else if (*prompt == 'g' && strlen(prompt) >= 3) { - /* get mode for g;X hint modes */ - pmode = prompt[2]; - } - - /* no mode found in prompt */ - if (!pmode) { - return false; - } - - res = *prompt == 'g' - ? strchr(g_modes, pmode) != NULL - : strchr(modes, pmode) != NULL; - - /* fill pointer only if the prompt was valid */ - if (res) { - if (mode != NULL) { - *mode = pmode; - } - if (is_gmode != NULL) { - *is_gmode = *prompt == 'g'; - } - } - - return res; -} - -static gboolean call_hints_function(const char *func, int count, JSValueRef params[]) -{ - char *value = js_object_call_function(hints.ctx, hints.obj, func, count, params); - - g_return_val_if_fail(value != NULL, false); - - if (!strncmp(value, "ERROR:", 6)) { - g_free(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; - } - - /* following return values mark fired hints */ - if (!strncmp(value, "DONE:", 5)) { - fire_timeout(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'); - } - } else if (!strncmp(value, "INSERT:", 7)) { - fire_timeout(false); - vb_enter('i'); - if (hints.mode == 'e') { - input_open_editor(); - } - } else if (!strncmp(value, "DATA:", 5)) { - fire_timeout(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'); - } - - char *v = (value + 5); - Arg a = {0}; - /* put the hinted value into register "; */ - vb_register_add(';', 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); - break; - - case 'O': - case 'T': - vb_echo(VB_MSG_NORMAL, false, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v); - if (!hints.gmode) { - vb_enter('c'); - } - break; - - case 's': - a.s = v; - a.i = COMMAND_SAVE_URI; - command_save(&a); - break; - - case 'x': - map_handle_string(GET_CHAR("x-hint-command"), true); - break; - - case 'y': - case 'Y': - a.i = COMMAND_YANK_ARG; - a.s = v; - command_yank(&a, vb.state.current_register); - break; - -#ifdef FEATURE_QUEUE - case 'p': - case 'P': - a.s = v; - a.i = (hints.mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH; - command_queue(&a); - break; -#endif - } - } - g_free(value); - return true; -} - -static void fire_timeout(gboolean on) -{ - int millis; - /* remove possible timeout function */ - if (hints.timeout_id) { - g_source_remove(hints.timeout_id); - hints.timeout_id = 0; - } - - if (on) { - millis = GET_INT("hint-timeout"); - if (millis) { - hints.timeout_id = g_timeout_add(millis, (GSourceFunc)fire_cb, NULL); - } - } -} - -static gboolean fire_cb(gpointer data) -{ - hints_fire(); - - /* remove timeout id for the timeout that is removed by return value of - * false automatic */ - hints.timeout_id = 0; - return false; -} diff --git a/src/hints.js b/src/hints.js deleted file mode 100644 index 458d85c..0000000 --- a/src/hints.js +++ /dev/null @@ -1,611 +0,0 @@ -Object.freeze((function(){ - 'use strict'; - - var hints = [], /* holds all hint data (hinted element, label, number) in view port */ - docs = [], /* hold the affected document with the start and end index of the hints */ - validHints = [], /* holds the valid hinted elements matching the filter condition */ - activeHint, /* holds the active hint object */ - filterText = "", /* holds the typed filter text */ - filterNum = 0, /* holds the numeric filter */ - /* TODO remove these classes and use the 'vimbhint' attribute for */ - /* styling the hints and labels - but this might break user */ - /* stylesheets that use the classes for styling */ - cId = "_hintContainer", /* id of the container holding the hint labels */ - lClass = "_hintLabel", /* class used on the hint labels with the hint numbers */ - hClass = "_hintElem", /* marks hinted elements */ - fClass = "_hintFocus", /* marks focused element and focussed hint */ - config; - /* the hint class used to maintain hinted element and labels */ - function Hint() { - /* hide hint label and remove coloring from hinted element */ - this.hide = function() { - /* remove hint labels from no more visible hints */ - this.label.style.display = "none"; - this.e.classList.remove(fClass); - this.e.classList.remove(hClass); - }; - - /* show the hint element colored with the hint label */ - this.show = function() { - this.label.style.display = ""; - this.e.classList.add(hClass); - - /* create the label with the hint number */ - var text = []; - if (this.e instanceof HTMLInputElement) { - var type = this.e.type; - if (type === "checkbox") { - text.push(this.e.checked ? "☑" : "☐"); - } else if (type === "radio") { - text.push(this.e.checked ? "⊙" : "○"); - } - } - if (this.showText && this.text) { - text.push(this.text.substr(0, 20)); - } - /* use \x20 instead of ' ' to keep this space during js2h.sh processing */ - this.label.innerText = this.num + (text.length ? ":\x20" + text.join("\x20") : ""); - }; - } - - function clear() { - var i, j, doc, e; - for (i = 0; i < docs.length; i++) { - doc = docs[i]; - /* find all hinted elements vimbhint 'hint' */ - var res = xpath(doc.doc, "//*[contains(@vimbhint, 'hint')]"); - for (j = 0; j < res.snapshotLength; j++) { - e = res.snapshotItem(j); - e.removeAttribute("vimbhint"); - e.classList.remove(fClass); - e.classList.remove(hClass); - } - doc.div.parentNode.removeChild(doc.div); - } - docs = []; - hints = []; - validHints = []; - filterText = ""; - filterNum = 0; - } - - function create() { - var count = 0; - - function helper(win, offsets) { - /* document may be undefined for frames out of the same origin */ - /* policy and will break the whole code - so we check this before */ - if (typeof win.document == "undefined") { - return; - } - - offsets = offsets || {left: 0, right: 0, top: 0, bottom: 0}; - offsets.right = win.innerWidth - offsets.right; - offsets.bottom = win.innerHeight - offsets.bottom; - - /* checks if given elemente is in viewport and visible */ - function isVisible(e) { - if (typeof e == "undefined") { - return false; - } - var rect = e.getBoundingClientRect(); - if (!rect || - rect.top > offsets.bottom || rect.bottom < offsets.top || - rect.left > offsets.right || rect.right < offsets.left - ) { - return false; - } - - if ((!rect.width || !rect.height) && (e.textContent || !e.name)) { - var arr = Array.prototype.slice.call(e.childNodes); - var check = function(e) { - return e instanceof Element - && e.style.float != "none" - && isVisible(e); - }; - if (!arr.some(check)) { - return false; - } - } - - var s = win.getComputedStyle(e, null); - return s.display !== "none" && s.visibility == "visible"; - } - - var doc = win.document, - res = xpath(doc, config.xpath), - /* generate basic hint element which will be cloned and updated later */ - labelTmpl = doc.createElement("span"), - e, i; - - labelTmpl.className = lClass; - labelTmpl.setAttribute("vimbhint", "label"); - - var containerOffsets = getOffsets(doc), - offsetX = containerOffsets[0], - offsetY = containerOffsets[1], - fragment = doc.createDocumentFragment(), - rect, label, text, showText, start = hints.length; - - /* collect all visible elements in hints array */ - for (i = 0; i < res.snapshotLength; i++) { - e = res.snapshotItem(i); - if (!isVisible(e)) { - continue; - } - - count++; - - /* create the hint label with number */ - rect = e.getBoundingClientRect(); - label = labelTmpl.cloneNode(false); - label.setAttribute( - "style", [ - "display:none;", - "left:", Math.max((rect.left + offsetX), offsetX), "px;", - "top:", Math.max((rect.top + offsetY), offsetY), "px;" - ].join("") - ); - - /* if hinted element is an image - show title or alt of the image in hint label */ - /* this allows to see how to filter for the image */ - text = ""; - showText = false; - if (e instanceof HTMLImageElement) { - text = e.title || e.alt; - showText = true; - } else if (e.firstElementChild instanceof HTMLImageElement && /^\s*$/.test(e.textContent)) { - text = e.firstElementChild.title || e.firstElementChild.alt; - showText = true; - } else if (e instanceof HTMLInputElement) { - var type = e.type; - if (type === "image") { - text = e.alt || ""; - } else if (e.value && type !== "password") { - text = e.value; - showText = (type === "radio" || type === "checkbox"); - } - } else if (e instanceof HTMLSelectElement) { - if (e.selectedIndex >= 0) { - text = e.item(e.selectedIndex).text; - } - } else { - text = e.textContent; - } - /* add the hint class to the hinted element */ - fragment.appendChild(label); - e.setAttribute("vimbhint", "hint"); - - hints.push({ - e: e, - label: label, - text: text, - showText: showText, - __proto__: new Hint - }); - - if (count >= config.maxHints) { - break; - } - } - - /* append the fragment to the document */ - var hDiv = doc.createElement("div"); - hDiv.id = cId; - hDiv.setAttribute("vimbhint", "container"); - hDiv.appendChild(fragment); - if (doc.body) { - doc.body.appendChild(hDiv); - } - /* create the default style sheet */ - createStyle(doc); - - docs.push({ - doc: doc, - start: start, - end: hints.length - 1, - div: hDiv - }); - - /* recurse into any iframe or frame element */ - for (i = 0; i < win.frames.length; i++) { - var rect, - f = win.frames[i], - e = f.frameElement; - - if (isVisible(e)) { - rect = e.getBoundingClientRect(); - helper(f, { - left: Math.max(offsets.left - rect.left, 0), - right: Math.max(rect.right - offsets.right, 0), - top: Math.max(offsets.top - rect.top, 0), - bottom: Math.max(rect.bottom - offsets.bottom, 0) - }); - } - } - } - - helper(window); - } - - function show(fireLast) { - var i, hint, newIdx, - n = 1, - matcher = getMatcher(filterText), - str = getHintString(filterNum); - - if (config.hintNumSameLength) { - /* get number of hints to be shown */ - var hintCount = 0; - for (i = 0; i < hints.length; i++) { - if (matcher(hints[i].text)) { - hintCount++; - } - } - /* increase starting point of hint numbers until there are */ - /* enough available numbers */ - var len = config.hintKeys.length; - while (n * (len - 1) < hintCount) { - n *= len; - } - } - - /* clear the array of valid hints */ - validHints = []; - for (i = 0; i < hints.length; i++) { - hint = hints[i]; - /* hide hints not matching the filter text */ - if (!matcher(hint.text)) { - hint.hide(); - } else { - /* assign the new hint number/letters as label to the hint */ - hint.num = getHintString(n++); - /* check for number filter */ - if (!filterNum || 0 === hint.num.indexOf(str)) { - hint.show(); - validHints.push(hint); - } else { - hint.hide(); - } - } - } - if (fireLast && config.followLast && validHints.length <= 1) { - focusHint(0); - return fire(); - } - - /* if the previous active hint isn't valid set focus to first */ - if (!activeHint || validHints.indexOf(activeHint) < 0) { - return focusHint(0); - } - } - - /* Returns a validator method to check if the hint elements text matches */ - /* the given filter text. */ - function getMatcher(text) { - var tokens = text.toLowerCase().split(/\s+/); - return function (itemText) { - itemText = itemText.toLowerCase(); - return tokens.every(function (token) { - return 0 <= itemText.indexOf(token); - }); - }; - } - - /* Retrun the hint string for a given number based on configured hintkeys */ - function getHintString(n) { - var res = [], - len = config.hintKeys.length; - do { - res.push(config.hintKeys[n % len]); - n = Math.floor(n / len); - } while (n > 0); - - return res.reverse().join(""); - } - - function getOffsets(doc) { - var body = doc.body || doc.documentElement, - style = body.style, - rect; - - if (style && /^(absolute|fixed|relative)$/.test(style.position)) { - rect = body.getClientRects()[0]; - return [-rect.left, -rect.top]; - } - 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 */ - if (idx < 0) { - idx = 0; - } - - if (back) { - if (--idx < 0) { - idx = validHints.length - 1; - } - } else { - if (++idx >= validHints.length) { - idx = 0; - } - } - return focusHint(idx); - } - - function fire() { - if (!activeHint) { - return "ERROR:"; - } - - var e = activeHint.e, - res; - - /* process form actions like focus toggling inputs */ - if (config.handleForm) { - res = handleForm(e); - } - - if (config.keepOpen) { - /* reset the filter number */ - filterNum = 0; - show(false); - } else { - clear(); - } - - return res || config.action(e); - } - - /* focus or toggle form fields */ - function handleForm(e) { - var tag = e.nodeName.toLowerCase(), - type = e.type || ""; - - if (tag === "input" || tag === "textarea" || tag === "select") { - if (type === "radio" || type === "checkbox") { - e.focus(); - click(e); - return "DONE:"; - } - if (type === "submit" || type === "reset" || type === "button" || type === "image") { - click(e); - return "DONE:"; - } - e.focus(); - return "INSERT:"; - } - if (tag === "iframe" || tag === "frame") { - e.focus(); - return "DONE:"; - } - } - - /* 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"); - } - /* 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 */ - function focusHint(newIdx) { - /* reset previous focused hint */ - if (activeHint) { - activeHint.e.classList.remove(fClass); - activeHint.label.classList.remove(fClass); - mouseEvent(activeHint.e, "mouseout"); - } - /* get the new active hint */ - if ((activeHint = validHints[newIdx])) { - 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) { - 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 - ); - e.dispatchEvent(evObj); - } - - /* retrieves the url of given element */ - function getSrc(e) { - return e.href || e.src || ""; - } - - function xpath(doc, expr) { - return doc.evaluate( - expr, doc, function (p) {return "http://www.w3.org/1999/xhtml";}, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null - ); - } - - /* 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++) { - frames.push(win.frames[i].frameElement); - } - return frames; - } - - /* the api */ - return { - init: function init(mode, keepOpen, maxHints, hintKeys, followLast, hintNumSameLength) { - var prop, - /* holds the xpaths for the different modes */ - xpathmap = { - otY: "//*[@href] | //*[@onclick or @tabindex or @class='lk' or @role='link' or @role='button'] | //input[not(@type='hidden' or @disabled or @readonly)] | //textarea[not(@disabled or @readonly)] | //button | //select", - e: "//input[not(@type) or @type='text'] | //textarea", - iI: "//img[@src]", - OpPsTxy: "//*[@href] | //img[@src and not(ancestor::a)] | //iframe[@src]" - }, - /* 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:";}, - eiIOpPsTxy: function(e) {return "DATA:" + getSrc(e);}, - Y: function(e) {return "DATA:" + (e.textContent || "");} - }; - - config = { - maxHints: maxHints, - keepOpen: keepOpen, - /* handle forms only useful when there are form fields in xpath */ - /* don't handle form for Y to allow to yank form filed content */ - /* instead of switching to input mode */ - handleForm: ("eot".indexOf(mode) >= 0), - hintKeys: hintKeys, - followLast: followLast, - hintNumSameLength: hintNumSameLength, - }; - for (prop in xpathmap) { - if (prop.indexOf(mode) >= 0) { - config["xpath"] = xpathmap[prop]; - break; - } - } - for (prop in actionmap) { - if (prop.indexOf(mode) >= 0) { - config["action"] = actionmap[prop]; - break; - } - } - - create(); - return show(true); - }, - filter: function filter(text) { - /* remove previously set number filters to make the filter */ - /* easier to understand for the users */ - filterNum = 0; - filterText = text || ""; - return show(true); - }, - update: function update(n) { - var pos, - keys = config.hintKeys; - /* delete last filter number digit */ - if (null === n && filterNum) { - filterNum = Math.floor(filterNum / keys.length); - return show(false); - } - if ((pos = keys.indexOf(n)) >= 0) { - filterNum = filterNum * keys.length + pos; - return show(true); - } - return "ERROR:"; - }, - clear: clear, - fire: fire, - focus: focus, - /* not really hintings but uses similar logic */ - followLink: followLink, - incrementUri: incrementUri, - }; -})()); diff --git a/src/hints.js.h b/src/hints.js.h new file mode 100644 index 0000000..102d9c6 --- /dev/null +++ b/src/hints.js.h @@ -0,0 +1 @@ +#define HINTS_JS "Object.freeze((function(){'use strict';var hints=[],docs=[],validHints=[],activeHint,filterText=\"\",filterNum=0,cId=\"_hintContainer\",lClass=\"_hintLabel\",hClass=\"_hintElem\",fClass=\"_hintFocus\",config;function Hint(){this.hide=function(){this.label.style.display=\"none\";this.e.classList.remove(fClass);this.e.classList.remove(hClass);};this.show=function(){this.label.style.display=\"\";this.e.classList.add(hClass);var text=[];if(this.e instanceof HTMLInputElement){var type=this.e.type;if(type===\"checkbox\"){text.push(this.e.checked?\"☑\":\"☐\");}else if(type===\"radio\"){text.push(this.e.checked?\"⊙\":\"○\");}}if(this.showText&&this.text){text.push(this.text.substr(0,20));}this.label.innerText=this.num+(text.length?\": \"+text.join(\" \"):\"\");};}function clear(){var i,j,doc,e;for(i=0;i<docs.length;i++){doc=docs[i];var res=xpath(doc.doc,\"//*[contains(@vimbhint,'hint')]\");for(j=0;j<res.snapshotLength;j++){e=res.snapshotItem(j);e.removeAttribute(\"vimbhint\");e.classList.remove(fClass);e.classList.remove(hClass);}doc.div.parentNode.removeChild(doc.div);}docs=[];hints=[];validHints=[];filterText=\"\";filterNum=0;}function create(){var count=0;function helper(win,offsets){if(typeof win.document==\"undefined\"){return;}offsets=offsets||{left:0,right:0,top:0,bottom:0};offsets.right=win.innerWidth-offsets.right;offsets.bottom=win.innerHeight-offsets.bottom;function isVisible(e){if(typeof e==\"undefined\"){return false;}var rect=e.getBoundingClientRect();if(!rect||rect.top>offsets.bottom||rect.bottom<offsets.top||rect.left>offsets.right||rect.right<offsets.left){return false;}if((!rect.width||!rect.height)&&(e.textContent||!e.name)){var arr=Array.prototype.slice.call(e.childNodes);var check=function(e){return e instanceof Element&&e.style.float!=\"none\"&&isVisible(e);};if(!arr.some(check)){return false;}}var s=win.getComputedStyle(e,null);return s.display!==\"none\"&&s.visibility==\"visible\";}var doc=win.document,res=xpath(doc,config.xpath),labelTmpl=doc.createElement(\"span\"),e,i;labelTmpl.className=lClass;labelTmpl.setAttribute(\"vimbhint\",\"label\");var containerOffsets=getOffsets(doc),offsetX=containerOffsets[0],offsetY=containerOffsets[1],fragment=doc.createDocumentFragment(),rect,label,text,showText,start=hints.length;for(i=0;i<res.snapshotLength;i++){e=res.snapshotItem(i);if(!isVisible(e)){continue;}count++;rect=e.getBoundingClientRect();label=labelTmpl.cloneNode(false);label.setAttribute(\"style\",[\"display:none;\",\"left:\",Math.max((rect.left+offsetX),offsetX),\"px;\",\"top:\",Math.max((rect.top+offsetY),offsetY),\"px;\"].join(\"\"));text=\"\";showText=false;if(e instanceof HTMLImageElement){text=e.title||e.alt;showText=true;}else if(e.firstElementChild instanceof HTMLImageElement&&/^\\s*$/.test(e.textContent)){text=e.firstElementChild.title||e.firstElementChild.alt;showText=true;}else if(e instanceof HTMLInputElement){var type=e.type;if(type===\"image\"){text=e.alt||\"\";}else if(e.value&&type!==\"password\"){text=e.value;showText=(type===\"radio\"||type===\"checkbox\");}}else if(e instanceof HTMLSelectElement){if(e.selectedIndex>=0){text=e.item(e.selectedIndex).text;}}else{text=e.textContent;}fragment.appendChild(label);e.setAttribute(\"vimbhint\",\"hint\");hints.push({e:e,label:label,text:text,showText:showText,__proto__:new Hint});if(count>=config.maxHints){break;}}var hDiv=doc.createElement(\"div\");hDiv.id=cId;hDiv.setAttribute(\"vimbhint\",\"container\");hDiv.appendChild(fragment);if(doc.body){doc.body.appendChild(hDiv);}createStyle(doc);docs.push({doc:doc,start:start,end:hints.length-1,div:hDiv});for(i=0;i<win.frames.length;i++){var rect,f=win.frames[i],e=f.frameElement;if(isVisible(e)){rect=e.getBoundingClientRect();helper(f,{left:Math.max(offsets.left-rect.left,0),right:Math.max(rect.right-offsets.right,0),top:Math.max(offsets.top-rect.top,0),bottom:Math.max(rect.bottom-offsets.bottom,0)});}}}helper(window);}function show(fireLast){var i,hint,newIdx,n=1,matcher=getMatcher(filterText),str=getHintString(filterNum);if(config.hintNumSameLength){var hintCount=0;for(i=0;i<hints.length;i++){if(matcher(hints[i].text)){hintCount++;}}var len=config.hintKeys.length;while(n *(len-1)<hintCount){n *=len;}}validHints=[];for(i=0;i<hints.length;i++){hint=hints[i];if(!matcher(hint.text)){hint.hide();}else{hint.num=getHintString(n++);if(!filterNum||0===hint.num.indexOf(str)){hint.show();validHints.push(hint);}else{hint.hide();}}}if(fireLast&&config.followLast&&validHints.length<=1){focusHint(0);return fire();}if(!activeHint||validHints.indexOf(activeHint)<0){return focusHint(0);}}function getMatcher(text){var tokens=text.toLowerCase().split(/\\s+/);return function(itemText){itemText=itemText.toLowerCase();return tokens.every(function(token){return 0<=itemText.indexOf(token);});};}function getHintString(n){var res=[],len=config.hintKeys.length;do{res.push(config.hintKeys[n % len]);n=Math.floor(n / len);}while(n>0);return res.reverse().join(\"\");}function getOffsets(doc){var body=doc.body||doc.documentElement,style=body.style,rect;if(style&&/^(absolute|fixed|relative)$/.test(style.position)){rect=body.getClientRects()[0];return [-rect.left,-rect.top];}return [doc.defaultView.scrollX,doc.defaultView.scrollY];}function createStyle(doc){if(doc.hasStyle){return;}var e=doc.createElement(\"style\");e.innerHTML=\"" HINT_CSS "\";doc.head.appendChild(e);doc.hasStyle=true;}function focus(back){var idx=validHints.indexOf(activeHint);if(idx<0){idx=0;}if(back){if(--idx<0){idx=validHints.length-1;}}else{if(++idx>=validHints.length){idx=0;}}return focusHint(idx);}function fire(){if(!activeHint){return\"ERROR:\";}var e=activeHint.e,res;if(config.handleForm){res=handleForm(e);}if(config.keepOpen){filterNum=0;show(false);}else{clear();}return res||config.action(e);}function handleForm(e){var tag=e.nodeName.toLowerCase(),type=e.type||\"\";if(tag===\"input\"||tag===\"textarea\"||tag===\"select\"){if(type===\"radio\"||type===\"checkbox\"){e.focus();click(e);return\"DONE:\";}if(type===\"submit\"||type===\"reset\"||type===\"button\"||type===\"image\"){click(e);return\"DONE:\";}e.focus();return\"INSERT:\";}if(tag===\"iframe\"||tag===\"frame\"){e.focus();return\"DONE:\";}}function open(e,newWin){var oldTarget=e.target;if(newWin){e.target=\"_blank\";}else if(e.target===\"_blank\"){e.removeAttribute(\"target\");}click(e,newWin);e.target=oldTarget;}function focusHint(newIdx){if(activeHint){activeHint.e.classList.remove(fClass);activeHint.label.classList.remove(fClass);mouseEvent(activeHint.e,\"mouseout\");}if((activeHint=validHints[newIdx])){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){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);e.dispatchEvent(evObj);}function getSrc(e){return e.href||e.src||\"\";}function xpath(doc,expr){return doc.evaluate(expr,doc,function(p){return\"http://www.w3.org/1999/xhtml\";},XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);}function followLink(rel,patterns,count){function followFrame(frame){var i,p,reg,res=[],doc=frame.document,elems=[],all=doc.getElementsByTagName(\"a\");for(i=all.length-1;i>=0;i--){var s=doc.defaultView.getComputedStyle(all[i],null);if(s.display!==\"none\"&&s.visibility===\"visible\"){if(all[i].rel.toLowerCase()===rel){res.push(all[i]);}else{elems.push(all[i]);}}}for(p=0;p<patterns.length;p++){reg=patterns[p];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));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++){frames.push(win.frames[i].frameElement);}return frames;}return{init:function init(mode,keepOpen,maxHints,hintKeys,followLast,hintNumSameLength){var prop,xpathmap={otY:\"//*[@href]|//*[@onclick or @tabindex or @class='lk'or @role='link'or @role='button']|//input[not(@type='hidden'or @disabled or @readonly)]|//textarea[not(@disabled or @readonly)]|//button|//select\",e:\"//input[not(@type)or @type='text']|//textarea\",iI:\"//img[@src]\",OpPsTxy:\"//*[@href]|//img[@src and not(ancestor::a)]|//iframe[@src]\"},actionmap={o:function(e){open(e,false);return\"DONE:\";},t:function(e){open(e,true);return\"DONE:\";},eiIOpPsTxy:function(e){return\"DATA:\"+getSrc(e);},Y:function(e){return\"DATA:\"+(e.textContent||\"\");}};config={maxHints:maxHints,keepOpen:keepOpen,handleForm:(\"eot\".indexOf(mode)>=0),hintKeys:hintKeys,followLast:followLast,hintNumSameLength:hintNumSameLength,};for(prop in xpathmap){if(prop.indexOf(mode)>=0){config[\"xpath\"]=xpathmap[prop];break;}}for(prop in actionmap){if(prop.indexOf(mode)>=0){config[\"action\"]=actionmap[prop];break;}}create();return show(true);},filter:function filter(text){filterNum=0;filterText=text||\"\";return show(true);},update:function update(n){var pos,keys=config.hintKeys;if(null===n&&filterNum){filterNum=Math.floor(filterNum / keys.length);return show(false);}if((pos=keys.indexOf(n))>=0){filterNum=filterNum * keys.length+pos;return show(true);}return\"ERROR:\";},clear:clear,fire:fire,focus:focus,followLink:followLink,incrementUri:incrementUri,};})());" diff --git a/src/history.c b/src/history.c index ceeb5e0..fcd89fc 100644 --- a/src/history.c +++ b/src/history.c @@ -17,78 +17,79 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" #include <fcntl.h> +#include <glib.h> #include <sys/file.h> -#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 a686285..8709f5d 100644 --- a/src/history.h +++ b/src/history.h @@ -20,6 +20,10 @@ #ifndef _HISTORY_H #define _HISTORY_H +#include <glib.h> + +#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 00b25a1..0000000 --- a/src/hsts.c +++ /dev/null @@ -1,458 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <fcntl.h> -#include <sys/file.h> -#include <string.h> -#include <glib-object.h> -#include <libsoup/soup.h> - -#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 c56b2c7..0000000 --- a/src/hsts.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <glib-object.h> -#include <libsoup/soup.h> - -#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 5292b4e..b5381ff 100644 --- a/src/input.c +++ b/src/input.c @@ -17,62 +17,57 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include <glib.h> #include <glib/gstdio.h> -#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; -} 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 --"); } /** * Called when the input mode is left. */ -void input_leave(void) +void input_leave(Client *c) { - vb_update_mode_label(""); + webkit_web_view_run_javascript(c->webview, "document.activeElement.blur();", NULL, NULL, 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,106 +75,31 @@ 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; - int argc; - GPid pid; - gboolean success; - - editor_command = GET_CHAR("editor-command"); - if (!editor_command || !*editor_command) { - vb_echo(VB_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; - } - - text = dom_editable_element_get_value(active); - if (!text) { - return RESULT_ERROR; - } - - if (!util_create_tmp_file(text, &file_path)) { - return RESULT_ERROR; - } - - /* spawn editor */ - char* command = g_strdup_printf(editor_command, file_path); - if (!g_shell_parse_argv(command, &argc, &argv, NULL)) { - g_critical("Could not parse editor-command '%s'", command); - g_free(command); - return RESULT_ERROR; - } - g_free(command); - - success = g_spawn_async( - NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &pid, NULL - ); - g_strfreev(argv); - - if (!success) { - unlink(file_path); - g_free(file_path); - g_warning("Could not spawn editor-command"); - return RESULT_ERROR; - } - - /* disable the active element */ - dom_editable_element_set_disable(active, true); - - EditorData *data = g_slice_new0(EditorData); - data->file = file_path; - data->element = active; - - g_child_watch_add(pid, (GChildWatchFunc)resume_editor, data); - + /* TODO should the editor be opened by the webextension or by the + * application? */ return RESULT_COMPLETE; } - -static void resume_editor(GPid pid, int status, EditorData *data) -{ - char *text; - if (status == 0) { - text = util_get_file_contents(data->file, NULL); - if (text) { - dom_editable_element_set_value(data->element, text); - } - g_free(text); - } - dom_editable_element_set_disable(data->element, false); - dom_give_focus(data->element); - - g_unlink(data->file); - g_free(data->file); - g_slice_free(EditorData, data); - g_spawn_close_pid(pid); -} diff --git a/src/input.h b/src/input.h index 8887abc..64d1d5c 100644 --- a/src/input.h +++ b/src/input.h @@ -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 0abac30..0000000 --- a/src/io.c +++ /dev/null @@ -1,170 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <errno.h> - -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), PROJECT, "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 @@ -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 <JavaScriptCore/JavaScript.h> +#include <glib.h> +#include <webkit2/webkit2.h> - 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; -} @@ -20,16 +20,8 @@ #ifndef _JS_H #define _JS_H -#include "main.h" +#include <webkit2/webkit2.h> -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 f849e85..0000000 --- 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 "" @@ -17,138 +17,76 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" -#include <stdio.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <gtk/gtkx.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> #include <sys/stat.h> #include <unistd.h> -#include <gdk/gdkx.h> -#include "main.h" -#include "util.h" -#include "command.h" -#include "setting.h" +#include <webkit2/webkit2.h> + +#include "ascii.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 "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 "scripts/scripts.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); +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_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data); +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 void on_window_destroy(GtkWidget *window, Client *c); +static gboolean quit(Client *c); +static void register_cleanup(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); + +struct Vimb vb; -/* 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); /** - * Creates a new mode with given callback functions. + * Write text to the inpubox if this isn't focused. */ -void vb_add_mode(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)free_mode); - } - g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new); -} - -void vb_echo_force(const MessageType type, gboolean hide, const char *error, ...) +void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...) { char *buffer; va_list args; @@ -157,11 +95,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 +112,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 +141,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 +160,69 @@ 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) +/** + * Retrieves the content of the command line. + * Returned string must be freed with g_free. + */ +char *vb_input_get_text(Client *c) { - VbResult res; - static gboolean ctrl_v = false; - - 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; - } + GtkTextIter start, end; - 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; - } - return RESULT_ERROR; -} - -static void input_print(gboolean force, const MessageType type, gboolean hide, - const char *message) -{ - 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; - } - - /* 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 +231,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,1548 +245,964 @@ 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; - - /* memory allocation */ - char **cmd = g_malloc_n( - 3 /* basename + uri + ending NULL */ - + (vb.embed ? 2 : 0) - + (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 *) - ); - - /* build commandline */ - cmd[i++] = argv0; - if (vb.embed) { - char xid[64]; - snprintf(xid, LENGTH(xid), "%u", (int)vb.embed); - cmd[i++] = "-e"; - cmd[i++] = xid; - } - 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; - - /* spawn a new browser instance */ - g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); - - /* 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); + 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); + /* Load the uri into the new client. */ + webkit_web_view_load_uri(newclient->webview, uri); + set_title(c, uri); } g_free(uri); - return true; -} - -gboolean vb_set_clipboard(const Arg *arg) -{ - gboolean result = false; - if (!arg->s) { - return result; - } - - 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; - } - - return result; -} - -void vb_set_widget_font(GtkWidget *widget, const VbColor *fg, const VbColor *bg, PangoFontDescription *font) -{ - 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); + return TRUE; } -#ifdef FEATURE_WGET_PROGRESS_BAR -static void wget_bar(int len, int progress, char *string) +/** + * 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) { - int i, state; + 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; - state = progress * len / 100; - for (i = 0; i < state; i++) { - string[i] = PROGRESS_BAR[0]; - } - string[i++] = PROGRESS_BAR[1]; - for (; i < len; i++) { - string[i] = PROGRESS_BAR[2]; + /* 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); } - string[i] = '\0'; + g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new); } -#endif -void vb_update_statusbar() +VbResult vb_mode_handle_key(Client *c, int key) { - int max, val, num; - GString *status; + VbResult res; - if (!gtk_widget_get_visible(GTK_WIDGET(vb.gui.statusbar.box))) { - return; - } + if (c->state.ctrlv) { + c->state.processed_key = FALSE; + c->state.ctrlv = FALSE; - 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"); + return RESULT_COMPLETE; } + if (c->mode->id != 'p' && key == CTRL('V')) { + c->mode->flags |= FLAG_NOMAP; + c->state.ctrlv = TRUE; -#ifdef FEATURE_SEARCH_HIGHLIGHT - /* show the number of matches search results */ - if (vb.state.search_matches) { - g_string_append_printf(status, " (%d)", vb.state.search_matches); + return RESULT_MORE; } -#endif - /* show load status of page or the downloads */ - if (vb.state.progress != 100) { -#ifdef FEATURE_WGET_PROGRESS_BAR - char bar[PROGRESS_BAR_LEN + 1]; - wget_bar(PROGRESS_BAR_LEN, vb.state.progress, bar); - g_string_append_printf(status, " [%s]", bar); + 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 - g_string_append_printf(status, " [%i%%]", vb.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)); - - if (max == 0) { - g_string_append(status, " All"); - } else if (val == 0) { - g_string_append(status, " Top"); - } else if (val >= 100) { - g_string_append(status, " Bot"); - } else { - g_string_append_printf(status, " %d%%", val); - } - - 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] - ); + res = c->mode->keypress(c, key); #endif -} - -void vb_update_input_style(void) -{ - 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] - ); -} - -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); - } -#endif /* FEATURE_PROFILE_INDICATOR */ - - g_string_append_printf(str, "%s", uri); - -#ifdef FEATURE_HISTORY_INDICATOR - gboolean back, fwd; - - back = webkit_web_view_can_go_back(gui->webview); - fwd = webkit_web_view_can_go_forward(gui->webview); - - /* show history indicator only if there is something to show */ - if (back || fwd) { - g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+"); + return res; } -#endif /* FEATURE_HISTORY_INDICATOR */ - - 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) */ + return RESULT_ERROR; } -void vb_update_mode_label(const char *label) +/** + * 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) { - if (GET_BOOL("input-autohide")) { + if (c->config.input_autohide) { /* if the inputbox is potentially not shown write mode into statusbar */ - gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.mode), label); + gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label); } else { - vb_echo(VB_MSG_NORMAL, false, "%s", label); + vb_echo(c, MSG_NORMAL, FALSE, "%s", label); } + gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label); } -void vb_quit(gboolean force) +/** + * Close the given client instances window. + */ +void vb_quit(Client *c, gboolean force) { +#if 0 /* TODO don't quit on running downloads */ /* 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"); + if (!force && c->state.downloads) { + vb_echo_force(c, VB_MSG_ERROR, TRUE, "Can't quit: there are running downloads"); return; } - - webkit_web_view_stop_loading(vb.gui.webview); - - /* 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); - } - - gtk_main_quit(); -} - -static gboolean hide_message() -{ - input_print(false, VB_MSG_NORMAL, false, ""); - - return false; +#endif + /* 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); } /** - * Process input changed event on current active mode. + * Adds content to a named register. */ -static void buffer_changed_cb(GtkTextBuffer* buffer, gpointer data) +void vb_register_add(Client *c, char buf, const char *value) { - char *text; - GtkTextIter start, end; - /* don't observe changes in completion mode */ - if (vb.mode->flags & FLAG_COMPLETION) { + char *mark; + int idx; + + if (!c->state.enable_register || !buf) { 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); - vb.mode->input_changed(text); + /* 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; - g_free(text); + OVERWRITE_STRING(c->state.reg[idx], value); } } -#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); - - return false; -} -#else -static void context_menu_cb(WebKitWebView *view, GtkMenu *menu, gpointer data) +/** + * Lookup register entry by it's name. + */ +const char *vb_register_get(Client *c, char buf) { - 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); -} -#endif + char *mark; + int idx; -static void context_menu_activate_cb(GtkMenuItem *item, gpointer data) -{ -#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)); + /* 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; - if (!action) { - return; + return c->state.reg[idx]; } - 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}) - ); - } -#endif -} - -static void uri_change_cb(WebKitWebView *view, GParamSpec param_spec) -{ - set_uri(webkit_web_view_get_uri(view)); -} - -static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec) -{ - vb.state.progress = webkit_web_view_get_progress(view) * 100; - vb_update_statusbar(); - update_title(); -} - -static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec) -{ - 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(); + return NULL; } -static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) +void vb_statusbar_update(Client *c) { - 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'); - } - - dom_install_focus_blur_callbacks(webkit_web_frame_get_dom_document(frame)); - vb.state.done_loading_page = false; - - break; - - 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(); - - if (strncmp(uri, "about:", 6)) { - history_add(HISTORY_URL, uri, webkit_web_view_get_title(view)); - } - break; - - 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); - } 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; - } -} + int num; + GString *status; -static void webview_request_starting_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitWebResource *res, WebKitNetworkRequest *req, - WebKitNetworkResponse *resp, gpointer data) -{ - 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"); + if (!gtk_widget_get_visible(GTK_WIDGET(c->statusbar.box))) { return; } - msg = webkit_network_request_get_message(req); - if (!msg) { - return; + status = g_string_new(""); + /* show the active downloads */ + if (c->state.downloads) { + num = g_list_length(c->state.downloads); + g_string_append_printf(status, " %d %s", num, num == 1 ? "download" : "downloads"); } - if (!vb.config.headers) { - return; + /* show the number of matches search results */ + if (c->state.search.matches) { + g_string_append_printf(status, " (%d)", c->state.search.matches); } - /* 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); + /* show load status of page or the downloads */ + if (c->state.progress != 100) { +#ifdef FEATURE_WGET_PROGRESS_BAR + char bar[PROGRESS_BAR_LEN + 1]; + 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%%]", c->state.progress); +#endif } -} - -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) -{ - vb.state.window_has_focus = true; - return false; -} - -static void destroy_window_cb(GtkWidget *widget) -{ - vb_quit(true); -} -static void scroll_cb(GtkAdjustment *adjustment) -{ - vb_update_statusbar(); -} - -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'); - - return false; -} - -static WebKitWebView *inspector_new(WebKitWebInspector *inspector, WebKitWebView *webview) -{ - return WEBKIT_WEB_VIEW(webkit_web_view_new()); -} - -static gboolean inspector_show(WebKitWebInspector *inspector) -{ - WebKitWebView *webview; - int height; - - if (vb.state.is_inspecting) { - return false; + /* show the scroll status */ + if (c->state.scroll_max == 0) { + g_string_append(status, " All"); + } else if (c->state.scroll_percent == 0) { + g_string_append(status, " Top"); + } else if (c->state.scroll_percent == 100) { + g_string_append(status, " Bot"); + } else { + g_string_append_printf(status, " %d%%", c->state.scroll_percent); } - webview = webkit_web_inspector_get_web_view(inspector); - - /* 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); - - 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; + gtk_label_set_text(GTK_LABEL(c->statusbar.right), status->str); + g_string_free(status, TRUE); } -static gboolean inspector_close(WebKitWebInspector *inspector) +/** + * 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) { - WebKitWebView *webview; + Client *p; + webkit_web_view_stop_loading(c->webview); + gtk_widget_destroy(c->window); - if (!vb.state.is_inspecting) { - return false; + /* 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; } - 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; + completion_cleanup(c); + map_cleanup(c); + register_cleanup(c); - return true; -} + g_slice_free(Client, c); -static void inspector_finished(WebKitWebInspector *inspector) -{ - g_free(vb.gui.inspector); -} - -static void set_status(const StatusType status) -{ - 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 there are no clients - quit the main loop */ + if (!vb.clients) { + gtk_main_quit(); } } -static void init_core(void) +/** + * 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) { - Gui *gui = &vb.gui; + Client *c; char *xid; + GtkWidget *box; + + /* create the client */ + c = g_slice_new0(Client); + c->state.progress = 100; if (vb.embed) { - gui->window = gtk_plug_new(vb.embed); - xid = g_strdup_printf("%u", (int)vb.embed); + c->window = gtk_plug_new(vb.embed); + xid = g_strdup_printf("%d", (int)vb.embed); } 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)); + c->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_role(GTK_WINDOW(c->window), PROJECT_UCFIRST); + gtk_widget_realize(GTK_WIDGET(c->window)); - /* set the x window id to env */ - xid = g_strdup_printf("%d", (int)GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(gui->window)))); + xid = g_strdup_printf("%d", + (int)GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(c->window)))); } - g_setenv("VIMB_XID", xid, true); - g_free(xid); - - 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 + completion_init(c); + map_init(c); - /* 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(); + g_object_connect( + G_OBJECT(c->window), + "signal::destroy", G_CALLBACK(on_window_destroy), 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)); + /* 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. */ + GtkCssProvider* provider = gtk_css_provider_get_default(); + gtk_css_provider_load_from_data(provider, GUI_STYLE, -1, NULL); + gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)), + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_style_context_add_provider(gtk_widget_get_style_context(c->input), + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + /* set the x window id to env */ + g_setenv("VIMB_XID", xid, TRUE); + g_free(xid); - setup_signals(); + /* initialize the settings */ + setting_init(c); - /* enter normal mode */ - vb_enter('n'); + /* start client in normal mode */ + vb_enter(c, 'n'); - /* make sure the main window and all its contents are visible */ - gtk_widget_show_all(gui->window); + gtk_widget_show_all(c->window); - /* read the config file */ - ex_run_file(vb.files[FILES_CONFIG]); + /* Prepend the new client to the queue of clients. */ + c->next = vb.clients; + vb.clients = c; - /* initially apply input style */ - vb_update_input_style(); + return c; +} - if (vb.config.kioskmode) { - WebKitWebSettings *setting = webkit_web_view_get_settings(gui->webview); +/** + * Callback that clear the input box after a timeout if this was set on + * input_print. + */ +static gboolean input_clear(Client *c) +{ + input_print(c, FALSE, MSG_NORMAL, FALSE, ""); - /* 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); + return FALSE; +} - /* disable context menu */ - g_object_set(G_OBJECT(setting), "enable-default-context-menu", false, NULL); +/** + * 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) +{ + /* don't print message if the input is focussed */ + if (!force && gtk_widget_is_focus(GTK_WIDGET(c->input))) { + return; } - vb.config.default_zoom = 1.0; - -#ifdef FEATURE_HIGH_DPI - /* 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); + /* apply input style only if the message type was changed */ + if (type != c->state.input_type) { + c->state.input_type = type; + } + 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 marks_clear(void) +/** + * Reinitializes or clears the set page marks. + */ +static void marks_clear(Client *c) { int i; /* init empty marks array */ - for (i = 0; i < VB_MARK_SIZE; i++) { - vb.state.marks[i] = -1; + for (i = 0; i < MARK_SIZE; i++) { + c->state.marks[i] = -1; } } -static void setup_signals() +/** + * Free the memory of given mode. This is used as destroy function of the + * modes hashmap. + */ +static void mode_free(Mode *mode) { - /* 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); + g_slice_free(Mode, mode); } -static void init_files(void) +/** + * Set the style of the statusbar. + */ +static void set_statusbar_style(Client *c, StatusType type) { - char *path = util_get_config_dir(vb.config.profile); + GtkStyleContext *ctx; + /* Do nothing if the new to set style is the same as the current. */ + if (type == c->state.status_type) { + return; + } - if (vb.config.file) { - char *rp = realpath(vb.config.file, NULL); - vb.files[FILES_CONFIG] = g_strdup(rp); - free(rp); + ctx = gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)); + + 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 { - vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL); - util_create_file_if_not_exists(vb.files[FILES_CONFIG]); + gtk_style_context_remove_class(ctx, "secure"); + gtk_style_context_remove_class(ctx, "unsecure"); } + c->state.status_type = type; +} -#ifdef FEATURE_COOKIE - vb.files[FILES_COOKIE] = g_build_filename(path, "cookies", NULL); - util_create_file_if_not_exists(vb.files[FILES_COOKIE]); -#endif +/** + * Update the window title of the main window. + */ +static void set_title(Client *c, const char *title) +{ + gtk_window_set_title(GTK_WINDOW(c->window), title); +} - vb.files[FILES_CLOSED] = g_build_filename(path, "closed", NULL); - util_create_file_if_not_exists(vb.files[FILES_CLOSED]); +/** + * 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) +{ + guint i = 0; + char xid[64]; + char *cmd[5]; - vb.files[FILES_HISTORY] = g_build_filename(path, "history", NULL); - util_create_file_if_not_exists(vb.files[FILES_HISTORY]); + cmd[i++] = vb.argv0; - vb.files[FILES_COMMAND] = g_build_filename(path, "command", NULL); - util_create_file_if_not_exists(vb.files[FILES_COMMAND]); + if (vb.embed && embed) { + cmd[i++] = "-e"; + snprintf(xid, LENGTH(xid), "%d", (int)vb.embed); + cmd[i++] = xid; + } + cmd[i++] = (char*)uri; + cmd[i++] = NULL; - vb.files[FILES_SEARCH] = g_build_filename(path, "search", NULL); - util_create_file_if_not_exists(vb.files[FILES_SEARCH]); + /* spawn a new browser instance */ + g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); +} - vb.files[FILES_BOOKMARK] = g_build_filename(path, "bookmark", NULL); - util_create_file_if_not_exists(vb.files[FILES_BOOKMARK]); +/** + * Callback for the web contexts initialize-web-extensions signal. + */ +static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data) +{ + char *name; + static guint ext_count = 0; + GVariant *vdata; -#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 + /* Setup the extension directory. */ + webkit_web_context_set_web_extensions_directory(webctx, EXTPREFIX); - vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL); + name = g_strdup_printf("%u-%u", getpid(), ++ext_count); + ext_proxy_init(name); - vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL); + vdata = g_variant_new("(s)", name); + webkit_web_context_set_web_extensions_initialization_user_data(webctx, vdata); - g_free(path); + g_free(name); } -static void session_init(void) +/** + * Callback for the webview close signal. + */ +static void on_webview_close(WebKitWebView *webview, 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 + client_destroy(c); } -static void session_cleanup(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) { -#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 -} + Client *new = client_new(webview); -static void register_init(void) -{ - memset(vb.state.reg, 0, sizeof(char*)); + return new->webview; } -void vb_register_add(char buf, const char *value) -{ - char *mark; - int idx; - - if (!vb.state.enable_register || !buf) { - return; - } +/** + * 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; + 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); + + if (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) { + if (webkit_navigation_action_get_mouse_button(a) == 2 + || (webkit_navigation_action_get_mouse_button(a) == 1 + && webkit_navigation_action_get_modifiers(a) & GDK_CONTROL_MASK)) { + webkit_policy_decision_ignore(dec); + spawn_new_instance(webkit_uri_request_get_uri(req), TRUE); + return TRUE; + } + } + return FALSE; - /* 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; + case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: + a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec)); + req = webkit_navigation_action_get_request(a); - OVERWRITE_STRING(vb.state.reg[idx], value); - } -} + /* 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; + } -const char *vb_register_get(char buf) -{ - char *mark; - int idx; + 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. */ + spawn_new_instance(webkit_uri_request_get_uri(req), TRUE); + return TRUE; + } + return FALSE; - /* 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; + case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: + req = webkit_response_policy_decision_get_request(WEBKIT_RESPONSE_POLICY_DECISION(dec)); + res = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(dec)); + status = webkit_uri_response_get_status_code(res); - return vb.state.reg[idx]; - } + if (!webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(dec)) + && (SOUP_STATUS_IS_SUCCESSFUL(status) || status == SOUP_STATUS_NONE)) { - return NULL; -} + webkit_policy_decision_download(dec); -static void register_cleanup(void) -{ - int i; - for (i = 0; i < VB_REG_SIZE; i++) { - if (vb.state.reg[i]) { - g_free(vb.state.reg[i]); - } - } -} + return TRUE; + } + return FALSE; -static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event) -{ - /* 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); - - 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; + default: + return FALSE; } - g_object_unref(result); - - return nopropagate; } -static gboolean new_window_policy_cb( - WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *navig, WebKitWebPolicyDecision *policy) +static void on_webview_load_changed(WebKitWebView *webview, + WebKitLoadEvent event, 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; -} + GTlsCertificateFlags tlsflags; + const char *uri; -static WebKitWebView *create_web_view_cb(WebKitWebView *view, WebKitWebFrame *frame) -{ - WebKitWebView *new = WEBKIT_WEB_VIEW(webkit_web_view_new()); + 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)); + break; - /* 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); + case WEBKIT_LOAD_REDIRECTED: + break; - return new; -} + 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); + } -static gboolean create_web_view_received_uri_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data) -{ - Arg a = {VB_TARGET_NEW, (char*)webkit_network_request_get_uri(request)}; - vb_load_uri(&a); + /* clear possible set marks */ + marks_clear(c); - /* destroy temporary webview */ - gtk_widget_destroy(GTK_WIDGET(view)); + break; - /* mark that we handled the signal */ - return true; + case WEBKIT_LOAD_FINISHED: + c->state.progress = 100; + break; + } } -static gboolean navigation_decision_requested_cb(WebKitWebView *view, - WebKitWebFrame *frame, WebKitNetworkRequest *request, - WebKitWebNavigationAction *action, WebKitWebPolicyDecision *policy, - gpointer data) +/** + * 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) { -#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); + char *msg; + const char *uri; - return true; + /* 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 false; } -static void onload_event_cb(WebKitWebView *view, WebKitWebFrame *frame, - gpointer user_data) +/** + * 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) { - Document *doc = webkit_web_frame_get_dom_document(frame); - dom_check_auto_insert(doc); - vb.state.done_loading_page = true; + c->state.progress = webkit_web_view_get_estimated_load_progress(webview) * 100; + vb_statusbar_update(c); } -static void hover_link_cb(WebKitWebView *webview, const char *title, const char *link) +/** + * 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) { - 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); - } else { - /* If there is no previous uri use the current uri from webview. */ - set_uri(webkit_web_view_get_uri(webview)); - } + set_title(c, webkit_web_view_get_title(webview)); } -static void title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, const char *title) +/** + * 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) { - set_title(title); + OVERWRITE_STRING(c->state.uri, webkit_web_view_get_uri(c->webview)); + update_urlbar(c); + g_setenv("VIMB_URI", c->state.uri, TRUE); } -static void update_title(void) +/** + * 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) { -#ifdef FEATURE_TITLE_PROGRESS - /* show load status of page or the downloads */ - if (vb.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); - g_free(title); - return; - } -#endif - if (vb.state.title) { - gtk_window_set_title(GTK_WINDOW(vb.gui.window), vb.state.title); - } + gtk_widget_show(GTK_WIDGET(webview)); } -static void set_uri(const char *uri) +/** + * Callback for the webview web-process-crashed signal. + */ +static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c) { - OVERWRITE_STRING(vb.state.uri, uri); - g_setenv("VIMB_URI", uri, true); - vb_update_urlbar(uri); + g_warning("Webview Crashed on %s", webkit_web_view_get_uri(webview)); + return TRUE; } -static void set_title(const char *title) +/** + * Callback for the window destroy signal. + * Destroys the client that is associated to the window. + */ +static void on_window_destroy(GtkWidget *window, Client *c) { - OVERWRITE_STRING(vb.state.title, title); - update_title(); - g_setenv("VIMB_TITLE", title ? title : "", true); + client_destroy(c); } -static gboolean mimetype_decision_cb(WebKitWebView *webview, - WebKitWebFrame *frame, WebKitNetworkRequest *request, char *mime_type, - WebKitWebPolicyDecision *decision) +/** + * Callback for to quit given client as idle event source. + */ +static gboolean quit(Client *c) { - 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)) { + /* Destroy the main window to tirgger the destruction of the client. */ + gtk_widget_destroy(c->window); - 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; + /* Remove this from the list of event sources. */ + return FALSE; } -gboolean vb_download(WebKitWebView *view, WebKitDownload *download, const char *path) +/** + * Free the register contents memory. + */ +static void register_cleanup(Client *c) { - 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); - - /* 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"; + int i; + for (i = 0; i < REG_SIZE; i++) { + if (c->state.reg[i]) { + g_free(c->state.reg[i]); } - file = util_build_path(path, vb.config.download_dir); - } - -#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); - - return true; } } -void vb_download_internal(WebKitWebView *view, WebKitDownload *download, const char *file) +/** + * 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) { - char *uri; - guint64 size; - WebKitDownloadStatus status; +#if !defined(FEATURE_HISTORY_INDICATOR) + /* if only the uri is shown - write it like it is on the label */ + gtk_label_set_text(GTK_LABEL(c->statusbar.left), c->state.uri); +#else + GString *str = g_string_new(c->state.uri); - /* 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); +#ifdef FEATURE_HISTORY_INDICATOR + gboolean back, fwd; - size = webkit_download_get_total_size(download); - if (size > 0) { - vb_echo(VB_MSG_NORMAL, false, "Download %s [%uB] started ...", file, size); - } else { - vb_echo(VB_MSG_NORMAL, false, "Download %s started ...", file); - } + back = webkit_web_view_can_go_back(c->webview); + fwd = webkit_web_view_can_go_forward(c->webview); - status = webkit_download_get_status(download); - if (status == WEBKIT_DOWNLOAD_STATUS_CREATED) { - webkit_download_start(download); + /* 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 */ - /* prepend the download to the download list */ - vb.state.downloads = g_list_prepend(vb.state.downloads, download); - - /* 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); - - vb_update_statusbar(); + gtk_label_set_text(GTK_LABEL(c->statusbar.left), str->str); + g_string_free(str, TRUE); +#endif /* !defined(FEATURE_HISTORY_INDICATOR) */ } -void vb_download_external(WebKitWebView *view, WebKitDownload *download, const char *file) +#ifdef FREE_ON_QUIT +/** + * Free memory of the whole application. + */ +static void vimb_cleanup(void) { - 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); - } + int i; - if (!user_agent) { - WebKitWebSettings *setting = webkit_web_view_get_settings(view); - g_object_get(G_OBJECT(setting), "user-agent", &user_agent, NULL); + while (vb.clients) { + client_destroy(vb.clients); } - 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); + /* free memory of other components */ + util_cleanup(); - return; - } - g_free(cmd); - - 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"); + for (i = 0; i < FILES_LAST; i++) { + if (vb.files[i]) { + g_free(vb.files[i]); } - } 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); } +#endif -static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec) +/** + * Setup resources used on application scope. + */ +static void vimb_setup(void) { - WebKitDownloadStatus status = webkit_download_get_status(download); + WebKitWebContext *ctx; + WebKitCookieManager *cm; + char *path; - if (status == WEBKIT_DOWNLOAD_STATUS_STARTED || status == WEBKIT_DOWNLOAD_STATUS_CREATED) { - return; - } + /* prepare the file pathes */ + path = util_get_config_dir(); - 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); + if (vb.configfile) { + char *rp = realpath(vb.configfile, NULL); + vb.files[FILES_CONFIG] = g_strdup(rp); + free(rp); } 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); - } + 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", FALSE); + vb.files[FILES_COOKIE] = util_get_filepath(path, "cookies", FALSE); + 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", FALSE); + vb.files[FILES_COMMAND] = util_get_filepath(path, "command", FALSE); + vb.files[FILES_HSTS] = util_get_filepath(path, "hsts", FALSE); + vb.files[FILES_BOOKMARK] = util_get_filepath(path, "bookmark", FALSE); + vb.files[FILES_QUEUE] = util_get_filepath(path, "queue", FALSE); + vb.files[FILES_SEARCH] = util_get_filepath(path, "search", FALSE); + g_free(path); - /* remove the download from the list */ - vb.state.downloads = g_list_remove(vb.state.downloads, download); + /* 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_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); -static void read_from_stdin(void) -{ - /* read content from stdin */ - GIOChannel *ch = g_io_channel_unix_new(fileno(stdin)); - gchar *buf = NULL; - GError *err = NULL; - gsize len; - - 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 { - webkit_web_view_load_string(vb.gui.webview, buf, "text/html", NULL, "(stdin)"); + /* 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); } - 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); - arh_run(vb.config.autoresponseheader, uri, msg); + /* TODO move to settings.c */ + webkit_web_context_set_tls_errors_policy(ctx, TRUE ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE); - g_free(uri); + /* 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); } -#endif /** - * Free some memory when vimb is quit. + * Factory to create a new webview. + * + * @webview: Relates webview or NULL. If given a related webview is + * generated. */ -static void vb_cleanup(void) +static WebKitWebView *webview_new(Client *c, WebKitWebView *webview) { + WebKitWebView *new; + WebKitUserContentManager *ucm; + WebKitUserScript *script; + char *js = NULL; - 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); - - g_slist_free_full(vb.config.cmdargs, g_free); - - for (int i = 0; i < FILES_LAST; i++) { - g_free(vb.files[i]); - vb.files[i] = NULL; + /* 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 { + ucm = webkit_user_content_manager_new(); + new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(ucm)); } -} -static void cleanup_modes(void) -{ - if (vb.modes) { - g_hash_table_destroy(vb.modes); - vb.modes = NULL; - vb.mode = NULL; + 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 + ); + + /* Inject the user script file. */ + if (g_file_get_contents(vb.files[FILES_SCRIPT], &js, NULL, NULL)) { + script = webkit_user_script_new(js, + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL); + webkit_user_content_manager_add_script(ucm, script); + webkit_user_script_unref(script); } -} -static void free_mode(Mode *mode) -{ - g_slice_free(Mode, mode); + return new; } -static gboolean autocmdOptionArgFunc(const gchar *option_name, const gchar *value, gpointer data, GError **error) +int main(int argc, char* argv[]) { - vb.config.cmdargs = g_slist_append(vb.config.cmdargs, g_strdup(value)); - return TRUE; -} + Client *c; + GError *err = NULL; + char *pidstr, *winid = NULL; + gboolean ver = FALSE; -int main(int argc, char *argv[]) -{ - static char *winid = NULL; - static gboolean ver = false; -#ifdef FEATURE_SOCKET - static gboolean dump = false; -#endif - static GError *err; - - 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}, - {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL}, -#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}, {"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/%s\n", PROJECT, VERSION); - return EXIT_SUCCESS; - } + fprintf(stdout, "%s, version %s\n\n", PROJECT, VERSION); + fprintf(stdout, "Copyright © 2012 - 2016 Daniel Carl <danielcarl@gmx.de>\n"); + fprintf(stdout, "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\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"); - /* save vimb basename */ - argv0 = argv[0]; - - if (winid) { - vb.embed = strtol(winid, NULL, 0); + return EXIT_SUCCESS; } - 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); if (argc <= 1) { - /* open configured home page if no uri was given */ - vb_load_uri(&(Arg){VB_TARGET_CURRENT, NULL}); - } else if (!strcmp(argv[argc - 1], "-")) { - /* read from stdin if uri is - */ - read_from_stdin(); + vb_load_uri(c, &(Arg){TARGET_CURRENT, NULL}); } 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; } @@ -20,31 +20,24 @@ #ifndef _MAIN_H #define _MAIN_H -#include <limits.h> -#include <stdlib.h> -#include <string.h> -#include <webkit/webkit.h> -#include <JavaScriptCore/JavaScript.h> #include <fcntl.h> -#include <stdio.h> -#ifdef HAS_GTK3 -#include <gdk/gdkx.h> #include <gtk/gtkx.h> -#endif -#include "config.h" -#ifdef FEATURE_HSTS -#include "hsts.h" -#endif +#include <stdio.h> +#include <webkit2/webkit2.h> -/* 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);} @@ -58,174 +51,88 @@ #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_HSTS, 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; @@ -241,16 +148,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 *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 acitve search */ + short direction; /* last direction 1 forward, -1 backward */ + int matches; /* number of matches 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 */ + gboolean enable_register; /* indicates if registers are filled */ +}; + +struct Mode { char id; ModeTransitionFunc enter; /* is called if the mode is entered */ ModeTransitionFunc leave; /* is called if the mode is left */ @@ -261,153 +200,79 @@ typedef struct { #define FLAG_COMPLETION 0x0004 /* marks active completion submode */ #define FLAG_PASSTHROUGH 0x0008 /* don't handle any other keybind than <esc> */ 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 */ -#ifdef FEATURE_SEARCH_HIGHLIGHT - int search_matches; /* number of matches search results */ -#endif - 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; + GtkWidget *mode, *left, *right, *cmd; +}; -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; +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; + GtkWidget *window, *input; + WebKitWebView *webview; + guint64 page_id; /* page id of the webview */ + GtkTextBuffer *buffer; + GDBusProxy *dbusproxy; + struct { + /* TODO split in global setting definitions and set values on a per + * client base. */ + GHashTable *settings; + guint scrollstep; + gboolean input_autohide; + /* completion */ + GdkRGBA comp_fg[COMP_LAST]; + GdkRGBA comp_bg[COMP_LAST]; + PangoFontDescription *comp_font; + } 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; +}; -typedef struct { - Gui gui; - State state; - - char *files[FILES_LAST]; - Mode *mode; - Config config; - VbStyle style; - SoupSession *session; -#ifdef HAS_GTK3 - Window embed; -#else - GdkNativeWindow embed; -#endif - GHashTable *modes; /* all available browser main modes */ -} VbCore; +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]; + struct { + guint history_max; + } config; +}; -/* main object */ -extern VbCore core; -/* functions */ -void vb_add_mode(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, +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); +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); +void 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); #endif /* end of include guard: _MAIN_H */ @@ -1,4 +1,6 @@ /** + * vimb - a webkit based vim like browser. + * * Copyright (C) 2012-2015 Daniel Carl * * This program is free software: you can redistribute it and/or modify @@ -15,47 +17,37 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include <gdk/gdkkeysyms.h> #include <gdk/gdkkeysyms-compat.h> +#include <gdk/gdkkeysyms.h> +#include <string.h> + +#include "ascii.h" #include "config.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; @@ -112,69 +104,21 @@ static struct { {"<F12>", 5, CSI_STR "F2", 3}, }; -void map_init(void) +void map_init(Client *c) { - map.queue = g_string_sized_new(50); + c->map.queue = g_string_sized_new(50); + /* TODO move this to settings */ + c->map.timeoutlen = 1000; } -void map_cleanup(void) +void map_cleanup(Client *c) { - if (map.list) { - g_slist_free_full(map.list, (GDestroyNotify)free_map); - } - g_string_free(map.queue, true); -} - -/** - * 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) -{ - 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 <S-Einf> to copy clipboard content into inputbox */ - return false; + if (c->map.queue) { + g_string_free(c->map.queue, TRUE); } - - /* set flag to notify that the key was typed by the user */ - vb.state.typed = true; - vb.state.processed_key = true; - - map_handle_keys(string, len, true); - - /* reset the typed flag */ - vb.state.typed = false; - - return vb.state.processed_key; } /** @@ -182,7 +126,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; @@ -190,99 +134,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 <Left> and * <Right> 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 */ @@ -290,7 +234,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; @@ -303,7 +247,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. */ @@ -312,30 +256,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; } } @@ -346,21 +290,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 <C-T> 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; @@ -370,81 +314,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 <S-Einf> 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 <cr> -> \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 <C-X> */ + 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); } /** @@ -505,6 +554,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) { @@ -545,122 +664,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 <C-X> */ - 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 <cr> -> \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); - - /* 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); -} @@ -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 54548a1..2980ecf 100644 --- a/src/normal.c +++ b/src/normal.c @@ -18,14 +18,15 @@ */ #include <gdk/gdkkeysyms.h> +#include <string.h> + +#include "ascii.h" +#include "command.h" #include "config.h" +#include "ext-proxy.h" #include "main.h" #include "normal.h" -#include "ascii.h" -#include "command.h" -#include "hints.h" -#include "dom.h" -#include "history.h" +#include "scripts/scripts.h" #include "util.h" typedef enum { @@ -47,32 +48,31 @@ 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_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_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; @@ -171,7 +171,7 @@ static struct { /* [ 0x5b */ {normal_prevnext}, /* \ 0x5c */ {NULL}, /* ] 0x5d */ {normal_prevnext}, -/* ^ 0x5e */ {normal_scroll}, +/* ^ 0x5e */ {NULL}, /* _ 0x5f */ {NULL}, /* ` 0x60 */ {NULL}, /* a 0x61 */ {NULL}, @@ -207,34 +207,33 @@ 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); + webkit_web_view_run_javascript(c->webview, "document.activeElement.blur();", NULL, NULL, NULL); /* 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)); + /* TODO clear possible active hints */ } /** * 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})); } /** * 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) { @@ -248,10 +247,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; @@ -264,7 +263,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; } @@ -276,7 +275,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 { @@ -292,10 +291,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; } @@ -312,51 +311,48 @@ 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(""); + 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})); 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 @@ -396,73 +392,68 @@ 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) -{ - dom_focus_input(webkit_web_view_get_dom_document(vb.gui.webview)); - return RESULT_COMPLETE; -} - -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}; @@ -471,92 +462,75 @@ 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_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(INCREMENT_URI_NUMBER, info->key == CTRL('A') ? count : -count); + webkit_web_view_run_javascript(c->webview, js, NULL, NULL, 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_input_set_text(c, 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': @@ -575,23 +549,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; @@ -600,155 +574,155 @@ 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; + char *file; - uri = util_file_pop_line(vb.files[FILES_CLOSED], NULL); - if (!uri) { - return RESULT_ERROR; - } + file = g_build_filename(util_get_config_dir(), FILE_CLOSED, NULL); + a.i = info->key == 'U' ? TARGET_NEW : TARGET_CURRENT; + a.s = util_get_file_contents(file, NULL); + g_free(file); - /* 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 need hinting to be available */ 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})); -#endif + /* TODO run next uri from queu and remove it from */ 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; + int x = 0, y = 0, page_height = 0, count = info->count ? info->count : 1; + char *js; + GtkAllocation alloc; + + /* The overall page height is only required for the <C-*> commands. */ + if (VB_IS_CTRL(info->key)) { + gtk_widget_get_allocation(GTK_WIDGET(c->webview), &alloc); + page_height = (int)alloc.height; + } - /* 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; + y = count * c->config.scrollstep; + break; + case 'h': + x = -count * c->config.scrollstep; break; case 'k': - adjust = vb.gui.adjust_v; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) - value * count; + y = -count * c->config.scrollstep; break; case 'l': - adjust = vb.gui.adjust_h; - value = vb.config.scrollstep; - new = gtk_adjustment_get_value(adjust) + value * count; + x = count * c->config.scrollstep; 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; + y = count * page_height / 2; 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; + y = -count * page_height / 2; break; case CTRL('F'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust); - new = gtk_adjustment_get_value(adjust) + value * count; + y = count * page_height; break; case CTRL('B'): - adjust = vb.gui.adjust_v; - value = gtk_adjustment_get_page_size(adjust); - new = gtk_adjustment_get_value(adjust) - value * count; + y = -count * page_height; 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; + if (info->count) { + js = g_strdup_printf( + "window.scroll(window.scrollX, %d * (1 + (document.height - window.innerHeight) / 100));", + info->count); + webkit_web_view_run_javascript(c->webview, js, NULL, NULL, NULL); + g_free(js); + return RESULT_COMPLETE; + } + /* Without count scroll to the end of the page. */ + webkit_web_view_run_javascript(c->webview, "window.scroll(window.scrollX, document.body.scrollHeight);", NULL, NULL, NULL); + return RESULT_COMPLETE; + case '0': + webkit_web_view_run_javascript(c->webview, "window.scroll(0, window.scrollY);", NULL, NULL, NULL); + return RESULT_COMPLETE; + case '$': + webkit_web_view_run_javascript(c->webview, "window.scroll(document.body.scrollWidth, window.scrollY);", NULL, NULL, NULL); + return RESULT_COMPLETE; 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; + if (info->count) { + js = g_strdup_printf( + "window.scroll(window.scrollX, %d * (1 + (document.height - window.innerHeight) / 100));", + info->count); + webkit_web_view_run_javascript(c->webview, js, NULL, NULL, NULL); + g_free(js); + return RESULT_COMPLETE; + } + /* Without count gg scrolls to the top of the page. */ + webkit_web_view_run_javascript(c->webview, "window.scroll(window.scrollX, 0);", NULL, NULL, NULL); + return RESULT_COMPLETE; } 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("window.scrollBy(%d,%d);", x, y); + webkit_web_view_run_javascript(c->webview, js, NULL, NULL, 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})); + 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; } @@ -756,52 +730,54 @@ static VbResult normal_search_selection(const NormalCmdInfo *info) /* 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){0})); + command_search(c, &((Arg){info->key == '*' ? count : -count, query})); 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_get_inspected_uri(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)) { @@ -811,20 +787,12 @@ 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); + webkit_web_view_set_zoom_level(view, 1.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') { @@ -834,6 +802,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 710a553..e4f626c 100644 --- a/src/normal.h +++ b/src/normal.h @@ -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 0000000..763376e --- /dev/null +++ b/src/scripts/.gitignore @@ -0,0 +1 @@ +scripts.h diff --git a/src/scripts/Makefile b/src/scripts/Makefile new file mode 100644 index 0000000..32af191 --- /dev/null +++ b/src/scripts/Makefile @@ -0,0 +1,19 @@ +BASEDIR=../.. +include $(BASEDIR)/config.mk + +JSFILES = $(wildcard *.js) + +all: scripts.h + +clean: + $(RM) scripts.h + +scripts.h: $(JSFILES) + @echo "create $@ from *.js" + @echo > $@ + @for file in $(JSFILES); do \ + ./js2h.sh $$file >> $@; \ + done + +.PHONY: all clean + diff --git a/src/scripts/increment_uri_number.js b/src/scripts/increment_uri_number.js new file mode 100644 index 0000000..d90adfb --- /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 0000000..b177085 --- /dev/null +++ b/src/scripts/js2h.sh @@ -0,0 +1,38 @@ +#!/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 + +# Remove the .js file extension and turn all chars to upper case to get the +# constant name. +CONSTANT=$(echo "$FILE" | sed 's:.js$::g' | tr a-z A-Z) + +# minify the script +cat $FILE | \ +# removove 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/$/"\n/' diff --git a/src/setting.c b/src/setting.c index 9c0492f..79d3ebb 100644 --- a/src/setting.c +++ b/src/setting.c @@ -17,27 +17,15 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include <glib.h> #include <string.h> + +#include "completion.h" +#include "config.h" +#include "ext-proxy.h" #include "main.h" #include "setting.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,194 +41,111 @@ 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 -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 fullscreen(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); + +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, "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"); + /* TODO use the real names for webkit settings */ + i = 14; + setting_add(c, "fontsize", TYPE_INTEGER, &i, webkit, 0, "default-font-size"); + setting_add(c, "caret", TYPE_BOOLEAN, &off, webkit, 0, "enable-caret-browsing"); + setting_add(c, "cursivfont", TYPE_CHAR, &"serif", webkit, 0, "cursive-font-family"); + setting_add(c, "default-charset", TYPE_CHAR, &"utf-8", webkit, 0, "default-charset"); + setting_add(c, "defaultfont", 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, "fontsize", TYPE_INTEGER, &i, webkit, 0, "default-font-size"); + setting_add(c, "frame-flattening", TYPE_BOOLEAN, &off, webkit, 0, "enable-frame-flattening"); + setting_add(c, "header", TYPE_CHAR, &"", headers, FLAG_LIST|FLAG_NODUP, "header"); + 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"); + setting_add(c, "minimumfontsize", TYPE_INTEGER, &i, webkit, 0, "minimum-font-size"); + setting_add(c, "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"); + setting_add(c, "monofontsize", TYPE_INTEGER, &i, webkit, 0, "default-monospace-font-size"); + setting_add(c, "offlinecache", 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, "sansfont", TYPE_CHAR, &"sans-serif", webkit, 0, "sans-serif-font-family"); + setting_add(c, "scripts", TYPE_BOOLEAN, &on, webkit, 0, "enable-javascript"); + setting_add(c, "seriffont", TYPE_CHAR, &"serif", webkit, 0, "serif-font-family"); + setting_add(c, "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"); + setting_add(c, "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, "spacial-navigation", TYPE_BOOLEAN, &off, webkit, 0, "enable-spatial-navigation"); + setting_add(c, "enable-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, "xssauditor", 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, "userscripts", 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\\|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, "scrollstep", 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 <C-R>;", NULL, 0, NULL); + /* 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, "statusbar", 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); /* 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; @@ -269,34 +174,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 */ @@ -306,49 +211,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; @@ -359,9 +264,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; } } @@ -390,12 +295,12 @@ free: /** * 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; @@ -410,7 +315,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) { @@ -473,7 +378,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 */ @@ -481,7 +386,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 */ @@ -489,14 +394,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; @@ -505,25 +410,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; } } @@ -536,392 +441,192 @@ 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 fullscreen(Client *c, const char *name, DataType type, void *value, void *data) { - int res; - gboolean on = *((gboolean*)value); - - /* first set the setting on the web settings */ - res = webkit(name, type, value, data); - - if (res == VB_CMD_SUCCESS && on) { - webkit_set_cache_model(WEBKIT_CACHE_MODEL_WEB_BROWSER); + 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; -} - -static int soup(const char *name, int 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; - } - 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); - } - - return VB_CMD_SUCCESS; -} - -static int input_color(const char *name, int type, void *value, void *data) -{ - VB_COLOR_PARSE((VbColor*)data, (char*)value); - vb_update_input_style(); - - return VB_CMD_SUCCESS; -} - -static int statusbar(const char *name, int type, void *value, void *data) -{ - gtk_widget_set_visible(GTK_WIDGET(vb.gui.statusbar.box), *(gboolean*)value); - - return VB_CMD_SUCCESS; -} - -static int status_color(const char *name, int type, void *value, void *data) -{ - VB_COLOR_PARSE((VbColor*)data, (char*)value); - vb_update_status_style(); - - 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); + gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE); } - *font = pango_font_description_from_string((char*)value); - vb_update_input_style(); - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -static int status_font(const char *name, int type, void *value, void *data) +static int internal(Client *c, const char *name, DataType 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(); + char **str; + switch (type) { + case TYPE_BOOLEAN: + *(gboolean*)data = *(gboolean*)value; + break; - return VB_CMD_SUCCESS; -} + case TYPE_INTEGER: + *(int*)data = *(int*)value; + break; -#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; - } + default: + str = (char**)data; + OVERWRITE_STRING(*str, (char*)value); + break; } - vb_echo(VB_MSG_ERROR, true, "%s must be in [always, origin, never]", name); - - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; + return CMD_SUCCESS; } -#endif -static int ca_bundle(const char *name, int type, void *value, void *data) +static int user_scripts(Client *c, const char *name, DataType 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; - } + WebKitUserContentManager *ucm; + WebKitUserScript *script; + gchar *source; - /* 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; -} - - -static int proxy(const char *name, int type, void *value, void *data) -{ 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); - } - - 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); - } - } 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 - } - - 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); + ucm = webkit_web_view_get_user_content_manager(c->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); + if (g_file_get_contents(vb.files[FILES_SCRIPT], &source, NULL, NULL)) { + script = webkit_user_script_new( + source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL + ); + + webkit_user_content_manager_add_script(ucm, script); + webkit_user_script_unref(script); + g_free(source); + } } else { - g_object_set(web_setting, "user-stylesheet-uri", NULL, NULL); - } - - return VB_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) -{ - /* remove previous parsed headers */ - if (vb.config.headers) { - soup_header_free_param_list(vb.config.headers); - vb.config.headers = NULL; + webkit_user_content_manager_remove_all_scripts(ucm); } - vb.config.headers = soup_header_parse_param_list((char*)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 user_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); + WebKitUserContentManager *ucm; + WebKitUserStyleSheet *style; + gchar *source; - /* add the new one */ - vb.config.autoresponseheader = new; + gboolean enabled = *(gboolean*)value; - return VB_CMD_SUCCESS; + ucm = webkit_web_view_get_user_content_manager(c->webview); - } else { - vb_echo(VB_MSG_ERROR, true, "auto-response-header: %s", error); - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; - } -} -#endif + 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 + ); -static int prevnext(const char *name, int type, void *value, void *data) -{ - if (validate_js_regexp_list((char*)value)) { - if (*name == 'n') { - OVERWRITE_STRING(vb.config.nextpattern, (char*)value); + webkit_user_content_manager_add_style_sheet(ucm, style); + webkit_user_style_sheet_unref(style); + g_free(source); } else { - OVERWRITE_STRING(vb.config.prevpattern, (char*)value); + g_warning("Could not reed style file: %s", vb.files[FILES_USER_STYLE]); } - return VB_CMD_SUCCESS; - } - - return VB_CMD_ERROR | VB_CMD_KEEPINPUT; -} - -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)); + webkit_user_content_manager_remove_all_style_sheets(ucm); } - return VB_CMD_SUCCESS; + return CMD_SUCCESS; } -#ifdef FEATURE_HSTS -static int hsts(const char *name, int type, void *value, void *data) +static int statusbar(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; + gtk_widget_set_visible(GTK_WIDGET(c->statusbar.box), *(gboolean*)value); + + return CMD_SUCCESS; } -#endif -#ifdef FEATURE_SOUP_CACHE -static int soup_cache(const char *name, int type, void *value, void *data) +static int tls_policy(Client *c, const char *name, DataType type, void *value, void *data) { - int kilobytes = *(int*)value; + gboolean strict = *((gboolean*)value); - soup_cache_set_max_size(vb.config.soup_cache, kilobytes * 1000); + webkit_web_context_set_tls_errors_policy( + webkit_web_context_get_default(), + strict ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE); - /* 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); - } - 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(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); + const char *property = (const char*)data; + WebKitSettings *web_setting = webkit_web_view_get_settings(c->webview); + + switch (type) { + case TYPE_BOOLEAN: + g_object_set(G_OBJECT(web_setting), property, *((gboolean*)value), NULL); + break; - 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); + case TYPE_INTEGER: + g_object_set(G_OBJECT(web_setting), property, *((int*)value), NULL); + break; - if (!result) { - vb_echo(VB_MSG_ERROR, true, "%s", value); + default: + g_object_set(G_OBJECT(web_setting), property, (char*)value, NULL); + break; } - g_free(value); - return result; + return CMD_SUCCESS; } diff --git a/src/setting.h b/src/setting.h index 9631920..cd1d8d5 100644 --- a/src/setting.h +++ b/src/setting.h @@ -20,11 +20,13 @@ #ifndef _SETTING_H #define _SETTING_H +#include <gtk/gtk.h> + #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 37b083f..0663b9f 100644 --- a/src/shortcut.c +++ b/src/shortcut.c @@ -17,65 +17,66 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include <glib.h> +#include <string.h> + +#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,19 +152,21 @@ char *shortcut_get_uri(const char *string) } current_num++; } - g_string_free(token, true); + g_string_free(token, TRUE); return uri; } +#if 0 gboolean shortcut_fill_completion(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); return found; } +#endif /** * Retrieves th highest placeholder number used in given string. @@ -190,20 +193,21 @@ 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); } - 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 c521984..f932abf 100644 --- a/src/shortcut.h +++ b/src/shortcut.h @@ -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 */ + @@ -17,109 +17,156 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#include "config.h" +#include <ctype.h> #include <fcntl.h> -#include <sys/file.h> -#include <stdio.h> +#include <glib.h> #include <pwd.h> -#include <ctype.h> -#include "main.h" -#include "util.h" +#include <stdio.h> +#include <string.h> +#include <sys/file.h> + #include "ascii.h" #include "completion.h" +#include "util.h" -extern VbCore vb; +static struct { + char *config_dir; +} util; + +static void create_dir_if_not_exists(const char *dirpath); -static gboolean match(const char *pattern, int patlen, const char *subject); -static gboolean match_list(const char *pattern, int patlen, const char *subject); /** - * 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. + * Free memory for allocated path strings. */ -char *util_get_config_dir(const char *profilename) +void util_cleanup(void) { - char *path = g_build_filename(g_get_user_config_dir(), PROJECT, G_DIR_SEPARATOR_S, profilename, NULL); - util_create_dir_if_not_exists(path); - - return path; + if (util.config_dir) { + g_free(util.config_dir); + } } /** - * Retrieves the path to the cache dir with profilename - * If profilename is NULL, path to default directory is returned. - * Returned string must be freed. + * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated + * string. + * + * Returned path must be g_freed. */ -char *util_get_cache_dir(const char *profilename) +char *util_expand(Client *c, const char *src, int expflags) { - char *path = g_build_filename(g_get_user_cache_dir(), PROJECT, G_DIR_SEPARATOR_S, profilename, NULL); - util_create_dir_if_not_exists(path); + const char **input = &src; + char *result; + GString *dst = g_string_new(""); + int flags = expflags; - return path; -} + while (**input) { + util_parse_expansion(c, 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)++; + } -/** - * 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); + result = dst->str; + g_string_free(dst, FALSE); - return path; + return result; } /** - * Retrieves the users home directory. + * Append new data to file. + * + * @file: File to append the data + * @format: Format string used to process va_list */ -const char *util_get_home_dir(void) +gboolean util_file_append(const char *file, const char *format, ...) { - const char *dir = g_getenv("HOME"); + va_list args; + FILE *f; - if (!dir) { - dir = g_get_home_dir(); - } + if ((f = fopen(file, "a+"))) { + flock(fileno(f), LOCK_EX); - return dir; -} + va_start(args, format); + vfprintf(f, format, args); + va_end(args); -void util_create_dir_if_not_exists(const char *dirpath) -{ - if (!g_file_test(dirpath, G_FILE_TEST_IS_DIR)) { - g_mkdir_with_parents(dirpath, 0755); + flock(fileno(f), LOCK_UN); + fclose(f); + + return TRUE; } + return FALSE; } -void util_create_file_if_not_exists(const char *filename) +/** + * Retrieves the config directory path. + * Returnes string must be freed. + */ +char *util_get_config_dir(void) { - if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { - FILE *f = fopen(filename, "a"); - fclose(f); - } + char *path = g_build_filename(g_get_user_config_dir(), PROJECT, NULL); + create_dir_if_not_exists(path); + + return path; } /** * Retrieves the length bytes from given file. * - * The memory of returned string have to be freed! + * 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; + 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); + + if (!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; +} + + +/** * Retrieves the file content as lines. * * The result have to be freed by g_strfreev(). @@ -147,7 +194,7 @@ char **util_get_lines(const char *filename) * unlimited items */ GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, - guint max_items) + guint max_items) { char *line, **lines; int i, len; @@ -213,247 +260,57 @@ GList *util_file_to_unique_list(const char *filename, Util_Content_Func func, } /** - * Append new data to file. - * - * @file: File to append the data - * @format: Format string used to process va_list - */ -gboolean util_file_append(const char *file, const char *format, ...) -{ - va_list args; - FILE *f; - - if ((f = fopen(file, "a+"))) { - flock(fileno(f), LOCK_EX); - - va_start(args, format); - vfprintf(f, format, args); - va_end(args); - - flock(fileno(f), LOCK_UN); - fclose(f); - - return true; - } - return false; -} - -/** - * Prepend new data to file. - * - * @file: File to prepend the data - * @format: Format string used to process va_list - */ -gboolean util_file_prepend(const char *file, const char *format, ...) -{ - gboolean res = false; - va_list args; - char *content; - FILE *f; - - content = util_get_file_contents(file, NULL); - if ((f = fopen(file, "w"))) { - flock(fileno(f), LOCK_EX); - - va_start(args, format); - /* write new content to the file */ - vfprintf(f, format, args); - va_end(args); - - /* append previous file content */ - fputs(content, f); - - flock(fileno(f), LOCK_UN); - fclose(f); - - res = true; - } - g_free(content); - - return res; -} - -/** - * Retrieves the first line from file and delete it from file. - * - * @file: file to read from - * @item_count: will be filled with the number of remaining lines in file if it - * is not NULL. - * - * Returned string must be freed with g_free. - */ -char *util_file_pop_line(const char *file, int *item_count) -{ - char **lines = util_get_lines(file); - char *line = NULL; - int count = 0; - - if (lines) { - int 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); - g_file_set_contents(file, new, -1, NULL); - g_free(new); - } - g_strfreev(lines); - } - - if (item_count) { - *item_count = count; - } - return line; -} - -char *util_strcasestr(const char *haystack, const char *needle) -{ - guchar c1, c2; - int i, j; - int nlen = strlen(needle); - int hlen = strlen(haystack) - nlen + 1; - - 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 NULL; -} - -/** - * Replaces appearances of search in string by given replace. - * Returns a new allocated string if search was found. - */ -char *util_str_replace(const char* search, const char* replace, const char* string) -{ - if (!string) { - return NULL; - } - - char **buf = g_strsplit(string, search, -1); - char *ret = g_strjoinv(replace, buf); - g_strfreev(buf); - - return ret; -} - -/** - * 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 file path completion entries into given list store for also given + * input. */ -gboolean util_create_tmp_file(const char *content, char **file) +gboolean util_filename_fill_completion(Client *c, GtkListStore *store, const char *input) { - 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); + 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 + ); - return true; -} + 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; -/** - * Build the absolute file path of given path and possible given directory. - * - * Returned path must be freed. - */ -char *util_build_path(const char *path, const char *dir) -{ - char *fullPath = NULL, *fexp, *dexp, *p; - int expflags = UTIL_EXP_TILDE|UTIL_EXP_DOLLAR; - - /* 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); + 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); - } - } - - /* 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 = '/'; - } - - return fullPath; -} - -/** - * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated - * string. - * - * Returned path must be g_freed. - */ -char *util_expand(const char *src, int expflags) -{ - 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)++; + g_dir_close(dir); } - result = dst->str; - g_string_free(dst, false); + g_free(input_dirname); + g_free(real_dirname); - return result; + return found; } /** @@ -462,19 +319,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,8 +339,8 @@ 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))) { @@ -501,9 +358,9 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags, /* 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 +397,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; } } @@ -583,275 +440,48 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags, return expanded; } -/** - * Compares given string against also given list of patterns. - * - * * 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 - */ -gboolean util_wildmatch(const char *pattern, const char *subject) -{ - 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; -} - -/** - * 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; } @@ -20,6 +20,7 @@ #ifndef _UTIL_H #define _UTIL_H +#include <glib.h> #include "main.h" enum { @@ -27,31 +28,21 @@ 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); +void util_cleanup(void); +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, ...); -char *util_file_pop_line(const char *file, int *item_count); -char* util_strcasestr(const char* haystack, const char* needle); +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_filename_fill_completion(Client *c, GtkListStore *store, const char *input); +gboolean util_parse_expansion(Client *c, const char **input, GString *str, + int flags, const char *quoteable); 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); -gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src); -gboolean util_filename_fill_completion(GtkListStore *store, const char *input); +char *util_strcasestr(const char *haystack, const char *needle); #endif /* end of include guard: _UTIL_H */ diff --git a/src/webextension/Makefile b/src/webextension/Makefile new file mode 100644 index 0000000..671c23f --- /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) -f $(EXTTARGET) *.lo + +$(EXTTARGET): $(OBJ) + @echo "$(CC) $@" + @$(CC) $(EXTLDFLAGS) ${OBJ} -o $@ + +%.lo: %.c + @echo "${CC} $@" + @$(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 0000000..26aeaf0 --- /dev/null +++ b/src/webextension/ext-dom.c @@ -0,0 +1,179 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2015 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 <glib.h> +#include <webkitdom/webkitdom.h> + +#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; + WebKitDOMNodeList *list; + WebKitDOMXPathNSResolver *resolver; + WebKitDOMXPathResult* result; + WebKitDOMDocument *frame_doc; + guint i, len; + + 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 (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. */ + 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 (ext_dom_focus_input(frame_doc)) { + g_object_unref(list); + return TRUE; + } + } + g_object_unref(list); + + 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/handlers.h b/src/webextension/ext-dom.h index a38d9e1..657821d 100644 --- a/src/handlers.h +++ b/src/webextension/ext-dom.h @@ -17,14 +17,14 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#ifndef _HANDLERS_H -#define _HANDLERS_H +#ifndef _EXT_DOM_H +#define _EXT_DOM_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); +#include <glib.h> +#include <webkitdom/webkitdom.h> -#endif /* end of include guard: _HANDLERS_H */ +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: _EXT-DOM_H */ diff --git a/src/webextension/ext-main.c b/src/webextension/ext-main.c new file mode 100644 index 0000000..669fd7d --- /dev/null +++ b/src/webextension/ext-main.c @@ -0,0 +1,360 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2015 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 <JavaScriptCore/JavaScript.h> +#include <gio/gio.h> +#include <glib.h> +#include <libsoup/soup.h> +#include <webkit2/webkit-web-extension.h> + +#include "ext-main.h" +#include "ext-dom.h" +#include "ext-util.h" + +static void add_onload_event_observers(WebKitDOMDocument *doc); +static void dbus_emit_signal(const char *name, GVariant *data); +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 gboolean dbus_own_name_sync(GDBusConnection *connection, const char *name, + GBusNameOwnerFlags flags); +static void on_dbus_name_acquire(GDBusConnection *connection, const char *name, gpointer data); +static void on_editable_change_focus(WebKitDOMEventTarget *target, WebKitDOMEvent *event); +static void on_page_created(WebKitWebExtension *ext, WebKitWebPage *page, gpointer data); +static void on_web_page_document_loaded(WebKitWebPage *page, gpointer data); +static gboolean on_web_page_send_request(WebKitWebPage *page, WebKitURIRequest *request, + WebKitURIResponse *response, gpointer data); + +static const GDBusInterfaceVTable interface_vtable = { + dbus_handle_method_call, + NULL, + NULL +}; + +static const char introspection_xml[] = + "<node>" + " <interface name='" VB_WEBEXTENSION_INTERFACE "'>" + " <signal name='EditableChangeFocus'>" + " <arg type='b' name='focused' direction='out'/>" + " </signal>" + " <method name='FocusInput'>" + " </method>" + " <signal name='PageCreated'>" + " <arg type='t' name='page_id' direction='out'/>" + " </signal>" + " <method name='SetHeaderSetting'>" + " <arg type='s' name='headers' direction='in'/>" + " </method>" + " </interface>" + "</node>"; + +/* Global struct to hold internal used variables. */ +struct Ext { + guint regid; + GDBusConnection *connection; + WebKitWebPage *webpage; + WebKitDOMElement *active; + GHashTable *headers; + GHashTable *documents; + gboolean input_focus; +}; +struct Ext ext = {0}; + + +/** + * Webextension entry point. + */ +G_MODULE_EXPORT +void webkit_web_extension_initialize_with_user_data(WebKitWebExtension *extension, GVariant *data) +{ + char *extid, *service_name; + g_variant_get(data, "(s)", &extid); + + /* Get the DBus connection for the bus type. It would be a better to use + * the async g_bus_own_name for this, but this leads to cases where pages + * are created and documents are loaded before we get a name and are able + * to call to the UI process. */ + ext.connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + + service_name = g_strdup_printf("%s-%s", VB_WEBEXTENSION_SERVICE_NAME, extid); + + /* Try to own name synchronously. */ + if (ext.connection && dbus_own_name_sync(ext.connection, service_name, G_BUS_NAME_OWNER_FLAGS_NONE)) { + on_dbus_name_acquire(ext.connection, service_name, extension); + } + + g_free(service_name); + + g_signal_connect(extension, "page-created", G_CALLBACK(on_page_created), NULL); +} + +/** + * Add observers to doc event for given document and all the contained iframes + * too. + */ +static void add_onload_event_observers(WebKitDOMDocument *doc) +{ + 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, NULL); + webkit_dom_event_target_add_event_listener(target, "blur", + G_CALLBACK(on_editable_change_focus), TRUE, NULL); + /* 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); +} + +/** + * 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; + + /* Don't do anythings if the dbus connection was not established. */ + 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); + + /* check for error */ + if (error) { + g_warning("Failed to emit signal '%s': %s", name, error->message); + g_error_free(error); + } +} + +/** + * 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 data) +{ + char *value; + + if (!g_strcmp0(method, "FocusInput")) { + ext_dom_focus_input(webkit_web_page_get_dom_document(ext.webpage)); + 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); + } +} + +/** + * The synchronous and blocking pendent to the g_bus_own_name(). + * + * @bus_type: The type of bus to own a name on. + * @name: The well-known name to own. + * @flags: A set of flags from the #GBusNameOwnerFlags enumeration. + */ +static gboolean dbus_own_name_sync(GDBusConnection *connection, const char *name, + GBusNameOwnerFlags flags) +{ + GError *error = NULL; + guint32 request_name_reply = 0; + GVariant *result; + + result = g_dbus_connection_call_sync( + connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RequestName", + g_variant_new("(su)", name, flags), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (result) { + g_variant_get(result, "(u)", &request_name_reply); + g_variant_unref(result); + + if (1 == request_name_reply) { + return TRUE; + } + } else { + g_warning("Failed to acquire DBus name: %s", error->message); + g_error_free(error); + } + return FALSE; +} + +/** + * Called when the dbus name is aquired and registers our object. + */ +static void on_dbus_name_acquire(GDBusConnection *connection, const char *name, gpointer data) +{ + GError *error = NULL; + static GDBusNodeInfo *node_info = NULL; + + g_return_if_fail(G_IS_DBUS_CONNECTION(connection)); + + if (!node_info) { + node_info = g_dbus_node_info_new_for_xml(introspection_xml, NULL); + } + + /* register the webextension object */ + ext.connection = connection; + 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); + } +} + +/** + * 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) +{ + gboolean input_focus; + WebKitDOMDocument *doc; + WebKitDOMElement *active; + + 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); + } + active = webkit_dom_document_get_active_element(doc); + /* Don't do anything if there is no active element or the active element + * is the same as before. */ + if (!active || active == ext.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); + return; + } + + ext.active = active; + + /* Check if the active element is an editable element. */ + input_focus = ext_dom_is_editable(active); + if (input_focus != ext.input_focus) { + ext.input_focus = input_focus; + + dbus_emit_signal("EditableChangeFocus", g_variant_new("(b)", input_focus)); + } +} + +/** + * Callback for web extensions page-created signal. + */ +static void on_page_created(WebKitWebExtension *extension, WebKitWebPage *page, gpointer data) +{ + /* Save the new created page in the extension data for later use. */ + ext.webpage = page; + + g_object_connect(page, + "signal::send-request", G_CALLBACK(on_web_page_send_request), NULL, + "signal::document-loaded", G_CALLBACK(on_web_page_document_loaded), NULL, + NULL); + + dbus_emit_signal("PageCreated", g_variant_new("(t)", webkit_web_page_get_id(page))); +} + +/** + * Callback for web pages document-loaded signal. + */ +static void on_web_page_document_loaded(WebKitWebPage *page, gpointer data) +{ + /* 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(page)); +} + +/** + * Callback for web pages send-request signal. + */ +static gboolean on_web_page_send_request(WebKitWebPage *page, WebKitURIRequest *request, + WebKitURIResponse *response, gpointer data) +{ + /* Change request headers according to the users preferences. */ + if (ext.headers) { + char *name, *value; + SoupMessageHeaders *headers; + GHashTableIter iter; + + headers = webkit_uri_request_get_http_headers(request); + 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/hints.h b/src/webextension/ext-main.h index 4ced9d8..6178800 100644 --- a/src/hints.h +++ b/src/webextension/ext-main.h @@ -17,19 +17,11 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -#ifndef _HINTS_H -#define _HINTS_H +#ifndef _EXT_MAIN_H +#define _EXT_MAIN_H -#include "main.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" -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); -gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode); -void hints_clear(void); -void hints_focus_next(const gboolean back); - -#endif /* end of include guard: _HINTS_H */ +#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 0000000..0bb40e4 --- /dev/null +++ b/src/webextension/ext-util.c @@ -0,0 +1,60 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2015 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 <glib.h> +#include <string.h> +#include <unistd.h> +#include "../config.h" +#include "ext-util.h" + +/** + * 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; +} diff --git a/src/io.h b/src/webextension/ext-util.h index 7fa894b..452c427 100644 --- a/src/io.h +++ b/src/webextension/ext-util.h @@ -17,16 +17,11 @@ * 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_UTIL_H +#define _EXT_UTIL_H #include <glib.h> -gboolean io_init_socket(const char *name); -void io_cleanup(void); +gboolean ext_util_create_tmp_file(const char *content, char **file); -#endif /* end of include guard: _IO_H */ -#endif +#endif /* end of include guard: _EXT_UTIL_H */ diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index 766f66e..0000000 --- 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/112-editable-focus.html b/tests/manual/112-editable-focus.html deleted file mode 100644 index 3accc37..0000000 --- a/tests/manual/112-editable-focus.html +++ /dev/null @@ -1,39 +0,0 @@ -<html> -<head> -<title>Input mode Switching (#112 #237)</title> -<script type="text/javascript"> -//<![CDATA[ -function setFocus() { - document.getElementById("text").focus(); -} -//]]> -</script> -</head> -<body onload="setFocus();"> - <p> - Run with <code>scripts=on</code> and <code>strict-focus=off</code> - <ol> - <li>If page is loade, vimb should be in input mode.</li> - <li>Set <code>strict-focus=on</code> and reload page. Vimb should keep - in normal mode</li> - <li>Independent from the <code>strict-focus</code> should vimb switch - to input mode if the button is clicked</li> - </ol> - </p> - <form action="#"> - <div> - <textarea name="text" id="text" rows="11" cols="50"></textarea><br/> - <input type="button" value="Focus Textarea" onclick="document.getElementById('text').focus();"/> - </div> - </form> - <p> - Also the following element using <code>contenteditable="true"</code> - should switch vimb into input mode on click. - </p> - <div contenteditable="true" style="width:50%;height:3em;border:1px solid #000"> - Clicking this element using contenteditable="true" should - switch vimb into input mode too. - </div> -</body> -</html> - diff --git a/tests/manual/146-hsts-iframe.html b/tests/manual/146-hsts-iframe.html deleted file mode 100644 index 7775b75..0000000 --- a/tests/manual/146-hsts-iframe.html +++ /dev/null @@ -1,14 +0,0 @@ -<html> -<head> -<title>iFrame URI change by HSTS (#146)</title> -</head> -<body> - <p> - The hsts domain used in iFrame with http must not lead to load the - iframe content as page. - </p> - <iframe src="http://github.com/fanglingsu/vimb" width="600" height="400"> - Github - </iframe> -</body> -</html> diff --git a/tests/manual/201-editable-focus-in-iframes.html b/tests/manual/201-editable-focus-in-iframes.html deleted file mode 100644 index d02c0fb..0000000 --- a/tests/manual/201-editable-focus-in-iframes.html +++ /dev/null @@ -1,9 +0,0 @@ -<html> -<head> -<title>Track Focu/Blur also within iFrames</title> -</head> -<body> -<iframe style="border: 1px solid #000; width: 500px; height: 500px;" src="./112-editable-focus.html"></iframe> -</body> -</html> - diff --git a/tests/test-handlers.c b/tests/test-handlers.c deleted file mode 100644 index 8cff0b1..0000000 --- a/tests/test-handlers.c +++ /dev/null @@ -1,80 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <gtk/gtk.h> -#include <src/handlers.h> - -#define TEST_URI "http://fanglingsu.github.io/vimb/" - -static void test_handler_add(void) -{ - /* check none handled http first */ - g_assert_true(handler_add("http", "echo -n 'handled uri %s'")); -} - -static void test_handler_remove(void) -{ - handler_add("http", "e"); - - g_assert_true(handler_remove("http")); - g_assert_false(handler_remove("http")); -} - -static void test_handler_run_success(void) -{ - if (g_test_subprocess()) { - handler_add("http", "echo -n 'handled uri %s'"); - handle_uri(TEST_URI); - return; - } - g_test_trap_subprocess(NULL, 0, 0); - g_test_trap_assert_passed(); - g_test_trap_assert_stdout("handled uri " TEST_URI); -} - -static void test_handler_run_failed(void) -{ - if (g_test_subprocess()) { - handler_add("http", "unknown-program %s"); - handle_uri(TEST_URI); - return; - } - g_test_trap_subprocess(NULL, 0, 0); - g_test_trap_assert_failed(); - g_test_trap_assert_stderr("*Can't run *unknown-program*"); -} - -int main(int argc, char *argv[]) -{ - int result; - handlers_init(); - - g_test_init(&argc, &argv, NULL); - - g_test_add_func("/test-handlers/add", test_handler_add); - g_test_add_func("/test-handlers/remove", test_handler_remove); - g_test_add_func("/test-handlers/handle_uri/success", test_handler_run_success); - g_test_add_func("/test-handlers/handle_uri/failed", test_handler_run_failed); - - result = g_test_run(); - - handlers_cleanup(); - - return result; -} diff --git a/tests/test-map.c b/tests/test-map.c deleted file mode 100644 index 8fe5ace..0000000 --- a/tests/test-map.c +++ /dev/null @@ -1,176 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <gtk/gtk.h> -#include <gdk/gdkkeysyms.h> -#include <gdk/gdkkeysyms-compat.h> -#include <src/map.h> -#include <src/main.h> - -static char queue[20]; /* receives the keypresses */ -static int qpos = 0; /* points to the queue entry for the next keypress */ - -#define QUEUE_APPEND(c) { \ - queue[qpos++] = (char)c; \ - queue[qpos] = '\0'; \ -} -#define QUEUE_CLEAR() {queue[(qpos = 0)] = '\0';} -#define ASSERT_MAPPING(s, e) { \ - QUEUE_CLEAR(); \ - map_handle_string(s, true); \ - g_assert_cmpstr(queue, ==, e); \ -} - -typedef struct { - guint state; - guint keyval; -} TestKeypress; - -VbResult keypress(int key) -{ - /* put the key into the queue */ - QUEUE_APPEND(key); - - return RESULT_COMPLETE; -} - -static void test_handle_string_simple(void) -{ - /* test simple mappings */ - ASSERT_MAPPING("a", "[a]"); - ASSERT_MAPPING("b", "[b]"); - ASSERT_MAPPING("[c]", "c"); - ASSERT_MAPPING("d", " [d]"); - ASSERT_MAPPING("<Tab>", "[tab]"); - ASSERT_MAPPING("<S-Tab>", "[shift-tab]"); - ASSERT_MAPPING("<C-F>", "[ctrl-f]"); - ASSERT_MAPPING("<C-f>", "[ctrl-f]"); - ASSERT_MAPPING("<CR>", "[cr]"); - ASSERT_MAPPING("foobar", "[baz]"); - - /* key sequences that are not changed by mappings */ - ASSERT_MAPPING("fghi", "fghi"); -} - -static void test_handle_string_alias(void) -{ - /* CTRL-I is the same like <Tab> and CTRL-M like <CR> */ - ASSERT_MAPPING("<C-I>", "[tab]"); - ASSERT_MAPPING("<C-M>", "[cr]"); -} - -static void test_handle_string_remapped(void) -{ - /* test multiple mappings together */ - ASSERT_MAPPING("ba", "[b][a]"); - ASSERT_MAPPING("ab12345[c]", "[a][b]12345c"); - - /* incomplete ambiguous sequences are not matched jet */ - ASSERT_MAPPING("foob", ""); - ASSERT_MAPPING("ar", "[baz]"); - - /* test remapping */ - map_insert("c", "baza", 't', true); - ASSERT_MAPPING("c", "[b][a]z[a]"); - map_insert("d", "cki", 't', true); - ASSERT_MAPPING("d", "[b][a]z[a]ki"); - - /* remove the no more needed mappings */ - map_delete("c", 't'); - map_delete("d", 't'); -} - -static void test_handle_string_overrule(void) -{ - /* add another map for 'a' and check if this overrules the previous set */ - map_insert("a", "overruled", 't', false); - ASSERT_MAPPING("a", "overruled"); -} - -static void test_remove(void) -{ - map_insert("x", "[x]", 't', false); - /* make sure that the mapping works */ - ASSERT_MAPPING("x", "[x]"); - - map_delete("x", 't'); - - /* make sure the mapping removed */ - ASSERT_MAPPING("x", "x"); -} - -static void test_keypress_single(void) -{ - QUEUE_CLEAR(); - - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_Tab, .state = GDK_SHIFT_MASK}), NULL); - g_assert_cmpstr(queue, ==, "[shift-tab]"); -} - -static void test_keypress_sequence(void) -{ - QUEUE_CLEAR(); - - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_f}), NULL); - g_assert_cmpstr(queue, ==, ""); - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_o}), NULL); - g_assert_cmpstr(queue, ==, ""); - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_o}), NULL); - g_assert_cmpstr(queue, ==, ""); - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_b}), NULL); - g_assert_cmpstr(queue, ==, ""); - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_a}), NULL); - g_assert_cmpstr(queue, ==, ""); - map_keypress(NULL, &((GdkEventKey){.keyval = GDK_r}), NULL); - g_assert_cmpstr(queue, ==, "[baz]"); -} - -int main(int argc, char *argv[]) -{ - int result; - g_test_init(&argc, &argv, NULL); - - /* add a test mode to handle the maped sequences */ - vb_add_mode('t', NULL, NULL, keypress, NULL); - vb_enter('t'); - map_init(); - - g_test_add_func("/test-map/handle_string/simple", test_handle_string_simple); - g_test_add_func("/test-map/handle_string/alias", test_handle_string_alias); - g_test_add_func("/test-map/handle_string/remapped", test_handle_string_remapped); - g_test_add_func("/test-map/handle_string/overrule", test_handle_string_overrule); - g_test_add_func("/test-map/remove", test_remove); - g_test_add_func("/test-map/keypress/single-char", test_keypress_single); - g_test_add_func("/test-map/keypress/sequence", test_keypress_sequence); - - /* add some mappings to test */ - map_insert("a", "[a]", 't', false); /* inlen < mappedlen */ - map_insert("b", "[b]", 't', false); - map_insert("d", "<Space>[d]", 't', false); - map_insert("[c]", "c", 't', false); /* inlen > mappedlen */ - map_insert("foobar", "[baz]", 't', false); - map_insert("<Tab>", "[tab]", 't', false); - map_insert("<S-Tab>", "[shift-tab]", 't', false); - map_insert("<C-F>", "[ctrl-f]", 't', false); - map_insert("<CR>", "[cr]", 't', false); - - result = g_test_run(); - map_cleanup(); - return result; -} diff --git a/tests/test-shortcut.c b/tests/test-shortcut.c deleted file mode 100644 index 697a04d..0000000 --- a/tests/test-shortcut.c +++ /dev/null @@ -1,142 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <gtk/gtk.h> -#include <src/shortcut.h> - -static void test_shortcut_single(void) -{ - char *uri; - - /* call with shortcut identifier */ - uri = shortcut_get_uri("_vimb1_ zero one"); - g_assert_cmpstr(uri, ==, "only-zero:zero%20one"); - g_free(uri); - - /* don't fail on unmatches quotes if there are only $0 placeholders */ - uri = shortcut_get_uri("_vimb1_ 'unmatched quote"); - g_assert_cmpstr(uri, ==, "only-zero:'unmatched%20quote"); - g_free(uri); - - /* check if all placeholders $0 are replaces */ - uri = shortcut_get_uri("_vimb5_ one two"); - g_assert_cmpstr(uri, ==, "double-zero:one%20two-one%20two"); - g_free(uri); -} - -static void test_shortcut_default(void) -{ - char *uri; - - /* call without shortcut identifier and if the last placeholder become the - * none matched query words */ - uri = shortcut_get_uri("zero one two three"); - g_assert_cmpstr(uri, ==, "default:zero-two%20three"); - g_free(uri); -} - -static void test_shortcut_keep_unmatched(void) -{ - char *uri; - - /* don't remove non matched placeholders */ - uri = shortcut_get_uri("zero"); - g_assert_cmpstr(uri, ==, "default:zero-$2"); - g_free(uri); -} - -static void test_shortcut_fullrange(void) -{ - char *uri; - - /* check if all placeholders $0-$9 are replaced */ - uri = shortcut_get_uri("_vimb3_ zero one two three four five six seven eight nine"); - g_assert_cmpstr(uri, ==, "fullrange:zero-one-nine"); - g_free(uri); -} - -static void test_shortcut_shell_param(void) -{ - char *uri; - - /* double quotes */ - uri = shortcut_get_uri("_vimb6_ \"rail station\" city hall"); - g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); - g_free(uri); - - /* single quotes */ - uri = shortcut_get_uri("_vimb6_ 'rail station' 'city hall'"); - g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); - g_free(uri); - - /* ignore none matching quote errors */ - uri = shortcut_get_uri("_vimb6_ \"rail station\" \"city hall"); - g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); - g_free(uri); - - /* don't fill up quoted param with unquoted stuff */ - uri = shortcut_get_uri("_vimb6_ \"param 1\" \"param 2\" ignored params"); - g_assert_cmpstr(uri, ==, "shell:param%201-param%202"); - g_free(uri); - - /* allo quotes within tha last parameter */ - uri = shortcut_get_uri("_vimb6_ param1 param2 \"containing quotes\""); - g_assert_cmpstr(uri, ==, "shell:param1-param2%20%22containing%20quotes%22"); - g_free(uri); -} - -static void test_shortcut_remove(void) -{ - char *uri; - - g_assert_true(shortcut_remove("_vimb4_")); - - /* check if the shortcut is really no used */ - uri = shortcut_get_uri("_vimb4_ test"); - g_assert_cmpstr(uri, ==, "default:_vimb4_-$2"); -} - -int main(int argc, char *argv[]) -{ - int result; - shortcut_init(); - - g_assert_true(shortcut_add("_vimb1_", "only-zero:$0")); - g_assert_true(shortcut_add("_vimb2_", "default:$0-$2")); - g_assert_true(shortcut_add("_vimb3_", "fullrange:$0-$1-$9")); - g_assert_true(shortcut_add("_vimb4_", "for-remove:$0")); - g_assert_true(shortcut_add("_vimb5_", "double-zero:$0-$0")); - g_assert_true(shortcut_add("_vimb6_", "shell:$0-$1")); - g_assert_true(shortcut_set_default("_vimb2_")); - - g_test_init(&argc, &argv, NULL); - - g_test_add_func("/test-shortcut/get_uri/single", test_shortcut_single); - g_test_add_func("/test-shortcut/get_uri/default", test_shortcut_default); - g_test_add_func("/test-shortcut/get_uri/keep-unmatched", test_shortcut_keep_unmatched); - g_test_add_func("/test-shortcut/get_uri/fullrange", test_shortcut_fullrange); - g_test_add_func("/test-shortcut/get_uri/shell-param", test_shortcut_shell_param); - g_test_add_func("/test-shortcut/remove", test_shortcut_remove); - - result = g_test_run(); - - shortcut_cleanup(); - - return result; -} diff --git a/tests/test-util.c b/tests/test-util.c deleted file mode 100644 index 67b1954..0000000 --- a/tests/test-util.c +++ /dev/null @@ -1,285 +0,0 @@ -/** - * vimb - a webkit based vim like browser. - * - * Copyright (C) 2012-2015 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 <gtk/gtk.h> -#include <src/util.h> - -extern VbCore vb; - -static void check_expand(const char *str, const char *expected) -{ - char *result = util_expand(str, UTIL_EXP_DOLLAR|UTIL_EXP_TILDE|UTIL_EXP_SPECIAL); - g_assert_cmpstr(result, ==, expected); - g_free(result); -} - -static void test_expand_evn(void) -{ - /* set environment var for testing expansion */ - g_setenv("VIMB_VAR", "value", true); - - check_expand("$VIMB_VAR", "value"); - check_expand("$VIMB_VAR", "value"); - check_expand("$VIMB_VAR$VIMB_VAR", "valuevalue"); - check_expand("${VIMB_VAR}", "value"); - check_expand("my$VIMB_VAR", "myvalue"); - check_expand("'$VIMB_VAR'", "'value'"); - check_expand("${VIMB_VAR}s ", "values "); - - g_unsetenv("UNKNOWN"); - - check_expand("$UNKNOWN", ""); - check_expand("${UNKNOWN}", ""); - check_expand("'$UNKNOWN'", "''"); -} - -static void test_expand_escaped(void) -{ - g_setenv("VIMB_VAR", "value", true); - - check_expand("\\$VIMB_VAR", "$VIMB_VAR"); - check_expand("\\${VIMB_VAR}", "${VIMB_VAR}"); - - check_expand("\\~/", "~/"); - check_expand("\\~/vimb", "~/vimb"); - check_expand("\\~root", "~root"); - - check_expand("\\%", "%"); - - check_expand("\\\\$VIMB_VAR", "\\value"); /* \\$VAR becomes \ExpandedVar */ - check_expand("\\\\\\$VIMB_VAR", "\\$VIMB_VAR"); /* \\\$VAR becomes \$VAR */ -} - -static void test_expand_tilde_home(void) -{ - char *dir; - const char *home = util_get_home_dir(); - - check_expand("~", "~"); - check_expand("~/", home); - check_expand("foo~/bar", "foo~/bar"); - check_expand("~/foo", (dir = g_strdup_printf("%s/foo", home))); - g_free(dir); - - check_expand("foo ~/bar", (dir = g_strdup_printf("foo %s/bar", home))); - g_free(dir); - - check_expand("~/~", (dir = g_strdup_printf("%s/~", home))); - g_free(dir); - - check_expand("~/~/", (dir = g_strdup_printf("%s/~/", home))); - g_free(dir); -} - -static void test_expand_tilde_user(void) -{ - const char *home = util_get_home_dir(); - const char *user = g_get_user_name(); - char *in, *out; - - /* don't expand within words */ - in = g_strdup_printf("foo~%s/bar", user); - check_expand(in, in); - g_free(in); - - check_expand((in = g_strdup_printf("foo ~%s", user)), (out = g_strdup_printf("foo %s", home))); - g_free(in); - g_free(out); - - check_expand((in = g_strdup_printf("~%s", user)), home); - g_free(in); - - check_expand((in = g_strdup_printf("~%s/bar", user)), (out = g_strdup_printf("%s/bar", home))); - g_free(in); - g_free(out); -} - -static void test_expand_speacial(void) -{ - vb.state.uri = "http://fanglingsu.github.io/vimb/"; - - check_expand("%", "http://fanglingsu.github.io/vimb/"); - check_expand("'%'", "'http://fanglingsu.github.io/vimb/'"); -} - -static void test_strcasestr(void) -{ - g_assert_nonnull(util_strcasestr("Vim like Browser", "browser")); - g_assert_nonnull(util_strcasestr("Vim like Browser", "vim LIKE")); -} - -static void test_str_replace(void) -{ - char *value; - - value = util_str_replace("a", "uu", "foo bar baz"); - g_assert_cmpstr(value, ==, "foo buur buuz"); - g_free(value); - - value = util_str_replace("$1", "placeholder", "string with $1"); - g_assert_cmpstr(value, ==, "string with placeholder"); - g_free(value); -} - -static void test_wildmatch_simple(void) -{ - g_assert_true(util_wildmatch("", "")); - g_assert_true(util_wildmatch("w", "w")); - g_assert_true(util_wildmatch(".", ".")); - g_assert_true(util_wildmatch("~", "~")); - g_assert_true(util_wildmatch("wildmatch", "WildMatch")); - g_assert_true(util_wildmatch("wild\\match", "wild\\match")); - - /* no special meaning of . and ~ in pattern */ - g_assert_false(util_wildmatch(".", "w")); - g_assert_false(util_wildmatch("~", "w")); - g_assert_false(util_wildmatch("wild", "wild ")); - g_assert_false(util_wildmatch("wild", " wild")); - g_assert_false(util_wildmatch("wild", "\\ wild")); - g_assert_false(util_wildmatch("wild", "\\wild")); - g_assert_false(util_wildmatch("wild", "wild\\")); - g_assert_false(util_wildmatch("wild\\1", "wild\\2")); -} - -static void test_wildmatch_questionmark(void) -{ - g_assert_true(util_wildmatch("wild?atch", "wildmatch")); - g_assert_true(util_wildmatch("wild?atch", "wildBatch")); - g_assert_true(util_wildmatch("wild?atch", "wild?atch")); - g_assert_true(util_wildmatch("?ild?atch", "MildBatch")); - g_assert_true(util_wildmatch("foo\\?bar", "foo?bar")); - g_assert_true(util_wildmatch("???", "foo")); - g_assert_true(util_wildmatch("???", "bar")); - - g_assert_false(util_wildmatch("foo\\?bar", "foorbar")); - g_assert_false(util_wildmatch("?", "")); - g_assert_false(util_wildmatch("b??r", "bar")); - /* ? does not match / in contrast to * which does */ - g_assert_false(util_wildmatch("user?share", "user/share")); -} - -static void test_wildmatch_wildcard(void) -{ - g_assert_true(util_wildmatch("*", "")); - g_assert_true(util_wildmatch("*", "Match as much as possible")); - g_assert_true(util_wildmatch("*match", "prefix match")); - g_assert_true(util_wildmatch("match*", "match suffix")); - g_assert_true(util_wildmatch("match*", "match*")); - g_assert_true(util_wildmatch("match\\*", "match*")); - g_assert_true(util_wildmatch("match\\\\*", "match\\*")); - g_assert_true(util_wildmatch("do * match", "do a infix match")); - /* '*' matches also / in contrast to other implementations */ - g_assert_true(util_wildmatch("start*end", "start/something/end")); - g_assert_true(util_wildmatch("*://*.io/*", "http://fanglingsu.github.io/vimb/")); - /* multiple * should act like a single one */ - g_assert_true(util_wildmatch("**", "")); - g_assert_true(util_wildmatch("match **", "Match as much as possible")); - g_assert_true(util_wildmatch("f***u", "fu")); - - g_assert_false(util_wildmatch("match\\*", "match fail")); - g_assert_false(util_wildmatch("f***u", "full")); -} - -static void test_wildmatch_curlybraces(void) -{ - g_assert_true(util_wildmatch("{foo}", "foo")); - g_assert_true(util_wildmatch("{foo,bar}", "foo")); - g_assert_true(util_wildmatch("{foo,bar}", "bar")); - g_assert_true(util_wildmatch("foo{lish,t}bar", "foolishbar")); - g_assert_true(util_wildmatch("foo{lish,t}bar", "footbar")); - /* esacped special chars */ - g_assert_true(util_wildmatch("foo\\{l\\}bar", "foo{l}bar")); - g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "bar")); - g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "baz{}")); - g_assert_true(util_wildmatch("test{one\\,two,three}", "testone,two")); - g_assert_true(util_wildmatch("test{one\\,two,three}", "testthree")); - /* backslash before none special char is a normal char */ - g_assert_true(util_wildmatch("back{\\slash,}", "back\\slash")); - g_assert_true(util_wildmatch("one\\two", "one\\two")); - g_assert_true(util_wildmatch("\\}match", "}match")); - g_assert_true(util_wildmatch("\\{", "{")); - /* empty list parts */ - g_assert_true(util_wildmatch("{}", "")); - g_assert_true(util_wildmatch("{,}", "")); - g_assert_true(util_wildmatch("{,foo}", "")); - g_assert_true(util_wildmatch("{,foo}", "foo")); - g_assert_true(util_wildmatch("{bar,}", "")); - g_assert_true(util_wildmatch("{bar,}", "bar")); - /* no special meaning of ? and * in curly braces */ - g_assert_true(util_wildmatch("ab{*,cd}ef", "ab*ef")); - g_assert_true(util_wildmatch("ab{d,?}ef", "ab?ef")); - - g_assert_false(util_wildmatch("{foo,bar}", "foo,bar")); - g_assert_false(util_wildmatch("}match{ it", "}match{ anything")); - /* don't match single parts that are seperated by escaped ',' */ - g_assert_false(util_wildmatch("{a,b\\,c,d}", "b")); - g_assert_false(util_wildmatch("{a,b\\,c,d}", "c")); - /* lonesome braces - this is a syntax error and will always be false */ - g_assert_false(util_wildmatch("}", "}")); - g_assert_false(util_wildmatch("}", "")); - g_assert_false(util_wildmatch("}suffix", "}suffux")); - g_assert_false(util_wildmatch("}suffix", "suffux")); - g_assert_false(util_wildmatch("{", "{")); - g_assert_false(util_wildmatch("{", "")); - g_assert_false(util_wildmatch("{foo", "{foo")); - g_assert_false(util_wildmatch("{foo", "foo")); - g_assert_false(util_wildmatch("foo{bar", "foo{bar")); -} - -static void test_wildmatch_complete(void) -{ - g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "http://fanglingsu.github.io/vimb/")); - g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "https://github.com/fanglingsu/vimb/")); -} - -static void test_wildmatch_multi(void) -{ - g_assert_true(util_wildmatch("foo,?", "foo")); - g_assert_true(util_wildmatch("foo,?", "f")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "foo")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bar")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bor")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "br")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "baz")); - g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bat")); - - g_assert_false(util_wildmatch("foo,b{a,o,}r,ba?", "foo,")); - g_assert_false(util_wildmatch("foo,?", "fo")); -} - -int main(int argc, char *argv[]) -{ - g_test_init(&argc, &argv, NULL); - - g_test_add_func("/test-util/expand-env", test_expand_evn); - g_test_add_func("/test-util/expand-escaped", test_expand_escaped); - g_test_add_func("/test-util/expand-tilde-home", test_expand_tilde_home); - g_test_add_func("/test-util/expand-tilde-user", test_expand_tilde_user); - g_test_add_func("/test-util/expand-spacial", test_expand_speacial); - g_test_add_func("/test-util/strcasestr", test_strcasestr); - g_test_add_func("/test-util/str_replace", test_str_replace); - g_test_add_func("/test-util/wildmatch-simple", test_wildmatch_simple); - g_test_add_func("/test-util/wildmatch-questionmark", test_wildmatch_questionmark); - g_test_add_func("/test-util/wildmatch-wildcard", test_wildmatch_wildcard); - g_test_add_func("/test-util/wildmatch-curlybraces", test_wildmatch_curlybraces); - g_test_add_func("/test-util/wildmatch-complete", test_wildmatch_complete); - g_test_add_func("/test-util/wildmatch-multi", test_wildmatch_multi); - - return g_test_run(); -} |