├── .github ├── ISSUE_TEMPLATE.md └── stale.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── config.mk ├── doc └── vimb.1 ├── src ├── .gitignore ├── Makefile ├── ascii.h ├── autocmd.c ├── autocmd.h ├── bookmark.c ├── bookmark.h ├── command.c ├── command.h ├── completion.c ├── completion.h ├── config.def.h ├── ex.c ├── ex.h ├── ext-proxy.c ├── ext-proxy.h ├── file-storage.c ├── file-storage.h ├── handler.c ├── handler.h ├── hints.c ├── hints.h ├── history.c ├── history.h ├── input.c ├── input.h ├── main.c ├── main.h ├── map.c ├── map.h ├── normal.c ├── normal.h ├── scripts │ ├── .gitignore │ ├── focus_editor_map_element.js │ ├── hints.css │ ├── hints.js │ ├── increment_uri_number.js │ ├── js2h.sh │ ├── scroll.js │ └── set_editor_map_element.js ├── setting.c ├── setting.h ├── shortcut.c ├── shortcut.h ├── util.c ├── util.h └── webextension │ ├── Makefile │ ├── ext-dom.c │ ├── ext-dom.h │ ├── ext-main.c │ ├── ext-main.h │ ├── ext-util.c │ └── ext-util.h ├── tests ├── .gitignore ├── Makefile ├── manual │ ├── dummy.html │ ├── editable-focus-in-iframe.html │ ├── editable-focus.html │ ├── hints │ │ ├── hints.html │ │ ├── label-positioning.html │ │ └── wrapped.html │ ├── js-window-close.html │ └── window-open.html ├── test-file-storage.c ├── test-handler.c ├── test-shortcut.c └── test-util.c ├── vimb.desktop └── vimb.metainfo.xml /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 3 | ### Steps to reproduce 4 | 5 | ### Expected behaviour 6 | 7 | ### Actual behaviour 8 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | # Number of days of inactivity before an issue becomes stale 3 | daysUntilStale: 60 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: false 6 | # Issues with these labels will never be considered stale 7 | exemptLabels: 8 | - rfc 9 | - security 10 | - pinned 11 | - bug 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | activity within the last 60 days. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | # closeComment: > 20 | # This issue has been automatically closed because it has not had activity 21 | # since it was marked as stale. Thank you for your contributions. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.[oad] 2 | *.lo 3 | *.so 4 | *.tar.gz 5 | sandbox 6 | version.h 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | except: 3 | gh-pages 4 | 5 | language: c 6 | dist: jammy 7 | sudo: required 8 | 9 | compiler: 10 | - gcc 11 | - clang 12 | 13 | before_install: 14 | - sudo apt-get update -q 15 | - sudo apt-get install -y --allow-unauthenticated --no-install-recommends libwebkit2gtk-4.1-dev 16 | 17 | script: make options && make -j test 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | This document contains guidelines for contributing to vimb, as well as useful 4 | hints when doing so. 5 | 6 | ## Goals 7 | 8 | Getting a light, fast and keyboard-driven browser that is easy to use for 9 | those users familiar with vim. 10 | 11 | - Provide powerful knobs allowing the user to tweak vimb to fit the own needs 12 | and usecases. 13 | - Add only knobs/features that do not do what other knobs do. In this point 14 | vimb is in contrast to vim. 15 | - If there are two colliding features we should pick the mightier one, or that 16 | which need less code or resources. 17 | 18 | ## Find something to work on 19 | 20 | If you are interested to contribute to vimb it's a good idea to check if 21 | someone else is already working on. I would be a shame when you work was for 22 | nothing. 23 | 24 | If you have some ideas how to improve vimb by new features or to simplify it. 25 | Write an issue so that other contributors can comment/vote on it or help you 26 | with it. 27 | 28 | If you do not want to write code you are pretty welcome to update 29 | [documentation][issue-doc] or to argue and vote for features and [request for 30 | comments][issue-rfc]. 31 | 32 | ## Communication 33 | 34 | If you want to discuss some feature or provide some suggestion that can't be 35 | done very well with the github issues. You should use the [mailing list][mail] 36 | for this purpose. Else it's a good decision to use the features provided by 37 | github for that. 38 | 39 | ## Patching and Coding style 40 | 41 | ### File Layout 42 | 43 | - Comment with LICENSE and possibly short explanation of file/tool 44 | - Headers 45 | - Macros 46 | - Types 47 | - Function declarations 48 | - Include variable names 49 | - For short files these can be left out 50 | - Group/order in logical manner 51 | - Global variables 52 | - Function definitions in same order as declarations 53 | - main 54 | 55 | ### C Features 56 | 57 | - Do not mix declarations and code 58 | - Do not use for loop initial declarations 59 | - Use `/* */` for comments, not `//` 60 | 61 | ### Headers 62 | 63 | - Place system/libc headers first in alphabetical order 64 | - If headers must be included in a specific order comment to explain 65 | - Place local headers after an empty line 66 | 67 | ### Variables 68 | 69 | - Global variables not used outside translation unit should be declared static 70 | - In declaration of pointers the `*` is adjacent to variable name, not type 71 | 72 | ### Indentation 73 | 74 | - the code is indented by 4 spaces - if you use vim to code you can set 75 | `:set expandtab ts=4 sts=4 sw=4` 76 | - it's a good advice to orientate on the already available code 77 | - if you are using `indent`, following options describe best the code style 78 | - `--k-and-r-style` 79 | - `--case-indentation4` 80 | - `--dont-break-function-decl-args` 81 | - `--dont-break-procedure-type` 82 | - `--dont-line-up-parentheses` 83 | - `--no-tabs` 84 | 85 | ## directories 86 | 87 | ├── doc documentation like manual page 88 | └── src all sources to build vimb 89 | ├── scripts JavaScripts and CSS that are compiled in for various purposes 90 | └── webextension Source files for the webextension 91 | 92 | ## compile and run 93 | 94 | To inform vimb during compile time where the webextension should be loaded 95 | from, the `RUNPREFIX` option can be set to a full qualified path to the 96 | directory where the extension should be stored in. 97 | 98 | To run vimb without installation you could run as a sandbox like this 99 | 100 | make runsandbox 101 | 102 | This will compile and install vimb into the local _sandbox_ folder in the 103 | project directory. 104 | 105 | [mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list" 106 | [issue-doc]: https://github.com/fanglingsu/vimb/labels/component%3A%20docu 107 | [issue-rfc]: https://github.com/fanglingsu/vimb/labels/rfc 108 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | version = 3.7.0 2 | include config.mk 3 | 4 | all: version.h src.subdir-all 5 | 6 | version.h: Makefile $(wildcard .git/index) 7 | @echo "create $@" 8 | $(Q)v="$$(git describe --tags 2>/dev/null)"; \ 9 | echo "#define VERSION \"$${v:-$(version)}\"" > $@ 10 | 11 | options: 12 | @echo "vimb build options:" 13 | @echo "LIBS = $(LIBS)" 14 | @echo "CFLAGS = $(CFLAGS)" 15 | @echo "LDFLAGS = $(LDFLAGS)" 16 | @echo "EXTCFLAGS = $(EXTCFLAGS)" 17 | @echo "CC = $(CC)" 18 | 19 | install: all 20 | @# binary 21 | install -d $(BINPREFIX) 22 | install -m 755 src/vimb $(BINPREFIX)/vimb 23 | @# extension 24 | install -d $(LIBDIR) 25 | install -m 644 src/webextension/$(EXTTARGET) $(LIBDIR)/$(EXTTARGET) 26 | @# man page 27 | install -d $(MANPREFIX)/man1 28 | @sed -e "s!VERSION!$(version)!g" \ 29 | -e "s!PREFIX!$(PREFIX)!g" \ 30 | -e "s!DATE!`date -u -r $(DOCDIR)/vimb.1 +'%m %Y' 2>/dev/null || date +'%m %Y'`!g" $(DOCDIR)/vimb.1 > $(MANPREFIX)/man1/vimb.1 31 | @# .desktop file 32 | install -d $(DOTDESKTOPPREFIX) 33 | install -m 644 vimb.desktop $(DOTDESKTOPPREFIX)/vimb.desktop 34 | @# .metainfo.xml file 35 | install -d $(METAINFOPREFIX) 36 | install -m 644 vimb.metainfo.xml $(METAINFOPREFIX)/vimb.metainfo.xml 37 | 38 | uninstall: 39 | $(RM) $(BINPREFIX)/vimb 40 | $(RM) $(DESTDIR)$(MANDIR)/man1/vimb.1 41 | $(RM) $(LIBDIR)/$(EXTTARGET) 42 | $(RM) $(DOTDESKTOPPREFIX)/vimb.desktop 43 | $(RM) $(METAINFOPREFIX)/vimb.metainfo.xml 44 | 45 | clean: src.subdir-clean test-clean 46 | 47 | sandbox: 48 | $(Q)$(MAKE) RUNPREFIX=$(CURDIR)/sandbox/usr PREFIX=/usr DESTDIR=./sandbox install 49 | 50 | runsandbox: sandbox 51 | sandbox/usr/bin/vimb 52 | 53 | test: version.h 54 | $(MAKE) -C src vimb.so 55 | $(MAKE) -C tests 56 | 57 | test-clean: 58 | $(MAKE) -C tests clean 59 | 60 | %.subdir-all: 61 | $(Q)$(MAKE) -C $* 62 | 63 | %.subdir-clean: 64 | $(Q)$(MAKE) -C $* clean 65 | 66 | .PHONY: all options install uninstall clean sandbox runsandbox 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vimb - the Vim-like browser 2 | 3 | [![Build Status](https://travis-ci.com/fanglingsu/vimb.svg?branch=master)](https://travis-ci.com/fanglingsu/vimb) 4 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 5 | [![Latest Release](https://img.shields.io/github/release/fanglingsu/vimb.svg?style=flat)](https://github.com/fanglingsu/vimb/releases/latest) 6 | 7 | Vimb is a Vim-like web browser that is inspired by Pentadactyl and Vimprobable. 8 | The goal of Vimb is to build a completely keyboard-driven, efficient and 9 | pleasurable browsing-experience with low memory and CPU usage that is 10 | intuitive to use for Vim users. 11 | 12 | More information and some screenshots of Vimb browser in action can be found on 13 | the project page of [Vimb][]. 14 | 15 | ## Features 16 | 17 | - it's modal like Vim 18 | - Vim like keybindings - assignable for each browser mode 19 | - nearly every configuration can be changed at runtime with Vim like set syntax 20 | - history for `ex` commands, search queries, URLs 21 | - completions for: commands, URLs, bookmarked URLs, variable names of settings, search-queries 22 | - hinting - marks links, form fields and other clickable elements to 23 | be clicked, opened or inspected 24 | - SSL validation against ca-certificate file 25 | - user defined URL-shortcuts with placeholders 26 | - read it later queue to collect URIs for later use 27 | - multiple yank/paste registers 28 | - Vim like autocmd - execute commands automatically after an event on specific URIs 29 | 30 | ## Packages 31 | 32 | - Arch Linux: [extra/vimb][], [aur/vimb-git][], [aur/vimb-gtk2][] 33 | - Fedora: [fedora/vimb][], 34 | - Gentoo: [tharvik overlay][], [jjakob overlay][] 35 | - openSUSE: [network/vimb][] 36 | - pkgsrc: [pkgsrc/www/vimb][], [pkgsrc/wip/vimb-git][] 37 | - Slackware: [slackbuild/vimb][] 38 | 39 | ## dependencies 40 | 41 | - gtk+-3.0 42 | - webkit2gtk-4.1 43 | - gst-libav, gst-plugins-good (optional, for media decoding among other things) 44 | 45 | ## Install 46 | 47 | Edit `config.mk` to match your local setup. You might need to do this if 48 | you use another compiler, like tcc. Most people, however, will almost never 49 | need to do this on systems like Ubuntu or Debian. 50 | 51 | Edit `src/config.h` to match your personal preferences, like changing the 52 | characters used in the loading bar, or the font. 53 | 54 | The default `Makefile` will not overwrite your customised `config.h` with the 55 | contents of `config.def.h`, even if it was updated in the latest git pull. 56 | Therefore, you should always compare your customised `config.h` with 57 | `config.def.h` and make sure you include any changes to the latter in your 58 | `config.h`. 59 | 60 | Run the following commands to compile and install Vimb (if necessary, the last one as 61 | root). If you want to change the `PREFIX`, note that it's required to give it on both stages, build and install. 62 | 63 | make PREFIX=/usr 64 | make PREFIX=/usr install 65 | 66 | To run vimb without installation for testing it out use the 'runsandbox' make 67 | target. 68 | 69 | make runsandbox 70 | 71 | ## Mailing list 72 | 73 | - feature requests, issues and patches can be discussed on the [mailing list][mail] ([list archive][mail-archive]) 74 | 75 | ## Similar projects 76 | 77 | - [luakit](https://luakit.github.io/) 78 | - [qutebrowser](https://www.qutebrowser.org/) 79 | - [surf](https://surf.suckless.org/) 80 | - [uzbl](https://www.uzbl.org/) 81 | - [wyeb](https://github.com/jun7/wyeb) 82 | 83 | ## license 84 | 85 | Information about the license are found in the file LICENSE. 86 | 87 | ## about 88 | 89 | - https://en.wikipedia.org/wiki/Vimb 90 | - http://thedarnedestthing.com/vimb 91 | - https://blog.jeaye.com/2015/08/23/vimb/ 92 | 93 | [aur/vimb-git]: https://aur.archlinux.org/packages/vimb-git 94 | [aur/vimb-gtk2]: https://aur.archlinux.org/packages/vimb-gtk2/ 95 | [extra/vimb]: https://www.archlinux.org/packages/extra/x86_64/vimb/ 96 | [fedora/vimb]: https://src.fedoraproject.org/rpms/vimb 97 | [tharvik overlay]: https://github.com/tharvik/overlay/tree/master/www-client/vimb 98 | [jjakob overlay]: https://github.com/jjakob/gentoo-overlay/tree/master/www-client/vimb 99 | [mail-archive]: https://sourceforge.net/p/vimb/vimb/vimb-users/ "vimb - mailing list archive" 100 | [mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list" 101 | [network/vimb]: https://build.opensuse.org/package/show/network/vimb 102 | [pkgsrc/wip/vimb-git]: http://pkgsrc.se/wip/vimb-git 103 | [pkgsrc/www/vimb]: http://pkgsrc.se/www/vimb 104 | [slackbuild/vimb]: https://slackbuilds.org/repository/14.2/network/vimb/ 105 | [vimb]: https://fanglingsu.github.io/vimb/ "Vimb - Vim like browser project page" 106 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | ifneq ($(V),1) 2 | Q := @ 3 | endif 4 | 5 | PREFIX ?= /usr/local 6 | BINPREFIX := $(DESTDIR)$(PREFIX)/bin 7 | MANPREFIX := $(DESTDIR)$(PREFIX)/share/man 8 | EXAMPLEPREFIX := $(DESTDIR)$(PREFIX)/share/vimb/example 9 | DOTDESKTOPPREFIX := $(DESTDIR)$(PREFIX)/share/applications 10 | METAINFOPREFIX := $(DESTDIR)$(PREFIX)/share/metainfo 11 | LIBDIR := $(DESTDIR)$(PREFIX)/lib/vimb 12 | RUNPREFIX := $(PREFIX) 13 | EXTENSIONDIR := $(RUNPREFIX)/lib/vimb 14 | OS := $(shell uname -s) 15 | 16 | # define some directories 17 | SRCDIR = src 18 | DOCDIR = doc 19 | 20 | # used libs 21 | LIBS = gtk+-3.0 webkit2gtk-4.1 22 | 23 | # setup general used CFLAGS 24 | CFLAGS += -std=c99 -pipe -Wall -fPIC 25 | CPPFLAGS += -DEXTENSIONDIR=\"${EXTENSIONDIR}\" 26 | CPPFLAGS += -DPROJECT=\"vimb\" -DPROJECT_UCFIRST=\"Vimb\" 27 | CPPFLAGS += -DGSEAL_ENABLE 28 | CPPFLAGS += -DGTK_DISABLE_SINGLE_INCLUDES 29 | CPPFLAGS += -DGDK_DISABLE_DEPRECATED 30 | 31 | ifeq "$(findstring $(OS),FreeBSD DragonFly)" "" 32 | CPPFLAGS += -D_XOPEN_SOURCE=500 33 | CPPFLAGS += -D__BSD_VISIBLE 34 | endif 35 | 36 | # flags used to build webextension 37 | EXTTARGET = webext_main.so 38 | EXTCFLAGS = ${CFLAGS} $(shell pkg-config --cflags webkit2gtk-web-extension-4.1) 39 | EXTCPPFLAGS = $(CPPFLAGS) 40 | EXTLDFLAGS = ${LDFLAGS} $(shell pkg-config --libs webkit2gtk-web-extension-4.1) -shared 41 | 42 | # flags used for the main application 43 | CFLAGS += $(shell pkg-config --cflags $(LIBS)) 44 | LDFLAGS += $(shell pkg-config --libs $(LIBS)) 45 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | vimb 3 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | 3 | OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) 4 | JSFILES = $(wildcard scripts/*.js) 5 | CSSFILES = $(wildcard scripts/*.css) 6 | 7 | all: vimb webextension.subdir-all 8 | 9 | clean: webextension.subdir-clean 10 | $(RM) vimb vimb.so $(OBJ) 11 | $(RM) scripts/scripts.h 12 | 13 | vimb: $(OBJ) 14 | @echo "${CC} $@" 15 | $(Q)$(CC) $(OBJ) $(LDFLAGS) -o $@ 16 | 17 | vimb.so: $(OBJ) 18 | $(Q)$(CC) -shared $(OBJ) $(LDFLAGS) -o $@ 19 | 20 | $(OBJ): config.h ../config.mk 21 | 22 | main.o: ../version.h 23 | 24 | input.o: scripts/scripts.h 25 | 26 | normal.o: scripts/scripts.h 27 | 28 | setting.o: scripts/scripts.h 29 | 30 | scripts/scripts.h: $(JSFILES) $(CSSFILES) 31 | $(Q)$(RM) $@ 32 | @echo "create $@ from *.{css,js}" 33 | $(Q)for file in $(JSFILES) $(CSSFILES); do \ 34 | ./scripts/js2h.sh $$file >> $@; \ 35 | done 36 | 37 | config.h: 38 | @echo create $@ from config.def.h 39 | $(Q)cp config.def.h $@ 40 | 41 | %.o: %.c 42 | @echo "${CC} $@" 43 | $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 44 | 45 | %.subdir-all: config.h 46 | $(Q)$(MAKE) -C $* 47 | 48 | %.subdir-clean: 49 | $(Q)$(MAKE) -C $* clean 50 | 51 | -include $(OBJ:.o=.d) 52 | 53 | .PHONY: all clean 54 | -------------------------------------------------------------------------------- /src/ascii.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _ASCII_H 21 | #define _ASCII_H 22 | 23 | #define VB_UPPER 0x01 24 | #define VB_LOWER 0x02 25 | #define VB_DIGIT 0x04 26 | #define VB_SPACE 0x08 27 | #define VB_PUNKT 0x10 28 | #define VB_CTRL 0x20 29 | 30 | #define U VB_UPPER 31 | #define L VB_LOWER 32 | #define D VB_DIGIT 33 | #define P VB_PUNKT 34 | #define S VB_SPACE 35 | #define C VB_CTRL 36 | #define SC VB_SPACE|VB_CTRL 37 | static const unsigned char chartable[256] = { 38 | /* 0x00-0x0f */ C, C, C, C, C, C, C, C, C, SC, SC, C, SC, SC, C, C, 39 | /* 0x10-0x1f */ C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, 40 | /* 0x20-0x2f */ S, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 41 | /* 0x30-0x3f */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, P, 42 | /* 0x40-0x4f */ P, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, 43 | /* 0x50-0x5f */ U, U, U, U, U, U, U, U, U, U, U, P, P, P, P, P, 44 | /* 0x60-0x6f */ P, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, 45 | /* 0x70-0x7f */ L, L, L, L, L, L, L, L, L, L, L, P, P, P, P, C, 46 | /* 0x80-0x8f */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 47 | /* 0x90-0x9f */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 48 | /* 0xa0-0xaf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 49 | /* 0xb0-0xbf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 50 | /* 0xc0-0xcf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 51 | /* 0xd0-0xdf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 52 | /* 0xe0-0xef */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, 53 | /* 0xf0-0xff */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P 54 | }; 55 | #undef U 56 | #undef L 57 | #undef D 58 | #undef P 59 | #undef S 60 | #undef C 61 | #undef SC 62 | 63 | #define VB_IS_UPPER(c) ((chartable[(unsigned char)c] & VB_UPPER) != 0) 64 | #define VB_IS_LOWER(c) ((chartable[(unsigned char)c] & VB_LOWER) != 0) 65 | #define VB_IS_DIGIT(c) ((chartable[(unsigned char)c] & VB_DIGIT) != 0) 66 | #define VB_IS_PUNKT(c) ((chartable[(unsigned char)c] & VB_PUNKT) != 0) 67 | #define VB_IS_SPACE(c) ((chartable[(unsigned char)c] & VB_SPACE) != 0) 68 | #define VB_IS_CTRL(c) ((chartable[(unsigned char)c] & VB_CTRL) != 0) 69 | #define VB_IS_SEPARATOR(c) (VB_IS_SPACE(c) || c == '"' || c == '\'') 70 | #define VB_IS_ALPHA(c) (VB_IS_LOWER(c) || VB_IS_UPPER(c)) 71 | #define VB_IS_ALNUM(c) (VB_IS_ALPHA(c) || VB_IS_DIGIT(c)) 72 | #define VB_IS_IDENT(c) (VB_IS_ALNUM(c) || c == '_') 73 | 74 | /* CSI (control sequence introducer) is the first byte of a control sequence 75 | * and is always followed by two bytes. */ 76 | #define CSI 0x80 77 | #define CSI_STR "\x80" 78 | 79 | /* get internal representation for control character ^C */ 80 | #define CTRL(c) ((c) ^ 0x40) 81 | #define UNCTRL(c) (((c) ^ 0x40) + 'a' - 'A') 82 | 83 | #define IS_SPECIAL(c) (c < 0) 84 | 85 | #define TERMCAP2KEY(a, b) (-((a) + ((int)(b) << 8))) 86 | #define KEY2TERMCAP0(x) ((-(x)) & 0xff) 87 | #define KEY2TERMCAP1(x) (((unsigned)(-(x)) >> 8) & 0xff) 88 | 89 | #define KEY_TAB '\x09' 90 | #define KEY_NL '\x15' 91 | #define KEY_CR '\x0d' 92 | #define KEY_ESC '\x1b' 93 | #define KEY_BS '\x08' 94 | #define KEY_SHIFT_TAB TERMCAP2KEY('k', 'B') 95 | #define KEY_UP TERMCAP2KEY('k', 'u') 96 | #define KEY_DOWN TERMCAP2KEY('k', 'd') 97 | #define KEY_LEFT TERMCAP2KEY('k', 'l') 98 | #define KEY_RIGHT TERMCAP2KEY('k', 'r') 99 | #define KEY_F1 TERMCAP2KEY('k', '1') 100 | #define KEY_F2 TERMCAP2KEY('k', '2') 101 | #define KEY_F3 TERMCAP2KEY('k', '3') 102 | #define KEY_F4 TERMCAP2KEY('k', '4') 103 | #define KEY_F5 TERMCAP2KEY('k', '5') 104 | #define KEY_F6 TERMCAP2KEY('k', '6') 105 | #define KEY_F7 TERMCAP2KEY('k', '7') 106 | #define KEY_F8 TERMCAP2KEY('k', '8') 107 | #define KEY_F9 TERMCAP2KEY('k', '9') 108 | #define KEY_F10 TERMCAP2KEY('k', ';') 109 | #define KEY_F11 TERMCAP2KEY('F', '1') 110 | #define KEY_F12 TERMCAP2KEY('F', '2') 111 | 112 | #endif /* end of include guard: _ASCII_H */ 113 | -------------------------------------------------------------------------------- /src/autocmd.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | 22 | #include "config.h" 23 | #ifdef FEATURE_AUTOCMD 24 | #include "autocmd.h" 25 | #include "ascii.h" 26 | #include "ex.h" 27 | #include "util.h" 28 | #include "completion.h" 29 | 30 | typedef struct { 31 | guint bits; /* the bits identify the events the command applies to */ 32 | char *excmd; /* ex command string to be run on matches event */ 33 | char *pattern; /* list of patterns the uri is matched agains */ 34 | } AutoCmd; 35 | 36 | struct AuGroup { 37 | char *name; 38 | GSList *cmds; 39 | }; 40 | 41 | typedef struct AuGroup AuGroup; 42 | 43 | static struct { 44 | const char *name; 45 | guint bits; 46 | } events[] = { 47 | {"*", 0x01ff}, 48 | {"LoadStarting", 0x0001}, 49 | {"LoadStarted", 0x0002}, 50 | {"LoadCommitted", 0x0004}, 51 | /*{"LoadFirstLayout", 0x0008},*/ 52 | {"LoadFinished", 0x0010}, 53 | /*{"LoadFailed", 0x0020},*/ 54 | {"DownloadStarted", 0x0040}, 55 | {"DownloadFinished", 0x0080}, 56 | {"DownloadFailed", 0x0100}, 57 | }; 58 | 59 | static GSList *get_group(Client *c, const char *name); 60 | static guint get_event_bits(Client *c, const char *name); 61 | static void rebuild_used_bits(Client *c); 62 | static char *get_next_word(char **line); 63 | static AuGroup *new_group(const char *name); 64 | static void free_group(AuGroup *group); 65 | static AutoCmd *new_autocmd(const char *excmd, const char *pattern); 66 | static void free_autocmd(AutoCmd *cmd); 67 | 68 | 69 | void autocmd_init(Client *c) 70 | { 71 | c->autocmd.curgroup = new_group("end"); 72 | c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup); 73 | c->autocmd.usedbits = 0; 74 | } 75 | 76 | void autocmd_cleanup(Client *c) 77 | { 78 | if (c->autocmd.groups) { 79 | g_slist_free_full(c->autocmd.groups, (GDestroyNotify)free_group); 80 | } 81 | } 82 | 83 | /** 84 | * Handle the :augroup {group} ex command. 85 | */ 86 | gboolean autocmd_augroup(Client *c, char *name, gboolean delete) 87 | { 88 | GSList *item; 89 | 90 | if (!*name) { 91 | return false; 92 | } 93 | 94 | /* check for group "end" that marks the default group */ 95 | if (!strcmp(name, "end")) { 96 | c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data; 97 | return true; 98 | } 99 | 100 | item = get_group(c, name); 101 | 102 | /* check if the group is going to be removed */ 103 | if (delete) { 104 | /* group does not exist - so do nothing */ 105 | if (!item) { 106 | return true; 107 | } 108 | if (c->autocmd.curgroup == (AuGroup*)item->data) { 109 | /* if the group to delete is the current - switch the the default 110 | * group after removing it */ 111 | c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data; 112 | } 113 | 114 | /* now remove the group */ 115 | free_group((AuGroup*)item->data); 116 | c->autocmd.groups = g_slist_delete_link(c->autocmd.groups, item); 117 | 118 | /* there where autocmds remove - so recreate the usedbits */ 119 | rebuild_used_bits(c); 120 | 121 | return true; 122 | } 123 | 124 | /* check if the group does already exists */ 125 | if (item) { 126 | /* if the group is found in the known groups use it as current */ 127 | c->autocmd.curgroup = (AuGroup*)item->data; 128 | 129 | return true; 130 | } 131 | 132 | /* create a new group and use it as current */ 133 | c->autocmd.curgroup = new_group(name); 134 | 135 | /* append it to known groups */ 136 | c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup); 137 | 138 | return true; 139 | } 140 | 141 | /** 142 | * Add or delete auto commands. 143 | * 144 | * :au[tocmd]! [group] {event} {pat} [nested] {cmd} 145 | * group and nested flag are not supported at the moment. 146 | */ 147 | gboolean autocmd_add(Client *c, char *name, gboolean delete) 148 | { 149 | guint bits; 150 | char *parse, *word, *pattern, *excmd; 151 | GSList *item; 152 | AuGroup *grp = NULL; 153 | 154 | parse = name; 155 | 156 | /* parse group name if it's there */ 157 | word = get_next_word(&parse); 158 | if (word) { 159 | /* check if the word is a known group name */ 160 | item = get_group(c, word); 161 | if (item) { 162 | grp = (AuGroup*)item->data; 163 | 164 | /* group is found - get the next word */ 165 | word = get_next_word(&parse); 166 | } 167 | } 168 | if (!grp) { 169 | /* no group found - use the current one */ 170 | grp = c->autocmd.curgroup; 171 | } 172 | 173 | /* parse event name - if none was matched run it for all events */ 174 | if (word) { 175 | bits = get_event_bits(c, word); 176 | if (!bits) { 177 | return false; 178 | } 179 | word = get_next_word(&parse); 180 | } else { 181 | bits = events[AU_ALL].bits; 182 | } 183 | 184 | /* last word is the pattern - if not found use '*' */ 185 | pattern = word ? word : "*"; 186 | 187 | /* the rest of the line becomes the ex command to run */ 188 | if (parse && !*parse) { 189 | parse = NULL; 190 | } 191 | excmd = parse; 192 | 193 | /* delete the autocmd if bang was given */ 194 | if (delete) { 195 | GSList *lc, *next; 196 | AutoCmd *cmd; 197 | gboolean removed = false; 198 | 199 | /* check if the group does already exists */ 200 | for (lc = grp->cmds; lc; lc = next) { 201 | /* Save the next element in case this element is removed */ 202 | next = lc->next; 203 | 204 | cmd = (AutoCmd*)lc->data; 205 | /* if not bits match - skip the command */ 206 | if (!(cmd->bits & bits)) { 207 | continue; 208 | } 209 | /* skip if pattern does not match - we check the pattern against 210 | * another pattern */ 211 | if (!util_wildmatch(pattern, cmd->pattern)) { 212 | continue; 213 | } 214 | 215 | /* if the command has no matching events - remove it */ 216 | grp->cmds = g_slist_delete_link(grp->cmds, lc); 217 | free_autocmd(cmd); 218 | 219 | removed = true; 220 | } 221 | 222 | /* if ther was at least one command removed - rebuilt the used bits */ 223 | if (removed) { 224 | rebuild_used_bits(c); 225 | } 226 | } 227 | 228 | /* add the new autocmd */ 229 | if (excmd && grp) { 230 | AutoCmd *cmd = new_autocmd(excmd, pattern); 231 | cmd->bits = bits; 232 | 233 | /* add the new autocmd to the group */ 234 | grp->cmds = g_slist_append(grp->cmds, cmd); 235 | 236 | /* merge the autocmd bits into the used bits */ 237 | c->autocmd.usedbits |= cmd->bits; 238 | } 239 | 240 | return true; 241 | } 242 | 243 | /** 244 | * Run named auto command. 245 | */ 246 | gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group) 247 | { 248 | GSList *lg, *lc, *lcc; 249 | AuGroup *grp; 250 | AutoCmd *cmd, *new; 251 | guint bits = events[event].bits; 252 | 253 | /* if there is no autocmd for this event - skip here */ 254 | if (!(c->autocmd.usedbits & bits)) { 255 | return true; 256 | } 257 | 258 | /* loop over the groups and find matching commands */ 259 | for (lg = c->autocmd.groups; lg; lg = lg->next) { 260 | grp = lg->data; 261 | /* if a group was given - skip all none matching groupes */ 262 | if (group && strcmp(group, grp->name)) { 263 | continue; 264 | } 265 | 266 | /* make a deep copy of grp->cmds since it can be modified by ex_run_string() below */ 267 | lcc = NULL; 268 | for (lc = grp->cmds; lc; lc = lc->next) { 269 | cmd = lc->data; 270 | new = new_autocmd(cmd->excmd, cmd->pattern); 271 | new->bits = cmd->bits; 272 | lcc = g_slist_prepend(lcc, new); // use prepend+reverse instead of append for efficiency 273 | } 274 | lcc = g_slist_reverse(lcc); 275 | 276 | /* test each command in group */ 277 | for (lc = lcc; lc; lc = lc->next, free_autocmd(cmd)) { 278 | cmd = lc->data; 279 | /* skip if this dos not match the event bits */ 280 | if (!(bits & cmd->bits)) { 281 | continue; 282 | } 283 | /* check pattern only if uri was given */ 284 | /* skip if pattern does not match */ 285 | if (uri && !util_wildmatch(cmd->pattern, uri)) { 286 | continue; 287 | } 288 | /* run the command */ 289 | /* TODO shoult the result be tested for RESULT_COMPLETE? */ 290 | /* run command and make sure it's not writte to command history */ 291 | ex_run_string(c, cmd->excmd, false); 292 | } 293 | 294 | g_slist_free(lcc); 295 | } 296 | 297 | return true; 298 | } 299 | 300 | gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input) 301 | { 302 | GSList *lg; 303 | gboolean found = false; 304 | GtkTreeIter iter; 305 | 306 | if (!input || !*input) { 307 | for (lg = c->autocmd.groups; lg; lg = lg->next) { 308 | gtk_list_store_append(store, &iter); 309 | gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, ((AuGroup*)lg->data)->name, -1); 310 | found = true; 311 | } 312 | } else { 313 | for (lg = c->autocmd.groups; lg; lg = lg->next) { 314 | char *value = ((AuGroup*)lg->data)->name; 315 | if (g_str_has_prefix(value, input)) { 316 | gtk_list_store_append(store, &iter); 317 | gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1); 318 | found = true; 319 | } 320 | } 321 | } 322 | 323 | return found; 324 | } 325 | 326 | gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input) 327 | { 328 | int i; 329 | const char *value; 330 | gboolean found = false; 331 | GtkTreeIter iter; 332 | 333 | if (!input || !*input) { 334 | for (i = 0; i < LENGTH(events); i++) { 335 | gtk_list_store_append(store, &iter); 336 | gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, events[i].name, -1); 337 | found = true; 338 | } 339 | } else { 340 | for (i = 0; i < LENGTH(events); i++) { 341 | value = events[i].name; 342 | if (g_str_has_prefix(value, input)) { 343 | gtk_list_store_append(store, &iter); 344 | gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1); 345 | found = true; 346 | } 347 | } 348 | } 349 | 350 | return found; 351 | } 352 | 353 | /** 354 | * Get the augroup by it's name. 355 | */ 356 | static GSList *get_group(Client *c, const char *name) 357 | { 358 | GSList *lg; 359 | AuGroup *grp; 360 | 361 | for (lg = c->autocmd.groups; lg; lg = lg->next) { 362 | grp = lg->data; 363 | if (!strcmp(grp->name, name)) { 364 | return lg; 365 | } 366 | } 367 | 368 | return NULL; 369 | } 370 | 371 | static guint get_event_bits(Client *c, const char *name) 372 | { 373 | int result = 0; 374 | 375 | while (*name) { 376 | int i, len; 377 | for (i = 0; i < LENGTH(events); i++) { 378 | /* we count the chars that given name has in common with the 379 | * current processed event */ 380 | len = 0; 381 | while (name[len] && events[i].name[len] && name[len] == events[i].name[len]) { 382 | len++; 383 | } 384 | 385 | /* check if the matching chars built a full word match */ 386 | if (events[i].name[len] == '\0' 387 | && (name[len] == '\0' || name[len] == ',') 388 | ) { 389 | /* add the bits to the result */ 390 | result |= events[i].bits; 391 | 392 | /* move pointer after the match and skip possible ',' */ 393 | name += len; 394 | if (*name == ',') { 395 | name++; 396 | } 397 | break; 398 | } 399 | } 400 | 401 | /* check if the end was reached without a match */ 402 | if (i >= LENGTH(events)) { 403 | /* is this the right place to write the error */ 404 | vb_echo(c, MSG_ERROR, TRUE, "Bad autocmd event name: %s", name); 405 | return 0; 406 | } 407 | } 408 | 409 | return result; 410 | } 411 | 412 | /** 413 | * Rebuild the usedbits from scratch. 414 | * Save all used autocmd event bits in the bitmap. 415 | */ 416 | static void rebuild_used_bits(Client *c) 417 | { 418 | GSList *lc, *lg; 419 | AuGroup *grp; 420 | 421 | /* clear the current set bits */ 422 | c->autocmd.usedbits = 0; 423 | /* loop over the groups */ 424 | for (lg = c->autocmd.groups; lg; lg = lg->next) { 425 | grp = (AuGroup*)lg->data; 426 | 427 | /* merge the used event bints into the bitmap */ 428 | for (lc = grp->cmds; lc; lc = lc->next) { 429 | c->autocmd.usedbits |= ((AutoCmd*)lc->data)->bits; 430 | } 431 | } 432 | } 433 | 434 | /** 435 | * Get the next word from given line. 436 | * Given line pointer is set past the word and and a 0-byte is added there. 437 | */ 438 | static char *get_next_word(char **line) 439 | { 440 | char *word; 441 | 442 | if (!*line || !**line) { 443 | return NULL; 444 | } 445 | 446 | /* remember where the word starts */ 447 | word = *line; 448 | 449 | /* move pointer to the end of the word or of the line */ 450 | while (**line && !VB_IS_SPACE(**line)) { 451 | (*line)++; 452 | } 453 | 454 | /* end the word */ 455 | if (**line) { 456 | *(*line)++ = '\0'; 457 | } 458 | 459 | /* skip trailing whitespace */ 460 | while (VB_IS_SPACE(**line)) { 461 | (*line)++; 462 | } 463 | 464 | return word; 465 | } 466 | 467 | static AuGroup *new_group(const char *name) 468 | { 469 | AuGroup *new = g_slice_new(AuGroup); 470 | new->name = g_strdup(name); 471 | new->cmds = NULL; 472 | 473 | return new; 474 | } 475 | 476 | static void free_group(AuGroup *group) 477 | { 478 | g_free(group->name); 479 | if (group->cmds) { 480 | g_slist_free_full(group->cmds, (GDestroyNotify)free_autocmd); 481 | } 482 | g_slice_free(AuGroup, group); 483 | } 484 | 485 | static AutoCmd *new_autocmd(const char *excmd, const char *pattern) 486 | { 487 | AutoCmd *new = g_slice_new(AutoCmd); 488 | new->excmd = g_strdup(excmd); 489 | new->pattern = g_strdup(pattern); 490 | return new; 491 | } 492 | 493 | static void free_autocmd(AutoCmd *cmd) 494 | { 495 | g_free(cmd->excmd); 496 | g_free(cmd->pattern); 497 | g_slice_free(AutoCmd, cmd); 498 | } 499 | 500 | #endif 501 | -------------------------------------------------------------------------------- /src/autocmd.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include "config.h" 21 | #ifdef FEATURE_AUTOCMD 22 | 23 | #ifndef _AUTOCMD_H 24 | #define _AUTOCMD_H 25 | 26 | #include "main.h" 27 | 28 | /* this values correspond to indices in events[] array in autocmd.c */ 29 | typedef enum { 30 | AU_ALL, 31 | AU_LOAD_STARTING, 32 | AU_LOAD_STARTED, 33 | AU_LOAD_COMMITTED, 34 | /*AU_LOAD_FIRST_LAYOUT,*/ 35 | AU_LOAD_FINISHED, 36 | /*AU_LOAD_FAILED,*/ 37 | AU_DOWNLOAD_STARTED, 38 | AU_DOWNLOAD_FINISHED, 39 | AU_DOWNLOAD_FAILED, 40 | } AuEvent; 41 | 42 | void autocmd_init(Client *c); 43 | void autocmd_cleanup(Client *c); 44 | gboolean autocmd_augroup(Client *c, char *name, gboolean delete); 45 | gboolean autocmd_add(Client *c, char *name, gboolean delete); 46 | gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group); 47 | gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input); 48 | gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input); 49 | 50 | #endif /* end of include guard: _AUTOCMD_H */ 51 | #endif 52 | -------------------------------------------------------------------------------- /src/bookmark.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | 22 | #include "config.h" 23 | #include "main.h" 24 | #include "bookmark.h" 25 | #include "util.h" 26 | #include "completion.h" 27 | 28 | typedef struct { 29 | char *uri; 30 | char *title; 31 | char *tags; 32 | } Bookmark; 33 | 34 | extern struct Vimb vb; 35 | 36 | static GList *load(const char *file); 37 | static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query, 38 | unsigned int qlen); 39 | static Bookmark *line_to_bookmark(const char *uri, const char *data); 40 | static void free_bookmark(Bookmark *bm); 41 | 42 | /** 43 | * Write a new bookmark entry to the end of bookmark file. 44 | */ 45 | gboolean bookmark_add(const char *uri, const char *title, const char *tags) 46 | { 47 | const char *file = vb.files[FILES_BOOKMARK]; 48 | if (tags) { 49 | return util_file_append(file, "%s\t%s\t%s\n", uri, title ? title : "", tags); 50 | } 51 | if (title) { 52 | return util_file_append(file, "%s\t%s\n", uri, title); 53 | } 54 | return util_file_append(file, "%s\n", uri); 55 | } 56 | 57 | gboolean bookmark_remove(const char *uri) 58 | { 59 | char **lines, *line, *p; 60 | int len, i; 61 | GString *new; 62 | gboolean removed = FALSE; 63 | 64 | if (!uri) { 65 | return FALSE; 66 | } 67 | 68 | lines = util_get_lines(vb.files[FILES_BOOKMARK]); 69 | if (lines) { 70 | new = g_string_new(NULL); 71 | len = g_strv_length(lines) - 1; 72 | for (i = 0; i < len; i++) { 73 | line = lines[i]; 74 | g_strstrip(line); 75 | /* ignore the title or bookmark tags and test only the uri */ 76 | if ((p = strchr(line, '\t'))) { 77 | *p = '\0'; 78 | if (!strcmp(uri, line)) { 79 | removed = TRUE; 80 | continue; 81 | } else { 82 | /* reappend the tags */ 83 | *p = '\t'; 84 | } 85 | } 86 | if (!strcmp(uri, line)) { 87 | removed = TRUE; 88 | continue; 89 | } 90 | g_string_append_printf(new, "%s\n", line); 91 | } 92 | g_strfreev(lines); 93 | util_file_set_content(vb.files[FILES_BOOKMARK], new->str); 94 | g_string_free(new, TRUE); 95 | } 96 | 97 | return removed; 98 | } 99 | 100 | gboolean bookmark_fill_completion(GtkListStore *store, const char *input) 101 | { 102 | gboolean found = FALSE; 103 | char **parts; 104 | unsigned int len; 105 | GtkTreeIter iter; 106 | GList *src = NULL; 107 | Bookmark *bm; 108 | 109 | src = load(vb.files[FILES_BOOKMARK]); 110 | src = g_list_reverse(src); 111 | if (!input || !*input) { 112 | /* without any tags return all bookmarked items */ 113 | for (GList *l = src; l; l = l->next) { 114 | bm = (Bookmark*)l->data; 115 | gtk_list_store_append(store, &iter); 116 | gtk_list_store_set( 117 | store, &iter, 118 | COMPLETION_STORE_FIRST, bm->uri, 119 | #ifdef FEATURE_TITLE_IN_COMPLETION 120 | COMPLETION_STORE_SECOND, bm->title, 121 | #endif 122 | -1 123 | ); 124 | found = TRUE; 125 | } 126 | } else { 127 | parts = g_strsplit(input, " ", 0); 128 | len = g_strv_length(parts); 129 | 130 | for (GList *l = src; l; l = l->next) { 131 | bm = (Bookmark*)l->data; 132 | if (bookmark_contains_all_tags(bm, parts, len)) { 133 | gtk_list_store_append(store, &iter); 134 | gtk_list_store_set( 135 | store, &iter, 136 | COMPLETION_STORE_FIRST, bm->uri, 137 | #ifdef FEATURE_TITLE_IN_COMPLETION 138 | COMPLETION_STORE_SECOND, bm->title, 139 | #endif 140 | -1 141 | ); 142 | found = TRUE; 143 | } 144 | } 145 | g_strfreev(parts); 146 | } 147 | 148 | g_list_free_full(src, (GDestroyNotify)free_bookmark); 149 | 150 | return found; 151 | } 152 | 153 | gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input) 154 | { 155 | gboolean found; 156 | unsigned int len, i; 157 | char **tags, *tag; 158 | GList *src = NULL, *taglist = NULL; 159 | Bookmark *bm; 160 | 161 | /* get all distinct tags from bookmark file */ 162 | src = load(vb.files[FILES_BOOKMARK]); 163 | for (GList *l = src; l; l = l->next) { 164 | bm = (Bookmark*)l->data; 165 | /* if bookmark contains no tags we can go to the next bookmark */ 166 | if (!bm->tags) { 167 | continue; 168 | } 169 | 170 | tags = g_strsplit(bm->tags, " ", -1); 171 | len = g_strv_length(tags); 172 | for (i = 0; i < len; i++) { 173 | tag = tags[i]; 174 | /* add tag only if it isn't already in the list */ 175 | if (!g_list_find_custom(taglist, tag, (GCompareFunc)strcmp)) { 176 | taglist = g_list_prepend(taglist, g_strdup(tag)); 177 | } 178 | } 179 | g_strfreev(tags); 180 | } 181 | 182 | /* generate the completion with the found tags */ 183 | found = util_fill_completion(store, input, taglist); 184 | 185 | g_list_free_full(src, (GDestroyNotify)free_bookmark); 186 | g_list_free_full(taglist, (GDestroyNotify)g_free); 187 | 188 | return found; 189 | } 190 | 191 | #ifdef FEATURE_QUEUE 192 | /** 193 | * Push a uri to the end of the queue. 194 | * 195 | * @uri: URI to put into the queue 196 | */ 197 | gboolean bookmark_queue_push(const char *uri) 198 | { 199 | return util_file_append(vb.files[FILES_QUEUE], "%s\n", uri); 200 | } 201 | 202 | /** 203 | * Push a uri to the beginning of the queue. 204 | * 205 | * @uri: URI to put into the queue 206 | */ 207 | gboolean bookmark_queue_unshift(const char *uri) 208 | { 209 | return util_file_prepend(vb.files[FILES_QUEUE], "%s\n", uri); 210 | } 211 | 212 | /** 213 | * Retrieves the oldest entry from queue. 214 | * 215 | * @item_count: will be filled with the number of remaining items in queue. 216 | * Returned uri must be freed with g_free. 217 | */ 218 | char *bookmark_queue_pop(int *item_count) 219 | { 220 | return util_file_pop_line(vb.files[FILES_QUEUE], item_count); 221 | } 222 | 223 | /** 224 | * Removes all contents from the queue file. 225 | */ 226 | gboolean bookmark_queue_clear(void) 227 | { 228 | FILE *f; 229 | if ((f = fopen(vb.files[FILES_QUEUE], "w"))) { 230 | fclose(f); 231 | return TRUE; 232 | } 233 | return FALSE; 234 | } 235 | #endif /* FEATURE_QUEUE */ 236 | 237 | static GList *load(const char *file) 238 | { 239 | char **lines; 240 | GList *list; 241 | lines = util_get_lines(file); 242 | list = util_strv_to_unique_list(lines, (Util_Content_Func)line_to_bookmark, 0); 243 | g_strfreev(lines); 244 | return list; 245 | } 246 | 247 | /** 248 | * Checks if the given bookmark matches all given query strings as prefix. If 249 | * the bookmark has no tags, the matching is done on the '/' splited URL. 250 | * 251 | * @bm: bookmark to test if it matches 252 | * @query: char array with tags to search for 253 | * @qlen: length of given query char array 254 | * 255 | * Return: TRUE if the bookmark contained all tags 256 | */ 257 | static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query, 258 | unsigned int qlen) 259 | { 260 | const char *separators; 261 | char *cursor; 262 | unsigned int i; 263 | gboolean found; 264 | 265 | if (!qlen) { 266 | return TRUE; 267 | } 268 | 269 | if (bm->tags) { 270 | /* If there are tags - use them for matching. */ 271 | separators = " "; 272 | cursor = bm->tags; 273 | } else { 274 | /* No tags available - matching is based on the path parts of the URL. */ 275 | separators = "./"; 276 | cursor = bm->uri; 277 | } 278 | 279 | /* iterate over all query parts */ 280 | for (i = 0; i < qlen; i++) { 281 | found = FALSE; 282 | 283 | /* we want to do a prefix match on all bookmark tags - so we check for 284 | * a match on string begin - if this fails we move the cursor to the 285 | * next space and do the test again */ 286 | while (cursor && *cursor) { 287 | /* match was not found at current cursor position */ 288 | if (g_str_has_prefix(cursor, query[i])) { 289 | found = TRUE; 290 | break; 291 | } 292 | /* If match was not found at the cursor position - move cursor 293 | * behind the next separator char. */ 294 | if ((cursor = strpbrk(cursor, separators))) { 295 | cursor++; 296 | } 297 | } 298 | 299 | if (!found) { 300 | return FALSE; 301 | } 302 | } 303 | 304 | return TRUE; 305 | } 306 | 307 | static Bookmark *line_to_bookmark(const char *uri, const char *data) 308 | { 309 | char *p; 310 | Bookmark *bm; 311 | 312 | /* data part may consist of title or titletags */ 313 | bm = g_slice_new(Bookmark); 314 | bm->uri = g_strdup(uri); 315 | if (data && (p = strchr(data, '\t'))) { 316 | *p = '\0'; 317 | bm->title = g_strdup(data); 318 | bm->tags = g_strdup(p + 1); 319 | } else { 320 | bm->title = g_strdup(data); 321 | bm->tags = NULL; 322 | } 323 | 324 | return bm; 325 | } 326 | 327 | static void free_bookmark(Bookmark *bm) 328 | { 329 | g_free(bm->uri); 330 | g_free(bm->title); 331 | g_free(bm->tags); 332 | g_slice_free(Bookmark, bm); 333 | } 334 | 335 | -------------------------------------------------------------------------------- /src/bookmark.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _BOOKMARK_H 21 | #define _BOOKMARK_H 22 | 23 | #include "main.h" 24 | 25 | gboolean bookmark_add(const char *uri, const char *title, const char *tags); 26 | gboolean bookmark_remove(const char *uri); 27 | gboolean bookmark_fill_completion(GtkListStore *store, const char *input); 28 | gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input); 29 | #ifdef FEATURE_QUEUE 30 | gboolean bookmark_queue_push(const char *uri); 31 | gboolean bookmark_queue_unshift(const char *uri); 32 | char *bookmark_queue_pop(int *item_count); 33 | gboolean bookmark_queue_clear(void); 34 | #endif 35 | 36 | #endif /* end of include guard: _BOOKMARK_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/command.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | /** 21 | * This file contains functions that are used by normal mode and command mode 22 | * together. 23 | */ 24 | #include 25 | #include 26 | 27 | #include "config.h" 28 | #ifdef FEATURE_QUEUE 29 | #include "bookmark.h" 30 | #endif 31 | #include "command.h" 32 | #include "history.h" 33 | #include "util.h" 34 | #include "main.h" 35 | 36 | typedef struct { 37 | Client *c; 38 | char *file; 39 | gpointer *data; 40 | PostEditFunc func; 41 | } EditorData; 42 | 43 | static void resume_editor(GPid pid, int status, gpointer edata); 44 | 45 | /** 46 | * Start/perform/stop searching in webview. 47 | * 48 | * @commit: If TRUE, the search query is registered into register "/ 49 | * In case searching is stopped the commit of value TRUE 50 | * is used to clear the inputbox if search is active. This is needed 51 | * in case a link is fired by on highlighted link. 52 | */ 53 | gboolean command_search(Client *c, const Arg *arg, bool commit) 54 | { 55 | const char *query; 56 | guint count; 57 | int direction; 58 | 59 | g_assert(c); 60 | g_assert(arg); 61 | 62 | if (arg->i == 0) { 63 | webkit_find_controller_search_finish(c->finder); 64 | 65 | /* Clear the input only if the search is active and commit flag is 66 | * set. This allows us to stop searching with and without cleaning 67 | * inputbox */ 68 | if (commit && c->state.search.active) { 69 | vb_echo(c, MSG_NORMAL, FALSE, ""); 70 | } 71 | 72 | c->state.search.active = FALSE; 73 | c->state.search.matches = 0; 74 | 75 | vb_statusbar_update(c); 76 | 77 | return TRUE; 78 | } 79 | 80 | query = arg->s; 81 | count = abs(arg->i); 82 | direction = arg->i > 0 ? 1 : -1; 83 | 84 | /* restart the last search if a search result is asked for and no 85 | * search was active */ 86 | if (!query && !c->state.search.active) { 87 | query = c->state.search.last_query; 88 | direction = c->state.search.direction; 89 | } 90 | 91 | /* Only committed search strings adjust registers and are recorded in 92 | * history, intermediate strings (while using incsearch) don't. */ 93 | if (commit) { 94 | if (query) { 95 | history_add(c, HISTORY_SEARCH, query, NULL); 96 | vb_register_add(c, '/', query); 97 | } else { 98 | /* Committed search without string re-searches last string. */ 99 | query = vb_register_get(c, '/'); 100 | } 101 | } 102 | 103 | /* Hand the query string to webkit's find controller. */ 104 | if (query) { 105 | /* Force a fresh start in order to have webkit select the first match 106 | * on the page. Without this workaround the first selected match 107 | * depends on the most recent selection or caret position (even when 108 | * caret browsing is disabled). */ 109 | if (commit) { 110 | webkit_find_controller_search(c->finder, "", WEBKIT_FIND_OPTIONS_NONE, G_MAXUINT); 111 | } 112 | 113 | if (!c->state.search.last_query) { 114 | c->state.search.last_query = g_strdup(query); 115 | } else if (strcmp(c->state.search.last_query, query)) { 116 | g_free(c->state.search.last_query); 117 | c->state.search.last_query = g_strdup(query); 118 | } 119 | 120 | webkit_find_controller_count_matches(c->finder, query, 121 | WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | 122 | WEBKIT_FIND_OPTIONS_WRAP_AROUND, 123 | G_MAXUINT); 124 | webkit_find_controller_search(c->finder, query, 125 | WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | 126 | WEBKIT_FIND_OPTIONS_WRAP_AROUND | 127 | (direction > 0 ? WEBKIT_FIND_OPTIONS_NONE : WEBKIT_FIND_OPTIONS_BACKWARDS), 128 | G_MAXUINT); 129 | 130 | c->state.search.active = TRUE; 131 | c->state.search.direction = direction; 132 | 133 | /* Skip first search because the first match is already 134 | * highlighted on search start. */ 135 | count -= 1; 136 | } 137 | 138 | /* Step through searchs result focus according to arg->i. */ 139 | if (c->state.search.active) { 140 | if (arg->i * c->state.search.direction > 0) { 141 | while (count--) { 142 | webkit_find_controller_search_next(c->finder); 143 | } 144 | } else { 145 | while (count--) { 146 | webkit_find_controller_search_previous(c->finder); 147 | } 148 | } 149 | } 150 | 151 | return TRUE; 152 | } 153 | 154 | gboolean command_yank(Client *c, const Arg *arg, char buf) 155 | { 156 | /** 157 | * This implementation is quite 'brute force', same as in vimb2 158 | * - both X clipboards are always set, PRIMARY and CLIPBOARD 159 | * - the X clipboards are always set, even though a vimb register was given 160 | */ 161 | 162 | const char *uri = NULL; 163 | char *yanked = NULL; 164 | 165 | g_assert(c); 166 | g_assert(arg); 167 | g_assert(c->webview); 168 | g_assert( 169 | arg->i == COMMAND_YANK_URI || 170 | arg->i == COMMAND_YANK_SELECTION || 171 | arg->i == COMMAND_YANK_ARG); 172 | 173 | if (arg->i == COMMAND_YANK_URI) { 174 | if ((uri = webkit_web_view_get_uri(c->webview))) { 175 | yanked = g_strdup(uri); 176 | } 177 | } else if (arg->i == COMMAND_YANK_SELECTION) { 178 | /* copy web view selection to clipboard */ 179 | webkit_web_view_execute_editing_command(c->webview, WEBKIT_EDITING_COMMAND_COPY); 180 | /* read back copy from clipboard */ 181 | yanked = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); 182 | } else { 183 | /* use current arg.s as new clipboard content */ 184 | yanked = g_strdup(arg->s); 185 | } 186 | 187 | if(!yanked) { 188 | return FALSE; 189 | } 190 | 191 | /* store in vimb default register */ 192 | vb_register_add(c, '"', yanked); 193 | /* store in vimb register buf if buf != 0 */ 194 | vb_register_add(c, buf, yanked); 195 | 196 | /* store in X clipboard primary (selected text copy, middle mouse paste) */ 197 | gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), yanked, -1); 198 | /* store in X "windows style" clipboard */ 199 | gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), yanked, -1); 200 | 201 | vb_echo(c, MSG_NORMAL, FALSE, "Yanked: %s", yanked); 202 | 203 | g_free(yanked); 204 | 205 | return TRUE; 206 | } 207 | 208 | gboolean command_save(Client *c, const Arg *arg) 209 | { 210 | const char *uri, *path = NULL; 211 | WebKitDownload *download; 212 | 213 | if (arg->i == COMMAND_SAVE_CURRENT) { 214 | uri = c->state.uri; 215 | /* given string is the path to save the download to */ 216 | if (arg->s && *(arg->s) != '\0') { 217 | path = arg->s; 218 | } 219 | } else { 220 | uri = arg->s; 221 | } 222 | 223 | if (!uri || !*uri) { 224 | return FALSE; 225 | } 226 | 227 | /* Start the download to given path. */ 228 | download = webkit_web_view_download_uri(c->webview, uri); 229 | 230 | return vb_download_set_destination(c, download, NULL, path); 231 | } 232 | 233 | #ifdef FEATURE_QUEUE 234 | gboolean command_queue(Client *c, const Arg *arg) 235 | { 236 | gboolean res = FALSE; 237 | int count = 0; 238 | char *uri; 239 | 240 | switch (arg->i) { 241 | case COMMAND_QUEUE_POP: 242 | if ((uri = bookmark_queue_pop(&count))) { 243 | res = vb_load_uri(c, &(Arg){TARGET_CURRENT, uri}); 244 | g_free(uri); 245 | } 246 | vb_echo(c, MSG_NORMAL, FALSE, "Queue length %d", count); 247 | break; 248 | 249 | case COMMAND_QUEUE_PUSH: 250 | res = bookmark_queue_push(arg->s ? arg->s : c->state.uri); 251 | if (res) { 252 | vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue"); 253 | } 254 | break; 255 | 256 | case COMMAND_QUEUE_UNSHIFT: 257 | res = bookmark_queue_unshift(arg->s ? arg->s : c->state.uri); 258 | if (res) { 259 | vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue"); 260 | } 261 | break; 262 | 263 | case COMMAND_QUEUE_CLEAR: 264 | if (bookmark_queue_clear()) { 265 | vb_echo(c, MSG_NORMAL, FALSE, "Queue cleared"); 266 | } 267 | break; 268 | } 269 | 270 | return res; 271 | } 272 | #endif 273 | 274 | /** 275 | * Asynchronously spawn editor. 276 | * 277 | * @posteditfunc: If not NULL posteditfunc is called and the following arguments 278 | * are passed: 279 | * - const char *text: text contents of the temporary file (or 280 | * NULL) 281 | * - Client *c: current client passed to command_spawn_editor() 282 | * - gpointer data: pointer that can be used for any local 283 | * purposes 284 | * @data: Generic pointer used to pass data to posteditfunc 285 | */ 286 | gboolean command_spawn_editor(Client *c, const Arg *arg, 287 | PostEditFunc posteditfunc, gpointer data) 288 | { 289 | char **argv = NULL, *file_path = NULL; 290 | char *command = NULL; 291 | int argc; 292 | GPid pid; 293 | gboolean success, result; 294 | char *editor_command; 295 | GError *error = NULL; 296 | 297 | /* get the editor command */ 298 | editor_command = GET_CHAR(c, "editor-command"); 299 | if (!editor_command || !*editor_command) { 300 | vb_echo(c, MSG_ERROR, TRUE, "No editor-command configured"); 301 | return FALSE; 302 | } 303 | 304 | /* create a temp file to pass text in/to editor */ 305 | if (!util_create_tmp_file(arg->s, &file_path)) { 306 | return FALSE; 307 | } 308 | 309 | /* spawn editor */ 310 | command = g_strdup_printf(editor_command, file_path); 311 | if (g_shell_parse_argv(command, &argc, &argv, NULL)) { 312 | success = g_spawn_async( 313 | NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, 314 | NULL, NULL, &pid, &error 315 | ); 316 | g_strfreev(argv); 317 | 318 | if (success) { 319 | EditorData *ed = g_slice_new0(EditorData); 320 | ed->file = file_path; 321 | ed->c = c; 322 | ed->data = data; 323 | ed->func = posteditfunc; 324 | 325 | g_child_watch_add(pid, resume_editor, ed); 326 | 327 | result = TRUE; 328 | } else { 329 | g_warning("Could not spawn editor-command: %s", error->message); 330 | g_error_free(error); 331 | result = FALSE; 332 | } 333 | } else { 334 | g_critical("Could not parse editor-command '%s'", command); 335 | result = FALSE; 336 | } 337 | g_free(command); 338 | 339 | if (!result) { 340 | unlink(file_path); 341 | g_free(file_path); 342 | } 343 | return result; 344 | } 345 | 346 | static void resume_editor(GPid pid, int status, gpointer edata) 347 | { 348 | char *text = NULL; 349 | EditorData *ed = edata; 350 | 351 | g_assert(pid); 352 | g_assert(ed); 353 | g_assert(ed->c); 354 | g_assert(ed->file); 355 | 356 | if (ed->func != NULL) { 357 | text = util_get_file_contents(ed->file, NULL); 358 | ed->func(text, ed->c, ed->data); 359 | g_free(text); 360 | } 361 | 362 | unlink(ed->file); 363 | g_free(ed->file); 364 | g_slice_free(EditorData, ed); 365 | g_spawn_close_pid(pid); 366 | } 367 | -------------------------------------------------------------------------------- /src/command.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _COMMAND_H 21 | #define _COMMAND_H 22 | 23 | #include 24 | #include "main.h" 25 | 26 | enum { 27 | COMMAND_YANK_ARG, 28 | COMMAND_YANK_URI, 29 | COMMAND_YANK_SELECTION 30 | }; 31 | 32 | enum { 33 | COMMAND_SAVE_CURRENT, 34 | COMMAND_SAVE_URI 35 | }; 36 | 37 | #ifdef FEATURE_QUEUE 38 | enum { 39 | COMMAND_QUEUE_PUSH, 40 | COMMAND_QUEUE_UNSHIFT, 41 | COMMAND_QUEUE_POP, 42 | COMMAND_QUEUE_CLEAR 43 | }; 44 | #endif 45 | 46 | typedef void (*PostEditFunc)(const char *, Client *, gpointer); 47 | 48 | gboolean command_search(Client *c, const Arg *arg, bool commit); 49 | gboolean command_yank(Client *c, const Arg *arg, char buf); 50 | gboolean command_save(Client *c, const Arg *arg); 51 | #ifdef FEATURE_QUEUE 52 | gboolean command_queue(Client *c, const Arg *arg); 53 | #endif 54 | gboolean command_spawn_editor(Client *c, const Arg *arg, PostEditFunc posteditfunc, gpointer data); 55 | 56 | #endif /* end of include guard: _COMMAND_H */ 57 | -------------------------------------------------------------------------------- /src/completion.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include "completion.h" 21 | #include "config.h" 22 | #include "main.h" 23 | 24 | typedef struct { 25 | GtkWidget *win, *tree; 26 | int active; /* number of the current active tree item */ 27 | CompletionSelectFunc selfunc; 28 | } Completion; 29 | 30 | static gboolean tree_selection_func(GtkTreeSelection *selection, 31 | GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data); 32 | 33 | extern struct Vimb vb; 34 | 35 | 36 | /** 37 | * Stop the completion and reset temporary used data. 38 | */ 39 | void completion_clean(Client *c) 40 | { 41 | Completion *comp = (Completion*)c->comp; 42 | c->mode->flags &= ~FLAG_COMPLETION; 43 | 44 | if (comp->win) { 45 | gtk_widget_destroy(comp->win); 46 | comp->win = NULL; 47 | comp->tree = NULL; 48 | } 49 | } 50 | 51 | /** 52 | * Free the memory of the completion set on the client. 53 | */ 54 | void completion_cleanup(Client *c) 55 | { 56 | if (c->comp) { 57 | g_slice_free(Completion, c->comp); 58 | c->comp = NULL; 59 | } 60 | } 61 | 62 | /** 63 | * Start the completion by creating the required widgets and setting a select 64 | * function. 65 | */ 66 | gboolean completion_create(Client *c, GtkTreeModel *model, 67 | CompletionSelectFunc selfunc, gboolean back) 68 | { 69 | GtkCellRenderer *renderer; 70 | GtkTreeSelection *selection; 71 | GtkTreeViewColumn *column; 72 | GtkRequisition size; 73 | GtkTreePath *path; 74 | GtkTreeIter iter; 75 | int height, width; 76 | Completion *comp = (Completion*)c->comp; 77 | 78 | /* if there is only one match - don't build the tree view */ 79 | if (gtk_tree_model_iter_n_children(model, NULL) == 1) { 80 | char *value; 81 | path = gtk_tree_path_new_from_indices(0, -1); 82 | if (gtk_tree_model_get_iter(model, &iter, path)) { 83 | gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1); 84 | 85 | /* call the select function */ 86 | selfunc(c, value); 87 | 88 | g_free(value); 89 | g_object_unref(model); 90 | 91 | return FALSE; 92 | } 93 | } 94 | 95 | comp->selfunc = selfunc; 96 | 97 | /* prepare the tree view */ 98 | comp->win = gtk_scrolled_window_new(NULL, NULL); 99 | comp->tree = gtk_tree_view_new_with_model(model); 100 | 101 | gtk_style_context_add_provider(gtk_widget_get_style_context(comp->tree), 102 | GTK_STYLE_PROVIDER(vb.style_provider), 103 | GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); 104 | gtk_widget_set_name(GTK_WIDGET(comp->tree), "completion"); 105 | 106 | gtk_box_pack_end(GTK_BOX(gtk_widget_get_parent(GTK_WIDGET(c->statusbar.box))), comp->win, FALSE, FALSE, 0); 107 | gtk_container_add(GTK_CONTAINER(comp->win), comp->tree); 108 | 109 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(comp->tree), FALSE); 110 | /* we have only on line per item so we can use the faster fixed heigh mode */ 111 | gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(comp->tree), TRUE); 112 | g_object_unref(model); 113 | 114 | /* prepare the selection */ 115 | selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree)); 116 | gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); 117 | gtk_tree_selection_set_select_function(selection, tree_selection_func, c, NULL); 118 | 119 | /* get window dimension */ 120 | gtk_window_get_size(GTK_WINDOW(c->window), &width, &height); 121 | 122 | /* prepare first column */ 123 | column = gtk_tree_view_column_new(); 124 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); 125 | gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column); 126 | 127 | renderer = gtk_cell_renderer_text_new(); 128 | g_object_set(renderer, 129 | "ellipsize", PANGO_ELLIPSIZE_MIDDLE, 130 | NULL 131 | ); 132 | gtk_tree_view_column_pack_start(column, renderer, TRUE); 133 | gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_FIRST); 134 | gtk_tree_view_column_set_min_width(column, 2 * width/3); 135 | 136 | /* prepare second column */ 137 | #ifdef FEATURE_TITLE_IN_COMPLETION 138 | column = gtk_tree_view_column_new(); 139 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); 140 | gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column); 141 | 142 | renderer = gtk_cell_renderer_text_new(); 143 | g_object_set(renderer, 144 | "ellipsize", PANGO_ELLIPSIZE_END, 145 | NULL 146 | ); 147 | gtk_tree_view_column_pack_start(column, renderer, TRUE); 148 | gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_SECOND); 149 | #endif 150 | 151 | /* to set the height for the treeview the tree must be realized first */ 152 | gtk_widget_show(comp->tree); 153 | 154 | /* this prevents the first item to be placed out of view if the completion 155 | * is shown */ 156 | while (gtk_events_pending()) { 157 | gtk_main_iteration(); 158 | } 159 | 160 | /* use max 1/3 of window height for the completion */ 161 | gtk_widget_get_preferred_size(comp->tree, NULL, &size); 162 | height /= 3; 163 | gtk_scrolled_window_set_min_content_height( 164 | GTK_SCROLLED_WINDOW(comp->win), 165 | size.height > height ? height : size.height 166 | ); 167 | 168 | c->mode->flags |= FLAG_COMPLETION; 169 | 170 | /* set to -1 to have the cursor on first or last item set in move_cursor */ 171 | comp->active = -1; 172 | completion_next(c, back); 173 | 174 | gtk_widget_show(comp->win); 175 | 176 | return TRUE; 177 | } 178 | 179 | /** 180 | * Initialize the completion system for given client. 181 | */ 182 | void completion_init(Client *c) 183 | { 184 | /* Allocate memory for the completion struct and save it on the client. */ 185 | c->comp = g_slice_new0(Completion); 186 | } 187 | 188 | /** 189 | * Moves the selection to the next/previous tree item. 190 | * If the end/beginning is reached return false and start on the opposite end 191 | * on the next call. 192 | */ 193 | gboolean completion_next(Client *c, gboolean back) 194 | { 195 | int rows; 196 | GtkTreePath *path; 197 | GtkTreeView *tree = GTK_TREE_VIEW(((Completion*)c->comp)->tree); 198 | Completion *comp = (Completion*)c->comp; 199 | 200 | rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(tree), NULL); 201 | if (back) { 202 | comp->active--; 203 | /* Step back over the beginning. */ 204 | if (comp->active == -1) { 205 | /* Deselect the current item to show the user the initial typed 206 | * content. */ 207 | gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree))); 208 | 209 | return FALSE; 210 | } 211 | if (comp->active < -1) { 212 | comp->active = rows - 1; 213 | } 214 | } else { 215 | comp->active++; 216 | /* Step over the end. */ 217 | if (comp->active == rows) { 218 | gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree))); 219 | 220 | return FALSE; 221 | } 222 | if (comp->active >= rows) { 223 | comp->active = 0; 224 | } 225 | } 226 | 227 | /* get new path and move cursor to it */ 228 | path = gtk_tree_path_new_from_indices(comp->active, -1); 229 | gtk_tree_view_set_cursor(tree, path, NULL, FALSE); 230 | gtk_tree_path_free(path); 231 | 232 | return TRUE; 233 | } 234 | 235 | static gboolean tree_selection_func(GtkTreeSelection *selection, 236 | GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data) 237 | { 238 | char *value; 239 | GtkTreeIter iter; 240 | Completion *comp = (Completion*)((Client*)data)->comp; 241 | 242 | /* if not selected means the item is going to be selected which we are 243 | * interested in */ 244 | if (!selected && gtk_tree_model_get_iter(model, &iter, path)) { 245 | gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1); 246 | 247 | comp->selfunc((Client*)data, value); 248 | 249 | g_free(value); 250 | /* TODO update comp->active on select by mouse to continue with 251 | * or from selected item on. */ 252 | } 253 | 254 | return TRUE; 255 | } 256 | -------------------------------------------------------------------------------- /src/completion.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _COMPLETION_H 21 | #define _COMPLETION_H 22 | 23 | #include 24 | 25 | #include "main.h" 26 | 27 | typedef void (*CompletionSelectFunc) (Client *c, char *match); 28 | 29 | enum { 30 | COMPLETION_STORE_FIRST, 31 | #ifdef FEATURE_TITLE_IN_COMPLETION 32 | COMPLETION_STORE_SECOND, 33 | #endif 34 | COMPLETION_STORE_NUM 35 | }; 36 | 37 | 38 | void completion_clean(Client *c); 39 | void completion_cleanup(Client *c); 40 | gboolean completion_create(Client *c, GtkTreeModel *model, 41 | CompletionSelectFunc selfunc, gboolean back); 42 | void completion_init(Client *c); 43 | gboolean completion_next(Client *c, gboolean back); 44 | 45 | #endif /* end of include guard: _COMPLETION_H */ 46 | -------------------------------------------------------------------------------- /src/config.def.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | /* features */ 21 | /* show wget style progressbar in status bar */ 22 | #define FEATURE_WGET_PROGRESS_BAR 23 | /* show load progress in window title */ 24 | #define FEATURE_TITLE_PROGRESS 25 | /* show page title in url completions */ 26 | #define FEATURE_TITLE_IN_COMPLETION 27 | /* enable the read it later queue */ 28 | #define FEATURE_QUEUE 29 | /* disable X window embedding */ 30 | /* #define FEATURE_NO_XEMBED */ 31 | 32 | #ifdef FEATURE_WGET_PROGRESS_BAR 33 | /* chars to use for the progressbar */ 34 | #define PROGRESS_BAR "=> " 35 | #define PROGRESS_BAR_LEN 20 36 | #endif 37 | 38 | #define FEATURE_AUTOCMD 39 | 40 | /* time in seconds after that message will be removed from inputbox if the 41 | * message where only temporary */ 42 | #define MESSAGE_TIMEOUT 5 43 | 44 | /* number of chars to be shown in statusbar for ambiguous commands */ 45 | #define SHOWCMD_LEN 10 46 | /* css applied to the gui elements regardless of user's settings */ 47 | #define GUI_STYLE_CSS_BASE "#input text{background-color:inherit;color:inherit;caret-color:@color;font:inherit;}" 48 | /* define this to set the initial background color for the GTK window */ 49 | #define GUI_WINDOW_BACKGROUND_COLOR "#FFFFFF" 50 | 51 | /* default font size for fonts in webview */ 52 | #define SETTING_DEFAULT_FONT_SIZE 16 53 | #define SETTING_DEFAULT_MONOSPACE_FONT_SIZE 13 54 | #define SETTING_GUI_FONT_NORMAL "10pt monospace" 55 | #define SETTING_GUI_FONT_EMPH "bold 10pt monospace" 56 | #define SETTING_HOME_PAGE "about:blank" 57 | #define SETTING_DOWNLOAD_PATH "~/Downloads" 58 | /* cookie-accept allowed values always, origin, never */ 59 | #define SETTING_COOKIE_ACCEPT "always" 60 | #define SETTING_HINT_KEYS "0123456789" 61 | #define SETTING_DOWNLOAD_COMMAND "/bin/sh -c \"curl -sLJOC - -e '$VIMB_URI' %s\"" 62 | #define SETTING_COMPLETION_CSS "color:#fff;background-color:#656565;font:" SETTING_GUI_FONT_NORMAL 63 | #define SETTING_COMPLETION_HOVER_CSS "background-color:#777;" 64 | #define SETTING_COMPLETION_SELECTED_CSS "color:#f6f3e8;background-color:#888;" 65 | #define SETTING_INPUT_CSS "background-color:#fff;color:#000;font:" SETTING_GUI_FONT_NORMAL 66 | #define SETTING_INPUT_ERROR_CSS "background-color:#f77;font:" SETTING_GUI_FONT_EMPH 67 | #define SETTING_STATUS_CSS "color:#fff;background-color:#000;font:" SETTING_GUI_FONT_EMPH 68 | #define SETTING_STATUS_SSL_CSS "background-color:#95e454;color:#000;" 69 | #define SETTING_STATUS_SSL_INVLID_CSS "background-color:#f77;color:#000;" 70 | 71 | #define MAXIMUM_HINTS 500 72 | /* default window dimensions */ 73 | #define WIN_WIDTH 800 74 | #define WIN_HEIGHT 600 75 | 76 | /* if set to 1 vimb will check if the webextension could be found. */ 77 | #define CHECK_WEBEXTENSION_ON_STARTUP 1 78 | 79 | /* This status indicator is only shown if "status-bar-show-settings" is 80 | * enabled. 81 | * The CHAR_MAP(value, internalValue, outputValue, valueIfNotMapped) is a 82 | * little workaround to translate internal used string value like for 83 | * GET_CHAR(c, "cookie-accept") which is one of "always", "origin" or "never" 84 | * to those values that should be shown on statusbar. 85 | * The STATUS_VARAIBLE_SHOW is used as argument for a printf like function. So 86 | * the first argument is the output pattern. */ 87 | /* 88 | #define STATUS_VARAIBLE_SHOW "js: %s, cookies: %s, hint-timeout: %d", \ 89 | GET_BOOL(c, "scripts") ? "on" : "off", \ 90 | GET_CHAR(c, "cookie-accept"), \ 91 | GET_INT(c, "hint-timeout") 92 | */ 93 | #define COOKIE GET_CHAR(c, "cookie-accept") 94 | #define CHAR_MAP(v, i, m, d) (strcmp(v, i) == 0 ? m : (d)) 95 | #define STATUS_VARAIBLE_SHOW "%c%c%c%c%c%c%c%c", \ 96 | CHAR_MAP(COOKIE, "always", 'A', CHAR_MAP(COOKIE, "origin", '@', 'a')), \ 97 | GET_BOOL(c, "dark-mode") ? 'D' : 'd', \ 98 | vb.incognito ? 'E' : 'e', \ 99 | GET_BOOL(c, "images") ? 'I' : 'i', \ 100 | GET_BOOL(c, "html5-local-storage") ? 'L' : 'l', \ 101 | GET_BOOL(c, "stylesheet") ? 'M' : 'm', \ 102 | GET_BOOL(c, "scripts") ? 'S' : 's', \ 103 | GET_BOOL(c, "strict-ssl") ? 'T' : 't' 104 | -------------------------------------------------------------------------------- /src/ex.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _EX_H 21 | #define _EX_H 22 | 23 | #include "config.h" 24 | #include "main.h" 25 | 26 | void ex_enter(Client *c); 27 | void ex_leave(Client *c); 28 | VbResult ex_keypress(Client *c, int key); 29 | void ex_input_changed(Client *c, const char *text); 30 | gboolean ex_fill_completion(GtkListStore *store, const char *input); 31 | VbCmdResult ex_run_file(Client *c, const char *filename); 32 | VbCmdResult ex_run_string(Client *c, const char *input, gboolean enable_history); 33 | 34 | #endif /* end of include guard: _EX_H */ 35 | -------------------------------------------------------------------------------- /src/ext-proxy.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "ext-proxy.h" 24 | #include "main.h" 25 | #include "webextension/ext-main.h" 26 | 27 | static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, 28 | GIOStream *stream, GCredentials *credentials, gpointer data); 29 | static gboolean on_new_connection(GDBusServer *server, 30 | GDBusConnection *connection, gpointer data); 31 | static void on_connection_close(GDBusConnection *connection, gboolean 32 | remote_peer_vanished, GError *error, gpointer data); 33 | static void on_proxy_created (GDBusProxy *proxy, GAsyncResult *result, 34 | gpointer data); 35 | static void on_vertical_scroll(GDBusConnection *connection, 36 | const char *sender_name, const char *object_path, 37 | const char *interface_name, const char *signal_name, 38 | GVariant *parameters, gpointer data); 39 | static void dbus_call(Client *c, const char *method, GVariant *param, 40 | GAsyncReadyCallback callback); 41 | static GVariant *dbus_call_sync(Client *c, const char *method, GVariant 42 | *param); 43 | static void on_web_extension_page_created(GDBusConnection *connection, 44 | const char *sender_name, const char *object_path, 45 | const char *interface_name, const char *signal_name, 46 | GVariant *parameters, gpointer data); 47 | 48 | /* TODO we need potentially multiple proxies. Because a single instance of 49 | * vimb may hold multiple clients which may use more than one webprocess and 50 | * therefore multiple webextension instances. */ 51 | extern struct Vimb vb; 52 | static GDBusServer *dbusserver; 53 | 54 | 55 | /** 56 | * Initialize the dbus proxy by watching for appearing dbus name. 57 | */ 58 | const char *ext_proxy_init(void) 59 | { 60 | char *address, *guid; 61 | GDBusAuthObserver *observer; 62 | GError *error = NULL; 63 | 64 | address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir()); 65 | guid = g_dbus_generate_guid(); 66 | observer = g_dbus_auth_observer_new(); 67 | 68 | g_signal_connect(observer, "authorize-authenticated-peer", 69 | G_CALLBACK(on_authorize_authenticated_peer), NULL); 70 | 71 | /* Use sync call because server must be starte before the web extension 72 | * attempt to connect */ 73 | dbusserver = g_dbus_server_new_sync(address, G_DBUS_SERVER_FLAGS_NONE, 74 | guid, observer, NULL, &error); 75 | 76 | if (error) { 77 | g_warning("Failed to start web extension server on %s: %s", address, error->message); 78 | g_error_free(error); 79 | goto out; 80 | } 81 | 82 | g_signal_connect(dbusserver, "new-connection", G_CALLBACK(on_new_connection), NULL); 83 | g_dbus_server_start(dbusserver); 84 | 85 | out: 86 | g_free(address); 87 | g_free(guid); 88 | g_object_unref(observer); 89 | 90 | return g_dbus_server_get_client_address(dbusserver); 91 | } 92 | 93 | /* TODO move this to a lib or somthing that can be used from ui and web 94 | * process together */ 95 | static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer, 96 | GIOStream *stream, GCredentials *credentials, gpointer data) 97 | { 98 | gboolean authorized = FALSE; 99 | 100 | if (credentials) { 101 | GCredentials *own_credentials; 102 | 103 | GError *error = NULL; 104 | own_credentials = g_credentials_new(); 105 | if (g_credentials_is_same_user(credentials, own_credentials, &error)) { 106 | authorized = TRUE; 107 | } else { 108 | g_warning("Failed to authorize web extension connection: %s", error->message); 109 | g_error_free(error); 110 | } 111 | g_object_unref(own_credentials); 112 | } else { 113 | g_warning ("No credentials received from web extension.\n"); 114 | } 115 | 116 | return authorized; 117 | } 118 | 119 | static gboolean on_new_connection(GDBusServer *server, 120 | GDBusConnection *connection, gpointer data) 121 | { 122 | /* Create dbus proxy. */ 123 | g_return_val_if_fail(G_IS_DBUS_CONNECTION(connection), FALSE); 124 | 125 | g_signal_connect(connection, "closed", G_CALLBACK(on_connection_close), NULL); 126 | 127 | g_dbus_proxy_new(connection, 128 | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES|G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, 129 | NULL, 130 | NULL, 131 | VB_WEBEXTENSION_OBJECT_PATH, 132 | VB_WEBEXTENSION_INTERFACE, 133 | NULL, 134 | (GAsyncReadyCallback)on_proxy_created, 135 | NULL); 136 | 137 | return TRUE; 138 | } 139 | 140 | static void on_connection_close(GDBusConnection *connection, gboolean 141 | remote_peer_vanished, GError *error, gpointer data) 142 | { 143 | if (error && !remote_peer_vanished) { 144 | g_warning("Unexpected lost connection to web extension: %s", error->message); 145 | } 146 | } 147 | 148 | static void on_proxy_created(GDBusProxy *new_proxy, GAsyncResult *result, 149 | gpointer data) 150 | { 151 | GError *error = NULL; 152 | GDBusProxy *proxy; 153 | GDBusConnection *connection; 154 | 155 | proxy = g_dbus_proxy_new_finish(result, &error); 156 | if (!proxy) { 157 | if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 158 | g_warning("Error creating web extension proxy: %s", error->message); 159 | } 160 | g_error_free(error); 161 | 162 | /* TODO cancel the dbus connection - use cancelable */ 163 | return; 164 | } 165 | 166 | connection = g_dbus_proxy_get_connection(proxy); 167 | g_dbus_connection_signal_subscribe(connection, NULL, 168 | VB_WEBEXTENSION_INTERFACE, "PageCreated", 169 | VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, 170 | (GDBusSignalCallback)on_web_extension_page_created, proxy, 171 | NULL); 172 | } 173 | 174 | /** 175 | * Listen to the VerticalScroll signal of the webextension and set the scroll 176 | * percent value on the client to update the statusbar. 177 | */ 178 | static void on_vertical_scroll(GDBusConnection *connection, 179 | const char *sender_name, const char *object_path, 180 | const char *interface_name, const char *signal_name, 181 | GVariant *parameters, gpointer data) 182 | { 183 | guint64 max, top; 184 | guint percent; 185 | guint64 pageid; 186 | Client *c; 187 | 188 | g_variant_get(parameters, "(ttqt)", &pageid, &max, &percent, &top); 189 | c = vb_get_client_for_page_id(pageid); 190 | if (c) { 191 | c->state.scroll_max = max; 192 | c->state.scroll_percent = percent; 193 | c->state.scroll_top = top; 194 | } 195 | 196 | vb_statusbar_update(c); 197 | } 198 | 199 | void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback) 200 | { 201 | if (callback) { 202 | dbus_call(c, "EvalJs", g_variant_new("(ts)", c->page_id, js), callback); 203 | } else { 204 | dbus_call(c, "EvalJsNoResult", g_variant_new("(ts)", c->page_id, js), NULL); 205 | } 206 | } 207 | 208 | GVariant *ext_proxy_eval_script_sync(Client *c, char *js) 209 | { 210 | return dbus_call_sync(c, "EvalJs", g_variant_new("(ts)", c->page_id, js)); 211 | } 212 | 213 | /** 214 | * Request the web extension to focus first editable element. 215 | * Returns whether an focusable element was found or not. 216 | */ 217 | void ext_proxy_focus_input(Client *c) 218 | { 219 | dbus_call(c, "FocusInput", g_variant_new("(t)", c->page_id), NULL); 220 | } 221 | 222 | /** 223 | * Send the headers string to the webextension. 224 | */ 225 | void ext_proxy_set_header(Client *c, const char *headers) 226 | { 227 | dbus_call(c, "SetHeaderSetting", g_variant_new("(s)", headers), NULL); 228 | } 229 | 230 | void ext_proxy_lock_input(Client *c, const char *element_id) 231 | { 232 | dbus_call(c, "LockInput", g_variant_new("(ts)", c->page_id, element_id), NULL); 233 | } 234 | 235 | void ext_proxy_unlock_input(Client *c, const char *element_id) 236 | { 237 | dbus_call(c, "UnlockInput", g_variant_new("(ts)", c->page_id, element_id), NULL); 238 | } 239 | 240 | /** 241 | * Returns the current selection if there is one as newly allocates string. 242 | * 243 | * Result must be freed by caller with g_free. 244 | */ 245 | char *ext_proxy_get_current_selection(Client *c) 246 | { 247 | char *selection, *js; 248 | gboolean success; 249 | GVariant *jsreturn; 250 | 251 | js = g_strdup_printf("getSelection().toString();"); 252 | jsreturn = ext_proxy_eval_script_sync(c, js); 253 | 254 | if (jsreturn == NULL) { 255 | g_warning("cannot get current selection: failed to evaluate js"); 256 | g_free(js); 257 | return NULL; 258 | } 259 | 260 | g_variant_get(jsreturn, "(bs)", &success, &selection); 261 | g_free(js); 262 | 263 | if (!success) { 264 | g_warning("can not get current selection: %s", selection); 265 | g_free(selection); 266 | return NULL; 267 | } 268 | 269 | return selection; 270 | } 271 | 272 | /** 273 | * Call a dbus method. 274 | */ 275 | static void dbus_call(Client *c, const char *method, GVariant *param, 276 | GAsyncReadyCallback callback) 277 | { 278 | /* TODO add function to queue calls until the proxy connection is 279 | * established */ 280 | if (!c->dbusproxy) { 281 | return; 282 | } 283 | g_dbus_proxy_call(c->dbusproxy, method, param, G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, c); 284 | } 285 | 286 | /** 287 | * Call a dbus method syncron. 288 | */ 289 | static GVariant *dbus_call_sync(Client *c, const char *method, GVariant *param) 290 | { 291 | GVariant *result = NULL; 292 | GError *error = NULL; 293 | 294 | if (!c->dbusproxy) { 295 | return NULL; 296 | } 297 | 298 | result = g_dbus_proxy_call_sync(c->dbusproxy, method, param, 299 | G_DBUS_CALL_FLAGS_NONE, 500, NULL, &error); 300 | 301 | if (error) { 302 | g_warning("Failed dbus method %s: %s", method, error->message); 303 | g_error_free(error); 304 | } 305 | 306 | return result; 307 | } 308 | 309 | /** 310 | * Called when the web context created the page. 311 | * 312 | * Find the right client to the page id returned from the webextension. 313 | * Add the proxy connection to the client for later calls. 314 | */ 315 | static void on_web_extension_page_created(GDBusConnection *connection, 316 | const char *sender_name, const char *object_path, 317 | const char *interface_name, const char *signal_name, 318 | GVariant *parameters, gpointer data) 319 | { 320 | Client *c; 321 | guint64 pageid; 322 | 323 | g_variant_get(parameters, "(t)", &pageid); 324 | 325 | /* Search for the client with the same page id as returned by the 326 | * webextension. */ 327 | c = vb_get_client_for_page_id(pageid); 328 | if (c) { 329 | /* Set the dbus proxy on the right client based on page id. */ 330 | c->dbusproxy = (GDBusProxy*)data; 331 | 332 | /* Subscribe to dbus signals here. */ 333 | g_dbus_connection_signal_subscribe(connection, NULL, 334 | VB_WEBEXTENSION_INTERFACE, "VerticalScroll", 335 | VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, 336 | (GDBusSignalCallback)on_vertical_scroll, NULL, NULL); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/ext-proxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _EXT_PROXY_H 21 | #define _EXT_PROXY_H 22 | 23 | #include "main.h" 24 | 25 | const char *ext_proxy_init(void); 26 | void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback); 27 | GVariant *ext_proxy_eval_script_sync(Client *c, char *js); 28 | void ext_proxy_focus_input(Client *c); 29 | void ext_proxy_set_header(Client *c, const char *headers); 30 | void ext_proxy_lock_input(Client *c, const char *element_id); 31 | void ext_proxy_unlock_input(Client *c, const char *element_id); 32 | char *ext_proxy_get_current_selection(Client *c); 33 | 34 | #endif /* end of include guard: _EXT_PROXY_H */ 35 | -------------------------------------------------------------------------------- /src/file-storage.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2019 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "file-storage.h" 26 | 27 | struct filestorage { 28 | char *file_path; 29 | gboolean readonly; 30 | GString *str; 31 | }; 32 | 33 | /** 34 | * Create new file storage instance for given directory and filename. If the 35 | * file does not exists in the directory and give mode is not 0 the file is 36 | * created with the given mode. 37 | * 38 | * The returned FileStorage must be freed by file_storage_free(). 39 | * 40 | * @dir: Directory in which the file is searched. 41 | * @filename: Filename to built the absolute path with. 42 | * @mode: Mode (file permission as chmod(2)) used for the file when 43 | * creating it. If 0 the file is not created and the storage is 44 | * used in read only mode - no data written to the file. 45 | */ 46 | FileStorage *file_storage_new(const char *dir, const char *filename, gboolean readonly) 47 | { 48 | FileStorage *storage; 49 | 50 | storage = g_slice_new(FileStorage); 51 | storage->readonly = readonly; 52 | storage->file_path = g_build_filename(dir, filename, NULL); 53 | 54 | /* Use gstring as storage in case when the file is used read only. */ 55 | if (storage->readonly) { 56 | storage->str = g_string_new(NULL); 57 | } else { 58 | storage->str = NULL; 59 | } 60 | 61 | return storage; 62 | } 63 | 64 | /** 65 | * Free memory for given file storage. 66 | */ 67 | void file_storage_free(FileStorage *storage) 68 | { 69 | if (storage) { 70 | g_free(storage->file_path); 71 | if (storage->str) { 72 | g_string_free(storage->str, TRUE); 73 | } 74 | g_slice_free(FileStorage, storage); 75 | } 76 | } 77 | 78 | /** 79 | * Append new data to file. 80 | * 81 | * @fileStorage: FileStorage to append the data to 82 | * @format: Format string used to process va_list 83 | */ 84 | gboolean file_storage_append(FileStorage *storage, const char *format, ...) 85 | { 86 | FILE *f; 87 | va_list args; 88 | 89 | g_assert(storage); 90 | 91 | /* Write data to in memory list in case the file storage is read only. */ 92 | if (storage->readonly) { 93 | va_start(args, format); 94 | g_string_append_vprintf(storage->str, format, args); 95 | va_end(args); 96 | return TRUE; 97 | } 98 | if ((f = fopen(storage->file_path, "a+"))) { 99 | flock(fileno(f), LOCK_EX); 100 | va_start(args, format); 101 | vfprintf(f, format, args); 102 | va_end(args); 103 | flock(fileno(f), LOCK_UN); 104 | fclose(f); 105 | return TRUE; 106 | } 107 | 108 | return FALSE; 109 | } 110 | 111 | /** 112 | * Retrieves all the lines from file storage. 113 | * 114 | * The result have to be freed by g_strfreev(). 115 | */ 116 | char **file_storage_get_lines(FileStorage *storage) 117 | { 118 | char *fullcontent = NULL; 119 | char *content = NULL; 120 | char **lines = NULL; 121 | 122 | g_file_get_contents(storage->file_path, &content, NULL, NULL); 123 | 124 | if (storage->str && storage->str->len) { 125 | if (content) { 126 | fullcontent = g_strconcat(content, storage->str->str, NULL); 127 | lines = g_strsplit(fullcontent, "\n", -1); 128 | g_free(fullcontent); 129 | } else { 130 | lines = g_strsplit(storage->str->str, "\n", -1); 131 | } 132 | } else { 133 | lines = g_strsplit(content ? content : "", "\n", -1); 134 | } 135 | 136 | if (content) { 137 | g_free(content); 138 | } 139 | 140 | return lines; 141 | } 142 | 143 | const char *file_storage_get_path(FileStorage *storage) 144 | { 145 | return storage->file_path; 146 | } 147 | 148 | gboolean file_storage_is_readonly(FileStorage *storage) 149 | { 150 | return storage->readonly; 151 | } 152 | -------------------------------------------------------------------------------- /src/file-storage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2019 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _FILE_STORAGE_H 21 | #define _FILE_STORAGE_H 22 | 23 | #include 24 | 25 | typedef struct filestorage FileStorage; 26 | FileStorage *file_storage_new(const char *dir, const char *filename, int mode); 27 | void file_storage_free(FileStorage *storage); 28 | gboolean file_storage_append(FileStorage *storage, const char *format, ...); 29 | char **file_storage_get_lines(FileStorage *storage); 30 | const char *file_storage_get_path(FileStorage *storage); 31 | gboolean file_storage_is_readonly(FileStorage *storage); 32 | 33 | #endif /* end of include guard: _FILE_STORAGE_H */ 34 | -------------------------------------------------------------------------------- /src/handler.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | 22 | #include "main.h" 23 | #include "handler.h" 24 | #include "util.h" 25 | 26 | extern struct Vimb vb; 27 | 28 | struct handler { 29 | GHashTable *table; /* holds the protocol handlers */ 30 | }; 31 | 32 | static char *handler_lookup(Handler *h, const char *uri); 33 | 34 | Handler *handler_new(void) 35 | { 36 | Handler *h = g_new(Handler, 1); 37 | h->table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 38 | 39 | return h; 40 | } 41 | 42 | void handler_free(Handler *h) 43 | { 44 | if (h->table) { 45 | g_hash_table_destroy(h->table); 46 | h->table = NULL; 47 | } 48 | g_free(h); 49 | } 50 | 51 | gboolean handler_add(Handler *h, const char *key, const char *cmd) 52 | { 53 | g_hash_table_insert(h->table, g_strdup(key), g_strdup(cmd)); 54 | 55 | return TRUE; 56 | } 57 | 58 | gboolean handler_remove(Handler *h, const char *key) 59 | { 60 | return g_hash_table_remove(h->table, key); 61 | } 62 | 63 | gboolean handler_handle_uri(Handler *h, const char *uri) 64 | { 65 | char *handler, *cmd; 66 | GError *error = NULL; 67 | gboolean res; 68 | 69 | if (!(handler = handler_lookup(h, uri))) { 70 | return FALSE; 71 | } 72 | 73 | cmd = g_strdup_printf(handler, uri); 74 | if (!g_spawn_command_line_async(cmd, &error)) { 75 | g_warning("Can't run '%s': %s", cmd, error->message); 76 | g_clear_error(&error); 77 | res = FALSE; 78 | } else { 79 | res = TRUE; 80 | } 81 | 82 | g_free(cmd); 83 | return res; 84 | } 85 | 86 | gboolean handler_fill_completion(Handler *h, GtkListStore *store, const char *input) 87 | { 88 | GList *src = g_hash_table_get_keys(h->table); 89 | gboolean found = util_fill_completion(store, input, src); 90 | g_list_free(src); 91 | 92 | return found; 93 | } 94 | 95 | static char *handler_lookup(Handler *h, const char *uri) 96 | { 97 | char *p, *schema, *handler = NULL; 98 | 99 | if ((p = strchr(uri, ':'))) { 100 | schema = g_strndup(uri, p - uri); 101 | handler = g_hash_table_lookup(h->table, schema); 102 | g_free(schema); 103 | } 104 | 105 | return handler; 106 | } 107 | -------------------------------------------------------------------------------- /src/handler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _HANDLERS_H 21 | #define _HANDLERS_H 22 | 23 | typedef struct handler Handler; 24 | 25 | Handler *handler_new(); 26 | void handler_free(Handler *h); 27 | gboolean handler_add(Handler *h, const char *key, const char *cmd); 28 | gboolean handler_remove(Handler *h, const char *key); 29 | gboolean handler_handle_uri(Handler *h, const char *uri); 30 | gboolean handler_fill_completion(Handler *h, GtkListStore *store, const char *input); 31 | 32 | #endif /* end of include guard: _HANDLERS_H */ 33 | 34 | -------------------------------------------------------------------------------- /src/hints.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include "config.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "hints.h" 27 | #include "main.h" 28 | #include "ascii.h" 29 | #include "command.h" 30 | #include "input.h" 31 | #include "map.h" 32 | #include "normal.h" 33 | #include "ext-proxy.h" 34 | 35 | static struct { 36 | char mode; /* mode identifying char - that last char of the hint prompt */ 37 | int promptlen; /* length of the hint prompt chars 2 or 3 */ 38 | gboolean gmode; /* indicate if the hints 'g' mode is used */ 39 | /* holds the setting if JavaScript can open windows automatically that we 40 | * have to change to open windows via hinting */ 41 | gboolean allow_open_win; 42 | gboolean allow_javascript; 43 | guint timeout_id; 44 | } hints; 45 | 46 | extern struct Vimb vb; 47 | 48 | static gboolean call_hints_function(Client *c, const char *func, const char* args, 49 | gboolean sync); 50 | static void on_hint_function_finished(GDBusProxy *proxy, GAsyncResult *result, 51 | Client *c); 52 | static gboolean hint_function_check_result(Client *c, GVariant *return_value); 53 | static void fire_timeout(Client *c, gboolean on); 54 | static gboolean fire_cb(gpointer data); 55 | 56 | 57 | VbResult hints_keypress(Client *c, int key) 58 | { 59 | if (key == KEY_CR) { 60 | hints_fire(c); 61 | 62 | return RESULT_COMPLETE; 63 | } else if (key == CTRL('H')) { /* backspace */ 64 | fire_timeout(c, FALSE); 65 | if (call_hints_function(c, "update", "null", TRUE)) { 66 | return RESULT_COMPLETE; 67 | } 68 | } else if (key == KEY_TAB) { 69 | fire_timeout(c, FALSE); 70 | hints_focus_next(c, FALSE); 71 | 72 | return RESULT_COMPLETE; 73 | } else if (key == KEY_SHIFT_TAB) { 74 | fire_timeout(c, FALSE); 75 | hints_focus_next(c, TRUE); 76 | 77 | return RESULT_COMPLETE; 78 | } else if (key == CTRL('D') || key == CTRL('F') || key == CTRL('B') || key == CTRL('U')) { 79 | return normal_keypress(c, key); 80 | } else if (key == CTRL('J') || key == CTRL('K')) { 81 | return normal_keypress(c, UNCTRL(key)); 82 | } else { 83 | fire_timeout(c, TRUE); 84 | /* try to handle the key by the javascript */ 85 | if (call_hints_function(c, "update", (char[]){'"', key, '"', '\0'}, TRUE)) { 86 | return RESULT_COMPLETE; 87 | } 88 | } 89 | 90 | fire_timeout(c, FALSE); 91 | return RESULT_ERROR; 92 | } 93 | 94 | void hints_clear(Client *c) 95 | { 96 | if (c->mode->flags & FLAG_HINTING) { 97 | c->mode->flags &= ~FLAG_HINTING; 98 | vb_input_set_text(c, ""); 99 | 100 | /* Run this sync else we would disable JavaScript before the hint is 101 | * fired. */ 102 | call_hints_function(c, "clear", "true", TRUE); 103 | 104 | /* if open window was not allowed for JavaScript, restore this */ 105 | WebKitSettings *setting = webkit_web_view_get_settings(c->webview); 106 | if (!hints.allow_open_win) { 107 | g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", hints.allow_open_win, NULL); 108 | } 109 | if (!hints.allow_javascript) { 110 | g_object_set(G_OBJECT(setting), "enable-javascript", hints.allow_javascript, NULL); 111 | } 112 | } 113 | } 114 | 115 | void hints_create(Client *c, const char *input) 116 | { 117 | char *jsargs; 118 | 119 | /* check if the input contains a valid hinting prompt */ 120 | if (!hints_parse_prompt(input, &hints.mode, &hints.gmode)) { 121 | /* if input is not valid, clear possible previous hint mode */ 122 | if (c->mode->flags & FLAG_HINTING) { 123 | vb_enter(c, 'n'); 124 | } 125 | return; 126 | } 127 | 128 | if (!(c->mode->flags & FLAG_HINTING)) { 129 | c->mode->flags |= FLAG_HINTING; 130 | 131 | WebKitSettings *setting = webkit_web_view_get_settings(c->webview); 132 | 133 | /* before we enable JavaScript to open new windows, we save the actual 134 | * value to be able restore it after hints where fired */ 135 | g_object_get(G_OBJECT(setting), 136 | "javascript-can-open-windows-automatically", &(hints.allow_open_win), 137 | "enable-javascript", &(hints.allow_javascript), 138 | NULL); 139 | 140 | /* if window open is already allowed there's no need to allow it again */ 141 | if (!hints.allow_open_win) { 142 | g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", TRUE, NULL); 143 | } 144 | /* TODO This might be a security issue to toggle JavaScript 145 | * temporarily on. */ 146 | /* This is a hack to allow window.setTimeout() and scroll observers in 147 | * hinting script which does not work when JavaScript is disabled. */ 148 | if (!hints.allow_javascript) { 149 | g_object_set(G_OBJECT(setting), "enable-javascript", TRUE, NULL); 150 | } 151 | 152 | hints.promptlen = hints.gmode ? 3 : 2; 153 | 154 | jsargs = g_strdup_printf("'%s', %s, %d, '%s', %s, %s", 155 | (char[]){hints.mode, '\0'}, 156 | hints.gmode ? "true" : "false", 157 | MAXIMUM_HINTS, 158 | GET_CHAR(c, "hint-keys"), 159 | GET_BOOL(c, "hint-follow-last") ? "true" : "false", 160 | GET_BOOL(c, "hint-keys-same-length") ? "true" : "false" 161 | ); 162 | 163 | call_hints_function(c, "init", jsargs, FALSE); 164 | g_free(jsargs); 165 | 166 | /* if hinting is started there won't be any additional filter given and 167 | * we can go out of this function */ 168 | return; 169 | } 170 | 171 | if (GET_BOOL(c, "hint-match-element")) { 172 | jsargs = g_strdup_printf("'%s'", *(input + hints.promptlen) ? input + hints.promptlen : ""); 173 | call_hints_function(c, "filter", jsargs, FALSE); 174 | g_free(jsargs); 175 | } 176 | } 177 | 178 | void hints_focus_next(Client *c, const gboolean back) 179 | { 180 | call_hints_function(c, "focus", back ? "true" : "false", FALSE); 181 | } 182 | 183 | void hints_fire(Client *c) 184 | { 185 | call_hints_function(c, "fire", "", FALSE); 186 | } 187 | 188 | void hints_follow_link(Client *c, const gboolean back, int count) 189 | { 190 | /* TODO implement outside of hints.c */ 191 | /* We would previously "piggyback" on hints.js for the "js" part of this feature 192 | * but this would actually be more elegant in its own JS file. This has nothing 193 | * to do with hints. 194 | */ 195 | /* char *json = g_strdup_printf( */ 196 | /* "[%s]", */ 197 | /* back ? vb.config.prevpattern : vb.config.nextpattern */ 198 | /* ); */ 199 | 200 | /* JSValueRef arguments[] = { */ 201 | /* js_string_to_ref(hints.ctx, back ? "prev" : "next"), */ 202 | /* js_object_to_ref(hints.ctx, json), */ 203 | /* JSValueMakeNumber(hints.ctx, count) */ 204 | /* }; */ 205 | /* g_free(json); */ 206 | 207 | /* call_hints_function(c, "followLink", 3, arguments); */ 208 | } 209 | 210 | void hints_increment_uri(Client *c, int count) 211 | { 212 | char *jsargs; 213 | 214 | jsargs = g_strdup_printf("%d", count); 215 | call_hints_function(c, "incrementUri", jsargs, FALSE); 216 | g_free(jsargs); 217 | } 218 | 219 | /** 220 | * Checks if the given hint prompt belong to a known and valid hints mode and 221 | * parses the mode and is_gmode into given pointers. 222 | * 223 | * The given prompt sting may also contain additional chars after the prompt. 224 | * 225 | * @prompt: String to be parsed as prompt. The Prompt can be followed by 226 | * additional characters. 227 | * @mode: Pointer to char that will be filled with mode char if prompt was 228 | * valid and given pointer is not NULL. 229 | * @is_gmode: Pointer to gboolean to be filled with the flag to indicate gmode 230 | * hinting. 231 | */ 232 | gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode) 233 | { 234 | gboolean res; 235 | char pmode = '\0'; 236 | #ifdef FEATURE_QUEUE 237 | static char *modes = "eiIkoOpPstTxyY"; 238 | static char *g_modes = "IopPstyY"; 239 | #else 240 | static char *modes = "eiIkoOstTxyY"; 241 | static char *g_modes = "IostyY"; 242 | #endif 243 | 244 | if (!prompt) { 245 | return FALSE; 246 | } 247 | 248 | /* get the mode identifying char from prompt */ 249 | if (*prompt == ';') { 250 | pmode = prompt[1]; 251 | } else if (*prompt == 'g' && strlen(prompt) >= 3) { 252 | /* get mode for g;X hint modes */ 253 | pmode = prompt[2]; 254 | } 255 | 256 | /* no mode found in prompt */ 257 | if (!pmode) { 258 | return FALSE; 259 | } 260 | 261 | res = *prompt == 'g' 262 | ? strchr(g_modes, pmode) != NULL 263 | : strchr(modes, pmode) != NULL; 264 | 265 | /* fill pointer only if the prompt was valid */ 266 | if (res) { 267 | if (mode != NULL) { 268 | *mode = pmode; 269 | } 270 | if (is_gmode != NULL) { 271 | *is_gmode = *prompt == 'g'; 272 | } 273 | } 274 | 275 | return res; 276 | } 277 | 278 | static gboolean call_hints_function(Client *c, const char *func, const char* args, 279 | gboolean sync) 280 | { 281 | char *jscode; 282 | /* Default value is only return in case of async call. */ 283 | gboolean success = TRUE; 284 | 285 | jscode = g_strdup_printf("hints.%s(%s);", func, args); 286 | if (sync) { 287 | GVariant *result; 288 | result = ext_proxy_eval_script_sync(c, jscode); 289 | success = hint_function_check_result(c, result); 290 | } else { 291 | ext_proxy_eval_script(c, jscode, (GAsyncReadyCallback)on_hint_function_finished); 292 | } 293 | g_free(jscode); 294 | 295 | return success; 296 | } 297 | 298 | static void on_hint_function_finished(GDBusProxy *proxy, GAsyncResult *result, 299 | Client *c) 300 | { 301 | GVariant *return_value; 302 | 303 | return_value = g_dbus_proxy_call_finish(proxy, result, NULL); 304 | hint_function_check_result(c, return_value); 305 | } 306 | 307 | static gboolean hint_function_check_result(Client *c, GVariant *return_value) 308 | { 309 | gboolean success = FALSE; 310 | char *value = NULL; 311 | 312 | if (!return_value) { 313 | goto error; 314 | } 315 | 316 | g_variant_get(return_value, "(bs)", &success, &value); 317 | if (!success || !strncmp(value, "ERROR:", 6)) { 318 | goto error; 319 | } 320 | if (!strncmp(value, "OVER:", 5)) { 321 | /* If focused elements src is given fire mouse-target-changed signal 322 | * to show its uri in the statusbar. */ 323 | if (*(value + 7)) { 324 | /* We get OVER:{I,A}:element-url so we use byte 6 to check for the 325 | * hinted element type image I or link A. */ 326 | if (*(value + 5) == 'I') { 327 | vb_statusbar_show_hover_url(c, LINK_TYPE_IMAGE, value + 7); 328 | } else { 329 | vb_statusbar_show_hover_url(c, LINK_TYPE_LINK, value + 7); 330 | } 331 | } 332 | } else if (!strncmp(value, "DONE:", 5)) { 333 | fire_timeout(c, FALSE); 334 | /* Change to normal mode only if we are currently in command mode and 335 | * we are not in g-mode hinting. This is required to not switch to 336 | * normal mode when the hinting triggered a click that set focus on 337 | * editable element that lead vimb to switch to input mode. */ 338 | if (!hints.gmode && c->mode->id == 'c') { 339 | vb_enter(c, 'n'); 340 | } 341 | /* If open in new window hinting is use, set a flag on the mode after 342 | * changing to normal mode. This is used in on_webview_decide_policy 343 | * to enforce opening into new instance for the next navigation 344 | * action. */ 345 | if (hints.mode == 't') { 346 | c->mode->flags |= FLAG_NEW_WIN; 347 | } 348 | } else if (!strncmp(value, "INSERT:", 7)) { 349 | fire_timeout(c, FALSE); 350 | vb_enter(c, 'i'); 351 | if (hints.mode == 'e') { 352 | input_open_editor(c); 353 | } 354 | } else if (!strncmp(value, "DATA:", 5)) { 355 | fire_timeout(c, FALSE); 356 | /* switch first to normal mode - else we would clear the inputbox 357 | * on switching mode also if we want to show yanked data */ 358 | if (!hints.gmode) { 359 | vb_enter(c, 'n'); 360 | } 361 | 362 | char *v = (value + 5); 363 | Arg a = {0}; 364 | /* put the hinted value into register "; */ 365 | vb_register_add(c, ';', v); 366 | switch (hints.mode) { 367 | /* used if images should be opened */ 368 | case 'i': 369 | case 'I': 370 | a.s = v; 371 | a.i = (hints.mode == 'I') ? TARGET_NEW : TARGET_CURRENT; 372 | vb_load_uri(c, &a); 373 | break; 374 | 375 | case 'O': 376 | case 'T': 377 | vb_echo(c, MSG_NORMAL, FALSE, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v); 378 | if (!hints.gmode) { 379 | vb_enter(c, 'c'); 380 | } 381 | break; 382 | 383 | case 's': 384 | a.s = v; 385 | a.i = COMMAND_SAVE_URI; 386 | command_save(c, &a); 387 | break; 388 | 389 | case 'x': 390 | map_handle_string(c, GET_CHAR(c, "x-hint-command"), TRUE); 391 | break; 392 | 393 | case 'y': 394 | case 'Y': 395 | a.i = COMMAND_YANK_ARG; 396 | a.s = v; 397 | command_yank(c, &a, c->state.current_register); 398 | break; 399 | 400 | #ifdef FEATURE_QUEUE 401 | case 'p': 402 | case 'P': 403 | a.s = v; 404 | a.i = (hints.mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH; 405 | command_queue(c, &a); 406 | break; 407 | #endif 408 | } 409 | } 410 | 411 | return TRUE; 412 | 413 | error: 414 | vb_statusbar_show_hover_url(c, LINK_TYPE_NONE, NULL); 415 | return FALSE; 416 | } 417 | 418 | static void fire_timeout(Client *c, gboolean on) 419 | { 420 | int millis; 421 | /* remove possible timeout function */ 422 | if (hints.timeout_id) { 423 | g_source_remove(hints.timeout_id); 424 | hints.timeout_id = 0; 425 | } 426 | 427 | if (on) { 428 | millis = GET_INT(c, "hint-timeout"); 429 | if (millis) { 430 | hints.timeout_id = g_timeout_add(millis, (GSourceFunc)fire_cb, c); 431 | } 432 | } 433 | } 434 | 435 | static gboolean fire_cb(gpointer data) 436 | { 437 | hints_fire(data); 438 | 439 | /* remove timeout id for the timeout that is removed by return value of 440 | * false automatic */ 441 | hints.timeout_id = 0; 442 | return FALSE; 443 | } 444 | -------------------------------------------------------------------------------- /src/hints.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _HINTS_H 21 | #define _HINTS_H 22 | 23 | #include "main.h" 24 | 25 | VbResult hints_keypress(Client *c, int key); 26 | void hints_create(Client *c, const char *input); 27 | void hints_fire(Client *c); 28 | void hints_follow_link(Client *c, gboolean back, int count); 29 | void hints_increment_uri(Client *c, int count); 30 | gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode); 31 | void hints_clear(Client *c); 32 | void hints_focus_next(Client *c, const gboolean back); 33 | 34 | #endif /* end of include guard: _HINTS_H */ 35 | -------------------------------------------------------------------------------- /src/history.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "ascii.h" 25 | #include "completion.h" 26 | #include "config.h" 27 | #include "history.h" 28 | #include "main.h" 29 | #include "util.h" 30 | #include "file-storage.h" 31 | 32 | #define HIST_STORAGE(t) (vb.storage[storage_map[t]]) 33 | typedef struct { 34 | char *first; 35 | char *second; 36 | } History; 37 | 38 | static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen); 39 | static void free_history(History *item); 40 | static History *line_to_history(const char *uri, const char *title); 41 | static GList *load(FileStorage *s); 42 | static void write_to_file(GList *list, const char *file); 43 | 44 | /* map history types to files */ 45 | static const int storage_map[HISTORY_LAST] = { 46 | STORAGE_COMMAND, 47 | STORAGE_SEARCH, 48 | STORAGE_HISTORY 49 | }; 50 | extern struct Vimb vb; 51 | 52 | /** 53 | * Write a new history entry to the end of history file. 54 | */ 55 | void history_add(Client *c, HistoryType type, const char *value, const char *additional) 56 | { 57 | FileStorage *s; 58 | 59 | /* Don't write a history entry if the history max size is set to 0. */ 60 | if (!vb.config.history_max) { 61 | return; 62 | } 63 | 64 | s = HIST_STORAGE(type); 65 | if (additional) { 66 | file_storage_append(s, "%s\t%s\n", value, additional); 67 | } else { 68 | file_storage_append(s, "%s\n", value); 69 | } 70 | } 71 | 72 | /** 73 | * Makes all history items unique and force them to fit the maximum history 74 | * size and writes all entries of the different history types to file. 75 | */ 76 | void history_cleanup(void) 77 | { 78 | FileStorage *s; 79 | GList *list; 80 | 81 | /* don't cleanup the history file if history max size is 0 */ 82 | if (!vb.config.history_max) { 83 | return; 84 | } 85 | 86 | for (HistoryType i = HISTORY_FIRST; i < HISTORY_LAST; i++) { 87 | s = HIST_STORAGE(i); 88 | if (!file_storage_is_readonly(s)) { 89 | list = load(s); 90 | write_to_file(list, file_storage_get_path(s)); 91 | g_list_free_full(list, (GDestroyNotify)free_history); 92 | } 93 | } 94 | } 95 | 96 | gboolean history_fill_completion(GtkListStore *store, HistoryType type, const char *input) 97 | { 98 | char **parts; 99 | unsigned int len; 100 | gboolean found = FALSE; 101 | GList *src = NULL; 102 | GtkTreeIter iter; 103 | History *item; 104 | 105 | src = load(HIST_STORAGE(type)); 106 | src = g_list_reverse(src); 107 | if (!input || !*input) { 108 | /* without any tags return all items */ 109 | for (GList *l = src; l; l = l->next) { 110 | item = l->data; 111 | gtk_list_store_append(store, &iter); 112 | gtk_list_store_set( 113 | store, &iter, 114 | COMPLETION_STORE_FIRST, item->first, 115 | #ifdef FEATURE_TITLE_IN_COMPLETION 116 | COMPLETION_STORE_SECOND, item->second, 117 | #endif 118 | -1 119 | ); 120 | found = TRUE; 121 | } 122 | } else if (HISTORY_URL == type) { 123 | parts = g_strsplit(input, " ", 0); 124 | len = g_strv_length(parts); 125 | 126 | for (GList *l = src; l; l = l->next) { 127 | item = l->data; 128 | if (history_item_contains_all_tags(item, parts, len)) { 129 | gtk_list_store_append(store, &iter); 130 | gtk_list_store_set( 131 | store, &iter, 132 | COMPLETION_STORE_FIRST, item->first, 133 | #ifdef FEATURE_TITLE_IN_COMPLETION 134 | COMPLETION_STORE_SECOND, item->second, 135 | #endif 136 | -1 137 | ); 138 | found = TRUE; 139 | } 140 | } 141 | g_strfreev(parts); 142 | } else { 143 | for (GList *l = src; l; l = l->next) { 144 | item = l->data; 145 | if (g_str_has_prefix(item->first, input)) { 146 | gtk_list_store_append(store, &iter); 147 | gtk_list_store_set( 148 | store, &iter, 149 | COMPLETION_STORE_FIRST, item->first, 150 | #ifdef FEATURE_TITLE_IN_COMPLETION 151 | COMPLETION_STORE_SECOND, item->second, 152 | #endif 153 | -1 154 | ); 155 | found = TRUE; 156 | } 157 | } 158 | } 159 | g_list_free_full(src, (GDestroyNotify)free_history); 160 | 161 | return found; 162 | } 163 | 164 | /** 165 | * Retrieves the list of matching history items. 166 | * The list must be freed. 167 | */ 168 | GList *history_get_list(VbInputType type, const char *query) 169 | { 170 | GList *result = NULL, *src = NULL; 171 | 172 | switch (type) { 173 | case INPUT_COMMAND: 174 | src = load(HIST_STORAGE(HISTORY_COMMAND)); 175 | break; 176 | 177 | case INPUT_SEARCH_FORWARD: 178 | case INPUT_SEARCH_BACKWARD: 179 | src = load(HIST_STORAGE(HISTORY_SEARCH)); 180 | break; 181 | 182 | default: 183 | return NULL; 184 | } 185 | 186 | /* generate new history list with the matching items */ 187 | for (GList *l = src; l; l = l->next) { 188 | History *item = l->data; 189 | if (g_str_has_prefix(item->first, query)) { 190 | result = g_list_prepend(result, g_strdup(item->first)); 191 | } 192 | } 193 | g_list_free_full(src, (GDestroyNotify)free_history); 194 | 195 | /* Prepend the original query as own item like done in vim to have the 196 | * original input string in input box if we step before the first real 197 | * item. */ 198 | result = g_list_prepend(result, g_strdup(query)); 199 | 200 | return result; 201 | } 202 | 203 | /** 204 | * Checks if the given array of tags are all found in history item. 205 | */ 206 | static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen) 207 | { 208 | unsigned int i; 209 | if (!qlen) { 210 | return TRUE; 211 | } 212 | 213 | /* iterate over all query parts */ 214 | for (i = 0; i < qlen; i++) { 215 | if (!(util_strcasestr(item->first, query[i]) 216 | || (item->second && util_strcasestr(item->second, query[i]))) 217 | ) { 218 | return FALSE; 219 | } 220 | } 221 | 222 | return TRUE; 223 | } 224 | 225 | static void free_history(History *item) 226 | { 227 | g_free(item->first); 228 | g_free(item->second); 229 | g_slice_free(History, item); 230 | } 231 | 232 | static History *line_to_history(const char *uri, const char *title) 233 | { 234 | History *item = g_slice_new0(History); 235 | 236 | item->first = g_strdup(uri); 237 | item->second = g_strdup(title); 238 | 239 | return item; 240 | } 241 | 242 | /** 243 | * Loads history items form file but eliminate duplicates in FIFO order. 244 | * 245 | * Returned list must be freed with (GDestroyNotify)free_history. 246 | */ 247 | static GList *load(FileStorage *s) 248 | { 249 | return util_strv_to_unique_list( 250 | file_storage_get_lines(s), 251 | (Util_Content_Func)line_to_history, 252 | vb.config.history_max 253 | ); 254 | } 255 | 256 | /** 257 | * Loads the entries from file, make them unique and write them back to file. 258 | */ 259 | static void write_to_file(GList *list, const char *file) 260 | { 261 | FILE *f; 262 | if ((f = fopen(file, "w"))) { 263 | flock(fileno(f), LOCK_EX); 264 | 265 | /* overwrite the history file with new unique history items */ 266 | for (GList *link = list; link; link = link->next) { 267 | History *item = link->data; 268 | if (item->second) { 269 | fprintf(f, "%s\t%s\n", item->first, item->second); 270 | } else { 271 | fprintf(f, "%s\n", item->first); 272 | } 273 | } 274 | 275 | flock(fileno(f), LOCK_UN); 276 | fclose(f); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/history.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _HISTORY_H 21 | #define _HISTORY_H 22 | 23 | #include 24 | 25 | #include "main.h" 26 | 27 | typedef enum { 28 | HISTORY_FIRST = 0, 29 | HISTORY_COMMAND = 0, 30 | HISTORY_SEARCH, 31 | HISTORY_URL, 32 | HISTORY_LAST 33 | } HistoryType; 34 | 35 | void history_add(Client *c, HistoryType type, const char *value, const char *additional); 36 | void history_cleanup(void); 37 | gboolean history_fill_completion(GtkListStore *store, HistoryType type, const char *input); 38 | GList *history_get_list(VbInputType type, const char *query); 39 | 40 | #endif /* end of include guard: _HISTORY_H */ 41 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "ascii.h" 25 | #include "command.h" 26 | #include "config.h" 27 | #include "input.h" 28 | #include "main.h" 29 | #include "normal.h" 30 | #include "util.h" 31 | #include "scripts/scripts.h" 32 | #include "ext-proxy.h" 33 | 34 | typedef struct { 35 | char *element_id; 36 | unsigned long element_map_key; 37 | } ElementEditorData; 38 | 39 | static void input_editor_formfiller(const char *text, Client *c, gpointer data); 40 | 41 | /** 42 | * Function called when vimb enters the input mode. 43 | */ 44 | void input_enter(Client *c) 45 | { 46 | /* switch focus first to make sure we can write to the inputbox without 47 | * disturbing the user */ 48 | gtk_widget_grab_focus(GTK_WIDGET(c->webview)); 49 | vb_modelabel_update(c, "-- INPUT --"); 50 | ext_proxy_eval_script(c, "var vimb_input_mode_element = document.activeElement;", NULL); 51 | } 52 | 53 | /** 54 | * Called when the input mode is left. 55 | */ 56 | void input_leave(Client *c) 57 | { 58 | ext_proxy_eval_script(c, "vimb_input_mode_element.blur();", NULL); 59 | vb_modelabel_update(c, ""); 60 | } 61 | 62 | /** 63 | * Handles the keypress events from webview and inputbox. 64 | */ 65 | VbResult input_keypress(Client *c, int key) 66 | { 67 | static gboolean ctrlo = FALSE; 68 | 69 | if (ctrlo) { 70 | /* if we are in ctrl-O mode perform the next keys as normal mode 71 | * commands until the command is complete or error */ 72 | VbResult res = normal_keypress(c, key); 73 | if (res != RESULT_MORE) { 74 | ctrlo = FALSE; 75 | /* Don't overwrite the mode label in case we landed in another 76 | * mode. This might occurre by CTRL-0 CTRL-Z or after running ex 77 | * command, where we mainly end up in normal mode. */ 78 | if (c->mode->id == 'i') { 79 | /* reenter the input mode */ 80 | input_enter(c); 81 | } 82 | } 83 | return res; 84 | } 85 | 86 | switch (key) { 87 | case CTRL('['): /* esc */ 88 | vb_enter(c, 'n'); 89 | return RESULT_COMPLETE; 90 | 91 | case CTRL('O'): 92 | /* enter CTRL-0 mode to execute next command in normal mode */ 93 | ctrlo = TRUE; 94 | c->mode->flags |= FLAG_NOMAP; 95 | vb_modelabel_update(c, "-- (input) --"); 96 | return RESULT_MORE; 97 | 98 | case CTRL('T'): 99 | return input_open_editor(c); 100 | 101 | case CTRL('Z'): 102 | vb_enter(c, 'p'); 103 | return RESULT_COMPLETE; 104 | } 105 | 106 | c->state.processed_key = FALSE; 107 | return RESULT_ERROR; 108 | } 109 | 110 | VbResult input_open_editor(Client *c) 111 | { 112 | static unsigned long element_map_key = 0; 113 | char *element_id = NULL; 114 | char *text = NULL, *id = NULL; 115 | gboolean success; 116 | GVariant *jsreturn; 117 | GVariant *idreturn; 118 | ElementEditorData *data = NULL; 119 | 120 | g_assert(c); 121 | 122 | /* get the selected input element */ 123 | jsreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.value"); 124 | g_variant_get(jsreturn, "(bs)", &success, &text); 125 | 126 | if (!success || !text) { 127 | return RESULT_ERROR; 128 | } 129 | 130 | idreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.id"); 131 | g_variant_get(idreturn, "(bs)", &success, &id); 132 | 133 | /* Special case: the input element does not have an id assigned to it */ 134 | if (!success || !*id) { 135 | char *js_command = g_strdup_printf(JS_SET_EDITOR_MAP_ELEMENT, ++element_map_key); 136 | ext_proxy_eval_script(c, js_command, NULL); 137 | g_free(js_command); 138 | } else { 139 | element_id = g_strdup(id); 140 | } 141 | 142 | data = g_slice_new0(ElementEditorData); 143 | data->element_id = element_id; 144 | data->element_map_key = element_map_key; 145 | 146 | if (command_spawn_editor(c, &((Arg){0, text}), input_editor_formfiller, data)) { 147 | /* disable the active element */ 148 | ext_proxy_lock_input(c, element_id); 149 | 150 | return RESULT_COMPLETE; 151 | } 152 | 153 | g_free(element_id); 154 | g_slice_free(ElementEditorData, data); 155 | return RESULT_ERROR; 156 | } 157 | 158 | static void input_editor_formfiller(const char *text, Client *c, gpointer data) 159 | { 160 | char *escaped; 161 | char *jscode; 162 | char *jscode_enable; 163 | ElementEditorData *eed = (ElementEditorData *)data; 164 | 165 | if (text) { 166 | escaped = util_strescape(text, NULL); 167 | 168 | /* put the text back into the element */ 169 | if (eed->element_id && strlen(eed->element_id) > 0) { 170 | jscode = g_strdup_printf("document.getElementById(\"%s\").value=\"%s\"", eed->element_id, escaped); 171 | } else { 172 | jscode = g_strdup_printf("vimb_editor_map.get(\"%lu\").value=\"%s\"", eed->element_map_key, escaped); 173 | } 174 | 175 | ext_proxy_eval_script(c, jscode, NULL); 176 | 177 | g_free(jscode); 178 | g_free(escaped); 179 | } 180 | 181 | if (eed->element_id && strlen(eed->element_id) > 0) { 182 | ext_proxy_unlock_input(c, eed->element_id); 183 | } else { 184 | jscode_enable = g_strdup_printf(JS_FOCUS_EDITOR_MAP_ELEMENT, 185 | eed->element_map_key, eed->element_map_key); 186 | ext_proxy_eval_script(c, jscode_enable, NULL); 187 | g_free(jscode_enable); 188 | } 189 | 190 | g_free(eed->element_id); 191 | g_slice_free(ElementEditorData, eed); 192 | } 193 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _INPUT_H 21 | #define _INPUT_H 22 | 23 | #include "config.h" 24 | #include "main.h" 25 | 26 | void input_enter(Client *c); 27 | void input_leave(Client *c); 28 | VbResult input_keypress(Client *c, int key); 29 | VbResult input_open_editor(Client *c); 30 | 31 | #endif /* end of include guard: _INPUT_H */ 32 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _MAIN_H 21 | #define _MAIN_H 22 | 23 | #include 24 | #include "config.h" 25 | #ifndef FEATURE_NO_XEMBED 26 | #include 27 | #endif 28 | #include 29 | #include 30 | #include "shortcut.h" 31 | #include "handler.h" 32 | #include "file-storage.h" 33 | 34 | 35 | #define LENGTH(x) (sizeof x / sizeof x[0]) 36 | #define OVERWRITE_STRING(t, s) {if (t) g_free(t); t = g_strdup(s);} 37 | #define OVERWRITE_NSTRING(t, s, l) {if (t) {g_free(t); t = NULL;} t = g_strndup(s, l);} 38 | #define GET_CHAR(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.s) 39 | #define GET_INT(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.i) 40 | #define GET_BOOL(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.b) 41 | 42 | 43 | #ifdef DEBUG 44 | #define PRINT_DEBUG(...) { \ 45 | fprintf(stderr, "\n\033[31;1mDEBUG: \033[32;1m%s +%d %s()\033[0m\t", __FILE__, __LINE__, __func__); \ 46 | fprintf(stderr, __VA_ARGS__);\ 47 | } 48 | #define TIMER_START GTimer *__timer; {__timer = g_timer_new(); g_timer_start(__timer);} 49 | #define TIMER_END {gdouble __debug_elapsed = g_timer_elapsed(__timer, NULL);\ 50 | PRINT_DEBUG("\033[33mtimer:\033[0m %fs", __debug_elapsed);\ 51 | g_timer_destroy(__timer); \ 52 | } 53 | #else 54 | #define PRINT_DEBUG(message, ...) 55 | #define TIMER_START 56 | #define TIMER_END 57 | #endif 58 | 59 | /* the special mark ' must be the first in the list for easiest lookup */ 60 | #define MARK_CHARS "'abcdefghijklmnopqrstuvwxyz" 61 | #define MARK_TICK 0 62 | #define MARK_SIZE (sizeof(MARK_CHARS) - 1) 63 | 64 | #define GLOBAL_MARK_CHARS "'ABCDEFGHIJKLMNOPQRSTUVWXYZ" 65 | 66 | #define USER_REG "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 67 | /* registers in order displayed for :register command */ 68 | #define REG_CHARS "\"" USER_REG ":%/;" 69 | #define REG_SIZE (sizeof(REG_CHARS) - 1) 70 | 71 | #define FILE_CLOSED "closed" 72 | #define FILE_COOKIES "cookies" 73 | 74 | enum { TARGET_CURRENT, TARGET_RELATED, TARGET_NEW }; 75 | 76 | typedef enum { 77 | RESULT_COMPLETE, RESULT_MORE, RESULT_ERROR 78 | } VbResult; 79 | 80 | typedef enum { 81 | CMD_ERROR, /* command could not be parses or executed */ 82 | CMD_SUCCESS = 0x01, /* command runned successfully */ 83 | CMD_KEEPINPUT = 0x02, /* don't clear inputbox after command run */ 84 | } VbCmdResult; 85 | 86 | typedef enum { 87 | TYPE_BOOLEAN, TYPE_INTEGER, TYPE_CHAR, TYPE_COLOR, TYPE_FONT 88 | } DataType; 89 | 90 | typedef enum { 91 | MSG_NORMAL, MSG_ERROR 92 | } MessageType; 93 | 94 | typedef enum { 95 | STATUS_NORMAL, STATUS_SSL_VALID, STATUS_SSL_INVALID 96 | } StatusType; 97 | 98 | typedef enum { 99 | INPUT_UNKNOWN, 100 | INPUT_SET = 0x01, 101 | INPUT_OPEN = 0x02, 102 | INPUT_TABOPEN = 0x04, 103 | INPUT_COMMAND = 0x08, 104 | INPUT_SEARCH_FORWARD = 0x10, 105 | INPUT_SEARCH_BACKWARD = 0x20, 106 | INPUT_BOOKMARK_ADD = 0x40, 107 | INPUT_ALL = 0xff, /* map to match all input types */ 108 | } VbInputType; 109 | 110 | enum { 111 | FILES_BOOKMARK, 112 | FILES_CLOSED, 113 | FILES_CONFIG, 114 | FILES_COOKIE, 115 | FILES_QUEUE, 116 | FILES_SCRIPT, 117 | FILES_USER_STYLE, 118 | FILES_LAST 119 | }; 120 | 121 | enum { 122 | STORAGE_CLOSED, 123 | STORAGE_COMMAND, 124 | STORAGE_CONFIG, 125 | STORAGE_HISTORY, 126 | STORAGE_SEARCH, 127 | STORAGE_LAST 128 | }; 129 | 130 | typedef enum { 131 | LINK_TYPE_NONE, 132 | LINK_TYPE_LINK, 133 | LINK_TYPE_IMAGE, 134 | } VbLinkType; 135 | 136 | typedef struct Client Client; 137 | typedef struct State State; 138 | typedef struct Map Map; 139 | typedef struct Mode Mode; 140 | typedef struct Arg Arg; 141 | typedef void (*ModeTransitionFunc)(Client*); 142 | typedef VbResult (*ModeKeyFunc)(Client*, int); 143 | typedef void (*ModeInputChangedFunc)(Client*, const char*); 144 | 145 | struct Arg { 146 | int i; 147 | char *s; 148 | }; 149 | 150 | typedef int (*SettingFunction)(Client *c, const char *name, DataType type, void *value, void *data); 151 | typedef union { 152 | gboolean b; 153 | int i; 154 | char *s; 155 | } SettingValue; 156 | 157 | typedef struct { 158 | const char *name; 159 | int type; 160 | SettingValue value; 161 | SettingFunction setter; 162 | int flags; 163 | void *data; /* data given to the setter */ 164 | } Setting; 165 | 166 | struct State { 167 | char *uri; 168 | gboolean typed; /* indicates if the user typed the keys */ 169 | gboolean processed_key; /* indicates if a key press was handled and should not bubbled up */ 170 | gboolean ctrlv; /* indicates if the CTRL-V temorary submode is on */ 171 | 172 | #define PROMPT_SIZE 4 173 | char prompt[PROMPT_SIZE];/* current prompt ':', 'g;t', '/' including nul */ 174 | guint64 marks[MARK_SIZE]; /* holds marks set to page with 'm{markchar}' */ 175 | guint input_timer; 176 | MessageType input_type; 177 | StatusType status_type; 178 | guint64 scroll_max; /* Maxmimum scrollable height of the document. */ 179 | guint scroll_percent; /* Current position of the viewport in document (percent). */ 180 | guint64 scroll_top; /* Current position of the viewport in document (pixel). */ 181 | char *title; /* Window title of the client. */ 182 | 183 | char *reg[REG_SIZE]; /* holds the yank buffers */ 184 | /* TODO rename to reg_{enabled,current} */ 185 | gboolean enable_register; /* indicates if registers are filled */ 186 | char current_register; /* holds char for current register to be used */ 187 | 188 | GList *downloads; 189 | guint progress; 190 | WebKitHitTestResult *hit_test_result; 191 | gboolean is_fullscreen; 192 | 193 | struct { 194 | gboolean active; /* indicate if there is a active search */ 195 | short direction; /* last direction 1 forward, -1 backward */ 196 | int matches; /* number of matching search results */ 197 | char *last_query; /* last search query */ 198 | } search; 199 | struct { 200 | guint64 pos; 201 | char *uri; 202 | } global_marks[MARK_SIZE]; 203 | }; 204 | 205 | struct Map { 206 | char *in; /* input keys */ 207 | int inlen; /* length of the input keys */ 208 | char *mapped; /* mapped keys */ 209 | int mappedlen; /* length of the mapped keys string */ 210 | char mode; /* mode for which the map is available */ 211 | gboolean remap; /* if FALSE do not remap the {rhs} of this map */ 212 | }; 213 | 214 | struct Mode { 215 | char id; 216 | ModeTransitionFunc enter; /* is called if the mode is entered */ 217 | ModeTransitionFunc leave; /* is called if the mode is left */ 218 | ModeKeyFunc keypress; /* receives key to process */ 219 | ModeInputChangedFunc input_changed; /* is triggered if input textbuffer is changed */ 220 | #define FLAG_NOMAP 0x0001 /* disables mapping for key strokes */ 221 | #define FLAG_HINTING 0x0002 /* marks active hinting submode */ 222 | #define FLAG_COMPLETION 0x0004 /* marks active completion submode */ 223 | #define FLAG_PASSTHROUGH 0x0008 /* don't handle any other keybind than */ 224 | #define FLAG_NEW_WIN 0x0010 /* enforce opening of pages into new window */ 225 | #define FLAG_IGNORE_FOCUS 0x0012 /* do not listen for focus change messages */ 226 | unsigned int flags; 227 | }; 228 | 229 | struct Statusbar { 230 | GtkBox *box; 231 | GtkWidget *mode, *left, *right, *cmd; 232 | }; 233 | 234 | struct AuGroup; 235 | 236 | struct Client { 237 | struct Client *next; 238 | struct State state; 239 | struct Statusbar statusbar; 240 | void *comp; /* pointer to data used in completion.c */ 241 | Mode *mode; /* current active browser mode */ 242 | /* WebKitWebContext *webctx; */ /* not used atm, use webkit_web_context_get_default() instead */ 243 | GtkWidget *window, *input; 244 | WebKitWebView *webview; 245 | WebKitFindController *finder; 246 | WebKitWebInspector *inspector; 247 | guint64 page_id; /* page id of the webview */ 248 | GtkTextBuffer *buffer; 249 | GDBusProxy *dbusproxy; 250 | GDBusServer *dbusserver; 251 | Handler *handler; /* the protocoll handlers */ 252 | struct { 253 | /* TODO split in global setting definitions and set values on a per 254 | * client base. */ 255 | GHashTable *settings; 256 | guint scrollstep; 257 | guint scrollmultiplier; 258 | gboolean input_autohide; 259 | gboolean incsearch; 260 | gboolean prevent_newwindow; 261 | guint default_zoom; /* default zoom level in percent */ 262 | Shortcut *shortcuts; 263 | gboolean statusbar_show_settings; 264 | } config; 265 | struct { 266 | GSList *list; 267 | GString *queue; /* queue holding typed keys */ 268 | int qlen; /* pointer to last char in queue */ 269 | int resolved; /* number of resolved keys (no mapping required) */ 270 | guint timout_id; /* source id of the timeout function */ 271 | char showcmd[SHOWCMD_LEN + 1]; /* buffer to show ambiguous key sequence */ 272 | guint timeoutlen; /* timeout for ambiguous mappings */ 273 | } map; 274 | struct { 275 | struct AuGroup *curgroup; 276 | GSList *groups; 277 | guint usedbits; /* holds all used event bits */ 278 | } autocmd; 279 | }; 280 | 281 | struct Vimb { 282 | char *argv0; 283 | Client *clients; 284 | #ifndef FEATURE_NO_XEMBED 285 | Window embed; 286 | #endif 287 | GHashTable *modes; /* all available browser main modes */ 288 | char *configfile; /* config file given as option on startup */ 289 | char *files[FILES_LAST]; 290 | FileStorage *storage[STORAGE_LAST]; 291 | char *profile; /* profile name */ 292 | GSList *cmdargs; /* ex commands given asl --command, -C option */ 293 | struct { 294 | guint history_max; 295 | guint closed_max; 296 | } config; 297 | GtkCssProvider *style_provider; 298 | gboolean no_maximize; 299 | gboolean incognito; 300 | 301 | WebKitWebContext *webcontext; 302 | }; 303 | 304 | gboolean vb_download_set_destination(Client *c, WebKitDownload *download, 305 | char *suggested_filename, const char *path); 306 | void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...); 307 | void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...); 308 | void vb_enter(Client *c, char id); 309 | void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt); 310 | Client *vb_get_client_for_page_id(guint64 pageid); 311 | char *vb_input_get_text(Client *c); 312 | void vb_input_set_text(Client *c, const char *text); 313 | void vb_input_update_style(Client *c); 314 | gboolean vb_load_uri(Client *c, const Arg *arg); 315 | void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave, 316 | ModeKeyFunc keypress, ModeInputChangedFunc input_changed); 317 | VbResult vb_mode_handle_key(Client *c, int key); 318 | void vb_modelabel_update(Client *c, const char *label); 319 | gboolean vb_quit(Client *c, gboolean force); 320 | void vb_register_add(Client *c, char buf, const char *value); 321 | const char *vb_register_get(Client *c, char buf); 322 | void vb_statusbar_update(Client *c); 323 | void vb_statusbar_show_hover_url(Client *c, VbLinkType type, const char *uri); 324 | void vb_gui_style_update(Client *c, const char *name, const char *value); 325 | 326 | #endif /* end of include guard: _MAIN_H */ 327 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _MAP_H 21 | #define _MAP_H 22 | 23 | typedef enum { 24 | MAP_DONE, 25 | MAP_AMBIGUOUS, 26 | MAP_NOMATCH 27 | } MapState; 28 | 29 | void map_init(Client *c); 30 | void map_cleanup(Client *c); 31 | MapState map_handle_keys(Client *c, const guchar *keys, int keylen, gboolean use_map); 32 | void map_handle_string(Client *c, const char *str, gboolean use_map); 33 | void map_insert(Client *c, const char *in, const char *mapped, char mode, gboolean remap); 34 | gboolean map_delete(Client *c, const char *in, char mode); 35 | gboolean on_map_keypress(GtkWidget *widget, GdkEventKey* event, Client *c); 36 | 37 | #endif /* end of include guard: _MAP_H */ 38 | -------------------------------------------------------------------------------- /src/normal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _NORMAL_H 21 | #define _NORMAL_H 22 | 23 | #include "config.h" 24 | #include "main.h" 25 | 26 | void normal_enter(Client *c); 27 | void normal_leave(Client *c); 28 | VbResult normal_keypress(Client *c, int key); 29 | void pass_enter(Client *c); 30 | void pass_leave(Client *c); 31 | VbResult pass_keypress(Client *c, int key); 32 | 33 | #endif /* end of include guard: _NORMAL_H */ 34 | -------------------------------------------------------------------------------- /src/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | scripts.h 2 | -------------------------------------------------------------------------------- /src/scripts/focus_editor_map_element.js: -------------------------------------------------------------------------------- 1 | vimb_editor_map.get("%lu").disabled=false; 2 | vimb_editor_map.get("%lu").focus(); 3 | -------------------------------------------------------------------------------- /src/scripts/hints.css: -------------------------------------------------------------------------------- 1 | span[vimbhint^='label']{ 2 | background-color:#fff; 3 | border:1px solid #444; 4 | color:#000; 5 | font:bold .8em monospace; 6 | margin:0; 7 | opacity:0.7; 8 | padding:0px 1px; 9 | position:fixed !important; 10 | z-index:225000 11 | } 12 | *[vimbhint^='hint']{ 13 | background-color:#ff0 !important; 14 | color:#000 !important; 15 | transition-delay:all 0 !important; 16 | transition:all 0 !important; 17 | opacity:1 !important 18 | } 19 | *[vimbhint='hint focus']{ 20 | background-color:#8f0 !important 21 | } 22 | span[vimbhint='label focus']{ 23 | opacity:1; 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/increment_uri_number.js: -------------------------------------------------------------------------------- 1 | /* TODO maybe it's better to inject this in the webview as method */ 2 | /* and to call it if needed */ 3 | var c = %d, on, nn, m = location.href.match(/(.*?)(\d+)(\D*)$/); 4 | if (m) { 5 | on = m[2]; 6 | nn = String(Math.max(parseInt(on) + c, 0)); 7 | /* keep prepending zeros */ 8 | if (/^0/.test(on)) { 9 | while (nn.length < on.length) { 10 | nn = "0" + nn; 11 | } 12 | } 13 | m[2] = nn; 14 | 15 | location.href = m.slice(1).join(""); 16 | } 17 | -------------------------------------------------------------------------------- /src/scripts/js2h.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Defined a constant for given JavaScript or CSS file. The file name is used 4 | # to get the constant name and the contents are minifed and escaped as the 5 | # value. ./js.h do_fancy_stuff.js creates somethings like 6 | # #define JS_DO_FANCY_STUFF "Escaped JavaScriptCode" 7 | 8 | FILE="$1" 9 | 10 | # Check if the file exists. 11 | if [ ! -r "$FILE" ]; then 12 | echo "File $FILE does not exist or is not readable" >&2 13 | exit 1 14 | fi 15 | 16 | # Put file extension and _ before file name, turn all to upper case to get the 17 | # constant name. 18 | CONSTANT=$(echo "$FILE" | sed -e 's:.*/::g' -e 's/.*\.css$/CSS_&/g' -e 's/.*\.js$/JS_&/g' -e 's/\.css$//' -e 's/\.js$//' | tr a-z A-Z) 19 | 20 | # minify the script 21 | cat $FILE | \ 22 | # remove single line comments 23 | sed -e 's|^//.*$||g' | \ 24 | # remove linebreaks 25 | tr '\n\r' ' ' | \ 26 | # remove unneeded whitespace 27 | sed -e 's:/\*[^*]*\*/::g' \ 28 | -e 's|[ ]\{2,\}| |g' \ 29 | -e 's|^ ||g' \ 30 | -e "s|[ ]\{0,\}\([-*[%/!?<>:=(){};+\&\"',\|]\)[ ]\{0,\}|\1|g" \ 31 | -e 's|"+"||g' | \ 32 | # ecaspe 33 | sed -e 's|\\x20| |g' \ 34 | -e 's|\\|\\\\|g' \ 35 | -e 's|"|\\"|g' | \ 36 | # write opener with the starting and ending quote char 37 | sed -e "1s/^/#define $CONSTANT \"/" \ 38 | -e '$s/$/"/' 39 | echo "" 40 | -------------------------------------------------------------------------------- /src/scripts/scroll.js: -------------------------------------------------------------------------------- 1 | function vbscroll(mode, scrollStep, count) { 2 | var w = window, 3 | d = document, 4 | x = y = 0, 5 | ph = w.innerHeight, 6 | de = d.documentElement, 7 | c = count||1, 8 | rel = true; 9 | switch (mode) { 10 | case 'j': 11 | y = c * scrollStep; 12 | break; 13 | case 'h': 14 | x = -c * scrollStep; 15 | break; 16 | case 'k': 17 | y = -c * scrollStep; 18 | break; 19 | case 'l': 20 | x = c * scrollStep; 21 | break; 22 | case '\x04': /* ^D */ 23 | y = c * ph / 2; 24 | break; 25 | case '\x15': /* ^U */ 26 | y = -c * ph / 2; 27 | break; 28 | case '\x06': /* ^F */ 29 | y = c * ph; 30 | break; 31 | case '\x02': /* ^B */ 32 | y = -c * ph; 33 | break; 34 | case 'G': /* fall through - gg and G differ only in y value when no count is given */ 35 | case 'g': 36 | x = w.scrollX; 37 | if (count) { 38 | y = c * ((d.documentElement.scrollHeight - ph) / 100); 39 | } else { 40 | y = 'G' == mode 41 | ? Math.max( 42 | d.body.scrollHeight, de.scrollHeight, 43 | d.body.offsetHeight, de.offsetHeight, 44 | d.body.clientHeight, de.clientHeight 45 | ) 46 | : 0; 47 | } 48 | rel = false; 49 | break; 50 | case '0': 51 | y = w.scrollY; 52 | rel = false; 53 | break; 54 | case '$': 55 | x = d.body.scrollWidth; 56 | y = w.scrollY; 57 | rel = false; 58 | break; 59 | default: 60 | return 1; 61 | } 62 | if (rel) { 63 | w.scrollBy(x, y); 64 | } else { 65 | w.scroll(x, y); 66 | } 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /src/scripts/set_editor_map_element.js: -------------------------------------------------------------------------------- 1 | if (typeof(vimb_editor_map) !== 'object') { 2 | var vimb_editor_map = new Map; 3 | } 4 | vimb_editor_map.set("%lu", vimb_input_mode_element); 5 | -------------------------------------------------------------------------------- /src/setting.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _SETTING_H 21 | #define _SETTING_H 22 | 23 | #include 24 | 25 | #include "main.h" 26 | 27 | void setting_init(Client *c); 28 | void setting_cleanup(Client *c); 29 | VbCmdResult setting_run(Client *c, char *name, const char *param); 30 | gboolean setting_fill_completion(Client *c, GtkListStore *store, const char *input); 31 | 32 | #endif /* end of include guard: _SETTING_H */ 33 | -------------------------------------------------------------------------------- /src/shortcut.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "ascii.h" 24 | #include "main.h" 25 | #include "shortcut.h" 26 | #include "util.h" 27 | 28 | struct shortcut { 29 | GHashTable *table; 30 | char *fallback; /* default shortcut to use if none given in request */ 31 | }; 32 | 33 | extern struct Vimb vb; 34 | 35 | static int get_max_placeholder(const char *str); 36 | static const char *shortcut_lookup(Shortcut *sc, const char *string, const char **query); 37 | 38 | Shortcut *shortcut_new(void) 39 | { 40 | Shortcut *sc = g_new(Shortcut, 1); 41 | sc->table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 42 | sc->fallback = NULL; 43 | 44 | return sc; 45 | } 46 | 47 | void shortcut_free(Shortcut *sc) 48 | { 49 | if (sc->table) { 50 | g_hash_table_destroy(sc->table); 51 | } 52 | g_free(sc); 53 | } 54 | 55 | gboolean shortcut_add(Shortcut *sc, const char *key, const char *uri) 56 | { 57 | g_hash_table_insert(sc->table, g_strdup(key), g_strdup(uri)); 58 | 59 | return TRUE; 60 | } 61 | 62 | gboolean shortcut_remove(Shortcut *sc, const char *key) 63 | { 64 | return g_hash_table_remove(sc->table, key); 65 | } 66 | 67 | gboolean shortcut_set_default(Shortcut *sc, const char *key) 68 | { 69 | /* do not check if the shortcut exists to be able to set the default 70 | * before defining the shortcut */ 71 | OVERWRITE_STRING(sc->fallback, key); 72 | 73 | return TRUE; 74 | } 75 | 76 | /** 77 | * Retrieves the uri for given query string. Not that the memory of the 78 | * returned uri must be freed. 79 | */ 80 | char *shortcut_get_uri(Shortcut *sc, const char *string) 81 | { 82 | const char *tmpl, *query = NULL; 83 | char *uri, *quoted_param; 84 | int max_num, current_num; 85 | GString *token; 86 | 87 | tmpl = shortcut_lookup(sc, string, &query); 88 | if (!tmpl) { 89 | return NULL; 90 | } 91 | 92 | max_num = get_max_placeholder(tmpl); 93 | /* if there are only $0 placeholders we don't need to split the parameters */ 94 | if (max_num == 0) { 95 | quoted_param = g_uri_escape_string(query, NULL, TRUE); 96 | uri = util_str_replace("$0", quoted_param, tmpl); 97 | g_free(quoted_param); 98 | 99 | return uri; 100 | } 101 | 102 | uri = g_strdup(tmpl); 103 | 104 | /* skip if no placeholders found */ 105 | if (max_num < 0) { 106 | return uri; 107 | } 108 | 109 | current_num = 0; 110 | token = g_string_new(NULL); 111 | while (*query) { 112 | /* parse the query tokens */ 113 | if (*query == '"' || *query == '\'') { 114 | /* save the last used quote char to find it's matching counterpart */ 115 | char last_quote = *query; 116 | 117 | /* skip the quote */ 118 | query++; 119 | /* collect the char until the closing quote or end of string */ 120 | while (*query && *query != last_quote) { 121 | g_string_append_c(token, *query); 122 | query++; 123 | } 124 | /* if we end up at the closing quote - skip this quote too */ 125 | if (*query == last_quote) { 126 | query++; 127 | } 128 | } else if (VB_IS_SPACE(*query)) { 129 | /* skip whitespace */ 130 | query++; 131 | 132 | continue; 133 | } else if (current_num >= max_num) { 134 | /* if we have parsed as many params like placeholders - put the 135 | * rest of the query as last parameter */ 136 | while (*query) { 137 | g_string_append_c(token, *query); 138 | query++; 139 | } 140 | } else { 141 | /* collect the following character up to the next whitespace */ 142 | while (*query && !VB_IS_SPACE(*query)) { 143 | g_string_append_c(token, *query); 144 | query++; 145 | } 146 | } 147 | 148 | /* replace the placeholders with parsed token */ 149 | if (token->len) { 150 | char *new; 151 | 152 | quoted_param = g_uri_escape_string(token->str, NULL, TRUE); 153 | new = util_str_replace((char[]){'$', current_num + '0', '\0'}, quoted_param, uri); 154 | g_free(quoted_param); 155 | g_free(uri); 156 | uri = new; 157 | 158 | /* truncate the last token to fill for next loop */ 159 | g_string_truncate(token, 0); 160 | } 161 | current_num++; 162 | } 163 | g_string_free(token, TRUE); 164 | 165 | return uri; 166 | } 167 | 168 | gboolean shortcut_fill_completion(Shortcut *sc, GtkListStore *store, const char *input) 169 | { 170 | GList *src = g_hash_table_get_keys(sc->table); 171 | gboolean found = util_fill_completion(store, input, src); 172 | g_list_free(src); 173 | 174 | return found; 175 | } 176 | 177 | /** 178 | * Retrieves th highest placeholder number used in given string. 179 | * If no placeholder is found -1 is returned. 180 | */ 181 | static int get_max_placeholder(const char *str) 182 | { 183 | int n, res; 184 | 185 | for (n = 0, res = -1; *str; str++) { 186 | if (*str == '$') { 187 | n = *(++str) - '0'; 188 | if (0 <= n && n <= 9 && n > res) { 189 | res = n; 190 | } 191 | } 192 | } 193 | 194 | return res; 195 | } 196 | 197 | /** 198 | * Retrieves the shortcut uri template for given string. And fills given query 199 | * pointer with the query part of the given string (everything except of the 200 | * shortcut identifier). 201 | */ 202 | static const char *shortcut_lookup(Shortcut *sc, const char *string, const char **query) 203 | { 204 | char *p, *uri = NULL; 205 | 206 | if ((p = strchr(string, ' '))) { 207 | char *key = g_strndup(string, p - string); 208 | /* is the first word might be a shortcut */ 209 | if ((uri = g_hash_table_lookup(sc->table, key))) { 210 | *query = p + 1; 211 | } 212 | g_free(key); 213 | } else { 214 | uri = g_hash_table_lookup(sc->table, string); 215 | } 216 | 217 | if (!uri && sc->fallback 218 | && (uri = g_hash_table_lookup(sc->table, sc->fallback))) { 219 | *query = string; 220 | } 221 | 222 | return uri; 223 | } 224 | -------------------------------------------------------------------------------- /src/shortcut.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _SHORTCUT_H 21 | #define _SHORTCUT_H 22 | 23 | typedef struct shortcut Shortcut; 24 | 25 | Shortcut *shortcut_new(void); 26 | void shortcut_free(Shortcut *sc); 27 | gboolean shortcut_add(Shortcut *sc, const char *key, const char *uri); 28 | gboolean shortcut_remove(Shortcut *sc, const char *key); 29 | gboolean shortcut_set_default(Shortcut *sc, const char *key); 30 | char *shortcut_get_uri(Shortcut *sc, const char *key); 31 | gboolean shortcut_fill_completion(Shortcut *c, GtkListStore *store, const char *input); 32 | 33 | #endif /* end of include guard: _SHORTCUT_H */ 34 | 35 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _UTIL_H 21 | #define _UTIL_H 22 | 23 | #include 24 | #include "main.h" 25 | 26 | enum { 27 | UTIL_EXP_TILDE = 0x01, /* ~/ and ~user expansion */ 28 | UTIL_EXP_DOLLAR = 0x02, /* $ENV and ${ENV} expansion */ 29 | }; 30 | typedef void *(*Util_Content_Func)(const char*, const char*); 31 | 32 | char *util_build_path(const char *path, const char *dir); 33 | void util_cleanup(void); 34 | gboolean util_create_dir_if_not_exists(const char *dirpath); 35 | gboolean util_create_tmp_file(const char *content, char **file); 36 | char *util_expand(const char *src, int expflags); 37 | gboolean util_file_append(const char *file, const char *format, ...); 38 | gboolean util_file_prepend(const char *file, const char *format, ...); 39 | void util_file_prepend_line(const char *file, const char *line, 40 | unsigned int max_lines); 41 | char *util_file_pop_line(const char *file, int *item_count); 42 | char *util_get_config_dir(void); 43 | char *util_get_data_dir(void); 44 | char *util_get_cache_dir(void); 45 | char *util_get_file_contents(const char *filename, gsize *length); 46 | gboolean util_file_set_content(const char *file, const char *contents); 47 | char **util_get_lines(const char *filename); 48 | GList *util_strv_to_unique_list(char **lines, Util_Content_Func func, 49 | guint max_items); 50 | gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src); 51 | gboolean util_filename_fill_completion(GtkListStore *store, const char *input); 52 | char *util_js_result_as_string(WebKitJavascriptResult *result); 53 | double util_js_result_as_number(WebKitJavascriptResult *result); 54 | gboolean util_parse_expansion(const char **input, GString *str, int flags, 55 | const char *quoteable); 56 | char *util_sanitize_filename(char *filename); 57 | char *util_sanitize_uri(const char *uri_str); 58 | char *util_strcasestr(const char *haystack, const char *needle); 59 | char *util_str_replace(const char* search, const char* replace, const char* string); 60 | char *util_strescape(const char *source, const char *exceptions); 61 | gboolean util_wildmatch(const char *pattern, const char *subject); 62 | GTimeSpan util_string_to_timespan(const char *input); 63 | 64 | #endif /* end of include guard: _UTIL_H */ 65 | -------------------------------------------------------------------------------- /src/webextension/Makefile: -------------------------------------------------------------------------------- 1 | include ../../config.mk 2 | 3 | OBJ = $(patsubst %.c, %.lo, $(wildcard *.c)) 4 | 5 | all: $(EXTTARGET) 6 | 7 | clean: 8 | $(RM) $(EXTTARGET) $(OBJ) 9 | 10 | $(EXTTARGET): $(OBJ) 11 | @echo "$(CC) $@" 12 | $(Q)$(CC) $(OBJ) $(EXTLDFLAGS) -o $@ 13 | 14 | %.lo: %.c 15 | @echo "${CC} $@" 16 | $(Q)$(CC) $(EXTCPPFLAGS) $(EXTCFLAGS) -fPIC -c -o $@ $< 17 | 18 | -include $(OBJ:.lo=.d) 19 | 20 | .PHONY: all clean 21 | -------------------------------------------------------------------------------- /src/webextension/ext-dom.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "ext-main.h" 24 | #include "ext-dom.h" 25 | 26 | static gboolean is_element_visible(WebKitDOMHTMLElement *element); 27 | 28 | 29 | /** 30 | * Checks if given dom element is an editable element. 31 | */ 32 | gboolean ext_dom_is_editable(WebKitDOMElement *element) 33 | { 34 | char *type; 35 | gboolean result = FALSE; 36 | 37 | if (!element) { 38 | return FALSE; 39 | } 40 | 41 | /* element is editable if it's a text area or input with no type, text or 42 | * password */ 43 | if (webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element)) 44 | || WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(element)) { 45 | return TRUE; 46 | } 47 | 48 | if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) { 49 | type = webkit_dom_html_input_element_get_input_type(WEBKIT_DOM_HTML_INPUT_ELEMENT(element)); 50 | /* Input element without type attribute are rendered and behave like 51 | * type = text and there are a lot of pages in the wild using input 52 | * field without type attribute. */ 53 | if (!*type 54 | || !g_ascii_strcasecmp(type, "text") 55 | || !g_ascii_strcasecmp(type, "password") 56 | || !g_ascii_strcasecmp(type, "color") 57 | || !g_ascii_strcasecmp(type, "date") 58 | || !g_ascii_strcasecmp(type, "datetime") 59 | || !g_ascii_strcasecmp(type, "datetime-local") 60 | || !g_ascii_strcasecmp(type, "email") 61 | || !g_ascii_strcasecmp(type, "month") 62 | || !g_ascii_strcasecmp(type, "number") 63 | || !g_ascii_strcasecmp(type, "search") 64 | || !g_ascii_strcasecmp(type, "tel") 65 | || !g_ascii_strcasecmp(type, "time") 66 | || !g_ascii_strcasecmp(type, "url") 67 | || !g_ascii_strcasecmp(type, "week")) 68 | { 69 | result = TRUE; 70 | } 71 | 72 | g_free(type); 73 | } 74 | 75 | return result; 76 | } 77 | 78 | /** 79 | * Find the first editable element and set the focus on it and enter input 80 | * mode. 81 | * Returns true if there was an editable element focused. 82 | */ 83 | gboolean ext_dom_focus_input(WebKitDOMDocument *doc) 84 | { 85 | WebKitDOMNode *html, *node; 86 | WebKitDOMHTMLCollection *collection; 87 | WebKitDOMXPathNSResolver *resolver; 88 | WebKitDOMXPathResult* result; 89 | WebKitDOMDocument *frame_doc; 90 | guint i, len; 91 | 92 | collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "html"); 93 | if (!collection) { 94 | return FALSE; 95 | } 96 | 97 | html = webkit_dom_html_collection_item(collection, 0); 98 | g_object_unref(collection); 99 | 100 | resolver = webkit_dom_document_create_ns_resolver(doc, html); 101 | if (!resolver) { 102 | return FALSE; 103 | } 104 | 105 | /* Use translate to match xpath expression case insensitive so that also 106 | * intput filed of type="TEXT" are matched. */ 107 | result = webkit_dom_document_evaluate( 108 | doc, "//input[not(@type) " 109 | "or translate(@type,'ETX','etx')='text' " 110 | "or translate(@type,'ADOPRSW','adoprsw')='password' " 111 | "or translate(@type,'CLOR','clor')='color' " 112 | "or translate(@type,'ADET','adet')='date' " 113 | "or translate(@type,'ADEIMT','adeimt')='datetime' " 114 | "or translate(@type,'ACDEILMOT','acdeilmot')='datetime-local' " 115 | "or translate(@type,'AEILM','aeilm')='email' " 116 | "or translate(@type,'HMNOT','hmnot')='month' " 117 | "or translate(@type,'BEMNRU','bemnru')='number' " 118 | "or translate(@type,'ACEHRS','acehrs')='search' " 119 | "or translate(@type,'ELT','elt')='tel' " 120 | "or translate(@type,'EIMT','eimt')='time' " 121 | "or translate(@type,'LRU','lru')='url' " 122 | "or translate(@type,'EKW','ekw')='week' " 123 | "]|//textarea", 124 | html, resolver, 5, NULL, NULL 125 | ); 126 | if (!result) { 127 | return FALSE; 128 | } 129 | while ((node = webkit_dom_xpath_result_iterate_next(result, NULL))) { 130 | if (is_element_visible(WEBKIT_DOM_HTML_ELEMENT(node))) { 131 | webkit_dom_element_focus(WEBKIT_DOM_ELEMENT(node)); 132 | return TRUE; 133 | } 134 | } 135 | 136 | /* Look for editable elements in frames too. */ 137 | collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "iframe"); 138 | len = webkit_dom_html_collection_get_length(collection); 139 | 140 | for (i = 0; i < len; i++) { 141 | node = webkit_dom_html_collection_item(collection, i); 142 | frame_doc = webkit_dom_html_iframe_element_get_content_document(WEBKIT_DOM_HTML_IFRAME_ELEMENT(node)); 143 | /* Stop on first frame with focused element. */ 144 | if (ext_dom_focus_input(frame_doc)) { 145 | g_object_unref(collection); 146 | return TRUE; 147 | } 148 | } 149 | g_object_unref(collection); 150 | 151 | return FALSE; 152 | } 153 | 154 | /** 155 | * Retrieves the content of given editable element. 156 | * Not that the returned value must be freed. 157 | */ 158 | char *ext_dom_editable_get_value(WebKitDOMElement *element) 159 | { 160 | char *value = NULL; 161 | 162 | if ((webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element)))) { 163 | value = webkit_dom_html_element_get_inner_text(WEBKIT_DOM_HTML_ELEMENT(element)); 164 | } else if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(WEBKIT_DOM_HTML_INPUT_ELEMENT(element))) { 165 | value = webkit_dom_html_input_element_get_value(WEBKIT_DOM_HTML_INPUT_ELEMENT(element)); 166 | } else { 167 | value = webkit_dom_html_text_area_element_get_value(WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT(element)); 168 | } 169 | 170 | return value; 171 | } 172 | 173 | void ext_dom_lock_input(WebKitDOMDocument *parent, char *element_id) 174 | { 175 | WebKitDOMElement *elem; 176 | 177 | elem = webkit_dom_document_get_element_by_id(parent, element_id); 178 | if (elem != NULL) { 179 | webkit_dom_element_set_attribute(elem, "disabled", "true", NULL); 180 | } 181 | } 182 | 183 | void ext_dom_unlock_input(WebKitDOMDocument *parent, char *element_id) 184 | { 185 | WebKitDOMElement *elem; 186 | 187 | elem = webkit_dom_document_get_element_by_id(parent, element_id); 188 | if (elem != NULL) { 189 | webkit_dom_element_remove_attribute(elem, "disabled"); 190 | webkit_dom_element_focus(elem); 191 | } 192 | } 193 | 194 | /** 195 | * Indicates if the give nelement is visible. 196 | */ 197 | static gboolean is_element_visible(WebKitDOMHTMLElement *element) 198 | { 199 | return TRUE; 200 | } 201 | 202 | -------------------------------------------------------------------------------- /src/webextension/ext-dom.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _EXT_DOM_H 21 | #define _EXT_DOM_H 22 | 23 | #include 24 | #include 25 | 26 | gboolean ext_dom_is_editable(WebKitDOMElement *element); 27 | gboolean ext_dom_focus_input(WebKitDOMDocument *doc); 28 | char *ext_dom_editable_get_value(WebKitDOMElement *element); 29 | void ext_dom_lock_input(WebKitDOMDocument *parent, char *element_id); 30 | void ext_dom_unlock_input(WebKitDOMDocument *parent, char *element_id); 31 | 32 | #endif /* end of include guard: _EXT-DOM_H */ 33 | -------------------------------------------------------------------------------- /src/webextension/ext-main.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _EXT_MAIN_H 21 | #define _EXT_MAIN_H 22 | 23 | #define VB_WEBEXTENSION_SERVICE_NAME "org.vimb.browser.WebExtension" 24 | #define VB_WEBEXTENSION_OBJECT_PATH "/org/vimb/browser/WebExtension" 25 | #define VB_WEBEXTENSION_INTERFACE "org.vimb.browser.WebExtension" 26 | 27 | #endif /* end of include guard: _EXT_MAIN_H */ 28 | -------------------------------------------------------------------------------- /src/webextension/ext-util.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "../config.h" 24 | #include "ext-util.h" 25 | 26 | /** 27 | * Evaluates given string as script and return if this call succeed or not. 28 | */ 29 | gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result) 30 | { 31 | JSStringRef js_str; 32 | JSValueRef exc = NULL, res = NULL; 33 | 34 | js_str = JSStringCreateWithUTF8CString(script); 35 | res = JSEvaluateScript(ctx, js_str, JSContextGetGlobalObject(ctx), NULL, 0, &exc); 36 | JSStringRelease(js_str); 37 | 38 | if (exc) { 39 | *result = exc; 40 | return FALSE; 41 | } 42 | 43 | *result = res; 44 | return TRUE; 45 | } 46 | 47 | /** 48 | * Creates a temporary file with given content. 49 | * 50 | * Upon success, and if file is non-NULL, the actual file path used is 51 | * returned in file. This string should be freed with g_free() when not 52 | * needed any longer. 53 | */ 54 | gboolean ext_util_create_tmp_file(const char *content, char **file) 55 | { 56 | int fp; 57 | ssize_t bytes, len; 58 | 59 | fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL); 60 | if (fp == -1) { 61 | g_critical("Could not create temp file %s", *file); 62 | g_free(*file); 63 | return FALSE; 64 | } 65 | 66 | len = strlen(content); 67 | 68 | /* write content into temporary file */ 69 | bytes = write(fp, content, len); 70 | if (bytes < len) { 71 | close(fp); 72 | unlink(*file); 73 | g_critical("Could not write temp file %s", *file); 74 | g_free(*file); 75 | 76 | return FALSE; 77 | } 78 | close(fp); 79 | 80 | return TRUE; 81 | } 82 | 83 | /** 84 | * Returns a new allocates string for given value reference. 85 | * String must be freed if not used anymore. 86 | */ 87 | char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref) 88 | { 89 | char *string; 90 | size_t len; 91 | JSStringRef str_ref; 92 | 93 | g_return_val_if_fail(ref != NULL, NULL); 94 | 95 | str_ref = JSValueToStringCopy(ctx, ref, NULL); 96 | len = JSStringGetMaximumUTF8CStringSize(str_ref); 97 | 98 | string = g_new0(char, len); 99 | JSStringGetUTF8CString(str_ref, string, len); 100 | JSStringRelease(str_ref); 101 | 102 | return string; 103 | } 104 | -------------------------------------------------------------------------------- /src/webextension/ext-util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #ifndef _EXT_UTIL_H 21 | #define _EXT_UTIL_H 22 | 23 | #include 24 | #include 25 | 26 | gboolean ext_util_create_tmp_file(const char *content, char **file); 27 | gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result); 28 | char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref); 29 | 30 | #endif /* end of include guard: _EXT_UTIL_H */ 31 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /test-* 2 | !/test-*.c 3 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | CPPFLAGS = -I ../ 2 | 3 | include ../config.mk 4 | 5 | TEST_PROGS = test-util \ 6 | test-shortcut \ 7 | test-handler \ 8 | test-file-storage 9 | 10 | all: $(TEST_PROGS) 11 | $(Q)LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):." gtester --verbose $(TEST_PROGS) 12 | 13 | ${TEST_PROGS}: ../$(SRCDIR)/vimb.so 14 | 15 | test-%: test-%.c 16 | @echo "${CC} $@" 17 | $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< ../$(SRCDIR)/vimb.so $(LDFLAGS) 18 | 19 | clean: 20 | $(RM) $(TEST_PROGS) 21 | -------------------------------------------------------------------------------- /tests/manual/dummy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dummy Page 4 | 5 | 6 | Dummy Page 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/manual/editable-focus-in-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Track Focu/Blur also within iFrames 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/manual/editable-focus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Input mode Switching 4 | 11 | 12 | 13 |

