├── .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 | [](https://travis-ci.com/fanglingsu/vimb)
4 | [](https://www.gnu.org/licenses/gpl-3.0)
5 | [](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 | - If page is loade, vimb should be in input mode.
17 | - Set
strict-focus=on
and reload page. Vimb should keep
18 | in normal mode
19 | - Independent from the
strict-focus
should vimb switch
20 | to input mode if the button is clicked
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------