pax_global_header00006660000000000000000000000064151454161230014514gustar00rootroot0000000000000052 comment=448e7e2e66471b4cbfa8f373bb4bd7d7134cafaa fanglingsu-vimb-448e7e2/000077500000000000000000000000001514541612300151505ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/.github/000077500000000000000000000000001514541612300165105ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/.github/ISSUE_TEMPLATE.md000066400000000000000000000002411514541612300212120ustar00rootroot00000000000000 ### Steps to reproduce ### Expected behaviour ### Actual behaviour fanglingsu-vimb-448e7e2/.github/stale.yml000066400000000000000000000015141514541612300203440ustar00rootroot00000000000000# 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/.gitignore000066400000000000000000000000551514541612300171400ustar00rootroot00000000000000*.[oad] *.lo *.so *.tar.gz sandbox version.h fanglingsu-vimb-448e7e2/.travis.yml000066400000000000000000000004321514541612300172600ustar00rootroot00000000000000branches: 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.md000066400000000000000000000362161514541612300167710ustar00rootroot00000000000000# 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.md000066400000000000000000000067331514541612300174120ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000001045131514541612300161610ustar00rootroot00000000000000 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/Makefile000066400000000000000000000032261514541612300166130ustar00rootroot00000000000000version = 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.md000066400000000000000000000107361514541612300164360ustar00rootroot00000000000000# Vimb - the Vim-like browser [![Build Status](https://travis-ci.com/fanglingsu/vimb.svg?branch=master)](https://travis-ci.com/fanglingsu/vimb) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Latest Release](https://img.shields.io/github/release/fanglingsu/vimb.svg?style=flat)](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.mk000066400000000000000000000030711514541612300167470ustar00rootroot00000000000000ifneq ($(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/000077500000000000000000000000001514541612300157155ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/doc/vimb.1000066400000000000000000001372341514541612300167460ustar00rootroot00000000000000.\" 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/000077500000000000000000000000001514541612300157375ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/src/.gitignore000066400000000000000000000000161514541612300177240ustar00rootroot00000000000000config.h vimb fanglingsu-vimb-448e7e2/src/Makefile000066400000000000000000000017211514541612300174000ustar00rootroot00000000000000include ../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.h000066400000000000000000000111051514541612300171760ustar00rootroot00000000000000/** * 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.c000066400000000000000000000324611514541612300175450ustar00rootroot00000000000000/** * 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.h000066400000000000000000000032171514541612300175470ustar00rootroot00000000000000/** * 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.c000066400000000000000000000214241514541612300177130ustar00rootroot00000000000000/** * 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.h000066400000000000000000000024571514541612300177250ustar00rootroot00000000000000/** * 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.c000066400000000000000000000252041514541612300175240ustar00rootroot00000000000000/** * 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.h000066400000000000000000000030311514541612300175230ustar00rootroot00000000000000/** * 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.c000066400000000000000000000176341514541612300202670ustar00rootroot00000000000000/** * 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.h000066400000000000000000000024741514541612300202700ustar00rootroot00000000000000/** * 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.h000066400000000000000000000115331514541612300201150ustar00rootroot00000000000000/** * 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.c000066400000000000000000000111021514541612300205240ustar00rootroot00000000000000/** * 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.h000066400000000000000000000020101514541612300205270ustar00rootroot00000000000000/** * 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.c000066400000000000000000001352641514541612300165320ustar00rootroot00000000000000/** * 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.h000066400000000000000000000022641514541612300165300ustar00rootroot00000000000000/** * 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.c000066400000000000000000000226231514541612300200670ustar00rootroot00000000000000/** * 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.h000066400000000000000000000027201514541612300200700ustar00rootroot00000000000000/** * 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.c000066400000000000000000000100061514541612300204610ustar00rootroot00000000000000/** * 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.h000066400000000000000000000023651514541612300204770ustar00rootroot00000000000000/** * 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.c000066400000000000000000000050061514541612300175210ustar00rootroot00000000000000/** * 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.h000066400000000000000000000022371514541612300175310ustar00rootroot00000000000000/** * 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.c000066400000000000000000000341321514541612300172330ustar00rootroot00000000000000/** * 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.h000066400000000000000000000023221514541612300172340ustar00rootroot00000000000000/** * 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.c000066400000000000000000000167221514541612300176140ustar00rootroot00000000000000/** * 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.h000066400000000000000000000023661514541612300176200ustar00rootroot00000000000000/** * 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.c000066400000000000000000000131411514541612300172420ustar00rootroot00000000000000/** * 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.h000066400000000000000000000017501514541612300172520ustar00rootroot00000000000000/** * 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.c000066400000000000000000002363361514541612300170440ustar00rootroot00000000000000/** * 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.h000066400000000000000000000304041514541612300170350ustar00rootroot00000000000000/** * 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.c000066400000000000000000000552431514541612300166710ustar00rootroot00000000000000/** * 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.h000066400000000000000000000024601514541612300166670ustar00rootroot00000000000000/** * 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.c000066400000000000000000000633241514541612300174030ustar00rootroot00000000000000/** * 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.h000066400000000000000000000020531514541612300174000ustar00rootroot00000000000000/** * 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/000077500000000000000000000000001514541612300174265ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/src/scripts/.gitignore000066400000000000000000000000121514541612300214070ustar00rootroot00000000000000scripts.h fanglingsu-vimb-448e7e2/src/scripts/dom_operations.js000066400000000000000000000320641514541612300230130ustar00rootroot00000000000000/* 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.js000066400000000000000000000001171514541612300250160ustar00rootroot00000000000000vimb_editor_map.get("%lu").disabled=false; vimb_editor_map.get("%lu").focus(); fanglingsu-vimb-448e7e2/src/scripts/hints.css000066400000000000000000000010161514541612300212630ustar00rootroot00000000000000span[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.js000066400000000000000000000520741514541612300211210ustar00rootroot00000000000000var 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.js000066400000000000000000000006641514541612300242050ustar00rootroot00000000000000/* 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.sh000077500000000000000000000022141514541612300206320ustar00rootroot00000000000000#!/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.js000066400000000000000000000056421514541612300212710ustar00rootroot00000000000000var 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.js000066400000000000000000000026211514541612300231720ustar00rootroot00000000000000(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.js000066400000000000000000000002061514541612300244710ustar00rootroot00000000000000if (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/000077500000000000000000000000001514541612300207245ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/src/scripts/webext/check_editable_focus.js000066400000000000000000000007731514541612300253760ustar00rootroot00000000000000// 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.js000066400000000000000000000015351514541612300236240ustar00rootroot00000000000000// 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.js000066400000000000000000000004571514541612300234370ustar00rootroot00000000000000// 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.js000066400000000000000000000005251514541612300237760ustar00rootroot00000000000000// 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.c000066400000000000000000001033221514541612300175610ustar00rootroot00000000000000/** * 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.h000066400000000000000000000020711514541612300175650ustar00rootroot00000000000000/** * 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.c000066400000000000000000000140351514541612300177610ustar00rootroot00000000000000/** * 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.h000066400000000000000000000023551514541612300177700ustar00rootroot00000000000000/** * 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.c000066400000000000000000000775541514541612300171020ustar00rootroot00000000000000/** * 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.h000066400000000000000000000053671514541612300171000ustar00rootroot00000000000000/** * 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/000077500000000000000000000000001514541612300204515ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/src/webextension/.gitignore000066400000000000000000000000531514541612300224370ustar00rootroot00000000000000# Auto-generated header file js-snippets.h fanglingsu-vimb-448e7e2/src/webextension/Makefile000066400000000000000000000021431514541612300221110ustar00rootroot00000000000000include ../../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.c000066400000000000000000000421421514541612300223420ustar00rootroot00000000000000/** * 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.h000066400000000000000000000020141514541612300223410ustar00rootroot00000000000000/** * 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.c000066400000000000000000000052451514541612300223760ustar00rootroot00000000000000/** * 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.h000066400000000000000000000021231514541612300223730ustar00rootroot00000000000000/** * 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/000077500000000000000000000000001514541612300163125ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/tests/.gitignore000066400000000000000000000000231514541612300202750ustar00rootroot00000000000000/test-* !/test-*.c fanglingsu-vimb-448e7e2/tests/Makefile000066400000000000000000000006171514541612300177560ustar00rootroot00000000000000CPPFLAGS = -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/000077500000000000000000000000001514541612300175675ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/tests/manual/dummy.html000066400000000000000000000001241514541612300216050ustar00rootroot00000000000000 Dummy Page Dummy Page fanglingsu-vimb-448e7e2/tests/manual/editable-focus-in-iframe.html000066400000000000000000000003141514541612300252060ustar00rootroot00000000000000 Track Focu/Blur also within iFrames fanglingsu-vimb-448e7e2/tests/manual/editable-focus.html000066400000000000000000000024361514541612300233500ustar00rootroot00000000000000 Input mode Switching