14 | Run with scripts=on and strict-focus=off 15 |

    16 |
  1. If page is loade, vimb should be in input mode.
  2. 17 |
  3. Set strict-focus=on and reload page. Vimb should keep 18 | in normal mode
  4. 19 |
  5. Independent from the strict-focus should vimb switch 20 | to input mode if the button is clicked
  6. 21 |
22 |

23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |

32 | Also the following element using contenteditable="true" 33 | should switch vimb into input mode on click. 34 |

35 |
36 | Clicking this element using contenteditable="true" should 37 | switch vimb into input mode too. 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/manual/hints/hints.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | hints 4 | 5 | 6 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/manual/hints/label-positioning.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | Hint Label positioning with negative margins 13 | 14 | 15 |

16 | When using hints (f) on this page, the hint should be placed on 17 | the upper left corner of the links.
18 | one
19 | two 20 |

21 |

22 | To test the hints within iFrame 23 | allow-universal-access-from-file-urls must be enabled. 24 | And the page reloaded. 25 | 26 |

27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/manual/hints/wrapped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Links 5 | 6 | 7 | one 8 |
9 | 10 | two 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/manual/js-window-close.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | JS Window Close 4 | 5 | 6 | Click on following link should close the window without crashing.
7 | Close this window 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/manual/window-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Window open 4 | 5 | 6 |

