pax_global_header 0000666 0000000 0000000 00000000064 15145416123 0014514 g ustar 00root root 0000000 0000000 52 comment=448e7e2e66471b4cbfa8f373bb4bd7d7134cafaa
fanglingsu-vimb-448e7e2/ 0000775 0000000 0000000 00000000000 15145416123 0015150 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/.github/ 0000775 0000000 0000000 00000000000 15145416123 0016510 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000000241 15145416123 0021212 0 ustar 00root root 0000000 0000000
### Steps to reproduce
### Expected behaviour
### Actual behaviour
fanglingsu-vimb-448e7e2/.github/stale.yml 0000664 0000000 0000000 00000001514 15145416123 0020344 0 ustar 00root root 0000000 0000000 # Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: false
# Issues with these labels will never be considered stale
exemptLabels:
- rfc
- security
- pinned
- bug
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
activity within the last 60 days.
# Comment to post when closing a stale issue. Set to `false` to disable
# closeComment: >
# This issue has been automatically closed because it has not had activity
# since it was marked as stale. Thank you for your contributions.
fanglingsu-vimb-448e7e2/.gitignore 0000664 0000000 0000000 00000000055 15145416123 0017140 0 ustar 00root root 0000000 0000000 *.[oad]
*.lo
*.so
*.tar.gz
sandbox
version.h
fanglingsu-vimb-448e7e2/.travis.yml 0000664 0000000 0000000 00000000432 15145416123 0017260 0 ustar 00root root 0000000 0000000 branches:
except:
gh-pages
language: c
dist: jammy
sudo: required
compiler:
- gcc
- clang
before_install:
- sudo apt-get update -q
- sudo apt-get install -y --allow-unauthenticated --no-install-recommends libwebkit2gtk-4.1-dev
script: make options && make -j test
fanglingsu-vimb-448e7e2/CHANGELOG.md 0000664 0000000 0000000 00000036216 15145416123 0016771 0 ustar 00root root 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [3.7.1]
### Added
* Allow special keys to be escaped in mappings using `\`. For example, `\`
represents the key sequence `<`, `C`, `-`, `R`, `>`.
* Add `` as an alias for `\` in mappings. For example, ``
represents the key sequence `\` followed by CTRL-R.
* Fixes crash of webextension on pages with cross-origin iframes
### Changed
* Reworked communication between main process and webextension
* Replaced deprecated dom api by JavaScript snippets
## [3.7.0] - 2023-06-19
### Added
* The new env variable `$VIMB_SELECTION` is set to the current selected text
whenever a `shellcmd` is run #592.
* Allow to push link url to queue by `` #610.
* Allow to decide if html5 notfication are allowed #651. New setting
`notification=[ask,always,never]` added.
* Add new env `VIMB_WIN_ID` var for `:shellcmd` which holds the own window id.
* Focus last focused input elemnt on page via `i` - or first found element if
none was focused before.
* Completion with bookmarked urls for `:qpush` and `qunshift`.
* Setting `scroll-multiplier` to allow for faster scrolling by mouse wheel.
* Setting `intelligent-tracking-prevention` to enable WebKit's Intelligent
Tracking Prevention (ITP).
* Setting `javascript-enable-markup` to disable js markup instead of disabling
* Setting `status-bar-show-settings` to enable showing indicators to currently
applied setting. Can be configured via config.h file.
```
#define COOKIE GET_CHAR(c, "cookie-accept")
#define STATUS_VARAIBLE_SHOW "%c%c%c%c%c%c%c%c", \
CHAR_MAP(COOKIE, "always", 'A', CHAR_MAP(COOKIE, "origin", '@', 'a')), \
GET_BOOL(c, "dark-mode") ? 'D' : 'd', \
vb.incognito ? 'E' : 'e', \
GET_BOOL(c, "images") ? 'I' : 'i', \
GET_BOOL(c, "html5-local-storage") ? 'L' : 'l', \
GET_BOOL(c, "stylesheet") ? 'M' : 'm', \
GET_BOOL(c, "scripts") ? 'S' : 's', \
GET_BOOL(c, "strict-ssl") ? 'T' : 't'
```
JavaScript in general.
* Allow to push link url to queue by `Shift-LeftMouse`.
* New hint mode `;k` do remove hinted elements like advertisement from DOM.
* Allow to map `` and `` keys.
### Changed
* Modes some files from `$XDG_CONFIG_HOME/vimb` into `$XDG_DATA_HOME/vimb` #582.
Following files are affected `bookmark`, `closed`, `command`, `config`,
`cookies.db`, `history`, `queue` and `search`.
Existing files could be moved to the new location by
```
mv $XDG_CONFIG_HOME/vimb/{bookmark,closed,command,cookies.db,history,queue,search} \
$XDG_DATA_HOME/vimb
# and same for existing profiles
mkdir $XDG_DATA_HOME/vimb/
mv $XDG_CONFIG_HOME/vimb//{bookmark,closed,command,cookies.db,history,queue,search} \
$XDG_DATA_HOME/vimb/
```
* Dependency moved to webkit-gtk 4.1 and libsoup 3.0
### Fixed
* Fixed ignored last line in config file if this line did not end in newline.
* Fixed crash in normal_focus_last_active (Thanks to Maxime Coste)
* Fixed hint keys going to webview if the hinted element had no src-attribut
(thanks to Maxime Coste)
* Fixed erro in hinting on gitlab which caused the hints to have ho labels #659.
* Prevent possible use after free in autocmd processing.
### Removed
* Expansion of `%` to the current opened URI for `:shellcmd` was removed
because it breaks the `x-hint-command` with URIs containing '%'. But it is
still possible to use `$VIMB_URI` for the `:shellcmd` which has the same
effect.
## [3.6.0] - 2020-01-02
### Added
* `:cleardata [listOfDataTypes] [timeSpan]` command to clear various types of
stored website data modified in the last _timeSpan_.
* Setting `hint-match-element` to allow to disable the hinting to filter hints
by the elements text content. This is useful if 'hint-keys' are set the
chars instead of numbers.
* New autocmd event `LoadStarting` to run auto commands before the first page
content is loaded (Thanks to Patrick Steinhardt).
* Setting `geolocation` with values ('ask', 'always' and 'never') to allow the
user to permit or disable geolcation requests by default (Thanks to Alva).
* Setting `dark-mode` to switch the webview into dark mode, which might be
picked up by pages media query to setup dark styling (Thanks to Alva).
* Option `--cmd, -C` to run ex commands on startup.
### Changed
### Fixed
### Removed
* `:clearcache` was removed in favor of more advanced `:cleardata` command.
The previous behaviour of `:clearcache` could be replaces by
`:cleardata memory-cache,disk-cache`.
## [3.5.0] - 2019-07-29
### Added
* Add external download command #543 #348.
* Added ephemeral mode by new option `--incognito` #562.
### Changed
* Hinting shows the current focused elements URI in the statusbar.
* Show error if printing with `:hardcopy` fails #564.
### Fixed
* Fixed compilation if source is not in a git repo (Thanks to Patrick Steinhardt).
* Fixed partial hidden hint labels on top of screen.
* Fix segfault on open in new tabe from context menu #556.
* Fix "... (null)" shown in title during url sanitization.
### Removed
* Setting `private-browsing` was removed in favor of `--incognito` option.
## [3.4.0] - 2019-03-26
### Added
* Allow to show video in fullscreen, without statusbar and inputbox, if requested.
* Added option `--no-maximize` to no start with maximized window #483.
* New setting `prevent-newwindow` to enforce opening links into same window
even if they are crafted by `target="_blank"` or using `window.open(...)` #544.
### Changed
* Increased min required webkit version to 2.20.x.
* Use man page date instead of build date ot make reproducible builds.
* URLs shown on statusbar and title are now shown as punicode if they contain
homographs.
### Fixed
* Fix out-of-bounds buffer access in parse_command (Thanks to Sören Tempel) #529.
* Fixed none shown hint labels by Content-Security-Policy headers #531.
* Fixed segfault on JavaScript `window.close()` call #537.
* Fixed no char inserted in input mode after timeout and imap/inoremap
candidate #546.
## [3.3.0] - 2018-11-06
### Added
* Allow to change following webkit settings during runtime
* allow-file-access-from-file-urls
* allow-universal-access-from-file-urls
* Added `#define CHECK_WEBEXTENSION_ON_STARTUP 1` to config.def.h to enable
checks during runtime if the webextension file could be found. Hope that
this helps user to fix compile/installation issues easier.
* Re-Added support for page marks to jump around within long single pages by
using names marks.
Set a marks by `m{a-z}` in normal mode. Jump to marks by `'{a-z}`.
* Re-Added `gf` to show page source (Thanks to Leonardo Taccari) #361.
Webkit2 does not allow to show the page in the source view mode so the `gf`
writes the HTML to a temporary files and opens it in the editor configured
by `:set editor-command=...`
### Changed
* New created files in `$XDG_CONFIG_HOME/vimb` are generated with `0600`
permission to prevent cookies be observed on multi users systems. Existing
files are not affected by this change. It's a good advice to change the
permission of all the files in `$XDG_CONFIG_HOME/vimb` to `0600` by
hand.
### Fixed
* Fixed missing dependency in Makefile which possibly caused broken builds
(Thanks to Patrick Steinhardt).
* Fixed weird scroll position values shown in scroll indicator on some pages #501.
* Fixed wrong hint label position on xkcd.com #506.
* Fixed wrong hint label position in case of hints within iframes.
## [3.2.0] - 2018-06-16
### Added
* Allow basic motion commands for hinting too.
* Show the numbers of search matches in status bar.
* Show dialog if the page makes a permission request e.g. geolocation to allow
the user to make a decision.
* new Setting `show-titlebar` to toggle window decorations.
### Changed
* Use sqlite as cookie storage #470 to prevent cookies lost on running many
vimb instances.
* Start vimb with maximized window #483.
* Hints are now styled based on the vimbhint attributes. The old additional set
classes are not set anymore to the hints. So customized css for the hints have
to be adapted to this.
* Element ID is stored in case the editor was spawned. So it's now possible to
start the editor, load another page, come back and paste the editor contents
(thanks to Sven Speckmaier).
### Fixed
* Fixed none cleaned webextension object files on `make clean`.
* Remove none used gui styling for completion.
### Removed
* Removed webkit1 combat code.
## [3.1.0] - 2017-12-18
### Added
* Added completion of bookmarked URIs for `:bmr` to allow to easily remove
bookmarks without loading the page first.
* Refresh hints after scrolling the page or resizing the window which makes
extended hint mode more comfortable.
* Reintroduce the automatic commands from vimb2. An automatic command is
executed automatically in response to some event, such as a URI being opened.
### Changed
* Number of webprocesses in no longer limited to one.
* Treat hint label generation depending on the first hint-key char.
If first char is '0' generate numeric style labels else the labels start with
the first char (thanks to Yoann Blein).
* `hint-keys=0123` -> `1 2 3 10 11 12 13`
* `hint-keys=asdf` -> `a s d f aa as ad af`
* Show versions of used libs on `vimb --bug-info` and the extension directory
for easier issue investigation.
* During hinting JavaScript is enabled and reset to it's previous setting after
hinting is done might be security relevant.
* Allow extended hints mode also for open `g;o` to allow the user to toggle
checkboxes and radiobuttons of forms.
* Rename `hint-number-same-length` into `hint-keys-same-length` for consistency.
* Search is restarted on pressing `n` or `N` with previous search query if no
one was given (thanks to Yoann Blein).
### Fixed
* Deduced min required webkit version 2.16.x -> 2.8.x to compile vimb also on
older systems.
* Fixed undeleted desktop file on `make uninstall`.
* Fixed window not redrawn properly in case vimb was run within tabbed.
* Fixed cursor appearing in empty inputbox on searching in case a normal mode
command was used that switches vimb into command mode like 'T' or ':'.
* Fixed hint labels never started by the first char of the 'hint-keys'.
* Fixed items where added to history even when `history-max-items` is set to 0
(thanks to Patrick Steinhardt).
* Fixed hinting caused dbus timeout on attempt to open URI with location hash.
* Fixed wrong scroll position shown in the right of the statusbar on some pages.
* Fixed vimb keeping in normal mode when HTTP Authentication dialog is shown.
* Fixed password show in title bar and beeing written to hisotry in case the
pssword was given by URI like https://user:password@host.tdl.
## [3.0-alpha] - 2017-05-27
### Changed
* completely rebuild of vimb on webkit2 api.
* Syntax for the font related gui settings has be changed.
Fonts have to be given as `[ font-style | font-variant | font-weight | font-stretch ]? font-size font-family`
Example `set input-font-normal=bold 10pt "DejaVu Sans Mono"` instead of
previous `set input-fg-normal=DejaVu Sans Mono Bold 10`
* Renames some settings to consequently use dashed setting names. Following
settings where changed.
```
previous setting - new setting name
--------------------------------------
cursivfont - cursiv-font
defaultfont - default-font
fontsize - font-size
hintkeys - hint-keys
minimumfontsize - minimum-font-size
monofont - monospace-font
monofontsize - monospace-font-size
offlinecache - offline-cache
useragent - user-agent
sansfont - sans-serif-font
scrollstep - scroll-step
seriffont - serif-font
statusbar - status-bar
userscripts - user-scripts
xssauditor - xss-auditor
```
### Removed
* There where many features removed during the webkit2 migration. That will
hopefully be added again soon.
* auto-response-headers
* autocommands and augroups
* external downloader
* HSTS
* kiosk mode
* multiple ex commands on startup via `--cmd, -C`
* page marks
* prevnext
* showing page source via `gF` this viewtype is not supported by webkit
anymore.
* socket support
---
## [2.12] - 2017-04-11
### Added
* Queueing of key events - fixes swallowed chars in case of some imap bindings
#258 (thanks to Michael Mackus)
* Allow to disable xembed by `FEATURE_NO_XEMBED` to compile on wayland only
platforms (thanks to Patrick Steinhardt)
* Custom default_zoom setting disables HIGH_DPI logic (thanks to Robert Timm)
* Allow link activation from search result via `` #131
### Changed
* Allow shortcuts without parameters #329
* Write soup cache to disk after each page load to allow other instances to
pick this up.
* Use the beginning position of links for hinting (thanks to Yutao Yuan)
### Fixed
* Fix path expansion to accept only valid POSIX.1-2008 usernames (thanks to
Manzur Mukhitdinov)
* Fix default previouspattern (thanks to Nicolas Porcel)
## [2.11] - 2015-12-17
### Added
* Added hint-number-same-length option
* VERBOSE flag to Makefile to toggle verbose make on
* `` removes selections in normal mode
* Support for multiple configuration profiles. New parameter `-p` or
`--profile`
* Adds support for contenteditable attribute as input mode trigger
[#237](https://github.com/fanglingsu/vimb/issues/237)
* Added `^` as normal mode alias of `0`
[#236](https://github.com/fanglingsu/vimb/issues/236)
* Added :source command to source a config file
* Added path completion for :save command too
* Added closed-max-items option to allow to store more than one closed page
### Changed
* Set only required CFLAGS
* Replaced `-Wpedantic` with `-pedantic` CFLAGS for older gcc versions
* Check for focused editable element as soon as possible
* Do not blur the focused element after alt-tabbing
* Show typed text as last completion entry to easily change it
[#253](https://github.com/fanglingsu/vimb/issues/253)
### Fixed
* Fixed [#224](https://github.com/fanglingsu/vimb/issues/224): Wrong URL and
titles shown in case one or more pages could not be loaded.
* Fixed Makefile install target using -D
* Fixed [#232](https://github.com/fanglingsu/vimb/issues/232): Fixed misplaced
hint labels on some sites
* Fixed [#235](https://github.com/fanglingsu/vimb/issues/235): Randomly reset
cookie file
* Fixed none POSIX `echo -n` call
[Unreleased]: https://github.com/fanglingsu/vimb/compare/3.7.0...master
[3.7.1]: https://github.com/fanglingsu/vimb/compare/3.7.0...3.7.1
[3.7.0]: https://github.com/fanglingsu/vimb/compare/3.6.0...3.7.0
[3.6.0]: https://github.com/fanglingsu/vimb/compare/3.5.0...3.6.0
[3.5.0]: https://github.com/fanglingsu/vimb/compare/3.4.0...3.5.0
[3.4.0]: https://github.com/fanglingsu/vimb/compare/3.3.0...3.4.0
[3.3.0]: https://github.com/fanglingsu/vimb/compare/3.2.0...3.3.0
[3.2.0]: https://github.com/fanglingsu/vimb/compare/3.1.0...3.2.0
[3.1.0]: https://github.com/fanglingsu/vimb/compare/3.0-alpha...3.1.0
[3.0-alpha]: https://github.com/fanglingsu/vimb/compare/2.12...3.0-alpha
[2.12]: https://github.com/fanglingsu/vimb/compare/2.11...2.12
[2.11]: https://github.com/fanglingsu/vimb/compare/2.10...2.11
fanglingsu-vimb-448e7e2/CONTRIBUTING.md 0000664 0000000 0000000 00000006733 15145416123 0017412 0 ustar 00root root 0000000 0000000 # Contribute
This document contains guidelines for contributing to vimb, as well as useful
hints when doing so.
## Goals
Getting a light, fast and keyboard-driven browser that is easy to use for
those users familiar with vim.
- Provide powerful knobs allowing the user to tweak vimb to fit the own needs
and usecases.
- Add only knobs/features that do not do what other knobs do. In this point
vimb is in contrast to vim.
- If there are two colliding features we should pick the mightier one, or that
which need less code or resources.
## Find something to work on
If you are interested to contribute to vimb it's a good idea to check if
someone else is already working on. I would be a shame when you work was for
nothing.
If you have some ideas how to improve vimb by new features or to simplify it.
Write an issue so that other contributors can comment/vote on it or help you
with it.
If you do not want to write code you are pretty welcome to update
[documentation][issue-doc] or to argue and vote for features and [request for
comments][issue-rfc].
## Communication
If you want to discuss some feature or provide some suggestion that can't be
done very well with the github issues. You should use the [mailing list][mail]
for this purpose. Else it's a good decision to use the features provided by
github for that.
## Patching and Coding style
### File Layout
- Comment with LICENSE and possibly short explanation of file/tool
- Headers
- Macros
- Types
- Function declarations
- Include variable names
- For short files these can be left out
- Group/order in logical manner
- Global variables
- Function definitions in same order as declarations
- main
### C Features
- Do not mix declarations and code
- Do not use for loop initial declarations
- Use `/* */` for comments, not `//`
### Headers
- Place system/libc headers first in alphabetical order
- If headers must be included in a specific order comment to explain
- Place local headers after an empty line
### Variables
- Global variables not used outside translation unit should be declared static
- In declaration of pointers the `*` is adjacent to variable name, not type
### Indentation
- the code is indented by 4 spaces - if you use vim to code you can set
`:set expandtab ts=4 sts=4 sw=4`
- it's a good advice to orientate on the already available code
- if you are using `indent`, following options describe best the code style
- `--k-and-r-style`
- `--case-indentation4`
- `--dont-break-function-decl-args`
- `--dont-break-procedure-type`
- `--dont-line-up-parentheses`
- `--no-tabs`
## directories
├── doc documentation like manual page
└── src all sources to build vimb
├── scripts JavaScripts and CSS that are compiled in for various purposes
└── webextension Source files for the webextension
## compile and run
To inform vimb during compile time where the webextension should be loaded
from, the `RUNPREFIX` option can be set to a full qualified path to the
directory where the extension should be stored in.
To run vimb without installation you could run as a sandbox like this
make runsandbox
This will compile and install vimb into the local _sandbox_ folder in the
project directory.
[mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list"
[issue-doc]: https://github.com/fanglingsu/vimb/labels/component%3A%20docu
[issue-rfc]: https://github.com/fanglingsu/vimb/labels/rfc
fanglingsu-vimb-448e7e2/LICENSE 0000664 0000000 0000000 00000104513 15145416123 0016161 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
fanglingsu-vimb-448e7e2/Makefile 0000664 0000000 0000000 00000003226 15145416123 0016613 0 ustar 00root root 0000000 0000000 version = 3.7.0
include config.mk
all: version.h src.subdir-all
version.h: Makefile $(wildcard .git/index)
@echo "create $@"
$(Q)v="$$(git describe --tags 2>/dev/null)"; \
echo "#define VERSION \"$${v:-$(version)}\"" > $@
options:
@echo "vimb build options:"
@echo "LIBS = $(LIBS)"
@echo "CFLAGS = $(CFLAGS)"
@echo "LDFLAGS = $(LDFLAGS)"
@echo "EXTCFLAGS = $(EXTCFLAGS)"
@echo "CC = $(CC)"
install: all
@# binary
install -d $(BINPREFIX)
install -m 755 src/vimb $(BINPREFIX)/vimb
@# extension
install -d $(LIBDIR)
install -m 644 src/webextension/$(EXTTARGET) $(LIBDIR)/$(EXTTARGET)
@# man page
install -d $(MANPREFIX)/man1
@sed -e "s!VERSION!$(version)!g" \
-e "s!PREFIX!$(PREFIX)!g" \
-e "s!DATE!`date -u -r $(DOCDIR)/vimb.1 +'%m %Y' 2>/dev/null || date +'%m %Y'`!g" $(DOCDIR)/vimb.1 > $(MANPREFIX)/man1/vimb.1
@# .desktop file
install -d $(DOTDESKTOPPREFIX)
install -m 644 vimb.desktop $(DOTDESKTOPPREFIX)/vimb.desktop
@# .metainfo.xml file
install -d $(METAINFOPREFIX)
install -m 644 vimb.metainfo.xml $(METAINFOPREFIX)/vimb.metainfo.xml
uninstall:
$(RM) $(BINPREFIX)/vimb
$(RM) $(DESTDIR)$(MANDIR)/man1/vimb.1
$(RM) $(LIBDIR)/$(EXTTARGET)
$(RM) $(DOTDESKTOPPREFIX)/vimb.desktop
$(RM) $(METAINFOPREFIX)/vimb.metainfo.xml
clean: src.subdir-clean test-clean
sandbox:
$(Q)$(MAKE) RUNPREFIX=$(CURDIR)/sandbox/usr PREFIX=/usr DESTDIR=./sandbox install
runsandbox: sandbox
sandbox/usr/bin/vimb
test: version.h
$(MAKE) -C src vimb.so
$(MAKE) -C tests
test-clean:
$(MAKE) -C tests clean
%.subdir-all:
$(Q)$(MAKE) -C $*
%.subdir-clean:
$(Q)$(MAKE) -C $* clean
.PHONY: all options install uninstall clean sandbox runsandbox
fanglingsu-vimb-448e7e2/README.md 0000664 0000000 0000000 00000010736 15145416123 0016436 0 ustar 00root root 0000000 0000000 # Vimb - the Vim-like browser
[](https://travis-ci.com/fanglingsu/vimb)
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://github.com/fanglingsu/vimb/releases/latest)
Vimb is a Vim-like web browser that is inspired by Pentadactyl and Vimprobable.
The goal of Vimb is to build a completely keyboard-driven, efficient and
pleasurable browsing-experience with low memory and CPU usage that is
intuitive to use for Vim users.
More information and some screenshots of Vimb browser in action can be found on
the project page of [Vimb][].
## Features
- it's modal like Vim
- Vim like keybindings - assignable for each browser mode
- nearly every configuration can be changed at runtime with Vim like set syntax
- history for `ex` commands, search queries, URLs
- completions for: commands, URLs, bookmarked URLs, variable names of settings, search-queries
- hinting - marks links, form fields and other clickable elements to
be clicked, opened or inspected
- SSL validation against ca-certificate file
- user defined URL-shortcuts with placeholders
- read it later queue to collect URIs for later use
- multiple yank/paste registers
- Vim like autocmd - execute commands automatically after an event on specific URIs
## Packages
- Arch Linux: [extra/vimb][], [aur/vimb-git][], [aur/vimb-gtk2][]
- Debian: [trixie/vimb][], [sid/vimb][]
- Fedora: [fedora/vimb][],
- Gentoo: [tharvik overlay][], [jjakob overlay][]
- openSUSE: [network/vimb][]
- pkgsrc: [pkgsrc/www/vimb][], [pkgsrc/wip/vimb-git][]
- Slackware: [slackbuild/vimb][]
## dependencies
- gtk+-3.0
- webkit2gtk-4.1
- gst-libav, gst-plugins-good (optional, for media decoding among other things)
## Install
Edit `config.mk` to match your local setup. You might need to do this if
you use another compiler, like tcc. Most people, however, will almost never
need to do this on systems like Ubuntu or Debian.
Edit `src/config.h` to match your personal preferences, like changing the
characters used in the loading bar, or the font.
The default `Makefile` will not overwrite your customised `config.h` with the
contents of `config.def.h`, even if it was updated in the latest git pull.
Therefore, you should always compare your customised `config.h` with
`config.def.h` and make sure you include any changes to the latter in your
`config.h`.
Run the following commands to compile and install Vimb (if necessary, the last one as
root). If you want to change the `PREFIX`, note that it's required to give it on both stages, build and install.
make PREFIX=/usr
make PREFIX=/usr install
To run vimb without installation for testing it out use the 'runsandbox' make
target.
make runsandbox
## Mailing list
- feature requests, issues and patches can be discussed on the [mailing list][mail] ([list archive][mail-archive])
## Similar projects
- [luakit](https://luakit.github.io/)
- [qutebrowser](https://www.qutebrowser.org/)
- [surf](https://surf.suckless.org/)
- [uzbl](https://www.uzbl.org/)
- [wyeb](https://github.com/jun7/wyeb)
## license
Information about the license are found in the file LICENSE.
## about
- https://en.wikipedia.org/wiki/Vimb
- http://thedarnedestthing.com/vimb
- https://blog.jeaye.com/2015/08/23/vimb/
[aur/vimb-git]: https://aur.archlinux.org/packages/vimb-git
[aur/vimb-gtk2]: https://aur.archlinux.org/packages/vimb-gtk2/
[trixie/vimb]: https://packages.debian.org/trixie/vimb
[sid/vimb]: https://packages.debian.org/sid/vimb
[extra/vimb]: https://www.archlinux.org/packages/extra/x86_64/vimb/
[fedora/vimb]: https://src.fedoraproject.org/rpms/vimb
[tharvik overlay]: https://github.com/tharvik/overlay/tree/master/www-client/vimb
[jjakob overlay]: https://github.com/jjakob/gentoo-overlay/tree/master/www-client/vimb
[mail-archive]: https://sourceforge.net/p/vimb/vimb/vimb-users/ "vimb - mailing list archive"
[mail]: https://lists.sourceforge.net/lists/listinfo/vimb-users "vimb - mailing list"
[network/vimb]: https://build.opensuse.org/package/show/network/vimb
[pkgsrc/wip/vimb-git]: http://pkgsrc.se/wip/vimb-git
[pkgsrc/www/vimb]: http://pkgsrc.se/www/vimb
[slackbuild/vimb]: https://slackbuilds.org/repository/14.2/network/vimb/
[vimb]: https://fanglingsu.github.io/vimb/ "Vimb - Vim like browser project page"
fanglingsu-vimb-448e7e2/config.mk 0000664 0000000 0000000 00000003071 15145416123 0016747 0 ustar 00root root 0000000 0000000 ifneq ($(V),1)
Q := @
endif
PREFIX ?= /usr/local
BINPREFIX := $(DESTDIR)$(PREFIX)/bin
MANPREFIX := $(DESTDIR)$(PREFIX)/share/man
EXAMPLEPREFIX := $(DESTDIR)$(PREFIX)/share/vimb/example
DOTDESKTOPPREFIX := $(DESTDIR)$(PREFIX)/share/applications
METAINFOPREFIX := $(DESTDIR)$(PREFIX)/share/metainfo
LIBDIR := $(DESTDIR)$(PREFIX)/lib/vimb
RUNPREFIX := $(PREFIX)
EXTENSIONDIR := $(RUNPREFIX)/lib/vimb
OS := $(shell uname -s)
# define some directories
SRCDIR = src
DOCDIR = doc
# used libs
LIBS = gtk+-3.0 webkit2gtk-4.1
# setup general used CFLAGS
# Use 'override' to ensure these flags are added even when CFLAGS is set on command line
override CFLAGS += -std=c99 -pipe -Wall -fPIC
CPPFLAGS += -DEXTENSIONDIR=\"${EXTENSIONDIR}\"
CPPFLAGS += -DPROJECT=\"vimb\" -DPROJECT_UCFIRST=\"Vimb\"
CPPFLAGS += -DGSEAL_ENABLE
CPPFLAGS += -DGTK_DISABLE_SINGLE_INCLUDES
CPPFLAGS += -DGDK_DISABLE_DEPRECATED
ifeq "$(findstring $(OS),FreeBSD DragonFly)" ""
CPPFLAGS += -D_XOPEN_SOURCE=500
CPPFLAGS += -D__BSD_VISIBLE
endif
# flags used to build webextension
EXTTARGET = webext_main.so
EXTCFLAGS = ${CFLAGS} $(shell pkg-config --cflags webkit2gtk-web-extension-4.1)
EXTCPPFLAGS = $(CPPFLAGS)
EXTLDFLAGS = ${LDFLAGS} $(shell pkg-config --libs webkit2gtk-web-extension-4.1) -shared
# flags used for the main application
# Use 'override' to ensure these flags are added even when CFLAGS/LDFLAGS is set on command line
override CFLAGS += $(shell pkg-config --cflags $(LIBS))
override LDFLAGS += $(shell pkg-config --libs $(LIBS))
fanglingsu-vimb-448e7e2/doc/ 0000775 0000000 0000000 00000000000 15145416123 0015715 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/doc/vimb.1 0000664 0000000 0000000 00000137234 15145416123 0016746 0 ustar 00root root 0000000 0000000 .\" vim: ft=groff
.TH VIMB 1 "DATE" "vimb/VERSION" "Vimb Manual"
.SH NAME
Vimb - Vim Browser - A modal web browser based on WebKit, inspired by Vim: the
great editor.
.
.
.SH SYNOPSIS
.B vimb
.OP OPTIONS
.RI [ URI "|" file "|" - ]
.
.
.SH DESCRIPTION
Vimb is a WebKit based web browser that behaves like the Vimperator
plugin for Firefox and has usage paradigms from the great editor, Vim.
The goal of Vimb is to build a completely keyboard-driven, efficient
and pleasurable browsing-experience.
.
.
.SH OPTIONS
If no \fIURI\fP or \fIfile\fP is given, Vimb will open the configured
home-page.
If \fIURI\fP is '-', Vimb reads the HTML to display from stdin.
.P
Mandatory arguments to long options are mandatory for short options too.
.TP
.BI "\-C, \-\-cmd " "CMD"
Run \fICMD\fP as ex command line right before the first page is loaded.
If the flag is used more than one time, the commands are called in order they
are given.
You could also pass several ex commands in one \fICMD\fP,
if they are separated by "|".
.sp
.EX
vimb --cmd "set dark-mode=on|set header=Referer,DNT=1"
.EE
.TP
.BI "\-c, \-\-config " "FILE"
Use custom configuration given as \fIFILE\fP.
This will also be applied on new spawned instances.
.TP
.BI "\-e, \-\-embed " "WINID"
.I WINID
of an XEmbed-aware application, that Vimb will use as its parent.
.TP
.B "\-i, \-\-incognito"
Start an instance with user data read-only (see \fIFILES\fP section).
.TP
.B "\-h, \-\-help"
Show help options.
.TP
.BI "\-p, \-\-profile " "PROFILE-NAME"
Create or open specified configuration profile.
Configuration data for the profile is stored in a directory named
\fIPROFILE-NAME\fP under default directory for configuration data.
.TP
.B "\-v, \-\-version"
Print build and version information and then quit.
.TP
.B "\-\-no-maximize"
Do no attempt to maximize window.
.TP
.B "\-\-bug-info"
Prints information about used libraries for bug reports and then quit.
.
.
.SH MODES
Vimb is modal and has the following main modes:
.IP "Normal Mode"
The default mode.
Pressing Escape always enter normal mode.
.IP "Input Mode"
Used for editing text elements in a webpage.
.IP "Command Mode"
Execute `ex` commands from the builtin inputbox (commandline).
.IP "Pass-Through Mode"
In Pass-Through mode only the `` and `` keybindings are interpreted
by Vimb, all other keystrokes are given to the webview to handle them.
This allows the use of a website's configured keybindings, that might otherwise
be swallowed by Vimb.
.
.
.SH NORMAL MODE COMMANDS
Some of the Normal Model Commands can have a numeric count to multiply the
effect of the command.
If a command supports the count this is shown as [\fBN\fP].
.
.SS General
.TP
.B :
Start Command Mode and print `:' to the input box.
.TP
.B gi
Set cursor to the first editable element in the page and switch to Input
Mode.
.TP
.B i
Set cursor to the last focused element in the page and switch to Input Mode.
If no element was focused before the first element is focused like with `gi'.
.TP
.B CTRL\-Z
Switch Vimb into Pass-Through Mode.
.TP
.B gf
Open the configured editor (`editor-command') with the current page's content.
.TP
.B gF
Open the Web Inspector for the current page.
.TP
.B CTRL\-V
Pass the next key press directly to GTK.
.TP
.B CTRL\-Q
Quit the browser if there are no running downloads.
.
.SS Navigation
.TP
.B o
Start Command Mode and print `:open ' to the input box.
.TP
.B O
Start Command Mode and print `:open URI' to the input box.
.TP
.B t
Start Command Mode and print `:tabopen ' to the input box.
.TP
.B T
Start Command Mode and print `:tabopen URI' to the input box.
.TP
.B gh
Open the configured home-page.
.TP
.B gH
Open the configured home-page in a new window.
.TP
.B u
Open the last closed page.
.TP
.B U
Open the last closed page in a new window.
.TP
.B CTRL\-P
Open the oldest entry from the read it later queue in the current browser
window.
.TP
.BI [ \(dqx ]p
Open the URI out of the register \fIx\fP or, if not given, from the clipboard.
.TP
.BI [ \(dqx ]P
Open the URI out of the register \fIx\fP or, if not given, from the clipboard in a
new window.
.TP
.BI [ N ]CTRL\-O
Go back \fIN\fP steps in the browser history.
.TP
.BI [ N ]CTRL\-I
Go forward \fIN\fP steps in the browser history.
.TP
.BI [ N ]gu
Go to the \fIN\fPth descendent directory of the current opened URI.
.TP
.B gU
Go to the domain of the current opened page.
.TP
.B r
Reload the website.
.TP
.B R
Reload the website without using caches.
.TP
.B CTRL\-C
Stop loading the current page.
.TP
.B CTRL-LeftMouse, MiddleMouse
Opens the clicked link in a new window.
.TP
.B Shift-LeftMouse
Push the link url under the cursor to the end of the Read It Later queue like
the `:qpush' command.
.
.SS Motion
.TP
.BI [ N ]CTRL\-F
Scroll \fIN\fP pages down.
.TP
.BI [ N ]CTRL\-B
Scroll \fIN\fP pages up.
.TP
.BI [ N ]CTRL\-D
Scroll \fIN\fP half pages down.
.TP
.BI [ N ]CTRL\-U
Scroll \fIN\fP half pages up.
.TP
.BI [ N ]gg
Scroll to the top of the current page.
Or if \fIN\fP is given to \fIN\fP% of the page.
.TP
.BI [ N ]G
Scroll to the bottom of the current page.
Or if \fIN\fP is given to \fIN\fP% of the page.
.TP
.B 0, ^
Scroll to the absolute left of the document.
Unlike in Vim, 0 and ^ work exactly the same way.
.TP
.B $
Scroll to the absolute right of the document.
.TP
.BI [ N ]h
Scroll \fIN\fP steps to the left of page.
.TP
.BI [ N ]l
Scroll \fIN\fP steps to the right of page.
.TP
.BI [ N ]j
Scroll page \fIN\fP steps down.
.TP
.BI [ N ]k
Scroll page \fIN\fP steps up.
.TP
.BI m{ a-z }
Set a page mark {\fIa-z\fP} at the current position on the page.
Such set marks are only available on the current page;
if the page is left, all marks will be removed.
.TP
.BI m{ A-Z }
Set a page mark {\fIA-Z\fP} at the current position on the page.
These marks are permanent across any page.
.TP
.BI '{ a-z }
Jump to the mark {\fIa-z\fP} on the current page.
.TP
.BI '{ A-Z }
Jump to the global mark {\fIA-Z\fP} on a (possibly) different page.
.TP
.B ''
Jumps to the position before the latest jump, or where the last "m'" command
was given.
.
.SS Hinting
Hinting in Vimb is how you accomplish the tasks that you would do with the
mouse in common mouse-driven browsers: open a URI, yank a URI, save a page and
so on. When hinting is started, the relevant elements on the page will
be marked by labels generated from configured `hint-keys'.
Hints can be selected by using , or , ,
by typing the chars of the label, or filtering the elements by some text
that is part of the hinted element (like URI, link text, button label)
or any combination of these methods.
If is pressed, the current active hint will be fired.
If only one possible hint remains, this will be fired automatically.
.P
.BR Syntax: " ;{mode}{hint}"
.P
Start Hints mode.
Different elements depending on \fImode\fP are highlighted and `numbered'.
Elements can be selected either by typing their label, or by typing part
of their text (\fIhint\fP) to narrow down the result.
When an element has been selected, it is automatically clicked
or used (depending on \fImode\fP) and hint mode ends.
.P
The filtering of hints by text splits the query at ' ' and use the single parts
as separate queries to filter the hints.
This is useful for hints that have a lot of filterable chars in common
and many chars are required to make a distinct selection.
For example ';over tw' will easily select the second hint out of
{'very long link text one', 'very long link text two'}.
.P
The following keys have special meanings in Hints modes:
.PD 0
.TP
.B
Selects the first highlighted element, or the current focused.
.TP
.B
Moves the focus to the next hint element.
.TP
.B
Moves the focus to the previous hint element.
.TP
.B , CTRL\-C, CTRL\-[
Exits Hints mode without selecting an element.
.PD
.TP
.B Hint modes:
.RS
.PD 0
.TP
.B f
Is an alias for the \fB;o\fP hint mode.
.TP
.B F
Is an alias for the \fB;t\fP hint mode.
.TP
.B ;o
Open hint's location in the current window.
.TP
.B ;t
Open hint's location in a new window.
.TP
.B ;s
Saves the hint's destination under the configured `download-path'.
.TP
.B ;O
Generate an `:open' prompt with hint's URI.
.TP
.B ;T
Generate an `:tabopen' prompt with hint's URI.
.TP
.B ;e
Open the configured editor (`editor-command') with the hinted form element's
content.
If the file in editor is saved and the editor is closed, the file
content will be put back in the form field.
.TP
.B ;i
Open hinted image in the current window.
.TP
.B ;I
Open hinted image in a new window.
.TP
.B ;k
Kill (remove) hinted element from the page.
.TP
.B ;p
Push the hint's URI to the end of the Read It Later queue like the `:qpush'
command.
This is only available if Vimb was compiled with the QUEUE feature.
.TP
.B ;P
Push the hint's URI to the beginning of the Read It Later queue like the
`:qunshift' command.
This is only available if Vimb was compiled with the QUEUE feature.
.TP
.B ;x
Hints like ;o, but instead of opening the hinted URI, the
`x-hint-command' is run in Vimb.
.TP
.BI [ \(dqx ];y
Yank hint's destination location into primary and secondary clipboard and into
the register \fIx\fP.
.TP
.BI [ \(dqx ];Y
Yank hint's text description or form text into primary and secondary clipboard
and into the register \fIx\fP.
.PD
.RE
.TP
.BR Syntax: " g;{mode}{hint}"
Start an extended hints mode and stay there until is pressed.
Like normal hinting, except that after a hint is selected, hints
remain visible so that another one can be selected with the same action
as the first.
Note that the extended hint mode can only be combined with the following
hint modes
.IR "I o p P s t y Y" .
.PD
.TP
.B Motion
.RS
Motions commands are like those for normal mode except that CTRL is used as
modifier.
But they can not be used together with a count.
.P
.PD 0
.TP
.B CTRL-F
Scroll one page down.
.TP
.B CTRL-B
Scroll one page up.
.TP
.B CTRL-D
Scroll half page down.
.TP
.B CTRL-U
Scroll half page up.
.TP
.B CTRL-J
Scroll one step down.
.TP
.B CTRL-K
Scroll one step up.
.PD
.RE
.
.SS Searching
.TP
.BI / QUERY ", ?" QUERY
Start searching for \fIQUERY\fP in the current page.
\fI/\fP start search forward, \fI?\fP in backward direction.
.TP
.BR * , " #"
Start searching for the current selected text, or if no text is selected for
the content of the primary or secondary clipboard.
\fI*\fP start the search in forward direction and \fI#\fP in backward
direction.
.sp
Note that these commands will yank the text selection into the clipboard and
may remove other content from there!
.TP
.BI [ N ]n
Search for \fIN\fPnth next search result depending on current search
direction.
.TP
.BI [ N ]N
Search for \fIN\fPnth previous search result depending on current search
.TP
.B
Perform a click on element containing the current highlighted search result.
direction.
.
.SS Zooming
.TP
.BI [ N ]zi
Zoom-In the text of the page by \fIN\fP steps.
.TP
.BI [ N ]zo
Zoom-Out the text of the page by \fIN\fP steps.
.TP
.BI [ N ]zI
Full-Content Zoom-In the page by \fIN\fP steps.
.TP
.BI [ N ]zO
Full-Content Zoom-Out the page by \fIN\fP steps.
.TP
.B zz
Reset Zoom.
.
.SS Yank
.TP
.BI [ \(dqx ]y
Yank the URI or current page into register \fIx\fP and clipboard.
.TP
.BI [ \(dqx ]Y
Yank the current selection into register x and clipboard.
.
.
.SH COMMAND MODE
Commands that are listed below are ex-commands like in Vim, that are typed
into the inputbox (the command line of vimb).
The commands may vary in their syntax or in the parts they allow,
but in general they follow a simple syntax.
.P
.BR Syntax: " :[:| ][N]cmd[name][!][ lhs][ rhs]"
.sp
Where \fIlhs\fP (left hand side) must not contain any unescaped space.
The syntax of the rhs (right hand side) if this is available depends on the
command.
At the moment the count parts [N] of commands is parsed, but currently there is
no command that uses the count.
.sp
Commands that are typed interactivly are normally recorded into command
history and register.
To avoid this, the commands can be prefixed by one or more additional `:' or
whitespace.
.P
Multiple commands, separated by a `|' can be given in a single command line
and will be executed consecutively.
The pipe can be included as an argument to a command by escaping it with a
backslash.
.br
Following commands process the entire command-line string literally.
These commands will include any `|' as part of their argument string and so
can not be followed by another command.
.P
.PD 0
.IP - 2
autocmd
.IP -
cmap, cnoremap, imap, inoremap, nmap, nnoremap
.IP -
eval
.IP -
normal
.IP -
open, tabopen
.IP -
shellcmd, shellex
.PD
.
.SS Command Line Editing
.TP
.B , CTRL\-[, CTRL-C
Ignore all typed content and switch back to normal mode.
.TP
.B
Submit the entered `ex` command or search query to run it.
.TP
.B CTRL\-H
Deletes the char before the cursor.
.TP
.B CTRL\-W
Deletes the last word before the cursor.
.TP
.B CTRL\-U
Remove everything between cursor and prompt.
.TP
.B CTRL\-B
Moves the cursor directly behind the prompt `:'.
.TP
.B CTRL\-E
Moves the cursor after the prompt in inputbox.
.TP
.B CTRL\-V
Pass the next key press directly to GTK.
.TP
.B CTRL\-R {a-z"%:/;}
Insert the content of given register at cursor position.
See also section about `:reg[ister]' command.
.
.SS Command Line History
.TP
.B
Start completion of the content in the inputbox in forward direction.
.TP
.B
Start completion of the content in the inputbox in backward direction.
.TP
.B
Step backward in the command history.
.TP
.B
Step forward in the command history.
Yank the current selection into register \fIx\fP and clipboard.
.
.SS Open
.TP
.BI ":o[pen] [" URI ]
Open the give \fIURI\fP in the current window.
If \fIURI\fP is empty, the configured 'home-page' is opened.
.TP
.BI ":t[abopen] [" URI ]
Open the give \fIURI\fP in a new window.
If \fIURI\fP is empty, the configured 'home-page' is opened.
.
.SS Key Mapping
Key mappings allow users to alter the actions of key presses.
Each key mapping is associated with a mode and only has effect
when the mode is active.
The following commands allow the user to substitute one sequence
of key presses by another.
.P
.BR Syntax: " :{m}map {lhs} {rhs}"
.P
Note that the \fIlhs\fP ends with the first found space.
If you want to use space also in the {lhs} you have to escape this
with a single '\e', as shown in the examples.
.sp
The \fIrhs\fP starts with the first non-space char. If you want a \fIrhs\fP
that starts with a space, you have to use "".
.P
Standard key mapping commands are provided for these modes \fIm\fP:
.PD 0
.TP
.B n
Normal mode: when browsing normally.
.TP
.B i
Insert mode: when interacting with text fields on a website.
.TP
.B c
Command Line mode: when typing into Vimb's command line.
.PD
.P
Most keys in key sequences are represented simply by the character that you
see on the screen when you type them.
However, as a number of these characters have special meanings, and a
number of keys have no visual representation, a special notation is required.
.P
As special key names have the format \fI<...>\fP.
The following special keys can be used: , , ,
for the cursor keys, , , , , , - and -.
If you want an actual sequence of keys like "<", "C", "R", ">" then
escape with a backslash: "\". If you want a backslash followed by a special
key then use .
.TP
.PD 0
.BI ":nm[ap] {" lhs "} {" rhs }
.TP
.BI ":im[ap] {" lhs "} {" rhs }
.TP
.BI ":cm[ap] {" lhs "} {" rhs }
Map the key sequence \fIlhs\fP to \fIrhs\fP for the modes where the map
command applies.
The result, including \fIrhs\fP, is then further scanned for mappings.
This allows for nested and recursive use of mappings.
.RS
.sp
.IP ":cmap h /home/user/downloads/"
Adds a keybind to insert a file path into the input box.
This could be useful for the `:save' command
that could be used as ":save ^Gh".
.IP ":nmap :set scripts=on:open !glib"
This will enable scripts and lookup the first bookmarked URI with the tag
`glib' and open it immediately if F1 key is pressed.
.IP ":nmap \e \e 50G"
Example which maps two spaces to go to 50% of the page.
.RE
.TP
.BI ":nn[oremap] {" lhs "} {" rhs }
.TP
.BI ":ino[remap] {" lhs "} {" rhs }
.TP
.BI ":cno[remap] {" lhs "} {" rhs }
Map the key sequence \fIlhs\fP to \fIrhs\fP for the mode where the map command
applies.
Disallow mapping of \fIrhs\fP, to avoid nested and recursive mappings.
Often used to redefine a command.
.TP
.BI ":nu[nmap] {" lhs }
.TP
.BI ":iu[nmap] {" lhs }
.TP
.BI ":cu[nmap] {" lhs }
Remove the mapping of \fIlhs\fP for the applicable mode.
.PD
.
.SS Bookmarks
.TP
.BI ":bma [" tags ]
Save the current opened URI with \fItags\fP to the bookmark file.
.TP
.BI ":bmr [" URI ]
Removes all bookmarks for given \fIURI\fP or, if not given, the current opened
page.
.
.SS Handlers
Handlers allow specifying external scripts to handle alternative URI methods.
.TP
.BI ":handler-add " "handler" "=" "cmd"
Adds a handler to direct \fIhandler\fP links to the external \fIcmd\fP.
The \fIcmd\fP can contain one placeholder `%s` that will be filled by the
full URI given when the command is called.
.RS
.P
.PD 0
.IP ":handler-add mailto=urxvt -e mutt %s"
to start email client for mailto links.
.IP ":handler-add magnet=xdg-open %s"
to open magnet links with xdg-open.
.IP ":handler-add ftp=urxvt -e wget %s -P ~/ftp-downloads"
to handle ftp downloads via wget.
.PD
.RE
.TP
.BI ":handler-remove " "handler"
Remove the handler for the given URI \fIhandler\fP.
.
.SS Shortcuts
Shortcuts allow the opening of an URI built up from a named template with additional
parameters.
If a shortcut named 'dd' is defined, you can use it with `:open dd
list of parameters' to open the generated URI.
.P
Shortcuts are convenient to use with search engines where the URI is standardised
and a single parameter is user defined.
.TP
.BI ":shortcut-add " shortcut = URI
Adds a shortcut with the \fIshortcut\fP and \fIURI\fP template.
The \fIURI\fP can contain multiple placeholders $0-$9 that will be
filled by the parameters given when the shortcut is called.
The parameters given when the shortcut is called will be split
into as many parameters like the highest used placeholder.
.sp
To use spaces within the parameters, the parameters can be grouped by
surrounding them with single-or double quotes-as shown in example shortcut
`map'.
.RS
.P
.PD 0
.IP ":shortcut-add dl=https://duckduckgo.com/lite/?q=$0"
to setup a search engine.
Can be called by `:open dl my search phrase'.
.IP ":shortcut-add gh=https://github.com/$0/$1"
to build URIs from given parameters.
Can be called `:open gh fanglingsu vimb'.
.IP ":shortcut-add map=https://maps.google.com/maps?saddr=$0&daddr=$1"
to search for a route, all but the last parameter must be quoted if they
contain spaces like `:open map "city hall, London" railway station, London'
.PD
.RE
.TP
.BI ":shortcut-remove " shortcut
Remove the search engine to the given \fIshortcut\fP.
.TP
.BI ":shortcut-default " shortcut
Set the shortcut for given \fIshortcut\fP as the default, that is the shortcut
to be used if no shortcut is given and the string to open is not an URI. It
doesn't matter if the \fIshortcut\fP is already in use or not to be able to set
it.
.
.SS Settings
.TP
.BI ":se[t] " var = value
Set configuration values named by \fIvar\fP.
To set boolean variable you should use 'on', 'off' or 'true' and 'false'.
Colors are given as hexadecimal value like '#f57700'. Spaces or more equals
signs in \fIvalue\fP just work without quotes: for example,
":set sans-serif-font=Some Sans Font".
.TP
.BI ":se[t] " var += value
Add the \fIvalue\fP to a number option, or append the \fIvalue\fP to a string
option.
When the option is a comma separated list, a comma is added, unless
the value was empty.
.TP
.BI ":se[t] " var ^= value
Multiply the \fIvalue\fP to a number option, or prepend the \fIvalue\fP to a
string option.
When the option is a comma separated list, a comma is added,
unless the value was empty.
.TP
.BI ":se[t] " var -= value
Subtract the \fIvalue\fP from a number option, or remove the \fIvalue\fP from
a string option, if it is there.
When the option is a comma separated list, a
comma is deleted, unless the option becomes empty.
.TP
.BI ":se[t] " var ?
Show the current set value of variable.
.IR VAR .
.TP
.BI ":se[t] " var !
Toggle the value of boolean variable \fIvar\fP and display the new set value.
.
.SS Queue
The queue allows the marking of URIs for later reading.
This list is shared between the single instances of Vimb.
.TP
.BI ":qpu[sh] [" URI ]
Push \fIURI\fP or, if not given, the current URI to the end of the queue.
.TP
.BI ":qun[shift] [" URI ]
Push \fIURI\fP or, if not given, the current URI to the beginning of the queue.
.TP
.B :qp[op]
Open the oldest queue entry in the current browser window and remove it from the
queue.
.TP
.B :qc[lear]
Removes all entries from queue.
.
.SS Automatic commands
An autocommand is a command that is executed automatically in response to some
event, such as a URI being opened.
Autocommands are very powerful.
Use them with care and they will help you avoid typing many commands.
.P
Autocommands are built with following properties.
.TP
.I group
When the [\fIgroup\fP] argument is not given, Vimb uses the current group as
defined with ':augroup', otherwise, Vimb uses the group defined with
[\fIgroup\fP].
Groups are useful to remove multiple grouped autocommands.
.TP
.I event
You can specify a comma separated list of event names.
No white space can be used in this list.
.P
.RS
.PD 0
Events:
.TP
.B LoadStarting
Fired before a new page is going to be opened.
No data has been sent or received yet, the load may still fail for transport issues.
.TP
.B LoadStarted
Fired if a new page is going to be opened.
No data has been received yet, the load may still fail for transport issues.
.TP
.B LoadCommitted
Fired if first data chunk has arrived, meaning that the necessary transport
requirements are established, and the load is being performed.
This is the right event to toggle content related setting
like 'scripts', 'plugins' and such things.
.TP
.B LoadFinished
Fires when everything that was required to display on the page has been loaded.
.TP
.B DownloadStarted
Fired right after a download is started.
.TP
.B DownloadFinished
Fired if a Vimb managed download is finished.
.TP
.B DownloadFailed
Fired if a Vimb managed download failed.
.PD
.RE
.TP
.I pat
Comma separated list of patterns, matches in order to check if a autocommand
applies to the URI associated to an event.
To use ',' within the single patterns this must be escaped as '\e,'.
.RS
.P
.PD 0
Patterns:
.IP "\fB*\fP"
Matches any sequence of characters.
This includes also '/' in contrast to shell patterns.
.IP "\fB?\fP"
Matches any single character except of '/'.
.IP "\fB{one,two}\fP"
Matches 'one' or 'two'.
Any '{', ',' and '}' within this pattern must be escaped by a '\e'.
\&'*' and '?' have no special meaning within the curly braces.
.IP "\fB\e\fP"
Use backslash to escape the special meaning of '?*{},' in the pattern or
pattern list.
.PD
.RE
.TP
.I cmd
Any `ex` command vimb understands.
The leading ':' is not required.
Multiple commands can be separated by '|'.
.TP
.BI ":au[tocmd] [" group "] {" event "} {" pat "} {" cmd "}"
Add \fIcmd\fP to the list of commands that Vimb will execute automatically on
\fIevent\fP for a URI matching \fIpat\fP autocmd-patterns.
Vimb always adds the \fIcmd\fP after existing autocommands, so that the
autocommands are executed in the order in which they were given.
.TP
.BI ":au[tocmd]! [" group "] {" event "} {" pat "} {" cmd "}"
Remove all autocommands associated with \fIevent\fP and which pattern match
\fIpat\fP, and add the command \fIcmd\fP.
Note that the pattern is not matched literally to find autocommands
to remove, like Vim does.
Vimb matches the autocommand pattern with \fIpat\fP.
If [\fIgroup\fP] is not given, deletes autocommands in current group,
as noted above.
.TP
.BI ":au[tocmd]! [" group "] {" event "} {" pat "}"
Remove all autocommands associated with \fIevent\fP and which pattern matches
\fIpat\fP in given group (current group by default).
.TP
.BI ":au[tocmd]! [" group "] * {" pat "}"
Remove all autocommands with patterns matching \fIpat\fP for all events
in given group (current group by default).
.TP
.BI ":au[tocmd]! [" group "] {" event "}"
Remove all autocommands for \fIevent\fP in given group (current group
by default).
.TP
.BI ":au[tocmd]! [" group "]"
Remove all autocommands in given group (current group by default).
.TP
.BI ":aug[roup] {" name "}"
Define the autocmd group \fIname\fP for the following ":autocmd" commands.
The name "end" selects the default group.
.TP
.BI ":aug[roup]! {" name "}"
Delete the autocmd group \fIname\fP.
.P
Example:
.EX
:aug github
: au LoadCommitted * set scripts=off|set cookie-accept=never
: au LoadCommitted http{s,}://github.com/* set scripts=on
:aug end
.EE
.
.SS Misc
.TP
.BI ":cl[eardata] [" dataTypes "] [" timespan "]"
Asynchronously clears the website data of the given list of \fIdataTypes\fP
modified in the past \fItimespan\fP.
Note that the \fIdataTypes\fP must not contain spaces.
If \fItimespan\fP is not given, all website data will be removed.
Note that this effects all running instances of vimb.
.RS
.P
.PD 0
The \fIdataTypes\fP is a comma separated list of following types.
.TP
.B memory-cache
Memory cache.
.TP
.B disk-cache
HTTP disk cache.
.TP
.B offline-cache
Offline web application cache.
.TP
.B session-storage
Session storage data.
.TP
.B local-storage
Local storage data.
.TP
.B indexeddb-databases
IndexedDB databases.
.TP
.B plugin-data
Plugin data.
.TP
.B cookies
Cookies. Note that the cookies are not cleared in case a \fItimespan\fP is
given.
.TP
.B hsts-cache
HTTP Strict Transport Security cache.
.TP
.B -
Can be used to clear all known data types in case a \fItimespan\fP is used.
.PD
.RE
.RS
.P
.PD 0
The \fItimespan\fP is given as sequence of '[multiplier]\fIunit\fP' tupels
with following units.
.TP
.B y
year (365 days)
.TP
.B w
week (7 days)
.TP
.B d
day
.TP
.B h
hour
.TP
.B m
minute
.TP
.B s
second
.PD
.P
.I Example:
.PD 0
.IP ":cleardata"
to clear all known website data types without any timespan restriction.
.IP ":cleardata - 5m"
to clear all known website data types modified in the last 5 minutes.
.IP ":cleardata local-storage,session-storage,cookies"
to completely clear the cookies, local- and session-storage without time
restrictions.
.IP ":cleardata disk-cache 2d4h"
to clear the disk cache that was modified in the past two days and four hours.
.PD
.RE
.TP
.BI ":sh[ellcmd] " cmd
Runs the given shell \fIcmd\fP syncron and print the output into inputbox.
The following patterns in \fIcmd\fP are expanded: '~username', '~/', '$VAR'
and '${VAR}'.
A '\e' before these patterns disables the expansion.
.P
.RS
.P
.PD 0
The following environment variables are set for called shell commands.
.TP
.B VIMB_URI
This variable is set by Vimb every time a new page is opened to the URI of the
page.
.TP
.B VIMB_SELECTION
This variable is set to the current selected text on the page.
.TP
.B VIMB_TITLE
Contains the title of the current opened page.
.TP
.B VIMB_PID
Contains the pid of the running Vimb instance.
.TP
.B VIMB_WIN_ID
Holds the X-Window id of the Vimb window.
.TP
.B VIMB_XID
Holds the X-Window id of the Vimb window or of the embedding window if Vimb is
compiled with XEMBED and started with the -e option.
.PD
.EE
.RE
.TP
.BI ":sh[ellcmd]! " cmd
Like :sh[ellcmd] but asynchronous.
.sp
Example:
.EX
:sh! /bin/sh -c 'echo "`date` $VIMB_URI" >> myhistory.txt'
.EE
.TP
.BI ":shelle[x] " cmd
Like :sh[ellcmd] but instead of printing the output into the inputbox, it runs
the output as `ex` commands line by line.
.sp
.RS
Example:
.EX
:shellex bash -c "sqlite3 \e"file:\e$XDG_CONFIG_HOME/chromium/Default/History?mode=ro\e" \e"SELECT url, title FROM urls ORDER BY last_visit_time DESC\e" | dmenu -l 20 | awk -F'|' '{print \e"open \e" \e$1}'"
.EE
.RS
to select an entry from the Chromium history and open in the current window
.RE
.RE
.TP
.BI ":s[ave] [" path "]"
Download current opened page into configured download directory.
If \fIpath\fP is given, download under this file name or path.
\fIpath\fP is expanded and can therefore contain '~/', '${ENV}'
and '~user' pattern.
.TP
.BI ":so[urce] [" file "]"
Read ex commands from \fIfile\fP.
.TP
.B :q[uit]
Close the browser.
This will be refused if there are running downloads.
.TP
.B :q[uit]!
Close the browser independent from an running download.
.TP
.B :reg[ister]
Display the contents of all registers.
.RS
.P
.PD 0
Registers:
.TP
.BR \(dqa " \(em " \(dqz
26 named registers "a to "z.
Vimb fills these registers only when you say so.
.TP
.B \(dq:
Last executed `ex` command.
.TP
.B \(dq"
Last yanked content.
.TP
.B \(dq%
Curent opened URI.
.TP
.B \(dq/
Last search phrase.
.TP
.B \(dq;
Contains the last hinted URL.
.PD
.RE
.TP
.BI :e[val] " javascript"
Runs the given \fIjavascript\fP in the current page and display the evaluated
value.
.sp
Example: :eval document.cookie
.TP
.BI :e[val]! " javascript"
Like :eval, but there is nothing print to the input box.
.TP
.BI ":no[rmal] [" cmds ]
Execute normal mode commands \fIcmds\fP.
This makes it possible to execute normal mode commands typed on the input box.
.sp
\fIcmds\fP cannot start with a space.
Put a count of 1 (one) before it, "1 " is one space.
.sp
Example: :set scripts!|no! R
.TP
.BI ":no[rmal]! [" cmds ]
Like :normal, but no mapping is applied to \fIcmds\fP.
.TP
.B :ha[rdcopy]
Print current document.
Open a GUI dialog where you can select the printer,
number of copies, orientation, etc.
.
.
.SH INPUT MODE
.TP
.BR , " CTRL\-["
Switch back to normal mode.
.TP
.B CTRL\-O
Executes the next command as normal mode command and return to input mode.
.TP
.B CTRL\-T
Open configured editor with content of current form field.
.TP
.B CTRL\-V
Pass the next key press directly to WebKit.
.TP
.B CTRL\-Z
Enter the pass-through mode.
.
.
.SH COMPLETIONS
The completions are triggered by pressing `` or `` in the
activated inputbox.
Depending of the current inserted content different completions are started.
The completion takes additional typed chars to filter
the completion list that is shown.
.TP
.B commands
The completion for commands are started when at least `:` is shown in the
inputbox.
If initial chars are passed, the completion will lookup those
commands that begin with the given chars.
.TP
.B settings
The setting name completion is started if at least `:set ` is shown in
inputbox and does also match settings that begins with already typed setting
prefix.
.TP
.B history
The history of URIs is shown for the `:open ` and `:tabopen ` commands.
This completion looks up every given word in the history URI and titles.
Only those history items are shown, where the title or URI contains all tags.
.RS
.IP ":open foo bar"
will complete only URIs that contain the words foo and bar.
.RE
.TP
.B bookmarks
The bookmark completion is similar to the history completion, but does match
only the tags of the bookmarks.
The bookmark completion is started by `:open \fB!\fP`, `:tabopen \fB!\fP` or
`:bmr ` and does a prefix search for all given words in the bookmark tags.
.RS
.IP ":open \fB!\fPfoo ba"
will match all bookmarks that have tags starting with "foo" and "ba".
If the bookmark does not have any tags set, the URL is split on `.' and `/'
into tags.
.IP ":bmr tag"
will match all bookmarks that have tags starting with "tag".
.RE
.TP
.B bookmark tags
The bookmark tag completion allows the insertion of already used bookmarks for the
`:bma ` commands.
.TP
.B search
The search completion allows a filtered list of already done searches.
This completion starts by `/` or `?` in inputbox and performs a prefix
comparison for further typed chars.
.
.
.SH SETTINGS
All settings listed below can be set with the `:set' command.
See \fBSettings\fP under \fBCOMMAND MODE\fP for syntax.
.TP
.BR accelerated-2d-canvas (bool)
Enable or disable accelerated 2D canvas.
When accelerated 2D canvas is enabled, WebKit may render some 2D canvas
content using hardware accelerated drawing operations.
.TP
.BR allow-file-access-from-file-urls (bool)
Indicates whether file access is allowed from file URLs.
By default, when something is loaded using a file URI, cross origin requests
to other file resources are not allowed.
.TP
.BR allow-universal-access-from-file-urls (bool)
Indicates whether or not JavaScript running in the context of a file scheme
URL should be allowed to access content from any origin.
By default, when something is loaded in a using a file scheme URL, access to
the local file system and arbitrary local storage is not allowed.
.TP
.BR caret (bool)
Whether to enable accessibility enhanced keyboard navigation.
.TP
.B cookie-accept (string)
Cookie accept policy {`always', `never', `origin' (accept all non-third-party
cookies)}.
.TP
.B closed-max-items (int)
Maximum number of stored last closed URLs.
If closed-max-items is set to 0, closed URLs will not be stored.
.TP
.B completion-css (string)
CSS style applied to the inputbox completion list items.
.TP
.B completion-hover-css (string)
CSS style applied to the inputbox completion list item that is currently
hovered by the mouse.
.TP
.B completion-selected-css (string)
CSS style applied to the inputbox completion list item that is currently
selected.
.TP
.B cursiv-font (string)
The font family used as the default for content using cursive font.
.TP
.B dark-mode (bool)
Whether to enable dark mode. Websites can use the `prefers-color-scheme' media
query to adjust styles according to this option.
.TP
.B default-charset (string)
The default text charset used when interpreting content with an unspecified
charset.
.TP
.B default-font (string)
The font family to use as the default for content that does not specify a
font.
.TP
.B default-zoom (int)
Default Full-Content zoom level in percent. Default is 100.
.TP
.B dns-prefetching (bool)
Indicates if Vimb prefetches domain names.
.TP
.B download-command (string)
A command with placeholder '%s' that will be invoked to download a URI in
case 'download-use-external' is enabled.
.RS
.TP
The following additional environment variable are available:
.PD 0
.TP
.B $VIMB_URI
The URI of the current opened page, normally the page where the download was
started from, also known as referer.
.TP
.B $VIMB_DOWNLOAD_PATH
Setting value of 'download-path' which would be used normally for downloads.
.PD
.P
.PD 0
.IP ":set download-command=/bin/sh -c ""cd '$VIMB_DOWNLOAD_PATH' \
&& curl -sLJOC - -e '$VIMB_URI' %s"""
.PD
.RE
.TP
.B download-path (string)
Path to the default download directory.
If no download directory is set, download will be written into current
directory.
The following pattern will be expanded if the download is
started '~/', '~user', '$VAR' and '${VAR}'.
.TP
.B download-use-external (bool)
Indicates if the external download tool set as 'download-command' should be
used to handle downloads.
If this is disabled Vimb will handle the download.
.TP
.B editor-command (string)
Command with placeholder '%s' called if form field is opened with $EDITOR to
spawn the editor-like `x-terminal-emulator -e vim %s'.
To use Gvim as the editor, it's necessary to call it with `-f' to run it in
the foreground.
.TP
.B font-size (int)
The default font size used to display text.
.TP
.B frame-flattening (bool)
Whether to enable the Frame Flattening.
With this setting each subframe is expanded to its contents,
which will flatten all the frames to become one scrollable page.
.TP
.B fullscreen (bool)
Show the current window full-screen.
.TP
.B hardware-acceleration-policy (string)
This setting decides how to enable and disable hardware acceleration.
.PD 0
.RS
.TP
.B ondemand
enables the hardware acceleration when the web contents request it, disabling
it again when no longer needed.
.TP
.B always
enforce hardware acceleration to be enabled.
.TP
.B never
disables it completely.
Note that disabling hardware acceleration might cause some websites to not
render correctly or consume more CPU.
.RE
.PD
.TP
.B header (list)
Comma separated list of headers that replaces default header sent by WebKit or
new headers.
The format for the header list elements is `name[=[value]]'.
.sp
Note that these headers will replace already existing headers.
If there is no '=' after the header name, then the complete header
will be removed from the request, if the '=' is present means that
the header value is set to empty value.
.sp
Note that webkit reused already set headers in case of a reload of a page.
So if there are headers removed that where previously use to access a certain
page and the page is reloaded or opened via back/forward history the header
will still be sent.
To apply the new header setting properly it's required to request another page
or to open current page new by `O`.
.sp
To use '=' within a header value the value must be quoted like shown in
Example for the Cookie header.
.RS
.P
.PD 0
.IP ":set header=DNT=1,User-Agent,Cookie='name=value'"
Send the 'Do Not Track' header with each request and remove the User-Agent
Header completely from request.
.PD
.RE
.TP
.B hint-follow-last (bool)
If on, vimb automatically follows the last remaining hint on the page.
If off hints are fired only if enter is pressed.
.TP
.B hint-keys-same-length (bool)
If on, all hint labels will have the same length, so no hints will be
ambiguous.
.TP
.B hint-timeout (int)
Timeout before automatically following a non-unique numerical hint.
To disable auto fire of hints, set this value to 0.
.TP
.B hint-keys (string)
The keys used to label and select hints.
With its default value, each hint has a unique label which can be typed
to select it, while all other characters are used to filter hints based
on their text.
With a value such as asdfg;lkjh,
each hint is `labeled' based on the characters of the home row.
.IP
If the hint-keys string starts with a '0' the keys are considered to follow
the rules of numeric labeling. So that the ifrst char of the label will never
start with the '0'.
.IP
Note that the hint matching by label built of hint-keys is case sensitive.
In this vimb differs from some other browsers that show hint labels in upper
case, but match them lowercase.
.IP
To have upper case hint labels, it's possible to add following css to the
`style.css' file in vimb's configuration directory.
.IP
"span[vimbhint="label"] {text-transform: uppercase !important;}"
.TP
.B hint-match-element (bool)
If this is set to 'true' typed chars that are not part of the set 'hint-keys'
are used to filter hinted DOM elements by their text value.
If 'hint-keys' are set to chars instead of numbers it might be useful to
disable matching of the elements by 'hint-match-element=false'.
.TP
.B histignore (string)
Determines whether an URI should be saved in the history file.
This option must be a valid POSIX Extended Regular Expression.
If URI matches this regular expression then it wont be saved in the history.
If this option is empty then any URI will be saved in the history.
.TP
.B history-max-items (int)
Maximum number of unique items stored in search-, command or URI history.
If history-max-items is set to 0, the history file will not be changed.
This setting has no effect if option \-\-incognito is set.
.TP
.B home-page (string)
Homepage that vimb opens if started without a URI.
.TP
.B html5-database (bool)
Whether to enable HTML5 client-side SQL database support.
Client-side SQL database allows web pages to store structured data
and be able to use SQL to manipulate that data asynchronously.
.TP
.B html5-local-storage (bool)
Whether to enable HTML5 localStorage support.
localStorage provides simple synchronous storage access.
.TP
.B hyperlink-auditing (bool)
Enable or disable support for .
.TP
.B images (bool)
Determines whether images should be automatically loaded or not.
.TP
.B incsearch (bool)
While typing a search command, show where the pattern typed so far matches.
.TP
.B input-autohide (bool)
If enabled the inputbox will be hidden whenever it contains no text.
.TP
.B input-css (string)
CSS style applied to the inputbox in normal state.
.TP
.B input-error-css (string)
CSS style applied to the inputbox in case of displayed error.
.TP
.B intelligent-tracking-prevention (bool)
Whether WebKit's Intelligent Tracking Prevention (ITP) is enabled.
.TP
.B javascript-can-access-clipboard (bool)
Whether JavaScript can access the clipboard.
.TP
.B javascript-can-open-windows-automatically (bool)
Whether JavaScript can open popup windows automatically without user
interaction.
.PD
.RE
.TP
.B javascript-enable-markup (bool)
Whether JavaScript markup is enabled.
Disabling can help with some older systems (ppc, ppc64, etc.) that don't have complete JavaScript support to run webpages without crashing.
.TP
.B geolocation (string)
Controls website access to the geolocation API {`always', `never', `ask' (display a prompt each time)}
.TP
.B media-playback-allows-inline (bool)
Whether media playback is full-screen only or inline playback is allowed.
Setting it to false allows specifying that media playback should be always
fullscreen.
.TP
.B media-playback-requires-user-gesture (bool)
Whether a user gesture (such as clicking the play button) would be required to
start media playback or load media.
Setting it on requires a gesture by the
user to start playback, or to load the media.
.TP
.B media-stream (bool)
Enable or disable support for MediaSource on pages.
MediaSource is an experimental proposal which extends HTMLMediaElement
to allow JavaScript to generate media streams for playback.
.TP
.B mediasource (bool)
Enable or disable support for MediaSource on pages.
MediaSource is an experimental proposal which extends HTMLMediaElement
to allow JavaScript to generate media streams for playback.
.TP
.B minimum-font-size (int)
The minimum font size used to display text.
.TP
.B monospace-font (string)
The font family used as the default for content using monospace font.
.TP
.B monospace-font-size (int)
Default font size for the monospace font.
.TP
.B notification (string)
Controls website access to the notification API, that sends notifications via
dbus. {`always', `never', `ask' (display a prompt each time)}
.TP
.B offline-cache (bool)
Whether to enable HTML5 offline web application cache support.
Offline web application cache allows web applications to run even
when the user is not connected to the network.
.TP
.B print-backgrounds (bool)
Whether background images should be drawn during printing.
.TP
.B plugins (bool)
Determines whether or not plugins on the page are enabled.
.TP
.B prevent-newwindow (bool)
Whether to open links, that would normally open in a new window, in the
current window.
This option does not affect links fired by hinting.
.TP
.B sans-serif-font (string)
The font family used as the default for content using sans-serif font.
.TP
.B scripts (bool)
Determines whether or not JavaScript executes within a page.
.TP
.B scroll-step (int)
Number of pixel vimb scrolls if 'j' or 'k' is used.
.TP
.B scroll-multiplier (int)
Multiplier to increase the scroll distance if window is scrolled by mouse
wheel.
.TP
.B serif-font (string)
The font family used as the default for content using serif font.
.TP
.B show-titlebar (bool)
Determines whether the titlebar is shown (on systems that provide window
decoration).
Defaults to true.
.TP
.B site-specific-quirks (bool)
Enables the site-specific compatibility workarounds.
.TP
.B smooth-scrolling (bool)
Enable or disable support for smooth scrolling.
.TP
.B spatial-navigation (bool)
Whether to enable the Spatial Navigation.
This feature consists in the ability to navigate between focusable
elements in a Web page, such as hyperlinks and form controls, by using
Left, Right, Up and Down arrow keys.
For example, if a user presses the Right key, heuristics determine whether
there is an element they might be trying to reach towards the right, and if
there are multiple elements, which element they probably want.
.TP
.B spell-checking (bool)
Enable or disable the spell checking feature.
.TP
.B spell-checking-languages (string)
Set comma separated list of spell checking languages to be used for spell
checking.
.br
The locale string typically is in the form lang_COUNTRY, where lang is an
ISO-639 language code, and COUNTRY is an ISO-3166 country code. For instance,
sv_FI for Swedish as written in Finland or pt_BR for Portuguese as written in
Brazil.
.TP
.B status-bar (bool)
Indicates if the status bar should be shown.
.PD
.RE
.TP
.B status-bar-show-settings (bool)
Whether to show settings on the status bar.
This shows on the right hand of the status bar some runtime settings that
where specified on compile time.
.TP
.B status-css (string)
CSS style applied to the status bar on none https pages.
.TP
.B status-ssl-css (string)
CSS style applied to the status bar on https pages with trusted certificate.
.TP
.B status-ssl-invalid-css (string)
CSS style applied to the status bar on https pages with untrusted certificate.
.TP
.B strict-ssl (bool)
If 'on', vimb will not load a untrusted https site.
.TP
.B stylesheet (bool)
If 'on' the user defined styles-sheet is used.
.TP
.B tabs-to-links (bool)
Whether the Tab key cycles through elements on the page.
.sp
If true, pressing the Tab key will focus the next element in the web view.
Otherwise, the web view will interpret Tab key presses as normal key presses.
If the selected element is editable, the Tab key will cause the insertion
of a Tab character.
.TP
.B timeoutlen (int)
The time in milliseconds that is waited for a key code or mapped key sequence
to complete.
.TP
.B user-agent (string)
The user-agent string used by WebKit.
.TP
.B user-scripts (bool)
If 'on' the user scripts are injected into every page.
.TP
.B webaudio (bool)
Enable or disable support for WebAudio on pages.
WebAudio is an experimental proposal for allowing web pages
to generate Audio WAVE data from JavaScript.
.TP
.B webgl (bool)
Enable or disable support for WebGL on pages.
.TP
.B webinspector (bool)
Determines whether or not developer tools, such as the Web Inspector, are
enabled.
.TP
.B x-hint-command (string)
Command used if hint mode ;x is fired.
The command can be any vimb command string.
Note that the command is run through the mapping mechanism of vimb so
it might change the behaviour by adding or changing mappings.
.RS
.P
.PD 0
.IP ":set x-hint-command=:sh! curl -e % ;"
This fills the inputbox with the prefilled download command and replaces
`%' with the current URI and `;' with the URI of the hinted element.
.IP ":nnoremap ;f :set x-hint-command=:sh! firefox '\;\;x"
This makes the key sequence ";f" start hinting and then open the hinted URI in
firefox.
.PD
.RE
.TP
.B xss-auditor (bool)
Whether to enable the XSS auditor.
This feature filters some kinds of reflective XSS attacks on vulnerable web
sites.
.
.
.SH FILES
.TP
.IR $XDG_CONFIG_HOME/vimb[/PROFILE]
Directory for configuration data.
If executed with \fB-p \fIPROFILE\fR parameter, configuration is read from
this subdirectory.
.RS
.PD 0
.TP
.I config
Ex commands from this file are executed on startup.
.TP
.I scripts.js
This file can be used to run user scripts, that are injected into every page
that is opened.
.TP
.I style.css
File for userdefined CSS styles.
These file is used if the config variable `stylesheet' is enabled.
.PD
.RE
.
.TP
.IR $XDG_DATA_HOME/vimb[/PROFILE]
Directory for runtime data.
If executed with \fB-p \fIPROFILE\fR parameter, data files are written from
this subdirectory.
.
.RS
.PD 0
.I cookies.db
Sqlite cookie storage.
This file will not be touched if option \-\-incognito is set.
.TP
.I closed
Holds the URIs of last closed browser windows.
This file will not be touched if option \-\-incognito is set.
.TP
.I history
This file holds the history of unique opened URIs.
This file will not be touched if option \-\-incognito is set.
.TP
.I bookmark
This file holds the list of bookmarked URIs with tags.
.TP
.I command
This file holds the history of commands and search queries performed via input
box.
This file will not be touched if option \-\-incognito is set.
.TP
.I queue
Holds the read it later queue filled by `qpush'.
.TP
.I search
This file holds the history of search queries.
This file will not be touched if option \-\-incognito is set.
.PD
.RE
.
.SH ENVIRONMENT
.TP
.B http_proxy, HTTP_PROXY
If either environment variable is non-empty, the specified host and
optional port is used to tunnel requests. For example:
HTTP_PROXY=localhost:8118.
.
.
.SH "REPORTING BUGS"
Report bugs to the main project page on https://github.com/fanglingsu/vimb/issues
.br
or on the mailing list https://lists.sourceforge.net/lists/listinfo/vimb-users.
.
.
.SH AUTHOR
Daniel Carl
fanglingsu-vimb-448e7e2/src/ 0000775 0000000 0000000 00000000000 15145416123 0015737 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/src/.gitignore 0000664 0000000 0000000 00000000016 15145416123 0017724 0 ustar 00root root 0000000 0000000 config.h
vimb
fanglingsu-vimb-448e7e2/src/Makefile 0000664 0000000 0000000 00000001721 15145416123 0017400 0 ustar 00root root 0000000 0000000 include ../config.mk
OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
JSFILES = $(wildcard scripts/*.js)
CSSFILES = $(wildcard scripts/*.css)
all: vimb webextension.subdir-all
clean: webextension.subdir-clean
$(RM) vimb vimb.so $(OBJ)
$(RM) scripts/scripts.h
vimb: $(OBJ)
@echo "${CC} $@"
$(Q)$(CC) $(OBJ) $(LDFLAGS) -o $@
vimb.so: $(OBJ)
$(Q)$(CC) -shared $(OBJ) $(LDFLAGS) -o $@
$(OBJ): config.h ../config.mk
main.o: ../version.h
input.o: scripts/scripts.h
normal.o: scripts/scripts.h
setting.o: scripts/scripts.h
scripts/scripts.h: $(JSFILES) $(CSSFILES)
$(Q)$(RM) $@
@echo "create $@ from *.{css,js}"
$(Q)for file in $(JSFILES) $(CSSFILES); do \
./scripts/js2h.sh $$file >> $@; \
done
config.h:
@echo create $@ from config.def.h
$(Q)cp config.def.h $@
%.o: %.c
@echo "${CC} $@"
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.subdir-all: config.h
$(Q)$(MAKE) -C $*
%.subdir-clean:
$(Q)$(MAKE) -C $* clean
-include $(OBJ:.o=.d)
.PHONY: all clean
fanglingsu-vimb-448e7e2/src/ascii.h 0000664 0000000 0000000 00000011105 15145416123 0017176 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _ASCII_H
#define _ASCII_H
#define VB_UPPER 0x01
#define VB_LOWER 0x02
#define VB_DIGIT 0x04
#define VB_SPACE 0x08
#define VB_PUNKT 0x10
#define VB_CTRL 0x20
#define U VB_UPPER
#define L VB_LOWER
#define D VB_DIGIT
#define P VB_PUNKT
#define S VB_SPACE
#define C VB_CTRL
#define SC VB_SPACE|VB_CTRL
static const unsigned char chartable[256] = {
/* 0x00-0x0f */ C, C, C, C, C, C, C, C, C, SC, SC, C, SC, SC, C, C,
/* 0x10-0x1f */ C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
/* 0x20-0x2f */ S, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0x30-0x3f */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, P,
/* 0x40-0x4f */ P, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U,
/* 0x50-0x5f */ U, U, U, U, U, U, U, U, U, U, U, P, P, P, P, P,
/* 0x60-0x6f */ P, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L,
/* 0x70-0x7f */ L, L, L, L, L, L, L, L, L, L, L, P, P, P, P, C,
/* 0x80-0x8f */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0x90-0x9f */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xa0-0xaf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xb0-0xbf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xc0-0xcf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xd0-0xdf */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xe0-0xef */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P,
/* 0xf0-0xff */ P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P
};
#undef U
#undef L
#undef D
#undef P
#undef S
#undef C
#undef SC
#define VB_IS_UPPER(c) ((chartable[(unsigned char)c] & VB_UPPER) != 0)
#define VB_IS_LOWER(c) ((chartable[(unsigned char)c] & VB_LOWER) != 0)
#define VB_IS_DIGIT(c) ((chartable[(unsigned char)c] & VB_DIGIT) != 0)
#define VB_IS_PUNKT(c) ((chartable[(unsigned char)c] & VB_PUNKT) != 0)
#define VB_IS_SPACE(c) ((chartable[(unsigned char)c] & VB_SPACE) != 0)
#define VB_IS_CTRL(c) ((chartable[(unsigned char)c] & VB_CTRL) != 0)
#define VB_IS_SEPARATOR(c) (VB_IS_SPACE(c) || c == '"' || c == '\'')
#define VB_IS_ALPHA(c) (VB_IS_LOWER(c) || VB_IS_UPPER(c))
#define VB_IS_ALNUM(c) (VB_IS_ALPHA(c) || VB_IS_DIGIT(c))
#define VB_IS_IDENT(c) (VB_IS_ALNUM(c) || c == '_')
/* CSI (control sequence introducer) is the first byte of a control sequence
* and is always followed by two bytes. */
#define CSI 0x80
#define CSI_STR "\x80"
/* get internal representation for control character ^C */
#define CTRL(c) ((c) ^ 0x40)
#define UNCTRL(c) (((c) ^ 0x40) + 'a' - 'A')
#define IS_SPECIAL(c) (c < 0)
#define TERMCAP2KEY(a, b) (-((a) + ((int)(b) << 8)))
#define KEY2TERMCAP0(x) ((-(x)) & 0xff)
#define KEY2TERMCAP1(x) (((unsigned)(-(x)) >> 8) & 0xff)
#define KEY_TAB '\x09'
#define KEY_NL '\x15'
#define KEY_CR '\x0d'
#define KEY_ESC '\x1b'
#define KEY_BS '\x08'
#define KEY_SHIFT_TAB TERMCAP2KEY('k', 'B')
#define KEY_UP TERMCAP2KEY('k', 'u')
#define KEY_DOWN TERMCAP2KEY('k', 'd')
#define KEY_LEFT TERMCAP2KEY('k', 'l')
#define KEY_RIGHT TERMCAP2KEY('k', 'r')
#define KEY_F1 TERMCAP2KEY('k', '1')
#define KEY_F2 TERMCAP2KEY('k', '2')
#define KEY_F3 TERMCAP2KEY('k', '3')
#define KEY_F4 TERMCAP2KEY('k', '4')
#define KEY_F5 TERMCAP2KEY('k', '5')
#define KEY_F6 TERMCAP2KEY('k', '6')
#define KEY_F7 TERMCAP2KEY('k', '7')
#define KEY_F8 TERMCAP2KEY('k', '8')
#define KEY_F9 TERMCAP2KEY('k', '9')
#define KEY_F10 TERMCAP2KEY('k', ';')
#define KEY_F11 TERMCAP2KEY('F', '1')
#define KEY_F12 TERMCAP2KEY('F', '2')
#endif /* end of include guard: _ASCII_H */
fanglingsu-vimb-448e7e2/src/autocmd.c 0000664 0000000 0000000 00000032461 15145416123 0017545 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include "config.h"
#ifdef FEATURE_AUTOCMD
#include "autocmd.h"
#include "ascii.h"
#include "ex.h"
#include "util.h"
#include "completion.h"
typedef struct {
guint bits; /* the bits identify the events the command applies to */
char *excmd; /* ex command string to be run on matches event */
char *pattern; /* list of patterns the uri is matched agains */
} AutoCmd;
struct AuGroup {
char *name;
GSList *cmds;
};
typedef struct AuGroup AuGroup;
static struct {
const char *name;
guint bits;
} events[] = {
{"*", 0x01ff},
{"LoadStarting", 0x0001},
{"LoadStarted", 0x0002},
{"LoadCommitted", 0x0004},
/*{"LoadFirstLayout", 0x0008},*/
{"LoadFinished", 0x0010},
/*{"LoadFailed", 0x0020},*/
{"DownloadStarted", 0x0040},
{"DownloadFinished", 0x0080},
{"DownloadFailed", 0x0100},
};
static GSList *get_group(Client *c, const char *name);
static guint get_event_bits(Client *c, const char *name);
static void rebuild_used_bits(Client *c);
static char *get_next_word(char **line);
static AuGroup *new_group(const char *name);
static void free_group(AuGroup *group);
static AutoCmd *new_autocmd(const char *excmd, const char *pattern);
static void free_autocmd(AutoCmd *cmd);
void autocmd_init(Client *c)
{
c->autocmd.curgroup = new_group("end");
c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup);
c->autocmd.usedbits = 0;
}
void autocmd_cleanup(Client *c)
{
if (c->autocmd.groups) {
g_slist_free_full(c->autocmd.groups, (GDestroyNotify)free_group);
}
}
/**
* Handle the :augroup {group} ex command.
*/
gboolean autocmd_augroup(Client *c, char *name, gboolean delete)
{
GSList *item;
if (!*name) {
return false;
}
/* check for group "end" that marks the default group */
if (!strcmp(name, "end")) {
c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data;
return true;
}
item = get_group(c, name);
/* check if the group is going to be removed */
if (delete) {
/* group does not exist - so do nothing */
if (!item) {
return true;
}
if (c->autocmd.curgroup == (AuGroup*)item->data) {
/* if the group to delete is the current - switch the the default
* group after removing it */
c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data;
}
/* now remove the group */
free_group((AuGroup*)item->data);
c->autocmd.groups = g_slist_delete_link(c->autocmd.groups, item);
/* there where autocmds remove - so recreate the usedbits */
rebuild_used_bits(c);
return true;
}
/* check if the group does already exists */
if (item) {
/* if the group is found in the known groups use it as current */
c->autocmd.curgroup = (AuGroup*)item->data;
return true;
}
/* create a new group and use it as current */
c->autocmd.curgroup = new_group(name);
/* append it to known groups */
c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup);
return true;
}
/**
* Add or delete auto commands.
*
* :au[tocmd]! [group] {event} {pat} [nested] {cmd}
* group and nested flag are not supported at the moment.
*/
gboolean autocmd_add(Client *c, char *name, gboolean delete)
{
guint bits;
char *parse, *word, *pattern, *excmd;
GSList *item;
AuGroup *grp = NULL;
parse = name;
/* parse group name if it's there */
word = get_next_word(&parse);
if (word) {
/* check if the word is a known group name */
item = get_group(c, word);
if (item) {
grp = (AuGroup*)item->data;
/* group is found - get the next word */
word = get_next_word(&parse);
}
}
if (!grp) {
/* no group found - use the current one */
grp = c->autocmd.curgroup;
}
/* parse event name - if none was matched run it for all events */
if (word) {
bits = get_event_bits(c, word);
if (!bits) {
return false;
}
word = get_next_word(&parse);
} else {
bits = events[AU_ALL].bits;
}
/* last word is the pattern - if not found use '*' */
pattern = word ? word : "*";
/* the rest of the line becomes the ex command to run */
if (parse && !*parse) {
parse = NULL;
}
excmd = parse;
/* delete the autocmd if bang was given */
if (delete) {
GSList *lc, *next;
AutoCmd *cmd;
gboolean removed = false;
/* check if the group does already exists */
for (lc = grp->cmds; lc; lc = next) {
/* Save the next element in case this element is removed */
next = lc->next;
cmd = (AutoCmd*)lc->data;
/* if not bits match - skip the command */
if (!(cmd->bits & bits)) {
continue;
}
/* skip if pattern does not match - we check the pattern against
* another pattern */
if (!util_wildmatch(pattern, cmd->pattern)) {
continue;
}
/* if the command has no matching events - remove it */
grp->cmds = g_slist_delete_link(grp->cmds, lc);
free_autocmd(cmd);
removed = true;
}
/* if ther was at least one command removed - rebuilt the used bits */
if (removed) {
rebuild_used_bits(c);
}
}
/* add the new autocmd */
if (excmd && grp) {
AutoCmd *cmd = new_autocmd(excmd, pattern);
cmd->bits = bits;
/* add the new autocmd to the group */
grp->cmds = g_slist_append(grp->cmds, cmd);
/* merge the autocmd bits into the used bits */
c->autocmd.usedbits |= cmd->bits;
}
return true;
}
/**
* Run named auto command.
*/
gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group)
{
GSList *lg, *lc, *lcc;
AuGroup *grp;
AutoCmd *cmd, *new;
guint bits = events[event].bits;
/* if there is no autocmd for this event - skip here */
if (!(c->autocmd.usedbits & bits)) {
return true;
}
/* loop over the groups and find matching commands */
for (lg = c->autocmd.groups; lg; lg = lg->next) {
grp = lg->data;
/* if a group was given - skip all none matching groupes */
if (group && strcmp(group, grp->name)) {
continue;
}
/* make a deep copy of grp->cmds since it can be modified by ex_run_string() below */
lcc = NULL;
for (lc = grp->cmds; lc; lc = lc->next) {
cmd = lc->data;
new = new_autocmd(cmd->excmd, cmd->pattern);
new->bits = cmd->bits;
lcc = g_slist_prepend(lcc, new); // use prepend+reverse instead of append for efficiency
}
lcc = g_slist_reverse(lcc);
/* test each command in group */
for (lc = lcc; lc; lc = lc->next, free_autocmd(cmd)) {
cmd = lc->data;
/* skip if this dos not match the event bits */
if (!(bits & cmd->bits)) {
continue;
}
/* check pattern only if uri was given */
/* skip if pattern does not match */
if (uri && !util_wildmatch(cmd->pattern, uri)) {
continue;
}
/* run the command */
/* TODO shoult the result be tested for RESULT_COMPLETE? */
/* run command and make sure it's not writte to command history */
ex_run_string(c, cmd->excmd, false);
}
g_slist_free(lcc);
}
return true;
}
gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input)
{
GSList *lg;
gboolean found = false;
GtkTreeIter iter;
if (!input || !*input) {
for (lg = c->autocmd.groups; lg; lg = lg->next) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, ((AuGroup*)lg->data)->name, -1);
found = true;
}
} else {
for (lg = c->autocmd.groups; lg; lg = lg->next) {
char *value = ((AuGroup*)lg->data)->name;
if (g_str_has_prefix(value, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1);
found = true;
}
}
}
return found;
}
gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input)
{
int i;
const char *value;
gboolean found = false;
GtkTreeIter iter;
if (!input || !*input) {
for (i = 0; i < LENGTH(events); i++) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, events[i].name, -1);
found = true;
}
} else {
for (i = 0; i < LENGTH(events); i++) {
value = events[i].name;
if (g_str_has_prefix(value, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1);
found = true;
}
}
}
return found;
}
/**
* Get the augroup by it's name.
*/
static GSList *get_group(Client *c, const char *name)
{
GSList *lg;
AuGroup *grp;
for (lg = c->autocmd.groups; lg; lg = lg->next) {
grp = lg->data;
if (!strcmp(grp->name, name)) {
return lg;
}
}
return NULL;
}
static guint get_event_bits(Client *c, const char *name)
{
int result = 0;
while (*name) {
int i, len;
for (i = 0; i < LENGTH(events); i++) {
/* we count the chars that given name has in common with the
* current processed event */
len = 0;
while (name[len] && events[i].name[len] && name[len] == events[i].name[len]) {
len++;
}
/* check if the matching chars built a full word match */
if (events[i].name[len] == '\0'
&& (name[len] == '\0' || name[len] == ',')
) {
/* add the bits to the result */
result |= events[i].bits;
/* move pointer after the match and skip possible ',' */
name += len;
if (*name == ',') {
name++;
}
break;
}
}
/* check if the end was reached without a match */
if (i >= LENGTH(events)) {
/* is this the right place to write the error */
vb_echo(c, MSG_ERROR, TRUE, "Bad autocmd event name: %s", name);
return 0;
}
}
return result;
}
/**
* Rebuild the usedbits from scratch.
* Save all used autocmd event bits in the bitmap.
*/
static void rebuild_used_bits(Client *c)
{
GSList *lc, *lg;
AuGroup *grp;
/* clear the current set bits */
c->autocmd.usedbits = 0;
/* loop over the groups */
for (lg = c->autocmd.groups; lg; lg = lg->next) {
grp = (AuGroup*)lg->data;
/* merge the used event bints into the bitmap */
for (lc = grp->cmds; lc; lc = lc->next) {
c->autocmd.usedbits |= ((AutoCmd*)lc->data)->bits;
}
}
}
/**
* Get the next word from given line.
* Given line pointer is set past the word and and a 0-byte is added there.
*/
static char *get_next_word(char **line)
{
char *word;
if (!*line || !**line) {
return NULL;
}
/* remember where the word starts */
word = *line;
/* move pointer to the end of the word or of the line */
while (**line && !VB_IS_SPACE(**line)) {
(*line)++;
}
/* end the word */
if (**line) {
*(*line)++ = '\0';
}
/* skip trailing whitespace */
while (VB_IS_SPACE(**line)) {
(*line)++;
}
return word;
}
static AuGroup *new_group(const char *name)
{
AuGroup *new = g_slice_new(AuGroup);
new->name = g_strdup(name);
new->cmds = NULL;
return new;
}
static void free_group(AuGroup *group)
{
g_free(group->name);
if (group->cmds) {
g_slist_free_full(group->cmds, (GDestroyNotify)free_autocmd);
}
g_slice_free(AuGroup, group);
}
static AutoCmd *new_autocmd(const char *excmd, const char *pattern)
{
AutoCmd *new = g_slice_new(AutoCmd);
new->excmd = g_strdup(excmd);
new->pattern = g_strdup(pattern);
return new;
}
static void free_autocmd(AutoCmd *cmd)
{
g_free(cmd->excmd);
g_free(cmd->pattern);
g_slice_free(AutoCmd, cmd);
}
#endif
fanglingsu-vimb-448e7e2/src/autocmd.h 0000664 0000000 0000000 00000003217 15145416123 0017547 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "config.h"
#ifdef FEATURE_AUTOCMD
#ifndef _AUTOCMD_H
#define _AUTOCMD_H
#include "main.h"
/* this values correspond to indices in events[] array in autocmd.c */
typedef enum {
AU_ALL,
AU_LOAD_STARTING,
AU_LOAD_STARTED,
AU_LOAD_COMMITTED,
/*AU_LOAD_FIRST_LAYOUT,*/
AU_LOAD_FINISHED,
/*AU_LOAD_FAILED,*/
AU_DOWNLOAD_STARTED,
AU_DOWNLOAD_FINISHED,
AU_DOWNLOAD_FAILED,
} AuEvent;
void autocmd_init(Client *c);
void autocmd_cleanup(Client *c);
gboolean autocmd_augroup(Client *c, char *name, gboolean delete);
gboolean autocmd_add(Client *c, char *name, gboolean delete);
gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group);
gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input);
gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input);
#endif /* end of include guard: _AUTOCMD_H */
#endif
fanglingsu-vimb-448e7e2/src/bookmark.c 0000664 0000000 0000000 00000021424 15145416123 0017713 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include "config.h"
#include "main.h"
#include "bookmark.h"
#include "util.h"
#include "completion.h"
typedef struct {
char *uri;
char *title;
char *tags;
} Bookmark;
extern struct Vimb vb;
static GList *load(const char *file);
static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query,
unsigned int qlen);
static Bookmark *line_to_bookmark(const char *uri, const char *data);
static void free_bookmark(Bookmark *bm);
/**
* Write a new bookmark entry to the end of bookmark file.
*/
gboolean bookmark_add(const char *uri, const char *title, const char *tags)
{
const char *file = vb.files[FILES_BOOKMARK];
if (tags) {
return util_file_append(file, "%s\t%s\t%s\n", uri, title ? title : "", tags);
}
if (title) {
return util_file_append(file, "%s\t%s\n", uri, title);
}
return util_file_append(file, "%s\n", uri);
}
gboolean bookmark_remove(const char *uri)
{
char **lines, *line, *p;
int len, i;
GString *new;
gboolean removed = FALSE;
if (!uri) {
return FALSE;
}
lines = util_get_lines(vb.files[FILES_BOOKMARK]);
if (lines) {
new = g_string_new(NULL);
len = g_strv_length(lines) - 1;
for (i = 0; i < len; i++) {
line = lines[i];
g_strstrip(line);
/* ignore the title or bookmark tags and test only the uri */
if ((p = strchr(line, '\t'))) {
*p = '\0';
if (!strcmp(uri, line)) {
removed = TRUE;
continue;
} else {
/* reappend the tags */
*p = '\t';
}
}
if (!strcmp(uri, line)) {
removed = TRUE;
continue;
}
g_string_append_printf(new, "%s\n", line);
}
g_strfreev(lines);
util_file_set_content(vb.files[FILES_BOOKMARK], new->str);
g_string_free(new, TRUE);
}
return removed;
}
gboolean bookmark_fill_completion(GtkListStore *store, const char *input)
{
gboolean found = FALSE;
char **parts;
unsigned int len;
GtkTreeIter iter;
GList *src = NULL;
Bookmark *bm;
src = load(vb.files[FILES_BOOKMARK]);
src = g_list_reverse(src);
if (!input || !*input) {
/* without any tags return all bookmarked items */
for (GList *l = src; l; l = l->next) {
bm = (Bookmark*)l->data;
gtk_list_store_append(store, &iter);
gtk_list_store_set(
store, &iter,
COMPLETION_STORE_FIRST, bm->uri,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND, bm->title,
#endif
-1
);
found = TRUE;
}
} else {
parts = g_strsplit(input, " ", 0);
len = g_strv_length(parts);
for (GList *l = src; l; l = l->next) {
bm = (Bookmark*)l->data;
if (bookmark_contains_all_tags(bm, parts, len)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(
store, &iter,
COMPLETION_STORE_FIRST, bm->uri,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND, bm->title,
#endif
-1
);
found = TRUE;
}
}
g_strfreev(parts);
}
g_list_free_full(src, (GDestroyNotify)free_bookmark);
return found;
}
gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input)
{
gboolean found;
unsigned int len, i;
char **tags, *tag;
GList *src = NULL, *taglist = NULL;
Bookmark *bm;
/* get all distinct tags from bookmark file */
src = load(vb.files[FILES_BOOKMARK]);
for (GList *l = src; l; l = l->next) {
bm = (Bookmark*)l->data;
/* if bookmark contains no tags we can go to the next bookmark */
if (!bm->tags) {
continue;
}
tags = g_strsplit(bm->tags, " ", -1);
len = g_strv_length(tags);
for (i = 0; i < len; i++) {
tag = tags[i];
/* add tag only if it isn't already in the list */
if (!g_list_find_custom(taglist, tag, (GCompareFunc)strcmp)) {
taglist = g_list_prepend(taglist, g_strdup(tag));
}
}
g_strfreev(tags);
}
/* generate the completion with the found tags */
found = util_fill_completion(store, input, taglist);
g_list_free_full(src, (GDestroyNotify)free_bookmark);
g_list_free_full(taglist, (GDestroyNotify)g_free);
return found;
}
#ifdef FEATURE_QUEUE
/**
* Push a uri to the end of the queue.
*
* @uri: URI to put into the queue
*/
gboolean bookmark_queue_push(const char *uri)
{
return util_file_append(vb.files[FILES_QUEUE], "%s\n", uri);
}
/**
* Push a uri to the beginning of the queue.
*
* @uri: URI to put into the queue
*/
gboolean bookmark_queue_unshift(const char *uri)
{
return util_file_prepend(vb.files[FILES_QUEUE], "%s\n", uri);
}
/**
* Retrieves the oldest entry from queue.
*
* @item_count: will be filled with the number of remaining items in queue.
* Returned uri must be freed with g_free.
*/
char *bookmark_queue_pop(int *item_count)
{
return util_file_pop_line(vb.files[FILES_QUEUE], item_count);
}
/**
* Removes all contents from the queue file.
*/
gboolean bookmark_queue_clear(void)
{
FILE *f;
if ((f = fopen(vb.files[FILES_QUEUE], "w"))) {
fclose(f);
return TRUE;
}
return FALSE;
}
#endif /* FEATURE_QUEUE */
static GList *load(const char *file)
{
char **lines;
GList *list;
lines = util_get_lines(file);
list = util_strv_to_unique_list(lines, (Util_Content_Func)line_to_bookmark, 0);
g_strfreev(lines);
return list;
}
/**
* Checks if the given bookmark matches all given query strings as prefix. If
* the bookmark has no tags, the matching is done on the '/' splited URL.
*
* @bm: bookmark to test if it matches
* @query: char array with tags to search for
* @qlen: length of given query char array
*
* Return: TRUE if the bookmark contained all tags
*/
static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query,
unsigned int qlen)
{
const char *separators;
char *cursor;
unsigned int i;
gboolean found;
if (!qlen) {
return TRUE;
}
if (bm->tags) {
/* If there are tags - use them for matching. */
separators = " ";
cursor = bm->tags;
} else {
/* No tags available - matching is based on the path parts of the URL. */
separators = "./";
cursor = bm->uri;
}
/* iterate over all query parts */
for (i = 0; i < qlen; i++) {
found = FALSE;
/* we want to do a prefix match on all bookmark tags - so we check for
* a match on string begin - if this fails we move the cursor to the
* next space and do the test again */
while (cursor && *cursor) {
/* match was not found at current cursor position */
if (g_str_has_prefix(cursor, query[i])) {
found = TRUE;
break;
}
/* If match was not found at the cursor position - move cursor
* behind the next separator char. */
if ((cursor = strpbrk(cursor, separators))) {
cursor++;
}
}
if (!found) {
return FALSE;
}
}
return TRUE;
}
static Bookmark *line_to_bookmark(const char *uri, const char *data)
{
char *p;
Bookmark *bm;
/* data part may consist of title or titletags */
bm = g_slice_new(Bookmark);
bm->uri = g_strdup(uri);
if (data && (p = strchr(data, '\t'))) {
*p = '\0';
bm->title = g_strdup(data);
bm->tags = g_strdup(p + 1);
} else {
bm->title = g_strdup(data);
bm->tags = NULL;
}
return bm;
}
static void free_bookmark(Bookmark *bm)
{
g_free(bm->uri);
g_free(bm->title);
g_free(bm->tags);
g_slice_free(Bookmark, bm);
}
fanglingsu-vimb-448e7e2/src/bookmark.h 0000664 0000000 0000000 00000002457 15145416123 0017725 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _BOOKMARK_H
#define _BOOKMARK_H
#include "main.h"
gboolean bookmark_add(const char *uri, const char *title, const char *tags);
gboolean bookmark_remove(const char *uri);
gboolean bookmark_fill_completion(GtkListStore *store, const char *input);
gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input);
#ifdef FEATURE_QUEUE
gboolean bookmark_queue_push(const char *uri);
gboolean bookmark_queue_unshift(const char *uri);
char *bookmark_queue_pop(int *item_count);
gboolean bookmark_queue_clear(void);
#endif
#endif /* end of include guard: _BOOKMARK_H */
fanglingsu-vimb-448e7e2/src/command.c 0000664 0000000 0000000 00000025204 15145416123 0017524 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
/**
* This file contains functions that are used by normal mode and command mode
* together.
*/
#include
#include
#include "config.h"
#ifdef FEATURE_QUEUE
#include "bookmark.h"
#endif
#include "command.h"
#include "history.h"
#include "util.h"
#include "main.h"
typedef struct {
Client *c;
char *file;
gpointer *data;
PostEditFunc func;
} EditorData;
static void resume_editor(GPid pid, int status, gpointer edata);
/**
* Start/perform/stop searching in webview.
*
* @commit: If TRUE, the search query is registered into register "/
* In case searching is stopped the commit of value TRUE
* is used to clear the inputbox if search is active. This is needed
* in case a link is fired by on highlighted link.
*/
gboolean command_search(Client *c, const Arg *arg, bool commit)
{
const char *query;
guint count;
int direction;
g_assert(c);
g_assert(arg);
if (arg->i == 0) {
webkit_find_controller_search_finish(c->finder);
/* Clear the input only if the search is active and commit flag is
* set. This allows us to stop searching with and without cleaning
* inputbox */
if (commit && c->state.search.active) {
vb_echo(c, MSG_NORMAL, FALSE, "");
}
c->state.search.active = FALSE;
vb_statusbar_update(c);
return TRUE;
}
query = arg->s;
count = abs(arg->i);
direction = arg->i > 0 ? 1 : -1;
/* restart the last search if a search result is asked for and no
* search was active */
if (!query && !c->state.search.active) {
query = c->state.search.last_query;
direction = c->state.search.direction;
}
/* Only committed search strings adjust registers and are recorded in
* history, intermediate strings (while using incsearch) don't. */
if (commit) {
if (query) {
history_add(c, HISTORY_SEARCH, query, NULL);
vb_register_add(c, '/', query);
} else {
/* Committed search without string re-searches last string. */
query = vb_register_get(c, '/');
}
}
/* Hand the query string to webkit's find controller. */
if (query) {
/* Force a fresh start in order to have webkit select the first match
* on the page. Without this workaround the first selected match
* depends on the most recent selection or caret position (even when
* caret browsing is disabled). */
if (commit) {
c->state.search.awaited_matches_updates++;
webkit_find_controller_search(c->finder, "", WEBKIT_FIND_OPTIONS_NONE, G_MAXUINT);
}
if (!c->state.search.last_query) {
c->state.search.last_query = g_strdup(query);
} else if (strcmp(c->state.search.last_query, query)) {
g_free(c->state.search.last_query);
c->state.search.last_query = g_strdup(query);
}
c->state.search.awaited_matches_updates++;
webkit_find_controller_search(c->finder, query,
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
WEBKIT_FIND_OPTIONS_WRAP_AROUND |
(direction > 0 ? WEBKIT_FIND_OPTIONS_NONE : WEBKIT_FIND_OPTIONS_BACKWARDS),
(!commit && arg->s ? INCSEARCH_MATCHES_LIMIT : G_MAXUINT));
c->state.search.active = TRUE;
c->state.search.direction = direction;
/* Skip first search because the first match is already
* highlighted on search start. */
count -= 1;
}
/* Step through searchs result focus according to arg->i. */
if (c->state.search.active) {
if (arg->i * c->state.search.direction > 0) {
while (count--) {
webkit_find_controller_search_next(c->finder);
}
} else {
while (count--) {
webkit_find_controller_search_previous(c->finder);
}
}
}
return TRUE;
}
gboolean command_yank(Client *c, const Arg *arg, char buf)
{
/**
* This implementation is quite 'brute force', same as in vimb2
* - both X clipboards are always set, PRIMARY and CLIPBOARD
* - the X clipboards are always set, even though a vimb register was given
*/
const char *uri = NULL;
char *yanked = NULL;
g_assert(c);
g_assert(arg);
g_assert(c->webview);
g_assert(
arg->i == COMMAND_YANK_URI ||
arg->i == COMMAND_YANK_SELECTION ||
arg->i == COMMAND_YANK_ARG);
if (arg->i == COMMAND_YANK_URI) {
if ((uri = webkit_web_view_get_uri(c->webview))) {
yanked = g_strdup(uri);
}
} else if (arg->i == COMMAND_YANK_SELECTION) {
/* copy web view selection to clipboard */
webkit_web_view_execute_editing_command(c->webview, WEBKIT_EDITING_COMMAND_COPY);
/* read back copy from clipboard */
yanked = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
} else {
/* use current arg.s as new clipboard content */
yanked = g_strdup(arg->s);
}
if(!yanked) {
return FALSE;
}
/* store in vimb default register */
vb_register_add(c, '"', yanked);
/* store in vimb register buf if buf != 0 */
vb_register_add(c, buf, yanked);
/* store in X clipboard primary (selected text copy, middle mouse paste) */
gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), yanked, -1);
/* store in X "windows style" clipboard */
gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), yanked, -1);
vb_echo(c, MSG_NORMAL, FALSE, "Yanked: %s", yanked);
g_free(yanked);
return TRUE;
}
gboolean command_save(Client *c, const Arg *arg)
{
const char *uri, *path = NULL;
WebKitDownload *download;
if (arg->i == COMMAND_SAVE_CURRENT) {
uri = c->state.uri;
/* given string is the path to save the download to */
if (arg->s && *(arg->s) != '\0') {
path = arg->s;
}
} else {
uri = arg->s;
}
if (!uri || !*uri) {
return FALSE;
}
/* Start the download to given path. */
download = webkit_web_view_download_uri(c->webview, uri);
return vb_download_set_destination(c, download, NULL, path);
}
#ifdef FEATURE_QUEUE
gboolean command_queue(Client *c, const Arg *arg)
{
gboolean res = FALSE;
int count = 0;
char *uri;
switch (arg->i) {
case COMMAND_QUEUE_POP:
if ((uri = bookmark_queue_pop(&count))) {
res = vb_load_uri(c, &(Arg){TARGET_CURRENT, uri});
g_free(uri);
}
vb_echo(c, MSG_NORMAL, FALSE, "Queue length %d", count);
break;
case COMMAND_QUEUE_PUSH:
res = bookmark_queue_push(arg->s ? arg->s : c->state.uri);
if (res) {
vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue");
}
break;
case COMMAND_QUEUE_UNSHIFT:
res = bookmark_queue_unshift(arg->s ? arg->s : c->state.uri);
if (res) {
vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue");
}
break;
case COMMAND_QUEUE_CLEAR:
if (bookmark_queue_clear()) {
vb_echo(c, MSG_NORMAL, FALSE, "Queue cleared");
}
break;
}
return res;
}
#endif
/**
* Asynchronously spawn editor.
*
* @posteditfunc: If not NULL posteditfunc is called and the following arguments
* are passed:
* - const char *text: text contents of the temporary file (or
* NULL)
* - Client *c: current client passed to command_spawn_editor()
* - gpointer data: pointer that can be used for any local
* purposes
* @data: Generic pointer used to pass data to posteditfunc
*/
gboolean command_spawn_editor(Client *c, const Arg *arg,
PostEditFunc posteditfunc, gpointer data)
{
char **argv = NULL, *file_path = NULL;
char *command = NULL;
int argc;
GPid pid;
gboolean success, result;
char *editor_command;
GError *error = NULL;
/* get the editor command */
editor_command = GET_CHAR(c, "editor-command");
if (!editor_command || !*editor_command) {
vb_echo(c, MSG_ERROR, TRUE, "No editor-command configured");
return FALSE;
}
/* create a temp file to pass text in/to editor */
if (!util_create_tmp_file(arg->s, &file_path)) {
return FALSE;
}
/* spawn editor */
command = g_strdup_printf(editor_command, file_path);
if (g_shell_parse_argv(command, &argc, &argv, NULL)) {
success = g_spawn_async(
NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
NULL, NULL, &pid, &error
);
g_strfreev(argv);
if (success) {
EditorData *ed = g_slice_new0(EditorData);
ed->file = file_path;
ed->c = c;
ed->data = data;
ed->func = posteditfunc;
g_child_watch_add(pid, resume_editor, ed);
result = TRUE;
} else {
g_warning("Could not spawn editor-command: %s", error->message);
g_error_free(error);
result = FALSE;
}
} else {
g_critical("Could not parse editor-command '%s'", command);
result = FALSE;
}
g_free(command);
if (!result) {
unlink(file_path);
g_free(file_path);
}
return result;
}
static void resume_editor(GPid pid, int status, gpointer edata)
{
char *text = NULL;
EditorData *ed = edata;
g_assert(pid);
g_assert(ed);
g_assert(ed->c);
g_assert(ed->file);
if (ed->func != NULL) {
text = util_get_file_contents(ed->file, NULL);
ed->func(text, ed->c, ed->data);
g_free(text);
}
unlink(ed->file);
g_free(ed->file);
g_slice_free(EditorData, ed);
g_spawn_close_pid(pid);
}
fanglingsu-vimb-448e7e2/src/command.h 0000664 0000000 0000000 00000003031 15145416123 0017523 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _COMMAND_H
#define _COMMAND_H
#include
#include "main.h"
enum {
COMMAND_YANK_ARG,
COMMAND_YANK_URI,
COMMAND_YANK_SELECTION
};
enum {
COMMAND_SAVE_CURRENT,
COMMAND_SAVE_URI
};
#ifdef FEATURE_QUEUE
enum {
COMMAND_QUEUE_PUSH,
COMMAND_QUEUE_UNSHIFT,
COMMAND_QUEUE_POP,
COMMAND_QUEUE_CLEAR
};
#endif
typedef void (*PostEditFunc)(const char *, Client *, gpointer);
gboolean command_search(Client *c, const Arg *arg, bool commit);
gboolean command_yank(Client *c, const Arg *arg, char buf);
gboolean command_save(Client *c, const Arg *arg);
#ifdef FEATURE_QUEUE
gboolean command_queue(Client *c, const Arg *arg);
#endif
gboolean command_spawn_editor(Client *c, const Arg *arg, PostEditFunc posteditfunc, gpointer data);
#endif /* end of include guard: _COMMAND_H */
fanglingsu-vimb-448e7e2/src/completion.c 0000664 0000000 0000000 00000017634 15145416123 0020267 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "completion.h"
#include "config.h"
#include "main.h"
typedef struct {
GtkWidget *win, *tree;
int active; /* number of the current active tree item */
CompletionSelectFunc selfunc;
} Completion;
static gboolean tree_selection_func(GtkTreeSelection *selection,
GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data);
extern struct Vimb vb;
/**
* Stop the completion and reset temporary used data.
*/
void completion_clean(Client *c)
{
Completion *comp = (Completion*)c->comp;
c->mode->flags &= ~FLAG_COMPLETION;
if (comp->win) {
gtk_widget_destroy(comp->win);
comp->win = NULL;
comp->tree = NULL;
}
}
/**
* Free the memory of the completion set on the client.
*/
void completion_cleanup(Client *c)
{
if (c->comp) {
g_slice_free(Completion, c->comp);
c->comp = NULL;
}
}
/**
* Start the completion by creating the required widgets and setting a select
* function.
*/
gboolean completion_create(Client *c, GtkTreeModel *model,
CompletionSelectFunc selfunc, gboolean back)
{
GtkCellRenderer *renderer;
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkRequisition size;
GtkTreePath *path;
GtkTreeIter iter;
int height, width;
Completion *comp = (Completion*)c->comp;
/* if there is only one match - don't build the tree view */
if (gtk_tree_model_iter_n_children(model, NULL) == 1) {
char *value;
path = gtk_tree_path_new_from_indices(0, -1);
if (gtk_tree_model_get_iter(model, &iter, path)) {
gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1);
/* call the select function */
selfunc(c, value);
g_free(value);
g_object_unref(model);
return FALSE;
}
}
comp->selfunc = selfunc;
/* prepare the tree view */
comp->win = gtk_scrolled_window_new(NULL, NULL);
comp->tree = gtk_tree_view_new_with_model(model);
gtk_style_context_add_provider(gtk_widget_get_style_context(comp->tree),
GTK_STYLE_PROVIDER(vb.style_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_widget_set_name(GTK_WIDGET(comp->tree), "completion");
gtk_box_pack_end(GTK_BOX(gtk_widget_get_parent(GTK_WIDGET(c->statusbar.box))), comp->win, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(comp->win), comp->tree);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(comp->tree), FALSE);
/* we have only on line per item so we can use the faster fixed heigh mode */
gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(comp->tree), TRUE);
g_object_unref(model);
/* prepare the selection */
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree));
gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
gtk_tree_selection_set_select_function(selection, tree_selection_func, c, NULL);
/* get window dimension */
gtk_window_get_size(GTK_WINDOW(c->window), &width, &height);
/* prepare first column */
column = gtk_tree_view_column_new();
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column);
renderer = gtk_cell_renderer_text_new();
g_object_set(renderer,
"ellipsize", PANGO_ELLIPSIZE_MIDDLE,
NULL
);
gtk_tree_view_column_pack_start(column, renderer, TRUE);
gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_FIRST);
gtk_tree_view_column_set_min_width(column, 2 * width/3);
/* prepare second column */
#ifdef FEATURE_TITLE_IN_COMPLETION
column = gtk_tree_view_column_new();
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_append_column(GTK_TREE_VIEW(comp->tree), column);
renderer = gtk_cell_renderer_text_new();
g_object_set(renderer,
"ellipsize", PANGO_ELLIPSIZE_END,
NULL
);
gtk_tree_view_column_pack_start(column, renderer, TRUE);
gtk_tree_view_column_add_attribute(column, renderer, "text", COMPLETION_STORE_SECOND);
#endif
/* to set the height for the treeview the tree must be realized first */
gtk_widget_show(comp->tree);
/* this prevents the first item to be placed out of view if the completion
* is shown */
while (gtk_events_pending()) {
gtk_main_iteration();
}
/* use max 1/3 of window height for the completion */
gtk_widget_get_preferred_size(comp->tree, NULL, &size);
height /= 3;
gtk_scrolled_window_set_min_content_height(
GTK_SCROLLED_WINDOW(comp->win),
size.height > height ? height : size.height
);
c->mode->flags |= FLAG_COMPLETION;
/* set to -1 to have the cursor on first or last item set in move_cursor */
comp->active = -1;
completion_next(c, back);
gtk_widget_show(comp->win);
return TRUE;
}
/**
* Initialize the completion system for given client.
*/
void completion_init(Client *c)
{
/* Allocate memory for the completion struct and save it on the client. */
c->comp = g_slice_new0(Completion);
}
/**
* Moves the selection to the next/previous tree item.
* If the end/beginning is reached return false and start on the opposite end
* on the next call.
*/
gboolean completion_next(Client *c, gboolean back)
{
int rows;
GtkTreePath *path;
GtkTreeView *tree = GTK_TREE_VIEW(((Completion*)c->comp)->tree);
Completion *comp = (Completion*)c->comp;
rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(tree), NULL);
if (back) {
comp->active--;
/* Step back over the beginning. */
if (comp->active == -1) {
/* Deselect the current item to show the user the initial typed
* content. */
gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree)));
return FALSE;
}
if (comp->active < -1) {
comp->active = rows - 1;
}
} else {
comp->active++;
/* Step over the end. */
if (comp->active == rows) {
gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(comp->tree)));
return FALSE;
}
if (comp->active >= rows) {
comp->active = 0;
}
}
/* get new path and move cursor to it */
path = gtk_tree_path_new_from_indices(comp->active, -1);
gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
gtk_tree_path_free(path);
return TRUE;
}
static gboolean tree_selection_func(GtkTreeSelection *selection,
GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data)
{
char *value;
GtkTreeIter iter;
Completion *comp = (Completion*)((Client*)data)->comp;
/* if not selected means the item is going to be selected which we are
* interested in */
if (!selected && gtk_tree_model_get_iter(model, &iter, path)) {
gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1);
comp->selfunc((Client*)data, value);
g_free(value);
/* TODO update comp->active on select by mouse to continue with
* or from selected item on. */
}
return TRUE;
}
fanglingsu-vimb-448e7e2/src/completion.h 0000664 0000000 0000000 00000002474 15145416123 0020270 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _COMPLETION_H
#define _COMPLETION_H
#include
#include "main.h"
typedef void (*CompletionSelectFunc) (Client *c, char *match);
enum {
COMPLETION_STORE_FIRST,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND,
#endif
COMPLETION_STORE_NUM
};
void completion_clean(Client *c);
void completion_cleanup(Client *c);
gboolean completion_create(Client *c, GtkTreeModel *model,
CompletionSelectFunc selfunc, gboolean back);
void completion_init(Client *c);
gboolean completion_next(Client *c, gboolean back);
#endif /* end of include guard: _COMPLETION_H */
fanglingsu-vimb-448e7e2/src/config.def.h 0000664 0000000 0000000 00000011533 15145416123 0020115 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
/* features */
/* show wget style progressbar in status bar */
#define FEATURE_WGET_PROGRESS_BAR
/* show load progress in window title */
#define FEATURE_TITLE_PROGRESS
/* show page title in url completions */
#define FEATURE_TITLE_IN_COMPLETION
/* enable the read it later queue */
#define FEATURE_QUEUE
/* disable X window embedding */
/* #define FEATURE_NO_XEMBED */
/* don't write the home-page uri in the history file */
/* #define FEATURE_HISTORY_WITHOUT_HOME_PAGE */
#ifdef FEATURE_WGET_PROGRESS_BAR
/* chars to use for the progressbar */
#define PROGRESS_BAR "=> "
#define PROGRESS_BAR_LEN 20
#endif
#define FEATURE_AUTOCMD
/* time in seconds after that message will be removed from inputbox if the
* message where only temporary */
#define MESSAGE_TIMEOUT 5
/* number of chars to be shown in statusbar for ambiguous commands */
#define SHOWCMD_LEN 10
/* css applied to the gui elements regardless of user's settings */
#define GUI_STYLE_CSS_BASE "#input text{background-color:inherit;color:inherit;caret-color:@color;font:inherit;}"
/* define this to set the initial background color for the GTK window */
#define GUI_WINDOW_BACKGROUND_COLOR "#FFFFFF"
#define INCSEARCH_MATCHES_LIMIT 1000
/* default font size for fonts in webview */
#define SETTING_DEFAULT_FONT_SIZE 16
#define SETTING_DEFAULT_MONOSPACE_FONT_SIZE 13
#define SETTING_GUI_FONT_NORMAL "10pt monospace"
#define SETTING_GUI_FONT_EMPH "bold 10pt monospace"
#define SETTING_HOME_PAGE "about:blank"
#define SETTING_DOWNLOAD_PATH "~/Downloads"
/* cookie-accept allowed values always, origin, never */
#define SETTING_COOKIE_ACCEPT "always"
#define SETTING_HINT_KEYS "0123456789"
#define SETTING_HISTIGNORE "^(about:)|(file:)"
#define SETTING_DOWNLOAD_COMMAND "/bin/sh -c \"curl -sLJOC - -e '$VIMB_URI' %s\""
#define SETTING_COMPLETION_CSS "color:#fff;background-color:#656565;font:" SETTING_GUI_FONT_NORMAL
#define SETTING_COMPLETION_HOVER_CSS "background-color:#777;"
#define SETTING_COMPLETION_SELECTED_CSS "color:#f6f3e8;background-color:#888;"
#define SETTING_INPUT_CSS "background-color:#fff;color:#000;font:" SETTING_GUI_FONT_NORMAL
#define SETTING_INPUT_ERROR_CSS "background-color:#f77;font:" SETTING_GUI_FONT_EMPH
#define SETTING_STATUS_CSS "color:#fff;background-color:#000;font:" SETTING_GUI_FONT_EMPH
#define SETTING_STATUS_SSL_CSS "background-color:#95e454;color:#000;"
#define SETTING_STATUS_SSL_INVLID_CSS "background-color:#f77;color:#000;"
#define MAXIMUM_HINTS 500
/* default window dimensions */
#define WIN_WIDTH 800
#define WIN_HEIGHT 600
/* if set to 1 vimb will check if the webextension could be found. */
#define CHECK_WEBEXTENSION_ON_STARTUP 1
/* This status indicator is only shown if "status-bar-show-settings" is
* enabled.
* The CHAR_MAP(value, internalValue, outputValue, valueIfNotMapped) is a
* little workaround to translate internal used string value like for
* GET_CHAR(c, "cookie-accept") which is one of "always", "origin" or "never"
* to those values that should be shown on statusbar.
* The STATUS_VARAIBLE_SHOW is used as argument for a printf like function. So
* the first argument is the output pattern. */
/*
#define STATUS_VARAIBLE_SHOW "js: %s, cookies: %s, hint-timeout: %d", \
GET_BOOL(c, "scripts") ? "on" : "off", \
GET_CHAR(c, "cookie-accept"), \
GET_INT(c, "hint-timeout")
*/
#define COOKIE GET_CHAR(c, "cookie-accept")
#define CHAR_MAP(v, i, m, d) (strcmp(v, i) == 0 ? m : (d))
#define STATUS_VARAIBLE_SHOW "%c%c%c%c%c%c%c%c", \
CHAR_MAP(COOKIE, "always", 'A', CHAR_MAP(COOKIE, "origin", '@', 'a')), \
GET_BOOL(c, "dark-mode") ? 'D' : 'd', \
vb.incognito ? 'E' : 'e', \
GET_BOOL(c, "images") ? 'I' : 'i', \
GET_BOOL(c, "html5-local-storage") ? 'L' : 'l', \
GET_BOOL(c, "stylesheet") ? 'M' : 'm', \
GET_BOOL(c, "scripts") ? 'S' : 's', \
GET_BOOL(c, "strict-ssl") ? 'T' : 't'
fanglingsu-vimb-448e7e2/src/context-menu.c 0000664 0000000 0000000 00000011102 15145416123 0020524 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "glib-object.h"
#include "glib.h"
#include "main.h"
#include "context-menu.h"
static void fix_open_in_new_window_stock_action(WebKitContextMenu *menu,
WebKitContextMenuItem *menu_item, int menu_item_position, char *msgid,
char *gaction_name, GVariant *data);
static void open_in_new_window(GVariant *data);
/**
* Callback for the webview context-menu signal.
*/
gboolean on_webview_context_menu(WebKitWebView *webview,
WebKitContextMenu *menu, GdkEvent *event,
WebKitHitTestResult *hit_test_result, Client *c)
{
GList* menu_item;
GList* next;
int position;
WebKitContextMenuAction menu_action;
menu_item = webkit_context_menu_get_items(menu);
for (position = 0; menu_item != NULL; position++) {
menu_action = webkit_context_menu_item_get_stock_action(menu_item->data);
next = menu_item->next;
switch (menu_action) {
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK_IN_NEW_WINDOW:
fix_open_in_new_window_stock_action(menu, menu_item->data,
position, "Open Link in New _Window", "open-link-in-new-window",
g_variant_new("(st)", webkit_hit_test_result_get_link_uri(hit_test_result), c));
break;
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_IMAGE_IN_NEW_WINDOW:
fix_open_in_new_window_stock_action(menu, menu_item->data,
position, "Open _Image in New Window", "open-image-in-new-window",
g_variant_new("(st)", webkit_hit_test_result_get_image_uri(hit_test_result), c));
break;
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_AUDIO_IN_NEW_WINDOW:
fix_open_in_new_window_stock_action(menu, menu_item->data,
position, "Open _Audio in New Window", "open-audio-in-new-window",
g_variant_new("(st)", webkit_hit_test_result_get_media_uri(hit_test_result), c));
break;
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_VIDEO_IN_NEW_WINDOW:
fix_open_in_new_window_stock_action(menu, menu_item->data,
position, "Open _Video in New Window", "open-video-in-new-window",
g_variant_new("(st)", webkit_hit_test_result_get_media_uri(hit_test_result), c));
break;
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_FRAME_IN_NEW_WINDOW:
// TODO figure out how to fix this action instead of removing
webkit_context_menu_remove(menu, menu_item->data);
position--;
break;
default:
break;
}
menu_item = next;
}
return FALSE;
}
/**
* When you click on "Open in New Window" context menu buttons
* WebKit creates a new window on its own, and then Vimb creates one more
* window. In some cases, it leads to segmentation fault after closing windows.
* To fix this issue, we can replace default context menu buttons with custom
* buttons.
*/
static void fix_open_in_new_window_stock_action(WebKitContextMenu *menu,
WebKitContextMenuItem *menu_item, int menu_item_position, char *msgid,
char *gaction_name, GVariant *data)
{
WebKitContextMenuItem *new_item;
GSimpleAction *action;
action = g_simple_action_new(gaction_name, NULL);
g_signal_connect_swapped(G_OBJECT(action), "activate", G_CALLBACK(open_in_new_window), data);
webkit_context_menu_remove(menu, menu_item);
new_item = webkit_context_menu_item_new_from_gaction(G_ACTION(action),
g_dgettext("WebKitGTK-4.1", msgid), NULL);
webkit_context_menu_insert(menu, new_item, menu_item_position);
g_object_unref(action);
}
static void open_in_new_window(GVariant *data)
{
Client *c;
char* uri;
g_variant_get(data, "(st)", &uri, &c);
vb_load_uri(c, &(Arg){TARGET_NEW, uri});
}
fanglingsu-vimb-448e7e2/src/context-menu.h 0000664 0000000 0000000 00000002010 15145416123 0020527 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2019 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _CONTEXT_MENU_H
#define _CONTEXT_MENU_H
#include "main.h"
gboolean on_webview_context_menu(WebKitWebView *webview,
WebKitContextMenu *context_menu, GdkEvent *event,
WebKitHitTestResult *hit_test_result, Client *c);
#endif /* end of include guard: _CONTEXT_MENU_H */
fanglingsu-vimb-448e7e2/src/ex.c 0000664 0000000 0000000 00000135264 15145416123 0016532 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
/**
* This file contains function to handle input editing, parsing of called ex
* commands from inputbox and the ex commands.
*/
#include
#include
#include
#include "ascii.h"
#include "bookmark.h"
#include "command.h"
#include "completion.h"
#include "config.h"
#include "ex.h"
#include "handler.h"
#include "hints.h"
#include "history.h"
#include "main.h"
#include "map.h"
#include "setting.h"
#include "shortcut.h"
#include "util.h"
#include "ext-proxy.h"
#include "autocmd.h"
#include "util.h"
typedef enum {
#ifdef FEATURE_AUTOCMD
EX_AUTOCMD,
EX_AUGROUP,
#endif
EX_BMA,
EX_BMR,
EX_EVAL,
EX_HARDCOPY,
EX_CLEARDATA,
EX_CMAP,
EX_CNOREMAP,
EX_HANDADD,
EX_HANDREM,
EX_IMAP,
EX_NMAP,
EX_NNOREMAP,
EX_CUNMAP,
EX_IUNMAP,
EX_INOREMAP,
EX_NUNMAP,
EX_NORMAL,
EX_OPEN,
#ifdef FEATURE_QUEUE
EX_QCLEAR,
EX_QPOP,
EX_QPUSH,
EX_QUNSHIFT,
#endif
EX_QUIT,
EX_REG,
EX_SAVE,
EX_SCA,
EX_SCD,
EX_SCR,
EX_SET,
EX_SHELLCMD,
EX_SHELLEX,
EX_SOURCE,
EX_TABOPEN,
} ExCode;
typedef enum {
PHASE_START,
PHASE_REG,
} Phase;
typedef struct {
int count; /* commands count */
int idx; /* index in commands array */
const char *name; /* name of the command */
ExCode code; /* id of the command */
gboolean bang; /* if the command was called with a bang ! */
GString *lhs; /* left hand side of the command - single word */
GString *rhs; /* right hand side of the command - multiple words */
int flags; /* flags for the already parsed command */
} ExArg;
typedef VbCmdResult (*ExFunc)(Client *c, const ExArg *arg);
typedef struct {
const char *name; /* full name of the command even if called abbreviated */
ExCode code; /* constant id for the command */
ExFunc func;
#define EX_FLAG_NONE 0x000 /* no flags set */
#define EX_FLAG_BANG 0x001 /* command uses the bang ! after command name */
#define EX_FLAG_LHS 0x002 /* command has a single word after the command name */
#define EX_FLAG_RHS 0x004 /* command has a right hand side */
#define EX_FLAG_EXP 0x008 /* expand pattern like % or %:p in rhs */
#define EX_FLAG_CMD 0x010 /* like EX_FLAG_RHS but can contain | chars */
int flags;
} ExInfo;
static struct {
char reg; /* char for the yank register */
Phase phase; /* current parsing phase */
} info = {'\0', PHASE_START};
static void input_activate(Client *c);
static gboolean parse(Client *c, const char **input, ExArg *arg, gboolean *nohist);
static gboolean parse_count(const char **input, ExArg *arg);
static gboolean parse_command_name(Client *c, const char **input, ExArg *arg);
static gboolean parse_bang(const char **input, ExArg *arg);
static gboolean parse_lhs(const char **input, ExArg *arg);
static gboolean parse_rhs(const char **input, ExArg *arg);
static void skip_whitespace(const char **input);
static void free_cmdarg(ExArg *arg);
static VbCmdResult execute(Client *c, const ExArg *arg);
#ifdef FEATURE_AUTOCMD
static VbCmdResult ex_augroup(Client *c, const ExArg *arg);
static VbCmdResult ex_autocmd(Client *c, const ExArg *arg);
#endif
static VbCmdResult ex_bookmark(Client *c, const ExArg *arg);
static VbCmdResult ex_eval(Client *c, const ExArg *arg);
static void on_eval_script_finished(GDBusProxy *proxy, GAsyncResult *result, Client *c);
static VbCmdResult ex_cleardata(Client *c, const ExArg *arg);
static VbCmdResult ex_hardcopy(Client *c, const ExArg *arg);
static void print_failed_cb(WebKitPrintOperation* op, GError *err, Client *c);
static VbCmdResult ex_map(Client *c, const ExArg *arg);
static VbCmdResult ex_unmap(Client *c, const ExArg *arg);
static VbCmdResult ex_normal(Client *c, const ExArg *arg);
static VbCmdResult ex_open(Client *c, const ExArg *arg);
#ifdef FEATURE_QUEUE
static VbCmdResult ex_queue(Client *c, const ExArg *arg);
#endif
static VbCmdResult ex_register(Client *c, const ExArg *arg);
static VbCmdResult ex_quit(Client *c, const ExArg *arg);
static VbCmdResult ex_save(Client *c, const ExArg *arg);
static VbCmdResult ex_set(Client *c, const ExArg *arg);
static VbCmdResult ex_shellcmd(Client *c, const ExArg *arg);
static VbCmdResult ex_shellex(Client *c, const ExArg *arg);
static VbCmdResult ex_shortcut(Client *c, const ExArg *arg);
static VbCmdResult ex_source(Client *c, const ExArg *arg);
static VbCmdResult ex_handlers(Client *c, const ExArg *arg);
static void update_current_selection_env_var(Client *c);
static gboolean complete(Client *c, short direction);
static void completion_select(Client *c, char *match);
static gboolean history(Client *c, gboolean prev);
static void history_rewind(void);
/* The order of following command names is significant. If there exists
* ambiguous commands matching to the users input, the first defined will be
* the preferred match.
* Also the sorting and grouping of command names matters, so we give up
* searching for a matching command if the next compared character did not
* match. */
static ExInfo commands[] = {
/* command code func flags */
#ifdef FEATURE_AUTOCMD
{"autocmd", EX_AUTOCMD, ex_autocmd, EX_FLAG_CMD|EX_FLAG_BANG},
{"augroup", EX_AUGROUP, ex_augroup, EX_FLAG_LHS|EX_FLAG_BANG},
#endif
{"bma", EX_BMA, ex_bookmark, EX_FLAG_RHS},
{"bmr", EX_BMR, ex_bookmark, EX_FLAG_RHS},
{"cmap", EX_CMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"cnoremap", EX_CNOREMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"cunmap", EX_CUNMAP, ex_unmap, EX_FLAG_LHS},
{"cleardata", EX_CLEARDATA, ex_cleardata, EX_FLAG_LHS|EX_FLAG_RHS},
{"hardcopy", EX_HARDCOPY, ex_hardcopy, EX_FLAG_NONE},
{"handler-add", EX_HANDADD, ex_handlers, EX_FLAG_RHS},
{"handler-remove", EX_HANDREM, ex_handlers, EX_FLAG_RHS},
{"eval", EX_EVAL, ex_eval, EX_FLAG_CMD|EX_FLAG_BANG},
{"imap", EX_IMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"inoremap", EX_INOREMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"iunmap", EX_IUNMAP, ex_unmap, EX_FLAG_LHS},
{"nmap", EX_NMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"nnoremap", EX_NNOREMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
{"normal", EX_NORMAL, ex_normal, EX_FLAG_BANG|EX_FLAG_CMD},
{"nunmap", EX_NUNMAP, ex_unmap, EX_FLAG_LHS},
{"open", EX_OPEN, ex_open, EX_FLAG_CMD},
{"quit", EX_QUIT, ex_quit, EX_FLAG_NONE|EX_FLAG_BANG},
#ifdef FEATURE_QUEUE
{"qunshift", EX_QUNSHIFT, ex_queue, EX_FLAG_RHS},
{"qclear", EX_QCLEAR, ex_queue, EX_FLAG_RHS},
{"qpop", EX_QPOP, ex_queue, EX_FLAG_NONE},
{"qpush", EX_QPUSH, ex_queue, EX_FLAG_RHS},
#endif
{"register", EX_REG, ex_register, EX_FLAG_NONE},
{"save", EX_SAVE, ex_save, EX_FLAG_RHS|EX_FLAG_EXP},
{"set", EX_SET, ex_set, EX_FLAG_RHS},
{"shellcmd", EX_SHELLCMD, ex_shellcmd, EX_FLAG_CMD|EX_FLAG_EXP|EX_FLAG_BANG},
{"shellex", EX_SHELLEX, ex_shellex, EX_FLAG_CMD|EX_FLAG_EXP},
{"shortcut-add", EX_SCA, ex_shortcut, EX_FLAG_RHS},
{"shortcut-default", EX_SCD, ex_shortcut, EX_FLAG_RHS},
{"shortcut-remove", EX_SCR, ex_shortcut, EX_FLAG_RHS},
{"source", EX_SOURCE, ex_source, EX_FLAG_RHS|EX_FLAG_EXP},
{"tabopen", EX_TABOPEN, ex_open, EX_FLAG_CMD},
};
static struct {
guint count;
char *prefix; /* completion prefix like :, ? and / */
char *current; /* holds the current written input box content */
char *token; /* initial filter content */
} excomp;
static struct {
char *prefix; /* prefix that is prepended to the history item to for the complete command */
char *query; /* part of input text to match the history items */
GList *active;
} exhist;
extern struct Vimb vb;
/**
* Function called when vimb enters the command mode.
*/
void ex_enter(Client *c)
{
gtk_widget_grab_focus(GTK_WIDGET(c->input));
#if 0
dom_clear_focus(c->webview);
#endif
}
/**
* Called when the command mode is left.
*/
void ex_leave(Client *c)
{
completion_clean(c);
hints_clear(c);
if (c->config.incsearch) {
command_search(c, &((Arg){0, NULL}), FALSE);
}
}
/**
* Handles the keypress events from webview and inputbox.
*/
VbResult ex_keypress(Client *c, int key)
{
GtkTextIter start, end;
gboolean check_empty = FALSE;
GtkTextBuffer *buffer = c->buffer;
GtkTextMark *mark;
VbResult res;
const char *text;
if (key == CTRL('C')) {
vb_enter(c, 'n');
return RESULT_COMPLETE;
}
/* delegate call to hint mode if this is active */
if (c->mode->flags & FLAG_HINTING
&& RESULT_COMPLETE == hints_keypress(c, key)) {
return RESULT_COMPLETE;
}
/* process the register */
if (info.phase == PHASE_REG) {
info.reg = (char)key;
info.phase = PHASE_REG;
/* insert the register text at cursor position */
text = vb_register_get(c, (char)key);
if (text) {
gtk_text_buffer_insert_at_cursor(buffer, text, strlen(text));
}
res = RESULT_COMPLETE;
} else {
res = RESULT_COMPLETE;
switch (key) {
case KEY_TAB:
complete(c, 1);
break;
case KEY_SHIFT_TAB:
complete(c, -1);
break;
case KEY_UP: /* fall through */
case CTRL('P'):
history(c, TRUE);
break;
case KEY_DOWN: /* fall through */
case CTRL('N'):
history(c, FALSE);
break;
case KEY_CR:
input_activate(c);
break;
case CTRL('['):
vb_enter(c, 'n');
vb_input_set_text(c, "");
break;
/* basic command line editing */
case CTRL('H'):
/* delete the last char before the cursor */
mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
gtk_text_buffer_backspace(buffer, &start, TRUE, TRUE);
check_empty = TRUE;
break;
case CTRL('W'):
/* delete word backward from cursor */
mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
/* copy the iter to build start and end point for deletion */
start = end;
/* move the iterator to the beginning of previous word */
if (gtk_text_iter_backward_word_start(&start)) {
gtk_text_buffer_delete(buffer, &start, &end);
}
check_empty = TRUE;
break;
case CTRL('B'):
/* move the cursor direct behind the prompt */
gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(c->state.prompt));
gtk_text_buffer_place_cursor(buffer, &start);
break;
case CTRL('E'):
/* move the cursor to the end of line */
gtk_text_buffer_get_end_iter(buffer, &start);
gtk_text_buffer_place_cursor(buffer, &start);
break;
case CTRL('U'):
/* remove everything between cursor and prompt */
mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(c->state.prompt));
gtk_text_buffer_delete(buffer, &start, &end);
break;
case CTRL('R'):
info.phase = PHASE_REG;
c->mode->flags |= FLAG_NOMAP;
res = RESULT_MORE;
break;
default:
/* if is printable ascii char, than write it at the cursor
* position into input box */
if (key >= 0x20 && key <= 0x7e) {
gtk_text_buffer_insert_at_cursor(buffer, (char[2]){key, 0}, 1);
} else {
c->state.processed_key = FALSE;
}
}
}
/* if the user deleted some content of the inputbox we check if the
* inputbox is empty - if so we switch back to normal like vim does */
if (check_empty) {
gtk_text_buffer_get_bounds(buffer, &start, &end);
if (gtk_text_iter_equal(&start, &end)) {
vb_enter(c, 'n');
vb_input_set_text(c, "");
}
}
if (res == RESULT_COMPLETE) {
info.reg = 0;
info.phase = PHASE_START;
}
return res;
}
/**
* Handles changes in the inputbox.
*/
void ex_input_changed(Client *c, const char *text)
{
GtkTextIter start, end;
GtkTextBuffer *buffer = c->buffer;
/* don't add line breaks if content is pasted from clipboard into inputbox */
if (gtk_text_buffer_get_line_count(buffer) > 1) {
/* remove everything from the buffer, except of the first line */
gtk_text_buffer_get_iter_at_line(buffer, &start, 0);
if (gtk_text_iter_forward_to_line_end(&start)) {
gtk_text_buffer_get_end_iter(buffer, &end);
/* TODO the following line creates a GTK warning.
* ex_input_changed() is called from the "changed" event handler of
* GtkTextBuffer. Apparently it's not supported to change a text
* buffer in the changed handler!?
*
* Gtk-WARNING **: Invalid text buffer iterator: either the
* iterator is uninitialized, or the characters/pixbufs/widgets in
* the buffer have been modified since the iterator was created.
*/
gtk_text_buffer_delete(buffer, &start, &end);
}
}
switch (*text) {
case ';': /* fall through - the modes are handled by hints_create */
case 'g':
hints_create(c, text);
break;
case '/': /* fall through */
case '?':
if (c->config.incsearch) {
command_search(c, &((Arg){*text == '/' ? 1 : -1, (char*)text + 1}), FALSE);
}
break;
}
}
gboolean ex_fill_completion(GtkListStore *store, const char *input)
{
GtkTreeIter iter;
ExInfo *cmd;
gboolean found = FALSE;
if (!input || *input == '\0') {
for (int i = 0; i < LENGTH(commands); i++) {
cmd = &commands[i];
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
found = TRUE;
}
} else {
for (int i = 0; i < LENGTH(commands); i++) {
cmd = &commands[i];
if (g_str_has_prefix(cmd->name, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
found = TRUE;
}
}
}
return found;
}
/**
* Run all ex commands from a file.
*/
VbCmdResult ex_run_file(Client *c, const char *filename)
{
int length, i;
char *line, **lines;
VbCmdResult res = CMD_SUCCESS;
lines = util_get_lines(filename);
if (!lines) {
return res;
}
length = g_strv_length(lines);
for (i = 0; i < length; i++) {
line = lines[i];
/* skip commented or empty lines */
if (*line == '#' || !*line) {
continue;
}
if ((ex_run_string(c, line, FALSE) & ~CMD_KEEPINPUT) == CMD_ERROR) {
res = CMD_ERROR | CMD_KEEPINPUT;
g_warning("Invalid command in %s line #%u : '%s'", filename, i+1, line);
}
}
g_strfreev(lines);
return res;
}
VbCmdResult ex_run_string(Client *c, const char *input, gboolean enable_history)
{
/* copy to have original command for history */
const char *in = input;
gboolean nohist = FALSE;
VbCmdResult res = CMD_ERROR | CMD_KEEPINPUT;
ExArg *arg = g_slice_new0(ExArg);
arg->lhs = g_string_new("");
arg->rhs = g_string_new("");
while (in && *in) {
if (!parse(c, &in, arg, &nohist) || !(res = execute(c, arg))) {
break;
}
}
if (enable_history && !nohist) {
history_add(c, HISTORY_COMMAND, input, NULL);
vb_register_add(c, ':', input);
}
free_cmdarg(arg);
return res;
}
/**
* This is called if the user typed or into the inputbox.
*/
static void input_activate(Client *c)
{
int count = -1;
char *text, *cmd;
VbCmdResult res;
text = vb_input_get_text(c);
/* skip leading prompt char like ':' or '/' */
cmd = text + 1;
switch (*text) {
case '/': count = 1; /* fall through */
case '?':
vb_enter(c, 'n');
command_search(c, &((Arg){count, strlen(cmd) ? cmd : NULL}), TRUE);
break;
case ';': /* fall through */
case 'g':
/* TODO fire hints */
break;
case ':':
vb_enter(c, 'n');
res = ex_run_string(c, cmd, TRUE);
if (!(res & CMD_KEEPINPUT)) {
/* clear input on success if this is not explicit ommited */
vb_input_set_text(c, "");
}
break;
}
g_free(text);
}
/**
* Parses given input string into given ExArg pointer.
*/
static gboolean parse(Client *c, const char **input, ExArg *arg, gboolean *nohist)
{
if (!*input || !**input) {
return FALSE;
}
/* truncate string from potentially previous run */
g_string_truncate(arg->lhs, 0);
g_string_truncate(arg->rhs, 0);
/* remove leading whitespace and : */
while (**input && (**input == ':' || VB_IS_SPACE(**input))) {
(*input)++;
/* Command started with additional ':' or whitespce - don't record it
* in history or registry. */
*nohist = TRUE;
}
parse_count(input, arg);
skip_whitespace(input);
if (!parse_command_name(c, input, arg)) {
return FALSE;
}
/* parse the bang if this is allowed */
if (arg->flags & EX_FLAG_BANG) {
parse_bang(input, arg);
}
/* parse the lhs if this is available */
skip_whitespace(input);
if (arg->flags & EX_FLAG_LHS) {
parse_lhs(input, arg);
}
/* parse the rhs if this is available */
skip_whitespace(input);
parse_rhs(input, arg);
if (**input) {
(*input)++;
}
return TRUE;
}
/**
* Parses possible found count of given input into ExArg pointer.
*/
static gboolean parse_count(const char **input, ExArg *arg)
{
if (!*input || !VB_IS_DIGIT(**input)) {
arg->count = 0;
} else {
do {
arg->count = arg->count * 10 + (**input - '0');
(*input)++;
} while (VB_IS_DIGIT(**input));
}
return TRUE;
}
/**
* Parse the command name from given input.
*/
static gboolean parse_command_name(Client *c, const char **input, ExArg *arg)
{
int len = 0;
int first = 0; /* number of first found command */
int matches = 0; /* number of commands that matches the input */
char cmd[20] = {0}; /* name of found command */
do {
/* copy the next char into the cmd buffer */
cmd[len++] = **input;
int i;
for (i = first, matches = 0; i < LENGTH(commands); i++) {
/* commands are grouped by their first letters, if we reached the
* end of the group there are no more possible matches to find */
if (len > 1 && strncmp(commands[i].name, cmd, len - 1)) {
break;
}
if (commands[i].name[len - 1] == **input) {
/* partial match found */
if (!matches) {
/* if this is the first then remember it */
first = i;
}
matches++;
}
}
(*input)++;
} while (matches > 0 && **input && !VB_IS_SPACE(**input) && **input != '!');
if (!matches) {
/* read until next whitespace or end of input to get command name for
* error message - vim uses the whole rest of the input string - but
* the first word seems to bee enough for the error message */
for (; len < (LENGTH(cmd) - 1) && *input && !VB_IS_SPACE(**input); (*input)++) {
cmd[len++] = **input;
}
cmd[len] = '\0';
vb_echo(c, MSG_ERROR, TRUE, "Unknown command: %s", cmd);
return FALSE;
}
arg->idx = first;
arg->code = commands[first].code;
arg->name = commands[first].name;
arg->flags = commands[first].flags;
return TRUE;
}
/**
* Parse a single bang ! after the command.
*/
static gboolean parse_bang(const char **input, ExArg *arg)
{
if (*input && **input == '!') {
arg->bang = TRUE;
(*input)++;
}
return TRUE;
}
/**
* Parse a single word left hand side of a command arg.
*/
static gboolean parse_lhs(const char **input, ExArg *arg)
{
char quote = '\\';
if (!*input || !**input) {
return FALSE;
}
/* get the char until the next none escaped whitespace and save it into
* the lhs */
while (**input && !VB_IS_SPACE(**input)) {
/* if we find a backslash this escapes the next whitespace */
if (**input == quote) {
/* move pointer to the next char */
(*input)++;
if (!*input) {
/* if input ends here - use only the backslash */
g_string_append_c(arg->lhs, quote);
} else if (**input == ' ') {
/* escaped whitespace becomes only whitespace */
g_string_append_c(arg->lhs, **input);
} else {
/* put escape char and next char into the result string */
g_string_append_c(arg->lhs, quote);
g_string_append_c(arg->lhs, **input);
}
} else {
/* unquoted char */
g_string_append_c(arg->lhs, **input);
}
(*input)++;
}
return TRUE;
}
/**
* Parses the right hand side of command args. If flag EX_FLAG_CMD is set the
* command can contain any char accept of the newline, else the right hand
* side end on the first none escaped | or newline.
*/
static gboolean parse_rhs(const char **input, ExArg *arg)
{
int expflags, flags;
gboolean cmdlist;
/* don't do anything if command has no right hand side or command list or
* there is nothing to parse */
if ((arg->flags & (EX_FLAG_RHS|EX_FLAG_CMD)) == 0
|| !*input || !**input
) {
return FALSE;
}
cmdlist = (arg->flags & EX_FLAG_CMD) != 0;
expflags = (arg->flags & EX_FLAG_EXP)
? UTIL_EXP_TILDE|UTIL_EXP_DOLLAR
: 0;
flags = expflags;
/* Get char until the end of command. Command ends on newline, and if
* EX_FLAG_CMD is not set also on | */
while (**input && **input != '\n' && (cmdlist || **input != '|')) {
/* check for expansion placeholder */
util_parse_expansion(input, arg->rhs, flags, "|\\");
if (VB_IS_SEPARATOR(**input)) {
/* add tilde expansion for next loop needs to be first char or to
* be after a space */
flags = expflags;
} else {
/* remove tile expansion for next loop */
flags &= ~UTIL_EXP_TILDE;
}
(*input)++;
}
return TRUE;
}
/**
* Executes the command given by ExArg.
*/
static VbCmdResult execute(Client *c, const ExArg *arg)
{
return (commands[arg->idx].func)(c, arg);
}
static void skip_whitespace(const char **input)
{
while (**input && VB_IS_SPACE(**input)) {
(*input)++;
}
}
static void free_cmdarg(ExArg *arg)
{
if (arg->lhs) {
g_string_free(arg->lhs, TRUE);
}
if (arg->rhs) {
g_string_free(arg->rhs, TRUE);
}
g_slice_free(ExArg, arg);
}
#ifdef FEATURE_AUTOCMD
static VbCmdResult ex_augroup(Client *c, const ExArg *arg)
{
return autocmd_augroup(c, arg->lhs->str, arg->bang) ? CMD_SUCCESS : CMD_ERROR;
}
static VbCmdResult ex_autocmd(Client *c, const ExArg *arg)
{
return autocmd_add(c, arg->rhs->str, arg->bang) ? CMD_SUCCESS : CMD_ERROR;
}
#endif
static VbCmdResult ex_bookmark(Client *c, const ExArg *arg)
{
if (arg->code == EX_BMR) {
if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : c->state.uri)) {
vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark removed");
return CMD_SUCCESS | CMD_KEEPINPUT;
}
} else if (bookmark_add(c->state.uri, c->state.title, arg->rhs->str)) {
vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark added");
return CMD_SUCCESS | CMD_KEEPINPUT;
}
return CMD_ERROR;
}
static VbCmdResult ex_eval(Client *c, const ExArg *arg)
{
/* Called as :eval! - don't print to inputbox. */
if (arg->bang) {
ext_proxy_eval_script(c, arg->rhs->str, NULL);
} else {
ext_proxy_eval_script(c, arg->rhs->str, (GAsyncReadyCallback)on_eval_script_finished);
}
return CMD_SUCCESS;
}
static void on_eval_script_finished(GDBusProxy *proxy, GAsyncResult *result, Client *c)
{
gboolean success = FALSE;
char *string = NULL;
GVariant *return_value = g_dbus_proxy_call_finish(proxy, result, NULL);
if (return_value) {
g_variant_get(return_value, "(bs)", &success, &string);
if (success) {
vb_echo(c, MSG_NORMAL, FALSE, "%s", string);
} else {
vb_echo(c, MSG_ERROR, TRUE, "%s", string);
}
} else {
vb_echo(c, MSG_ERROR, TRUE, "");
}
}
/**
* Clear website data by ':cleardata {dataTypeList} {timespan}'.
*/
static VbCmdResult ex_cleardata(Client *c, const ExArg *arg)
{
unsigned int data_types = 0;
WebKitWebsiteDataManager *manager;
VbCmdResult result = CMD_SUCCESS;
GTimeSpan timespan = 0;
/* Parse the left hand side if this is available and not '-' */
if (arg->lhs->len && strcmp(arg->lhs->str, "-") != 0) {
GString *str;
char **types;
char *value;
unsigned int len, i, t;
gboolean found;
static const Arg type_map[] = {
{WEBKIT_WEBSITE_DATA_MEMORY_CACHE, "memory-cache"},
{WEBKIT_WEBSITE_DATA_DISK_CACHE, "disk-cache"},
{WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE, "offline-cache"},
{WEBKIT_WEBSITE_DATA_SESSION_STORAGE, "session-storage"},
{WEBKIT_WEBSITE_DATA_LOCAL_STORAGE, "local-storage"},
{WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES, "indexeddb-databases"},
{WEBKIT_WEBSITE_DATA_PLUGIN_DATA, "plugin-data"},
{WEBKIT_WEBSITE_DATA_COOKIES, "cookies"},
#if WEBKIT_CHECK_VERSION(2, 26, 0)
{WEBKIT_WEBSITE_DATA_HSTS_CACHE, "hsts-cache"},
#endif
{0, ""},
};
/* Walk through list of data types given as rhs. */
types = g_strsplit(arg->rhs->str, ",", -1);
len = g_strv_length(types);
str = g_string_new(NULL);
/* Loop over given types and collect the type values into bitmap. */
for (i = 0; i < len; i++) {
value = types[i];
found = FALSE;
for (t = 0; type_map[t].i; t++) {
if (!strcmp(value, type_map[t].s)) {
data_types |= type_map[t].i;
found = TRUE;
break;
}
}
if (!found) {
/* collect unknown types to be shown in error message. */
g_string_append_printf(str, "%s ", value);
}
}
if (*str->str) {
vb_echo(c, MSG_ERROR, TRUE, "unknown data type(s): %s", str->str);
result = CMD_ERROR|CMD_KEEPINPUT;
}
g_string_free(str, TRUE);
g_strfreev(types);
} else {
/* No special type or '-' given - clear all known types. */
data_types = WEBKIT_WEBSITE_DATA_ALL;
}
if (arg->rhs->len) {
timespan = util_string_to_timespan(arg->rhs->str);
}
manager = webkit_web_context_get_website_data_manager(webkit_web_view_get_context(c->webview));
webkit_website_data_manager_clear(manager, data_types, timespan, NULL, NULL, NULL);
return result;
}
/**
* Opens the gtk print dialog.
*/
static VbCmdResult ex_hardcopy(Client *c, const ExArg *arg)
{
WebKitPrintOperation *op = webkit_print_operation_new(c->webview);
GtkPrintSettings *settings = gtk_print_settings_new();
gtk_print_settings_set(settings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, c->state.title);
g_signal_connect(op, "failed", G_CALLBACK(print_failed_cb), c);
webkit_print_operation_set_print_settings(op, settings);
webkit_print_operation_run_dialog(op, NULL);
g_object_unref(op);
g_object_unref(settings);
return CMD_SUCCESS;
}
/**
* Callback called when printing failed.
*/
static void print_failed_cb(WebKitPrintOperation* op, GError *err, Client *c)
{
vb_echo(c, MSG_ERROR, FALSE, "print failed: %s", err->message);
}
static VbCmdResult ex_map(Client *c, const ExArg *arg)
{
if (!arg->lhs->len || !arg->rhs->len) {
return CMD_ERROR;
}
/* instead of using the EX_XMAP constants we use the first char of the
* command name as mode and the second to determine if noremap is used */
map_insert(c, arg->lhs->str, arg->rhs->str, arg->name[0], arg->name[1] != 'n');
return CMD_SUCCESS;
}
static VbCmdResult ex_unmap(Client *c, const ExArg *arg)
{
char *lhs;
if (!arg->lhs->len) {
return CMD_ERROR;
}
lhs = arg->lhs->str;
if (arg->code == EX_NUNMAP) {
map_delete(c, lhs, 'n');
} else if (arg->code == EX_CUNMAP) {
map_delete(c, lhs, 'c');
} else {
map_delete(c, lhs, 'i');
}
return CMD_SUCCESS;
}
static VbCmdResult ex_normal(Client *c, const ExArg *arg)
{
vb_enter(c, 'n');
/* if called with bang - don't apply mapping */
map_handle_string(c, arg->rhs->str, !arg->bang);
return CMD_SUCCESS | CMD_KEEPINPUT;
}
static VbCmdResult ex_open(Client *c, const ExArg *arg)
{
if (arg->code == EX_TABOPEN) {
return vb_load_uri(c, &((Arg){TARGET_NEW, arg->rhs->str})) ? CMD_SUCCESS : CMD_ERROR;
}
return vb_load_uri(c, &((Arg){TARGET_CURRENT, arg->rhs->str})) ? CMD_SUCCESS :CMD_ERROR;
}
#ifdef FEATURE_QUEUE
static VbCmdResult ex_queue(Client *c, const ExArg *arg)
{
Arg a = {0};
switch (arg->code) {
case EX_QPUSH:
a.i = COMMAND_QUEUE_PUSH;
break;
case EX_QUNSHIFT:
a.i = COMMAND_QUEUE_UNSHIFT;
break;
case EX_QPOP:
a.i = COMMAND_QUEUE_POP;
break;
case EX_QCLEAR:
a.i = COMMAND_QUEUE_CLEAR;
break;
default:
return CMD_ERROR;
}
/* if no argument is found in rhs, keep the uri in arg null to force
* command_queue() to use current URI */
if (arg->rhs->len) {
a.s = arg->rhs->str;
}
return command_queue(c, &a)
? CMD_SUCCESS | CMD_KEEPINPUT
: CMD_ERROR;
}
#endif
/**
* Show the contents of the registers :reg.
*/
static VbCmdResult ex_register(Client *c, const ExArg *arg)
{
int idx;
char *reg;
const char *regchars = REG_CHARS;
GString *str = g_string_new("-- Register --");
for (idx = 0; idx < REG_SIZE; idx++) {
/* show only filled registers */
if (c->state.reg[idx]) {
/* replace all newlines with enough spaces to align nicely */
reg = util_str_replace("\n", "\n ", c->state.reg[idx]);
g_string_append_printf(str, "\n\"%c %s", regchars[idx], reg);
g_free(reg);
}
}
vb_echo(c, MSG_NORMAL, FALSE, "%s", str->str);
g_string_free(str, TRUE);
return CMD_SUCCESS | CMD_KEEPINPUT;
}
static VbCmdResult ex_quit(Client *c, const ExArg *arg)
{
vb_quit(c, arg->bang);
return CMD_SUCCESS;
}
static VbCmdResult ex_save(Client *c, const ExArg *arg)
{
return command_save(c, &((Arg){COMMAND_SAVE_CURRENT, arg->rhs->str}))
? CMD_SUCCESS | CMD_KEEPINPUT
: CMD_ERROR | CMD_KEEPINPUT;
}
static VbCmdResult ex_set(Client *c, const ExArg *arg)
{
char *param = NULL;
if (!arg->rhs->len) {
return FALSE;
}
/* split the input string into parameter and value part */
if ((param = strchr(arg->rhs->str, '='))) {
*param++ = '\0';
g_strstrip(arg->rhs->str);
g_strstrip(param);
return setting_run(c, arg->rhs->str, param);
}
return setting_run(c, arg->rhs->str, NULL);
}
static VbCmdResult ex_shellcmd(Client *c, const ExArg *arg)
{
int status;
char *stdOut = NULL, *stdErr = NULL;
VbCmdResult res;
GError *error = NULL;
if (!*arg->rhs->str) {
return CMD_ERROR;
}
update_current_selection_env_var(c);
if (arg->bang) {
if (!g_spawn_command_line_async(arg->rhs->str, &error)) {
g_warning("Can't run '%s': %s", arg->rhs->str, error->message);
g_clear_error(&error);
res = CMD_ERROR | CMD_KEEPINPUT;
} else {
res = CMD_SUCCESS;
}
} else {
if (!g_spawn_command_line_sync(arg->rhs->str, &stdOut, &stdErr, &status, &error)) {
g_warning("Can't run '%s': %s", arg->rhs->str, error->message);
g_clear_error(&error);
res = CMD_ERROR | CMD_KEEPINPUT;
} else {
/* the commands success depends not on the return code of the
* called shell command, so we know the result already here */
res = CMD_SUCCESS | CMD_KEEPINPUT;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
vb_echo(c, MSG_NORMAL, FALSE, "%s", stdOut);
} else {
vb_echo(c, MSG_ERROR, TRUE, "[%d] %s", WEXITSTATUS(status), stdErr);
}
g_free(stdOut);
g_free(stdErr);
}
return res;
}
static VbCmdResult ex_shellex(Client *c, const ExArg *arg)
{
int status;
char *stdOut = NULL, *stdErr = NULL, *ex_line = NULL;
VbCmdResult res;
GError *error = NULL;
if (!*arg->rhs->str) {
return CMD_ERROR;
}
update_current_selection_env_var(c);
if (!g_spawn_command_line_sync(arg->rhs->str, &stdOut, &stdErr, &status, &error)) {
g_warning("Can't run '%s': %s", arg->rhs->str, error->message);
g_clear_error(&error);
res = CMD_ERROR | CMD_KEEPINPUT;
} else {
/* the commands success depends not on the return code of the
* called shell command, so we know the result already here */
res = CMD_SUCCESS;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
/* Read line by line stdOut and execute it */
ex_line = strtok(stdOut, "\n");
while (ex_line) {
res = ex_run_string(c, ex_line, false);
if (!(res & CMD_SUCCESS)) {
break;
}
ex_line = strtok(NULL, "\n");
}
} else {
vb_echo(c, MSG_ERROR, TRUE, "[%d] %s", WEXITSTATUS(status), stdErr);
}
g_free(stdOut);
g_free(stdErr);
return res;
}
static VbCmdResult ex_handlers(Client *c, const ExArg *arg)
{
char *p;
gboolean res = FALSE;
switch (arg->code) {
case EX_HANDADD:
if (arg->rhs->len && (p = strchr(arg->rhs->str, '='))) {
*p++ = '\0';
res = handler_add(c->handler, arg->rhs->str, p);
}
break;
case EX_HANDREM:
res = handler_remove(c->handler, arg->rhs->str);
break;
default:
break;
}
return res ? CMD_SUCCESS : CMD_ERROR;
}
static VbCmdResult ex_shortcut(Client *c, const ExArg *arg)
{
gchar *uri;
gboolean success = FALSE;
if (!c) {
return CMD_ERROR;
}
if (!arg->rhs || !arg->rhs->str || !*arg->rhs->str) {
return CMD_ERROR;
}
switch(arg->code) {
case EX_SCA:
if ((uri = strchr(arg->rhs->str, '='))) {
*uri++ = '\0'; /* devide key and uri */
g_strstrip(arg->rhs->str);
g_strstrip(uri);
success = shortcut_add(c->config.shortcuts, arg->rhs->str, uri);
}
break;
case EX_SCR:
g_strstrip(arg->rhs->str);
success = shortcut_remove(c->config.shortcuts, arg->rhs->str);
break;
case EX_SCD:
g_strstrip(arg->rhs->str);
success = shortcut_set_default(c->config.shortcuts, arg->rhs->str);
break;
default:
break;
}
return success ? CMD_SUCCESS : CMD_ERROR;
}
static VbCmdResult ex_source(Client *c, const ExArg *arg)
{
return ex_run_file(c, arg->rhs->str);
}
static void update_current_selection_env_var(Client *c)
{
char *selection = NULL;
selection = ext_proxy_get_current_selection(c);
if (selection) {
g_setenv("VIMB_SELECTION", selection, TRUE);
g_free(selection);
} else {
g_setenv("VIMB_SELECTION", "", TRUE);
}
}
/**
* Manage the generation and stepping through completions.
* This function prepared some prefix and suffix string that are required to
* put the matched data back to inputbox, and prepares the tree list store
* model containing matched values.
*/
static gboolean complete(Client *c, short direction)
{
char *input; /* input read from inputbox */
const char *in; /* pointer to input that we move */
gboolean found = FALSE;
gboolean sort = TRUE;
GtkListStore *store;
input = vb_input_get_text(c);
/* if completion was already started move to the next/prev item */
if (c->mode->flags & FLAG_COMPLETION) {
if (excomp.current && !strcmp(input, excomp.current)) {
/* Step through the next/prev completion item. */
if (!completion_next(c, direction < 0)) {
/* If we stepped over the last/first item - put the initial content in */
completion_select(c, excomp.token);
}
g_free(input);
return TRUE;
}
/* if current input isn't the content of the completion item, stop
* completion and start it after that again */
completion_clean(c);
}
store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING);
in = (const char*)input;
if (*in == ':') {
const char *before_cmdname;
/* skip leading ':' and whitespace */
while (*in && (*in == ':' || VB_IS_SPACE(*in))) {
in++;
}
ExArg *arg = g_slice_new0(ExArg);
parse_count(&in, arg);
/* Backup the current pointer so that we can restore the input pointer
* if the command name parsing fails. */
before_cmdname = in;
/* Do ex command specific completion if the command is recognized and
* there is a space after the command and the optional '!' bang. */
if (parse_command_name(c, &in, arg) && parse_bang(&in, arg) && VB_IS_SPACE(*in)) {
const char *token;
/* Get only the last word of input string for the completion for
* bookmark tag completion. */
if (arg->code == EX_BMA) {
/* Find the end of the input and search for the next
* whitespace toward the beginning. */
token = strrchr(in, '\0');
while (token >= in && !VB_IS_SPACE(*token)) {
token--;
}
} else {
/* Use all input except of the command and it's possible bang
* itself for the completion. */
token = in;
}
/* Save the string prefix that will not be part of completion like
* the ':open ' if ':open something' is completed. This means that
* the completion will only the none prefix part of the input */
OVERWRITE_NSTRING(excomp.prefix, input, token - input + 1);
OVERWRITE_STRING(excomp.token, token + 1);
/* the token points to a space, skip this */
skip_whitespace(&token);
switch (arg->code) {
case EX_OPEN:
case EX_TABOPEN:
case EX_QPUSH:
case EX_QUNSHIFT:
sort = FALSE;
if (*token == '!') {
found = bookmark_fill_completion(store, token + 1);
} else {
found = history_fill_completion(store, HISTORY_URL, token);
}
break;
case EX_SET:
found = setting_fill_completion(c, store, token);
break;
case EX_BMA:
found = bookmark_fill_tag_completion(store, token);
break;
case EX_BMR:
sort = FALSE;
found = bookmark_fill_completion(store, token);
break;
case EX_SCR: /* Fallthrough */
case EX_SCD:
found = shortcut_fill_completion(c->config.shortcuts, store, token);
break;
case EX_HANDREM:
found = handler_fill_completion(c->handler, store, token);
break;
case EX_SAVE: /* Fallthrough */
case EX_SOURCE:
found = util_filename_fill_completion(store, token);
break;
#ifdef FEATURE_AUTOCMD
case EX_AUTOCMD:
found = autocmd_fill_event_completion(c, store, token);
break;
case EX_AUGROUP:
found = autocmd_fill_group_completion(c, store, token);
break;
#endif
default:
break;
}
} else { /* complete command names */
/* restore the 'in' pointer after try to parse command name */
in = before_cmdname;
OVERWRITE_STRING(excomp.token, in);
/* Backup the parsed data so we can access them in
* completion_select function. */
excomp.count = arg->count;
if (ex_fill_completion(store, in)) {
/* Use all the input before the command as prefix. */
OVERWRITE_NSTRING(excomp.prefix, input, in - input);
found = TRUE;
}
}
free_cmdarg(arg);
} else if (*in == '/' || *in == '?') {
if (history_fill_completion(store, HISTORY_SEARCH, in + 1)) {
OVERWRITE_STRING(excomp.token, in + 1);
OVERWRITE_NSTRING(excomp.prefix, in, 1);
found = TRUE;
sort = FALSE;
}
}
/* if the input could be parsed and the tree view could be filled */
if (sort) {
gtk_tree_sortable_set_sort_column_id(
GTK_TREE_SORTABLE(store), COMPLETION_STORE_FIRST, GTK_SORT_ASCENDING
);
}
if (found) {
completion_create(c, GTK_TREE_MODEL(store), completion_select, direction < 0);
}
g_free(input);
return TRUE;
}
/**
* Callback called from the completion if a item is selected to write the
* matched item according with previously saved prefix and command name to the
* inputbox.
*/
static void completion_select(Client *c, char *match)
{
OVERWRITE_STRING(excomp.current, NULL);
if (excomp.count) {
excomp.current = g_strdup_printf("%s%d%s", excomp.prefix, excomp.count, match);
} else {
excomp.current = g_strconcat(excomp.prefix, match, NULL);
}
vb_input_set_text(c, excomp.current);
}
static gboolean history(Client *c, gboolean prev)
{
char *input;
GList *new = NULL;
input = vb_input_get_text(c);
if (exhist.active) {
/* calculate the actual content of the inpubox from history data, if
* the theoretical content and the actual given input are different
* rewind the history to recreate it later new */
char *current = g_strconcat(exhist.prefix, (char*)exhist.active->data, NULL);
if (strcmp(input, current)) {
history_rewind();
}
g_free(current);
}
/* create the history list if the lookup is started or input was changed */
if (!exhist.active) {
int type;
char prefix[2] = {0};
const char *in = (const char*)input;
skip_whitespace(&in);
/* save the first char as prefix - this should be : / or ? */
prefix[0] = *in;
/* check which type of history we should use */
if (*in == ':') {
type = INPUT_COMMAND;
} else if (*in == '/' || *in == '?') {
/* the history does not distinguish between forward and backward
* search, so we don't need the backward search here too */
type = INPUT_SEARCH_FORWARD;
} else {
goto failed;
}
exhist.active = history_get_list(type, in + 1);
if (!exhist.active) {
goto failed;
}
OVERWRITE_STRING(exhist.prefix, prefix);
}
if (prev) {
if ((new = g_list_next(exhist.active))) {
exhist.active = new;
}
} else if ((new = g_list_previous(exhist.active))) {
exhist.active = new;
}
if (!exhist.active) {
goto failed;
}
vb_echo_force(c, MSG_NORMAL, FALSE, "%s%s", exhist.prefix, (char*)exhist.active->data);
g_free(input);
return TRUE;
failed:
g_free(input);
return FALSE;
}
static void history_rewind(void)
{
if (exhist.active) {
/* free temporary used history list */
g_list_free_full(exhist.active, (GDestroyNotify)g_free);
exhist.active = NULL;
OVERWRITE_STRING(exhist.prefix, NULL);
}
}
fanglingsu-vimb-448e7e2/src/ex.h 0000664 0000000 0000000 00000002264 15145416123 0016530 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _EX_H
#define _EX_H
#include "config.h"
#include "main.h"
void ex_enter(Client *c);
void ex_leave(Client *c);
VbResult ex_keypress(Client *c, int key);
void ex_input_changed(Client *c, const char *text);
gboolean ex_fill_completion(GtkListStore *store, const char *input);
VbCmdResult ex_run_file(Client *c, const char *filename);
VbCmdResult ex_run_string(Client *c, const char *input, gboolean enable_history);
#endif /* end of include guard: _EX_H */
fanglingsu-vimb-448e7e2/src/ext-proxy.c 0000664 0000000 0000000 00000022623 15145416123 0020067 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include "ext-proxy.h"
#include "main.h"
#include "webextension/ext-main.h"
static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer,
GIOStream *stream, GCredentials *credentials, gpointer data);
static gboolean on_new_connection(GDBusServer *server,
GDBusConnection *connection, gpointer data);
static void on_connection_close(GDBusConnection *connection, gboolean
remote_peer_vanished, GError *error, gpointer data);
static void on_proxy_created (GDBusProxy *proxy, GAsyncResult *result,
gpointer data);
static void dbus_call(Client *c, const char *method, GVariant *param,
GAsyncReadyCallback callback);
static GVariant *dbus_call_sync(Client *c, const char *method, GVariant
*param);
static void on_web_extension_page_created(GDBusConnection *connection,
const char *sender_name, const char *object_path,
const char *interface_name, const char *signal_name,
GVariant *parameters, gpointer data);
/* TODO we need potentially multiple proxies. Because a single instance of
* vimb may hold multiple clients which may use more than one webprocess and
* therefore multiple webextension instances. */
extern struct Vimb vb;
static GDBusServer *dbusserver;
/**
* Initialize the dbus proxy by watching for appearing dbus name.
*/
const char *ext_proxy_init(void)
{
char *address, *guid;
GDBusAuthObserver *observer;
GError *error = NULL;
address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir());
guid = g_dbus_generate_guid();
observer = g_dbus_auth_observer_new();
g_signal_connect(observer, "authorize-authenticated-peer",
G_CALLBACK(on_authorize_authenticated_peer), NULL);
/* Use sync call because server must be starte before the web extension
* attempt to connect */
dbusserver = g_dbus_server_new_sync(address, G_DBUS_SERVER_FLAGS_NONE,
guid, observer, NULL, &error);
if (error) {
g_warning("Failed to start web extension server on %s: %s", address, error->message);
g_error_free(error);
goto out;
}
g_signal_connect(dbusserver, "new-connection", G_CALLBACK(on_new_connection), NULL);
g_dbus_server_start(dbusserver);
out:
g_free(address);
g_free(guid);
g_object_unref(observer);
return g_dbus_server_get_client_address(dbusserver);
}
/* TODO move this to a lib or somthing that can be used from ui and web
* process together */
static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer,
GIOStream *stream, GCredentials *credentials, gpointer data)
{
gboolean authorized = FALSE;
if (credentials) {
GCredentials *own_credentials;
GError *error = NULL;
own_credentials = g_credentials_new();
if (g_credentials_is_same_user(credentials, own_credentials, &error)) {
authorized = TRUE;
} else {
g_warning("Failed to authorize web extension connection: %s", error->message);
g_error_free(error);
}
g_object_unref(own_credentials);
} else {
g_warning ("No credentials received from web extension.\n");
}
return authorized;
}
static gboolean on_new_connection(GDBusServer *server,
GDBusConnection *connection, gpointer data)
{
/* Create dbus proxy. */
g_return_val_if_fail(G_IS_DBUS_CONNECTION(connection), FALSE);
g_signal_connect(connection, "closed", G_CALLBACK(on_connection_close), NULL);
g_dbus_proxy_new(connection,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES|G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
NULL,
VB_WEBEXTENSION_OBJECT_PATH,
VB_WEBEXTENSION_INTERFACE,
NULL,
(GAsyncReadyCallback)on_proxy_created,
NULL);
return TRUE;
}
static void on_connection_close(GDBusConnection *connection, gboolean
remote_peer_vanished, GError *error, gpointer data)
{
if (error && !remote_peer_vanished) {
g_warning("Unexpected lost connection to web extension: %s", error->message);
}
}
static void on_proxy_created(GDBusProxy *new_proxy, GAsyncResult *result,
gpointer data)
{
GError *error = NULL;
GDBusProxy *proxy;
GDBusConnection *connection;
proxy = g_dbus_proxy_new_finish(result, &error);
if (!proxy) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning("Error creating web extension proxy: %s", error->message);
}
g_error_free(error);
/* TODO cancel the dbus connection - use cancelable */
return;
}
connection = g_dbus_proxy_get_connection(proxy);
g_dbus_connection_signal_subscribe(connection, NULL,
VB_WEBEXTENSION_INTERFACE, "PageCreated",
VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE,
(GDBusSignalCallback)on_web_extension_page_created, proxy,
NULL);
}
void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback)
{
if (callback) {
dbus_call(c, "EvalJs", g_variant_new("(ts)", c->page_id, js), callback);
} else {
dbus_call(c, "EvalJsNoResult", g_variant_new("(ts)", c->page_id, js), NULL);
}
}
GVariant *ext_proxy_eval_script_sync(Client *c, char *js)
{
return dbus_call_sync(c, "EvalJs", g_variant_new("(ts)", c->page_id, js));
}
/**
* Request the web extension to focus first editable element.
* Returns whether an focusable element was found or not.
*/
void ext_proxy_focus_input(Client *c)
{
dbus_call(c, "FocusInput", g_variant_new("(t)", c->page_id), NULL);
}
/**
* Send the headers string to the webextension.
*/
void ext_proxy_set_header(Client *c, const char *headers)
{
dbus_call(c, "SetHeaderSetting", g_variant_new("(s)", headers), NULL);
}
void ext_proxy_lock_input(Client *c, const char *element_id)
{
dbus_call(c, "LockInput", g_variant_new("(ts)", c->page_id, element_id), NULL);
}
void ext_proxy_unlock_input(Client *c, const char *element_id)
{
dbus_call(c, "UnlockInput", g_variant_new("(ts)", c->page_id, element_id), NULL);
}
/**
* Returns the current selection if there is one as newly allocates string.
*
* Result must be freed by caller with g_free.
*/
char *ext_proxy_get_current_selection(Client *c)
{
char *selection, *js;
gboolean success;
GVariant *jsreturn;
js = g_strdup_printf("getSelection().toString();");
jsreturn = ext_proxy_eval_script_sync(c, js);
if (jsreturn == NULL) {
g_warning("cannot get current selection: failed to evaluate js");
g_free(js);
return NULL;
}
g_variant_get(jsreturn, "(bs)", &success, &selection);
g_free(js);
if (!success) {
g_warning("can not get current selection: %s", selection);
g_free(selection);
return NULL;
}
return selection;
}
/**
* Call a dbus method.
*/
static void dbus_call(Client *c, const char *method, GVariant *param,
GAsyncReadyCallback callback)
{
/* TODO add function to queue calls until the proxy connection is
* established */
if (!c->dbusproxy) {
return;
}
g_dbus_proxy_call(c->dbusproxy, method, param, G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, c);
}
/**
* Call a dbus method syncron.
*/
static GVariant *dbus_call_sync(Client *c, const char *method, GVariant *param)
{
GVariant *result = NULL;
GError *error = NULL;
if (!c->dbusproxy) {
return NULL;
}
result = g_dbus_proxy_call_sync(c->dbusproxy, method, param,
G_DBUS_CALL_FLAGS_NONE, 500, NULL, &error);
if (error) {
g_warning("Failed dbus method %s: %s", method, error->message);
g_error_free(error);
}
return result;
}
/**
* Called when the web context created the page.
* Store the proxy in a pending list. The actual client association happens when
* the page sends a user message after document load, at which point the page ID
* is stable and we can match the proxy to the correct client.
*/
static void on_web_extension_page_created(GDBusConnection *connection,
const char *sender_name, const char *object_path,
const char *interface_name, const char *signal_name,
GVariant *parameters, gpointer data)
{
guint64 pageid;
ProxyPageId *pending;
g_variant_get(parameters, "(t)", &pageid);
PRINT_DEBUG("PageCreated signal received: page_id=%" G_GUINT64_FORMAT ", proxy=%p",
pageid, data);
/* Create a pending proxy entry */
pending = g_slice_new(ProxyPageId);
pending->proxy = (GDBusProxy *)data;
pending->pageid = pageid;
/* Add to pending list - will be claimed when client receives user message */
vb.pending_proxies = g_slist_prepend(vb.pending_proxies, pending);
}
fanglingsu-vimb-448e7e2/src/ext-proxy.h 0000664 0000000 0000000 00000002720 15145416123 0020070 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _EXT_PROXY_H
#define _EXT_PROXY_H
#include "main.h"
#include
/* Structure to hold pending proxy-page associations before they are claimed by clients */
typedef struct {
GDBusProxy *proxy;
guint64 pageid;
} ProxyPageId;
const char *ext_proxy_init(void);
void ext_proxy_eval_script(Client *c, char *js, GAsyncReadyCallback callback);
GVariant *ext_proxy_eval_script_sync(Client *c, char *js);
void ext_proxy_focus_input(Client *c);
void ext_proxy_set_header(Client *c, const char *headers);
void ext_proxy_lock_input(Client *c, const char *element_id);
void ext_proxy_unlock_input(Client *c, const char *element_id);
char *ext_proxy_get_current_selection(Client *c);
#endif /* end of include guard: _EXT_PROXY_H */
fanglingsu-vimb-448e7e2/src/file-storage.c 0000664 0000000 0000000 00000010006 15145416123 0020461 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2019 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include
#include "file-storage.h"
struct filestorage {
char *file_path;
gboolean readonly;
GString *str;
};
/**
* Create new file storage instance for given directory and filename. If the
* file does not exists in the directory and give mode is not 0 the file is
* created with the given mode.
*
* The returned FileStorage must be freed by file_storage_free().
*
* @dir: Directory in which the file is searched.
* @filename: Filename to built the absolute path with.
* @mode: Mode (file permission as chmod(2)) used for the file when
* creating it. If 0 the file is not created and the storage is
* used in read only mode - no data written to the file.
*/
FileStorage *file_storage_new(const char *dir, const char *filename, gboolean readonly)
{
FileStorage *storage;
storage = g_slice_new(FileStorage);
storage->readonly = readonly;
storage->file_path = g_build_filename(dir, filename, NULL);
/* Use gstring as storage in case when the file is used read only. */
if (storage->readonly) {
storage->str = g_string_new(NULL);
} else {
storage->str = NULL;
}
return storage;
}
/**
* Free memory for given file storage.
*/
void file_storage_free(FileStorage *storage)
{
if (storage) {
g_free(storage->file_path);
if (storage->str) {
g_string_free(storage->str, TRUE);
}
g_slice_free(FileStorage, storage);
}
}
/**
* Append new data to file.
*
* @fileStorage: FileStorage to append the data to
* @format: Format string used to process va_list
*/
gboolean file_storage_append(FileStorage *storage, const char *format, ...)
{
FILE *f;
va_list args;
g_assert(storage);
/* Write data to in memory list in case the file storage is read only. */
if (storage->readonly) {
va_start(args, format);
g_string_append_vprintf(storage->str, format, args);
va_end(args);
return TRUE;
}
if ((f = fopen(storage->file_path, "a+"))) {
flock(fileno(f), LOCK_EX);
va_start(args, format);
vfprintf(f, format, args);
va_end(args);
flock(fileno(f), LOCK_UN);
fclose(f);
return TRUE;
}
return FALSE;
}
/**
* Retrieves all the lines from file storage.
*
* The result have to be freed by g_strfreev().
*/
char **file_storage_get_lines(FileStorage *storage)
{
char *fullcontent = NULL;
char *content = NULL;
char **lines = NULL;
g_file_get_contents(storage->file_path, &content, NULL, NULL);
if (storage->str && storage->str->len) {
if (content) {
fullcontent = g_strconcat(content, storage->str->str, NULL);
lines = g_strsplit(fullcontent, "\n", -1);
g_free(fullcontent);
} else {
lines = g_strsplit(storage->str->str, "\n", -1);
}
} else {
lines = g_strsplit(content ? content : "", "\n", -1);
}
if (content) {
g_free(content);
}
return lines;
}
const char *file_storage_get_path(FileStorage *storage)
{
return storage->file_path;
}
gboolean file_storage_is_readonly(FileStorage *storage)
{
return storage->readonly;
}
fanglingsu-vimb-448e7e2/src/file-storage.h 0000664 0000000 0000000 00000002365 15145416123 0020477 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2019 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _FILE_STORAGE_H
#define _FILE_STORAGE_H
#include
typedef struct filestorage FileStorage;
FileStorage *file_storage_new(const char *dir, const char *filename, int mode);
void file_storage_free(FileStorage *storage);
gboolean file_storage_append(FileStorage *storage, const char *format, ...);
char **file_storage_get_lines(FileStorage *storage);
const char *file_storage_get_path(FileStorage *storage);
gboolean file_storage_is_readonly(FileStorage *storage);
#endif /* end of include guard: _FILE_STORAGE_H */
fanglingsu-vimb-448e7e2/src/handler.c 0000664 0000000 0000000 00000005006 15145416123 0017521 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include "main.h"
#include "handler.h"
#include "util.h"
extern struct Vimb vb;
struct handler {
GHashTable *table; /* holds the protocol handlers */
};
static char *handler_lookup(Handler *h, const char *uri);
Handler *handler_new(void)
{
Handler *h = g_new(Handler, 1);
h->table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
return h;
}
void handler_free(Handler *h)
{
if (h->table) {
g_hash_table_destroy(h->table);
h->table = NULL;
}
g_free(h);
}
gboolean handler_add(Handler *h, const char *key, const char *cmd)
{
g_hash_table_insert(h->table, g_strdup(key), g_strdup(cmd));
return TRUE;
}
gboolean handler_remove(Handler *h, const char *key)
{
return g_hash_table_remove(h->table, key);
}
gboolean handler_handle_uri(Handler *h, const char *uri)
{
char *handler, *cmd;
GError *error = NULL;
gboolean res;
if (!(handler = handler_lookup(h, uri))) {
return FALSE;
}
cmd = g_strdup_printf(handler, uri);
if (!g_spawn_command_line_async(cmd, &error)) {
g_warning("Can't run '%s': %s", cmd, error->message);
g_clear_error(&error);
res = FALSE;
} else {
res = TRUE;
}
g_free(cmd);
return res;
}
gboolean handler_fill_completion(Handler *h, GtkListStore *store, const char *input)
{
GList *src = g_hash_table_get_keys(h->table);
gboolean found = util_fill_completion(store, input, src);
g_list_free(src);
return found;
}
static char *handler_lookup(Handler *h, const char *uri)
{
char *p, *schema, *handler = NULL;
if ((p = strchr(uri, ':'))) {
schema = g_strndup(uri, p - uri);
handler = g_hash_table_lookup(h->table, schema);
g_free(schema);
}
return handler;
}
fanglingsu-vimb-448e7e2/src/handler.h 0000664 0000000 0000000 00000002237 15145416123 0017531 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _HANDLERS_H
#define _HANDLERS_H
typedef struct handler Handler;
Handler *handler_new();
void handler_free(Handler *h);
gboolean handler_add(Handler *h, const char *key, const char *cmd);
gboolean handler_remove(Handler *h, const char *key);
gboolean handler_handle_uri(Handler *h, const char *uri);
gboolean handler_fill_completion(Handler *h, GtkListStore *store, const char *input);
#endif /* end of include guard: _HANDLERS_H */
fanglingsu-vimb-448e7e2/src/hints.c 0000664 0000000 0000000 00000034132 15145416123 0017233 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "config.h"
#include
#include
#include
#include
#include
#include "hints.h"
#include "main.h"
#include "ascii.h"
#include "command.h"
#include "input.h"
#include "map.h"
#include "normal.h"
#include "ext-proxy.h"
static struct {
char mode; /* mode identifying char - that last char of the hint prompt */
int promptlen; /* length of the hint prompt chars 2 or 3 */
gboolean gmode; /* indicate if the hints 'g' mode is used */
/* holds the setting if JavaScript can open windows automatically that we
* have to change to open windows via hinting */
gboolean allow_open_win;
gboolean allow_javascript;
guint timeout_id;
} hints;
extern struct Vimb vb;
static gboolean call_hints_function(Client *c, const char *func, const char* args,
gboolean sync);
static void on_hint_function_finished(GDBusProxy *proxy, GAsyncResult *result,
Client *c);
static gboolean hint_function_check_result(Client *c, GVariant *return_value);
static void fire_timeout(Client *c, gboolean on);
static gboolean fire_cb(gpointer data);
VbResult hints_keypress(Client *c, int key)
{
if (key == KEY_CR) {
hints_fire(c);
return RESULT_COMPLETE;
} else if (key == CTRL('H')) { /* backspace */
fire_timeout(c, FALSE);
if (call_hints_function(c, "update", "null", TRUE)) {
return RESULT_COMPLETE;
}
} else if (key == KEY_TAB) {
fire_timeout(c, FALSE);
hints_focus_next(c, FALSE);
return RESULT_COMPLETE;
} else if (key == KEY_SHIFT_TAB) {
fire_timeout(c, FALSE);
hints_focus_next(c, TRUE);
return RESULT_COMPLETE;
} else if (key == CTRL('D') || key == CTRL('F') || key == CTRL('B') || key == CTRL('U')) {
return normal_keypress(c, key);
} else if (key == CTRL('J') || key == CTRL('K')) {
return normal_keypress(c, UNCTRL(key));
} else {
fire_timeout(c, TRUE);
/* try to handle the key by the javascript */
if (call_hints_function(c, "update", (char[]){'"', key, '"', '\0'}, TRUE)) {
return RESULT_COMPLETE;
}
}
fire_timeout(c, FALSE);
return RESULT_ERROR;
}
void hints_clear(Client *c)
{
if (c->mode->flags & FLAG_HINTING) {
c->mode->flags &= ~FLAG_HINTING;
vb_input_set_text(c, "");
/* Run this sync else we would disable JavaScript before the hint is
* fired. */
call_hints_function(c, "clear", "true", TRUE);
/* if open window was not allowed for JavaScript, restore this */
WebKitSettings *setting = webkit_web_view_get_settings(c->webview);
if (!hints.allow_open_win) {
g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", hints.allow_open_win, NULL);
}
if (!hints.allow_javascript) {
g_object_set(G_OBJECT(setting), "enable-javascript", hints.allow_javascript, NULL);
}
}
}
void hints_create(Client *c, const char *input)
{
char *jsargs;
/* check if the input contains a valid hinting prompt */
if (!hints_parse_prompt(input, &hints.mode, &hints.gmode)) {
/* if input is not valid, clear possible previous hint mode */
if (c->mode->flags & FLAG_HINTING) {
vb_enter(c, 'n');
}
return;
}
if (!(c->mode->flags & FLAG_HINTING)) {
c->mode->flags |= FLAG_HINTING;
WebKitSettings *setting = webkit_web_view_get_settings(c->webview);
/* before we enable JavaScript to open new windows, we save the actual
* value to be able restore it after hints where fired */
g_object_get(G_OBJECT(setting),
"javascript-can-open-windows-automatically", &(hints.allow_open_win),
"enable-javascript", &(hints.allow_javascript),
NULL);
/* if window open is already allowed there's no need to allow it again */
if (!hints.allow_open_win) {
g_object_set(G_OBJECT(setting), "javascript-can-open-windows-automatically", TRUE, NULL);
}
/* TODO This might be a security issue to toggle JavaScript
* temporarily on. */
/* This is a hack to allow window.setTimeout() and scroll observers in
* hinting script which does not work when JavaScript is disabled. */
if (!hints.allow_javascript) {
g_object_set(G_OBJECT(setting), "enable-javascript", TRUE, NULL);
}
hints.promptlen = hints.gmode ? 3 : 2;
jsargs = g_strdup_printf("'%s', %s, %d, '%s', %s, %s",
(char[]){hints.mode, '\0'},
hints.gmode ? "true" : "false",
MAXIMUM_HINTS,
GET_CHAR(c, "hint-keys"),
GET_BOOL(c, "hint-follow-last") ? "true" : "false",
GET_BOOL(c, "hint-keys-same-length") ? "true" : "false"
);
call_hints_function(c, "init", jsargs, FALSE);
g_free(jsargs);
/* if hinting is started there won't be any additional filter given and
* we can go out of this function */
return;
}
if (GET_BOOL(c, "hint-match-element")) {
jsargs = g_strdup_printf("'%s'", *(input + hints.promptlen) ? input + hints.promptlen : "");
call_hints_function(c, "filter", jsargs, FALSE);
g_free(jsargs);
}
}
void hints_focus_next(Client *c, const gboolean back)
{
call_hints_function(c, "focus", back ? "true" : "false", FALSE);
}
void hints_fire(Client *c)
{
call_hints_function(c, "fire", "", FALSE);
}
void hints_follow_link(Client *c, const gboolean back, int count)
{
/* TODO implement outside of hints.c */
/* We would previously "piggyback" on hints.js for the "js" part of this feature
* but this would actually be more elegant in its own JS file. This has nothing
* to do with hints.
*/
/* char *json = g_strdup_printf( */
/* "[%s]", */
/* back ? vb.config.prevpattern : vb.config.nextpattern */
/* ); */
/* JSValueRef arguments[] = { */
/* js_string_to_ref(hints.ctx, back ? "prev" : "next"), */
/* js_object_to_ref(hints.ctx, json), */
/* JSValueMakeNumber(hints.ctx, count) */
/* }; */
/* g_free(json); */
/* call_hints_function(c, "followLink", 3, arguments); */
}
void hints_increment_uri(Client *c, int count)
{
char *jsargs;
jsargs = g_strdup_printf("%d", count);
call_hints_function(c, "incrementUri", jsargs, FALSE);
g_free(jsargs);
}
/**
* Checks if the given hint prompt belong to a known and valid hints mode and
* parses the mode and is_gmode into given pointers.
*
* The given prompt sting may also contain additional chars after the prompt.
*
* @prompt: String to be parsed as prompt. The Prompt can be followed by
* additional characters.
* @mode: Pointer to char that will be filled with mode char if prompt was
* valid and given pointer is not NULL.
* @is_gmode: Pointer to gboolean to be filled with the flag to indicate gmode
* hinting.
*/
gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode)
{
gboolean res;
char pmode = '\0';
#ifdef FEATURE_QUEUE
static char *modes = "eiIkoOpPstTxyY";
static char *g_modes = "IopPstyY";
#else
static char *modes = "eiIkoOstTxyY";
static char *g_modes = "IostyY";
#endif
if (!prompt) {
return FALSE;
}
/* get the mode identifying char from prompt */
if (*prompt == ';') {
pmode = prompt[1];
} else if (*prompt == 'g' && strlen(prompt) >= 3) {
/* get mode for g;X hint modes */
pmode = prompt[2];
}
/* no mode found in prompt */
if (!pmode) {
return FALSE;
}
res = *prompt == 'g'
? strchr(g_modes, pmode) != NULL
: strchr(modes, pmode) != NULL;
/* fill pointer only if the prompt was valid */
if (res) {
if (mode != NULL) {
*mode = pmode;
}
if (is_gmode != NULL) {
*is_gmode = *prompt == 'g';
}
}
return res;
}
static gboolean call_hints_function(Client *c, const char *func, const char* args,
gboolean sync)
{
char *jscode;
/* Default value is only return in case of async call. */
gboolean success = TRUE;
jscode = g_strdup_printf("hints.%s(%s);", func, args);
if (sync) {
GVariant *result;
result = ext_proxy_eval_script_sync(c, jscode);
success = hint_function_check_result(c, result);
} else {
ext_proxy_eval_script(c, jscode, (GAsyncReadyCallback)on_hint_function_finished);
}
g_free(jscode);
return success;
}
static void on_hint_function_finished(GDBusProxy *proxy, GAsyncResult *result,
Client *c)
{
GVariant *return_value;
return_value = g_dbus_proxy_call_finish(proxy, result, NULL);
hint_function_check_result(c, return_value);
}
static gboolean hint_function_check_result(Client *c, GVariant *return_value)
{
gboolean success = FALSE;
char *value = NULL;
if (!return_value) {
goto error;
}
g_variant_get(return_value, "(bs)", &success, &value);
if (!success || !strncmp(value, "ERROR:", 6)) {
goto error;
}
if (!strncmp(value, "OVER:", 5)) {
/* If focused elements src is given fire mouse-target-changed signal
* to show its uri in the statusbar. */
if (*(value + 7)) {
/* We get OVER:{I,A}:element-url so we use byte 6 to check for the
* hinted element type image I or link A. */
if (*(value + 5) == 'I') {
vb_statusbar_show_hover_url(c, LINK_TYPE_IMAGE, value + 7);
} else {
vb_statusbar_show_hover_url(c, LINK_TYPE_LINK, value + 7);
}
}
} else if (!strncmp(value, "DONE:", 5)) {
fire_timeout(c, FALSE);
/* Change to normal mode only if we are currently in command mode and
* we are not in g-mode hinting. This is required to not switch to
* normal mode when the hinting triggered a click that set focus on
* editable element that lead vimb to switch to input mode. */
if (!hints.gmode && c->mode->id == 'c') {
vb_enter(c, 'n');
}
/* If open in new window hinting is use, set a flag on the mode after
* changing to normal mode. This is used in on_webview_decide_policy
* to enforce opening into new instance for the next navigation
* action. */
if (hints.mode == 't') {
c->mode->flags |= FLAG_NEW_WIN;
}
} else if (!strncmp(value, "INSERT:", 7)) {
fire_timeout(c, FALSE);
vb_enter(c, 'i');
if (hints.mode == 'e') {
input_open_editor(c);
}
} else if (!strncmp(value, "DATA:", 5)) {
fire_timeout(c, FALSE);
/* switch first to normal mode - else we would clear the inputbox
* on switching mode also if we want to show yanked data */
if (!hints.gmode) {
vb_enter(c, 'n');
}
char *v = (value + 5);
Arg a = {0};
/* put the hinted value into register "; */
vb_register_add(c, ';', v);
switch (hints.mode) {
/* used if images should be opened */
case 'i':
case 'I':
a.s = v;
a.i = (hints.mode == 'I') ? TARGET_NEW : TARGET_CURRENT;
vb_load_uri(c, &a);
break;
case 'O':
case 'T':
vb_echo(c, MSG_NORMAL, FALSE, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v);
if (!hints.gmode) {
vb_enter(c, 'c');
}
break;
case 's':
a.s = v;
a.i = COMMAND_SAVE_URI;
command_save(c, &a);
break;
case 'x':
map_handle_string(c, GET_CHAR(c, "x-hint-command"), TRUE);
break;
case 'y':
case 'Y':
a.i = COMMAND_YANK_ARG;
a.s = v;
command_yank(c, &a, c->state.current_register);
break;
#ifdef FEATURE_QUEUE
case 'p':
case 'P':
a.s = v;
a.i = (hints.mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH;
command_queue(c, &a);
break;
#endif
}
}
return TRUE;
error:
vb_statusbar_show_hover_url(c, LINK_TYPE_NONE, NULL);
return FALSE;
}
static void fire_timeout(Client *c, gboolean on)
{
int millis;
/* remove possible timeout function */
if (hints.timeout_id) {
g_source_remove(hints.timeout_id);
hints.timeout_id = 0;
}
if (on) {
millis = GET_INT(c, "hint-timeout");
if (millis) {
hints.timeout_id = g_timeout_add(millis, (GSourceFunc)fire_cb, c);
}
}
}
static gboolean fire_cb(gpointer data)
{
hints_fire(data);
/* remove timeout id for the timeout that is removed by return value of
* false automatic */
hints.timeout_id = 0;
return FALSE;
}
fanglingsu-vimb-448e7e2/src/hints.h 0000664 0000000 0000000 00000002322 15145416123 0017234 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _HINTS_H
#define _HINTS_H
#include "main.h"
VbResult hints_keypress(Client *c, int key);
void hints_create(Client *c, const char *input);
void hints_fire(Client *c);
void hints_follow_link(Client *c, gboolean back, int count);
void hints_increment_uri(Client *c, int count);
gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode);
void hints_clear(Client *c);
void hints_focus_next(Client *c, const gboolean back);
#endif /* end of include guard: _HINTS_H */
fanglingsu-vimb-448e7e2/src/history.c 0000664 0000000 0000000 00000016722 15145416123 0017614 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include "ascii.h"
#include "completion.h"
#include "config.h"
#include "history.h"
#include "main.h"
#include "util.h"
#include "file-storage.h"
#define HIST_STORAGE(t) (vb.storage[storage_map[t]])
typedef struct {
char *first;
char *second;
} History;
static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen);
static void free_history(History *item);
static History *line_to_history(const char *uri, const char *title);
static GList *load(FileStorage *s);
static void write_to_file(GList *list, const char *file);
/* map history types to files */
static const int storage_map[HISTORY_LAST] = {
STORAGE_COMMAND,
STORAGE_SEARCH,
STORAGE_HISTORY
};
extern struct Vimb vb;
/**
* Write a new history entry to the end of history file.
*/
void history_add(Client *c, HistoryType type, const char *value, const char *additional)
{
FileStorage *s;
/* Don't write a history entry if the history max size is set to 0. */
if (!vb.config.history_max) {
return;
}
s = HIST_STORAGE(type);
if (additional) {
file_storage_append(s, "%s\t%s\n", value, additional);
} else {
file_storage_append(s, "%s\n", value);
}
}
/**
* Makes all history items unique and force them to fit the maximum history
* size and writes all entries of the different history types to file.
*/
void history_cleanup(void)
{
FileStorage *s;
GList *list;
/* don't cleanup the history file if history max size is 0 */
if (!vb.config.history_max) {
return;
}
for (HistoryType i = HISTORY_FIRST; i < HISTORY_LAST; i++) {
s = HIST_STORAGE(i);
if (!file_storage_is_readonly(s)) {
list = load(s);
write_to_file(list, file_storage_get_path(s));
g_list_free_full(list, (GDestroyNotify)free_history);
}
}
}
gboolean history_fill_completion(GtkListStore *store, HistoryType type, const char *input)
{
char **parts;
unsigned int len;
gboolean found = FALSE;
GList *src = NULL;
GtkTreeIter iter;
History *item;
src = load(HIST_STORAGE(type));
src = g_list_reverse(src);
if (!input || !*input) {
/* without any tags return all items */
for (GList *l = src; l; l = l->next) {
item = l->data;
gtk_list_store_append(store, &iter);
gtk_list_store_set(
store, &iter,
COMPLETION_STORE_FIRST, item->first,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND, item->second,
#endif
-1
);
found = TRUE;
}
} else if (HISTORY_URL == type) {
parts = g_strsplit(input, " ", 0);
len = g_strv_length(parts);
for (GList *l = src; l; l = l->next) {
item = l->data;
if (history_item_contains_all_tags(item, parts, len)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(
store, &iter,
COMPLETION_STORE_FIRST, item->first,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND, item->second,
#endif
-1
);
found = TRUE;
}
}
g_strfreev(parts);
} else {
for (GList *l = src; l; l = l->next) {
item = l->data;
if (g_str_has_prefix(item->first, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(
store, &iter,
COMPLETION_STORE_FIRST, item->first,
#ifdef FEATURE_TITLE_IN_COMPLETION
COMPLETION_STORE_SECOND, item->second,
#endif
-1
);
found = TRUE;
}
}
}
g_list_free_full(src, (GDestroyNotify)free_history);
return found;
}
/**
* Retrieves the list of matching history items.
* The list must be freed.
*/
GList *history_get_list(VbInputType type, const char *query)
{
GList *result = NULL, *src = NULL;
switch (type) {
case INPUT_COMMAND:
src = load(HIST_STORAGE(HISTORY_COMMAND));
break;
case INPUT_SEARCH_FORWARD:
case INPUT_SEARCH_BACKWARD:
src = load(HIST_STORAGE(HISTORY_SEARCH));
break;
default:
return NULL;
}
/* generate new history list with the matching items */
for (GList *l = src; l; l = l->next) {
History *item = l->data;
if (g_str_has_prefix(item->first, query)) {
result = g_list_prepend(result, g_strdup(item->first));
}
}
g_list_free_full(src, (GDestroyNotify)free_history);
/* Prepend the original query as own item like done in vim to have the
* original input string in input box if we step before the first real
* item. */
result = g_list_prepend(result, g_strdup(query));
return result;
}
/**
* Checks if the given array of tags are all found in history item.
*/
static gboolean history_item_contains_all_tags(History *item, char **query, guint qlen)
{
unsigned int i;
if (!qlen) {
return TRUE;
}
/* iterate over all query parts */
for (i = 0; i < qlen; i++) {
if (!(util_strcasestr(item->first, query[i])
|| (item->second && util_strcasestr(item->second, query[i])))
) {
return FALSE;
}
}
return TRUE;
}
static void free_history(History *item)
{
g_free(item->first);
g_free(item->second);
g_slice_free(History, item);
}
static History *line_to_history(const char *uri, const char *title)
{
History *item = g_slice_new0(History);
item->first = g_strdup(uri);
item->second = g_strdup(title);
return item;
}
/**
* Loads history items form file but eliminate duplicates in FIFO order.
*
* Returned list must be freed with (GDestroyNotify)free_history.
*/
static GList *load(FileStorage *s)
{
return util_strv_to_unique_list(
file_storage_get_lines(s),
(Util_Content_Func)line_to_history,
vb.config.history_max
);
}
/**
* Loads the entries from file, make them unique and write them back to file.
*/
static void write_to_file(GList *list, const char *file)
{
FILE *f;
if ((f = fopen(file, "w"))) {
flock(fileno(f), LOCK_EX);
/* overwrite the history file with new unique history items */
for (GList *link = list; link; link = link->next) {
History *item = link->data;
if (item->second) {
fprintf(f, "%s\t%s\n", item->first, item->second);
} else {
fprintf(f, "%s\n", item->first);
}
}
flock(fileno(f), LOCK_UN);
fclose(f);
}
}
fanglingsu-vimb-448e7e2/src/history.h 0000664 0000000 0000000 00000002366 15145416123 0017620 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _HISTORY_H
#define _HISTORY_H
#include
#include "main.h"
typedef enum {
HISTORY_FIRST = 0,
HISTORY_COMMAND = 0,
HISTORY_SEARCH,
HISTORY_URL,
HISTORY_LAST
} HistoryType;
void history_add(Client *c, HistoryType type, const char *value, const char *additional);
void history_cleanup(void);
gboolean history_fill_completion(GtkListStore *store, HistoryType type, const char *input);
GList *history_get_list(VbInputType type, const char *query);
#endif /* end of include guard: _HISTORY_H */
fanglingsu-vimb-448e7e2/src/input.c 0000664 0000000 0000000 00000013141 15145416123 0017242 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include "ascii.h"
#include "command.h"
#include "config.h"
#include "input.h"
#include "main.h"
#include "normal.h"
#include "util.h"
#include "scripts/scripts.h"
#include "ext-proxy.h"
typedef struct {
char *element_id;
unsigned long element_map_key;
} ElementEditorData;
static void input_editor_formfiller(const char *text, Client *c, gpointer data);
/**
* Function called when vimb enters the input mode.
*/
void input_enter(Client *c)
{
/* switch focus first to make sure we can write to the inputbox without
* disturbing the user */
gtk_widget_grab_focus(GTK_WIDGET(c->webview));
vb_modelabel_update(c, "-- INPUT --");
ext_proxy_eval_script(c, "var vimb_input_mode_element = document.activeElement;", NULL);
}
/**
* Called when the input mode is left.
*/
void input_leave(Client *c)
{
ext_proxy_eval_script(c, "vimb_input_mode_element.blur();", NULL);
vb_modelabel_update(c, "");
}
/**
* Handles the keypress events from webview and inputbox.
*/
VbResult input_keypress(Client *c, int key)
{
static gboolean ctrlo = FALSE;
if (ctrlo) {
/* if we are in ctrl-O mode perform the next keys as normal mode
* commands until the command is complete or error */
VbResult res = normal_keypress(c, key);
if (res != RESULT_MORE) {
ctrlo = FALSE;
/* Don't overwrite the mode label in case we landed in another
* mode. This might occurre by CTRL-0 CTRL-Z or after running ex
* command, where we mainly end up in normal mode. */
if (c->mode->id == 'i') {
/* reenter the input mode */
input_enter(c);
}
}
return res;
}
switch (key) {
case CTRL('['): /* esc */
vb_enter(c, 'n');
return RESULT_COMPLETE;
case CTRL('O'):
/* enter CTRL-0 mode to execute next command in normal mode */
ctrlo = TRUE;
c->mode->flags |= FLAG_NOMAP;
vb_modelabel_update(c, "-- (input) --");
return RESULT_MORE;
case CTRL('T'):
return input_open_editor(c);
case CTRL('Z'):
vb_enter(c, 'p');
return RESULT_COMPLETE;
}
c->state.processed_key = FALSE;
return RESULT_ERROR;
}
VbResult input_open_editor(Client *c)
{
static unsigned long element_map_key = 0;
char *element_id = NULL;
char *text = NULL, *id = NULL;
gboolean success;
GVariant *jsreturn;
GVariant *idreturn;
ElementEditorData *data = NULL;
g_assert(c);
/* get the selected input element */
jsreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.value");
g_variant_get(jsreturn, "(bs)", &success, &text);
if (!success || !text) {
return RESULT_ERROR;
}
idreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.id");
g_variant_get(idreturn, "(bs)", &success, &id);
/* Special case: the input element does not have an id assigned to it */
if (!success || !*id) {
char *js_command = g_strdup_printf(JS_SET_EDITOR_MAP_ELEMENT, ++element_map_key);
ext_proxy_eval_script(c, js_command, NULL);
g_free(js_command);
} else {
element_id = g_strdup(id);
}
data = g_slice_new0(ElementEditorData);
data->element_id = element_id;
data->element_map_key = element_map_key;
if (command_spawn_editor(c, &((Arg){0, text}), input_editor_formfiller, data)) {
/* disable the active element */
ext_proxy_lock_input(c, element_id);
return RESULT_COMPLETE;
}
g_free(element_id);
g_slice_free(ElementEditorData, data);
return RESULT_ERROR;
}
static void input_editor_formfiller(const char *text, Client *c, gpointer data)
{
char *escaped;
char *jscode;
char *jscode_enable;
ElementEditorData *eed = (ElementEditorData *)data;
if (text) {
escaped = util_strescape(text, NULL);
/* put the text back into the element */
if (eed->element_id && strlen(eed->element_id) > 0) {
jscode = g_strdup_printf("document.getElementById(\"%s\").value=\"%s\"", eed->element_id, escaped);
} else {
jscode = g_strdup_printf("vimb_editor_map.get(\"%lu\").value=\"%s\"", eed->element_map_key, escaped);
}
ext_proxy_eval_script(c, jscode, NULL);
g_free(jscode);
g_free(escaped);
}
if (eed->element_id && strlen(eed->element_id) > 0) {
ext_proxy_unlock_input(c, eed->element_id);
} else {
jscode_enable = g_strdup_printf(JS_FOCUS_EDITOR_MAP_ELEMENT,
eed->element_map_key, eed->element_map_key);
ext_proxy_eval_script(c, jscode_enable, NULL);
g_free(jscode_enable);
}
g_free(eed->element_id);
g_slice_free(ElementEditorData, eed);
}
fanglingsu-vimb-448e7e2/src/input.h 0000664 0000000 0000000 00000001750 15145416123 0017252 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _INPUT_H
#define _INPUT_H
#include "config.h"
#include "main.h"
void input_enter(Client *c);
void input_leave(Client *c);
VbResult input_keypress(Client *c, int key);
VbResult input_open_editor(Client *c);
#endif /* end of include guard: _INPUT_H */
fanglingsu-vimb-448e7e2/src/main.c 0000664 0000000 0000000 00000236336 15145416123 0017044 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "config.h"
#include
#include
#ifndef FEATURE_NO_XEMBED
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include "../version.h"
#include "ascii.h"
#include "command.h"
#include "completion.h"
#include "ex.h"
#include "ext-proxy.h"
#include "handler.h"
#include "history.h"
#include "input.h"
#include "main.h"
#include "map.h"
#include "normal.h"
#include "setting.h"
#include "shortcut.h"
#include "util.h"
#include "autocmd.h"
#include "file-storage.h"
#include "context-menu.h"
#include "webextension/ext-main.h"
static void client_destroy(Client *c);
static Client *client_new(WebKitWebView *webview);
static void client_show(WebKitWebView *webview, Client *c);
static GtkWidget *create_window(Client *c);
static gboolean input_clear(Client *c);
static void input_print(Client *c, MessageType type, gboolean hide,
const char *message);
static gboolean is_plausible_uri(const char *path);
static void marks_clear(Client *c);
static void mode_free(Mode *mode);
static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data);
static void on_webctx_download_started(WebKitWebContext *webctx,
WebKitDownload *download, Client *c);
static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data);
static gboolean on_webdownload_decide_destination(WebKitDownload *download,
gchar *suggested_filename, Client *c);
static void on_webdownload_response_received(WebKitDownload *download,
GParamSpec *ps, Client *c);
static void spawn_download_command(Client *c, WebKitURIResponse *response);
static void on_webdownload_failed(WebKitDownload *download,
GError *error, Client *c);
static void on_webdownload_finished(WebKitDownload *download, Client *c);
static void on_webdownload_received_data(WebKitDownload *download,
guint64 data_length, Client *c);
static void on_webview_close(WebKitWebView *webview, Client *c);
static WebKitWebView *on_webview_create(WebKitWebView *webview,
WebKitNavigationAction *navact, Client *c);
static gboolean on_webview_decide_policy(WebKitWebView *webview,
WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c);
static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec);
static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec);
static void decide_response(Client *c, WebKitPolicyDecision *dec);
static void on_webview_load_changed(WebKitWebView *webview,
WebKitLoadEvent event, Client *c);
static void on_webview_mouse_target_changed(WebKitWebView *webview,
WebKitHitTestResult *result, guint modifiers, Client *c);
static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
GParamSpec *spec, Client *c);
static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec,
Client *c);
static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec,
Client *c);
static void on_webview_ready_to_show(WebKitWebView *webview, Client *c);
static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c);
static gboolean on_webview_authenticate(WebKitWebView *webview,
WebKitAuthenticationRequest *request, Client *c);
static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c);
static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c);
static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c);
static void on_window_destroy(GtkWidget *window, Client *c);
static gboolean quit(Client *c);
static void read_from_stdin(Client *c);
static void register_cleanup(Client *c);
static void update_title(Client *c);
static void update_urlbar(Client *c);
static void set_statusbar_style(Client *c, StatusType type);
static void set_title(Client *c, const char *title);
static void spawn_new_instance(const char *uri);
#ifdef FREE_ON_QUIT
static void vimb_cleanup(void);
#endif
static void vimb_setup(void);
static WebKitWebView *webview_new(Client *c, WebKitWebView *webview);
static void on_found_text(WebKitFindController *finder, guint count, Client *c);
static void on_failed_to_find_text(WebKitFindController *finder, Client *c);
static gboolean on_user_message_received(WebKitWebView *webview, WebKitUserMessage *message, Client *c);
static void on_vertical_scroll(GDBusConnection *connection,
const char *sender_name, const char *object_path,
const char *interface_name, const char *signal_name,
GVariant *parameters, Client* c);
static gboolean on_permission_request(WebKitWebView *webview,
WebKitPermissionRequest *request, Client *c);
static gboolean on_scroll(WebKitWebView *webview, GdkEvent *event, Client *c);
static void on_script_message_focus(WebKitUserContentManager *manager,
WebKitJavascriptResult *res, gpointer data);
static void on_script_message_scroll(WebKitUserContentManager *manager,
WebKitJavascriptResult *res, gpointer data);
static gboolean profileOptionArgFunc(const gchar *option_name,
const gchar *value, gpointer data, GError **error);
static gboolean autocmdOptionArgFunc(const gchar *option_name,
const gchar *value, gpointer data, GError **error);
struct Vimb vb;
/**
* Set the destination for a download according to suggested file name and
* possible given path.
*/
gboolean vb_download_set_destination(Client *c, WebKitDownload *download,
char *suggested_filename, const char *path)
{
char *download_path, *dir, *file, *uri, *basename = NULL;
download_path = GET_CHAR(c, "download-path");
if (!suggested_filename || !*suggested_filename) {
const char *download_uri;
GUri *parsed_uri = NULL;
char *decoded_uri;
/* Try to find a matching name if there is no suggested filename. */
download_uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
parsed_uri = g_uri_parse(download_uri, G_URI_FLAGS_NONE, NULL);
decoded_uri = g_uri_to_string(parsed_uri);
basename = g_filename_display_basename(decoded_uri);
suggested_filename = basename;
g_uri_unref(parsed_uri);
g_free(decoded_uri);
}
/* Prepare the path to save the download. */
if (path && *path) {
file = util_build_path(path, download_path);
/* if file is an directory append a file name */
if (g_file_test(file, (G_FILE_TEST_IS_DIR))) {
dir = file;
file = g_build_filename(dir, suggested_filename, NULL);
g_free(dir);
}
} else {
file = util_build_path(suggested_filename, download_path);
}
g_free(basename);
if (!file) {
return FALSE;
}
/* If the filepath exists already insert numerical suffix before file
* extension. */
if (g_file_test(file, G_FILE_TEST_EXISTS)) {
const char *dot_pos;
char *num = NULL;
GString *tmp;
gssize suffix;
int i = 1;
/* position on .tar. (special case, extension with two dots),
* position on last dot (if any) otherwise */
if (!(dot_pos = strstr(file, ".tar."))) {
dot_pos = strrchr(file, '.');
}
/* the position to insert the suffix at */
if (dot_pos) {
suffix = dot_pos - file;
} else {
suffix = strlen(file);
}
tmp = g_string_new(NULL);
/* Construct a new complete download filepath with suffix before the
* file extension. */
do {
num = g_strdup_printf("_%d", i++);
g_string_assign(tmp, file);
g_string_insert(tmp, suffix, num);
g_free(num);
} while (g_file_test(tmp->str, G_FILE_TEST_EXISTS));
file = g_strdup(tmp->str);
g_string_free(tmp, TRUE);
}
/* Build URI from filepath. */
uri = g_filename_to_uri(file, NULL, NULL);
g_free(file);
/* configure download */
g_assert(uri);
webkit_download_set_allow_overwrite(download, FALSE);
webkit_download_set_destination(download, uri);
g_free(uri);
return TRUE;
}
/**
* Write text to the inpubox if this isn't focused.
*/
void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...)
{
char *buffer;
va_list args;
/* Don't write to input box in case this is focused, might be the user is
* typing in it. */
if (gtk_widget_is_focus(GTK_WIDGET(c->input))) {
return;
}
va_start(args, error);
buffer = g_strdup_vprintf(error, args);
va_end(args);
input_print(c, type, hide, buffer);
g_free(buffer);
}
/**
* Write text to the inpubox independent if this is focused or not.
* Note that this could disturb the user during typing into inputbox.
*/
void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...)
{
char *buffer;
va_list args;
va_start(args, error);
buffer = g_strdup_vprintf(error, args);
va_end(args);
input_print(c, type, hide, buffer);
g_free(buffer);
}
/**
* Enter into the new given mode and leave possible active current mode.
*/
void vb_enter(Client *c, char id)
{
Mode *new = g_hash_table_lookup(vb.modes, GINT_TO_POINTER(id));
g_return_if_fail(new != NULL);
if (c->mode) {
/* don't do anything if the mode isn't a new one */
if (c->mode == new) {
return;
}
/* if there is a active mode, leave this first */
if (c->mode->leave) {
c->mode->leave(c);
}
}
/* reset the flags of the new entered mode */
new->flags = 0;
/* set the new mode so that it is available also in enter function */
c->mode = new;
/* call enter only if the new mode isn't the current mode */
if (new->enter) {
new->enter(c);
}
#ifndef TESTLIB
vb_statusbar_update(c);
#endif
}
/**
* Set the prompt chars and switch to new mode.
*
* @id: Mode id.
* @prompt: Prompt string to set as current prompt.
* @print_prompt: Indicates if the new set prompt should be put into inputbox
* after switching the mode.
*/
void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt)
{
/* set the prompt to be accessible in vb_enter */
strncpy(c->state.prompt, prompt, PROMPT_SIZE - 1);
c->state.prompt[PROMPT_SIZE - 1] = '\0';
vb_enter(c, id);
if (print_prompt) {
/* set it after the mode was entered so that the modes input change
* event listener could grep the new prompt */
vb_echo_force(c, MSG_NORMAL, FALSE, c->state.prompt);
}
}
/**
* Returns the client for given page id.
*/
Client *vb_get_client_for_page_id(guint64 pageid)
{
Client *c;
/* Search for the client with the same page id. */
for (c = vb.clients; c && c->page_id != pageid; c = c->next);
if (c) {
return c;
}
return NULL;
}
/**
* Retrieves the content of the command line.
* Returned string must be freed with g_free.
*/
char *vb_input_get_text(Client *c)
{
GtkTextIter start, end;
gtk_text_buffer_get_bounds(c->buffer, &start, &end);
return gtk_text_buffer_get_text(c->buffer, &start, &end, FALSE);
}
/**
* Writes given text into the command line.
*/
void vb_input_set_text(Client *c, const char *text)
{
gtk_text_buffer_set_text(c->buffer, text, -1);
if (c->config.input_autohide) {
gtk_widget_set_visible(GTK_WIDGET(c->input), *text != '\0');
}
}
/**
* Set the style of the inputbox according to current input type (normal or
* error).
*/
void vb_input_update_style(Client *c)
{
MessageType type = c->state.input_type;
if (type == MSG_ERROR) {
gtk_style_context_add_class(gtk_widget_get_style_context(c->input), "error");
} else {
gtk_style_context_remove_class(gtk_widget_get_style_context(c->input), "error");
}
}
/**
* Load the a uri given in Arg. This function handles also shortcuts and local
* file paths.
*
* If arg.i = TARGET_CURRENT, the url is opened into the current webview.
* TARGET_RELATED causes the generation of a new window within the current
* instance of vimb with a own, but related webview. And TARGET_NEW spawns a
* new instance of vimb with the given uri.
*/
gboolean vb_load_uri(Client *c, const Arg *arg)
{
char *uri = NULL, *rp, *path = NULL;
struct stat st;
if (arg->s) {
path = g_strstrip(arg->s);
}
if (!path || !*path) {
path = GET_CHAR(c, "home-page");
}
/* If path contains :// but no space we open it direct. This is required
* to use :// also with shortcuts */
if ((strstr(path, "://") && !strchr(path, ' ')) || !strncmp(path, "about:", 6)) {
uri = g_strdup(path);
} else if (stat(path, &st) == 0) {
/* check if the path is a file path */
rp = realpath(path, NULL);
uri = g_strconcat("file://", rp, NULL);
free(rp);
} else if (!is_plausible_uri(path)) {
/* use a shortcut if path contains spaces or doesn't contain typical
* tokens ('.', [:] for IPv6 addresses, 'localhost') */
uri = shortcut_get_uri(c->config.shortcuts, path);
}
if (!uri) {
uri = g_strconcat("http://", path, NULL);
}
if (arg->i == TARGET_CURRENT) {
/* Load the uri into the browser instance. */
webkit_web_view_load_uri(c->webview, uri);
set_title(c, uri);
} else if (arg->i == TARGET_NEW) {
spawn_new_instance(uri);
} else { /* TARGET_RELATED */
Client *newclient = client_new(c->webview);
/* Load the uri into the new client. */
webkit_web_view_load_uri(newclient->webview, uri);
set_title(c, uri);
}
g_free(uri);
return TRUE;
}
/**
* Creates and add a new mode with given callback functions.
*/
void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
ModeKeyFunc keypress, ModeInputChangedFunc input_changed)
{
Mode *new = g_slice_new(Mode);
new->id = id;
new->enter = enter;
new->leave = leave;
new->keypress = keypress;
new->input_changed = input_changed;
new->flags = 0;
/* Initialize the hashmap if this was not done before */
if (!vb.modes) {
vb.modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)mode_free);
}
g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new);
}
VbResult vb_mode_handle_key(Client *c, int key)
{
VbResult res;
if (c->state.ctrlv) {
c->state.processed_key = FALSE;
c->state.ctrlv = FALSE;
return RESULT_COMPLETE;
}
if (c->mode->id != 'p' && key == CTRL('V')) {
c->mode->flags |= FLAG_NOMAP;
c->state.ctrlv = TRUE;
return RESULT_MORE;
}
if (c->mode && c->mode->keypress) {
#ifdef DEBUGDISABLED
int flags = c->mode->flags;
int id = c->mode->id;
res = c->mode->keypress(c, key);
if (c->mode) {
PRINT_DEBUG(
"%c[%d]: %#.2x '%c' -> %c[%d]",
id - ' ', flags, key, (key >= 0x20 && key <= 0x7e) ? key : ' ',
c->mode->id - ' ', c->mode->flags
);
}
#else
res = c->mode->keypress(c, key);
#endif
return res;
}
return RESULT_ERROR;
}
/**
* Change the label for the current mode in inputbox or on the left of
* statusbar if inputbox is in autohide mode.
*/
void vb_modelabel_update(Client *c, const char *label)
{
if (c->config.input_autohide) {
/* if the inputbox is potentially not shown write mode into statusbar */
gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label);
} else {
vb_echo(c, MSG_NORMAL, FALSE, "%s", label);
}
}
/**
* Close the given client instances window.
*/
gboolean vb_quit(Client *c, gboolean force)
{
/* if not forced quit - don't quit if there are still running downloads */
if (!force && c->state.downloads) {
vb_echo_force(c, MSG_ERROR, TRUE, "Can't quit: there are running downloads. Use :q! to force quit");
return FALSE;
}
/* Don't run the quit synchronously, because this could lead to access of
* no more existing widget where some command response is written. */
g_idle_add((GSourceFunc)quit, c);
return TRUE;
}
/**
* Adds content to a named register.
*/
void vb_register_add(Client *c, char buf, const char *value)
{
char *mark;
int idx;
bool shouldAppend = false;
if (!c->state.enable_register || !buf) {
return;
}
/* check if its capital letter, if yes - append, don't overwrite */
if(buf >= 'A' && buf <= 'Z') {
shouldAppend = true;
// 32 is the magic number - ASCII offset from start of uppercase
// to lowercase letters
buf = buf + 32;
}
/* make sure the mark is a valid mark char */
if ((mark = strchr(REG_CHARS, buf))) {
/* get the index of the mark char */
idx = mark - REG_CHARS;
const gchar* newcontents = value;
if(shouldAppend && c->state.reg[idx])
newcontents = g_strjoin("\n", c->state.reg[idx], value, NULL);
OVERWRITE_STRING(c->state.reg[idx], newcontents);
}
}
/**
* Lookup register entry by it's name.
*/
const char *vb_register_get(Client *c, char buf)
{
char *mark;
int idx;
/* make sure the mark is a valid mark char */
if ((mark = strchr(REG_CHARS, buf))) {
/* get the index of the mark char */
idx = mark - REG_CHARS;
return c->state.reg[idx];
}
return NULL;
}
static void statusbar_update_downloads(Client *c, GString *status)
{
GList *list;
guint list_length, remaining_max = 0;
gdouble progress, elapsed, total, remaining;
WebKitDownload *download;
g_assert(c);
g_assert(status);
if (c->state.downloads) {
list_length = g_list_length(c->state.downloads);
g_assert(list_length);
/* get highest ETA value of all downloads based on each download's
* current progress fraction and time elapsed */
for (list = c->state.downloads; list != NULL; list = list->next) {
download = (WebKitDownload *)list->data;
g_assert(download);
progress = webkit_download_get_estimated_progress(download);
/* avoid dividing by zero */
if (progress == 0.0) {
continue;
}
elapsed = webkit_download_get_elapsed_time(download);
total = (1.0 / progress) * elapsed;
remaining = total - elapsed;
remaining_max = MAX(remaining, remaining_max);
}
g_string_append_printf(status, " %d %s (ETA %us)",
list_length, list_length == 1? "dnld" : "dnlds", remaining_max);
}
}
void vb_statusbar_update(Client *c)
{
GString *status;
if (!gtk_widget_get_visible(GTK_WIDGET(c->statusbar.box))) {
return;
}
status = g_string_new("");
/* show the number of matches search results */
if (c->state.search.active && c->state.search.matches) {
if (c->state.search.matches == G_MAXUINT) {
g_string_append_printf(status, " (>%d)", INCSEARCH_MATCHES_LIMIT);
} else {
g_string_append_printf(status, " (%d)", c->state.search.matches);
}
}
/* show load status of page or the downloads */
if (c->state.progress != 100) {
#ifdef FEATURE_WGET_PROGRESS_BAR
char bar[PROGRESS_BAR_LEN + 1];
int i, state;
state = c->state.progress * PROGRESS_BAR_LEN / 100;
for (i = 0; i < state; i++) {
bar[i] = PROGRESS_BAR[0];
}
bar[i++] = PROGRESS_BAR[1];
for (; i < PROGRESS_BAR_LEN; i++) {
bar[i] = PROGRESS_BAR[2];
}
bar[i] = '\0';
g_string_append_printf(status, " [%s]", bar);
#else
g_string_append_printf(status, " [%i%%]", c->state.progress);
#endif
}
statusbar_update_downloads(c, status);
/* These architectures have different kinds of issues with scroll
* percentage, this is a somewhat clean fix that doesn't affect others. */
#if defined(_ARCH_PPC64) || defined(_ARCH_PPC) | defined(_ARCH_ARM)
/* force the scroll percent to be 16-bit */
c->state.scroll_percent = *(guint16*)(&c->state.scroll_percent);
#endif
#ifdef STATUS_VARAIBLE_SHOW
if (c->config.statusbar_show_settings) {
g_string_append_printf(status, STATUS_VARAIBLE_SHOW);
}
#endif
/* show the scroll status */
if (c->state.scroll_max == 0) {
g_string_append(status, " All");
} else if (c->state.scroll_percent == 0) {
g_string_append(status, " Top");
} else if (c->state.scroll_percent == 100) {
g_string_append(status, " Bot");
} else {
g_string_append_printf(status, " %d%%", c->state.scroll_percent);
}
gtk_label_set_text(GTK_LABEL(c->statusbar.right), status->str);
g_string_free(status, TRUE);
}
/**
* Show the given url on the left of statusbar.
*/
void vb_statusbar_show_hover_url(Client *c, VbLinkType type, const char *uri)
{
char *sanitized_uri,
*msg;
const char *type_label;
/* No uri given - show the current URI. */
if (!uri || !*uri) {
update_urlbar(c);
return;
}
switch (type) {
case LINK_TYPE_LINK:
type_label = "Link: ";
break;
case LINK_TYPE_IMAGE:
type_label = "Image: ";
break;
default:
return;
}
sanitized_uri = util_sanitize_uri(uri);
msg = g_strconcat(type_label, uri, NULL);
gtk_label_set_text(GTK_LABEL(c->statusbar.left), msg);
g_free(msg);
g_free(sanitized_uri);
}
/**
* Destroys given client and removed it from client queue. If no client is
* there in queue, quit the gtk main loop.
*/
static void client_destroy(Client *c)
{
Client *p;
webkit_web_view_stop_loading(c->webview);
/* Write last URL into file for recreation.
* The URL is only stored if the closed-max-items is not 0 and the file
* exists. */
if (c->state.uri && vb.config.closed_max && vb.files[FILES_CLOSED]) {
util_file_prepend_line(vb.files[FILES_CLOSED], c->state.uri,
vb.config.closed_max);
}
gtk_widget_destroy(c->window);
/* Look for the client in the list, if we searched through the list and
* didn't find it the client must be the first item. */
for (p = vb.clients; p && p->next != c; p = p->next);
if (p) {
p->next = c->next;
} else {
vb.clients = c->next;
}
if (c->state.search.last_query) {
g_free(c->state.search.last_query);
}
completion_cleanup(c);
map_cleanup(c);
register_cleanup(c);
setting_cleanup(c);
#ifdef FEATURE_AUTOCMD
autocmd_cleanup(c);
#endif
handler_free(c->handler);
shortcut_free(c->config.shortcuts);
g_slice_free(Client, c);
/* if there are no clients - quit the main loop */
if (!vb.clients) {
gtk_main_quit();
}
}
/**
* Creates a new client instance with it's own window.
*
* @webview: Related webview or NULL if a client with an independent
* webview shoudl be created.
*/
static Client *client_new(WebKitWebView *webview)
{
Client *c;
/* create the client */
/* Prepend the new client to the queue of clients. */
c = g_slice_new0(Client);
c->next = vb.clients;
vb.clients = c;
c->state.progress = 100;
c->config.shortcuts = shortcut_new();
completion_init(c);
map_init(c);
c->handler = handler_new();
#ifdef FEATURE_AUTOCMD
autocmd_init(c);
#endif
/* webview */
c->webview = webview_new(c, webview);
c->finder = webkit_web_view_get_find_controller(c->webview);
g_signal_connect(c->finder, "found-text", G_CALLBACK(on_found_text), c);
g_signal_connect(c->finder, "failed-to-find-text", G_CALLBACK(on_failed_to_find_text), c);
g_signal_connect(c->webview, "user-message-received", G_CALLBACK(on_user_message_received), c);
/* Get initial page ID (may change due to process isolation) */
c->page_id = webkit_web_view_get_page_id(c->webview);
c->webview_id = (guint64)c->webview;
PRINT_DEBUG("Client created: webview=%p, page_id=%" G_GUINT64_FORMAT, c->webview, c->page_id);
c->inspector = webkit_web_view_get_inspector(c->webview);
return c;
}
static void client_show(WebKitWebView *webview, Client *c)
{
GtkWidget *box;
c->window = create_window(c);
/* statusbar */
c->statusbar.box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
c->statusbar.mode = gtk_label_new(NULL);
c->statusbar.left = gtk_label_new(NULL);
c->statusbar.right = gtk_label_new(NULL);
c->statusbar.cmd = gtk_label_new(NULL);
gtk_widget_set_name(GTK_WIDGET(c->statusbar.box), "statusbar");
gtk_label_set_ellipsize(GTK_LABEL(c->statusbar.left), PANGO_ELLIPSIZE_MIDDLE);
gtk_widget_set_halign(c->statusbar.left, GTK_ALIGN_START);
gtk_widget_set_halign(c->statusbar.mode, GTK_ALIGN_START);
gtk_box_pack_start(c->statusbar.box, c->statusbar.mode, FALSE, TRUE, 0);
gtk_box_pack_start(c->statusbar.box, c->statusbar.left, TRUE, TRUE, 2);
gtk_box_pack_start(c->statusbar.box, c->statusbar.cmd, FALSE, FALSE, 0);
gtk_box_pack_start(c->statusbar.box, c->statusbar.right, FALSE, FALSE, 2);
/* inputbox */
c->input = gtk_text_view_new();
gtk_widget_set_name(c->input, "input");
c->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(c->input));
g_signal_connect(c->buffer, "changed", G_CALLBACK(on_textbuffer_changed), c);
/* Make sure the user can see the typed text. */
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(c->input), GTK_WRAP_WORD_CHAR);
/* pack the parts together */
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add(GTK_CONTAINER(c->window), box);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->webview), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->statusbar.box), FALSE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(c->input), FALSE, FALSE, 0);
/* Set the default style for statusbar and inputbox. */
gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)),
GTK_STYLE_PROVIDER(vb.style_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_style_context_add_provider(gtk_widget_get_style_context(c->input),
GTK_STYLE_PROVIDER(vb.style_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
/* TODO separate initialization o setting from applying the values or
* allow to se the default values for different scopes. For now we can
* init the settings not in client_new because we need the access to some
* widget for some settings. */
setting_init(c);
gtk_widget_show_all(c->window);
#ifndef FEATURE_NO_XEMBED
char *wid;
wid = g_strdup_printf("%d", (int)GDK_WINDOW_XID(gtk_widget_get_window(c->window)));
g_setenv("VIMB_WIN_ID", wid, TRUE);
/* set the x window id to env */
if (vb.embed) {
char *xid;
xid = g_strdup_printf("%d", (int)vb.embed);
g_setenv("VIMB_XID", xid, TRUE);
g_free(xid);
} else {
g_setenv("VIMB_XID", wid, TRUE);
}
g_free(wid);
#endif
/* start client in normal mode */
vb_enter(c, 'n');
c->state.enable_register = TRUE;
/* read the config file */
ex_run_file(c, vb.files[FILES_CONFIG]);
}
static GtkWidget *create_window(Client *c)
{
GtkWidget *window;
#ifndef FEATURE_NO_XEMBED
if (vb.embed) {
window = gtk_plug_new(vb.embed);
} else {
#endif
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_role(GTK_WINDOW(window), PROJECT_UCFIRST);
gtk_window_set_default_size(GTK_WINDOW(window), WIN_WIDTH, WIN_HEIGHT);
if (!vb.no_maximize) {
gtk_window_maximize(GTK_WINDOW(window));
}
#ifndef FEATURE_NO_XEMBED
}
#endif
#ifdef GUI_WINDOW_BACKGROUND_COLOR
{
// force setting background color to prevent white flashes in dark mode
GdkRGBA color;
gdk_rgba_parse (&color, GUI_WINDOW_BACKGROUND_COLOR);
GtkCssProvider *provider = gtk_css_provider_new();
gchar *css = g_strdup_printf("window { background-color: %s; }", GUI_WINDOW_BACKGROUND_COLOR);
gtk_css_provider_load_from_data(provider, css, -1, NULL);
gtk_style_context_add_provider(
gtk_widget_get_style_context(window),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_free(css);
g_object_unref(provider);
}
#endif
g_object_connect(
G_OBJECT(window),
"signal::destroy", G_CALLBACK(on_window_destroy), c,
"signal::delete-event", G_CALLBACK(on_window_delete_event), c,
"signal::key-press-event", G_CALLBACK(on_map_keypress), c,
NULL);
return window;
}
/**
* Callback that clear the input box after a timeout if this was set on
* input_print.
*/
static gboolean input_clear(Client *c)
{
if (!gtk_widget_is_focus(GTK_WIDGET(c->input))) {
return FALSE;
}
input_print(c, MSG_NORMAL, FALSE, "");
return FALSE;
}
/**
* Print a message to the input box.
*
* @type: Type of message normal or error
* @hide: If TRUE the inputbox is cleared after a short timeout.
* @message: The message to print.
*/
static void input_print(Client *c, MessageType type, gboolean hide,
const char *message)
{
/* apply input style only if the message type was changed */
if (type != c->state.input_type) {
c->state.input_type = type;
vb_input_update_style(c);
}
vb_input_set_text(c, message);
if (hide) {
/* add timeout function */
c->state.input_timer = g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)input_clear, c);
} else if (c->state.input_timer > 0) {
/* If there is already a timeout function but the input box content is
* changed - remove the timeout. Seems the user started another
* command or typed into inputbox. */
g_source_remove(c->state.input_timer);
c->state.input_timer = 0;
}
}
/**
* Tests if a path is likely intended to be an URI (given that it's not a file
* path or containing "://").
*/
static gboolean is_plausible_uri(const char *path)
{
const char *i, *j;
if (strchr(path, ' ')) {
return FALSE;
}
if (strchr(path, '.')) {
return TRUE;
}
if ((i = strstr(path, "localhost")) &&
(i == path || i[-1] == '/' || i[-1] == '@') &&
(i[9] == 0 || i[9] == '/' || i[9] == ':')
) {
return TRUE;
}
return (i = strchr(path, '[')) && (j = strchr(i, ':')) && strchr(j, ']');
}
/**
* Reinitializes or clears the set page marks.
*/
static void marks_clear(Client *c)
{
int i;
/* init empty marks array */
for (i = 0; i < MARK_SIZE; i++) {
c->state.marks[i] = -1;
}
}
/**
* Free the memory of given mode. This is used as destroy function of the
* modes hashmap.
*/
static void mode_free(Mode *mode)
{
g_slice_free(Mode, mode);
}
/**
* The ::changed signal is emitted when the content of a GtkTextBuffer has
* changed. This call back function is connected to the input box' text buffer.
*/
static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data)
{
gchar *text;
GtkTextIter start, end;
Client *c = (Client *)user_data;
g_assert(c);
/* don't observe changes in completion mode */
if (c->mode->flags & FLAG_COMPLETION) {
return;
}
/* don't process changes not typed by the user */
if (gtk_widget_is_focus(c->input) && c->mode && c->mode->input_changed) {
gtk_text_buffer_get_bounds(textbuffer, &start, &end);
text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
c->mode->input_changed(c, text);
g_free(text);
}
}
/**
* Set the style of the statusbar.
*/
static void set_statusbar_style(Client *c, StatusType type)
{
GtkStyleContext *ctx;
/* Do nothing if the new to set style is the same as the current. */
if (type == c->state.status_type) {
return;
}
ctx = gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box));
if (type == STATUS_SSL_VALID) {
gtk_style_context_remove_class(ctx, "unsecure");
gtk_style_context_add_class(ctx, "secure");
} else if (type == STATUS_SSL_INVALID) {
gtk_style_context_remove_class(ctx, "secure");
gtk_style_context_add_class(ctx, "unsecure");
} else {
gtk_style_context_remove_class(ctx, "secure");
gtk_style_context_remove_class(ctx, "unsecure");
}
c->state.status_type = type;
}
/**
* Update the window title of the main window.
*/
static void set_title(Client *c, const char *title)
{
OVERWRITE_STRING(c->state.title, title);
update_title(c);
g_setenv("VIMB_TITLE", title ? title : "", TRUE);
}
/**
* Spawns a new browser instance for given uri.
*
* @uri: URI used for the new instance.
*/
static void spawn_new_instance(const char *uri)
{
guint i = 0;
/* memory allocation */
char **cmd = g_malloc_n(
3 /* basename + uri + ending NULL */
+ (vb.configfile ? 2 : 0)
#ifndef FEATURE_NO_XEMBED
+ (vb.embed ? 2 : 0)
#endif
+ (vb.incognito ? 1 : 0)
+ (vb.profile ? 2 : 0)
+ (vb.no_maximize ? 1 : 0)
+ g_slist_length(vb.cmdargs) * 2,
sizeof(char *)
);
cmd[i++] = vb.argv0;
if (vb.configfile) {
cmd[i++] = "-c";
cmd[i++] = vb.configfile;
}
#ifndef FEATURE_NO_XEMBED
if (vb.embed) {
char xid[64];
cmd[i++] = "-e";
snprintf(xid, LENGTH(xid), "%d", (int)vb.embed);
cmd[i++] = xid;
}
#endif
if (vb.incognito) {
cmd[i++] = "-i";
}
if (vb.profile) {
cmd[i++] = "-p";
cmd[i++] = vb.profile;
}
if (vb.no_maximize) {
cmd[i++] = "--no-maximize";
}
for (GSList *l = vb.cmdargs; l; l = l->next) {
cmd[i++] = "-C";
cmd[i++] = l->data;
}
cmd[i++] = (char*)uri;
cmd[i++] = NULL;
/* spawn a new browser instance */
g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
/* free commandline */
g_free(cmd);
}
/**
* Callback for the web contexts download-started signal.
*/
static void on_webctx_download_started(WebKitWebContext *webctx,
WebKitDownload *download, Client *c)
{
#ifdef FEATURE_AUTOCMD
const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
autocmd_run(c, AU_DOWNLOAD_STARTED, uri, NULL);
#endif
if (GET_BOOL(c, "download-use-external")) {
g_signal_connect(download, "notify::response", G_CALLBACK(on_webdownload_response_received), c);
} else {
g_signal_connect(download, "decide-destination", G_CALLBACK(on_webdownload_decide_destination), c);
g_signal_connect(download, "failed", G_CALLBACK(on_webdownload_failed), c);
g_signal_connect(download, "finished", G_CALLBACK(on_webdownload_finished), c);
g_signal_connect(download, "received-data", G_CALLBACK(on_webdownload_received_data), c);
c->state.downloads = g_list_append(c->state.downloads, download);
/* to reflect the correct download count */
vb_statusbar_update(c);
}
}
/**
* Callback for the web contexts initialize-web-extensions signal.
*/
static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data)
{
const char *name;
GVariant *vdata;
#if (CHECK_WEBEXTENSION_ON_STARTUP)
char *extension = g_build_filename(EXTENSIONDIR, "webext_main.so", NULL);
if (!g_file_test(extension, G_FILE_TEST_IS_REGULAR)) {
g_error("Cannot access web extension %s", extension);
}
g_free(extension);
#endif
name = ext_proxy_init();
vdata = g_variant_new("(ms)", name);
webkit_web_context_set_web_extensions_initialization_user_data(webctx, vdata);
/* Setup the extension directory. */
webkit_web_context_set_web_extensions_directory(webctx, EXTENSIONDIR);
}
/**
* Callback for the webkit download decide destination signal.
* This signal is emitted after response is received to decide a destination
* URI for the download.
*/
static gboolean on_webdownload_decide_destination(WebKitDownload *download,
gchar *suggested_filename, Client *c)
{
if (webkit_download_get_destination(download)) {
return TRUE;
}
return vb_download_set_destination(c, download, suggested_filename, NULL);
}
static void on_webdownload_response_received(WebKitDownload *download,
GParamSpec *ps, Client *c)
{
spawn_download_command(c, webkit_download_get_response(download));
webkit_download_cancel(download);
}
static void spawn_download_command(Client *c, WebKitURIResponse *response)
{
char *cmd;
char **argv, **envp;
int argc;
GError *error = NULL;
cmd = g_strdup_printf(GET_CHAR(c, "download-command"),
webkit_uri_response_get_uri(response));
if (!g_shell_parse_argv(cmd, &argc, &argv, &error)) {
g_warning("Could not parse download-command '%s': %s",
cmd,
error->message);
g_error_free(error);
g_free(cmd);
return;
}
envp = g_get_environ();
envp = g_environ_setenv(envp, "VIMB_DOWNLOAD_PATH",
GET_CHAR(c, "download-path"), TRUE);
if (g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
vb_echo(c, MSG_NORMAL, FALSE, "Download started");
} else {
vb_echo(c, MSG_ERROR, TRUE, "Could not start download");
g_warning("%s", error->message);
g_clear_error(&error);
}
g_free(cmd);
g_strfreev(envp);
g_strfreev(argv);
}
/**
* Callback for the webkit download failed signal.
* This signal is emitted when an error occurs during the download operation.
*/
static void on_webdownload_failed(WebKitDownload *download,
GError *error, Client *c)
{
gchar *destination = NULL, *filename = NULL, *basename = NULL;
g_assert(download);
g_assert(error);
g_assert(c);
#ifdef FEATURE_AUTOCMD
const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
autocmd_run(c, AU_DOWNLOAD_FAILED, uri, NULL);
#endif
/* get the failed download's destination uri */
g_object_get(download, "destination", &destination, NULL);
g_assert(destination);
/* filename from uri */
if (destination) {
filename = g_filename_from_uri(destination, NULL, NULL);
g_free(destination);
}
/* basename from filename */
if (filename) {
basename = g_path_get_basename(filename);
g_free(filename);
}
/* report the error to the user */
if (basename) {
vb_echo(c, MSG_ERROR, FALSE, "Download of %s failed (%s)", basename, error->message);
g_free(basename);
}
}
/**
* Callback for the webkit download finished signal.
* This signal is emitted when download finishes successfully or due to an
* error. In case of errors “failed” signal is emitted before this one.
*/
static void on_webdownload_finished(WebKitDownload *download, Client *c)
{
gchar *destination = NULL, *filename = NULL, *basename = NULL;
g_assert(download);
g_assert(c);
#ifdef FEATURE_AUTOCMD
const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
autocmd_run(c, AU_DOWNLOAD_FINISHED, uri, NULL);
#endif
c->state.downloads = g_list_remove(c->state.downloads, download);
/* to reflect the correct download count */
vb_statusbar_update(c);
/* get the finished downloads destination uri */
g_object_get(download, "destination", &destination, NULL);
g_assert(destination);
/* filename from uri */
if (destination) {
filename = g_filename_from_uri(destination, NULL, NULL);
g_free(destination);
}
if (filename) {
/* basename from filename */
basename = g_path_get_basename(filename);
if (basename) {
/* Only report to the user if the downloaded file exists, so the
* download was successful. Otherwise, this is a failed download
* finished signal and it was reported to the user in
* on_webdownload_failed() already. */
if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
vb_echo(c, MSG_NORMAL, FALSE, "Download of %s finished", basename);
}
g_free(basename);
}
g_free(filename);
}
}
/**
* Callback for the webkit download received-data signal.
* This signal is emitted after response is received, every time new data has
* been written to the destination. It's useful to know the progress of the
* download operation.
*/
static void on_webdownload_received_data(WebKitDownload *download,
guint64 data_length, Client *c)
{
/* rate limit statusbar updates */
static gint64 statusbar_update_next = 0;
if (g_get_monotonic_time() > statusbar_update_next) {
statusbar_update_next = g_get_monotonic_time() + 1000000; /* 1 second */
vb_statusbar_update(c);
}
}
/**
* Callback for the webview close signal.
*/
static void on_webview_close(WebKitWebView *webview, Client *c)
{
gtk_widget_destroy(c->window);
}
/**
* Callback for the webview create signal.
* This creates a new client - with it's own window with a related webview.
*/
static WebKitWebView *on_webview_create(WebKitWebView *webview,
WebKitNavigationAction *navact, Client *c)
{
WebKitURIRequest *req;
if (c->config.prevent_newwindow) {
req = webkit_navigation_action_get_request(navact);
vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
return NULL;
}
Client *new = client_new(webview);
return new->webview;
}
/**
* Callback for the webview decide-policy signal.
* Checks the reasons for some navigation actions and decides if the action is
* allowed, or should go into a new instance of vimb.
*/
static gboolean on_webview_decide_policy(WebKitWebView *webview,
WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c)
{
switch (type) {
case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
decide_navigation_action(c, dec);
break;
case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
decide_new_window_action(c, dec);
break;
case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
decide_response(c, dec);
break;
default:
webkit_policy_decision_ignore(dec);
break;
}
return TRUE;
}
static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec)
{
guint button, mod;
WebKitNavigationAction *a;
WebKitURIRequest *req;
const char *uri;
a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
req = webkit_navigation_action_get_request(a);
uri = webkit_uri_request_get_uri(req);
/* Try to handle with specific protocol handler. */
if (handler_handle_uri(c->handler, uri)) {
webkit_policy_decision_ignore(dec);
return;
}
button = webkit_navigation_action_get_mouse_button(a);
mod = webkit_navigation_action_get_modifiers(a);
/* Spawn new instance if the new win flag is set on the mode, or the
* navigation was triggered by CTRL-LeftMouse or MiddleMouse. */
if ((c->mode->flags & FLAG_NEW_WIN)
|| (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
&& (button == 2 || (button == 1 && mod & GDK_CONTROL_MASK)))) {
/* Remove the FLAG_NEW_WIN after the first use. */
c->mode->flags &= ~FLAG_NEW_WIN;
webkit_policy_decision_ignore(dec);
spawn_new_instance(uri);
} else {
#ifdef FEATURE_QUEUE
/* Push link target to queue on Shift-LeftMouse. */
if (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
&& button == 1
&& mod & GDK_SHIFT_MASK
&& strcmp(uri, "about:blank")) {
command_queue(c, &((Arg){.i = COMMAND_QUEUE_PUSH, .s = (char *)uri}));
webkit_policy_decision_ignore(dec);
return;
}
#endif
#ifdef FEATURE_AUTOCMD
if (strcmp(uri, "about:blank")) {
autocmd_run(c, AU_LOAD_STARTING, uri, NULL);
}
#endif
webkit_policy_decision_use(dec);
}
}
static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec)
{
WebKitNavigationAction *a;
WebKitURIRequest *req;
a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
switch (webkit_navigation_action_get_navigation_type(a)) {
case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
/* This is triggered on link click for links with target="_blank".
* Maybe it should be configurable if the page is opened as tab or
* a new instance. Ignore opening new window if this was started
* without user gesture. */
if (webkit_navigation_action_is_user_gesture(a)) {
req = webkit_navigation_action_get_request(a);
if (c->config.prevent_newwindow) {
/* Load the uri into the browser instance. */
vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
} else {
spawn_new_instance(webkit_uri_request_get_uri(req));
}
}
break;
case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
default:
break;
}
webkit_policy_decision_ignore(dec);
}
static void decide_response(Client *c, WebKitPolicyDecision *dec)
{
guint status;
WebKitURIResponse *res;
res = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(dec));
status = webkit_uri_response_get_status_code(res);
if (webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(dec))) {
webkit_policy_decision_use(dec);
} else if (SOUP_STATUS_IS_SUCCESSFUL(status) || status == SOUP_STATUS_NONE) {
webkit_policy_decision_download(dec);
} else {
webkit_policy_decision_ignore(dec);
}
}
static void on_webview_load_changed(WebKitWebView *webview,
WebKitLoadEvent event, Client *c)
{
GTlsCertificateFlags tlsflags;
const char *raw_uri;
char *uri = NULL;
raw_uri = webkit_web_view_get_uri(webview);
if (raw_uri) {
uri = util_sanitize_uri(raw_uri);
}
switch (event) {
case WEBKIT_LOAD_STARTED:
#ifdef FEATURE_AUTOCMD
autocmd_run(c, AU_LOAD_STARTED, raw_uri, NULL);
#endif
/* update load progress in statusbar */
c->state.progress = 0;
vb_statusbar_update(c);
if (uri) {
set_title(c, uri);
}
/* Make sure hinting is cleared before the new page is loaded.
* Without that vimb would still be in hinting mode after hinting
* was started and some links was clicked my mouse. Even if there
* could not hints be shown. */
if (c->mode->flags & FLAG_HINTING) {
vb_enter(c, 'n');
}
break;
case WEBKIT_LOAD_REDIRECTED:
break;
case WEBKIT_LOAD_COMMITTED:
/* In case of HTTP authentication request we ignore the focus
* changes so that the input mode can be set for the
* authentication request. If the authentication dialog is filled
* or aborted the load will be commited. So this seems to be the
* right place to remove the flag. */
c->mode->flags &= ~FLAG_IGNORE_FOCUS;
#ifdef FEATURE_AUTOCMD
autocmd_run(c, AU_LOAD_COMMITTED, raw_uri, NULL);
#endif
/* save the current URI in register % */
vb_register_add(c, '%', uri);
/* check if tls is on and the page is trusted */
if (uri && g_str_has_prefix(uri, "https://")) {
if (webkit_web_view_get_tls_info(webview, NULL, &tlsflags) && tlsflags) {
set_statusbar_style(c, STATUS_SSL_INVALID);
} else {
set_statusbar_style(c, STATUS_SSL_VALID);
}
} else {
set_statusbar_style(c, STATUS_NORMAL);
}
/* clear possible set marks */
marks_clear(c);
/* Unset possible last search. Use commit==TRUE to clear inputbox
* in case a link was fired from highlighted link. */
command_search(c, &(Arg){0, NULL}, TRUE);
break;
case WEBKIT_LOAD_FINISHED:
#ifdef FEATURE_AUTOCMD
autocmd_run(c, AU_LOAD_FINISHED, raw_uri, NULL);
#endif
c->state.progress = 100;
if (uri
&& regexec(&c->config.histignore_preg, uri, 1, NULL, 0)
#ifdef FEATURE_HISTORY_WITHOUT_HOME_PAGE
&& strcmp(uri, GET_CHAR(c, "home-page"))
#endif
) {
history_add(c, HISTORY_URL, uri, webkit_web_view_get_title(webview));
}
break;
}
if (uri) {
g_free(uri);
}
}
/**
* Callback for the webview mouse-target-changed signal.
* This is used to print the uri too statusbar if the user hovers over links
* or images.
*/
static void on_webview_mouse_target_changed(WebKitWebView *webview,
WebKitHitTestResult *result, guint modifiers, Client *c)
{
/* Save the hitTestResult to have this later available for events that
* don't support this. */
if (c->state.hit_test_result) {
g_object_unref(c->state.hit_test_result);
}
c->state.hit_test_result = g_object_ref(result);
if (webkit_hit_test_result_context_is_link(result)) {
vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
webkit_hit_test_result_get_link_uri(result));
} else if (webkit_hit_test_result_context_is_image(result)) {
vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
webkit_hit_test_result_get_image_uri(result));
} else {
/* No link under cursor - show the current URI. */
vb_statusbar_show_hover_url(c, LINK_TYPE_NONE, NULL);
}
}
/**
* Called on webviews notify::estimated-load-progress event. This writes the
* esitamted load progress in percent in a variable and updates the statusbar
* to make the changes visible.
*/
static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
GParamSpec *spec, Client *c)
{
c->state.progress = webkit_web_view_get_estimated_load_progress(webview) * 100;
vb_statusbar_update(c);
update_title(c);
}
/**
* Callback for the webview notify::title signal.
* Changes the window title according to the title of the current page.
*/
static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec, Client *c)
{
const char *title = webkit_web_view_get_title(webview);
if (*title) {
set_title(c, title);
}
}
/**
* Callback for the webview notify::uri signal.
* Changes the current uri shown on left of statusbar.
*/
static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec, Client *c)
{
if (c->state.uri) {
g_free(c->state.uri);
}
c->state.uri = util_sanitize_uri(webkit_web_view_get_uri(c->webview));
update_urlbar(c);
g_setenv("VIMB_URI", c->state.uri, TRUE);
}
/**
* Callback for the webview ready-to-show signal.
* Show the webview only if it's ready to be shown.
*/
static void on_webview_ready_to_show(WebKitWebView *webview, Client *c)
{
client_show(webview, c);
}
/**
* Callback for the webview web-process-crashed signal.
*/
static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c)
{
vb_echo(c, MSG_ERROR, FALSE, "Webview Crashed on %s", webkit_web_view_get_uri(webview));
return TRUE;
}
/**
* Callback in case HTTP authentication is requested by the server.
*/
static gboolean on_webview_authenticate(WebKitWebView *webview,
WebKitAuthenticationRequest *request, Client *c)
{
/* Don't change the mode if we are in pass through mode. */
if (c->mode->id == 'n') {
vb_enter(c, 'i');
/* Make sure we do not switch back to normal mode in case a previos
* page is open and looses the focus. */
c->mode->flags |= FLAG_IGNORE_FOCUS;
}
return FALSE;
}
/**
* Callback in case JS calls element.webkitRequestFullScreen.
*/
static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c)
{
c->state.is_fullscreen = TRUE;
gtk_widget_hide(GTK_WIDGET(c->statusbar.box));
gtk_widget_set_visible(GTK_WIDGET(c->input), FALSE);
return FALSE;
}
/**
* Callback to restore the window state after entering fullscreen.
*/
static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c)
{
c->state.is_fullscreen = FALSE;
gtk_widget_show(GTK_WIDGET(c->statusbar.box));
gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE);
return FALSE;
}
/**
* Callback for window ::delete-event signal which is emitted if a user
* requests that a toplevel window is closed. The default handler for this
* signal destroys the window. Returns TRUE to stop other handlers from being
* invoked for the event. FALSE to propagate the event further.
*/
static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c)
{
/* if vb_quit fails, do not propagate event further, keep window open */
return !vb_quit(c, FALSE);
}
/**
* Callback for the window destroy signal.
* Destroys the client that is associated to the window.
*/
static void on_window_destroy(GtkWidget *window, Client *c)
{
client_destroy(c);
}
/**
* Callback for to quit given client as idle event source.
*/
static gboolean quit(Client *c)
{
/* Destroy the main window to tirgger the destruction of the client. */
gtk_widget_destroy(c->window);
/* Remove this from the list of event sources. */
return FALSE;
}
/**
* Read string from stdin and pass it to webkit for html interpretation.
*/
static void read_from_stdin(Client *c)
{
GIOChannel *ch;
gchar *buf = NULL;
GError *err = NULL;
gsize len = 0;
g_assert(c);
ch = g_io_channel_unix_new(fileno(stdin));
g_io_channel_read_to_end(ch, &buf, &len, &err);
g_io_channel_unref(ch);
if (err) {
g_warning("Error loading from stdin: %s", err->message);
g_error_free(err);
} else {
webkit_web_view_load_html(c->webview, buf, NULL);
}
g_free(buf);
}
/**
* Free the register contents memory.
*/
static void register_cleanup(Client *c)
{
int i;
for (i = 0; i < REG_SIZE; i++) {
if (c->state.reg[i]) {
g_free(c->state.reg[i]);
}
}
}
static void update_title(Client *c)
{
#ifdef FEATURE_TITLE_PROGRESS
/* Show load status of page or the downloads. */
if (c->state.progress != 100) {
char *title = g_strdup_printf(
"[%i%%] %s",
c->state.progress,
c->state.title ? c->state.title : "");
gtk_window_set_title(GTK_WINDOW(c->window), title);
g_free(title);
return;
}
#endif
if (c->state.title) {
gtk_window_set_title(GTK_WINDOW(c->window), c->state.title);
}
}
/**
* Update the contents of the url bar on the left of the statu bar according
* to current opened url and position in back forward history.
*/
static void update_urlbar(Client *c)
{
GString *str;
gboolean back, fwd;
str = g_string_new("");
/* show profile name */
if (vb.profile) {
g_string_append_printf(str, "[%s] ", vb.profile);
}
g_string_append_printf(str, "%s", c->state.uri);
/* show history indicator only if there is something to show */
back = webkit_web_view_can_go_back(c->webview);
fwd = webkit_web_view_can_go_forward(c->webview);
if (back || fwd) {
g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+");
}
gtk_label_set_text(GTK_LABEL(c->statusbar.left), str->str);
g_string_free(str, TRUE);
}
#ifdef FREE_ON_QUIT
/**
* Free memory of the whole application.
*/
static void vimb_cleanup(void)
{
int i;
while (vb.clients) {
client_destroy(vb.clients);
}
/* free memory of other components */
util_cleanup();
for (i = 0; i < STORAGE_LAST; i++) {
file_storage_free(vb.storage[i]);
}
for (i = 0; i < FILES_LAST; i++) {
if (vb.files[i]) {
g_free(vb.files[i]);
}
}
g_free(vb.profile);
g_hash_table_destroy(vb.webview_to_proxy);
/* Clean up any remaining pending proxies */
if (vb.pending_proxies) {
GSList *item;
for (item = vb.pending_proxies; item != NULL; item = item->next) {
g_slice_free(ProxyPageId, item->data);
}
g_slist_free(vb.pending_proxies);
}
g_slist_free_full(vb.cmdargs, g_free);
g_clear_object(&vb.webcontext);
}
#endif
/**
* Setup resources used on application scope.
*/
static void vimb_setup(void)
{
char *path, *dataPath;
/* Prepare files in XDG_CONFIG_HOME */
path = util_get_config_dir();
if (vb.configfile) {
char *rp = realpath(vb.configfile, NULL);
vb.files[FILES_CONFIG] = g_strdup(rp);
free(rp);
} else {
vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL);
}
vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL);
vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL);
g_free(path);
/* Prepare files in XDG_DATA_HOME */
dataPath = util_get_data_dir();
if (!vb.incognito) {
vb.files[FILES_CLOSED] = g_build_filename(dataPath, "closed", NULL);
vb.files[FILES_COOKIE] = g_build_filename(dataPath, "cookies.db", NULL);
}
vb.files[FILES_BOOKMARK] = g_build_filename(dataPath, "bookmark", NULL);
vb.files[FILES_QUEUE] = g_build_filename(dataPath, "queue", NULL);
vb.storage[STORAGE_HISTORY] = file_storage_new(dataPath, "history", vb.incognito);
vb.storage[STORAGE_COMMAND] = file_storage_new(dataPath, "command", vb.incognito);
vb.storage[STORAGE_SEARCH] = file_storage_new(dataPath, "search", vb.incognito);
g_free(dataPath);
WebKitWebsiteDataManager *manager = NULL;
if (vb.incognito) {
manager = webkit_website_data_manager_new_ephemeral();
} else {
manager = webkit_website_data_manager_new(
"base-data-directory", util_get_data_dir(),
"base-cache-directory", util_get_cache_dir(),
NULL);
}
vb.webcontext = webkit_web_context_new_with_website_data_manager(manager);
manager = webkit_web_context_get_website_data_manager(vb.webcontext);
webkit_web_context_set_cache_model(vb.webcontext, WEBKIT_CACHE_MODEL_WEB_BROWSER);
g_signal_connect(vb.webcontext, "initialize-web-extensions", G_CALLBACK(on_webctx_init_web_extension), NULL);
/* Add cookie support only if the cookie file exists and web context is
* not ephemeral */
if (vb.files[FILES_COOKIE] && !webkit_web_context_is_ephemeral(vb.webcontext)) {
WebKitCookieManager *cm;
cm = webkit_web_context_get_cookie_manager(vb.webcontext);
webkit_cookie_manager_set_persistent_storage(
cm,
vb.files[FILES_COOKIE],
WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
}
vb.webview_to_proxy = g_hash_table_new(g_direct_hash, g_direct_equal);
vb.pending_proxies = NULL;
/* initialize the modes */
vb_mode_add('n', normal_enter, normal_leave, normal_keypress, NULL);
vb_mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed);
vb_mode_add('i', input_enter, input_leave, input_keypress, NULL);
vb_mode_add('p', pass_enter, pass_leave, pass_keypress, NULL);
/* Prepare the style provider to be used for the clients and completion. */
vb.style_provider = gtk_css_provider_new();
}
/**
* Update the gui style settings for client c, given a style setting name and a
* style setting value to be updated. The complete style sheet document will be
* regenerated and re-fed into gtk css provider.
*/
void vb_gui_style_update(Client *c, const char *setting_name_new, const char *setting_value_new)
{
g_assert(c);
g_assert(setting_name_new);
g_assert(setting_value_new);
/* The css style sheet document being composed in this function */
GString *style_sheet = g_string_new(GUI_STYLE_CSS_BASE);
size_t i;
/* Mapping from vimb config setting name to css style sheet string */
static const char *setting_style_map[][2] = {
{"completion-css", " #completion{%s}"},
{"completion-hover-css", " #completion:hover{%s}"},
{"completion-selected-css", " #completion:selected{%s}"},
{"input-css", " #input{%s}"},
{"input-error-css", " #input.error{%s}"},
{"status-css", " #statusbar{%s}"},
{"status-ssl-css", " #statusbar.secure{%s}"},
{"status-ssl-invalid-css", " #statusbar.unsecure{%s}"},
{0, 0},
};
/* For each supported style setting name */
for (i = 0; setting_style_map[i][0]; i++) {
const char *setting_name = setting_style_map[i][0];
const char *style_string = setting_style_map[i][1];
/* If the current style setting name is the one to be updated,
* append the given value with appropriate css wrapping to the
* style sheet document. */
if (strcmp(setting_name, setting_name_new) == 0) {
if (strlen(setting_value_new)) {
g_string_append_printf(style_sheet, style_string, setting_value_new);
}
}
/* If the current style setting name is NOT the one being updated,
* append the css string based on the current config setting. */
else {
Setting* setting_value = (Setting*)g_hash_table_lookup(c->config.settings, setting_name);
/* If the current style setting name is not available via settings
* yet - this happens during setting_init() - cleanup and return.
* We are going to be called again. With the last setting_add(),
* all style setting names are available. */
if(!setting_value) {
goto cleanup;
}
if (strlen(setting_value->value.s)) {
g_string_append_printf(style_sheet, style_string, setting_value->value.s);
}
}
}
/* Feed style sheet document to gtk */
gtk_css_provider_load_from_data(vb.style_provider, style_sheet->str, -1, NULL);
/* WORKAROUND to always ensure correct size of input field
*
* The following line is required to apply the style defined font size on
* the GtkTextView c->input. Without the call, the font size is updated on
* first user input, leading to a sudden unpleasant widget size and layout
* change. According to the GTK+ docs, this call should not be required as
* style context invalidation is automatic.
*
* "gtk_style_context_invalidate has been deprecated since version 3.12
* and should not be used in newly-written code. Style contexts are
* invalidated automatically."
* https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-style-context-invalidate
*
* Required settings in vimb config file:
* set input-autohide=true
* set input-font-normal=20pt monospace
*
* A bug has been filed at GTK+
* https://bugzilla.gnome.org/show_bug.cgi?id=781158
*
* Tested on ARCH linux with gtk3 3.22.10-1
*/
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
gtk_style_context_invalidate(gtk_widget_get_style_context(c->input));
G_GNUC_END_IGNORE_DEPRECATIONS;
cleanup:
g_string_free(style_sheet, TRUE);
}
/**
* Factory to create a new webview.
*
* @webview: Relates webview or NULL. If given a related webview is
* generated.
*/
static WebKitWebView *webview_new(Client *c, WebKitWebView *webview)
{
WebKitWebView *new;
WebKitUserContentManager *ucm;
WebKitWebContext *webcontext;
/* create a new webview */
ucm = webkit_user_content_manager_new();
if (webview) {
new = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"user-content-manager", ucm,
"related-view", webview,
NULL));
} else {
new = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"user-content-manager", ucm,
"web-context", vb.webcontext,
NULL));
}
g_object_connect(
G_OBJECT(new),
"signal::close", G_CALLBACK(on_webview_close), c,
"signal::create", G_CALLBACK(on_webview_create), c,
"signal::decide-policy", G_CALLBACK(on_webview_decide_policy), c,
"signal::load-changed", G_CALLBACK(on_webview_load_changed), c,
"signal::mouse-target-changed", G_CALLBACK(on_webview_mouse_target_changed), c,
"signal::notify::estimated-load-progress", G_CALLBACK(on_webview_notify_estimated_load_progress), c,
"signal::notify::title", G_CALLBACK(on_webview_notify_title), c,
"signal::notify::uri", G_CALLBACK(on_webview_notify_uri), c,
"signal::permission-request", G_CALLBACK(on_permission_request), c,
"signal::scroll-event", G_CALLBACK(on_scroll), c,
"signal::ready-to-show", G_CALLBACK(on_webview_ready_to_show), c,
"signal::web-process-crashed", G_CALLBACK(on_webview_web_process_crashed), c,
"signal::authenticate", G_CALLBACK(on_webview_authenticate), c,
"signal::enter-fullscreen", G_CALLBACK(on_webview_enter_fullscreen), c,
"signal::leave-fullscreen", G_CALLBACK(on_webview_leave_fullscreen), c,
"signal::context-menu", G_CALLBACK(on_webview_context_menu), c,
NULL
);
webcontext = webkit_web_view_get_context(new);
g_signal_connect(webcontext, "download-started", G_CALLBACK(on_webctx_download_started), c);
/* Setup script message handlers. */
webkit_user_content_manager_register_script_message_handler(ucm, "focus");
webkit_user_content_manager_register_script_message_handler(ucm, "scroll");
g_signal_connect(ucm, "script-message-received::focus", G_CALLBACK(on_script_message_focus), NULL);
g_signal_connect(ucm, "script-message-received::scroll", G_CALLBACK(on_script_message_scroll), NULL);
return new;
}
static void on_found_text(WebKitFindController *finder, guint count, Client *c)
{
if (c->state.search.awaited_matches_updates <= 0) {
return;
}
c->state.search.matches = count;
c->state.search.awaited_matches_updates--;
vb_statusbar_update(c);
}
static void on_failed_to_find_text(WebKitFindController *finder, Client *c)
{
if (c->state.search.awaited_matches_updates <= 0) {
return;
}
c->state.search.matches = 0;
c->state.search.awaited_matches_updates--;
vb_statusbar_update(c);
}
static gboolean on_user_message_received(WebKitWebView *webview, WebKitUserMessage *message, Client *c)
{
const char *name;
guint64 pageid_from_ext;
guint64 pageid_from_view;
GVariant *params;
GSList *item;
GDBusProxy *found_proxy = NULL;
name = webkit_user_message_get_name(message);
PRINT_DEBUG("Received user message: %s", name);
/* Only handle page-document-loaded message */
if (g_strcmp0(name, "page-document-loaded") != 0) {
return FALSE;
}
/* Get the page ID sent by the webextension */
params = webkit_user_message_get_parameters(message);
if (params) {
g_variant_get(params, "(t)", &pageid_from_ext);
} else {
g_warning("page-document-loaded message missing parameters");
return FALSE;
}
/* Get the current page ID from the webview (should be stable now) */
pageid_from_view = webkit_web_view_get_page_id(c->webview);
c->page_id = pageid_from_view;
c->webview_id = (guint64)c->webview;
PRINT_DEBUG("Page IDs: from_ext=%" G_GUINT64_FORMAT ", from_view=%" G_GUINT64_FORMAT,
pageid_from_ext, pageid_from_view);
/* Search for a matching proxy in the pending list.
* Match by either the webextension's page ID or the view's page ID. */
for (item = vb.pending_proxies; item != NULL; item = item->next) {
ProxyPageId *pending = (ProxyPageId *)item->data;
if (pending->pageid == pageid_from_ext || pending->pageid == pageid_from_view) {
found_proxy = pending->proxy;
/* Remove from pending list and free the struct */
vb.pending_proxies = g_slist_remove(vb.pending_proxies, pending);
g_slice_free(ProxyPageId, pending);
break;
}
}
if (!found_proxy) {
g_warning("Could not find pending proxy for page IDs (ext=%" G_GUINT64_FORMAT
", view=%" G_GUINT64_FORMAT ")", pageid_from_ext, pageid_from_view);
return FALSE;
}
/* Assign the proxy to this client */
c->dbusproxy = found_proxy;
/* Store in hash table for quick lookup by page ID */
g_hash_table_insert(vb.webview_to_proxy, GINT_TO_POINTER(c->page_id), c->dbusproxy);
/* Subscribe to signals for this client */
GDBusConnection *connection = g_dbus_proxy_get_connection(c->dbusproxy);
if (connection) {
g_dbus_connection_signal_subscribe(connection,
NULL, VB_WEBEXTENSION_INTERFACE, "VerticalScroll",
VB_WEBEXTENSION_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE,
(GDBusSignalCallback)on_vertical_scroll, c, NULL);
} else {
g_warning("Could not get dbus connection from proxy for page ID %" G_GUINT64_FORMAT, c->page_id);
}
return TRUE;
}
/**
* Listen to the VerticalScroll signal and set the scroll percent value on the
* client to update the statusbar.
*/
static void on_vertical_scroll(GDBusConnection *connection,
const char *sender_name, const char *object_path,
const char *interface_name, const char *signal_name,
GVariant *parameters, Client* c)
{
guint64 max, top;
guint percent;
guint64 pageid;
g_variant_get(parameters, "(ttqt)", &pageid, &max, &percent, &top);
c->state.scroll_max = max;
c->state.scroll_percent = percent;
c->state.scroll_top = top;
vb_statusbar_update(c);
}
static gboolean on_permission_request(WebKitWebView *webview,
WebKitPermissionRequest *request, Client *c)
{
GtkWidget *dialog;
int result;
char *msg = NULL;
if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
char* geolocation_setting = GET_CHAR(c, "geolocation");
if (strcmp(geolocation_setting, "ask") == 0) {
msg = "access your location";
} else if (strcmp(geolocation_setting, "always") == 0) {
webkit_permission_request_allow(request);
return TRUE;
} else if (strcmp(geolocation_setting, "never") == 0) {
webkit_permission_request_deny(request);
return TRUE;
}
} else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
msg = "access the microphone";
} else if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
msg = "access you webcam";
}
} else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) {
char* notification_setting = GET_CHAR(c, "notification");
if (strcmp(notification_setting, "ask") == 0) {
msg = "show notifications";
} else if (strcmp(notification_setting, "always") == 0) {
webkit_permission_request_allow(request);
return TRUE;
} else if (strcmp(notification_setting, "never") == 0) {
webkit_permission_request_deny(request);
return TRUE;
}
} else {
return FALSE;
}
dialog = gtk_message_dialog_new(GTK_WINDOW(c->window), GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Page wants to %s",
msg);
gtk_widget_show(dialog);
result = gtk_dialog_run(GTK_DIALOG(dialog));
if (GTK_RESPONSE_YES == result) {
webkit_permission_request_allow(request);
} else {
webkit_permission_request_deny(request);
}
gtk_widget_destroy(dialog);
return TRUE;
}
static gboolean on_scroll(WebKitWebView *webview, GdkEvent *event, Client *c)
{
event->scroll.delta_y *= c->config.scrollmultiplier;
return FALSE;
}
static void on_script_message_focus(WebKitUserContentManager *manager,
WebKitJavascriptResult *res, gpointer data)
{
char *message;
GVariant *variant;
guint64 pageid;
gboolean is_focused;
Client *c;
message = util_js_result_as_string(res);
variant = g_variant_parse(G_VARIANT_TYPE("(tb)"), message, NULL, NULL, NULL);
g_free(message);
g_variant_get(variant, "(tb)", &pageid, &is_focused);
g_variant_unref(variant);
c = vb_get_client_for_page_id(pageid);
if (!c || c->mode->flags & FLAG_IGNORE_FOCUS) {
return;
}
/* Don't change the mode if we are in pass through mode. */
if (c->mode->id == 'n' && is_focused) {
vb_enter(c, 'i');
} else if (c->mode->id == 'i' && !is_focused) {
vb_enter(c, 'n');
}
}
/**
* Callback for scroll position messages from injected JavaScript.
* Updates the scroll percentage display in the statusbar.
*/
static void on_script_message_scroll(WebKitUserContentManager *manager,
WebKitJavascriptResult *res, gpointer data)
{
JSCValue *value;
JSCValue *max_val, *percent_val, *top_val;
Client *c;
/* Get the JavaScript result value */
value = webkit_javascript_result_get_js_value(res);
if (!jsc_value_is_object(value)) {
return;
}
/* Extract the scroll data from the JavaScript object */
max_val = jsc_value_object_get_property(value, "max");
percent_val = jsc_value_object_get_property(value, "percent");
top_val = jsc_value_object_get_property(value, "top");
if (!max_val || !percent_val || !top_val) {
return;
}
/* Find the client for this webview
* Since we don't have page_id in the message, we need to find it differently */
for (c = vb.clients; c; c = c->next) {
WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(
WEBKIT_WEB_VIEW(c->webview));
if (ucm == manager) {
/* Update scroll state */
c->state.scroll_max = jsc_value_to_double(max_val);
c->state.scroll_percent = jsc_value_to_int32(percent_val);
c->state.scroll_top = jsc_value_to_double(top_val);
/* Update the statusbar */
vb_statusbar_update(c);
break;
}
}
}
static gboolean profileOptionArgFunc(const gchar *option_name,
const gchar *value, gpointer data, GError **error)
{
vb.profile = util_sanitize_filename(g_strdup(value));
return TRUE;
}
static gboolean autocmdOptionArgFunc(const gchar *option_name,
const gchar *value, gpointer data, GError **error)
{
vb.cmdargs = g_slist_append(vb.cmdargs, g_strdup(value));
return TRUE;
}
int main(int argc, char* argv[])
{
Client *c;
GError *err = NULL;
char *pidstr;
#ifndef FEATURE_NO_XEMBED
char *winid = NULL;
#endif
gboolean ver = FALSE, buginfo = FALSE;
GOptionEntry opts[] = {
{"cmd", 'C', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)autocmdOptionArgFunc, "Ex command run before first page is loaded", NULL},
{"config", 'c', 0, G_OPTION_ARG_FILENAME, &vb.configfile, "Custom configuration file", NULL},
#ifndef FEATURE_NO_XEMBED
{"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL},
#endif
{"incognito", 'i', 0, G_OPTION_ARG_NONE, &vb.incognito, "Run with user data read-only", NULL},
{"profile", 'p', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)profileOptionArgFunc, "Profile name", NULL},
{"version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Print version", NULL},
{"no-maximize", 0, 0, G_OPTION_ARG_NONE, &vb.no_maximize, "Do no attempt to maximize window", NULL},
{"bug-info", 0, 0, G_OPTION_ARG_NONE, &buginfo, "Print used library versions", NULL},
{NULL}
};
bindtextdomain("WebKitGTK-4.1", "/usr/share/locale/");
/* initialize GTK+ */
if (!gtk_init_with_args(&argc, &argv, "[URI]", opts, NULL, &err)) {
fprintf(stderr, "can't init gtk: %s\n", err->message);
g_error_free(err);
return EXIT_FAILURE;
}
if (ver) {
printf("%s, version %s\n", PROJECT, VERSION);
return EXIT_SUCCESS;
}
if (buginfo) {
printf("Version: %s\n", VERSION);
printf("WebKit compile: %d.%d.%d\n",
WEBKIT_MAJOR_VERSION,
WEBKIT_MINOR_VERSION,
WEBKIT_MICRO_VERSION);
printf("WebKit run: %d.%d.%d\n",
webkit_get_major_version(),
webkit_get_minor_version(),
webkit_get_micro_version());
printf("GTK compile: %d.%d.%d\n",
GTK_MAJOR_VERSION,
GTK_MINOR_VERSION,
GTK_MICRO_VERSION);
printf("GTK run: %d.%d.%d\n",
gtk_major_version,
gtk_minor_version,
gtk_micro_version);
printf("libsoup compile: %d.%d.%d\n",
SOUP_MAJOR_VERSION,
SOUP_MINOR_VERSION,
SOUP_MICRO_VERSION);
printf("libsoup run: %u.%u.%u\n",
soup_get_major_version(),
soup_get_minor_version(),
soup_get_micro_version());
printf("Extension dir: %s\n",
EXTENSIONDIR);
return EXIT_SUCCESS;
}
/* Save the base name for spawning new instances. */
vb.argv0 = argv[0];
/* set the current pid in env */
pidstr = g_strdup_printf("%d", (int)getpid());
g_setenv("VIMB_PID", pidstr, TRUE);
g_free(pidstr);
vimb_setup();
#ifndef FEATURE_NO_XEMBED
if (winid) {
vb.embed = strtol(winid, NULL, 0);
}
#endif
c = client_new(NULL);
client_show(NULL, c);
/* process the --cmd if this was given */
for (GSList *l = vb.cmdargs; l; l = l->next) {
ex_run_string(c, l->data, false);
}
if (argc <= 1) {
vb_load_uri(c, &(Arg){TARGET_CURRENT, NULL});
} else if (!strcmp(argv[argc - 1], "-")) {
/* read from stdin if uri is - */
read_from_stdin(c);
} else {
vb_load_uri(c, &(Arg){TARGET_CURRENT, argv[argc - 1]});
}
gtk_main();
#ifdef FREE_ON_QUIT
vimb_cleanup();
#endif
return EXIT_SUCCESS;
}
fanglingsu-vimb-448e7e2/src/main.h 0000664 0000000 0000000 00000030404 15145416123 0017035 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _MAIN_H
#define _MAIN_H
#include
#include
#include "config.h"
#ifndef FEATURE_NO_XEMBED
#include
#endif
#include
#include
#include "shortcut.h"
#include "handler.h"
#include "file-storage.h"
#define LENGTH(x) (sizeof x / sizeof x[0])
#define OVERWRITE_STRING(t, s) {if (t) g_free(t); t = g_strdup(s);}
#define OVERWRITE_NSTRING(t, s, l) {if (t) {g_free(t); t = NULL;} t = g_strndup(s, l);}
#define GET_CHAR(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.s)
#define GET_INT(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.i)
#define GET_BOOL(c, n) (((Setting*)g_hash_table_lookup(c->config.settings, n))->value.b)
#ifdef DEBUG
#define PRINT_DEBUG(...) { \
fprintf(stderr, "\n\033[31;1mDEBUG: \033[32;1m%s +%d %s()\033[0m\t", __FILE__, __LINE__, __func__); \
fprintf(stderr, __VA_ARGS__);\
}
#define TIMER_START GTimer *__timer; {__timer = g_timer_new(); g_timer_start(__timer);}
#define TIMER_END {gdouble __debug_elapsed = g_timer_elapsed(__timer, NULL);\
PRINT_DEBUG("\033[33mtimer:\033[0m %fs", __debug_elapsed);\
g_timer_destroy(__timer); \
}
#else
#define PRINT_DEBUG(message, ...)
#define TIMER_START
#define TIMER_END
#endif
/* the special mark ' must be the first in the list for easiest lookup */
#define MARK_CHARS "'abcdefghijklmnopqrstuvwxyz"
#define MARK_TICK 0
#define MARK_SIZE (sizeof(MARK_CHARS) - 1)
#define GLOBAL_MARK_CHARS "'ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define USER_REG "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
/* registers in order displayed for :register command */
#define REG_CHARS "\"" USER_REG ":%/;"
#define REG_SIZE (sizeof(REG_CHARS) - 1)
#define FILE_CLOSED "closed"
#define FILE_COOKIES "cookies"
enum { TARGET_CURRENT, TARGET_RELATED, TARGET_NEW };
typedef enum {
RESULT_COMPLETE, RESULT_MORE, RESULT_ERROR
} VbResult;
typedef enum {
CMD_ERROR, /* command could not be parses or executed */
CMD_SUCCESS = 0x01, /* command runned successfully */
CMD_KEEPINPUT = 0x02, /* don't clear inputbox after command run */
} VbCmdResult;
typedef enum {
TYPE_BOOLEAN, TYPE_INTEGER, TYPE_CHAR, TYPE_COLOR, TYPE_FONT
} DataType;
typedef enum {
MSG_NORMAL, MSG_ERROR
} MessageType;
typedef enum {
STATUS_NORMAL, STATUS_SSL_VALID, STATUS_SSL_INVALID
} StatusType;
typedef enum {
INPUT_UNKNOWN,
INPUT_SET = 0x01,
INPUT_OPEN = 0x02,
INPUT_TABOPEN = 0x04,
INPUT_COMMAND = 0x08,
INPUT_SEARCH_FORWARD = 0x10,
INPUT_SEARCH_BACKWARD = 0x20,
INPUT_BOOKMARK_ADD = 0x40,
INPUT_ALL = 0xff, /* map to match all input types */
} VbInputType;
enum {
FILES_BOOKMARK,
FILES_CLOSED,
FILES_CONFIG,
FILES_COOKIE,
FILES_QUEUE,
FILES_SCRIPT,
FILES_USER_STYLE,
FILES_LAST
};
enum {
STORAGE_CLOSED,
STORAGE_COMMAND,
STORAGE_CONFIG,
STORAGE_HISTORY,
STORAGE_SEARCH,
STORAGE_LAST
};
typedef enum {
LINK_TYPE_NONE,
LINK_TYPE_LINK,
LINK_TYPE_IMAGE,
} VbLinkType;
typedef struct Client Client;
typedef struct State State;
typedef struct Map Map;
typedef struct Mode Mode;
typedef struct Arg Arg;
typedef void (*ModeTransitionFunc)(Client*);
typedef VbResult (*ModeKeyFunc)(Client*, int);
typedef void (*ModeInputChangedFunc)(Client*, const char*);
struct Arg {
int i;
char *s;
};
typedef int (*SettingFunction)(Client *c, const char *name, DataType type, void *value, void *data);
typedef union {
gboolean b;
int i;
char *s;
} SettingValue;
typedef struct {
const char *name;
int type;
SettingValue value;
SettingFunction setter;
int flags;
void *data; /* data given to the setter */
} Setting;
struct State {
char *uri;
gboolean typed; /* indicates if the user typed the keys */
gboolean processed_key; /* indicates if a key press was handled and should not bubbled up */
gboolean ctrlv; /* indicates if the CTRL-V temorary submode is on */
#define PROMPT_SIZE 4
char prompt[PROMPT_SIZE];/* current prompt ':', 'g;t', '/' including nul */
guint64 marks[MARK_SIZE]; /* holds marks set to page with 'm{markchar}' */
guint input_timer;
MessageType input_type;
StatusType status_type;
guint64 scroll_max; /* Maxmimum scrollable height of the document. */
guint scroll_percent; /* Current position of the viewport in document (percent). */
guint64 scroll_top; /* Current position of the viewport in document (pixel). */
char *title; /* Window title of the client. */
char *reg[REG_SIZE]; /* holds the yank buffers */
/* TODO rename to reg_{enabled,current} */
gboolean enable_register; /* indicates if registers are filled */
char current_register; /* holds char for current register to be used */
GList *downloads;
guint progress;
WebKitHitTestResult *hit_test_result;
gboolean is_fullscreen;
struct {
gboolean active; /* indicate if there is a active search */
short direction; /* last direction 1 forward, -1 backward */
int matches; /* number of matching search results */
int awaited_matches_updates;
char *last_query; /* last search query */
} search;
struct {
guint64 pos;
char *uri;
} global_marks[MARK_SIZE];
};
struct Map {
char *in; /* input keys */
int inlen; /* length of the input keys */
char *mapped; /* mapped keys */
int mappedlen; /* length of the mapped keys string */
char mode; /* mode for which the map is available */
gboolean remap; /* if FALSE do not remap the {rhs} of this map */
};
struct Mode {
char id;
ModeTransitionFunc enter; /* is called if the mode is entered */
ModeTransitionFunc leave; /* is called if the mode is left */
ModeKeyFunc keypress; /* receives key to process */
ModeInputChangedFunc input_changed; /* is triggered if input textbuffer is changed */
#define FLAG_NOMAP 0x0001 /* disables mapping for key strokes */
#define FLAG_HINTING 0x0002 /* marks active hinting submode */
#define FLAG_COMPLETION 0x0004 /* marks active completion submode */
#define FLAG_PASSTHROUGH 0x0008 /* don't handle any other keybind than */
#define FLAG_NEW_WIN 0x0010 /* enforce opening of pages into new window */
#define FLAG_IGNORE_FOCUS 0x0012 /* do not listen for focus change messages */
unsigned int flags;
};
struct Statusbar {
GtkBox *box;
GtkWidget *mode, *left, *right, *cmd;
};
struct AuGroup;
struct Client {
struct Client *next;
struct State state;
struct Statusbar statusbar;
void *comp; /* pointer to data used in completion.c */
Mode *mode; /* current active browser mode */
/* WebKitWebContext *webctx; */ /* not used atm, use webkit_web_context_get_default() instead */
GtkWidget *window, *input;
WebKitWebView *webview;
WebKitFindController *finder;
WebKitWebInspector *inspector;
guint64 page_id; /* page id of the webview */
guint64 webview_id; /* unique stable identifier derived from webview pointer */
GtkTextBuffer *buffer;
GDBusProxy *dbusproxy;
GDBusServer *dbusserver;
Handler *handler; /* the protocoll handlers */
struct {
/* TODO split in global setting definitions and set values on a per
* client base. */
GHashTable *settings;
guint scrollstep;
guint scrollmultiplier;
gboolean input_autohide;
gboolean incsearch;
gboolean prevent_newwindow;
guint default_zoom; /* default zoom level in percent */
Shortcut *shortcuts;
gboolean statusbar_show_settings;
gboolean smooth_scrolling;
regex_t histignore_preg;
} config;
struct {
GSList *list;
GString *queue; /* queue holding typed keys */
int qlen; /* pointer to last char in queue */
int resolved; /* number of resolved keys (no mapping required) */
guint timout_id; /* source id of the timeout function */
char showcmd[SHOWCMD_LEN + 1]; /* buffer to show ambiguous key sequence */
guint timeoutlen; /* timeout for ambiguous mappings */
} map;
struct {
struct AuGroup *curgroup;
GSList *groups;
guint usedbits; /* holds all used event bits */
} autocmd;
};
struct Vimb {
char *argv0;
Client *clients;
#ifndef FEATURE_NO_XEMBED
Window embed;
#endif
GHashTable *modes; /* all available browser main modes */
char *configfile; /* config file given as option on startup */
char *files[FILES_LAST];
FileStorage *storage[STORAGE_LAST];
char *profile; /* profile name */
GSList *cmdargs; /* ex commands given asl --command, -C option */
struct {
guint history_max;
guint closed_max;
} config;
GtkCssProvider *style_provider;
gboolean no_maximize;
gboolean incognito;
WebKitWebContext *webcontext;
GHashTable *webview_to_proxy; /* maps page_id to dbus proxy for connected clients */
GSList *pending_proxies; /* list of pending ProxyPageId structs */
};
gboolean vb_download_set_destination(Client *c, WebKitDownload *download,
char *suggested_filename, const char *path);
void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...);
void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...);
void vb_enter(Client *c, char id);
void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt);
Client *vb_get_client_for_page_id(guint64 pageid);
char *vb_input_get_text(Client *c);
void vb_input_set_text(Client *c, const char *text);
void vb_input_update_style(Client *c);
gboolean vb_load_uri(Client *c, const Arg *arg);
void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
ModeKeyFunc keypress, ModeInputChangedFunc input_changed);
VbResult vb_mode_handle_key(Client *c, int key);
void vb_modelabel_update(Client *c, const char *label);
gboolean vb_quit(Client *c, gboolean force);
void vb_register_add(Client *c, char buf, const char *value);
const char *vb_register_get(Client *c, char buf);
void vb_statusbar_update(Client *c);
void vb_statusbar_show_hover_url(Client *c, VbLinkType type, const char *uri);
void vb_gui_style_update(Client *c, const char *name, const char *value);
#endif /* end of include guard: _MAIN_H */
fanglingsu-vimb-448e7e2/src/map.c 0000664 0000000 0000000 00000055243 15145416123 0016671 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2019 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include "ascii.h"
#include "config.h"
#include "main.h"
#include "map.h"
#include "util.h"
static char *convert_keylabel(const char *in, int inlen, int *len);
static char *convert_keys(const char *in, int inlen, int *len);
static gboolean do_timeout(Client *c);
static void free_map(Map *map);
static int keyval_to_string(guint keyval, guint state, guchar *string);
static gboolean map_delete_by_lhs(Client *c, const char *lhs, int len, char mode);
static void showcmd(Client *c, int ch);
static char *transchar(int c);
static int utf_char2bytes(guint c, guchar *buf);
static void queue_event(GdkEventKey *e);
static void process_events(void);
static void free_events(void);
static void process_event(GdkEventKey* event);
extern struct Vimb vb;
static struct {
guint state;
guint keyval;
char one;
char two;
} special_keys[] = {
/* TODO: In GTK3, keysyms changed to have a KEY_ prefix.
* See gdkkeysyms.h and gdkkeysyms-compat.h
*/
{GDK_SHIFT_MASK, GDK_Tab, 'k', 'B'},
{0, GDK_Up, 'k', 'u'},
{0, GDK_Down, 'k', 'd'},
{0, GDK_Left, 'k', 'l'},
{0, GDK_Right, 'k', 'r'},
{0, GDK_F1, 'k', '1'},
{0, GDK_F2, 'k', '2'},
{0, GDK_F3, 'k', '3'},
{0, GDK_F4, 'k', '4'},
{0, GDK_F5, 'k', '5'},
{0, GDK_F6, 'k', '6'},
{0, GDK_F7, 'k', '7'},
{0, GDK_F8, 'k', '8'},
{0, GDK_F9, 'k', '9'},
{0, GDK_F10, 'k', ';'},
{0, GDK_F11, 'F', '1'},
{0, GDK_F12, 'F', '2'},
{0, GDK_Prior, 'k', 'P'}, /* page up */
{0, GDK_Next, 'k', 'N'}, /* page down */
};
static struct {
char *label;
int len;
char *ch;
int chlen;
} key_labels[] = {
{"", 8, "\\", 1},
{"", 4, "\x0d", 1},
{"", 5, "\t", 1},
{"", 7, CSI_STR "kB", 3},
{"", 5, "\x1b", 1},
{"", 7, "\x20", 1},
{"", 4, "\x08", 1},
{"", 4, CSI_STR "ku", 3},
{"", 6, CSI_STR "kd", 3},
{"", 6, CSI_STR "kl", 3},
{"", 7, CSI_STR "kr", 3},
{"", 4, CSI_STR "k1", 3},
{"", 4, CSI_STR "k2", 3},
{"", 4, CSI_STR "k3", 3},
{"", 4, CSI_STR "k4", 3},
{"", 4, CSI_STR "k5", 3},
{"", 4, CSI_STR "k6", 3},
{"", 4, CSI_STR "k7", 3},
{"", 4, CSI_STR "k8", 3},
{"", 4, CSI_STR "k9", 3},
{"", 5, CSI_STR "k;", 3},
{"", 5, CSI_STR "F1", 3},
{"", 5, CSI_STR "F2", 3},
{"", 8, CSI_STR "kP", 3},
{"", 10,CSI_STR "kN", 3},
};
/* this is only to queue GDK key events, in order to later send them if the map didn't match */
static struct {
GSList *list; /* queue holding submitted events */
gboolean processing; /* whether or not events are processing */
} events = {0};
void map_init(Client *c)
{
c->map.queue = g_string_sized_new(50);
/* TODO move this to settings */
c->map.timeoutlen = 1000;
}
void map_cleanup(Client *c)
{
if (c->map.list) {
g_slist_free_full(c->map.list, (GDestroyNotify)free_map);
}
if (c->map.queue) {
g_string_free(c->map.queue, TRUE);
}
}
/**
* Added the given key sequence to the key queue and process the mapping of
* chars. The key sequence do not need to be NUL terminated.
* Keylen of 0 signalized a key timeout.
*/
MapState map_handle_keys(Client *c, const guchar *keys, int keylen, gboolean use_map)
{
int ambiguous;
Map *match = NULL;
gboolean timeout = (keylen == 0); /* keylen 0 signalized timeout */
static int showlen = 0; /* track the number of keys in showcmd of status bar */
/* if a previous timeout function was set remove this */
if (c->map.timout_id) {
g_source_remove(c->map.timout_id);
c->map.timout_id = 0;
}
/* don't set the timeout function if the timeout is processed now */
if (!timeout) {
c->map.timout_id = g_timeout_add(c->map.timeoutlen, (GSourceFunc)do_timeout, c);
}
/* copy the keys onto the end of queue */
if (keylen > 0) {
g_string_overwrite_len(c->map.queue, c->map.qlen, (char*)keys, keylen);
c->map.qlen += keylen;
}
/* try to resolve keys against the map */
while (TRUE) {
/* send any resolved key to the parser */
while (c->map.resolved > 0) {
int qk;
/* skip csi indicator and the next 2 chars - if the csi sequence
* isn't part of a mapped command we let gtk handle the key - this
* is required allow to move cursor in inputbox with and
* keys */
if ((c->map.queue->str[0] & 0xff) == CSI && c->map.qlen >= 3) {
/* get next 2 chars to build the termcap key */
qk = TERMCAP2KEY(c->map.queue->str[1], c->map.queue->str[2]);
c->map.resolved -= 3;
c->map.qlen -= 3;
/* move all other queue entries three steps to the left */
g_string_erase(c->map.queue, 0, 3);
} else {
/* get first char of queue */
qk = c->map.queue->str[0];
c->map.resolved--;
c->map.qlen--;
/* move all other queue entries one step to the left */
g_string_erase(c->map.queue, 0, 1);
}
/* remove the no-map flag */
c->mode->flags &= ~FLAG_NOMAP;
/* send the key to the parser */
if (RESULT_MORE != vb_mode_handle_key(c, (int)qk)) {
showcmd(c, 0);
showlen = 0;
} else if (showlen > 0) {
showlen--;
} else {
showcmd(c, qk);
}
}
/* if all keys where processed return MAP_DONE */
if (c->map.qlen == 0) {
c->map.resolved = 0;
return match ? MAP_DONE : MAP_NOMATCH;
}
/* try to find matching maps */
match = NULL;
ambiguous = 0;
if (use_map && !(c->mode->flags & FLAG_NOMAP)) {
for (GSList *l = c->map.list; l != NULL; l = l->next) {
Map *m = (Map*)l->data;
/* ignore maps for other modes */
if (m->mode != c->mode->id) {
continue;
}
/* find ambiguous matches */
if (!timeout && m->inlen > c->map.qlen && !strncmp(m->in, c->map.queue->str, c->map.qlen)) {
if (ambiguous == 0) {
/* show command chars for the ambiguous commands */
int i = c->map.qlen > SHOWCMD_LEN ? c->map.qlen - SHOWCMD_LEN : 0;
/* appen only those chars that are not already in showcmd */
i += showlen;
while (i < c->map.qlen) {
showcmd(c, c->map.queue->str[i++]);
showlen++;
}
}
ambiguous++;
}
/* complete match or better/longer match than previous found */
if (m->inlen <= c->map.qlen
&& !strncmp(m->in, c->map.queue->str, m->inlen)
&& (!match || match->inlen < m->inlen)
) {
/* backup this found possible match */
match = m;
}
}
/* if there are ambiguous matches return MAP_AMBIGUOUS and flush queue
* after a timeout if the user do not type more keys */
if (ambiguous) {
return MAP_AMBIGUOUS;
}
}
/* Replace the matched chars from queue by the cooked string that
* is the result of the mapping. */
if (match) {
/* Flush the show command to make room for possible mapped command
* chars to show. For example if :nmap foo 12g is use we want to
* display the incomplete 12g command. */
showcmd(c, 0);
showlen = 0;
/* Replace the matching input chars by the mapped chars. */
if (match->inlen == match->mappedlen) {
/* If inlen and mappedlen are the same - replace the inlin
* chars with the mapped chars. This case could also be
* handled by the later string erase and prepend, but handling
* it special avoids unneded function call. */
g_string_overwrite_len(c->map.queue, 0, match->mapped, match->mappedlen);
} else {
/* Remove all the chars that where matched and prepend the
* mapped chars to the queue. */
g_string_erase(c->map.queue, 0, match->inlen);
g_string_prepend_len(c->map.queue, match->mapped, match->mappedlen);
}
c->map.qlen += match->mappedlen - match->inlen;
/* without remap the mapped chars are resolved now */
if (!match->remap) {
c->map.resolved = match->mappedlen;
} else if (match->inlen <= match->mappedlen
&& !strncmp(match->in, match->mapped, match->inlen)
) {
c->map.resolved = match->inlen;
}
/* Unset the typed flag - if there where keys replaced by a
* mapping the resulting key string is considered as not typed by
* the user. */
c->state.typed = FALSE;
} else {
/* first char is not mapped but resolved */
c->map.resolved = 1;
}
}
g_return_val_if_reached(MAP_DONE);
}
/**
* Like map_handle_keys but use a null terminates string with untranslated
* keys like that are converted here before calling map_handle_keys.
*/
void map_handle_string(Client *c, const char *str, gboolean use_map)
{
int len;
char *keys = convert_keys(str, strlen(str), &len);
map_handle_keys(c, (guchar*)keys, len, use_map);
}
void map_insert(Client *c, const char *in, const char *mapped, char mode, gboolean remap)
{
int inlen, mappedlen;
char *lhs = convert_keys(in, strlen(in), &inlen);
char *rhs = convert_keys(mapped, strlen(mapped), &mappedlen);
/* if lhs was already mapped, remove this first */
map_delete_by_lhs(c, lhs, inlen, mode);
Map *new = g_slice_new(Map);
new->in = lhs;
new->inlen = inlen;
new->mapped = rhs;
new->mappedlen = mappedlen;
new->mode = mode;
new->remap = remap;
c->map.list = g_slist_prepend(c->map.list, new);
}
gboolean map_delete(Client *c, const char *in, char mode)
{
int len;
char *lhs = convert_keys(in, strlen(in), &len);
return map_delete_by_lhs(c, lhs, len, mode);
}
/**
* Handle all key events, convert the key event into the internal used ASCII
* representation and put this into the key queue to be mapped.
*/
gboolean on_map_keypress(GtkWidget *widget, GdkEventKey* event, Client *c)
{
if (events.processing) {
/* events are processing, pass all keys unmodified */
return FALSE;
}
guint state = event->state;
guint keyval = event->keyval;
guchar string[32];
int len;
len = keyval_to_string(keyval, state, string);
/* translate iso left tab to shift tab */
if (keyval == GDK_ISO_Left_Tab) {
keyval = GDK_Tab;
state |= GDK_SHIFT_MASK;
}
if (len == 0 || len == 1) {
for (int i = 0; i < LENGTH(special_keys); i++) {
if (special_keys[i].keyval == keyval
&& (special_keys[i].state == 0 || state & special_keys[i].state)
) {
state &= ~special_keys[i].state;
string[0] = CSI;
string[1] = special_keys[i].one;
string[2] = special_keys[i].two;
len = 3;
break;
}
}
}
if (len == 0) {
/* mark all unknown key events as unhandled to not break some gtk features
* like to copy clipboard content into inputbox */
return FALSE;
}
/* set flag to notify that the key was typed by the user */
c->state.typed = TRUE;
c->state.processed_key = TRUE;
queue_event(event);
MapState res = map_handle_keys(c, string, len, true);
if (res != MAP_AMBIGUOUS) {
if (c->state.typed && !c->state.processed_key) {
/* events ready to be consumed */
process_events();
} else {
/* no ambiguous - key processed elsewhere */
free_events();
}
}
/* reset the typed flag */
c->state.typed = FALSE;
/* prevent input from going to GDK - input is sent via process_events(); */
return TRUE;
}
/**
* Translate given key string into a internal representation -> \n.
* The len of the translated key sequence is put into given *len pointer.
*/
static char *convert_keylabel(const char *in, int inlen, int *len)
{
for (int i = 0; i < LENGTH(key_labels); i++) {
if (inlen == key_labels[i].len
&& !strncmp(key_labels[i].label, in, inlen)
) {
*len = key_labels[i].chlen;
return key_labels[i].ch;
}
}
*len = 0;
return NULL;
}
/**
* Converts a keysequence into a internal raw keysequence.
* Returned keyseqence must be freed if not used anymore.
*/
static char *convert_keys(const char *in, int inlen, int *len)
{
int symlen, rawlen;
char *dest;
char ch[1];
const char *p, *raw;
GString *str = g_string_new("");
*len = 0;
for (p = in; p < &in[inlen]; p++) {
/* if we encounter the sequence \< we replace it with just < but act
* like it's a non-special character */
if (*p == '\\' && p+1 < &in[inlen] && *(p+1) == '<') {
g_string_append_len(str, p+1, 1);
*len += 1;
++p;
continue;
}
/* if it starts not with < we can add it literally */
if (*p != '<') {
g_string_append_len(str, p, 1);
*len += 1;
continue;
}
/* search matching > of symbolic name */
symlen = 1;
do {
if (&p[symlen] == &in[inlen]
|| p[symlen] == '<'
|| p[symlen] == ' '
) {
break;
}
} while (p[symlen++] != '>');
raw = NULL;
rawlen = 0;
/* check if we found a real keylabel */
if (p[symlen - 1] == '>') {
if (symlen == 5 && p[2] == '-') {
/* is it a */
if (p[1] == 'C') {
if (VB_IS_UPPER(p[3])) {
ch[0] = p[3] - 0x40;
raw = ch;
rawlen = 1;
} else if (VB_IS_LOWER(p[3])) {
ch[0] = p[3] - 0x60;
raw = ch;
rawlen = 1;
}
}
}
/* if we could not convert it jet - try to translate the label */
if (!rawlen) {
raw = convert_keylabel(p, symlen, &rawlen);
}
}
/* we found no known keylabel - so use the chars literally */
if (!rawlen) {
rawlen = symlen;
raw = p;
}
/* write the converted keylabel into the buffer */
g_string_append_len(str, raw, rawlen);
/* move p after the keylabel */
p += symlen - 1;
*len += rawlen;
}
/* don't free the character data of the GString */
dest = g_string_free(str, FALSE);
return dest;
}
/**
* Timeout function to signalize a key timeout to the map.
*/
static gboolean do_timeout(Client *c)
{
/* signalize the timeout to the key handler */
map_handle_keys(c, (guchar*)"", 0, TRUE);
/* consume any unprocessed events */
process_events();
/* we return TRUE to not automatically remove the resource - this is
* required to prevent critical error when we remove the source in
* map_handle_keys where we don't know if the timeout was called or not */
return TRUE;
}
static void free_map(Map *map)
{
g_free(map->in);
g_free(map->mapped);
g_slice_free(Map, map);
}
/**
* Translate a keyvalue to utf-8 encoded and null terminated string.
* Given string must have room for 6 bytes.
*/
static int keyval_to_string(guint keyval, guint state, guchar *string)
{
int len;
guint32 uc;
len = 1;
switch (keyval) {
case GDK_Tab:
case GDK_KP_Tab:
case GDK_ISO_Left_Tab:
string[0] = KEY_TAB;
break;
case GDK_Linefeed:
string[0] = KEY_NL;
break;
case GDK_Return:
case GDK_ISO_Enter:
case GDK_3270_Enter:
string[0] = KEY_CR;
break;
case GDK_Escape:
string[0] = KEY_ESC;
break;
case GDK_BackSpace:
string[0] = KEY_BS;
break;
default:
if ((uc = gdk_keyval_to_unicode(keyval))) {
if ((state & GDK_CONTROL_MASK) && uc >= 0x20 && uc < 0x80) {
if (uc >= '@') {
string[0] = uc & 0x1f;
} else if (uc == '8') {
string[0] = KEY_BS;
} else {
string[0] = uc;
}
} else {
/* translate a normal key to utf-8 */
len = utf_char2bytes((guint)uc, string);
}
} else {
len = 0;
}
break;
}
return len;
}
static gboolean map_delete_by_lhs(Client *c, const char *lhs, int len, char mode)
{
for (GSList *l = c->map.list; l != NULL; l = l->next) {
Map *m = (Map*)l->data;
/* remove only if the map's lhs matches the given key sequence */
if (m->mode == mode && m->inlen == len && !strcmp(m->in, lhs)) {
/* remove the found list item */
c->map.list = g_slist_delete_link(c->map.list, l);
free_map(m);
return TRUE;
}
}
return FALSE;
}
/**
* Put the given char onto the show command buffer.
*/
static void showcmd(Client *c, int ch)
{
char *translated;
int old, extra, overflow;
if (ch) {
translated = transchar(ch);
old = strlen(c->map.showcmd);
extra = strlen(translated);
overflow = old + extra - SHOWCMD_LEN;
if (overflow > 0) {
memmove(c->map.showcmd, c->map.showcmd + overflow, old - overflow + 1);
}
strcat(c->map.showcmd, translated);
} else {
c->map.showcmd[0] = '\0';
}
#ifndef TESTLIB
/* show the typed keys */
gtk_label_set_text(GTK_LABEL(c->statusbar.cmd), c->map.showcmd);
#endif
}
/**
* Translate a singe char into a readable representation to be show to the
* user in status bar.
*/
static char *transchar(int c)
{
static char trans[5];
int i = 0;
if (VB_IS_CTRL(c)) {
trans[i++] = '^';
trans[i++] = CTRL(c);
} else if ((unsigned)c >= 0x80) {
/* convert the lower 4 bits of byte n to its hex character */
#define NR2HEX(n) (n & 0xf) <= 9 ? (n & 0xf) + '0' : (c & 0xf) - 10 + 'a'
trans[i++] = '<';
trans[i++] = NR2HEX((unsigned)c >> 4);
trans[i++] = NR2HEX((unsigned)c);
trans[i++] = '>';
#undef NR2HEX
} else {
trans[i++] = c;
}
trans[i++] = '\0';
return trans;
}
static int utf_char2bytes(guint c, guchar *buf)
{
if (c < 0x80) {
buf[0] = c;
return 1;
}
if (c < 0x800) {
buf[0] = 0xc0 + (c >> 6);
buf[1] = 0x80 + (c & 0x3f);
return 2;
}
if (c < 0x10000) {
buf[0] = 0xe0 + (c >> 12);
buf[1] = 0x80 + ((c >> 6) & 0x3f);
buf[2] = 0x80 + (c & 0x3f);
return 3;
}
if (c < 0x200000) {
buf[0] = 0xf0 + (c >> 18);
buf[1] = 0x80 + ((c >> 12) & 0x3f);
buf[2] = 0x80 + ((c >> 6) & 0x3f);
buf[3] = 0x80 + (c & 0x3f);
return 4;
}
if (c < 0x4000000) {
buf[0] = 0xf8 + (c >> 24);
buf[1] = 0x80 + ((c >> 18) & 0x3f);
buf[2] = 0x80 + ((c >> 12) & 0x3f);
buf[3] = 0x80 + ((c >> 6) & 0x3f);
buf[4] = 0x80 + (c & 0x3f);
return 5;
}
buf[0] = 0xfc + (c >> 30);
buf[1] = 0x80 + ((c >> 24) & 0x3f);
buf[2] = 0x80 + ((c >> 18) & 0x3f);
buf[3] = 0x80 + ((c >> 12) & 0x3f);
buf[4] = 0x80 + ((c >> 6) & 0x3f);
buf[5] = 0x80 + (c & 0x3f);
return 6;
}
/**
* Append an event into the queue.
*/
static void queue_event(GdkEventKey *e)
{
events.list = g_slist_append(events.list, gdk_event_copy((GdkEvent*)e));
}
/**
* Process events in the queue, sending the key events to GDK.
*/
static void process_events(void)
{
for (GSList *l = events.list; l != NULL; l = l->next) {
process_event((GdkEventKey*)l->data);
/* TODO take into account qk mapped key? */
}
free_events();
}
/**
* Clear the events list and free the allocated memory.
*/
static void free_events(void)
{
if (events.list) {
g_slist_free_full(events.list, (GDestroyNotify)gdk_event_free);
events.list = NULL;
}
}
static void process_event(GdkEventKey* event)
{
if (event) {
/* signal not to queue other events */
events.processing = TRUE;
gtk_main_do_event((GdkEvent*)event);
events.processing = FALSE;
}
}
fanglingsu-vimb-448e7e2/src/map.h 0000664 0000000 0000000 00000002460 15145416123 0016667 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _MAP_H
#define _MAP_H
typedef enum {
MAP_DONE,
MAP_AMBIGUOUS,
MAP_NOMATCH
} MapState;
void map_init(Client *c);
void map_cleanup(Client *c);
MapState map_handle_keys(Client *c, const guchar *keys, int keylen, gboolean use_map);
void map_handle_string(Client *c, const char *str, gboolean use_map);
void map_insert(Client *c, const char *in, const char *mapped, char mode, gboolean remap);
gboolean map_delete(Client *c, const char *in, char mode);
gboolean on_map_keypress(GtkWidget *widget, GdkEventKey* event, Client *c);
#endif /* end of include guard: _MAP_H */
fanglingsu-vimb-448e7e2/src/normal.c 0000664 0000000 0000000 00000063324 15145416123 0017403 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include "ascii.h"
#include "command.h"
#include "config.h"
#include "hints.h"
#include "ext-proxy.h"
#include "main.h"
#include "normal.h"
#include "scripts/scripts.h"
#include "util.h"
#include "ext-proxy.h"
typedef enum {
PHASE_START,
PHASE_KEY2,
PHASE_KEY3,
PHASE_REG,
PHASE_COMPLETE,
} Phase;
typedef struct NormalCmdInfo_s {
int count; /* count used for the command */
char key; /* command key */
char key2; /* second command key (optional) */
char key3; /* third command key only for hinting */
char reg; /* char for the yank register */
Phase phase; /* current parsing phase */
} NormalCmdInfo;
static NormalCmdInfo info = {0, '\0', '\0', PHASE_START};
typedef VbResult (*NormalCommand)(Client *c, const NormalCmdInfo *info);
static VbResult normal_clear_input(Client *c, const NormalCmdInfo *info);
static VbResult normal_descent(Client *c, const NormalCmdInfo *info);
static VbResult normal_ex(Client *c, const NormalCmdInfo *info);
static VbResult normal_fire(Client *c, const NormalCmdInfo *info);
static VbResult normal_focus_last_active(Client *c, const NormalCmdInfo *info);
static VbResult normal_g_cmd(Client *c, const NormalCmdInfo *info);
static VbResult normal_hint(Client *c, const NormalCmdInfo *info);
static VbResult normal_do_hint(Client *c, const char *prompt);
static VbResult normal_increment_decrement(Client *c, const NormalCmdInfo *info);
static VbResult normal_input_open(Client *c, const NormalCmdInfo *info);
static VbResult normal_mark(Client *c, const NormalCmdInfo *info);
static void jump (Client *c, const guint64 p);
static void jump_after_load(WebKitWebView *webview, WebKitLoadEvent event, Client *c);
static VbResult normal_navigate(Client *c, const NormalCmdInfo *info);
static VbResult normal_open_clipboard(Client *c, const NormalCmdInfo *info);
static VbResult normal_open(Client *c, const NormalCmdInfo *info);
static VbResult normal_pass(Client *c, const NormalCmdInfo *info);
static VbResult normal_prevnext(Client *c, const NormalCmdInfo *info);
static VbResult normal_queue(Client *c, const NormalCmdInfo *info);
static VbResult normal_quit(Client *c, const NormalCmdInfo *info);
static VbResult normal_scroll(Client *c, const NormalCmdInfo *info);
static VbResult normal_search(Client *c, const NormalCmdInfo *info);
static VbResult normal_search_selection(Client *c, const NormalCmdInfo *info);
static VbResult normal_view_inspector(Client *c, const NormalCmdInfo *info);
static VbResult normal_view_source(Client *c, const NormalCmdInfo *info);
static void normal_view_source_loaded(WebKitWebResource *resource, GAsyncResult *res, Client *c);
static VbResult normal_yank(Client *c, const NormalCmdInfo *info);
static VbResult normal_zoom(Client *c, const NormalCmdInfo *info);
static struct {
NormalCommand func;
} commands[] = {
/* NUL 0x00 */ {NULL},
/* ^A 0x01 */ {normal_increment_decrement},
/* ^B 0x02 */ {normal_scroll},
/* ^C 0x03 */ {normal_navigate},
/* ^D 0x04 */ {normal_scroll},
/* ^E 0x05 */ {NULL},
/* ^F 0x06 */ {normal_scroll},
/* ^G 0x07 */ {NULL},
/* ^H 0x08 */ {NULL},
/* ^I 0x09 */ {normal_navigate},
/* ^J 0x0a */ {NULL},
/* ^K 0x0b */ {NULL},
/* ^L 0x0c */ {NULL},
/* ^M 0x0d */ {normal_fire},
/* ^N 0x0e */ {NULL},
/* ^O 0x0f */ {normal_navigate},
/* ^P 0x10 */ {normal_queue},
/* ^Q 0x11 */ {normal_quit},
/* ^R 0x12 */ {NULL},
/* ^S 0x13 */ {NULL},
/* ^T 0x14 */ {NULL},
/* ^U 0x15 */ {normal_scroll},
/* ^V 0x16 */ {NULL},
/* ^W 0x17 */ {NULL},
/* ^X 0x18 */ {normal_increment_decrement},
/* ^Y 0x19 */ {NULL},
/* ^Z 0x1a */ {normal_pass},
/* ^[ 0x1b */ {normal_clear_input},
/* ^\ 0x1c */ {NULL},
/* ^] 0x1d */ {NULL},
/* ^^ 0x1e */ {NULL},
/* ^_ 0x1f */ {NULL},
/* SPC 0x20 */ {NULL},
/* ! 0x21 */ {NULL},
/* " 0x22 */ {NULL},
/* # 0x23 */ {normal_search_selection},
/* $ 0x24 */ {normal_scroll},
/* % 0x25 */ {NULL},
/* & 0x26 */ {NULL},
/* ' 0x27 */ {normal_mark},
/* ( 0x28 */ {NULL},
/* ) 0x29 */ {NULL},
/* * 0x2a */ {normal_search_selection},
/* + 0x2b */ {NULL},
/* , 0x2c */ {NULL},
/* - 0x2d */ {NULL},
/* . 0x2e */ {NULL},
/* / 0x2f */ {normal_ex},
/* 0 0x30 */ {normal_scroll},
/* 1 0x31 */ {NULL},
/* 2 0x32 */ {NULL},
/* 3 0x33 */ {NULL},
/* 4 0x34 */ {NULL},
/* 5 0x35 */ {NULL},
/* 6 0x36 */ {NULL},
/* 7 0x37 */ {NULL},
/* 8 0x38 */ {NULL},
/* 9 0x39 */ {NULL},
/* : 0x3a */ {normal_ex},
/* ; 0x3b */ {normal_hint},
/* < 0x3c */ {NULL},
/* = 0x3d */ {NULL},
/* > 0x3e */ {NULL},
/* ? 0x3f */ {normal_ex},
/* @ 0x40 */ {NULL},
/* A 0x41 */ {NULL},
/* B 0x42 */ {NULL},
/* C 0x43 */ {NULL},
/* D 0x44 */ {NULL},
/* E 0x45 */ {NULL},
/* F 0x46 */ {normal_ex},
/* G 0x47 */ {normal_scroll},
/* H 0x48 */ {NULL},
/* I 0x49 */ {NULL},
/* J 0x4a */ {NULL},
/* K 0x4b */ {NULL},
/* L 0x4c */ {NULL},
/* M 0x4d */ {NULL},
/* N 0x4e */ {normal_search},
/* O 0x4f */ {normal_input_open},
/* P 0x50 */ {normal_open_clipboard},
/* Q 0x51 */ {NULL},
/* R 0x52 */ {normal_navigate},
/* S 0x53 */ {NULL},
/* T 0x54 */ {normal_input_open},
/* U 0x55 */ {normal_open},
/* V 0x56 */ {NULL},
/* W 0x57 */ {NULL},
/* X 0x58 */ {NULL},
/* Y 0x59 */ {normal_yank},
/* Z 0x5a */ {NULL},
/* [ 0x5b */ {normal_prevnext},
/* \ 0x5c */ {NULL},
/* ] 0x5d */ {normal_prevnext},
/* ^ 0x5e */ {NULL},
/* _ 0x5f */ {NULL},
/* ` 0x60 */ {NULL},
/* a 0x61 */ {NULL},
/* b 0x62 */ {NULL},
/* c 0x63 */ {NULL},
/* d 0x64 */ {NULL},
/* e 0x65 */ {NULL},
/* f 0x66 */ {normal_ex},
/* g 0x67 */ {normal_g_cmd},
/* h 0x68 */ {normal_scroll},
/* i 0x69 */ {normal_focus_last_active},
/* j 0x6a */ {normal_scroll},
/* k 0x6b */ {normal_scroll},
/* l 0x6c */ {normal_scroll},
/* m 0x6d */ {normal_mark},
/* n 0x6e */ {normal_search},
/* o 0x6f */ {normal_input_open},
/* p 0x70 */ {normal_open_clipboard},
/* q 0x71 */ {NULL},
/* r 0x72 */ {normal_navigate},
/* s 0x73 */ {NULL},
/* t 0x74 */ {normal_input_open},
/* u 0x75 */ {normal_open},
/* v 0x76 */ {NULL},
/* w 0x77 */ {NULL},
/* x 0x78 */ {NULL},
/* y 0x79 */ {normal_yank},
/* z 0x7a */ {normal_zoom},
/* { 0x7b */ {NULL},
/* | 0x7c */ {NULL},
/* } 0x7d */ {NULL},
/* ~ 0x7e */ {NULL},
/* DEL 0x7f */ {NULL},
};
extern struct Vimb vb;
/**
* Function called when vimb enters the normal mode.
*/
void normal_enter(Client *c)
{
/* Make sure that when the browser area becomes visible, it will get mouse
* and keyboard events */
gtk_widget_grab_focus(GTK_WIDGET(c->webview));
hints_clear(c);
}
/**
* Called when the normal mode is left.
*/
void normal_leave(Client *c)
{
command_search(c, &((Arg){0, NULL}), FALSE);
}
/**
* Handles the keypress events from webview and inputbox.
*/
VbResult normal_keypress(Client *c, int key)
{
VbResult res;
switch (info.phase) {
case PHASE_START:
if (info.count == 0 && key == '0') {
info.key = key;
info.phase = PHASE_COMPLETE;
} else if (VB_IS_DIGIT(key)) {
info.count = info.count * 10 + key - '0';
} else if (strchr(";zg[]'m", key)) {
/* handle commands that needs additional char */
info.phase = PHASE_KEY2;
info.key = key;
c->mode->flags |= FLAG_NOMAP;
} else if (key == '"') {
info.phase = PHASE_REG;
c->mode->flags |= FLAG_NOMAP;
} else {
info.key = key;
info.phase = PHASE_COMPLETE;
}
break;
case PHASE_KEY2:
info.key2 = key;
/* hinting g; mode requires a third key */
if (info.key == 'g' && info.key2 == ';') {
info.phase = PHASE_KEY3;
c->mode->flags |= FLAG_NOMAP;
} else {
info.phase = PHASE_COMPLETE;
}
break;
case PHASE_KEY3:
info.key3 = key;
info.phase = PHASE_COMPLETE;
break;
case PHASE_REG:
if (strchr(REG_CHARS, key)) {
info.reg = key;
info.phase = PHASE_START;
} else {
info.phase = PHASE_COMPLETE;
}
break;
case PHASE_COMPLETE:
break;
}
if (info.phase == PHASE_COMPLETE) {
/* TODO allow more commands - some that are looked up via command key
* direct and those that are searched via binary search */
if ((guchar)info.key <= LENGTH(commands) && commands[(guchar)info.key].func) {
res = commands[(guchar)info.key].func(c, &info);
} else {
/* let gtk handle the keyevent if we have no command attached to it */
c->state.processed_key = FALSE;
res = RESULT_COMPLETE;
}
/* unset the info */
info.key = info.key2 = info.key3 = info.count = info.reg = 0;
info.phase = PHASE_START;
} else {
res = RESULT_MORE;
}
return res;
}
/**
* Function called when vimb enters the passthrough mode.
*/
void pass_enter(Client *c)
{
vb_modelabel_update(c, "-- PASS THROUGH --");
}
/**
* Called when passthrough mode is left.
*/
void pass_leave(Client *c)
{
ext_proxy_eval_script(c, "document.activeElement.blur();", NULL);
vb_modelabel_update(c, "");
}
VbResult pass_keypress(Client *c, int key)
{
if (key == CTRL('[')) { /* esc */
vb_enter(c, 'n');
}
c->state.processed_key = FALSE;
return RESULT_COMPLETE;
}
static VbResult normal_clear_input(Client *c, const NormalCmdInfo *info)
{
/* If an element requested the fullscreen - the shoudl cause to
* leave this fullscreen mode. */
if (c->state.is_fullscreen) {
/* Unset the processed_key to indicate that the was not handled
* by us and letting the event bubble up. So that webkit handles the
* key for us to leave the fullscreen mode. */
c->state.processed_key = FALSE;
return RESULT_COMPLETE;
}
gtk_widget_grab_focus(GTK_WIDGET(c->webview));
/* Clear the inputbox and change the style to normal to reset also the
* possible colored error background. */
vb_echo(c, MSG_NORMAL, FALSE, "");
/* Unset search highlightning. */
command_search(c, &((Arg){0, NULL}), FALSE);
return RESULT_COMPLETE;
}
static VbResult normal_descent(Client *c, const NormalCmdInfo *info)
{
int count = info->count ? info->count : 1;
const char *uri, *p = NULL, *domain = NULL;
uri = c->state.uri;
/* get domain part */
if (!uri || !*uri
|| !(domain = strstr(uri, "://"))
|| !(domain = strchr(domain + 3, '/'))
) {
return RESULT_ERROR;
}
switch (info->key2) {
case 'U':
p = domain;
break;
case 'u':
/* start at the end */
p = uri + strlen(uri);
/* if last char is / increment count to step over this first */
if (*(p - 1) == '/') {
count++;
}
for (int i = 0; i < count; i++) {
while (*(p--) != '/') {
if (p == uri) {
/* reach the beginning */
return RESULT_ERROR;
}
}
}
/* keep the last / in uri */
p++;
break;
}
/* if the url is shorter than the domain use the domain instead */
if (p < domain) {
p = domain;
}
Arg a = {TARGET_CURRENT};
a.s = g_strndup(uri, p - uri + 1);
vb_load_uri(c, &a);
g_free(a.s);
return RESULT_COMPLETE;
}
static VbResult normal_ex(Client *c, const NormalCmdInfo *info)
{
if (info->key == 'F') {
vb_enter_prompt(c, 'c', ";t", TRUE);
} else if (info->key == 'f') {
vb_enter_prompt(c, 'c', ";o", TRUE);
} else {
char prompt[2] = {info->key, '\0'};
vb_enter_prompt(c, 'c', prompt, TRUE);
}
return RESULT_COMPLETE;
}
static VbResult normal_fire(Client *c, const NormalCmdInfo *info)
{
/* If searching is currently active - click link containing current search
* highlight. We use the search_matches as indicator that the searching is
* active. */
if (c->state.search.active) {
ext_proxy_eval_script(c, "getSelection().anchorNode.parentNode.click();", NULL);
return RESULT_COMPLETE;
}
return RESULT_ERROR;
}
static VbResult normal_focus_last_active(Client *c, const NormalCmdInfo *info)
{
GVariant *variant;
gboolean focused;
variant = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.focus();");
if (variant == NULL) {
g_warning("cannot get current selection: failed to evaluate js");
return RESULT_ERROR;
}
g_variant_get(variant, "(bs)", &focused, NULL);
if (!focused) {
ext_proxy_focus_input(c);
}
return RESULT_ERROR;
}
static VbResult normal_g_cmd(Client *c, const NormalCmdInfo *info)
{
Arg a;
switch (info->key2) {
case ';': {
const char prompt[4] = {'g', ';', info->key3, 0};
return normal_do_hint(c, prompt);
}
case 'F':
return normal_view_inspector(c, info);
case 'f':
return normal_view_source(c, info);
case 'g':
return normal_scroll(c, info);
case 'H':
case 'h':
a.i = info->key2 == 'H' ? TARGET_NEW : TARGET_CURRENT;
a.s = NULL;
vb_load_uri(c, &a);
return RESULT_COMPLETE;
case 'i':
ext_proxy_focus_input(c);
return RESULT_COMPLETE;
case 'U':
case 'u':
return normal_descent(c, info);
}
return RESULT_ERROR;
}
static VbResult normal_hint(Client *c, const NormalCmdInfo *info)
{
const char prompt[3] = {info->key, info->key2, 0};
/* Save the current register char to make it available in case of ;y
* hinting. This is only a hack, because we don't need this state variable
* somewhere else - it's only use is for hinting. It might be better to
* allow to set various data to the mode itself to avoid toggling
* variables in global skope. */
c->state.current_register = info->reg;
return normal_do_hint(c, prompt);
}
static VbResult normal_do_hint(Client *c, const char *prompt)
{
/* TODO check if the prompt is of a valid hint mode */
vb_enter_prompt(c, 'c', prompt, TRUE);
return RESULT_COMPLETE;
}
static VbResult normal_increment_decrement(Client *c, const NormalCmdInfo *info)
{
char *js;
int count = info->count ? info->count : 1;
js = g_strdup_printf(JS_INCREMENT_URI_NUMBER, info->key == CTRL('A') ? count : -count);
ext_proxy_eval_script(c, js, NULL);
g_free(js);
return RESULT_COMPLETE;
}
static VbResult normal_input_open(Client *c, const NormalCmdInfo *info)
{
if (strchr("ot", info->key)) {
vb_echo(c, MSG_NORMAL, FALSE,
":%s ", info->key == 't' ? "tabopen" : "open");
} else {
vb_echo(c, MSG_NORMAL, FALSE,
":%s %s", info->key == 'T' ? "tabopen" : "open", c->state.uri);
}
/* switch mode after setting the input text to not trigger the
* commands modes input change handler */
vb_enter_prompt(c, 'c', ":", FALSE);
return RESULT_COMPLETE;
}
static void jump(Client *c, guint64 p) {
char *js = g_strdup_printf("window.scroll(window.screenLeft,%" G_GUINT64_FORMAT ");", p);
ext_proxy_eval_script(c, js, NULL);
g_free(js);
}
static guint64 jump_pos;
static void jump_after_load(WebKitWebView *webview, WebKitLoadEvent event, Client *c) {
if (event == WEBKIT_LOAD_FINISHED) {
jump(c, jump_pos);
jump_pos = 0;
}
}
static VbResult normal_mark(Client *c, const NormalCmdInfo *info)
{
guint64 current_pos;
char *current_uri;
char *mark;
Arg *arg;
int idx;
/* check if the second char is a valid mark char */
if (!(mark = strchr(MARK_CHARS, info->key2)) && !(mark = strchr(GLOBAL_MARK_CHARS, info->key2))) {
return RESULT_ERROR;
}
if (islower(*mark)) {
/* get the index of the mark char */
idx = mark - MARK_CHARS;
if ('m' == info->key) {
c->state.marks[idx] = c->state.scroll_top;
} else {
/* check if the mark was set */
if ((int)(c->state.marks[idx] - .5) < 0) {
return RESULT_ERROR;
}
current_pos = c->state.scroll_top;
jump(c, c->state.marks[idx]);
/* save previous adjust as last position */
c->state.marks[MARK_TICK] = current_pos;
}
} else {
/* get the index of the mark char */
idx = mark - GLOBAL_MARK_CHARS;
if ('m' == info->key) {
g_free(c->state.global_marks[idx].uri);
c->state.global_marks[idx].pos = c->state.scroll_top;
c->state.global_marks[idx].uri = g_strdup(c->state.uri);
} else {
/* check if the mark was set */
if (!c->state.global_marks[idx].uri) {
return RESULT_ERROR;
}
current_pos = c->state.scroll_top;
current_uri = c->state.uri;
jump_pos = c->state.global_marks[idx].pos;
g_signal_handlers_disconnect_by_func(c->webview, jump_after_load, c);
g_signal_connect(c->webview, "load-changed", G_CALLBACK(jump_after_load), c);
/* jump to the uri */
arg = g_new(Arg, 1);
arg->i = TARGET_CURRENT;
arg->s = c->state.global_marks[idx].uri;
vb_load_uri(c, arg);
g_free(arg);
/* save previous adjust as last position */
g_free(c->state.global_marks[MARK_TICK].uri);
c->state.global_marks[MARK_TICK].pos = current_pos;
c->state.global_marks[MARK_TICK].uri = g_strdup(current_uri);
}
}
return RESULT_COMPLETE;
}
static VbResult normal_navigate(Client *c, const NormalCmdInfo *info)
{
int count;
WebKitBackForwardList *list;
WebKitBackForwardListItem *item;
WebKitWebView *view = c->webview;
switch (info->key) {
case CTRL('I'): /* fall through */
case CTRL('O'):
count = info->count ? info->count : 1;
if (info->key == CTRL('O')) {
if (webkit_web_view_can_go_back(view)) {
list = webkit_web_view_get_back_forward_list(view);
item = webkit_back_forward_list_get_nth_item(list, -1 * count);
webkit_web_view_go_to_back_forward_list_item(view, item);
}
} else {
if (webkit_web_view_can_go_forward(view)) {
list = webkit_web_view_get_back_forward_list(view);
item = webkit_back_forward_list_get_nth_item(list, count);
webkit_web_view_go_to_back_forward_list_item(view, item);
}
}
break;
case 'r':
webkit_web_view_reload(view);
break;
case 'R':
webkit_web_view_reload_bypass_cache(view);
break;
case CTRL('C'):
webkit_web_view_stop_loading(view);
break;
}
return RESULT_COMPLETE;
}
static VbResult normal_open_clipboard(Client *c, const NormalCmdInfo *info)
{
Arg a = {info->key == 'P' ? TARGET_NEW : TARGET_CURRENT};
/* if register is not the default - read out of the internal register */
if (info->reg) {
a.s = g_strdup(vb_register_get(c, info->reg));
} else {
/* if no register is given use the system clipboard */
a.s = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
if (!a.s) {
a.s = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_NONE));
}
}
if (a.s) {
vb_load_uri(c, &a);
g_free(a.s);
return RESULT_COMPLETE;
}
return RESULT_ERROR;
}
/**
* Open the last closed page.
*/
static VbResult normal_open(Client *c, const NormalCmdInfo *info)
{
Arg a;
if (!vb.files[FILES_CLOSED]) {
return RESULT_ERROR;
}
a.i = info->key == 'U' ? TARGET_NEW : TARGET_CURRENT;
a.s = util_file_pop_line(vb.files[FILES_CLOSED], NULL);
if (!a.s) {
return RESULT_ERROR;
}
vb_load_uri(c, &a);
g_free(a.s);
return RESULT_COMPLETE;
}
static VbResult normal_pass(Client *c, const NormalCmdInfo *info)
{
vb_enter(c, 'p');
return RESULT_COMPLETE;
}
static VbResult normal_prevnext(Client *c, const NormalCmdInfo *info)
{
#if 0 /* TODO implement outside of hints.js */
int count = info->count ? info->count : 1;
if (info->key2 == ']') {
hints_follow_link(FALSE, count);
} else if (info->key2 == '[') {
hints_follow_link(TRUE, count);
} else {
return RESULT_ERROR;
}
#endif
return RESULT_COMPLETE;
}
static VbResult normal_queue(Client *c, const NormalCmdInfo *info)
{
#ifdef FEATURE_QUEUE
command_queue(c, &((Arg){COMMAND_QUEUE_POP}));
#endif
return RESULT_COMPLETE;
}
static VbResult normal_quit(Client *c, const NormalCmdInfo *info)
{
vb_quit(c, FALSE);
return RESULT_COMPLETE;
}
static VbResult normal_scroll(Client *c, const NormalCmdInfo *info)
{
char *js;
js = g_strdup_printf("vbscroll('%c',%d,%d,%d);", info->key, c->config.scrollstep,
info->count, c->config.smooth_scrolling);
ext_proxy_eval_script(c, js, NULL);
g_free(js);
return RESULT_COMPLETE;
}
static VbResult normal_search(Client *c, const NormalCmdInfo *info)
{
int count = (info->count > 0) ? info->count : 1;
command_search(c, &((Arg){info->key == 'n' ? count : -count, NULL}), FALSE);
return RESULT_COMPLETE;
}
static VbResult normal_search_selection(Client *c, const NormalCmdInfo *info)
{
int count;
char *query;
/* there is no function to get the selected text so we copy current
* selection to clipboard */
webkit_web_view_execute_editing_command(c->webview, WEBKIT_EDITING_COMMAND_COPY);
query = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
if (!query) {
return RESULT_ERROR;
}
count = (info->count > 0) ? info->count : 1;
command_search(c, &((Arg){info->key == '*' ? count : -count, query}), TRUE);
g_free(query);
return RESULT_COMPLETE;
}
static VbResult normal_view_inspector(Client *c, const NormalCmdInfo *info)
{
WebKitSettings *settings;
settings = webkit_web_view_get_settings(c->webview);
/* Try to get the inspected uri to identify if the inspector is shown at
* the time or not. */
if (webkit_web_inspector_is_attached(c->inspector)) {
webkit_web_inspector_close(c->inspector);
} else if (webkit_settings_get_enable_developer_extras(settings)) {
webkit_web_inspector_show(c->inspector);
} else {
/* Inform the user on attempt to enable webinspector when the
* developer extra are not enabled. */
vb_echo(c, MSG_ERROR, TRUE, "webinspector is not enabled");
return RESULT_ERROR;
}
return RESULT_COMPLETE;
}
static VbResult normal_view_source(Client *c, const NormalCmdInfo *info)
{
WebKitWebResource *resource;
if ((resource = webkit_web_view_get_main_resource(c->webview)) == NULL) {
return RESULT_ERROR;
}
webkit_web_resource_get_data(resource, NULL,
(GAsyncReadyCallback)normal_view_source_loaded, c);
return RESULT_COMPLETE;
}
static void normal_view_source_loaded(WebKitWebResource *resource,
GAsyncResult *res, Client *c)
{
gsize length;
guchar *data = NULL;
char *text = NULL;
data = webkit_web_resource_get_data_finish(resource, res, &length, NULL);
if (data) {
text = g_strndup((gchar*)data, length);
command_spawn_editor(c, &((Arg){0, (char *)text}), NULL, NULL);
g_free(data);
g_free(text);
}
}
static VbResult normal_yank(Client *c, const NormalCmdInfo *info)
{
Arg a = {info->key == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI};
return command_yank(c, &a, info->reg) ? RESULT_COMPLETE : RESULT_ERROR;
}
static VbResult normal_zoom(Client *c, const NormalCmdInfo *info)
{
float step = 0.1, level, count;
WebKitWebView *view = c->webview;
/* check if the second key is allowed */
if (!strchr("iIoOz", info->key2)) {
return RESULT_ERROR;
}
count = info->count ? (float)info->count : 1.0;
/* zz reset zoom to it's default zoom level */
if (info->key2 == 'z') {
webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(view), FALSE);
webkit_web_view_set_zoom_level(view, c->config.default_zoom / 100.0);
return RESULT_COMPLETE;
}
level= webkit_web_view_get_zoom_level(view);
/* calculate the new zoom level */
if (info->key2 == 'i' || info->key2 == 'I') {
level += ((float)count * step);
} else {
level -= ((float)count * step);
}
/* apply the new zoom level */
webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(view), VB_IS_LOWER(info->key2));
webkit_web_view_set_zoom_level(view, level);
return RESULT_COMPLETE;
}
fanglingsu-vimb-448e7e2/src/normal.h 0000664 0000000 0000000 00000002053 15145416123 0017400 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _NORMAL_H
#define _NORMAL_H
#include "config.h"
#include "main.h"
void normal_enter(Client *c);
void normal_leave(Client *c);
VbResult normal_keypress(Client *c, int key);
void pass_enter(Client *c);
void pass_leave(Client *c);
VbResult pass_keypress(Client *c, int key);
#endif /* end of include guard: _NORMAL_H */
fanglingsu-vimb-448e7e2/src/scripts/ 0000775 0000000 0000000 00000000000 15145416123 0017426 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/src/scripts/.gitignore 0000664 0000000 0000000 00000000012 15145416123 0021407 0 ustar 00root root 0000000 0000000 scripts.h
fanglingsu-vimb-448e7e2/src/scripts/dom_operations.js 0000664 0000000 0000000 00000032064 15145416123 0023013 0 ustar 00root root 0000000 0000000 /* vimb - DOM Operations Module
*
* This module replaces the deprecated WebKitDOM API with JavaScript-based
* DOM manipulation functions. */
var vimbDomOps = Object.freeze((function() {
'use strict';
/* List of input types that are considered editable. */
var EDITABLE_INPUT_TYPES = [
'', 'text', 'password', 'color', 'date', 'datetime',
'datetime-local', 'email', 'month', 'number', 'search',
'tel', 'time', 'url', 'week'
];
/* Check if an element is editable.
*
* @param {Element} element - The element to check
* @return {boolean} true if element is editable */
function isEditable(element) {
if (!element) {
return false;
}
// Check if element has contentEditable attribute
if (element.isContentEditable) {
return true;
}
// Check if element is a textarea
if (element.tagName === 'TEXTAREA') {
return true;
}
// Check if element is an input with editable type
if (element.tagName === 'INPUT') {
var type = (element.type || '').toLowerCase();
return EDITABLE_INPUT_TYPES.indexOf(type) >= 0;
}
return false;
}
/* Check if an element is visible.
*
* @param {Element} element - The element to check
* @return {boolean} true if element is visible */
function isVisible(element) {
if (!element) {
return false;
}
var rect = element.getBoundingClientRect();
if (!rect || rect.width === 0 || rect.height === 0) {
return false;
}
var style = window.getComputedStyle(element);
return style.display !== 'none' && style.visibility === 'visible';
}
/* Find and focus the first editable input element in the document.
*
* This function searches for input fields and textareas using XPath,
* checks if they are visible, and focuses the first visible one.
* It also recursively searches in iframes.
*
* @param {Document} doc - The document to search (defaults to current document)
* @return {boolean} true if an input was focused, false otherwise */
function focusFirstInput(doc) {
doc = doc || document;
/* XPath expression to find editable elements.
* Uses translate() for case-insensitive matching. */
var xpath = "//input[not(@type) " +
"or translate(@type,'ETX','etx')='text' " +
"or translate(@type,'ADOPRSW','adoprsw')='password' " +
"or translate(@type,'CLOR','clor')='color' " +
"or translate(@type,'ADET','adet')='date' " +
"or translate(@type,'ADEIMT','adeimt')='datetime' " +
"or translate(@type,'ACDEILMOT','acdeilmot')='datetime-local' " +
"or translate(@type,'AEILM','aeilm')='email' " +
"or translate(@type,'HMNOT','hmnot')='month' " +
"or translate(@type,'BEMNRU','bemnru')='number' " +
"or translate(@type,'ACEHRS','acehrs')='search' " +
"or translate(@type,'ELT','elt')='tel' " +
"or translate(@type,'EIMT','eimt')='time' " +
"or translate(@type,'LRU','lru')='url' " +
"or translate(@type,'EKW','ekw')='week' " +
"]|//textarea";
try {
/* Evaluate XPath expression */
var result = doc.evaluate(
xpath,
doc.documentElement,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
/* Iterate through results and focus first visible element */
for (var i = 0; i < result.snapshotLength; i++) {
var element = result.snapshotItem(i);
if (isVisible(element)) {
element.focus();
return true;
}
}
/* If no input found in main document, search in iframes */
var iframes = doc.querySelectorAll('iframe');
for (var j = 0; j < iframes.length; j++) {
try {
var frameDoc = iframes[j].contentDocument;
if (frameDoc && focusFirstInput(frameDoc)) {
return true;
}
} catch (e) {
/* Cross-origin iframe, skip it */
continue;
}
}
} catch (e) {
console.error("focusFirstInput error:", e);
}
return false;
}
/* Get the value of an editable element.
*
* Note: This function is defined in ext-dom.c but not actually used
* anywhere in the codebase. Included for completeness.
*
* @param {Element} element - The element to get value from
* @return {string} The element's value or empty string */
function getEditableValue(element) {
if (!element) {
return '';
}
try {
if (element.isContentEditable) {
return element.innerText || '';
} else if (element.tagName === 'INPUT') {
return element.value || '';
} else if (element.tagName === 'TEXTAREA') {
return element.value || '';
}
} catch (e) {
console.error("getEditableValue error:", e);
}
return '';
}
/* Lock (disable) an input element by ID.
*
* @param {string} elementId - The ID of the element to lock
* @return {boolean} true if element was locked, false if not found */
function lockInput(elementId) {
try {
var elem = document.getElementById(elementId);
if (elem) {
elem.disabled = true;
return true;
}
} catch (e) {
console.error("lockInput error:", e);
}
return false;
}
/* Unlock (enable) and focus an input element by ID.
*
* @param {string} elementId - The ID of the element to unlock
* @return {boolean} true if element was unlocked, false if not found */
function unlockInput(elementId) {
try {
var elem = document.getElementById(elementId);
if (elem) {
elem.disabled = false;
elem.focus();
return true;
}
} catch (e) {
console.error("unlockInput error:", e);
}
return false;
}
/* Get the current scroll position and calculate percentage.
*
* Returns an object with scroll information that can be serialized
* and sent to the main process.
*
* @return {Object} Object with properties: max, percent, top */
function getScrollPosition() {
try {
var de = document.documentElement;
var body = document.body;
if (!de || !body) {
return {max: 0, percent: 0, top: 0};
}
/* Get scroll position (use maximum of documentElement and body) */
var scrollTop = Math.max(
de.scrollTop || 0,
body.scrollTop || 0
);
/* Get total scrollable height */
var scrollHeight = Math.max(
de.scrollHeight || 0,
body.scrollHeight || 0
);
/* Get viewport height */
var clientHeight = window.innerHeight || 0;
/* Calculate maximum scroll distance */
var max = scrollHeight - clientHeight;
var percent = 0;
var top = 0;
if (max > 0) {
percent = Math.round(scrollTop * 100 / max);
top = scrollTop;
}
return {
max: max,
percent: percent,
top: top
};
} catch (e) {
console.error("getScrollPosition error:", e);
return {max: 0, percent: 0, top: 0};
}
}
/* Check if the currently active element is editable.
*
* This is used for focus event handling to determine if input mode
* should be activated.
*
* @param {Document} doc - The document to check (defaults to current document)
* @return {boolean} true if active element is editable */
function checkActiveElement(doc) {
doc = doc || document;
try {
var active = doc.activeElement;
if (!active) {
return false;
}
/* If active element is an iframe, check inside it */
if (active.tagName === 'IFRAME') {
try {
return checkActiveElement(active.contentDocument);
} catch (e) {
/* Cross-origin iframe */
return false;
}
}
/* Check if the active element is editable */
return isEditable(active);
} catch (e) {
console.error("checkActiveElement error:", e);
return false;
}
}
/* Setup event listeners for focus tracking.
*
* This should be called when a page loads to set up listeners
* that will notify the extension when editable elements gain/lose focus.
*
* @param {number} pageId - The WebKit page ID */
function setupFocusTracking(pageId) {
/* Focus event - check if focused element is editable */
window.addEventListener('focus', function(event) {
try {
var isEditable = checkActiveElement();
/* Send message to extension via message handler */
if (window.webkit && window.webkit.messageHandlers &&
window.webkit.messageHandlers.focus) {
window.webkit.messageHandlers.focus.postMessage(
JSON.stringify({
pageId: pageId,
isEditable: isEditable
})
);
}
} catch (e) {
console.error("focus event error:", e);
}
}, true); /* Use capture phase */
/* Blur event - element lost focus */
window.addEventListener('blur', function(event) {
try {
/* Send message indicating no editable element has focus */
if (window.webkit && window.webkit.messageHandlers &&
window.webkit.messageHandlers.focus) {
window.webkit.messageHandlers.focus.postMessage(
JSON.stringify({
pageId: pageId,
isEditable: false
})
);
}
} catch (e) {
console.error("blur event error:", e);
}
}, true); /* Use capture phase */
}
/* Setup event listeners for scroll tracking.
*
* This should be called when a page loads to set up listeners
* that will notify the extension of scroll position changes.
*
* @param {number} pageId - The WebKit page ID */
function setupScrollTracking(pageId) {
window.addEventListener('scroll', function() {
try {
var scrollData = getScrollPosition();
/* Send message to extension via message handler or signal */
/* Note: This might need to use a different communication method
* depending on how the extension is set up */
if (window.webkit && window.webkit.messageHandlers &&
window.webkit.messageHandlers.scroll) {
window.webkit.messageHandlers.scroll.postMessage(
JSON.stringify({
pageId: pageId,
max: scrollData.max,
percent: scrollData.percent,
top: scrollData.top
})
);
}
} catch (e) {
console.error("scroll event error:", e);
}
}, false);
}
/* Initialize all event tracking for a page.
*
* This should be called once when a document is loaded.
*
* @param {number} pageId - The WebKit page ID */
function init(pageId) {
try {
setupFocusTracking(pageId);
setupScrollTracking(pageId);
/* Check initial state */
checkActiveElement();
/* Get initial scroll position */
getScrollPosition();
return true;
} catch (e) {
console.error("vimbDomOps.init error:", e);
return false;
}
}
/* Public API */
return {
isEditable: isEditable,
isVisible: isVisible,
focusFirstInput: focusFirstInput,
getEditableValue: getEditableValue,
lockInput: lockInput,
unlockInput: unlockInput,
getScrollPosition: getScrollPosition,
checkActiveElement: checkActiveElement,
setupFocusTracking: setupFocusTracking,
setupScrollTracking: setupScrollTracking,
init: init
};
})());
fanglingsu-vimb-448e7e2/src/scripts/focus_editor_map_element.js 0000664 0000000 0000000 00000000117 15145416123 0025016 0 ustar 00root root 0000000 0000000 vimb_editor_map.get("%lu").disabled=false;
vimb_editor_map.get("%lu").focus();
fanglingsu-vimb-448e7e2/src/scripts/hints.css 0000664 0000000 0000000 00000001016 15145416123 0021263 0 ustar 00root root 0000000 0000000 span[vimbhint^='label']{
background-color:#fff;
border:1px solid #444;
color:#000;
font:bold .8em monospace;
margin:0;
opacity:0.7;
padding:0px 1px;
position:fixed !important;
z-index:225000
}
*[vimbhint^='hint']{
background-color:#ff0 !important;
color:#000 !important;
transition-delay:all 0 !important;
transition:all 0 !important;
opacity:1 !important
}
*[vimbhint='hint focus']{
background-color:#8f0 !important
}
span[vimbhint='label focus']{
opacity:1;
}
fanglingsu-vimb-448e7e2/src/scripts/hints.js 0000664 0000000 0000000 00000052074 15145416123 0021121 0 ustar 00root root 0000000 0000000 var hints = Object.freeze((function(){
'use strict';
var hints = [], /* holds all hint data (hinted element, label, number) in view port */
docs = [], /* hold the affected document with the start and end index of the hints */
validHints = [], /* holds the valid hinted elements matching the filter condition */
activeHint, /* holds the active hint object */
filterText = "", /* holds the typed text filter */
filterKeys = "", /* holds the typed hint-keys filter */
attr = "vimbhint",
config;
/* the hint class used to maintain hinted element and labels */
function Hint() {
var state = "";
/* hide hint label and remove coloring from hinted element */
this.hide = function() {
var l = this.label,
e = this.e;
/* remove hint labels from no more visible hints */
l.style.display = "none";
l.setAttribute(attr, "");
e.setAttribute(attr, "");
state = "hidden";
};
/* marks the element and label of a hint as focused */
this.focus = function() {
this.label.setAttribute(attr, "label focus");
this.e.setAttribute(attr, "hint focus");
state = "focus";
};
/* remove focus mark from hint and label */
this.unfocus = function() {
/* do not unfocus hidden hints */
if (state != "hidden") {
this.label.setAttribute(attr, "label");
this.e.setAttribute(attr, "hint");
state = "visible";
}
};
/* show the hint element colored with the hint label */
this.show = function() {
var e = this.e,
l = this.label,
text = [];
if (state != "focus") {
l.style.display = "";
l.setAttribute(attr, "label");
e.setAttribute(attr, "hint");
state = "visible";
}
/* create the label with the hint number/letters */
if (e instanceof HTMLInputElement) {
var type = e.type;
if (type === "checkbox") {
text.push(e.checked ? "☑" : "☐");
} else if (type === "radio") {
text.push(e.checked ? "⊙" : "○");
}
}
if (this.showText && this.text) {
text.push(this.text.substr(0, 20));
}
/* use \x20 instead of ' ' to keep this space during js2h.sh processing */
l.innerText = this.num + (text.length ? ":\x20" + text.join("\x20") : "");
};
}
function onresize() {
clear(false);
create();
show(false);
}
function clear(removeListener) {
var i, j, doc, e, w = window;
if (removeListener && w) {
w.removeEventListener("resize", onresize, true);
w.removeEventListener("scroll", onresize, false);
for (i = 0; i < w.frames.length; i++) {
try {
w.frames[i].frameElement.contentDocument.removeEventListener("scroll", onresize, false);
} catch (ex) {
}
}
}
for (i = 0; i < docs.length; i++) {
doc = docs[i];
/* find all hinted elements vimbhint 'hint' */
var res = xpath(doc.doc, "//*[@vimbhint]");
for (j = 0; j < res.snapshotLength; j++) {
e = res.snapshotItem(j);
e.removeAttribute(attr);
}
doc.div.parentNode.removeChild(doc.div);
}
docs = [];
hints = [];
validHints = [];
filterText = "";
filterKeys = "";
}
function create() {
var count = 0;
function helper(win, offsets) {
/* document may be undefined for frames out of the same origin */
/* policy and will break the whole code - so we check this before */
if (typeof win.document == "undefined") {
return;
}
offsets = offsets || {left: 0, right: 0, top: 0, bottom: 0};
offsets.right = win.innerWidth - offsets.right;
offsets.bottom = win.innerHeight - offsets.bottom;
/* checks if given elemente is in viewport and visible */
function isVisible(e) {
if (typeof e == "undefined") {
return false;
}
var rect = e.getBoundingClientRect();
if (!rect ||
rect.top >= offsets.bottom || rect.bottom <= offsets.top ||
rect.left >= offsets.right || rect.right <= offsets.left
) {
return false;
}
if ((!rect.width || !rect.height) && (e.textContent || !e.name)) {
var arr = Array.prototype.slice.call(e.childNodes);
var check = function(e) {
return e instanceof Element
&& e.style.float != "none"
&& isVisible(e);
};
if (!arr.some(check)) {
return false;
}
}
var s = win.getComputedStyle(e, null);
return s.display !== "none" && s.visibility == "visible";
}
var doc = win.document,
res = xpath(doc, config.xpath),
/* generate basic hint element which will be cloned and updated later */
labelTmpl = doc.createElement("span"),
e, i;
labelTmpl.setAttribute(attr, "label");
var containerOffsets = getOffsets(doc),
offsetX = containerOffsets[0],
offsetY = containerOffsets[1],
fragment = doc.createDocumentFragment(),
rect, label, text, showText, start = hints.length;
/* collect all visible elements in hints array */
for (i = 0; i < res.snapshotLength; i++) {
e = res.snapshotItem(i);
if (!isVisible(e)) {
continue;
}
rect = e.getClientRects()[0];
if (!rect) {
continue;
}
count++;
label = labelTmpl.cloneNode(false);
label.style.display = "none";
label.style.left = Math.max(rect.left - 4, 0) + "px";
label.style.top = Math.max(rect.top - 4, 0) + "px";
/* create the hint label with number/letters */
/* if hinted element is an image - show title or alt of the image in hint label */
/* this allows to see how to filter for the image */
text = "";
showText = false;
if (e instanceof HTMLImageElement) {
text = e.title || e.alt;
showText = true;
} else if (e.firstElementChild instanceof HTMLImageElement && /^\s*$/.test(e.textContent)) {
text = e.firstElementChild.title || e.firstElementChild.alt;
showText = true;
} else if (e instanceof HTMLInputElement) {
var type = e.type;
if (type === "image") {
text = e.alt || "";
} else if (e.value && type !== "password") {
text = e.value;
showText = (type === "radio" || type === "checkbox");
}
} else if (e instanceof HTMLSelectElement) {
if (e.selectedIndex >= 0) {
text = e.item(e.selectedIndex).text;
}
} else {
text = e.textContent;
}
/* add the hint class to the hinted element */
fragment.appendChild(label);
e.setAttribute(attr, "hint");
hints.push({
e: e,
label: label,
text: text,
showText: showText,
__proto__: new Hint
});
if (count >= config.maxHints) {
break;
}
}
/* append the fragment to the document */
var hDiv = doc.createElement("div");
hDiv.setAttribute(attr, "container");
hDiv.style.position = "fixed";
hDiv.style.top = "0";
hDiv.style.left = "0";
hDiv.style.zIndex = "2147483647";
hDiv.appendChild(fragment);
if (doc.body) {
doc.body.appendChild(hDiv);
}
docs.push({
doc: doc,
start: start,
end: hints.length - 1,
div: hDiv
});
/* recurse into any iframe or frame element */
for (i = 0; i < win.frames.length; i++) {
try {
var rect, f = win.frames[i], e = f.frameElement;
} catch (ex) {
continue;
}
if (isVisible(e)) {
rect = e.getBoundingClientRect();
helper(f, {
left: Math.max(offsets.left - rect.left, 0),
right: Math.max(rect.right - offsets.right, 0),
top: Math.max(offsets.top - rect.top, 0),
bottom: Math.max(rect.bottom - offsets.bottom, 0)
});
}
}
}
helper(window);
}
function show(fireLast) {
var i, hint, newIdx,
matcher = getMatcher(filterText);
var hintCount = 0,
candidates = [];
/* Check which hints match to the filter. */
for (i = 0; i < hints.length; i++) {
hint = hints[i];
/* hide hints not matching the text filter */
if (!matcher(hint.text)) {
hint.hide();
} else {
hintCount++;
candidates.push(hint);
}
}
/* clear the array of valid hints */
validHints = [];
/* Now we can assigne the hint labels and check if hose match. */
var labeler = config.getHintLabeler(hintCount);
for (i = 0; i < candidates.length; i++) {
hint = candidates[i];
/* assign the new hint number/letters as label to the hint */
hint.num = labeler();
/* check for hint-keys filter */
if (!filterKeys.length || hint.num.indexOf(filterKeys) == 0) {
hint.show();
validHints.push(hint);
} else {
hint.hide();
}
}
if (fireLast && config.followLast && validHints.length <= 1) {
focusHint(0);
return fire();
}
/* if the previous active hint isn't valid set focus to first */
if (!activeHint || validHints.indexOf(activeHint) < 0) {
return focusHint(0);
}
}
/* Returns a validator method to check if the hint elements text matches */
/* the given text filter. */
function getMatcher(text) {
var tokens = text.toLowerCase().split(/\s+/);
return function (itemText) {
itemText = itemText.toLowerCase();
return tokens.every(function (token) {
return 0 <= itemText.indexOf(token);
});
};
}
function getOffsets(doc) {
var body = doc.body || doc.documentElement,
style = body.style,
rect;
if (style && /^(absolute|fixed|relative)$/.test(style.position)) {
rect = body.getClientRects()[0];
return [-rect.left, -rect.top];
}
return [doc.defaultView.scrollX, doc.defaultView.scrollY];
}
function focus(back) {
var idx = validHints.indexOf(activeHint);
/* previous active hint not found */
if (idx < 0) {
idx = 0;
}
if (back) {
if (--idx < 0) {
idx = validHints.length - 1;
}
} else {
if (++idx >= validHints.length) {
idx = 0;
}
}
return focusHint(idx);
}
function fire() {
if (!activeHint) {
return "ERROR:";
}
var e = activeHint.e,
res;
/* process form actions like focus toggling inputs */
if (config.handleForm) {
res = handleForm(e);
}
if (config.keepOpen) {
/* reset the hint-keys filter */
filterKeys = "";
show(false);
} else {
clear(true);
}
return res || config.action(e);
}
/* focus or toggle form fields */
function handleForm(e) {
var tag = e.nodeName.toLowerCase(),
type = e.type || "";
if (tag === "input" || tag === "textarea" || tag === "select") {
if (type === "radio" || type === "checkbox") {
e.focus();
e.click();
return "DONE:";
}
if (type === "submit" || type === "reset" || type === "button" || type === "image") {
e.click();
return "DONE:";
}
e.focus();
return "INSERT:";
}
if (tag === "iframe" || tag === "frame") {
e.focus();
return "DONE:";
}
}
/* internal used methods */
function open(e) {
/* We call click() in async mode to return as fast as possible. If
* we don't return immediately, the EvalJS dbus call will probably
* timeout and cause errors. */
window.setTimeout(function() {
var href;
if ((href = e.getAttribute('href')) && href != '#') {
window.location.href = href;
} else {
e.click();
}
}, 0);
}
/* set focus on hint with given index valid hints array */
function focusHint(newIdx) {
/* reset previous focused hint */
if (activeHint) {
activeHint.unfocus();
mouseEvent(activeHint.e, "mouseout");
}
/* get the new active hint */
if ((activeHint = validHints[newIdx])) {
var e = activeHint.e;
activeHint.focus();
mouseEvent(e, "mouseover");
return "OVER:" + (e instanceof HTMLImageElement ? "I:" : "A:") + getSrc(e);
}
}
function mouseEvent(e, name) {
var evObj = e.ownerDocument.createEvent("MouseEvents");
evObj.initMouseEvent(
name, true, true, e.ownerDocument.defaultView,
0, 0, 0, 0, 0,
false, false, false, false, 0, null
);
e.dispatchEvent(evObj);
}
/* retrieves the url of given element */
function getSrc(e) {
return e.href || e.src || "";
}
function xpath(doc, expr) {
return doc.evaluate(
expr, doc, function (p) {return "http://www.w3.org/1999/xhtml";},
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
);
}
function allFrames(win) {
var i, f, frames = [win];
for (i = 0; i < win.frames.length; i++) {
frames.push(win.frames[i].frameElement);
}
return frames;
}
function _labeler(keys, sameLength) {
var kl = keys.length,
/* Avoid don't consider the hint keys to be numeric in case the */
/* hintKeys='0' to avoid endless loop by attempt to use next */
/* hintKey char later. */
num = (keys[0] == '0' && kl > 1) ? 1 : 0,
sl = sameLength;
return function (count) {
/* if hint keys starts with '0' count from 1 instead of 0 */
var hcount = num,
offset = 0;
function getMaxHintOfLen(len) {
return (len > 0) ? ((kl - num) ** len) : 0;
}
/* We can generate same length label if there is only one hint key */
/* except in case there is only one hint. But we don't need to */
/* handle this. */
if (sl && kl > 1) {
if (num) {
offset = 1;
/* increase starting point of hint numbers until there are */
/* enough available numbers */
while (offset * (kl - 1) < count) {
offset *= kl;
}
offset--;
} else {
var val, labellen = 0, res = 0;
/* Find hint string length to describe all hints with same length. */
while ((val = getMaxHintOfLen(labellen)) < count) {
labellen++;
res += val;
}
/* the offset-th hint string is the first one to use */
offset = res;
}
}
return function () {
/* Start on second hint key in incase of numeric hints. */
var res = [],
n = hcount + offset;
do {
res.push(keys[n % kl]);
n = ~~(n / kl);
if (!num) {
n--;
}
} while (n - num >= 0);
hcount++;
return res.reverse().join("");
};
};
}
/* the api */
return {
init: function(mode, keepOpen, maxHints, hintKeys, followLast, keysSameLength) {
var prop,
/* holds the xpaths for the different modes */
xpathmap = {
otY: "//*[@href] | //*[@onclick or @tabindex or @class='lk' or @role='link' or @role='button'] | //input[not(@type='hidden' or @disabled or @readonly)] | //textarea[not(@disabled or @readonly)] | //button | //select | //summary",
k: "//div",
e: "//input[not(@type) or @type='text'] | //textarea",
iI: "//img[@src]",
OpPsTxy: "//*[@href] | //img[@src and not(ancestor::a)] | //iframe[@src]"
},
/* holds the actions to perform on hint fire */
actionmap = {
k: function(e) {e.remove(); return "DONE:";},
ot: function(e) {open(e); return "DONE:";},
eiIOpPsTxy: function(e) {return "DATA:" + getSrc(e);},
Y: function(e) {return "DATA:" + (e.textContent || "");}
};
config = {
maxHints: maxHints,
keepOpen: keepOpen,
/* handle forms only useful when there are form fields in xpath */
/* don't handle form for Y to allow to yank form filed content */
/* instead of switching to input mode */
handleForm: ("eot".indexOf(mode) >= 0),
hintKeys: hintKeys,
followLast: followLast,
keysSameLength: keysSameLength,
getHintLabeler: _labeler(hintKeys, keysSameLength)
};
for (prop in xpathmap) {
if (prop.indexOf(mode) >= 0) {
config.xpath = xpathmap[prop];
break;
}
}
for (prop in actionmap) {
if (prop.indexOf(mode) >= 0) {
config.action = actionmap[prop];
break;
}
}
window.addEventListener("resize", onresize, true);
window.addEventListener("scroll", onresize, false);
for (var i = 0; i < window.frames.length; i++) {
try {
window.frames[i].frameElement.contentDocument.addEventListener("scroll", onresize, false);
} catch (ex) {
}
}
create();
return show(true);
},
filter: function(text) {
/* remove previously set hint-keys filters to make the filter */
/* easier to understand for the users */
filterKeys = "";
filterText = text || "";
return show(true);
},
update: function(n) {
var pos;
/* delete last hint-keys filter digit */
if (null === n && filterKeys.length) {
filterKeys = filterKeys.slice(0, -1);
return show(false);
}
if ((pos = config.hintKeys.indexOf(n)) >= 0) {
filterKeys = filterKeys + n;
return show(true);
}
return "ERROR:";
},
clear: clear,
fire: fire,
focus: focus,
};
})());
fanglingsu-vimb-448e7e2/src/scripts/increment_uri_number.js 0000664 0000000 0000000 00000000664 15145416123 0024205 0 ustar 00root root 0000000 0000000 /* TODO maybe it's better to inject this in the webview as method */
/* and to call it if needed */
var c = %d, on, nn, m = location.href.match(/(.*?)(\d+)(\D*)$/);
if (m) {
on = m[2];
nn = String(Math.max(parseInt(on) + c, 0));
/* keep prepending zeros */
if (/^0/.test(on)) {
while (nn.length < on.length) {
nn = "0" + nn;
}
}
m[2] = nn;
location.href = m.slice(1).join("");
}
fanglingsu-vimb-448e7e2/src/scripts/js2h.sh 0000775 0000000 0000000 00000002214 15145416123 0020632 0 ustar 00root root 0000000 0000000 #!/bin/sh
#
# Defined a constant for given JavaScript or CSS file. The file name is used
# to get the constant name and the contents are minifed and escaped as the
# value. ./js.h do_fancy_stuff.js creates somethings like
# #define JS_DO_FANCY_STUFF "Escaped JavaScriptCode"
FILE="$1"
# Check if the file exists.
if [ ! -r "$FILE" ]; then
echo "File $FILE does not exist or is not readable" >&2
exit 1
fi
# Put file extension and _ before file name, turn all to upper case to get the
# constant name.
CONSTANT=$(echo "$FILE" | sed -e 's:.*/::g' -e 's/.*\.css$/CSS_&/g' -e 's/.*\.js$/JS_&/g' -e 's/\.css$//' -e 's/\.js$//' | tr a-z A-Z)
# minify the script
cat $FILE | \
# remove single line comments
sed -e 's|^//.*$||g' | \
# remove linebreaks
tr '\n\r' ' ' | \
# remove unneeded whitespace
sed -e 's:/\*[^*]*\*/::g' \
-e 's|[ ]\{2,\}| |g' \
-e 's|^ ||g' \
-e "s|[ ]\{0,\}\([-*[%/!?<>:=(){};+\&\"',\|]\)[ ]\{0,\}|\1|g" \
-e 's|"+"||g' | \
# ecaspe
sed -e 's|\\x20| |g' \
-e 's|\\|\\\\|g' \
-e 's|"|\\"|g' | \
# write opener with the starting and ending quote char
sed -e "1s/^/#define $CONSTANT \"/" \
-e '$s/$/"/'
echo ""
fanglingsu-vimb-448e7e2/src/scripts/scroll.js 0000664 0000000 0000000 00000005642 15145416123 0021271 0 ustar 00root root 0000000 0000000 var vbscroll = (function() {
var w = window,
d = document,
origin = {x: 0, y: 0},
target = {x: 0, y: 0},
previousScrollTime = 0,
previousTarget = {x: 0, y: 0},
previousScrollDuration = 0,
offset = 0;
return function (mode, scrollStep, count, smooth) {
var ph = w.innerHeight,
de = d.documentElement,
c = count||1,
maxY = Math.max(
d.body.scrollHeight, de.scrollHeight,
d.body.offsetHeight, de.offsetHeight,
d.body.clientHeight, de.clientHeight
);
if (smooth && performance.now() - previousScrollTime < previousScrollDuration) {
origin.x = previousTarget.x;
origin.y = previousTarget.y;
} else {
origin.x = w.scrollX;
origin.y = w.scrollY;
}
target.x = origin.x;
target.y = origin.y;
switch (mode) {
case 'j':
target.y += c * scrollStep;
break;
case 'h':
target.x -= c * scrollStep;
break;
case 'k':
target.y -= c * scrollStep;
break;
case 'l':
target.x += c * scrollStep;
break;
case '\x04': /* ^D */
target.y += c * ph / 2;
break;
case '\x15': /* ^U */
target.y -= c * ph / 2;
break;
case '\x06': /* ^F */
target.y += c * ph;
break;
case '\x02': /* ^B */
target.y -= c * ph;
break;
case 'G': /* fall through - gg and G differ only in y value when no count is given */
case 'g':
if (count) {
target.y = (d.documentElement.scrollHeight - ph) * c / 100;
} else {
target.y = mode == 'G' ? maxY : 0;
}
break;
case '0':
target.x = 0;
break;
case '$':
target.x = d.body.scrollWidth;
break;
default:
return 1;
}
target.x = Math.max(0, Math.min(d.body.scrollWidth, target.x));
target.y = Math.max(0, Math.min(maxY, target.y));
offset = Math.max(
Math.abs(target.x - origin.x),
Math.abs(target.y - origin.y),
);
if (target.x == origin.x && target.y == origin.y) {
return 0;
}
previousTarget.x = target.x;
previousTarget.y = target.y;
previousScrollDuration = Math.min(offset, 100);
previousScrollTime = performance.now();
w.scrollTo({
top: target.y,
left: target.x,
behavior: smooth ? 'smooth' : 'instant',
});
return 0;
};
})();
fanglingsu-vimb-448e7e2/src/scripts/scroll_observer.js 0000664 0000000 0000000 00000002621 15145416123 0023172 0 ustar 00root root 0000000 0000000 (function() {
'use strict';
function updateScrollPosition() {
var de = document.documentElement;
var body = document.body;
if (!de || !body) {
return;
}
var scrollTop = Math.max(de.scrollTop || 0, body.scrollTop || 0);
var scrollHeight = Math.max(de.scrollHeight || 0, body.scrollHeight || 0);
var clientHeight = window.innerHeight || 0;
var max = scrollHeight - clientHeight;
var percent = 0;
if (max > 0) {
percent = Math.round(scrollTop * 100 / max);
}
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.scroll) {
window.webkit.messageHandlers.scroll.postMessage({
max: max,
percent: percent,
top: scrollTop
});
}
}
var scrollTimeout;
window.addEventListener('scroll', function() {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(updateScrollPosition, 50);
}, {passive: true});
window.addEventListener('resize', updateScrollPosition, {passive: true});
if (document.readyState === 'complete') {
setTimeout(updateScrollPosition, 100);
} else {
window.addEventListener('load', function() {
setTimeout(updateScrollPosition, 100);
});
}
})();
fanglingsu-vimb-448e7e2/src/scripts/set_editor_map_element.js 0000664 0000000 0000000 00000000206 15145416123 0024471 0 ustar 00root root 0000000 0000000 if (typeof(vimb_editor_map) !== 'object') {
var vimb_editor_map = new Map;
}
vimb_editor_map.set("%lu", vimb_input_mode_element);
fanglingsu-vimb-448e7e2/src/scripts/webext/ 0000775 0000000 0000000 00000000000 15145416123 0020724 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/src/scripts/webext/check_editable_focus.js 0000664 0000000 0000000 00000000773 15145416123 0025376 0 ustar 00root root 0000000 0000000 // Check if the currently focused element is editable
// Returns: true if active element is editable, false otherwise
(function() {
var a = document.activeElement;
if (!a) return false;
if (a.isContentEditable) return true;
if (a.tagName === 'TEXTAREA') return true;
if (a.tagName === 'INPUT') {
var t = (a.type || '').toLowerCase();
return ['', 'text', 'password', 'email', 'search', 'tel', 'url', 'number', 'date', 'time'].indexOf(t) >= 0;
}
return false;
})()
fanglingsu-vimb-448e7e2/src/scripts/webext/focus_input.js 0000664 0000000 0000000 00000001535 15145416123 0023624 0 ustar 00root root 0000000 0000000 // Focus the first visible input element on the page
// Returns: true if an input was focused, false otherwise
(function() {
function isVisible(e) {
if (!e) return false;
var r = e.getBoundingClientRect();
if (!r || r.width === 0 || r.height === 0) return false;
var s = window.getComputedStyle(e);
return s.display !== 'none' && s.visibility === 'visible';
}
var xpath = '//input[not(@type) or @type="text" or @type="password" or @type="email" or @type="search"] | //textarea';
var result = document.evaluate(xpath, document.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0; i < result.snapshotLength; i++) {
var e = result.snapshotItem(i);
if (isVisible(e)) {
e.focus();
return true;
}
}
return false;
})()
fanglingsu-vimb-448e7e2/src/scripts/webext/lock_input.js 0000664 0000000 0000000 00000000457 15145416123 0023437 0 ustar 00root root 0000000 0000000 // Lock (disable) an input element by ID
// Usage: g_strdup_printf(JS_LOCK_INPUT, escaped_id)
// Returns: true if element found and locked, false otherwise
(function() {
var e = document.getElementById('%s');
if (e) {
e.disabled = true;
return true;
}
return false;
})()
fanglingsu-vimb-448e7e2/src/scripts/webext/unlock_input.js 0000664 0000000 0000000 00000000525 15145416123 0023776 0 ustar 00root root 0000000 0000000 // Unlock (enable) an input element by ID and focus it
// Usage: g_strdup_printf(JS_UNLOCK_INPUT, escaped_id)
// Returns: true if element found and unlocked, false otherwise
(function() {
var e = document.getElementById('%s');
if (e) {
e.disabled = false;
e.focus();
return true;
}
return false;
})()
fanglingsu-vimb-448e7e2/src/setting.c 0000664 0000000 0000000 00000103322 15145416123 0017561 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include "../version.h"
#include "completion.h"
#include "config.h"
#include "ext-proxy.h"
#include "main.h"
#include "setting.h"
#include "scripts/scripts.h"
#include "shortcut.h"
#include "regex.h"
typedef enum {
SETTING_SET, /* :set option=value */
SETTING_APPEND, /* :set option+=value */
SETTING_PREPEND, /* :set option^=value */
SETTING_REMOVE, /* :set option-=value */
SETTING_GET, /* :set option? */
SETTING_TOGGLE /* :set option! */
} SettingType;
enum {
FLAG_LIST = (1<<1), /* setting contains a ',' separated list of values */
FLAG_NODUP = (1<<2), /* don't allow duplicate strings within list values */
};
static int setting_set_value(Client *c, Setting *prop, void *value, SettingType type);
static gboolean prepare_setting_value(Setting *prop, void *value, SettingType type, void **newvalue);
static gboolean setting_add(Client *c, const char *name, DataType type, void *value,
SettingFunction setter, int flags, void *data);
static void setting_print(Client *c, Setting *s);
static void setting_free(Setting *s);
static int cookie_accept(Client *c, const char *name, DataType type, void *value, void *data);
static int dark_mode(Client *c, const char *name, DataType type, void *value, void *data);
static int default_zoom(Client *c, const char *name, DataType type, void *value, void *data);
static int fullscreen(Client *c, const char *name, DataType type, void *value, void *data);
static int geolocation(Client *c, const char *name, DataType type, void *value, void *data);
static int gui_style(Client *c, const char *name, DataType type, void *value, void *data);
static int hardware_acceleration_policy(Client *c, const char *name, DataType type, void *value, void *data);
static int input_autohide(Client *c, const char *name, DataType type, void *value, void *data);
static int internal(Client *c, const char *name, DataType type, void *value, void *data);
static int notification(Client *c, const char *name, DataType type, void *value, void *data);
static int headers(Client *c, const char *name, DataType type, void *value, void *data);
static int histignore(Client *c, const char *name, DataType type, void *value, void *data);
static int intelligent_tracking_prevention(Client *c, const char *name, DataType type, void *value, void *data);
static int user_scripts(Client *c, const char *name, DataType type, void *value, void *data);
static int user_style(Client *c, const char *name, DataType type, void *value, void *data);
static int smooth_scrolling(Client *c, const char *name, DataType type, void *value, void *data);
static int statusbar(Client *c, const char *name, DataType type, void *value, void *data);
static int tls_policy(Client *c, const char *name, DataType type, void *value, void *data);
static int webkit(Client *c, const char *name, DataType type, void *value, void *data);
static int webkit_spell_checking(Client *c, const char *name, DataType type, void *value, void *data);
static int webkit_spell_checking_language(Client *c, const char *name, DataType type, void *value, void *data);
static int window_decorate(Client *c, const char *name, DataType type, void *value, void *data);
extern struct Vimb vb;
void setting_init(Client *c)
{
int i;
gboolean on = TRUE, off = FALSE;
c->config.settings = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)setting_free);
setting_add(c, "user-agent", TYPE_CHAR, &"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15 " PROJECT "/" VERSION, webkit, 0, "user-agent");
/* TODO use the real names for webkit settings */
i = 14;
setting_add(c, "accelerated-2d-canvas", TYPE_BOOLEAN, &off, webkit, 0, "enable-accelerated-2d-canvas");
setting_add(c, "allow-file-access-from-file-urls", TYPE_BOOLEAN, &off, webkit, 0, "allow-file-access-from-file-urls");
setting_add(c, "allow-universal-access-from-file-urls", TYPE_BOOLEAN, &off, webkit, 0, "allow-universal-access-from-file-urls");
setting_add(c, "caret", TYPE_BOOLEAN, &off, webkit, 0, "enable-caret-browsing");
setting_add(c, "cursiv-font", TYPE_CHAR, &"serif", webkit, 0, "cursive-font-family");
setting_add(c, "dark-mode", TYPE_BOOLEAN, &off, dark_mode, 0, NULL);
setting_add(c, "default-charset", TYPE_CHAR, &"utf-8", webkit, 0, "default-charset");
setting_add(c, "default-font", TYPE_CHAR, &"sans-serif", webkit, 0, "default-font-family");
setting_add(c, "dns-prefetching", TYPE_BOOLEAN, &on, webkit, 0, "enable-dns-prefetching");
i = SETTING_DEFAULT_FONT_SIZE;
setting_add(c, "font-size", TYPE_INTEGER, &i, webkit, 0, "default-font-size");
setting_add(c, "frame-flattening", TYPE_BOOLEAN, &off, webkit, 0, "enable-frame-flattening");
setting_add(c, "geolocation", TYPE_CHAR, &"ask", geolocation, FLAG_NODUP, NULL);
setting_add(c, "hardware-acceleration-policy", TYPE_CHAR, &"ondemand", hardware_acceleration_policy, FLAG_NODUP, NULL);
setting_add(c, "header", TYPE_CHAR, &"", headers, FLAG_LIST|FLAG_NODUP, "header");
i = 1000;
setting_add(c, "hint-timeout", TYPE_INTEGER, &i, NULL, 0, NULL);
setting_add(c, "hint-keys", TYPE_CHAR, &SETTING_HINT_KEYS, NULL, 0, NULL);
setting_add(c, "hint-follow-last", TYPE_BOOLEAN, &on, NULL, 0, NULL);
setting_add(c, "hint-keys-same-length", TYPE_BOOLEAN, &off, NULL, 0, NULL);
setting_add(c, "hint-match-element", TYPE_BOOLEAN, &on, NULL, 0, NULL);
setting_add(c, "histignore", TYPE_CHAR, &SETTING_HISTIGNORE, histignore, 0, NULL);
setting_add(c, "html5-database", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-database");
setting_add(c, "html5-local-storage", TYPE_BOOLEAN, &on, webkit, 0, "enable-html5-local-storage");
setting_add(c, "hyperlink-auditing", TYPE_BOOLEAN, &off, webkit, 0, "enable-hyperlink-auditing");
setting_add(c, "images", TYPE_BOOLEAN, &on, webkit, 0, "auto-load-images");
#if WEBKIT_CHECK_VERSION(2, 30, 0)
setting_add(c, "intelligent-tracking-prevention", TYPE_BOOLEAN, &off, intelligent_tracking_prevention, 0, NULL);
#endif
setting_add(c, "javascript-can-access-clipboard", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-access-clipboard");
setting_add(c, "javascript-can-open-windows-automatically", TYPE_BOOLEAN, &off, webkit, 0, "javascript-can-open-windows-automatically");
#if WEBKIT_CHECK_VERSION(2, 24, 0)
setting_add(c, "javascript-enable-markup", TYPE_BOOLEAN, &on, webkit, 0, "enable-javascript-markup");
#endif
setting_add(c, "media-playback-allows-inline", TYPE_BOOLEAN, &on, webkit, 0, "media-playback-allows-inline");
setting_add(c, "media-playback-requires-user-gesture", TYPE_BOOLEAN, &off, webkit, 0, "media-playback-requires-user-gesture");
setting_add(c, "media-stream", TYPE_BOOLEAN, &off, webkit, 0, "enable-media-stream");
setting_add(c, "mediasource", TYPE_BOOLEAN, &off, webkit, 0, "enable-mediasource");
i = 5;
setting_add(c, "minimum-font-size", TYPE_INTEGER, &i, webkit, 0, "minimum-font-size");
setting_add(c, "monospace-font", TYPE_CHAR, &"monospace", webkit, 0, "monospace-font-family");
i = SETTING_DEFAULT_MONOSPACE_FONT_SIZE;
setting_add(c, "monospace-font-size", TYPE_INTEGER, &i, webkit, 0, "default-monospace-font-size");
setting_add(c, "notification", TYPE_CHAR, &"ask", notification, FLAG_NODUP, NULL);
setting_add(c, "offline-cache", TYPE_BOOLEAN, &on, webkit, 0, "enable-offline-web-application-cache");
setting_add(c, "plugins", TYPE_BOOLEAN, &on, webkit, 0, "enable-plugins");
setting_add(c, "prevent-newwindow", TYPE_BOOLEAN, &off, internal, 0, &c->config.prevent_newwindow);
setting_add(c, "print-backgrounds", TYPE_BOOLEAN, &on, webkit, 0, "print-backgrounds");
setting_add(c, "sans-serif-font", TYPE_CHAR, &"sans-serif", webkit, 0, "sans-serif-font-family");
setting_add(c, "scripts", TYPE_BOOLEAN, &on, webkit, 0, "enable-javascript");
setting_add(c, "serif-font", TYPE_CHAR, &"serif", webkit, 0, "serif-font-family");
setting_add(c, "site-specific-quirks", TYPE_BOOLEAN, &off, webkit, 0, "enable-site-specific-quirks");
setting_add(c, "smooth-scrolling", TYPE_BOOLEAN, &off, smooth_scrolling, 0, NULL);
setting_add(c, "spatial-navigation", TYPE_BOOLEAN, &off, webkit, 0, "enable-spatial-navigation");
setting_add(c, "tabs-to-links", TYPE_BOOLEAN, &on, webkit, 0, "enable-tabs-to-links");
setting_add(c, "webaudio", TYPE_BOOLEAN, &off, webkit, 0, "enable-webaudio");
setting_add(c, "webgl", TYPE_BOOLEAN, &off, webkit, 0, "enable-webgl");
setting_add(c, "webinspector", TYPE_BOOLEAN, &on, webkit, 0, "enable-developer-extras");
setting_add(c, "xss-auditor", TYPE_BOOLEAN, &on, webkit, 0, "enable-xss-auditor");
/* internal variables */
setting_add(c, "stylesheet", TYPE_BOOLEAN, &on, user_style, 0, NULL);
setting_add(c, "user-scripts", TYPE_BOOLEAN, &on, user_scripts, 0, NULL);
setting_add(c, "cookie-accept", TYPE_CHAR, &SETTING_COOKIE_ACCEPT, cookie_accept, 0, NULL);
i = 40;
setting_add(c, "scroll-step", TYPE_INTEGER, &i, internal, 0, &c->config.scrollstep);
i = 1;
setting_add(c, "scroll-multiplier", TYPE_INTEGER, &i, internal, 0, &c->config.scrollmultiplier);
setting_add(c, "home-page", TYPE_CHAR, &SETTING_HOME_PAGE, NULL, 0, NULL);
i = 2000;
setting_add(c, "status-bar-show-settings", TYPE_BOOLEAN, &off, internal, 0, &c->config.statusbar_show_settings);
/* TODO should be global and not overwritten by a new client */
setting_add(c, "history-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.history_max);
setting_add(c, "editor-command", TYPE_CHAR, &"x-terminal-emulator -e -vi '%s'", NULL, 0, NULL);
setting_add(c, "strict-ssl", TYPE_BOOLEAN, &on, tls_policy, 0, NULL);
setting_add(c, "status-bar", TYPE_BOOLEAN, &on, statusbar, 0, NULL);
i = 1000;
setting_add(c, "timeoutlen", TYPE_INTEGER, &i, internal, 0, &c->map.timeoutlen);
setting_add(c, "input-autohide", TYPE_BOOLEAN, &on, input_autohide, 0, &c->config.input_autohide);
setting_add(c, "fullscreen", TYPE_BOOLEAN, &off, fullscreen, 0, NULL);
setting_add(c, "show-titlebar", TYPE_BOOLEAN, &on, window_decorate, 0, NULL);
i = 100;
setting_add(c, "default-zoom", TYPE_INTEGER, &i, default_zoom, 0, NULL);
setting_add(c, "download-path", TYPE_CHAR, &SETTING_DOWNLOAD_PATH, NULL, 0, NULL);
setting_add(c, "download-command", TYPE_CHAR, &SETTING_DOWNLOAD_COMMAND, NULL, 0, NULL);
setting_add(c, "download-use-external", TYPE_BOOLEAN, &off, NULL, 0, NULL);
setting_add(c, "incsearch", TYPE_BOOLEAN, &on, internal, 0, &c->config.incsearch);
i = 10;
/* TODO should be global and not overwritten by a new client */
setting_add(c, "closed-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.closed_max);
setting_add(c, "x-hint-command", TYPE_CHAR, &":o ;", NULL, 0, NULL);
setting_add(c, "spell-checking", TYPE_BOOLEAN, &off, webkit_spell_checking, 0, NULL);
setting_add(c, "spell-checking-languages", TYPE_CHAR, &"en_US", webkit_spell_checking_language, FLAG_LIST|FLAG_NODUP, NULL);
/* gui style settings vimb */
setting_add(c, "completion-css", TYPE_CHAR, &SETTING_COMPLETION_CSS, gui_style, 0, NULL);
setting_add(c, "completion-hover-css", TYPE_CHAR, &SETTING_COMPLETION_HOVER_CSS, gui_style, 0, NULL);
setting_add(c, "completion-selected-css", TYPE_CHAR, &SETTING_COMPLETION_SELECTED_CSS, gui_style, 0, NULL);
setting_add(c, "input-css", TYPE_CHAR, &SETTING_INPUT_CSS, gui_style, 0, NULL);
setting_add(c, "input-error-css", TYPE_CHAR, &SETTING_INPUT_ERROR_CSS, gui_style, 0, NULL);
setting_add(c, "status-css", TYPE_CHAR, &SETTING_STATUS_CSS, gui_style, 0, NULL);
setting_add(c, "status-ssl-css", TYPE_CHAR, &SETTING_STATUS_SSL_CSS, gui_style, 0, NULL);
setting_add(c, "status-ssl-invalid-css", TYPE_CHAR, &SETTING_STATUS_SSL_INVLID_CSS, gui_style, 0, NULL);
/* initialize the shortcuts and set the default shortcuts */
shortcut_add(c->config.shortcuts, "dl", "https://duckduckgo.com/html/?q=$0");
shortcut_add(c->config.shortcuts, "dd", "https://duckduckgo.com/?q=$0");
shortcut_set_default(c->config.shortcuts, "dl");
}
VbCmdResult setting_run(Client *c, char *name, const char *param)
{
SettingType type = SETTING_SET;
char modifier;
int res, len;
/* determine the type to names last char and param */
len = strlen(name);
modifier = name[len - 1];
if (modifier == '?') {
name[len - 1] = '\0';
type = SETTING_GET;
} else if (modifier == '+') {
name[len - 1] = '\0';
type = SETTING_APPEND;
} else if (modifier == '^') {
name[len - 1] = '\0';
type = SETTING_PREPEND;
} else if (modifier == '-') {
name[len - 1] = '\0';
type = SETTING_REMOVE;
} else if (modifier == '!') {
name[len - 1] = '\0';
type = SETTING_TOGGLE;
} else if (!param) {
type = SETTING_GET;
}
/* lookup a matching setting */
Setting *s = g_hash_table_lookup(c->config.settings, name);
if (!s) {
vb_echo(c, MSG_ERROR, TRUE, "Config '%s' not found", name);
return CMD_ERROR | CMD_KEEPINPUT;
}
if (type == SETTING_GET) {
setting_print(c, s);
return CMD_SUCCESS | CMD_KEEPINPUT;
}
if (type == SETTING_TOGGLE) {
if (s->type != TYPE_BOOLEAN) {
vb_echo(c, MSG_ERROR, TRUE, "Could not toggle none boolean %s", s->name);
return CMD_ERROR | CMD_KEEPINPUT;
}
gboolean value = !s->value.b;
res = setting_set_value(c, s, &value, SETTING_SET);
setting_print(c, s);
/* make sure the new value set by the toggle keep visible */
res |= CMD_KEEPINPUT;
} else {
if (!param) {
vb_echo(c, MSG_ERROR, TRUE, "No valid value");
return CMD_ERROR | CMD_KEEPINPUT;
}
/* convert sting value into internal used data type */
gboolean boolvar;
int intvar;
switch (s->type) {
case TYPE_BOOLEAN:
boolvar = g_ascii_strncasecmp(param, "true", 4) == 0
|| g_ascii_strncasecmp(param, "on", 2) == 0;
res = setting_set_value(c, s, &boolvar, type);
break;
case TYPE_INTEGER:
intvar = g_ascii_strtoull(param, (char**)NULL, 10);
res = setting_set_value(c, s, &intvar, type);
break;
default:
res = setting_set_value(c, s, (void*)param, type);
break;
}
}
if (res & (CMD_SUCCESS | CMD_KEEPINPUT)) {
return res;
}
vb_echo(c, MSG_ERROR, TRUE, "Could not set %s", s->name);
return CMD_ERROR | CMD_KEEPINPUT;
}
gboolean setting_fill_completion(Client *c, GtkListStore *store, const char *input)
{
GtkTreeIter iter;
gboolean found = FALSE;
GList *src = g_hash_table_get_keys(c->config.settings);
/* If no filter input given - copy all entries into the data store. */
if (!input || !*input) {
for (GList *l = src; l; l = l->next) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
found = TRUE;
}
g_list_free(src);
return found;
}
/* If filter input is given - copy matching list entires into data store.
* Strings are compared by prefix matching. */
for (GList *l = src; l; l = l->next) {
char *value = (char*)l->data;
if (g_str_has_prefix(value, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
found = TRUE;
}
}
g_list_free(src);
return found;
}
void setting_cleanup(Client *c)
{
if (c->config.settings) {
g_hash_table_destroy(c->config.settings);
c->config.settings = NULL;
}
}
static int setting_set_value(Client *c, Setting *prop, void *value, SettingType type)
{
int res = CMD_SUCCESS;
/* by default given value is also the new value */
void *newvalue = NULL;
gboolean free_newvalue;
/* get prepared value according to setting type */
free_newvalue = prepare_setting_value(prop, value, type, &newvalue);
/* if there is a setter defined - call this first to check if the value is
* accepted */
if (prop->setter) {
res = prop->setter(c, prop->name, prop->type, newvalue, prop->data);
/* break here on error and don't change the setting */
if (!(res & CMD_SUCCESS)) {
goto free;
}
}
/* save the new value also in the setting */
switch (prop->type) {
case TYPE_BOOLEAN:
prop->value.b = *((gboolean*)newvalue);
break;
case TYPE_INTEGER:
prop->value.i = *((int*)newvalue);
break;
default:
OVERWRITE_STRING(prop->value.s, newvalue);
break;
}
free:
if (free_newvalue) {
g_free(newvalue);
}
return res;
}
/**
* Prepares the value for the setting for the different setting types.
* Return value TRUE indicates that the memory of newvalue must be freed by
* the caller.
*/
static gboolean prepare_setting_value(Setting *prop, void *value, SettingType type, void **newvalue)
{
gboolean islist, res = FALSE;
int vlen, i = 0;
char *p = NULL;
if ((type != SETTING_APPEND && type != SETTING_PREPEND && type != SETTING_REMOVE)
|| prop->type == TYPE_BOOLEAN
) {
/* if type is not append, prepend or remove there is nothing to be done */
*newvalue = value;
return res;
}
/* perform arithmetic operation for integer values */
if (prop->type == TYPE_INTEGER) {
int *newint = g_malloc(sizeof(int));
res = TRUE;
if (type == SETTING_APPEND) {
*newint = prop->value.i + *((int*)value);
} else if (type == SETTING_PREPEND) {
*newint = prop->value.i * *((int*)value);
} else if (type == SETTING_REMOVE) {
*newint = prop->value.i - *((int*)value);
}
*newvalue = (void*)newint;
return res;
}
/* handle operations on currently empty value */
if (!*prop->value.s) {
if (type == SETTING_APPEND || type == SETTING_PREPEND) {
*newvalue = value;
} else {
*newvalue = prop->value.s;
}
return res;
}
islist = (prop->flags & FLAG_LIST);
vlen = strlen((char*)value);
/* check if value already exists in current set option */
if (type == SETTING_REMOVE || prop->flags & FLAG_NODUP) {
for (p = prop->value.s; *p; p++) {
if ((!islist || p == prop->value.s || (p[-1] == ','))
&& strncmp(p, value, vlen) == 0
&& (!islist || p[vlen] == ',' || p[vlen] == '\0')
) {
i = vlen;
if (islist) {
if (p == prop->value.s) {
/* include the comma after the matched string */
if (p[vlen] == ',') {
i++;
}
} else {
/* include the comma before the string */
p--;
i++;
}
}
break;
}
}
/* do not add the value if it already exists */
if ((type == SETTING_APPEND || type == SETTING_PREPEND) && *p) {
*newvalue = value;
return res;
}
}
if (type == SETTING_APPEND) {
if (islist && *(char*)value) {
/* don't append a comma if the value is empty */
*newvalue = g_strconcat(prop->value.s, ",", value, NULL);
} else {
*newvalue = g_strconcat(prop->value.s, value, NULL);
}
res = TRUE;
} else if (type == SETTING_PREPEND) {
if (islist && *(char*)value) {
/* don't prepend a comma if the value is empty */
*newvalue = g_strconcat(value, ",", prop->value.s, NULL);
} else {
*newvalue = g_strconcat(value, prop->value.s, NULL);
}
res = TRUE;
} else if (type == SETTING_REMOVE && p) {
char *copy = g_strdup(prop->value.s);
/* make p to point to the same position in the copy */
p = copy + (p - prop->value.s);
memmove(p, p + i, 1 + strlen(p + vlen));
*newvalue = copy;
res = TRUE;
}
return res;
}
static gboolean setting_add(Client *c, const char *name, DataType type, void *value,
SettingFunction setter, int flags, void *data)
{
Setting *prop = g_slice_new0(Setting);
prop->name = name;
prop->type = type;
prop->setter = setter;
prop->flags = flags;
prop->data = data;
setting_set_value(c, prop, value, SETTING_SET);
g_hash_table_insert(c->config.settings, (char*)name, prop);
return TRUE;
}
static void setting_print(Client *c, Setting *s)
{
switch (s->type) {
case TYPE_BOOLEAN:
vb_echo(c, MSG_NORMAL, FALSE, " %s=%s", s->name, s->value.b ? "true" : "false");
break;
case TYPE_INTEGER:
vb_echo(c, MSG_NORMAL, FALSE, " %s=%d", s->name, s->value.i);
break;
default:
vb_echo(c, MSG_NORMAL, FALSE, " %s=%s", s->name, s->value.s);
break;
}
}
static void setting_free(Setting *s)
{
if (s->type == TYPE_CHAR || s->type == TYPE_COLOR || s->type == TYPE_FONT) {
g_free(s->value.s);
}
g_slice_free(Setting, s);
}
static int cookie_accept(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitWebContext *ctx;
WebKitCookieManager *cm;
char *policy = (char*)value;
ctx = webkit_web_view_get_context(c->webview);
cm = webkit_web_context_get_cookie_manager(ctx);
if (strcmp("always", policy) == 0) {
webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
} else if (strcmp("origin", policy) == 0) {
webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
} else if (strcmp("never", policy) == 0) {
webkit_cookie_manager_set_accept_policy(cm, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
} else {
vb_echo(c, MSG_ERROR, TRUE, "%s must be in [always, origin, never]", name);
return CMD_ERROR | CMD_KEEPINPUT;
}
return CMD_SUCCESS;
}
static int dark_mode(Client *c, const char *name, DataType type, void *value, void *data)
{
g_object_set(gtk_widget_get_settings(GTK_WIDGET(c->window)), "gtk-application-prefer-dark-theme", *(gboolean*)value, NULL);
return CMD_SUCCESS;
}
static int default_zoom(Client *c, const char *name, DataType type, void *value, void *data)
{
/* Store the percent value in the client config. */
c->config.default_zoom = *(int*)value;
/* Apply the default zoom to the webview. */
webkit_settings_set_zoom_text_only(webkit_web_view_get_settings(c->webview), FALSE);
webkit_web_view_set_zoom_level(c->webview, c->config.default_zoom / 100.0);
return CMD_SUCCESS;
}
static int fullscreen(Client *c, const char *name, DataType type, void *value, void *data)
{
if (*(gboolean*)value) {
gtk_window_fullscreen(GTK_WINDOW(c->window));
} else {
gtk_window_unfullscreen(GTK_WINDOW(c->window));
}
return CMD_SUCCESS;
}
static int geolocation(Client *c, const char *name, DataType type, void *value, void *data)
{
char *policy = (char *)value;
if (strcmp("always", policy) != 0 && strcmp("ask", policy) != 0 && strcmp("never", policy) != 0) {
vb_echo(c, MSG_ERROR, FALSE, "%s must be in [always, ask, never]", name);
return CMD_ERROR | CMD_KEEPINPUT;
}
return CMD_SUCCESS;
}
#if WEBKIT_CHECK_VERSION(2, 30, 0)
static int intelligent_tracking_prevention(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitWebContext *ctx = webkit_web_view_get_context(c->webview);
WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(ctx);
webkit_website_data_manager_set_itp_enabled(manager, *(gboolean*)value);
return CMD_SUCCESS;
}
#endif
/* This needs to be called before the window is shown for the best chance of
* success, but it may be called at any time.
* Currently the setting file is read after the window has been shown, which
* might mean the titlebar isn't hidden on certain environments. */
static int window_decorate(Client *c, const char *name, DataType type, void *value, void *data)
{
gtk_window_set_decorated(GTK_WINDOW(c->window), *(gboolean*)value);
return CMD_SUCCESS;
}
static int hardware_acceleration_policy(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitSettings *settings = webkit_web_view_get_settings(c->webview);
if (g_str_equal(value, "ondemand")) {
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
} else if (g_str_equal(value, "always")) {
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
} else if (g_str_equal(value, "never")) {
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
} else {
vb_echo(c, MSG_ERROR, TRUE, "%s must be in [ondemand, always, never]", name);
return CMD_ERROR|CMD_KEEPINPUT;
}
return CMD_SUCCESS;
}
/**
* Allow to set user defined http headers.
*
* :set header=NAME1=VALUE!,NAME2=,NAME3
*
* Note that these headers will replace already existing headers. If there is
* no '=' after the header name, than the complete header will be removed from
* the request (NAME3), if the '=' is present means that the header value is
* set to empty value.
*/
static int headers(Client *c, const char *name, DataType type, void *value, void *data)
{
ext_proxy_set_header(c, (char*)value);
return CMD_SUCCESS;
}
static int histignore(Client *c, const char *name, DataType type, void *value, void *data)
{
regex_t preg;
int code;
char error_msg[50];
/* Empty regex string would match anything but we want it to match nothing */
if (strlen(value) == 0) {
value = "^$";
}
code = regcomp(&preg, value, REG_EXTENDED|REG_NOSUB);
if (code) {
regerror(code, &preg, error_msg, sizeof(error_msg));
vb_echo(c, MSG_ERROR, FALSE, "Invalid histignore value: %s", error_msg);
regfree(&preg);
return CMD_ERROR | CMD_KEEPINPUT;
}
regfree(&c->config.histignore_preg);
c->config.histignore_preg = preg;
return CMD_SUCCESS;
}
static int input_autohide(Client *c, const char *name, DataType type, void *value, void *data)
{
char *text;
/* save selected value in internal variable */
*(gboolean*)data = *(gboolean*)value;
/* if autohide is on and inputbox contains no text - hide it now */
if (*(gboolean*)value) {
text = vb_input_get_text(c);
if (!*text) {
gtk_widget_set_visible(GTK_WIDGET(c->input), FALSE);
}
g_free(text);
} else {
/* autohide is off - make sure the input box is shown */
gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE);
}
return CMD_SUCCESS;
}
static int internal(Client *c, const char *name, DataType type, void *value, void *data)
{
char **str;
switch (type) {
case TYPE_BOOLEAN:
*(gboolean*)data = *(gboolean*)value;
break;
case TYPE_INTEGER:
*(int*)data = *(int*)value;
break;
default:
str = (char**)data;
OVERWRITE_STRING(*str, (char*)value);
break;
}
return CMD_SUCCESS;
}
static int notification(Client *c, const char *name, DataType type, void *value, void *data)
{
char *policy = (char *)value;
if (strcmp("always", policy) != 0 && strcmp("ask", policy) != 0 && strcmp("never", policy) != 0) {
vb_echo(c, MSG_ERROR, FALSE, "%s must be in [always, ask, never]", name);
return CMD_ERROR | CMD_KEEPINPUT;
}
return CMD_SUCCESS;
}
static int user_scripts(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitUserContentManager *ucm;
WebKitUserScript *script;
gchar *source;
gboolean enabled = *(gboolean*)value;
ucm = webkit_web_view_get_user_content_manager(c->webview);
webkit_user_content_manager_remove_all_scripts(ucm);
if (enabled) {
if (vb.files[FILES_SCRIPT]
&& g_file_get_contents(vb.files[FILES_SCRIPT], &source, NULL, NULL)) {
script = webkit_user_script_new(
source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL
);
webkit_user_content_manager_add_script(ucm, script);
webkit_user_script_unref(script);
g_free(source);
}
}
/* Inject the global scripts including scroll observer. */
script = webkit_user_script_new(JS_HINTS " " JS_SCROLL " " JS_SCROLL_OBSERVER,
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL);
webkit_user_content_manager_add_script(ucm, script);
webkit_user_script_unref(script);
return CMD_SUCCESS;
}
static int user_style(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitUserContentManager *ucm;
WebKitUserStyleSheet *style;
gchar *source;
gboolean enabled = *(gboolean*)value;
ucm = webkit_web_view_get_user_content_manager(c->webview);
if (enabled) {
if (g_file_get_contents(vb.files[FILES_USER_STYLE], &source, NULL, NULL)) {
style = webkit_user_style_sheet_new(
source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL
);
webkit_user_content_manager_add_style_sheet(ucm, style);
webkit_user_style_sheet_unref(style);
g_free(source);
} else {
g_message("Could not read style file: %s", vb.files[FILES_USER_STYLE]);
}
} else {
webkit_user_content_manager_remove_all_style_sheets(ucm);
}
/* Inject the global styles with author level to allow restyling by user
* style sheets. */
style = webkit_user_style_sheet_new(CSS_HINTS,
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_STYLE_LEVEL_AUTHOR, NULL, NULL);
webkit_user_content_manager_add_style_sheet(ucm, style);
webkit_user_style_sheet_unref(style);
return CMD_SUCCESS;
}
static int smooth_scrolling(Client *c, const char *name, DataType type, void *value, void *data)
{
WebKitSettings *settings = webkit_web_view_get_settings(c->webview);
webkit_settings_set_enable_smooth_scrolling(settings, *(gboolean*) value);
c->config.smooth_scrolling = *(gboolean*) value;
return CMD_SUCCESS;
}
static int statusbar(Client *c, const char *name, DataType type, void *value, void *data)
{
gtk_widget_set_visible(GTK_WIDGET(c->statusbar.box), *(gboolean*)value);
return CMD_SUCCESS;
}
static int gui_style(Client *c, const char *name, DataType type, void *value, void *data)
{
vb_gui_style_update(c, name, (const char*)value);
return CMD_SUCCESS;
}
static int tls_policy(Client *c, const char *name, DataType type, void *value, void *data)
{
gboolean strict = *((gboolean*)value);
#if WEBKIT_CHECK_VERSION(2, 30, 0)
WebKitWebContext *ctx = webkit_web_view_get_context(c->webview);
WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(ctx);
webkit_website_data_manager_set_tls_errors_policy(manager,
strict ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
#else
webkit_web_context_set_tls_errors_policy(
webkit_web_context_get_default(),
strict ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
#endif
return CMD_SUCCESS;
}
static int webkit(Client *c, const char *name, DataType type, void *value, void *data)
{
const char *property = (const char*)data;
WebKitSettings *web_setting = webkit_web_view_get_settings(c->webview);
switch (type) {
case TYPE_BOOLEAN:
g_object_set(G_OBJECT(web_setting), property, *((gboolean*)value), NULL);
break;
case TYPE_INTEGER:
g_object_set(G_OBJECT(web_setting), property, *((int*)value), NULL);
break;
default:
g_object_set(G_OBJECT(web_setting), property, (char*)value, NULL);
break;
}
return CMD_SUCCESS;
}
static int webkit_spell_checking(Client *c, const char *name, DataType type, void *value, void *data)
{
gboolean enabled = *((gboolean*)value);
webkit_web_context_set_spell_checking_enabled(
webkit_web_context_get_default(),
enabled);
return CMD_SUCCESS;
}
static int webkit_spell_checking_language(Client *c, const char *name, DataType type, void *value, void *data)
{
char **languages = g_strsplit((char*)value, ",", -1);
webkit_web_context_set_spell_checking_languages(
webkit_web_context_get_default(),
(const char * const *)languages);
g_strfreev(languages);
return CMD_SUCCESS;
}
fanglingsu-vimb-448e7e2/src/setting.h 0000664 0000000 0000000 00000002071 15145416123 0017565 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _SETTING_H
#define _SETTING_H
#include
#include "main.h"
void setting_init(Client *c);
void setting_cleanup(Client *c);
VbCmdResult setting_run(Client *c, char *name, const char *param);
gboolean setting_fill_completion(Client *c, GtkListStore *store, const char *input);
#endif /* end of include guard: _SETTING_H */
fanglingsu-vimb-448e7e2/src/shortcut.c 0000664 0000000 0000000 00000014035 15145416123 0017761 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include "ascii.h"
#include "main.h"
#include "shortcut.h"
#include "util.h"
struct shortcut {
GHashTable *table;
char *fallback; /* default shortcut to use if none given in request */
};
extern struct Vimb vb;
static int get_max_placeholder(const char *str);
static const char *shortcut_lookup(Shortcut *sc, const char *string, const char **query);
Shortcut *shortcut_new(void)
{
Shortcut *sc = g_new(Shortcut, 1);
sc->table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
sc->fallback = NULL;
return sc;
}
void shortcut_free(Shortcut *sc)
{
if (sc->table) {
g_hash_table_destroy(sc->table);
}
g_free(sc);
}
gboolean shortcut_add(Shortcut *sc, const char *key, const char *uri)
{
g_hash_table_insert(sc->table, g_strdup(key), g_strdup(uri));
return TRUE;
}
gboolean shortcut_remove(Shortcut *sc, const char *key)
{
return g_hash_table_remove(sc->table, key);
}
gboolean shortcut_set_default(Shortcut *sc, const char *key)
{
/* do not check if the shortcut exists to be able to set the default
* before defining the shortcut */
OVERWRITE_STRING(sc->fallback, key);
return TRUE;
}
/**
* Retrieves the uri for given query string. Not that the memory of the
* returned uri must be freed.
*/
char *shortcut_get_uri(Shortcut *sc, const char *string)
{
const char *tmpl, *query = NULL;
char *uri, *quoted_param;
int max_num, current_num;
GString *token;
tmpl = shortcut_lookup(sc, string, &query);
if (!tmpl) {
return NULL;
}
max_num = get_max_placeholder(tmpl);
/* if there are only $0 placeholders we don't need to split the parameters */
if (max_num == 0) {
quoted_param = g_uri_escape_string(query, NULL, TRUE);
uri = util_str_replace("$0", quoted_param, tmpl);
g_free(quoted_param);
return uri;
}
uri = g_strdup(tmpl);
/* skip if no placeholders found */
if (max_num < 0) {
return uri;
}
current_num = 0;
token = g_string_new(NULL);
while (*query) {
/* parse the query tokens */
if (*query == '"' || *query == '\'') {
/* save the last used quote char to find it's matching counterpart */
char last_quote = *query;
/* skip the quote */
query++;
/* collect the char until the closing quote or end of string */
while (*query && *query != last_quote) {
g_string_append_c(token, *query);
query++;
}
/* if we end up at the closing quote - skip this quote too */
if (*query == last_quote) {
query++;
}
} else if (VB_IS_SPACE(*query)) {
/* skip whitespace */
query++;
continue;
} else if (current_num >= max_num) {
/* if we have parsed as many params like placeholders - put the
* rest of the query as last parameter */
while (*query) {
g_string_append_c(token, *query);
query++;
}
} else {
/* collect the following character up to the next whitespace */
while (*query && !VB_IS_SPACE(*query)) {
g_string_append_c(token, *query);
query++;
}
}
/* replace the placeholders with parsed token */
if (token->len) {
char *new;
quoted_param = g_uri_escape_string(token->str, NULL, TRUE);
new = util_str_replace((char[]){'$', current_num + '0', '\0'}, quoted_param, uri);
g_free(quoted_param);
g_free(uri);
uri = new;
/* truncate the last token to fill for next loop */
g_string_truncate(token, 0);
}
current_num++;
}
g_string_free(token, TRUE);
return uri;
}
gboolean shortcut_fill_completion(Shortcut *sc, GtkListStore *store, const char *input)
{
GList *src = g_hash_table_get_keys(sc->table);
gboolean found = util_fill_completion(store, input, src);
g_list_free(src);
return found;
}
/**
* Retrieves th highest placeholder number used in given string.
* If no placeholder is found -1 is returned.
*/
static int get_max_placeholder(const char *str)
{
int n, res;
for (n = 0, res = -1; *str; str++) {
if (*str == '$') {
n = *(++str) - '0';
if (0 <= n && n <= 9 && n > res) {
res = n;
}
}
}
return res;
}
/**
* Retrieves the shortcut uri template for given string. And fills given query
* pointer with the query part of the given string (everything except of the
* shortcut identifier).
*/
static const char *shortcut_lookup(Shortcut *sc, const char *string, const char **query)
{
char *p, *uri = NULL;
if ((p = strchr(string, ' '))) {
char *key = g_strndup(string, p - string);
/* is the first word might be a shortcut */
if ((uri = g_hash_table_lookup(sc->table, key))) {
*query = p + 1;
}
g_free(key);
} else {
uri = g_hash_table_lookup(sc->table, string);
}
if (!uri && sc->fallback
&& (uri = g_hash_table_lookup(sc->table, sc->fallback))) {
*query = string;
}
return uri;
}
fanglingsu-vimb-448e7e2/src/shortcut.h 0000664 0000000 0000000 00000002355 15145416123 0017770 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _SHORTCUT_H
#define _SHORTCUT_H
typedef struct shortcut Shortcut;
Shortcut *shortcut_new(void);
void shortcut_free(Shortcut *sc);
gboolean shortcut_add(Shortcut *sc, const char *key, const char *uri);
gboolean shortcut_remove(Shortcut *sc, const char *key);
gboolean shortcut_set_default(Shortcut *sc, const char *key);
char *shortcut_get_uri(Shortcut *sc, const char *key);
gboolean shortcut_fill_completion(Shortcut *c, GtkListStore *store, const char *input);
#endif /* end of include guard: _SHORTCUT_H */
fanglingsu-vimb-448e7e2/src/util.c 0000664 0000000 0000000 00000077554 15145416123 0017102 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ascii.h"
#include "completion.h"
#include "util.h"
static struct {
char *config_dir;
} util;
extern struct Vimb vb;
static void create_dir_if_not_exists(const char *dirpath);
static gboolean match(const char *pattern, int patlen, const char *subject);
static gboolean match_list(const char *pattern, int patlen, const char *subject);
/**
* Build the absolute file path of given path and possible given directory.
*
* Returned path must be freed.
*/
char *util_build_path(const char *path, const char *dir)
{
char *fullPath = NULL, *fexp, *dexp, *p;
int expflags = UTIL_EXP_TILDE|UTIL_EXP_DOLLAR;
/* if the path could be expanded */
if ((fexp = util_expand(path, expflags))) {
if (*fexp == '/') {
/* path is already absolute, no need to use given dir - there is
* no need to free fexp, because this should be done by the caller
* on fullPath later */
fullPath = fexp;
} else if (dir && *dir) {
/* try to expand also the dir given - this may be ~/path */
if ((dexp = util_expand(dir, expflags))) {
/* use expanded dir and append expanded path */
fullPath = g_build_filename(dexp, fexp, NULL);
g_free(dexp);
}
g_free(fexp);
}
}
/* if full path not found use current dir */
if (!fullPath) {
fullPath = g_build_filename(g_get_current_dir(), path, NULL);
}
/* Create the directory part of the path if it does not exists. */
if ((p = strrchr(fullPath, '/'))) {
gboolean res;
*p = '\0';
res = util_create_dir_if_not_exists(fullPath);
*p = '/';
if (!res) {
g_free(fullPath);
return NULL;
}
}
return fullPath;
}
/**
* Free memory for allocated path strings.
*/
void util_cleanup(void)
{
if (util.config_dir) {
g_free(util.config_dir);
}
}
gboolean util_create_dir_if_not_exists(const char *dirpath)
{
if (g_mkdir_with_parents(dirpath, 0755) == -1) {
g_critical("Could not create directory '%s': %s", dirpath, g_strerror(errno));
return FALSE;
}
return TRUE;
}
/**
* Creates a temporary file with given content.
*
* Upon success, and if file is non-NULL, the actual file path used is
* returned in file. This string should be freed with g_free() when not
* needed any longer.
*/
gboolean util_create_tmp_file(const char *content, char **file)
{
int fp;
ssize_t bytes, len;
fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL);
if (fp == -1) {
g_critical("Could not create temp file %s", *file);
g_free(*file);
return FALSE;
}
if (content == NULL) {
close(fp);
return TRUE;
}
len = strlen(content);
/* write content into temporary file */
bytes = write(fp, content, len);
if (bytes < len) {
close(fp);
unlink(*file);
g_critical("Could not write temp file %s", *file);
g_free(*file);
return FALSE;
}
close(fp);
return TRUE;
}
/**
* Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated
* string.
*
* Returned path must be g_freed.
*/
char *util_expand(const char *src, int expflags)
{
const char **input = &src;
char *result;
GString *dst = g_string_new("");
int flags = expflags;
while (**input) {
util_parse_expansion(input, dst, flags, "\\");
if (VB_IS_SEPARATOR(**input)) {
/* after space the tilde expansion is allowed */
flags = expflags;
} else {
/* remove tile expansion for next loop */
flags &= ~UTIL_EXP_TILDE;
}
/* move pointer to the next char */
(*input)++;
}
result = g_string_free(dst, FALSE);
return result;
}
/**
* Append new data to file.
*
* @file: File to append the data
* @format: Format string used to process va_list
*/
gboolean util_file_append(const char *file, const char *format, ...)
{
va_list args;
FILE *f;
if (file && (f = fopen(file, "a+"))) {
flock(fileno(f), LOCK_EX);
va_start(args, format);
vfprintf(f, format, args);
va_end(args);
flock(fileno(f), LOCK_UN);
fclose(f);
return TRUE;
}
return FALSE;
}
/**
* Prepend new data to file.
*
* @file: File to prepend the data
* @format: Format string used to process va_list
*/
gboolean util_file_prepend(const char *file, const char *format, ...)
{
gboolean res = FALSE;
va_list args;
char *content;
FILE *f;
if (!file) {
return FALSE;
}
content = util_get_file_contents(file, NULL);
if ((f = fopen(file, "w"))) {
flock(fileno(f), LOCK_EX);
va_start(args, format);
/* write new content to the file */
vfprintf(f, format, args);
va_end(args);
/* append previous file content */
fputs(content, f);
flock(fileno(f), LOCK_UN);
fclose(f);
res = TRUE;
}
g_free(content);
return res;
}
/**
* Prepend a new line to the file and make sure there are not more than
* max_lines in the file.
*
* @file: File to prepend the data
* @line: Line to be written as new first line into the file.
* The line ending is inserted automatic.
* @max_lines Maximum number of lines in file after the operation.
*/
void util_file_prepend_line(const char *file, const char *line,
unsigned int max_lines)
{
char **lines;
GString *new_content;
g_assert(file);
g_assert(line);
lines = util_get_lines(file);
/* Write first the new line into the string and append the new line. */
new_content = g_string_new(line);
g_string_append(new_content, "\n");
if (lines) {
int len, i;
len = g_strv_length(lines);
for (i = 0; i < len - 1 && i < max_lines - 1; i++) {
g_string_append_printf(new_content, "%s\n", lines[i]);
}
g_strfreev(lines);
}
util_file_set_content(file, new_content->str);
g_string_free(new_content, TRUE);
}
/**
* Retrieves the first line from file and delete it from file.
*
* @file: file to read from
* @item_count: will be filled with the number of remaining lines in file if it
* is not NULL.
*
* Returned string must be freed with g_free.
*/
char *util_file_pop_line(const char *file, int *item_count)
{
char **lines;
char *new,
*line = NULL;
int len,
count = 0;
if (!file) {
return NULL;
}
lines = util_get_lines(file);
if (lines) {
len = g_strv_length(lines);
if (len) {
line = g_strdup(lines[0]);
/* minus one for last empty item and one for popped item */
count = len - 2;
new = g_strjoinv("\n", lines + 1);
util_file_set_content(file, new);
g_free(new);
}
g_strfreev(lines);
}
if (item_count) {
*item_count = count;
}
return line;
}
/**
* Retrieves the config directory path according to current used profile.
* Returned string must be freed.
*/
char *util_get_config_dir(void)
{
char *path = g_build_filename(g_get_user_config_dir(), PROJECT, vb.profile, NULL);
create_dir_if_not_exists(path);
return path;
}
/**
* Retrieves the data directory path according to current used profile.
* Returned string must be freed.
*/
char *util_get_data_dir(void)
{
char *path = g_build_filename(g_get_user_data_dir(), PROJECT, vb.profile, NULL);
create_dir_if_not_exists(path);
return path;
}
/**
* Retrieves the cache directory path according to current used profile.
* Returned string must be freed.
*/
char *util_get_cache_dir(void)
{
char *path = g_build_filename(g_get_user_cache_dir(), PROJECT, vb.profile, NULL);
create_dir_if_not_exists(path);
return path;
}
/**
* Retrieves the length bytes from given file.
*
* The memory of returned string have to be freed with g_free().
*/
char *util_get_file_contents(const char *filename, gsize *length)
{
GError *error = NULL;
char *content = NULL;
if (filename && !g_file_get_contents(filename, &content, length, &error)) {
g_warning("Cannot open %s: %s", filename, error->message);
g_error_free(error);
}
return content;
}
/**
* Atomicly writes contents to given file.
* Returns TRUE on success, FALSE otherwise.
*/
gboolean util_file_set_content(const char *file, const char *contents)
{
gboolean retval = FALSE;
char *tmp_name;
int fd, mode;
gsize length;
struct stat st;
mode = 0600;
if (stat(file, &st) == 0) {
mode = st.st_mode;
}
/* Create a temporary file. */
tmp_name = g_strconcat(file, ".XXXXXX", NULL);
errno = 0;
fd = g_mkstemp_full(tmp_name, O_RDWR, mode);
length = strlen(contents);
if (fd == -1) {
g_error("Failed to create file %s: %s", tmp_name, g_strerror(errno));
goto out;
}
/* Write the contents to the temporary file. */
while (length > 0) {
gssize s;
s = write(fd, contents, length);
if (s < 0) {
if (errno == EINTR) {
continue;
}
g_error("Failed to write to file %s: write() failed: %s",
tmp_name, g_strerror(errno));
close(fd);
g_unlink(tmp_name);
goto out;
}
g_assert (s <= length);
contents += s;
length -= s;
}
if (!g_close(fd, NULL)) {
g_unlink(tmp_name);
goto out;
}
/* Atomic rename the temporary file into the destination file. */
if (g_rename(tmp_name, file) == -1) {
g_error("Failed to rename file %s to %s: g_rename() failed: %s",
tmp_name, file, g_strerror(errno));
g_unlink(tmp_name);
goto out;
}
retval = TRUE;
out:
g_free(tmp_name);
return retval;
}
/**
* Retrieves the file content as lines.
*
* The result have to be freed by g_strfreev().
*/
char **util_get_lines(const char *filename)
{
char *content;
char **lines = NULL;
if (!filename) {
return NULL;
}
if (g_file_get_contents(filename, &content, NULL, NULL)) {
/* split the file content into lines */
lines = g_strsplit(content, "\n", -1);
g_free(content);
}
return lines;
}
/**
* Retrieves a list with unique items from file. The uniqueness is calculated
* based on the lines comparing all chars until the next char or end of
* line.
*
* @func: Function to parse a single line to item.
* @max_items: maximum number of items that are returned, use 0 for
* unlimited items
*/
GList *util_strv_to_unique_list(char **lines, Util_Content_Func func,
guint max_items)
{
char *line;
int i, len;
GList *gl = NULL;
GHashTable *ht;
if (!lines) {
return NULL;
}
/* Use the hashtable to check for duplicates in a faster way than by
* iterating over the generated list itself. So it's enough to store the
* the keys only. */
ht = g_hash_table_new(g_str_hash, g_str_equal);
/* Begin with the last line of the file to make unique check easier -
* every already existing item in the table is the latest, so we don't need
* to do anything if an item already exists in the hash table. */
len = g_strv_length(lines);
for (i = len - 1; i >= 0; i--) {
char *key, *data;
void *item;
line = lines[i];
g_strstrip(line);
if (!*line) {
continue;
}
/* if line contains tab char - separate the line at this */
if ((data = strchr(line, '\t'))) {
*data = '\0';
key = line;
data++;
} else {
key = line;
data = NULL;
}
/* Check if the entry is already in the result list. */
if (g_hash_table_lookup_extended(ht, key, NULL, NULL)) {
continue;
}
/* Item is new - prepend it to the list. Because the record are read
* in reverse order the prepend generates a list in the right order. */
if ((item = func(key, data))) {
g_hash_table_insert(ht, key, NULL);
gl = g_list_prepend(gl, item);
/* Don't put more entries into the list than requested. */
if (max_items && g_hash_table_size(ht) >= max_items) {
break;
}
}
}
g_hash_table_destroy(ht);
return gl;
}
/**
* Fills the given list store by matching data of also given src list.
*/
gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src)
{
gboolean found = FALSE;
GtkTreeIter iter;
if (!input || !*input) {
for (GList *l = src; l; l = l->next) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
found = TRUE;
}
} else {
for (GList *l = src; l; l = l->next) {
char *value = (char*)l->data;
if (g_str_has_prefix(value, input)) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
found = TRUE;
}
}
}
return found;
}
/**
* Fills file path completion entries into given list store for also given
* input.
*/
gboolean util_filename_fill_completion(GtkListStore *store, const char *input)
{
gboolean found = FALSE;
GError *error = NULL;
char *input_dirname, *real_dirname;
const char *last_slash, *input_basename;
GDir *dir;
last_slash = strrchr(input, '/');
input_basename = last_slash ? last_slash + 1 : input;
input_dirname = g_strndup(input, input_basename - input);
real_dirname = util_expand(
*input_dirname ? input_dirname : ".",
UTIL_EXP_TILDE|UTIL_EXP_DOLLAR
);
dir = g_dir_open(real_dirname, 0, &error);
if (error) {
/* Can't open directory, likely bad user input */
g_error_free(error);
} else {
GtkTreeIter iter;
const char *filename;
char *fullpath, *result;
while ((filename = g_dir_read_name(dir))) {
if (g_str_has_prefix(filename, input_basename)) {
fullpath = g_build_filename(real_dirname, filename, NULL);
if (g_file_test(fullpath, G_FILE_TEST_IS_DIR)) {
result = g_strconcat(input_dirname, filename, "/", NULL);
} else {
result = g_strconcat(input_dirname, filename, NULL);
}
g_free(fullpath);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, result, -1);
g_free(result);
found = TRUE;
}
}
g_dir_close(dir);
}
g_free(input_dirname);
g_free(real_dirname);
return found;
}
/**
* Returns the script result as string.
* Returned string must be freed by g_free.
*/
char *util_js_result_as_string(WebKitJavascriptResult *result)
{
#if WEBKIT_CHECK_VERSION(2, 22, 0)
JSCValue *value;
value = webkit_javascript_result_get_js_value(result);
return jsc_value_to_string(value);
#else
JSValueRef value;
JSStringRef string;
size_t len;
char *retval = NULL;
value = webkit_javascript_result_get_value(result);
string = JSValueToStringCopy(webkit_javascript_result_get_global_context(result),
value, NULL);
len = JSStringGetMaximumUTF8CStringSize(string);
if (len) {
retval = g_malloc(len);
JSStringGetUTF8CString(string, retval, len);
}
JSStringRelease(string);
return retval;
#endif
}
double util_js_result_as_number(WebKitJavascriptResult *result)
{
#if WEBKIT_CHECK_VERSION(2, 22, 0)
JSCValue *value;
value = webkit_javascript_result_get_js_value(result);
return jsc_value_to_double(value);
#else
JSValueRef value = webkit_javascript_result_get_value(result);
return JSValueToNumber(webkit_javascript_result_get_global_context(result), value,
NULL);
#endif
}
/**
* Reads given input and try to parse ~/, ~user, $VAR or ${VAR} expansion
* from the start of the input and moves the input pointer to the first
* not expanded char. If no expansion pattern was found, the first char is
* appended to given GString.
*
* Please note that for a single ~, g_get_home_dir() is used and if a valid
* HOME environment variable is set it is preferred than passwd file.
* However, for ~user expansion the passwd file is always used.
*
* @input: String pointer with the content to be parsed.
* @str: GString that will be filled with expanded content.
* @flags Flags that determine which expansion are processed.
* @quoteable: String of chars that are additionally escapable by \.
* Returns TRUE if input started with expandable pattern.
*/
gboolean util_parse_expansion(const char **input, GString *str,
int flags, const char *quoteable)
{
GString *name;
const char *env, *prev, quote = '\\';
struct passwd *pwd;
gboolean expanded = FALSE;
prev = *input;
if (flags & UTIL_EXP_TILDE && **input == '~') {
/* skip ~ */
(*input)++;
if (**input == '/') {
g_string_append(str, g_get_home_dir());
expanded = TRUE;
/* if there is no char or space after ~/ skip the / to get
* /home/user instead of /home/user/ */
if (!*(*input + 1) || VB_IS_SPACE(*(*input + 1))) {
(*input)++;
}
} else {
/* look ahead to / space or end of string to get a possible
* username for ~user pattern */
name = g_string_new("");
/* current char is ~ that is skipped to get the user name */
while (VB_IS_IDENT(**input)) {
g_string_append_c(name, **input);
(*input)++;
}
/* append the name to the destination string */
if ((pwd = getpwnam(name->str))) {
g_string_append(str, pwd->pw_dir);
expanded = TRUE;
}
g_string_free(name, TRUE);
}
/* move pointer back to last expanded char */
(*input)--;
} else if (flags & UTIL_EXP_DOLLAR && **input == '$') {
/* skip the $ */
(*input)++;
name = g_string_new("");
/* look for ${VAR}*/
if (**input == '{') {
/* skip { */
(*input)++;
/* look ahead to } or end of string */
while (**input && **input != '}') {
g_string_append_c(name, **input);
(*input)++;
}
/* if the } was reached - skip this */
if (**input == '}') {
(*input)++;
}
} else { /* process $VAR */
/* look ahead to /, space or end of string */
while (VB_IS_IDENT(**input)) {
g_string_append_c(name, **input);
(*input)++;
}
}
/* append the variable to the destination string */
if ((env = g_getenv(name->str))) {
g_string_append(str, env);
}
/* move pointer back to last expanded char */
(*input)--;
/* variable are expanded even if they do not exists */
expanded = TRUE;
g_string_free(name, TRUE);
}
if (!expanded) {
/* restore the pointer position if no expansion was found */
*input = prev;
/* handle escaping of quoteable chars */
if (**input == quote) {
/* move pointer to the next char */
(*input)++;
if (!**input) {
/* if input ends here - use only the quote char */
g_string_append_c(str, quote);
(*input)--;
} else if (strchr(quoteable, **input)
|| (flags & UTIL_EXP_TILDE && **input == '~')
|| (flags & UTIL_EXP_DOLLAR && **input == '$')
) {
/* escaped char becomes only char */
g_string_append_c(str, **input);
} else {
/* put escape char and next char into the result string */
g_string_append_c(str, quote);
g_string_append_c(str, **input);
}
} else {
/* take the char like it is */
g_string_append_c(str, **input);
}
}
return expanded;
}
/**
* Sanituze filename by removeing directory separator by underscore.
*
* The string is modified in place.
*/
char *util_sanitize_filename(char *filename)
{
return g_strdelimit(filename, G_DIR_SEPARATOR_S, '_');
}
/**
* Strips password from a uri.
*
* Return newly allocated string or NULL.
*/
char *util_sanitize_uri(const char *uri_str)
{
char *sanitized_uri;
char *for_display;
GUri *uri;
if (!uri_str) {
return NULL;
}
#if WEBKIT_CHECK_VERSION(2, 24, 0)
for_display = webkit_uri_for_display(uri_str);
if (!for_display) {
for_display = g_strdup(uri_str);
}
#else
for_display = g_strdup(uri_str);
#endif
/* Sanitize the uri only in case there is a @ which might be the indicator
* for credentials used in uri. */
if (!strchr(for_display, '@')) {
return for_display;
}
uri = g_uri_parse(for_display, G_URI_FLAGS_NONE, NULL);
sanitized_uri = g_uri_to_string_partial(uri, G_URI_HIDE_PASSWORD);
g_uri_unref(uri);
g_free(for_display);
return sanitized_uri;
}
char *util_strcasestr(const char *haystack, const char *needle)
{
guchar c1, c2;
int i, j;
int nlen = strlen(needle);
int hlen = strlen(haystack) - nlen + 1;
for (i = 0; i < hlen; i++) {
for (j = 0; j < nlen; j++) {
c1 = haystack[i + j];
c2 = needle[j];
if (toupper(c1) != toupper(c2)) {
goto next;
}
}
return (char*)haystack + i;
next:
;
}
return NULL;
}
/**
* Replaces appearances of search in string by given replace.
* Returns a new allocated string if search was found.
*/
char *util_str_replace(const char *search, const char *replace, const char *string)
{
if (!string) {
return NULL;
}
char **buf = g_strsplit(string, search, -1);
char *ret = g_strjoinv(replace, buf);
g_strfreev(buf);
return ret;
}
/**
* Escapes some special characters in the source string by inserting a '\'
* before them. Acts like g_strescape() but does not demage utf8 chars.
* Returns a newly allocated string.
*/
char *util_strescape(const char *source, const char *exceptions)
{
GString *result = g_string_new(NULL);
while (TRUE) {
char c = *source++;
if ('\0' == c) {
goto done;
}
if (exceptions && !strchr(exceptions, c)) {
continue;
}
switch (c) {
case '\n':
g_string_append(result, "\\n");
break;
case '\"':
g_string_append(result, "\\\"");
break;
case '\\':
g_string_append(result, "\\\\");
break;
case '\b':
g_string_append(result, "\\b");
break;
case '\f':
g_string_append(result, "\\f");
break;
case '\r':
g_string_append(result, "\\r");
break;
case '\t':
g_string_append(result, "\\t");
break;
default:
g_string_append_c(result, c);
}
}
done:
return g_string_free(result, FALSE);
}
static void create_dir_if_not_exists(const char *dirpath)
{
if (!g_file_test(dirpath, G_FILE_TEST_IS_DIR)) {
g_mkdir_with_parents(dirpath, 0755);
}
}
/**
* Compares given string against also given list of patterns.
*
* * Matches any sequence of characters.
* ? Matches any single character except of '/'.
* {foo,bar} Matches foo or bar - '{', ',' and '}' within this pattern must be
* escaped by '\'. '*' and '?' have no special meaning within the
* curly braces.
* *?{} these chars must always be escaped by '\' to match them literally
*/
gboolean util_wildmatch(const char *pattern, const char *subject)
{
const char *end;
int braces, patlen, count;
/* loop through all pattens */
for (count = 0; *pattern; pattern = (*end == ',' ? end + 1 : end), count++) {
/* find end of the pattern - but be careful with comma in curly braces */
braces = 0;
for (end = pattern; *end && (*end != ',' || braces || *(end - 1) == '\\'); ++end) {
if (*end == '{') {
braces++;
} else if (*end == '}') {
braces--;
}
}
/* ignore single comma */
if (*pattern == *end) {
continue;
}
/* calculate the length of the pattern */
patlen = end - pattern;
/* if this pattern matches - return */
if (match(pattern, patlen, subject)) {
return true;
}
}
if (!count) {
/* empty pattern matches only on empty subject */
return !*subject;
}
/* there where one or more patterns but none of them matched */
return false;
}
/**
* Compares given subject string against the given pattern.
* The pattern needs not to bee NUL terminated.
*/
static gboolean match(const char *pattern, int patlen, const char *subject)
{
int i;
char sl, pl;
while (patlen > 0) {
switch (*pattern) {
case '?':
/* '?' matches a single char except of / and subject end */
if (*subject == '/' || !*subject) {
return false;
}
break;
case '*':
/* easiest case - the '*' ist the last char in pattern - this
* will always match */
if (patlen == 1) {
return true;
}
/* Try to match as much as possible. Try to match the complete
* uri, if that fails move forward in uri and check for a
* match. */
i = strlen(subject);
while (i >= 0 && !match(pattern + 1, patlen - 1, subject + i)) {
i--;
}
return i >= 0;
case '}':
/* spurious '}' in pattern */
return false;
case '{':
/* possible {foo,bar} pattern */
return match_list(pattern, patlen, subject);
case '\\':
/* '\' escapes next special char */
if (strchr("*?{}", pattern[1])) {
pattern++;
patlen--;
if (*pattern != *subject) {
return false;
}
}
break;
default:
/* compare case insensitive */
sl = *subject;
if (VB_IS_UPPER(sl)) {
sl += 'a' - 'A';
}
pl = *pattern;
if (VB_IS_UPPER(pl)) {
pl += 'a' - 'A';
}
if (sl != pl) {
return false;
}
break;
}
/* do another loop run with next pattern and subject char */
pattern++;
patlen--;
subject++;
}
/* on end of pattern only a also ended subject is a match */
return !*subject;
}
/**
* Matches pattern starting with '{'.
* This function can process also on none null terminated pattern.
*/
static gboolean match_list(const char *pattern, int patlen, const char *subject)
{
int endlen;
const char *end, *s;
/* finde the next none escaped '}' */
for (end = pattern, endlen = patlen; endlen > 0 && *end != '}'; end++, endlen--) {
/* if escape char - move pointer one additional step */
if (*end == '\\') {
end++;
endlen--;
}
}
if (!*end) {
/* unterminated '{' in pattern */
return false;
}
s = subject;
end++; /* skip over } */
endlen--;
pattern++; /* skip over { */
patlen--;
while (true) {
switch (*pattern) {
case ',':
if (match(end, endlen, s)) {
return true;
}
s = subject;
pattern++;
patlen--;
break;
case '}':
return match(end, endlen, s);
case '\\':
if (pattern[1] == ',' || pattern[1] == '}' || pattern[1] == '{') {
pattern++;
patlen--;
}
/* fall through */
default:
if (*pattern == *s) {
pattern++;
patlen--;
s++;
} else {
/* this item of the list does not match - move forward to
* the next none escaped ',' or '}' */
s = subject;
for (s = subject; *pattern != ',' && *pattern != '}'; pattern++, patlen--) {
/* if escape char is found - skip next char */
if (*pattern == '\\') {
pattern++;
patlen--;
}
}
/* found ',' skip over it to check the next list item */
if (*pattern == ',') {
pattern++;
patlen--;
}
}
}
}
}
/**
* Get the time span to given string like '1y5dh' (one year and five days and
* one hour).
*/
GTimeSpan util_string_to_timespan(const char *input)
{
unsigned int multiplier = 0;
GTimeSpan sum = 0;
gboolean hasmultiplier = FALSE;
while (*input) {
if (VB_IS_DIGIT(*input)) {
multiplier = multiplier * 10 + (*input - '0');
/* Use separate variable to differentiate between no multiplier
* set (is same as 1) or ultiplier of '0'. */
hasmultiplier = TRUE;
} else {
GTimeSpan s = 0;
switch (*input) {
case 'y': s = 365 * G_TIME_SPAN_DAY; break;
case 'w': s = 7 * G_TIME_SPAN_DAY; break;
case 'd': s = G_TIME_SPAN_DAY; break;
case 'h': s = G_TIME_SPAN_HOUR; break;
case 'm': s = G_TIME_SPAN_MINUTE; break;
case 's': s = G_TIME_SPAN_SECOND; break;
}
sum += s * (hasmultiplier ? multiplier : 1);
/* Unset multiplier for th epossible next multiplier in input */
multiplier = 0;
hasmultiplier = FALSE;
}
input++;
}
return sum;
}
fanglingsu-vimb-448e7e2/src/util.h 0000664 0000000 0000000 00000005367 15145416123 0017100 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _UTIL_H
#define _UTIL_H
#include
#include "main.h"
enum {
UTIL_EXP_TILDE = 0x01, /* ~/ and ~user expansion */
UTIL_EXP_DOLLAR = 0x02, /* $ENV and ${ENV} expansion */
};
typedef void *(*Util_Content_Func)(const char*, const char*);
char *util_build_path(const char *path, const char *dir);
void util_cleanup(void);
gboolean util_create_dir_if_not_exists(const char *dirpath);
gboolean util_create_tmp_file(const char *content, char **file);
char *util_expand(const char *src, int expflags);
gboolean util_file_append(const char *file, const char *format, ...);
gboolean util_file_prepend(const char *file, const char *format, ...);
void util_file_prepend_line(const char *file, const char *line,
unsigned int max_lines);
char *util_file_pop_line(const char *file, int *item_count);
char *util_get_config_dir(void);
char *util_get_data_dir(void);
char *util_get_cache_dir(void);
char *util_get_file_contents(const char *filename, gsize *length);
gboolean util_file_set_content(const char *file, const char *contents);
char **util_get_lines(const char *filename);
GList *util_strv_to_unique_list(char **lines, Util_Content_Func func,
guint max_items);
gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src);
gboolean util_filename_fill_completion(GtkListStore *store, const char *input);
char *util_js_result_as_string(WebKitJavascriptResult *result);
double util_js_result_as_number(WebKitJavascriptResult *result);
gboolean util_parse_expansion(const char **input, GString *str, int flags,
const char *quoteable);
char *util_sanitize_filename(char *filename);
char *util_sanitize_uri(const char *uri_str);
char *util_strcasestr(const char *haystack, const char *needle);
char *util_str_replace(const char* search, const char* replace, const char* string);
char *util_strescape(const char *source, const char *exceptions);
gboolean util_wildmatch(const char *pattern, const char *subject);
GTimeSpan util_string_to_timespan(const char *input);
#endif /* end of include guard: _UTIL_H */
fanglingsu-vimb-448e7e2/src/webextension/ 0000775 0000000 0000000 00000000000 15145416123 0020451 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/src/webextension/.gitignore 0000664 0000000 0000000 00000000053 15145416123 0022437 0 ustar 00root root 0000000 0000000 # Auto-generated header file
js-snippets.h
fanglingsu-vimb-448e7e2/src/webextension/Makefile 0000664 0000000 0000000 00000002143 15145416123 0022111 0 ustar 00root root 0000000 0000000 include ../../config.mk
OBJ = $(patsubst %.c, %.lo, $(wildcard *.c))
JSFILES = $(wildcard ../scripts/webext/*.js)
all: $(EXTTARGET)
clean:
$(RM) $(EXTTARGET) $(OBJ)
$(RM) js-snippets.h
$(EXTTARGET): $(OBJ)
@echo "$(CC) $@"
$(Q)$(CC) $(OBJ) $(EXTLDFLAGS) -o $@
$(OBJ): js-snippets.h
js-snippets.h: $(JSFILES)
$(Q)$(RM) $@
@echo "create $@ from webextension scripts"
@echo "/**" > $@
@echo " * Minified JavaScript snippets for webextension DOM operations." >> $@
@echo " * Auto-generated by Makefile - DO NOT EDIT MANUALLY!" >> $@
@echo " * Generated from: $(JSFILES)" >> $@
@echo " * " >> $@
@echo " * To update: modify source .js files in src/scripts/webext/" >> $@
@echo " * Then run: make clean && make" >> $@
@echo " */" >> $@
@echo "" >> $@
@echo "#ifndef _JS_SNIPPETS_H" >> $@
@echo "#define _JS_SNIPPETS_H" >> $@
@echo "" >> $@
$(Q)for file in $(JSFILES); do \
../scripts/js2h.sh $$file >> $@; \
done
@echo "" >> $@
@echo "#endif /* _JS_SNIPPETS_H */" >> $@
%.lo: %.c
@echo "${CC} $@"
$(Q)$(CC) $(EXTCPPFLAGS) $(EXTCFLAGS) -fPIC -c -o $@ $<
-include $(OBJ:.lo=.d)
.PHONY: all clean
fanglingsu-vimb-448e7e2/src/webextension/ext-main.c 0000664 0000000 0000000 00000042142 15145416123 0022342 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include
#include
#include "ext-main.h"
#include "ext-util.h"
#include "js-snippets.h"
static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer,
GIOStream *stream, GCredentials *credentials, gpointer extension);
static void on_dbus_connection_created(GObject *source_object,
GAsyncResult *result, gpointer data);
static void emit_page_created(GDBusConnection *connection, guint64 pageid);
static void emit_page_created_pending(GDBusConnection *connection);
static void queue_page_created_signal(guint64 pageid);
static WebKitWebPage *get_web_page_or_return_dbus_error(GDBusMethodInvocation *invocation,
WebKitWebExtension *extension, guint64 pageid);
static void dbus_handle_method_call(GDBusConnection *conn, const char *sender,
const char *object_path, const char *interface_name, const char *method,
GVariant *parameters, GDBusMethodInvocation *invocation, gpointer data);
static void on_page_created(WebKitWebExtension *ext, WebKitWebPage *webpage, gpointer data);
static void on_web_page_document_loaded(WebKitWebPage *webpage, gpointer extension);
static gboolean on_web_page_send_request(WebKitWebPage *webpage, WebKitURIRequest *request,
WebKitURIResponse *response, gpointer extension);
static const GDBusInterfaceVTable interface_vtable = {
dbus_handle_method_call,
NULL,
NULL
};
static const char introspection_xml[] =
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"";
/* Global struct to hold internal used variables. */
struct Ext {
guint regid;
GDBusConnection *connection;
GHashTable *headers;
/* GHashTable *documents removed - was only used for DOM API event listener tracking */
GArray *page_created_signals;
};
struct Ext ext = {0};
/**
* Webextension entry point.
*/
G_MODULE_EXPORT
void webkit_web_extension_initialize_with_user_data(WebKitWebExtension *extension, GVariant *data)
{
char *server_address;
GDBusAuthObserver *observer;
g_variant_get(data, "(m&s)", &server_address);
if (!server_address) {
g_warning("UI process did not start D-Bus server");
return;
}
g_signal_connect(extension, "page-created", G_CALLBACK(on_page_created), NULL);
observer = g_dbus_auth_observer_new();
g_signal_connect(observer, "authorize-authenticated-peer",
G_CALLBACK(on_authorize_authenticated_peer), extension);
g_dbus_connection_new_for_address(server_address,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, observer, NULL,
(GAsyncReadyCallback)on_dbus_connection_created, extension);
g_object_unref(observer);
}
static gboolean on_authorize_authenticated_peer(GDBusAuthObserver *observer,
GIOStream *stream, GCredentials *credentials, gpointer extension)
{
gboolean authorized = FALSE;
if (credentials) {
GCredentials *own_credentials;
GError *error = NULL;
own_credentials = g_credentials_new();
if (g_credentials_is_same_user(credentials, own_credentials, &error)) {
authorized = TRUE;
} else {
g_warning("Failed to authorize web extension connection: %s", error->message);
g_error_free(error);
}
g_object_unref(own_credentials);
} else {
g_warning ("No credentials received from UI process.\n");
}
return authorized;
}
static void on_dbus_connection_created(GObject *source_object,
GAsyncResult *result, gpointer data)
{
static GDBusNodeInfo *node_info = NULL;
GDBusConnection *connection;
GError *error = NULL;
if (!node_info) {
node_info = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
}
connection = g_dbus_connection_new_for_address_finish(result, &error);
if (error) {
g_warning("Failed to connect to UI process: %s", error->message);
g_error_free(error);
return;
}
/* register the webextension object */
ext.regid = g_dbus_connection_register_object(
connection,
VB_WEBEXTENSION_OBJECT_PATH,
node_info->interfaces[0],
&interface_vtable,
WEBKIT_WEB_EXTENSION(data),
NULL,
&error);
if (!ext.regid) {
g_warning("Failed to register web extension object: %s", error->message);
g_error_free(error);
g_object_unref(connection);
return;
}
emit_page_created_pending(connection);
ext.connection = connection;
}
/**
* Emit the page created signal that is used in the UI process to finish the
* dbus proxy connection.
*/
static void emit_page_created(GDBusConnection *connection, guint64 pageid)
{
GError *error = NULL;
/* propagate the signal over dbus */
g_dbus_connection_emit_signal(G_DBUS_CONNECTION(connection), NULL,
VB_WEBEXTENSION_OBJECT_PATH, VB_WEBEXTENSION_INTERFACE,
"PageCreated", g_variant_new("(t)", pageid), &error);
if (error) {
g_warning("Failed to emit signal PageCreated: %s", error->message);
g_error_free(error);
}
}
/**
* Emit queued page created signals.
*/
static void emit_page_created_pending(GDBusConnection *connection)
{
int i;
guint64 pageid;
if (!ext.page_created_signals) {
return;
}
for (i = 0; i < ext.page_created_signals->len; i++) {
pageid = g_array_index(ext.page_created_signals, guint64, i);
emit_page_created(connection, pageid);
}
g_array_free(ext.page_created_signals, TRUE);
ext.page_created_signals = NULL;
}
/**
* Write the page id of the created page to a queue to send them to the ui
* process when the dbus connection is established.
*/
static void queue_page_created_signal(guint64 pageid)
{
if (!ext.page_created_signals) {
ext.page_created_signals = g_array_new(FALSE, FALSE, sizeof(guint64));
}
ext.page_created_signals = g_array_append_val(ext.page_created_signals, pageid);
}
static WebKitWebPage *get_web_page_or_return_dbus_error(GDBusMethodInvocation *invocation,
WebKitWebExtension *extension, guint64 pageid)
{
WebKitWebPage *page = webkit_web_extension_get_page(extension, pageid);
if (!page) {
g_warning("invalid page id %lu", pageid);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS, "Invalid page ID: %"G_GUINT64_FORMAT, pageid);
}
return page;
}
/**
* Handle dbus method calls.
*/
static void dbus_handle_method_call(GDBusConnection *conn, const char *sender,
const char *object_path, const char *interface_name, const char *method,
GVariant *parameters, GDBusMethodInvocation *invocation, gpointer extension)
{
char *value;
guint64 pageid;
WebKitWebPage *page;
if (g_str_has_prefix(method, "EvalJs")) {
char *result = NULL;
gboolean success;
gboolean no_result;
JSValueRef ref = NULL;
JSGlobalContextRef jsContext;
g_variant_get(parameters, "(ts)", &pageid, &value);
page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid);
if (!page) {
return;
}
no_result = !g_strcmp0(method, "EvalJsNoResult");
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
WebKitFrame *frame = webkit_web_page_get_main_frame(page);
G_GNUC_END_IGNORE_DEPRECATIONS
if (!frame) {
g_warning("EvalJs: main frame is NULL for page %" G_GUINT64_FORMAT, pageid);
if (no_result) {
g_dbus_method_invocation_return_value(invocation, NULL);
} else {
g_dbus_method_invocation_return_value(invocation, g_variant_new("(bs)", FALSE, ""));
}
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
jsContext = webkit_frame_get_javascript_context_for_script_world(
frame,
webkit_script_world_get_default()
);
G_GNUC_END_IGNORE_DEPRECATIONS
success = ext_util_js_eval(jsContext, value, &ref);
if (no_result) {
g_dbus_method_invocation_return_value(invocation, NULL);
} else {
result = ext_util_js_ref_to_string(jsContext, ref);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(bs)", success, result));
g_free(result);
}
} else if (!g_strcmp0(method, "FocusInput")) {
JSValueRef result = NULL;
JSGlobalContextRef jsContext;
WebKitFrame *frame;
g_variant_get(parameters, "(t)", &pageid);
page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid);
if (!page) {
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
frame = webkit_web_page_get_main_frame(page);
G_GNUC_END_IGNORE_DEPRECATIONS
if (!frame) {
g_warning("FocusInput: main frame is NULL for page %" G_GUINT64_FORMAT, pageid);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", FALSE));
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
jsContext = webkit_frame_get_javascript_context_for_script_world(
frame,
webkit_script_world_get_default()
);
G_GNUC_END_IGNORE_DEPRECATIONS
/* Focus first visible input using minified JavaScript */
ext_util_js_eval(jsContext, JS_FOCUS_INPUT, &result);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (!g_strcmp0(method, "SetHeaderSetting")) {
g_variant_get(parameters, "(s)", &value);
if (ext.headers) {
soup_header_free_param_list(ext.headers);
ext.headers = NULL;
}
ext.headers = soup_header_parse_param_list(value);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (!g_strcmp0(method, "LockInput")) {
char *js;
JSValueRef result = NULL;
JSGlobalContextRef jsContext;
WebKitFrame *frame;
g_variant_get(parameters, "(ts)", &pageid, &value);
page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid);
if (!page) {
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
frame = webkit_web_page_get_main_frame(page);
G_GNUC_END_IGNORE_DEPRECATIONS
if (!frame) {
g_warning("LockInput: main frame is NULL for page %" G_GUINT64_FORMAT, pageid);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", FALSE));
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
jsContext = webkit_frame_get_javascript_context_for_script_world(
frame,
webkit_script_world_get_default()
);
G_GNUC_END_IGNORE_DEPRECATIONS
/* Lock input using minified JavaScript - escape ID to prevent injection */
char *escaped_id = g_strescape(value, NULL);
js = g_strdup_printf(JS_LOCK_INPUT, escaped_id);
ext_util_js_eval(jsContext, js, &result);
g_free(js);
g_free(escaped_id);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (!g_strcmp0(method, "UnlockInput")) {
char *js;
JSValueRef result = NULL;
JSGlobalContextRef jsContext;
WebKitFrame *frame;
g_variant_get(parameters, "(ts)", &pageid, &value);
page = get_web_page_or_return_dbus_error(invocation, WEBKIT_WEB_EXTENSION(extension), pageid);
if (!page) {
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
frame = webkit_web_page_get_main_frame(page);
G_GNUC_END_IGNORE_DEPRECATIONS
if (!frame) {
g_warning("UnlockInput: main frame is NULL for page %" G_GUINT64_FORMAT, pageid);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", FALSE));
return;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
jsContext = webkit_frame_get_javascript_context_for_script_world(
frame,
webkit_script_world_get_default()
);
G_GNUC_END_IGNORE_DEPRECATIONS
/* Unlock input using minified JavaScript - escape ID to prevent injection */
char *escaped_id = g_strescape(value, NULL);
js = g_strdup_printf(JS_UNLOCK_INPUT, escaped_id);
ext_util_js_eval(jsContext, js, &result);
g_free(js);
g_free(escaped_id);
g_dbus_method_invocation_return_value(invocation, NULL);
}
}
/**
* Callback for web extensions page-created signal.
*/
static void on_page_created(WebKitWebExtension *extension, WebKitWebPage *webpage, gpointer data)
{
guint64 pageid;
if (!webpage) {
g_warning("on_page_created called with NULL webpage!");
return;
}
if (!extension) {
g_warning("on_page_created called with NULL extension!");
return;
}
pageid = webkit_web_page_get_id(webpage);
if (ext.connection) {
emit_page_created(ext.connection, pageid);
} else {
queue_page_created_signal(pageid);
}
g_object_connect(webpage,
"signal::send-request", G_CALLBACK(on_web_page_send_request), extension,
"signal::document-loaded", G_CALLBACK(on_web_page_document_loaded), extension,
NULL);
}
/**
* Callback for web pages document-loaded signal.
*/
static void on_web_page_document_loaded(WebKitWebPage *webpage, gpointer extension)
{
guint64 pageid = webkit_web_page_get_id(webpage);
WebKitUserMessage *message;
/* Send message with page ID as parameter.
* This allows the main process to match this page with the correct dbus proxy. */
message = webkit_user_message_new("page-document-loaded",
g_variant_new("(t)", pageid));
webkit_web_page_send_message_to_view(webpage, message, NULL, NULL, NULL);
}
/**
* Callback for web pages send-request signal.
*/
static gboolean on_web_page_send_request(WebKitWebPage *webpage, WebKitURIRequest *request,
WebKitURIResponse *response, gpointer extension)
{
char *name, *value;
SoupMessageHeaders *headers;
GHashTableIter iter;
if (!ext.headers) {
return FALSE;
}
/* Change request headers according to the users preferences. */
headers = webkit_uri_request_get_http_headers(request);
if (!headers) {
return FALSE;
}
g_hash_table_iter_init(&iter, ext.headers);
while (g_hash_table_iter_next(&iter, (gpointer*)&name, (gpointer*)&value)) {
/* Null value is used to indicate that the header should be
* removed completely. */
if (value == NULL) {
soup_message_headers_remove(headers, name);
} else {
soup_message_headers_replace(headers, name, value);
}
}
return FALSE;
}
fanglingsu-vimb-448e7e2/src/webextension/ext-main.h 0000664 0000000 0000000 00000002014 15145416123 0022341 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _EXT_MAIN_H
#define _EXT_MAIN_H
#define VB_WEBEXTENSION_SERVICE_NAME "org.vimb.browser.WebExtension"
#define VB_WEBEXTENSION_OBJECT_PATH "/org/vimb/browser/WebExtension"
#define VB_WEBEXTENSION_INTERFACE "org.vimb.browser.WebExtension"
#endif /* end of include guard: _EXT_MAIN_H */
fanglingsu-vimb-448e7e2/src/webextension/ext-util.c 0000664 0000000 0000000 00000005245 15145416123 0022376 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
#include "../config.h"
#include "ext-util.h"
/**
* Evaluates given string as script and return if this call succeed or not.
*/
gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result)
{
JSStringRef js_str;
JSValueRef exc = NULL, res = NULL;
js_str = JSStringCreateWithUTF8CString(script);
res = JSEvaluateScript(ctx, js_str, JSContextGetGlobalObject(ctx), NULL, 0, &exc);
JSStringRelease(js_str);
if (exc) {
*result = exc;
return FALSE;
}
*result = res;
return TRUE;
}
/**
* Creates a temporary file with given content.
*
* Upon success, and if file is non-NULL, the actual file path used is
* returned in file. This string should be freed with g_free() when not
* needed any longer.
*/
gboolean ext_util_create_tmp_file(const char *content, char **file)
{
int fp;
ssize_t bytes, len;
fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL);
if (fp == -1) {
g_critical("Could not create temp file %s", *file);
g_free(*file);
return FALSE;
}
len = strlen(content);
/* write content into temporary file */
bytes = write(fp, content, len);
if (bytes < len) {
close(fp);
unlink(*file);
g_critical("Could not write temp file %s", *file);
g_free(*file);
return FALSE;
}
close(fp);
return TRUE;
}
/**
* Returns a new allocates string for given value reference.
* String must be freed if not used anymore.
*/
char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref)
{
char *string;
size_t len;
JSStringRef str_ref;
g_return_val_if_fail(ref != NULL, NULL);
str_ref = JSValueToStringCopy(ctx, ref, NULL);
len = JSStringGetMaximumUTF8CStringSize(str_ref);
string = g_new0(char, len);
JSStringGetUTF8CString(str_ref, string, len);
JSStringRelease(str_ref);
return string;
}
fanglingsu-vimb-448e7e2/src/webextension/ext-util.h 0000664 0000000 0000000 00000002123 15145416123 0022373 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#ifndef _EXT_UTIL_H
#define _EXT_UTIL_H
#include
#include
gboolean ext_util_create_tmp_file(const char *content, char **file);
gboolean ext_util_js_eval(JSContextRef ctx, const char *script, JSValueRef *result);
char* ext_util_js_ref_to_string(JSContextRef ctx, JSValueRef ref);
#endif /* end of include guard: _EXT_UTIL_H */
fanglingsu-vimb-448e7e2/tests/ 0000775 0000000 0000000 00000000000 15145416123 0016312 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/tests/.gitignore 0000664 0000000 0000000 00000000023 15145416123 0020275 0 ustar 00root root 0000000 0000000 /test-*
!/test-*.c
fanglingsu-vimb-448e7e2/tests/Makefile 0000664 0000000 0000000 00000000617 15145416123 0017756 0 ustar 00root root 0000000 0000000 CPPFLAGS = -I ../
include ../config.mk
TEST_PROGS = test-util \
test-shortcut \
test-handler \
test-file-storage
all: $(TEST_PROGS)
$(Q)LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):." gtester --verbose $(TEST_PROGS)
${TEST_PROGS}: ../$(SRCDIR)/vimb.so
test-%: test-%.c
@echo "${CC} $@"
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< ../$(SRCDIR)/vimb.so $(LDFLAGS)
clean:
$(RM) $(TEST_PROGS)
fanglingsu-vimb-448e7e2/tests/manual/ 0000775 0000000 0000000 00000000000 15145416123 0017567 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/tests/manual/dummy.html 0000664 0000000 0000000 00000000124 15145416123 0021605 0 ustar 00root root 0000000 0000000
Dummy Page
Dummy Page
fanglingsu-vimb-448e7e2/tests/manual/editable-focus-in-iframe.html 0000664 0000000 0000000 00000000314 15145416123 0025206 0 ustar 00root root 0000000 0000000
Track Focu/Blur also within iFrames
fanglingsu-vimb-448e7e2/tests/manual/editable-focus.html 0000664 0000000 0000000 00000002436 15145416123 0023350 0 ustar 00root root 0000000 0000000
Input mode Switching
Run with scripts=on and strict-focus=off
- If page is loade, vimb should be in input mode.
- Set
strict-focus=on and reload page. Vimb should keep
in normal mode
- Independent from the
strict-focus should vimb switch
to input mode if the button is clicked
Also the following element using contenteditable="true"
should switch vimb into input mode on click.
Clicking this element using contenteditable="true" should
switch vimb into input mode too.
fanglingsu-vimb-448e7e2/tests/manual/hints/ 0000775 0000000 0000000 00000000000 15145416123 0020714 5 ustar 00root root 0000000 0000000 fanglingsu-vimb-448e7e2/tests/manual/hints/hints.html 0000664 0000000 0000000 00000002166 15145416123 0022734 0 ustar 00root root 0000000 0000000
hints
fanglingsu-vimb-448e7e2/tests/manual/hints/label-positioning.html 0000664 0000000 0000000 00000001360 15145416123 0025221 0 ustar 00root root 0000000 0000000
Hint Label positioning with negative margins
When using hints (f) on this page, the hint should be placed on
the upper left corner of the links.
one
two
To test the hints within iFrame
allow-universal-access-from-file-urls must be enabled.
And the page reloaded.
fanglingsu-vimb-448e7e2/tests/manual/hints/wrapped.html 0000664 0000000 0000000 00000000400 15145416123 0023236 0 ustar 00root root 0000000 0000000
Simple Links
one
two
fanglingsu-vimb-448e7e2/tests/manual/js-window-close.html 0000664 0000000 0000000 00000000315 15145416123 0023500 0 ustar 00root root 0000000 0000000
JS Window Close
Click on following link should close the window without crashing.
Close this window
fanglingsu-vimb-448e7e2/tests/manual/window-open.html 0000664 0000000 0000000 00000001334 15145416123 0022724 0 ustar 00root root 0000000 0000000
Window open
Enable set prevent-newwindow=on
Following link should be opened into current window.
target="_new"
target="_blank"
javascript:window-open
onclick window-open
This target iframe link should
load into the iframe.
fanglingsu-vimb-448e7e2/tests/test-file-storage.c 0000664 0000000 0000000 00000010547 15145416123 0022023 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2019 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
static char *pwd;
static char *none_existing_file = "_absent.txt";
static char *created_file = "_created.txt";
static char *existing_file = "_existent.txt";
static void test_ephemeral_no_file(void)
{
FileStorage *s;
char **lines;
char *file_path;
file_path = g_build_filename(pwd, none_existing_file, NULL);
/* make sure the file does not exist */
remove(file_path);
s = file_storage_new(pwd, none_existing_file, TRUE);
g_assert_nonnull(s);
g_assert_cmpstr(file_path, ==, file_storage_get_path(s));
g_assert_true(file_storage_is_readonly(s));
/* empty file storage */
lines = file_storage_get_lines(s);
g_assert_cmpint(g_strv_length(lines), ==, 0);
g_strfreev(lines);
file_storage_append(s, "-%s\n", "foo");
file_storage_append(s, "-%s\n", "bar");
/* File must not be created on appending data to storage */
g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR));
lines = file_storage_get_lines(s);
g_assert_cmpint(g_strv_length(lines), ==, 3);
g_assert_cmpstr(lines[0], ==, "-foo");
g_assert_cmpstr(lines[1], ==, "-bar");
g_strfreev(lines);
file_storage_free(s);
g_free(file_path);
}
static void test_file_created(void)
{
FileStorage *s;
char *file_path;
file_path = g_build_filename(pwd, created_file, NULL);
remove(file_path);
g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR));
s = file_storage_new(pwd, created_file, FALSE);
g_assert_false(file_storage_is_readonly(s));
g_assert_cmpstr(file_path, ==, file_storage_get_path(s));
/* check that file is created only on first write */
g_assert_false(g_file_test(file_path, G_FILE_TEST_IS_REGULAR));
file_storage_append(s, "");
g_assert_true(g_file_test(file_path, G_FILE_TEST_IS_REGULAR));
file_storage_free(s);
g_free(file_path);
}
static void test_ephemeral_with_file(void)
{
FileStorage *s;
char *file_path;
char **lines;
char *content = NULL;
gboolean result;
file_path = g_build_filename(pwd, existing_file, NULL);
s = file_storage_new(pwd, existing_file, TRUE);
g_assert_nonnull(s);
g_assert_true(file_storage_is_readonly(s));
g_assert_cmpstr(file_path, ==, file_storage_get_path(s));
/* file does not exists yet */
lines = file_storage_get_lines(s);
g_assert_cmpint(g_strv_length(lines), ==, 0);
g_strfreev(lines);
/* empty file storage but file with two lines */
result = g_file_set_contents(file_path, "one\n", -1, NULL);
g_assert_true(result);
lines = file_storage_get_lines(s);
g_assert_cmpint(g_strv_length(lines), ==, 2);
g_strfreev(lines);
file_storage_append(s, "%s\n", "two ephemeral");
lines = file_storage_get_lines(s);
g_assert_cmpint(g_strv_length(lines), ==, 3);
g_assert_cmpstr(lines[0], ==, "one");
g_assert_cmpstr(lines[1], ==, "two ephemeral");
g_strfreev(lines);
/* now make sure the file was not changed */
g_file_get_contents(file_path, &content, NULL, NULL);
g_assert_cmpstr(content, ==, "one\n");
file_storage_free(s);
g_free(file_path);
}
int main(int argc, char *argv[])
{
int result;
g_test_init(&argc, &argv, NULL);
pwd = g_get_current_dir();
g_test_add_func("/test-file-storage/ephemeral-no-file", test_ephemeral_no_file);
g_test_add_func("/test-file-storage/file-created", test_file_created);
g_test_add_func("/test-file-storage/ephemeral-with-file", test_ephemeral_with_file);
result = g_test_run();
remove(existing_file);
remove(created_file);
g_free(pwd);
return result;
}
fanglingsu-vimb-448e7e2/tests/test-handler.c 0000664 0000000 0000000 00000011453 15145416123 0021054 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
static Handler *handler = NULL;
#define TEST_URI "http://fanglingsu.github.io/vimb/"
static void test_handler_add(void)
{
g_assert_true(handler_add(handler, "https", "e"));
}
static void test_handler_remove(void)
{
g_assert_true(handler_add(handler, "https", "e"));
g_assert_true(handler_remove(handler, "https"));
g_assert_false(handler_remove(handler, "https"));
}
static void null_log_handler(const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
/* Suppress all log messages in subprocess to avoid test framework issues */
}
static void test_handler_run_success(void)
{
if (g_test_subprocess()) {
/* Suppress test framework log tracking to avoid critical errors */
g_log_set_handler(NULL, G_LOG_LEVEL_MASK, null_log_handler, NULL);
handler_add(handler, "http", "echo -n 'handled uri %s'");
handler_handle_uri(handler, TEST_URI);
return;
}
g_test_trap_subprocess("/test-handlers/handle_uri/success", 0, 0);
g_test_trap_assert_passed();
g_test_trap_assert_stdout("handled uri " TEST_URI);
}
static void stderr_log_handler(const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
/* Write warnings directly to stderr, bypassing test framework */
if (log_level & G_LOG_LEVEL_WARNING) {
fprintf(stderr, "%s\n", message);
fflush(stderr);
}
}
static void test_handler_run_failed(void)
{
if (g_test_subprocess()) {
/* Set our handler to write warnings directly to stderr, bypassing test framework */
g_log_set_handler(NULL, G_LOG_LEVEL_WARNING, stderr_log_handler, NULL);
handler_add(handler, "http", "unknown-program %s");
handler_handle_uri(handler, TEST_URI);
return;
}
g_test_trap_subprocess("/test-handlers/handle_uri/failed", 0, 0);
g_test_trap_assert_failed();
g_test_trap_assert_stderr("*Can't run *unknown-program*");
}
static void test_handler_fill_completion(void)
{
GtkListStore *store;
g_assert_true(handler_add(handler, "http", "echo"));
g_assert_true(handler_add(handler, "https", "echo"));
g_assert_true(handler_add(handler, "about", "echo"));
g_assert_true(handler_add(handler, "ftp", "echo"));
store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING);
/* check case where multiple matches are found */
g_assert_true(handler_fill_completion(handler, store, "http"));
g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 2);
gtk_list_store_clear(store);
/* check case where only one matches are found */
g_assert_true(handler_fill_completion(handler, store, "f"));
g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 1);
gtk_list_store_clear(store);
/* check case where no match is found */
g_assert_false(handler_fill_completion(handler, store, "unknown"));
g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 0);
gtk_list_store_clear(store);
/* check case without apllied filters */
g_assert_true(handler_fill_completion(handler, store, ""));
g_assert_cmpint(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL), ==, 4);
gtk_list_store_clear(store);
}
int main(int argc, char *argv[])
{
int result;
handler = handler_new();
g_test_init(&argc, &argv, NULL);
g_test_add_func("/test-handlers/add", test_handler_add);
g_test_add_func("/test-handlers/remove", test_handler_remove);
g_test_add_func("/test-handlers/handle_uri/success", test_handler_run_success);
g_test_add_func("/test-handlers/handle_uri/failed", test_handler_run_failed);
g_test_add_func("/test-handlers/fill-completion", test_handler_fill_completion);
result = g_test_run();
handler_free(handler);
return result;
}
fanglingsu-vimb-448e7e2/tests/test-shortcut.c 0000664 0000000 0000000 00000007732 15145416123 0021317 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
Shortcut *sc = NULL;
static void test_shortcut(void)
{
char *uri;
unsigned int i;
struct {
char *in;
char *out;
} data[] = {
/* call with shortcut identifier */
{"_vimb1_ zero one", "only-zero:zero%20one"},
/* don't fail on unmatches quotes if there are only $0 placeholders */
{"_vimb1_ 'unmatches quote", "only-zero:'unmatches%20quote"},
/* check if all placeholders $0 are replaces */
{"_vimb5_ one two", "double-zero:one%20two-one%20two"},
/* check the defautl shortcut is used */
{"zero one two three", "default:zero-two%20three"},
/* don't remove non matched placeholders */
{"zero", "default:zero-$2"},
{"_vimb3_ zero one two three four five six seven eight nine", "fullrange:zero-one-nine"}
};
for (i = 0; i < LENGTH(data); i++) {
uri = shortcut_get_uri(sc, data->in);
g_assert_cmpstr(uri, ==, data->out);
g_free(uri);
}
}
static void test_shortcut_shell_param(void)
{
char *uri;
/* double quotes */
uri = shortcut_get_uri(sc, "_vimb6_ \"rail station\" city hall");
g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall");
g_free(uri);
/* single quotes */
uri = shortcut_get_uri(sc, "_vimb6_ 'rail station' 'city hall'");
g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall");
g_free(uri);
/* ignore none matching quote errors */
uri = shortcut_get_uri(sc, "_vimb6_ \"rail station\" \"city hall");
g_assert_cmpstr(uri, ==, "shell:rail%20station-city%20hall");
g_free(uri);
/* don't fill up quoted param with unquoted stuff */
uri = shortcut_get_uri(sc, "_vimb6_ \"param 1\" \"param 2\" ignored params");
g_assert_cmpstr(uri, ==, "shell:param%201-param%202");
g_free(uri);
/* allo quotes within tha last parameter */
uri = shortcut_get_uri(sc, "_vimb6_ param1 param2 \"containing quotes\"");
g_assert_cmpstr(uri, ==, "shell:param1-param2%20%22containing%20quotes%22");
g_free(uri);
}
static void test_shortcut_remove(void)
{
char *uri;
g_assert_true(shortcut_remove(sc, "_vimb4_"));
/* check if the shortcut is really no used */
uri = shortcut_get_uri(sc, "_vimb4_ test");
g_assert_cmpstr(uri, ==, "default:_vimb4_-$2");
g_free(uri);
g_assert_false(shortcut_remove(sc, "_vimb4_"));
}
int main(int argc, char *argv[])
{
int result;
sc = shortcut_new();
g_assert_true(shortcut_add(sc, "_vimb1_", "only-zero:$0"));
g_assert_true(shortcut_add(sc, "_vimb2_", "default:$0-$2"));
g_assert_true(shortcut_add(sc, "_vimb3_", "fullrange:$0-$1-$9"));
g_assert_true(shortcut_add(sc, "_vimb4_", "for-remove:$0"));
g_assert_true(shortcut_add(sc, "_vimb5_", "double-zero:$0-$0"));
g_assert_true(shortcut_add(sc, "_vimb6_", "shell:$0-$1"));
g_assert_true(shortcut_set_default(sc, "_vimb2_"));
g_test_init(&argc, &argv, NULL);
g_test_add_func("/test-shortcut/get_uri/single", test_shortcut);
g_test_add_func("/test-shortcut/get_uri/shell-param", test_shortcut_shell_param);
g_test_add_func("/test-shortcut/remove", test_shortcut_remove);
result = g_test_run();
shortcut_free(sc);
return result;
}
fanglingsu-vimb-448e7e2/tests/test-util.c 0000664 0000000 0000000 00000031357 15145416123 0020421 0 ustar 00root root 0000000 0000000 /**
* vimb - a webkit based vim like browser.
*
* Copyright (C) 2012-2018 Daniel Carl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include
#include
#include
static void check_expand(const char *str, const char *expected)
{
char *result = util_expand(str, UTIL_EXP_DOLLAR|UTIL_EXP_TILDE);
g_assert_cmpstr(result, ==, expected);
g_free(result);
}
static void test_expand_evn(void)
{
/* set environment var for testing expansion */
g_setenv("VIMB_VAR", "value", true);
check_expand("$VIMB_VAR", "value");
check_expand("$VIMB_VAR", "value");
check_expand("$VIMB_VAR$VIMB_VAR", "valuevalue");
check_expand("${VIMB_VAR}", "value");
check_expand("my$VIMB_VAR", "myvalue");
check_expand("'$VIMB_VAR'", "'value'");
check_expand("${VIMB_VAR}s ", "values ");
g_unsetenv("UNKNOWN");
check_expand("$UNKNOWN", "");
check_expand("${UNKNOWN}", "");
check_expand("'$UNKNOWN'", "''");
}
static void test_expand_escaped(void)
{
g_setenv("VIMB_VAR", "value", true);
check_expand("\\$VIMB_VAR", "$VIMB_VAR");
check_expand("\\${VIMB_VAR}", "${VIMB_VAR}");
check_expand("\\~/", "~/");
check_expand("\\~/vimb", "~/vimb");
check_expand("\\~root", "~root");
check_expand("\\\\$VIMB_VAR", "\\value"); /* \\$VAR becomes \ExpandedVar */
check_expand("\\\\\\$VIMB_VAR", "\\$VIMB_VAR"); /* \\\$VAR becomes \$VAR */
}
static void test_expand_tilde_home(void)
{
char *dir;
const char *home = g_get_home_dir();
check_expand("~", "~");
check_expand("~/", home);
check_expand("foo~/bar", "foo~/bar");
check_expand("~/foo", (dir = g_strdup_printf("%s/foo", home)));
g_free(dir);
check_expand("foo ~/bar", (dir = g_strdup_printf("foo %s/bar", home)));
g_free(dir);
check_expand("~/~", (dir = g_strdup_printf("%s/~", home)));
g_free(dir);
check_expand("~/~/", (dir = g_strdup_printf("%s/~/", home)));
g_free(dir);
}
static void test_expand_tilde_user(void)
{
const char *user = g_get_user_name();
const char *home;
char *in, *out;
struct passwd *pwd;
/* initialize home */
pwd = getpwnam(user);
g_assert_nonnull(pwd);
home = pwd->pw_dir;
/* don't expand within words */
in = g_strdup_printf("foo~%s/bar", user);
check_expand(in, in);
g_free(in);
check_expand((in = g_strdup_printf("foo ~%s", user)), (out = g_strdup_printf("foo %s", home)));
g_free(in);
g_free(out);
check_expand((in = g_strdup_printf("~%s", user)), home);
g_free(in);
check_expand((in = g_strdup_printf("~%s/bar", user)), (out = g_strdup_printf("%s/bar", home)));
g_free(in);
g_free(out);
}
static void test_strcasestr(void)
{
g_assert_nonnull(util_strcasestr("Vim like Browser", "browser"));
g_assert_nonnull(util_strcasestr("Vim like Browser", "vim LIKE"));
}
static void test_str_replace(void)
{
char *value;
value = util_str_replace("a", "uu", "foo bar baz");
g_assert_cmpstr(value, ==, "foo buur buuz");
g_free(value);
value = util_str_replace("$1", "placeholder", "string with $1");
g_assert_cmpstr(value, ==, "string with placeholder");
g_free(value);
}
static void test_wildmatch_simple(void)
{
g_assert_true(util_wildmatch("", ""));
g_assert_true(util_wildmatch("w", "w"));
g_assert_true(util_wildmatch(".", "."));
g_assert_true(util_wildmatch("~", "~"));
g_assert_true(util_wildmatch("wildmatch", "WildMatch"));
g_assert_true(util_wildmatch("wild\\match", "wild\\match"));
/* no special meaning of . and ~ in pattern */
g_assert_false(util_wildmatch(".", "w"));
g_assert_false(util_wildmatch("~", "w"));
g_assert_false(util_wildmatch("wild", "wild "));
g_assert_false(util_wildmatch("wild", " wild"));
g_assert_false(util_wildmatch("wild", "\\ wild"));
g_assert_false(util_wildmatch("wild", "\\wild"));
g_assert_false(util_wildmatch("wild", "wild\\"));
g_assert_false(util_wildmatch("wild\\1", "wild\\2"));
}
static void test_wildmatch_questionmark(void)
{
g_assert_true(util_wildmatch("wild?atch", "wildmatch"));
g_assert_true(util_wildmatch("wild?atch", "wildBatch"));
g_assert_true(util_wildmatch("wild?atch", "wild?atch"));
g_assert_true(util_wildmatch("?ild?atch", "MildBatch"));
g_assert_true(util_wildmatch("foo\\?bar", "foo?bar"));
g_assert_true(util_wildmatch("???", "foo"));
g_assert_true(util_wildmatch("???", "bar"));
g_assert_false(util_wildmatch("foo\\?bar", "foorbar"));
g_assert_false(util_wildmatch("?", ""));
g_assert_false(util_wildmatch("b??r", "bar"));
/* ? does not match / in contrast to * which does */
g_assert_false(util_wildmatch("user?share", "user/share"));
}
static void test_wildmatch_wildcard(void)
{
g_assert_true(util_wildmatch("*", ""));
g_assert_true(util_wildmatch("*", "Match as much as possible"));
g_assert_true(util_wildmatch("*match", "prefix match"));
g_assert_true(util_wildmatch("match*", "match suffix"));
g_assert_true(util_wildmatch("match*", "match*"));
g_assert_true(util_wildmatch("match\\*", "match*"));
g_assert_true(util_wildmatch("match\\\\*", "match\\*"));
g_assert_true(util_wildmatch("do * match", "do a infix match"));
/* '*' matches also / in contrast to other implementations */
g_assert_true(util_wildmatch("start*end", "start/something/end"));
g_assert_true(util_wildmatch("*://*.io/*", "http://fanglingsu.github.io/vimb/"));
/* multiple * should act like a single one */
g_assert_true(util_wildmatch("**", ""));
g_assert_true(util_wildmatch("match **", "Match as much as possible"));
g_assert_true(util_wildmatch("f***u", "fu"));
g_assert_false(util_wildmatch("match\\*", "match fail"));
g_assert_false(util_wildmatch("f***u", "full"));
}
static void test_wildmatch_curlybraces(void)
{
g_assert_true(util_wildmatch("{foo}", "foo"));
g_assert_true(util_wildmatch("{foo,bar}", "foo"));
g_assert_true(util_wildmatch("{foo,bar}", "bar"));
g_assert_true(util_wildmatch("foo{lish,t}bar", "foolishbar"));
g_assert_true(util_wildmatch("foo{lish,t}bar", "footbar"));
/* esacped special chars */
g_assert_true(util_wildmatch("foo\\{l\\}bar", "foo{l}bar"));
g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "bar"));
g_assert_true(util_wildmatch("ba{r,z\\{\\}}", "baz{}"));
g_assert_true(util_wildmatch("test{one\\,two,three}", "testone,two"));
g_assert_true(util_wildmatch("test{one\\,two,three}", "testthree"));
/* backslash before none special char is a normal char */
g_assert_true(util_wildmatch("back{\\slash,}", "back\\slash"));
g_assert_true(util_wildmatch("one\\two", "one\\two"));
g_assert_true(util_wildmatch("\\}match", "}match"));
g_assert_true(util_wildmatch("\\{", "{"));
/* empty list parts */
g_assert_true(util_wildmatch("{}", ""));
g_assert_true(util_wildmatch("{,}", ""));
g_assert_true(util_wildmatch("{,foo}", ""));
g_assert_true(util_wildmatch("{,foo}", "foo"));
g_assert_true(util_wildmatch("{bar,}", ""));
g_assert_true(util_wildmatch("{bar,}", "bar"));
/* no special meaning of ? and * in curly braces */
g_assert_true(util_wildmatch("ab{*,cd}ef", "ab*ef"));
g_assert_true(util_wildmatch("ab{d,?}ef", "ab?ef"));
g_assert_false(util_wildmatch("{foo,bar}", "foo,bar"));
g_assert_false(util_wildmatch("}match{ it", "}match{ anything"));
/* don't match single parts that are seperated by escaped ',' */
g_assert_false(util_wildmatch("{a,b\\,c,d}", "b"));
g_assert_false(util_wildmatch("{a,b\\,c,d}", "c"));
/* lonesome braces - this is a syntax error and will always be false */
g_assert_false(util_wildmatch("}", "}"));
g_assert_false(util_wildmatch("}", ""));
g_assert_false(util_wildmatch("}suffix", "}suffux"));
g_assert_false(util_wildmatch("}suffix", "suffux"));
g_assert_false(util_wildmatch("{", "{"));
g_assert_false(util_wildmatch("{", ""));
g_assert_false(util_wildmatch("{foo", "{foo"));
g_assert_false(util_wildmatch("{foo", "foo"));
g_assert_false(util_wildmatch("foo{bar", "foo{bar"));
}
static void test_wildmatch_complete(void)
{
g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "http://fanglingsu.github.io/vimb/"));
g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "https://github.com/fanglingsu/vimb/"));
}
static void test_wildmatch_multi(void)
{
g_assert_true(util_wildmatch("foo,?", "foo"));
g_assert_true(util_wildmatch("foo,?", "f"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "foo"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bar"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bor"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "br"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "baz"));
g_assert_true(util_wildmatch("foo,b{a,o,}r,ba?", "bat"));
g_assert_false(util_wildmatch("foo,b{a,o,}r,ba?", "foo,"));
g_assert_false(util_wildmatch("foo,?", "fo"));
}
static void test_strescape(void)
{
unsigned int i;
char *value;
struct {
char *in;
char *expected;
char *excludes;
} data[] = {
{"", "", NULL},
{"foo", "foo", NULL},
{"a\nb\nc", "a\\nb\\nc", NULL},
{"foo\"bar", "foo\\bar", NULL},
{"s\\t", "s\\\\t", NULL},
{"u\bv", "u\\bv", NULL},
{"w\fx", "w\\fx", NULL},
{"y\rz", "y\\rz", NULL},
{"tab\tdi\t", "tab\\tdi\\t", NULL},
{"❧äüö\n@foo\t\"bar\"", "❧äüö\\n@foo\\t\\\"bar\\\"", NULL},
{"❧äüö\n@foo\t\"bar\"", "❧äüö\\n@foo\\t\"bar\"", "\""},
{"❧äüö\n@foo\t\"bar\"", "❧äüö\n@foo\t\\\"bar\\\"", "\n\t"},
};
for (i = 0; i < LENGTH(data); i++) {
value = util_strescape(data->in, data->excludes);
g_assert_cmpstr(value, ==, data->expected);
g_free(value);
}
}
static void test_string_to_timespan(void)
{
g_assert_cmpuint(util_string_to_timespan("d"), ==, G_TIME_SPAN_DAY);
g_assert_cmpuint(util_string_to_timespan("h"), ==, G_TIME_SPAN_HOUR);
g_assert_cmpuint(util_string_to_timespan("m"), ==, G_TIME_SPAN_MINUTE);
g_assert_cmpuint(util_string_to_timespan("s"), ==, G_TIME_SPAN_SECOND);
g_assert_cmpuint(util_string_to_timespan("y"), ==, G_TIME_SPAN_DAY * 365);
g_assert_cmpuint(util_string_to_timespan("w"), ==, G_TIME_SPAN_DAY * 7);
/* use counters */
g_assert_cmpuint(util_string_to_timespan("1s"), ==, G_TIME_SPAN_SECOND);
g_assert_cmpuint(util_string_to_timespan("2s"), ==, 2 * G_TIME_SPAN_SECOND);
g_assert_cmpuint(util_string_to_timespan("34s"), ==, 34 * G_TIME_SPAN_SECOND);
g_assert_cmpuint(util_string_to_timespan("0s"), ==, 0);
/* combine counters and different units */
g_assert_cmpuint(util_string_to_timespan("ds"), ==, G_TIME_SPAN_DAY + G_TIME_SPAN_SECOND);
g_assert_cmpuint(util_string_to_timespan("2dh0s"), ==, (2 * G_TIME_SPAN_DAY) + G_TIME_SPAN_HOUR);
/* unparsabel values */
g_assert_cmpuint(util_string_to_timespan(""), ==, 0);
g_assert_cmpuint(util_string_to_timespan("-"), ==, 0);
g_assert_cmpuint(util_string_to_timespan("5-"), ==, 0);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/test-util/expand-env", test_expand_evn);
g_test_add_func("/test-util/expand-escaped", test_expand_escaped);
g_test_add_func("/test-util/expand-tilde-home", test_expand_tilde_home);
g_test_add_func("/test-util/expand-tilde-user", test_expand_tilde_user);
g_test_add_func("/test-util/strcasestr", test_strcasestr);
g_test_add_func("/test-util/str_replace", test_str_replace);
g_test_add_func("/test-util/wildmatch-simple", test_wildmatch_simple);
g_test_add_func("/test-util/wildmatch-questionmark", test_wildmatch_questionmark);
g_test_add_func("/test-util/wildmatch-wildcard", test_wildmatch_wildcard);
g_test_add_func("/test-util/wildmatch-curlybraces", test_wildmatch_curlybraces);
g_test_add_func("/test-util/wildmatch-complete", test_wildmatch_complete);
g_test_add_func("/test-util/wildmatch-multi", test_wildmatch_multi);
g_test_add_func("/test-util/strescape", test_strescape);
g_test_add_func("/test-util/string_to_timespan", test_string_to_timespan);
return g_test_run();
}
fanglingsu-vimb-448e7e2/vimb.desktop 0000664 0000000 0000000 00000000443 15145416123 0017501 0 ustar 00root root 0000000 0000000 # Based on Arch Linux' chromium.desktop
[Desktop Entry]
Name=vimb
GenericName=Web Browser
Comment=Access the Internet
Exec=vimb %U
Terminal=false
Icon=
Type=Application
Categories=GTK;Network;WebBrowser;
MimeType=text/html;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;
fanglingsu-vimb-448e7e2/vimb.metainfo.xml 0000664 0000000 0000000 00000002055 15145416123 0020432 0 ustar 00root root 0000000 0000000
comical.desktop
CC0-1.0
GPL-3.0+
Vimb
the Vim-like browser
Vimb is a fast and lightweight vim like web browser based on the webkit
web browser engine and the GTK toolkit. Vimb is modal like the great vim
editor and also easily configurable during runtime. Vimb is mostly keyboard
driven and does not distract you from your daily work.
https://fanglingsu.github.io/vimb/media/vimb-completion.png
https://fanglingsu.github.io/vimb/media/vimb-hints.png
https://fanglingsu.github.io/vimb/
https://github.com/fanglingsu/vimb/issues