Run with scripts=on and strict-focus=off

  1. If page is loade, vimb should be in input mode.
  2. Set strict-focus=on and reload page. Vimb should keep in normal mode
  3. 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/000077500000000000000000000000001514541612300207145ustar00rootroot00000000000000fanglingsu-vimb-448e7e2/tests/manual/hints/hints.html000066400000000000000000000021661514541612300227340ustar00rootroot00000000000000 hints
fanglingsu-vimb-448e7e2/tests/manual/hints/label-positioning.html000066400000000000000000000013601514541612300252210ustar00rootroot00000000000000 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.html000066400000000000000000000004001514541612300232360ustar00rootroot00000000000000 Simple Links one
two fanglingsu-vimb-448e7e2/tests/manual/js-window-close.html000066400000000000000000000003151514541612300235000ustar00rootroot00000000000000 JS Window Close Click on following link should close the window without crashing.
Close this window fanglingsu-vimb-448e7e2/tests/manual/window-open.html000066400000000000000000000013341514541612300227240ustar00rootroot00000000000000 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.c000066400000000000000000000105471514541612300220230ustar00rootroot00000000000000/** * 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.c000066400000000000000000000114531514541612300210540ustar00rootroot00000000000000/** * 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.c000066400000000000000000000077321514541612300213170ustar00rootroot00000000000000/** * 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.c000066400000000000000000000313571514541612300204210ustar00rootroot00000000000000/** * 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.desktop000066400000000000000000000004431514541612300175010ustar00rootroot00000000000000# 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.xml000066400000000000000000000020551514541612300204320ustar00rootroot00000000000000 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