7 | Enable set prevent-newwindow=on
8 | Following link should be opened into current window.

9 | target="_new"
10 | target="_blank"
11 | javascript:window-open
12 | onclick window-open
13 |

14 |

15 | This target iframe link should 16 | load into the iframe.
17 | 18 |

19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/test-file-storage.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2019 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | static char *pwd; 25 | static char *none_existing_file = "_absent.txt"; 26 | static char *created_file = "_created.txt"; 27 | static char *existing_file = "_existent.txt"; 28 | 29 | static void test_ephemeral_no_file(void) 30 | { 31 | FileStorage *s; 32 | char **lines; 33 | char *file_path; 34 | 35 | file_path = g_build_filename(pwd, none_existing_file, NULL); 36 | 37 | /* make sure the file does not exist */ 38 | remove(file_path); 39 | s = file_storage_new(pwd, none_existing_file, TRUE); 40 | g_assert_nonnull(s); 41 | g_assert_cmpstr(file_path, ==, file_storage_get_path(s)); 42 | g_assert_true(file_storage_is_readonly(s)); 43 | 44 | /* empty file storage */ 45 | lines = file_storage_get_lines(s); 46 | g_assert_cmpint(g_strv_length(lines), ==, 0); 47 | g_strfreev(lines); 48 | 49 | file_storage_append(s, "-%s\n", "foo"); 50 | file_storage_append(s, "-%s\n", "bar"); 51 | 52 | /* File must not be created on appending data to storage */ 53 | g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR)); 54 | 55 | lines = file_storage_get_lines(s); 56 | g_assert_cmpint(g_strv_length(lines), ==, 3); 57 | g_assert_cmpstr(lines[0], ==, "-foo"); 58 | g_assert_cmpstr(lines[1], ==, "-bar"); 59 | g_strfreev(lines); 60 | file_storage_free(s); 61 | g_free(file_path); 62 | } 63 | 64 | static void test_file_created(void) 65 | { 66 | FileStorage *s; 67 | char *file_path; 68 | 69 | file_path = g_build_filename(pwd, created_file, NULL); 70 | remove(file_path); 71 | 72 | g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR)); 73 | s = file_storage_new(pwd, created_file, FALSE); 74 | g_assert_false(file_storage_is_readonly(s)); 75 | g_assert_cmpstr(file_path, ==, file_storage_get_path(s)); 76 | 77 | /* check that file is created only on first write */ 78 | g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR)); 79 | file_storage_append(s, ""); 80 | g_assert_true(g_file_test(file_path, G_FILE_TEST_IS_REGULAR)); 81 | 82 | file_storage_free(s); 83 | g_free(file_path); 84 | } 85 | 86 | static void test_ephemeral_with_file(void) 87 | { 88 | FileStorage *s; 89 | char *file_path; 90 | char **lines; 91 | char *content = NULL; 92 | gboolean result; 93 | 94 | file_path = g_build_filename(pwd, existing_file, NULL); 95 | 96 | s = file_storage_new(pwd, existing_file, TRUE); 97 | g_assert_nonnull(s); 98 | g_assert_true(file_storage_is_readonly(s)); 99 | g_assert_cmpstr(file_path, ==, file_storage_get_path(s)); 100 | 101 | /* file does not exists yet */ 102 | lines = file_storage_get_lines(s); 103 | g_assert_cmpint(g_strv_length(lines), ==, 0); 104 | g_strfreev(lines); 105 | 106 | /* empty file storage but file with two lines */ 107 | result = g_file_set_contents(file_path, "one\n", -1, NULL); 108 | g_assert_true(result); 109 | lines = file_storage_get_lines(s); 110 | g_assert_cmpint(g_strv_length(lines), ==, 2); 111 | g_strfreev(lines); 112 | 113 | file_storage_append(s, "%s\n", "two ephemeral"); 114 | 115 | lines = file_storage_get_lines(s); 116 | g_assert_cmpint(g_strv_length(lines), ==, 3); 117 | g_assert_cmpstr(lines[0], ==, "one"); 118 | g_assert_cmpstr(lines[1], ==, "two ephemeral"); 119 | g_strfreev(lines); 120 | 121 | /* now make sure the file was not changed */ 122 | g_file_get_contents(file_path, &content, NULL, NULL); 123 | g_assert_cmpstr(content, ==, "one\n"); 124 | 125 | file_storage_free(s); 126 | g_free(file_path); 127 | } 128 | 129 | int main(int argc, char *argv[]) 130 | { 131 | int result; 132 | g_test_init(&argc, &argv, NULL); 133 | 134 | pwd = g_get_current_dir(); 135 | 136 | g_test_add_func("/test-file-storage/ephemeral-no-file", test_ephemeral_no_file); 137 | g_test_add_func("/test-file-storage/file-created", test_file_created); 138 | g_test_add_func("/test-file-storage/ephemeral-with-file", test_ephemeral_with_file); 139 | 140 | result = g_test_run(); 141 | 142 | remove(existing_file); 143 | remove(created_file); 144 | g_free(pwd); 145 | 146 | return result; 147 | } 148 | -------------------------------------------------------------------------------- /tests/test-handler.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | static Handler *handler = NULL; 25 | 26 | #define TEST_URI "http://fanglingsu.github.io/vimb/" 27 | 28 | static void test_handler_add(void) 29 | { 30 | g_assert_true(handler_add(handler, "https", "e")); 31 | } 32 | 33 | static void test_handler_remove(void) 34 | { 35 | g_assert_true(handler_add(handler, "https", "e")); 36 | 37 | g_assert_true(handler_remove(handler, "https")); 38 | g_assert_false(handler_remove(handler, "https")); 39 | } 40 | 41 | static void test_handler_run_success(void) 42 | { 43 | if (g_test_subprocess()) { 44 | handler_add(handler, "http", "echo -n 'handled uri %s'"); 45 | handler_handle_uri(handler, TEST_URI); 46 | return; 47 | } 48 | g_test_trap_subprocess(NULL, 0, 0); 49 | g_test_trap_assert_passed(); 50 | g_test_trap_assert_stdout("handled uri " TEST_URI); 51 | } 52 | 53 | static void test_handler_run_failed(void) 54 | { 55 | if (g_test_subprocess()) { 56 | handler_add(handler, "http", "unknown-program %s"); 57 | handler_handle_uri(handler, TEST_URI); 58 | return; 59 | } 60 | g_test_trap_subprocess(NULL, 0, 0); 61 | g_test_trap_assert_failed(); 62 | g_test_trap_assert_stderr("*Can't run *unknown-program*"); 63 | } 64 | 65 | static void test_handler_fill_completion(void) 66 | { 67 | GtkListStore *store; 68 | g_assert_true(handler_add(handler, "http", "echo")); 69 | g_assert_true(handler_add(handler, "https", "echo")); 70 | g_assert_true(handler_add(handler, "about", "echo")); 71 | g_assert_true(handler_add(handler, "ftp", "echo")); 72 | 73 | store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING); 74 | /* check case where multiple matches are found */ 75 | g_assert_true(handler_fill_completion(handler, store, "http")); 76 | g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 2); 77 | gtk_list_store_clear(store); 78 | 79 | /* check case where only one matches are found */ 80 | g_assert_true(handler_fill_completion(handler, store, "f")); 81 | g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 1); 82 | gtk_list_store_clear(store); 83 | 84 | /* check case where no match is found */ 85 | g_assert_false(handler_fill_completion(handler, store, "unknown")); 86 | g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 0); 87 | gtk_list_store_clear(store); 88 | 89 | /* check case without apllied filters */ 90 | g_assert_true(handler_fill_completion(handler, store, "")); 91 | g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 4); 92 | gtk_list_store_clear(store); 93 | } 94 | 95 | int main(int argc, char *argv[]) 96 | { 97 | int result; 98 | handler = handler_new(); 99 | 100 | g_test_init(&argc, &argv, NULL); 101 | 102 | g_test_add_func("/test-handlers/add", test_handler_add); 103 | g_test_add_func("/test-handlers/remove", test_handler_remove); 104 | g_test_add_func("/test-handlers/handle_uri/success", test_handler_run_success); 105 | g_test_add_func("/test-handlers/handle_uri/failed", test_handler_run_failed); 106 | g_test_add_func("/test-handlers/fill-completion", test_handler_fill_completion); 107 | result = g_test_run(); 108 | 109 | handler_free(handler); 110 | 111 | return result; 112 | } 113 | -------------------------------------------------------------------------------- /tests/test-shortcut.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | Shortcut *sc = NULL; 25 | 26 | static void test_shortcut(void) 27 | { 28 | char *uri; 29 | unsigned int i; 30 | struct { 31 | char *in; 32 | char *out; 33 | } data[] = { 34 | /* call with shortcut identifier */ 35 | {"_vimb1_ zero one", "only-zero:zero%20one"}, 36 | /* don't fail on unmatches quotes if there are only $0 placeholders */ 37 | {"_vimb1_ 'unmatches quote", "only-zero:'unmatches%20quote"}, 38 | /* check if all placeholders $0 are replaces */ 39 | {"_vimb5_ one two", "double-zero:one%20two-one%20two"}, 40 | /* check the defautl shortcut is used */ 41 | {"zero one two three", "default:zero-two%20three"}, 42 | /* don't remove non matched placeholders */ 43 | {"zero", "default:zero-$2"}, 44 | {"_vimb3_ zero one two three four five six seven eight nine", "fullrange:zero-one-nine"} 45 | }; 46 | 47 | for (i = 0; i < LENGTH(data); i++) { 48 | uri = shortcut_get_uri(sc, data->in); 49 | g_assert_cmpstr(uri, ==, data->out); 50 | g_free(uri); 51 | } 52 | } 53 | 54 | static void test_shortcut_shell_param(void) 55 | { 56 | char *uri; 57 | 58 | /* double quotes */ 59 | uri = shortcut_get_uri(sc, "_vimb6_ \"rail station\" city hall"); 60 | g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); 61 | g_free(uri); 62 | 63 | /* single quotes */ 64 | uri = shortcut_get_uri(sc, "_vimb6_ 'rail station' 'city hall'"); 65 | g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); 66 | g_free(uri); 67 | 68 | /* ignore none matching quote errors */ 69 | uri = shortcut_get_uri(sc, "_vimb6_ \"rail station\" \"city hall"); 70 | g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall"); 71 | g_free(uri); 72 | 73 | /* don't fill up quoted param with unquoted stuff */ 74 | uri = shortcut_get_uri(sc, "_vimb6_ \"param 1\" \"param 2\" ignored params"); 75 | g_assert_cmpstr(uri, ==, "shell:param%201-param%202"); 76 | g_free(uri); 77 | 78 | /* allo quotes within tha last parameter */ 79 | uri = shortcut_get_uri(sc, "_vimb6_ param1 param2 \"containing quotes\""); 80 | g_assert_cmpstr(uri, ==, "shell:param1-param2%20%22containing%20quotes%22"); 81 | g_free(uri); 82 | } 83 | 84 | static void test_shortcut_remove(void) 85 | { 86 | char *uri; 87 | 88 | g_assert_true(shortcut_remove(sc, "_vimb4_")); 89 | 90 | /* check if the shortcut is really no used */ 91 | uri = shortcut_get_uri(sc, "_vimb4_ test"); 92 | g_assert_cmpstr(uri, ==, "default:_vimb4_-$2"); 93 | g_free(uri); 94 | 95 | g_assert_false(shortcut_remove(sc, "_vimb4_")); 96 | } 97 | 98 | int main(int argc, char *argv[]) 99 | { 100 | int result; 101 | sc = shortcut_new(); 102 | 103 | g_assert_true(shortcut_add(sc, "_vimb1_", "only-zero:$0")); 104 | g_assert_true(shortcut_add(sc, "_vimb2_", "default:$0-$2")); 105 | g_assert_true(shortcut_add(sc, "_vimb3_", "fullrange:$0-$1-$9")); 106 | g_assert_true(shortcut_add(sc, "_vimb4_", "for-remove:$0")); 107 | g_assert_true(shortcut_add(sc, "_vimb5_", "double-zero:$0-$0")); 108 | g_assert_true(shortcut_add(sc, "_vimb6_", "shell:$0-$1")); 109 | g_assert_true(shortcut_set_default(sc, "_vimb2_")); 110 | 111 | g_test_init(&argc, &argv, NULL); 112 | 113 | g_test_add_func("/test-shortcut/get_uri/single", test_shortcut); 114 | g_test_add_func("/test-shortcut/get_uri/shell-param", test_shortcut_shell_param); 115 | g_test_add_func("/test-shortcut/remove", test_shortcut_remove); 116 | 117 | result = g_test_run(); 118 | 119 | shortcut_free(sc); 120 | 121 | return result; 122 | } 123 | -------------------------------------------------------------------------------- /tests/test-util.c: -------------------------------------------------------------------------------- 1 | /** 2 | * vimb - a webkit based vim like browser. 3 | * 4 | * Copyright (C) 2012-2018 Daniel Carl 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see http://www.gnu.org/licenses/. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | static void check_expand(const char *str, const char *expected) 25 | { 26 | char *result = util_expand(str, UTIL_EXP_DOLLAR|UTIL_EXP_TILDE); 27 | g_assert_cmpstr(result, ==, expected); 28 | g_free(result); 29 | } 30 | 31 | static void test_expand_evn(void) 32 | { 33 | /* set environment var for testing expansion */ 34 | g_setenv("VIMB_VAR", "value", true); 35 | 36 | check_expand("$VIMB_VAR", "value"); 37 | check_expand("$VIMB_VAR", "value"); 38 | check_expand("$VIMB_VAR$VIMB_VAR", "valuevalue"); 39 | check_expand("${VIMB_VAR}", "value"); 40 | check_expand("my$VIMB_VAR", "myvalue"); 41 | check_expand("'$VIMB_VAR'", "'value'"); 42 | check_expand("${VIMB_VAR}s ", "values "); 43 | 44 | g_unsetenv("UNKNOWN"); 45 | 46 | check_expand("$UNKNOWN", ""); 47 | check_expand("${UNKNOWN}", ""); 48 | check_expand("'$UNKNOWN'", "''"); 49 | } 50 | 51 | static void test_expand_escaped(void) 52 | { 53 | g_setenv("VIMB_VAR", "value", true); 54 | 55 | check_expand("\\$VIMB_VAR", "$VIMB_VAR"); 56 | check_expand("\\${VIMB_VAR}", "${VIMB_VAR}"); 57 | check_expand("\\~/", "~/"); 58 | check_expand("\\~/vimb", "~/vimb"); 59 | check_expand("\\~root", "~root"); 60 | check_expand("\\\\$VIMB_VAR", "\\value"); /* \\$VAR becomes \ExpandedVar */ 61 | check_expand("\\\\\\$VIMB_VAR", "\\$VIMB_VAR"); /* \\\$VAR becomes \$VAR */ 62 | } 63 | 64 | static void test_expand_tilde_home(void) 65 | { 66 | char *dir; 67 | const char *home = g_get_home_dir(); 68 | 69 | check_expand("~", "~"); 70 | check_expand("~/", home); 71 | check_expand("foo~/bar", "foo~/bar"); 72 | check_expand("~/foo", (dir = g_strdup_printf("%s/foo", home))); 73 | g_free(dir); 74 | 75 | check_expand("foo ~/bar", (dir = g_strdup_printf("foo %s/bar", home))); 76 | g_free(dir); 77 | 78 | check_expand("~/~", (dir = g_strdup_printf("%s/~", home))); 79 | g_free(dir); 80 | 81 | check_expand("~/~/", (dir = g_strdup_printf("%s/~/", home))); 82 | g_free(dir); 83 | } 84 | 85 | static void test_expand_tilde_user(void) 86 | { 87 | const char *user = g_get_user_name(); 88 | const char *home; 89 | char *in, *out; 90 | struct passwd *pwd; 91 | 92 | /* initialize home */ 93 | pwd = getpwnam(user); 94 | g_assert_nonnull(pwd); 95 | home = pwd->pw_dir; 96 | 97 | /* don't expand within words */ 98 | in = g_strdup_printf("foo~%s/bar", user); 99 | check_expand(in, in); 100 | g_free(in); 101 | 102 | check_expand((in = g_strdup_printf("foo ~%s", user)), (out = g_strdup_printf("foo %s", home))); 103 | g_free(in); 104 | g_free(out); 105 | 106 | check_expand((in = g_strdup_printf("~%s", user)), home); 107 | g_free(in); 108 | 109 | check_expand((in = g_strdup_printf("~%s/bar", user)), (out = g_strdup_printf("%s/bar", home))); 110 | g_free(in); 111 | g_free(out); 112 | } 113 | 114 | static void test_strcasestr(void) 115 | { 116 | g_assert_nonnull(util_strcasestr("Vim like Browser", "browser")); 117 | g_assert_nonnull(util_strcasestr("Vim like Browser", "vim LIKE")); 118 | } 119 | 120 | static void test_str_replace(void) 121 | { 122 | char *value; 123 | 124 | value = util_str_replace("a", "uu", "foo bar baz"); 125 | g_assert_cmpstr(value, ==, "foo buur buuz"); 126 | g_free(value); 127 | 128 | value = util_str_replace("$1", "placeholder", "string with $1"); 129 | g_assert_cmpstr(value, ==, "string with placeholder"); 130 | g_free(value); 131 | } 132 | 133 | static void test_wildmatch_simple(void) 134 | { 135 | g_assert_true(util_wildmatch("", "")); 136 | g_assert_true(util_wildmatch("w", "w")); 137 | g_assert_true(util_wildmatch(".", ".")); 138 | g_assert_true(util_wildmatch("~", "~")); 139 | g_assert_true(util_wildmatch("wildmatch", "WildMatch")); 140 | g_assert_true(util_wildmatch("wild\\match", "wild\\match")); 141 | 142 | /* no special meaning of . and ~ in pattern */ 143 | g_assert_false(util_wildmatch(".", "w")); 144 | g_assert_false(util_wildmatch("~", "w")); 145 | g_assert_false(util_wildmatch("wild", "wild ")); 146 | g_assert_false(util_wildmatch("wild", " wild")); 147 | g_assert_false(util_wildmatch("wild", "\\ wild")); 148 | g_assert_false(util_wildmatch("wild", "\\wild")); 149 | g_assert_false(util_wildmatch("wild", "wild\\")); 150 | g_assert_false(util_wildmatch("wild\\1", "wild\\2")); 151 | } 152 | 153 | static void test_wildmatch_questionmark(void) 154 | { 155 | g_assert_true(util_wildmatch("wild?atch", "wildmatch")); 156 | g_assert_true(util_wildmatch("wild?atch", "wildBatch")); 157 | g_assert_true(util_wildmatch("wild?atch", "wild?atch")); 158 | g_assert_true(util_wildmatch("?ild?atch", "MildBatch")); 159 | g_assert_true(util_wildmatch("foo\\?bar", "foo?bar")); 160 | g_assert_true(util_wildmatch("???", "foo")); 161 | g_assert_true(util_wildmatch("???", "bar")); 162 | 163 | g_assert_false(util_wildmatch("foo\\?bar", "foorbar")); 164 | g_assert_false(util_wildmatch("?", "")); 165 | g_assert_false(util_wildmatch("b??r", "bar")); 166 | /* ? does not match / in contrast to * which does */ 167 | g_assert_false(util_wildmatch("user?share", "user/share")); 168 | } 169 | 170 | static void test_wildmatch_wildcard(void) 171 | { 172 | g_assert_true(util_wildmatch("*", "")); 173 | g_assert_true(util_wildmatch("*", "Match as much as possible")); 174 | g_assert_true(util_wildmatch("*match", "prefix match")); 175 | g_assert_true(util_wildmatch("match*", "match suffix")); 176 | g_assert_true(util_wildmatch("match*", "match*")); 177 | g_assert_true(util_wildmatch("match\\*", "match*")); 178 | g_assert_true(util_wildmatch("match\\\\*", "match\\*")); 179 | g_assert_true(util_wildmatch("do * match", "do a infix match")); 180 | /* '*' matches also / in contrast to other implementations */ 181 | g_assert_true(util_wildmatch("start*end", "start/something/end")); 182 | g_assert_true(util_wildmatch("*://*.io/*", "http://fanglingsu.github.io/vimb/")); 183 | /* multiple * should act like a single one */ 184 | g_assert_true(util_wildmatch("**", "")); 185 | g_assert_true(util_wildmatch("match **", "Match as much as possible")); 186 | g_assert_true(util_wildmatch("f***u", "fu")); 187 | 188 | g_assert_false(util_wildmatch("match\\*", "match fail")); 189 | g_assert_false(util_wildmatch("f***u", "full")); 190 | } 191 | 192 | static void test_wildmatch_curlybraces(void) 193 | { 194 | g_assert_true(util_wildmatch("{foo}", "foo")); 195 | g_assert_true(util_wildmatch("{foo,bar}", "foo")); 196 | g_assert_true(util_wildmatch("{foo,bar}", "bar")); 197 | g_assert_true(util_wildmatch("foo{lish,t}bar", "foolishbar")); 198 | g_assert_true(util_wildmatch("foo{lish,t}bar", "footbar")); 199 | /* esacped special chars */ 200 | g_assert_true(util_wildmatch("foo\\{l\\}bar", "foo{l}bar")); 201 | g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "bar")); 202 | g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "baz{}")); 203 | g_assert_true(util_wildmatch("test{one\\,two,three}", "testone,two")); 204 | g_assert_true(util_wildmatch("test{one\\,two,three}", "testthree")); 205 | /* backslash before none special char is a normal char */ 206 | g_assert_true(util_wildmatch("back{\\slash,}", "back\\slash")); 207 | g_assert_true(util_wildmatch("one\\two", "one\\two")); 208 | g_assert_true(util_wildmatch("\\}match", "}match")); 209 | g_assert_true(util_wildmatch("\\{", "{")); 210 | /* empty list parts */ 211 | g_assert_true(util_wildmatch("{}", "")); 212 | g_assert_true(util_wildmatch("{,}", "")); 213 | g_assert_true(util_wildmatch("{,foo}", "")); 214 | g_assert_true(util_wildmatch("{,foo}", "foo")); 215 | g_assert_true(util_wildmatch("{bar,}", "")); 216 | g_assert_true(util_wildmatch("{bar,}", "bar")); 217 | /* no special meaning of ? and * in curly braces */ 218 | g_assert_true(util_wildmatch("ab{*,cd}ef", "ab*ef")); 219 | g_assert_true(util_wildmatch("ab{d,?}ef", "ab?ef")); 220 | 221 | g_assert_false(util_wildmatch("{foo,bar}", "foo,bar")); 222 | g_assert_false(util_wildmatch("}match{ it", "}match{ anything")); 223 | /* don't match single parts that are seperated by escaped ',' */ 224 | g_assert_false(util_wildmatch("{a,b\\,c,d}", "b")); 225 | g_assert_false(util_wildmatch("{a,b\\,c,d}", "c")); 226 | /* lonesome braces - this is a syntax error and will always be false */ 227 | g_assert_false(util_wildmatch("}", "}")); 228 | g_assert_false(util_wildmatch("}", "")); 229 | g_assert_false(util_wildmatch("}suffix", "}suffux")); 230 | g_assert_false(util_wildmatch("}suffix", "suffux")); 231 | g_assert_false(util_wildmatch("{", "{")); 232 | g_assert_false(util_wildmatch("{", "")); 233 | g_assert_false(util_wildmatch("{foo", "{foo")); 234 | g_assert_false(util_wildmatch("{foo", "foo")); 235 | g_assert_false(util_wildmatch("foo{bar", "foo{bar")); 236 | } 237 | 238 | static void test_wildmatch_complete(void) 239 | { 240 | g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "http://fanglingsu.github.io/vimb/")); 241 | g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "https://github.com/fanglingsu/vimb/")); 242 | } 243 | 244 | static void test_wildmatch_multi(void) 245 | { 246 | g_assert_true(util_wildmatch("foo,?", "foo")); 247 | g_assert_true(util_wildmatch("foo,?", "f")); 248 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "foo")); 249 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bar")); 250 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bor")); 251 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "br")); 252 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "baz")); 253 | g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bat")); 254 | 255 | g_assert_false(util_wildmatch("foo,b{a,o,}r,ba?", "foo,")); 256 | g_assert_false(util_wildmatch("foo,?", "fo")); 257 | } 258 | 259 | static void test_strescape(void) 260 | { 261 | unsigned int i; 262 | char *value; 263 | struct { 264 | char *in; 265 | char *expected; 266 | char *excludes; 267 | } data[] = { 268 | {"", "", NULL}, 269 | {"foo", "foo", NULL}, 270 | {"a\nb\nc", "a\\nb\\nc", NULL}, 271 | {"foo\"bar", "foo\\bar", NULL}, 272 | {"s\\t", "s\\\\t", NULL}, 273 | {"u\bv", "u\\bv", NULL}, 274 | {"w\fx", "w\\fx", NULL}, 275 | {"y\rz", "y\\rz", NULL}, 276 | {"tab\tdi\t", "tab\\tdi\\t", NULL}, 277 | {"❧äüö\n@foo\t\"bar\"", "❧äüö\\n@foo\\t\\\"bar\\\"", NULL}, 278 | {"❧äüö\n@foo\t\"bar\"", "❧äüö\\n@foo\\t\"bar\"", "\""}, 279 | {"❧äüö\n@foo\t\"bar\"", "❧äüö\n@foo\t\\\"bar\\\"", "\n\t"}, 280 | }; 281 | for (i = 0; i < LENGTH(data); i++) { 282 | value = util_strescape(data->in, data->excludes); 283 | g_assert_cmpstr(value, ==, data->expected); 284 | g_free(value); 285 | } 286 | } 287 | 288 | static void test_string_to_timespan(void) 289 | { 290 | g_assert_cmpuint(util_string_to_timespan("d"), ==, G_TIME_SPAN_DAY); 291 | g_assert_cmpuint(util_string_to_timespan("h"), ==, G_TIME_SPAN_HOUR); 292 | g_assert_cmpuint(util_string_to_timespan("m"), ==, G_TIME_SPAN_MINUTE); 293 | g_assert_cmpuint(util_string_to_timespan("s"), ==, G_TIME_SPAN_SECOND); 294 | 295 | g_assert_cmpuint(util_string_to_timespan("y"), ==, G_TIME_SPAN_DAY * 365); 296 | g_assert_cmpuint(util_string_to_timespan("w"), ==, G_TIME_SPAN_DAY * 7); 297 | 298 | /* use counters */ 299 | g_assert_cmpuint(util_string_to_timespan("1s"), ==, G_TIME_SPAN_SECOND); 300 | g_assert_cmpuint(util_string_to_timespan("2s"), ==, 2 * G_TIME_SPAN_SECOND); 301 | g_assert_cmpuint(util_string_to_timespan("34s"), ==, 34 * G_TIME_SPAN_SECOND); 302 | g_assert_cmpuint(util_string_to_timespan("0s"), ==, 0); 303 | 304 | /* combine counters and different units */ 305 | g_assert_cmpuint(util_string_to_timespan("ds"), ==, G_TIME_SPAN_DAY + G_TIME_SPAN_SECOND); 306 | g_assert_cmpuint(util_string_to_timespan("2dh0s"), ==, (2 * G_TIME_SPAN_DAY) + G_TIME_SPAN_HOUR); 307 | 308 | /* unparsabel values */ 309 | g_assert_cmpuint(util_string_to_timespan(""), ==, 0); 310 | g_assert_cmpuint(util_string_to_timespan("-"), ==, 0); 311 | g_assert_cmpuint(util_string_to_timespan("5-"), ==, 0); 312 | } 313 | 314 | int main(int argc, char *argv[]) 315 | { 316 | g_test_init(&argc, &argv, NULL); 317 | 318 | g_test_add_func("/test-util/expand-env", test_expand_evn); 319 | g_test_add_func("/test-util/expand-escaped", test_expand_escaped); 320 | g_test_add_func("/test-util/expand-tilde-home", test_expand_tilde_home); 321 | g_test_add_func("/test-util/expand-tilde-user", test_expand_tilde_user); 322 | g_test_add_func("/test-util/strcasestr", test_strcasestr); 323 | g_test_add_func("/test-util/str_replace", test_str_replace); 324 | g_test_add_func("/test-util/wildmatch-simple", test_wildmatch_simple); 325 | g_test_add_func("/test-util/wildmatch-questionmark", test_wildmatch_questionmark); 326 | g_test_add_func("/test-util/wildmatch-wildcard", test_wildmatch_wildcard); 327 | g_test_add_func("/test-util/wildmatch-curlybraces", test_wildmatch_curlybraces); 328 | g_test_add_func("/test-util/wildmatch-complete", test_wildmatch_complete); 329 | g_test_add_func("/test-util/wildmatch-multi", test_wildmatch_multi); 330 | g_test_add_func("/test-util/strescape", test_strescape); 331 | g_test_add_func("/test-util/string_to_timespan", test_string_to_timespan); 332 | 333 | return g_test_run(); 334 | } 335 | -------------------------------------------------------------------------------- /vimb.desktop: -------------------------------------------------------------------------------- 1 | # Based on Arch Linux' chromium.desktop 2 | [Desktop Entry] 3 | Name=vimb 4 | GenericName=Web Browser 5 | Comment=Access the Internet 6 | Exec=vimb %U 7 | Terminal=false 8 | Icon= 9 | Type=Application 10 | Categories=GTK;Network;WebBrowser; 11 | MimeType=text/html;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https; 12 | -------------------------------------------------------------------------------- /vimb.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | comical.desktop 5 | CC0-1.0 6 | GPL-3.0+ 7 | Vimb 8 | the Vim-like browser 9 | 10 |

11 | Vimb is a fast and lightweight vim like web browser based on the webkit 12 | web browser engine and the GTK toolkit. Vimb is modal like the great vim 13 | editor and also easily configurable during runtime. Vimb is mostly keyboard 14 | driven and does not distract you from your daily work. 15 |

16 |
17 | 18 | 19 | https://fanglingsu.github.io/vimb/media/vimb-completion.png 20 | 21 | 22 | https://fanglingsu.github.io/vimb/media/vimb-hints.png 23 | 24 | 25 | https://fanglingsu.github.io/vimb/ 26 | https://github.com/fanglingsu/vimb/issues 27 |
28 | 29 | --------------------------------------------------------------------------------