pax_global_header00006660000000000000000000000064147536322220014520gustar00rootroot0000000000000052 comment=636637cdbe734c615dbbf2fd9c5533295d0a79c1 luakit-2.4.0/000077500000000000000000000000001475363222200130145ustar00rootroot00000000000000luakit-2.4.0/.gitattributes000066400000000000000000000001011475363222200156770ustar00rootroot00000000000000build-utils/getversion.sh export-subst *.c diff=cpp *.h diff=cpp luakit-2.4.0/.github/000077500000000000000000000000001475363222200143545ustar00rootroot00000000000000luakit-2.4.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001475363222200165375ustar00rootroot00000000000000luakit-2.4.0/.github/ISSUE_TEMPLATE/bug-report---feature-request.md000066400000000000000000000010021475363222200244110ustar00rootroot00000000000000--- name: Report luakit issue about: Use this template to create issues. --- **Current Behavior:** **Desired Behavior:** **How can we reproduce it (step by step):** **Environment:** Linux Distribution & Version: Output of `luakit --version`: **Note about webkit issues:** If you're reporting a rendering issue, please test it with the gnome browser ephiphany as well. If the issue occurs there too, we're very likely not able to help. These issues should be reported to webkit: https://bugs.webkit.org luakit-2.4.0/.github/workflows/000077500000000000000000000000001475363222200164115ustar00rootroot00000000000000luakit-2.4.0/.github/workflows/tests.yml000066400000000000000000000010251475363222200202740ustar00rootroot00000000000000name: Luakit tests on: push: branches: [ "develop" ] pull_request: branches: [ "develop" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install depenencies run: | sudo apt-get update sudo apt-get install luajit libluajit-5.1-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev libgtk-3-dev libglib2.0-dev lua-check lua-luassert lua-filesystem lua-socket - name: make run: make - name: make run-tests run: make run-tests luakit-2.4.0/.gitignore000066400000000000000000000004111475363222200150000ustar00rootroot00000000000000# Generated documentation doc/apidocs doc/html # Build targets luakit luakit.so tests/util.so # Generated source files common/tokenize.h common/tokenize.c buildopts.h # Intermediate build files *.o *.1 # Miscellaneous *.swp *~ .gdb_history .tags tags cscope.* luakit-2.4.0/.travis.yml000066400000000000000000000003231475363222200151230ustar00rootroot00000000000000dist: xenial language: c compiler: - gcc install: sudo apt-get install luajit luajit-5.1 libluajit-5.1-dev lua-filesystem lua-luassert lua-check lua-socket libwebkit2gtk-4.0-dev sqlite3 script: make run-tests luakit-2.4.0/AUTHORS000066400000000000000000000066541475363222200140770ustar00rootroot00000000000000Authors of luakit: Aidan Holm (aidanholm) 2016-2017 Mason Larobina (mason-l) 2010-2012 Contributors to luakit (A to Z): Alexis Daboville 2011 André Aparício 2011 Ben Armston 2012 Chris van Dijk (quigybo) 2010 Clint Adams (Clint) 2010 Constantin Schomburg (xconstruct) 2011 Fabian Streitel 2010-2011 Gregor Uhlenheuer (kongo) 2010 HarryD 2010 Henning Hasemann 2010 Henrik Hallberg (halhen) 2010 Javier Rojas (jerojasro) 2011 Jonas Höglund (FireFly) 2012 Kumar Appaiah (kmap) 2010 LokiChaos 2012 Matthew Wild (MattJ) 2010 Michael Dietrich (emdete) 2010 Michishige Kaito (mkaito) 2012 Pawel Tomak (grodzik) 2010 Pawel Zuzelski (pawelz) 2010 Pete Elmore 2010 Piotr Husiatyński (husio) 2010 Richard Gay 2010 Roman Leonov (liaonau) 2011 Stefan Bolte (portix) 2011 Stjujsckij Nickolaj 2011 Vasuvi 2010-2011 Author of `lib/markdown.lua`: Niklas Frykholm 2008 Author of the JavaScript greasemonkey methods in `lib/userscripts.lua`: Jim Tuttle 2009 Author of the `lib/go_input.lua` javascript: Aldrik Dunbar (n30n) 2009 Author of the `lib/follow_selected.lua` javascript: israellevin 2009 Inherited authors from the awesomewm project: Julien Danjou 2007-2009 Pierre Habouzit 2008 Michael Gehring 2008 Aldo Cortesi 2007-2008 Special thanks to authors from the uzbl & surf projects for inspiration and support: Dieter Plaetinck (Dieter@be) 2009-2010 Enno Boland (tox) 2009-2010 Dequis 2009 Robert Manea (robm) 2009-2010 Simon Lipp (sloonz) 2010 For volumetric graphs on specific contributers "impact" checkout: https://github.com/aidanholm/luakit/graphs/contributors If you believe you should be in this file or notice that I have missed an attribution to somebody else, please contact me (Aidan) or email me a patch for this file with your/their name or names inserted. luakit-2.4.0/CHANGELOG.md000066400000000000000000000752331475363222200146370ustar00rootroot00000000000000# Changelog ## [2.4.0] ### Added - Manage dom events with luakit signals. - An enable_pdfjs setting to go back to letting viewpdf handle PDFs. - `` elements are now hinted as clickable. - `eval_js` of the `page` module can now return wrapped JavaScript function to Lua. ### Changed - Don't compress man page. This should be done by maintainers. - Removed now unsupported/ignored load-icons-ignoring-image-load-setting. - Removed now unsupported/ignored offline_web_application_cache setting. - The `hide_scrollbars` module is now not loaded as a part of the default `rc.lua` as hiding the scrollbars causes issues with smooth scrolling. - The hardware acceleration policy setting now defaults to always since on-demand has been reported to cause rendering issues on at least some systems. - The default WebKit data and cache subdirectories are now used. If you use any website which uses IndexedDB or local storage and wish to retain the saved information, you will need to manually move the corresponding directories to their new locations. Specifically, the mapping is as follows: - `$XDG_DATA_HOME/luakit/local_storage -> $XDG_DATA_HOME/luakit/localstorage` - `$XDG_DATA_HOME/luakit/indexeddb -> $XDG_DATA_HOME/luakit/databases/indexeddb` ### Fixed - Fixed build in a git worktree checkout - Fixed undoclose test sequence issue - Fixed luaH_init() implicit prototype warning - Fixed refresh being needed for the correct NoScript policy to take effect. - Fixes occasional zoom_level reset on start, reset or navigation - Show proper error message when the formfiller module can't parse form.lua - Fixed the scroll widget getting stuck at 99% instead of showing "Bot" - Fixed the document property of the instance of the dom_element class to work for elements that are not part of a subframe. ### Contributors to this release: - @aidanholm - @balejk - @c0dev0id - @harishnkr - @msdemlei - @rdbo - @serg-kozhemyakin - @sideeffect42 ## [2.3.6] ### Fixed - Correct regex compile options type if none are passed ### Contributors to this release: - @jpalus ## [2.3.5] ### Fixed - Disabled matching masked password field content when hinting - Fix assumptions about stack position of package and package.path - Fix crash with --log= argument (remove G_REGEX_OPTIMIZE) ### Contributors to this release: - @PluMGMK - @c0dev0id - @mmmpx ## [2.3.4] ### Added - Add Support for SOUP3 and Webkit2gtk 4.1 ### Fixed - label_styles table fixes for certain conditions ### Contributors to this release: - @c0dev0id - @taobert - @fosskers ## [2.3.3] ### Fixed - Fixed a trailing white space, which fixes the test-suite. ### Contributors to this release: - @c0dev0id (1 commits) ## [2.3.2] ### Changed - Mention FAQ, Quick Start, etc on :help ### Fixed - Fix for glib now using pcre2 - Show proper error message on :tab, :tabd[o] without parameter - Fix undoclose test ### Contributors to this release: - @taobert (2 commits) - @c0dev0id (2 commits) ## [2.3.1] ### Added - Added command `:userscripts-reload` to reload lua scripts. - The tabgroup plugin is now included in luakit (:tabmenu). - Allow configuration of shortcuts that should be passed through (https://github.com/luakit/luakit/pull/921). - Added scalable SVG desktop icon version. ### Changed - Allow functions to be bound to more than one key (https://github.com/luakit/luakit/issues/913). ### Fixed - No more `gdk_keymap_get_default()` compiler warning. - Fixed an issue where links were hinted, but then could not be followed. - Fixed the paging on the the bookmarks page. - Fixed build on Solaris. - Fixed relocation errors on Sparc64. - Fixed proxymenu to show default entries when proxymenu file is not present. - The command mode cursor is now drawn in the user-specified foreground color. ### Contributors to this release: - taobert (9 commits) - Stefan Hagen (9 commits) - Charadon (1 commit) - Claes Nästén (1 commit) - Samuel Walladge (1 commit) - Tom Repetti (1 commit) - arcnmx (1 commit) - jgart (1 commit) ## [2.3] ### Added - Added Gopher protocol support, see comment in rc.lua. - Added two commands to clear website data (:clear-data, :clear-favicon-db). - Added dark mode support setting `application.prefer_dark_mode`. - The tabmenu plugin is now included in luakit (:tabmenu). ### Changed - Removed debug symbol generation for default make. - Changed the C standard from gnu99 to c11 because Webkit wants it. - The proxy module remembers when no proxy or system proxy was used last. - The proxy widget is hidden when proxy "None" is active. ### Fixed - Fixed bounding box not spanning over whole element. - Fixed an issue where styled hint labels caused intransparent bounding boxes. - Fixed a race condition when a tab is closed on NetBSD. - Do not execute "git ls-files" when luakit is not a git repository ### Update information - The gopher module needs `luasocket` installed. ## [2.2] ### Added - Hint CSS can now be customized via `theme.lua`. - Added the `:save` command, to save the complete page as a single MHTML file. ### Changed - `mime-type-decision` is now only emitted for a successful response. - Removed the default Shift-d binding for deleting the current session. - Removed the `socket` widget for Wayland compatibility. - `:bookmarks` will now reuse an existing bookmarks tab if present. - `tablist.always_visible` has been deprecated in favour of `tablist.visibility`. - Empty tabs are no longer saved to history. - Session recovery has been made slightly more robust. ### Fixed - Fixed the tablist not being hidden in fullscreen mode. - Fixed poor performance when loading/saving data with `lousy.pickle`. - Fixed poor memory usage behaviour caused by excessive numbers of Web Views. - Fixed proxy not being set from `proxy.set_active()`. - Fixed `window.new_tab_page` being ignored for bare `:open`/`:tabopen` commands. - Fixed `re_match_text` always matching everything. - Fixed panic when `/etc/hosts` cannot be read. - Fixed `view-source:` pages being inadvertently affected by stylesheets. - Fixed domain-specific keys not being loaded from persisted settings. ## [2.1] ### Added - Added `userstyles.toggle_sheet` function. - Added WebKit build version information to the `luakit://help/` page header - Added WebKit build/runtime version information to the output of `luakit --help` ### Changed - `userstyles` module now continuously applies styles while editing. - Duplicate `download::status` signals are no longer emitted. - Changed default data directory permissions to be user-accessible only (`0700`). - Luakit now changes the cookie database to be user-accessible only (`0600`) automatically. ### Fixed - Improved error when calling `:javascript` command without an argument. ## [2.0] ### Migrating from version 2017-08-10 1. Remove the two `if unique then ... end` blocks from your `rc.lua`. 2. Add `require "unique_instance"` to your `rc.lua`, before all other `require` statements. 4. Remove all configuration files except `rc.lua` and `theme.lua`. Any changes to `globals.lua` need to be migrated to `rc.lua` and changed to use the `settings` API. ### Added - Added `styles.new_style` function. - Added `styles.toggle_sheet` function. - Added `styles.watch_styles` function, and enabled live-editing of user styles. - Added `luakit.install_paths` table. `luakit.install_path` is now deprecated. - Added `Control-Y` readline binding. - Added ability to control whether links from secondary instances open in a new window. - Added `luakit.resource_path` property to control where luakit searches for resource files. - Added `lousy.util.find_resource` function. - Added `scroll` signal. - Added ability to bind actions to webview scroll events. - Added ability to set the default zoom level. - Added `webview` widget `"permission-request"` signal. - Added `webview` widget `hardware_acceleration_policy` property. - Added `webview` widget `allow_file_access_from_file_urls` and `allow_universal_access_from_file_urls` properties. - Added `settings` module and APIs. This replaces the `domain_props` module. - Added `tablist.always_visible` setting. - Added `utf8.len` (same as `string.wlen`) and `utf8.offset` methods. - Added `utf8.charpattern` property. - Added `:set` and `:seton` commands, for changing settings. - Added ability to always save session before exiting luakit. - Added `markup` option to window `set_prompt()` method. - Added `detach-tab` signal. - Added support for multi-byte characters in hints. - Added widget `replace` method. ### Changed - It is no longer necessary to add bindings to tables with `lousy.bind.add_binds()`. - Readline bindings have been moved to `readline.lua`. - Readline bindings are now automatically bound when the input bar is visible. - Unique instance support has been moved to `unique_instance.lua`. - The `image` widget now uses `luakit.resource_path` to locate local files. - The log viewer now shows errors logged by a user-defined rc.lua failing to load. - Luakit will now remove its IPC socket file before restarting. - The editor command now defaults to using `xdg-open` to edit files. The `default` builtin command has been renamed `autodetect`. - Changed `luakit://introspector/` to `luakit://binds/`. - URL completion now uses word-based fuzzy matching. - `:download` now uses the current page URI by default. - `gy` now accepts a count. - `:tabopen` will now only open local files when given an absolute path. - `:styles-list` now lists active styles first and disabled styles last. ### Removed - Removed `domain_props` module. It is replaced by the `settings` module and its APIs. - Removed all configuration files except `rc.lua` and `theme.lua`. - Removed `enable_private_browsing` webview property. - Removed `w.closed_tabs` field. It is now private to the `undoclose` module. ### Fixed - Fixed not finding documentation with custom DOCDIR - Various minor documentation fixes. - Fixed `Control-Scroll` and `Shift-Scroll` key bindings not working with smooth scrolling. - Fixed inability to switch focus between web page elements with `Tab` and `Shift-Tab`. - Fixed log page bug when logging messages with newlines. - Fixed `Up` and `Down` keybindings being broken on completion menu. - Fixed hardcoded path to luakit icon. - Fixed luakit:// pages not working and spewing errors when not using LuaJIT. - Fixed thumbnail hinting not retrieving thumbnail links correctly. - Fixed inability to bind `Modifier-Minus`. - Fixed readline handling of wide characters. - Fixed completion not suggesting history/bookmarks items without titles/tags. - Fixed `:dump` command not working due to use of a removed API. - Fixed follow hints being sometimes truncated by the viewport edge. - Follow mode now renders hints much faster. - Fixed Forward/Back keys not working due to outdated bind syntax. - Fixed opening local files with names containing spaces from `:tabopen` and the command-line. ## [2017-08-10] - Required WebKitGTK+ version: 2.16+ ### Breaking changes - Support for WebKitGTK+ versions older than 2.16 has been removed. - It is no longer possible to override built-in luakit modules with Lua files in one's personal configuration directory. - The configuration files `binds.lua` and `modes.lua` have become built-in luakit modules. Configuration files named `binds.lua` or `modes.lua` will not be loaded. Any custom bindings should be moved to `rc.lua`. ### Added - New `history.frozen` API allows temporarily freezing history collection. - New `lousy.widget.zoom` statusbar widget: shows current page zoom level. - Added `log` signal, emitted whenever a message is logged. - New `widget.is_alive` property. Can be accessed even if the widget has been destroyed. - Added `luakit://log/` chrome page: displays log messages. - Added status bar widget that notifies of any Lua warnings or errors. - Added migration, quick-start, and files and directories guides to documentation. - Added frequently-asked questions to documentation. - Added `luakit.wch_upper` and `luakit.wch_lower` key case conversion utility functions. - Added `formfiller.extend` function for extending the formfiller DSL. - Added context-aware command completion. ### Fixed - Fixed code-blocks in documentation being formatted incorrectly. - Fixed incompatibility of `editor.lua` with urxvt. - Fixed slow performance while beginning a search. - Fixed `lousy.util.table.join` merging tables in unpredictable order. - Fixed `image_css` raising errors on page zoom in/out. - Worked around `image_css` breaking slightly when using non-1.0 zoom_level. - Fixed sessions failing to save with a mix of tabs and private tabs. - Worked around webview widgets self-focusing on click. ### Changed - `editor.lua` now uses substitution strings, rather than `global` to determine which editor to open. - `open_editor.lua` now uses `editor.lua` rather than always using `xdg-open`. - `--log` can now set different log levels for different modules, similarly to `mpv`. - Documentation now has inter-page references. - The adblock page-blocked page now has a "Continue anyway" button. - Serializing Lua functions now includes their upvalues. - `adblock` and `styles` now log the directory searched for files. - `` and `` bindings now take an optional count. - Luakit's IPC socket files are now opened in `/tmp/`. - Luakit now checks for accidental use of DEVELOPMENT_PATHS. - IPC endpoints' `emit_signal()` method now accepts a webview ID as its first argument, as well as a webview. This specifies a single destination for the IPC call. - `modes.add_binds()` now verifies that modes with the given names already exist, to guard against typos. - Scrolling keybinds now take a count. ### Contributors to this release: - Aidan Holm (150 commits) - gleachkr (10 commits) - Zhong Jianxin (4 commits) - Stefan Hagen (3 commits) - Aric Belsito (1 commit) - Ygrex (1 commit) ## [2017-07-26] - Required WebKitGTK+ version: 2.14+ - A relatively recent version of GTK+ 3 is required; some features are not available on older versions. ### Added #### Adblock module The adblock module previously available at has been included into the main luakit repository, with the following changes: - Ported adblock module to use WebKit 2 compatible APIs. This breaks compatibility with WebKit 1. - Added color-coding to adblock filter list status indicator. - The Adblock chrome page CSS has been updated to be more consistent with other luakit chrome pages. - An enable/disable button has been added to the Adblock chrome page. - The adblock chrome page has received several other refactors and improvements. - Adblock no longer blocks ads on local files (pages on the `file://` scheme). - Adblock no longer blocks data URIs for performance reasons. - Added links for quickly enabling/disabling filter lists to adblock chrome page. - Made `adblock.enabled` writeable, and removed `adblock.state()` function. - Adblock now blocks pages from being loaded until all filter list rules are fully loaded. - Adblock now enables newly-added filter lists by default. - Fixed a bug where luakit would not start if the adblock subscriptions file was missing. - Fixed broken `:adblock-reload` command. - Improved the consistency and formatting of adblock log messages. - Fixed a bug where the adblock subscriptions file would become corrupted. - Adblock simple mode has been removed. - Fixed parsing of adblock filter list rules containing '#'. - Fixed a bug where adblock would incorrectly block URIs on many domains, due to an design flaw. - Adblock now displays an error page when the adblock module blocks a page navigation. - Improved filter list rule length and ignore count calculation. - Added several optimizations for rule matching that significantly improve performance. See also: - - #### Error pages A new module, `error_page.lua`, allows customization of luakit error pages, such as those displayed when a page fails to load. - Luakit error pages are now displayed with a nicer interface, provided by `error_page.lua` - Chrome page errors are now displayed with the `error_page.lua` module interface. - Error pages now show information about the current proxy, as unintended proxy use can be responsible for page load failures. - Error pages can now be customized with user CSS. See also: - #### User styles A new module, `styles.lua`, supports user stylesheets with `@-moz-document` sections. User stylesheets from are supported. - Luakit now automatically detects and parses user stylesheets on startup. - Added support for enabling/disabling user stylesheets immediately, without refreshing the page. - Added the `:styles-list` command to display the user stylesheets menu. - Added the `:styles-reload` command to reload all user stylesheet files from disk. - Removed the site-specific `user_stylesheet_uri` interface. See also: - #### Other new modules - `open_editor.lua`: Adds support for editing text areas and input fields in an external text editor. - `newtab_chrome.lua`: Adds support for customizing the new/blank tab page (`luakit://newtab/`) with HTML and CSS. - `image_css.lua`: Improves how images are displayed by WebKit. - `vertical_tabs.lua`: Displays tabs in a vertical tab bar to the left of the tab content. - `referer_control_wm.lua`: Adds support for blocking the `Referer` header on cross-origin requests. - `viewpdf.lua`: Adds support for automatically viewing downloaded PDF files. #### New APIs Core APIs: - Added `luakit.process_limit` to control the maximum number of web processes. - Added `luakit.options` and `luakit.webkit2` properties. - Added `lousy.util.table.filter_array()` and `lousy.util.lua_escape()`. - Added luakit spell checking API. A suitable language to check spelling with is automatically detected. - Added website data retrieval and removal APIs. - Added user stylesheet APIs, used by `styles.lua`. Stylesheet objects can be created from Lua code and enabled/disabled for individual `webview` widgets. - Added request API. This supports handling custom URI scheme requests asynchronously. - Added `msg` logging library. This replaces the `info()` and `warn()` functions. - Added more log levels. Luakit now has `fatal`, `error`, `warn`, `info`, `verbose`, and `debug` log levels. - Added `regex` class, to provide JavaScript- and PCRE-compatible regular expressions. - Added `lousy.pickle` library for Lua table serializing. - Added missing `remove_signals` method to Lua objects. - Added `soup.cookies_storage` to control the path to the cookies SQLite database. - Added IPC endpoint and web module APIs. - Added API for registering Lua functions accessible from JavaScript. - Added API for intercepting and modifying outgoing requests. New widget APIs: - Added `drawing_area`, `spinner`, `image`, `overlay` widgets. - Added unique IDs to `window` widgets. - Added widget `parent`, `focused` properties. - Added widget `"resize"` signal. - Added `"mouse-enter"` and `"mouse-leave"` signals to `eventbox` widget. - Added `window.ancestor()` method to retrieve the `window` widget that a given widget is contained in. - Added support for getting/setting `scrolled` widget scroll position and scrollbar settings. - Added support for displaying tooltips over widgets. - Added support for customizing individual widgets with GTK 3's CSS support. - Added `nrows()` getter to `lousy.widget.menu` widget instances. New webview APIs: - Added `webview` widget properties `editable` and `is_playing_audio`. - Added `webview.modify_load_block()` API. This allows Lua code to suspend page load operations. - Added `webview` widget `private` property. - Added `webview` widget `"crashed"` and `"go-back-forward"` signals. - Added APIs to get the web process ID of `webview` widgets and the current web extension ID. - Added APIs to save/restore the internal state of a `webview` widget. - Added `"enable-scripts"`, `"enable-styles"`, `"enable-userscripts"` signal APIs to customize module behavior for individual `webview` widgets. - Added signal for tab save decisions. #### Miscellaneous - Added `globals.page_step` to control the size of the scrolling step. - Added the `:tabdetach` command to detach a tab into a separate window. The tab is not destroyed and recreated, so any ongoing work in the tab will not be lost. - Added build options to specify more system paths, easing installation and packaging for a variety of systems. - The build system now uses the correct Lua/LuaJIT binary for build scripts. - A testing framework has been added that supports asynchronous tests. - Automatically generated documentation is now included in luakit installations. - Mode and bind information is now included in generated documentation. - The documentation index now displays which modules are loaded. - Added support for private browsing on a per-tab basis. - Added support for defining search engines as Lua functions. This allows more complex input, such as specifying multiple fields in technical search engines. - Added support for getting/setting the text alignment of `label` widgets. - Added support for getting/setting the divider position of `paned` widgets. - Added support for getting/setting the background color of `box` and `label` widgets. - Added support for getting the width and height of widgets. - Added support for setting the minimum width and height of widgets. - Added basic profile support. - Added options to control externally editing text files. - Added a crash recovery session that is automatically saved regularly. - Improved the formatting of error tracebacks. Improved tracebacks are now used for `debug.traceback()` as well as error messages. - The `xdg` module now has new properties `system_data_dirs` and `system_config_dirs`. - The `xdg` module now ensures that the paths it returns do not end in a trailing slash, regardless of how the relevant environment variables are set. ### Changed - User scripts can now run even when JavaScript has been disabled. They now use an isolated script world inaccessible from the web page. - User scripts now show an error message on failure. - The status bar and the tab bar are now hidden when luakit is fullscreen. - GLib logs are now funneled through luakit's log system. - Subsequent lines in log messages with multiple lines are now indented. - When the input bar is shown, the status bar is hidden. This is to prevent webview resizes causing performance issues for some users. - Error messages within the luakit window can now be selected with the mouse and copied. - An error message is now shown when the formfiller module fails to fill a form. - The undoclose menu is now automatically closed when there are no more menu entries. - Closed tabs are now saved in the luakit session file, so undoclose now works across sessions. - Individual tab history is now saved in the luakit session file. - The `"navigation-request"` signal now includes the reason for the navigation. - Plugin errors, load cancel errors, and frame load errors are now ignored. - Search behavior across multiple tabs has been improved. - Idle callback functions that throw errors are now removed. - Follow mode now has a new label maker: `trim()`. - `w:run_cmd()` no longer adds the given command to the mode command history. - A compile-time check for older WebKit versions has been added. - All uses of `module()` in Lua code have been removed. - Most variables have been made non-global. - A follow mode heuristic has been added for links that contain a single image element. - Luakit no longer uses a custom luakit-specific useragent string. This mproves site compatibility with sites such as Google Maps and decreases user fingerprint. - All binds now have accompanying descriptions. - Chrome pages now have consistent CSS and page style. - `introspector.lua` has been renamed to `introspector_chrome.lua` for consistency with other chrome page modules. - Added a help chrome page. - Luakit now gives a full backtrace on startup failure. - Formfiller mode now uses visual selection to add forms to the formfiller file. - Formfiller mode now uses Lua patterns instead of JavaScript regular expressions. - Widget getters and setters now verify that the widget is still valid. - The widget `"created"` signal now has the new widget as an argument, making it much more useful. - Accessing unknown widget properties now prints a warning. - A developer warning is now printed if the web extension binary is not found. - Luakit is now completely restarted if loading a configuration file fails. - Luakit no longer shows follow hints for invisible elements. - The `:lua` command now has an implicit variable `w`, the current window table. This is for convenience. - The `:lua` command can now evaluate expressions as well as execute statements. - A `resources/` directory tree has been added. - Tabs now have a themable hover color. - The default set of key bindings now includes bindings for number pad keys. - A small margin has been added to the status bar. - The formfiller now supports automatically filling forms when pages have finished loading. This is useful for automatically logging in to certain sites. - Added `export_funcs` parameter to `chrome.add()`. - Key presses that do not prefix any valid bindings are now ignored. This prevents key bindings being ignored because the input buffer has filled up with garbage. - Follow mode now allows focusing inputs by their value (the text within them) and focusing empty inputs by their placeholder text. - The `:javascript` command now has improved error handling. - `luakit://` URIs are no longer added to history. - Download objects now have the `allow_overwrite` property. - Performance of the `ssl` widget has been improved. - The downloads chrome page now displays file size statistics. - Trailing newlines are now stripped from log messages. - The `webview` widget scrolling interface has been modified for compatibility with WebKit 2. - The API for retrieving page source is now asynchronous. - Follow mode now strips the leading `mailto:` from email links, and allows the user to configure whether to ignore case in or not. - Changed the `label` widget `width` property to `textwidth`. - The `socket` widget is no longer destroyed upon plug disconnect. - `go_next_prev.lua` now uses an improved heuristic for guessing page relationship. - Other minor changes. ### Removed - All support for building with WebKit 1 has been removed. - All support for building with GTK+ 2 has been removed. - The `"cookie-changed"` signal has been removed, due to a WebKit API limitation. - The download creation API has been removed, due to a WebKit API limitation. - The global `info()` and `warn()` functions have been removed in favor of the `msg` library. - The `:viewsource` command is removed, and replaced with `:view-source`. - The `WITH_UNIQUE` build option has been removed, as `libunique` is no longer used. - The `webview` widget `show_scrollbars` property has been removed. It is replaced by the `hide_scrollbars.lua` module. - The default mouse forward/backward bindings have been removed. - Support for `webview.init_funcs` and `window.init_funcs` has been removed. There are replacement signals that serve the same purpose. ### Fixed - Changed outdated `luaL_reg` to `luaL_Reg`. - Fixed a desktop file issue preventing setting luakit as default browser for GNOME. - Fixed evaluated scripts appearing in the web inspector debugger tab. - Fixed `find_config()` assuming the system configuration is located at `/etc/xdg/`. - Fixed luakit window losing initial focus, preventing some key bindings from working. - Fixed luakit icon having incorrect permissions. - Removed use of some deprecated functions. - Fixed completion for hyphenated commands not working. - Fixed bind activation for hyphenated commands not working. - Fixed completion menu not closing. - Fixed a segmentation fault when removing a non-present signal from an object. - Fixed issues in how follow mode handled clicking on `` elements. - Fixed broken conditional in `noscript.lua`. - Fixed a bug where calling `view:load_string()` from `load_failed_cb()` would cause reload loops. - Fixed a bug where the `"link-unhover"` signal was not being emitted. - Fixed `click()` in `follow.lua` to trigger more events to work around glitches. - Fixed `go_up` breaking on `file://` URIs. - Fixed PKGBUILD issues. - Fixed contributor emails. - Fixed use-after-free of destroyed widgets. - Fixed incorrect chrome page header z-index. - Fixed a bug where the `bin` widget `child` property always returned itself. - Fixed a bug in URI `__add` operation. - Fixed long source paths appearing in Lua log output. - Fixed formfiller silently failing to add forms. - Fixed formfiller radio button and checkbox clicking behavior. - Fixed errors when handling tabs with empty titles - Change context menu 'New Window' items to 'New Tab' items - Fixed a bug where `"property::textwidth"` signal was not emitted. - Fixed a bug where invalid color codes were silently ignored. - Fixed unstable behavior when creating widgets without a specified type. - Fixed design flaws where several modules would not work without JavaScript enabled. - Fixed the bookmarks chrome page missing pagination. - Fixed a bug where user scripts would fail to add CSS on pages without a `` element. - Fixed a bug where quitting luakit through the window manager circumvented luakit's exit prevention system. - Fixed the `<` and `>` binds not wrapping around consistently. - Fixed a bug where the `"destroy"` signal would not be emitted for some widget types. - Numerous other fixes and performance improvements. ### Contributors to this release: - Aidan Holm (1585 commits) - Jenny Wong (71 commits) - Mason Larobina (17 commits) - Grégory DAVID (8 commits) - karottenreibe (7 commits) - Ygrex (6 commits) - Robbie Smith (4 commits) - Michishige Kaito (4 commits) - Ambrevar (3 commits) - Yuriy Melnyk (2 commits) - Plaque-fcc (2 commits) - loblik (2 commits) - Daniel Bolgheroni (2 commits) - windowsrefund (1 commit) - walt (1 commit) - Robbie (1 commit) - Peter Hofmann (1 commit) - Nuno Vieira (1 commit) - nmeum (1 commit) - Kane Wallmann (1 commit) - Jasper den Ouden (1 commit) - gleachkr (1 commit) - feivel (1 commit) - eshizhan (1 commit) - donlzx (1 commit) - Bartłomiej Piotrowski (1 commit) - Babken Vardanyan (1 commit) luakit-2.4.0/CONTRIBUTING.md000066400000000000000000000027721475363222200152550ustar00rootroot00000000000000# Contributing to luakit ## Submitting trivial fixes If you notice a small issue that's easy to fix, you're free to submit a pull request or patch directly, without asking or opening an issue first. This includes spelling / grammar mistakes, whitespace / formatting fixes, and any tweaks needed to get luakit to run. ## Submitting pull requests and patches If you plan to submit more involved patches, here's a checklist: 1. Unless you're contributing something you've already written, please open an issue to first discuss your plan; maintainers of luakit may have preferences about how you implement your goal or insights about needed changes. 2. Focus on a single goal at a time: - Do _not_ modify piece of code not related to your commit - Do _not_ include fixes for existing code-style problems; it's just adding noise for no gain 3. Ensure your commits are well-formed: - Make commits of logical units - Provide a meaningful commit message for each commit: the first line of the commit message should be a short description and should skip the full stop 4. Check for problems and formatting errors: - Ensure that your work does not cause any tests to fail - Add new tests if appropriate - Add an entry to the changelog if appropriate - Check for unnecessary whitespace with "git diff --check" before committing - Do not check in commented out code or unneeded files GitHub pull requests should be made against [https://github.com/luakit/luakit/](https://github.com/luakit/luakit/). luakit-2.4.0/COPYING.GPLv3000066400000000000000000001044671475363222200147550ustar00rootroot00000000000000GNU 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 . luakit-2.4.0/MIGRATE.md000066400000000000000000000016451475363222200144340ustar00rootroot00000000000000## Migration information Luakit has undergone some major refactorings in the last years. If you're coming from a luakit version older than July 2017, you need to redo your configuration. The following files used to be configuration files, but are not anymore: * binds.lua -- is now a built-in module providing the default bindings. Bindings should be changed with the `modes` APIs. * modes.lua -- is now a built-in module providing built-in modes, as well as providing APIs to manage bindings within those modes. * window.lua -- is now a built-in module. * webview.lua -- is now a built-in module. * webview_wm.lua -- is now a built-in module. * globals.lua -- global settings have been moved to other modules. These files will be silently ignored on startup so as to prevent errors; users wishing to override the built-in modules should change `package.path`. luakit-2.4.0/Makefile000066400000000000000000000122441475363222200144570ustar00rootroot00000000000000# Include makefile config include config.mk # Token lib generation TLIST = common/tokenize.list THEAD = common/tokenize.h TSRC = common/tokenize.c SRCS = $(filter-out $(TSRC),$(wildcard *.c) $(wildcard common/*.c) $(wildcard common/clib/*.c) $(wildcard clib/*.c) $(wildcard clib/soup/*.c) $(wildcard widgets/*.c)) $(TSRC) HEADS = $(wildcard *.h) $(wildcard common/*.h) $(wildcard common/clib/*.h) $(wildcard widgets/*.h) $(wildcard clib/*.h) $(wildcard clib/soup/*.h) $(THEAD) buildopts.h OBJS = $(foreach obj,$(SRCS:.c=.o),$(obj)) EXT_SRCS = $(filter-out $(TSRC),$(wildcard extension/*.c) $(wildcard extension/clib/*.c) $(wildcard common/*.c)) $(wildcard common/clib/*.c) $(TSRC) EXT_OBJS = $(foreach obj,$(EXT_SRCS:.c=.o),$(obj)) # List of sources used to generate Lua API documentation # Must be kept in sync with doc/docgen.ld DOC_SRCS = $(filter-out lib/markdown.lua lib/lousy/init.lua,$(shell for d in doc/luadoc lib common/clib; do find $$d -type f; done)) tests/lib.lua all: options newline luakit luakit.1 luakit.so apidoc options: @echo luakit build options: @echo "CC = $(CC)" @echo "LUA_PKG_NAME = $(LUA_PKG_NAME)" @echo "LUA_BIN_NAME = $(LUA_BIN_NAME)" @echo "CFLAGS = $(CFLAGS)" @echo "CPPFLAGS = $(CPPFLAGS)" @echo "LDFLAGS = $(LDFLAGS)" @echo "MANPREFIX = $(MANPREFIX)" @echo "DOCDIR = $(DOCDIR)" @echo "XDGPREFIX = $(XDGPREFIX)" @echo "PIXMAPDIR = $(PIXMAPDIR)" @echo "APPDIR = $(APPDIR)" @echo "LIBDIR = $(LIBDIR)" @echo @echo build targets: @echo "SRCS = $(SRCS)" @echo "HEADS = $(HEADS)" @echo "OBJS = $(OBJS)" @echo "EXT_SRCS = $(EXT_SRCS)" @echo "EXT_OBJS = $(EXT_OBJS)" $(THEAD) $(TSRC): $(TLIST) $(LUA_BIN_NAME) ./build-utils/gentokens.lua $(TLIST) $@ buildopts.h: buildopts.h.in sed -e 's#LUAKIT_INSTALL_PATH .*#LUAKIT_INSTALL_PATH "$(PREFIX)/share/luakit"#' \ -e 's#LUAKIT_CONFIG_PATH .*#LUAKIT_CONFIG_PATH "$(XDGPREFIX)"#' \ -e 's#LUAKIT_DOC_PATH .*#LUAKIT_DOC_PATH "$(DOCDIR)"#' \ -e 's#LUAKIT_MAN_PATH .*#LUAKIT_MAN_PATH "$(MANPREFIX)"#' \ -e 's#LUAKIT_PIXMAP_PATH .*#LUAKIT_PIXMAP_PATH "$(PIXMAPDIR)"#' \ -e 's#LUAKIT_APP_PATH .*#LUAKIT_APP_PATH "$(APPDIR)"#' \ -e 's#LUAKIT_LIB_PATH .*#LUAKIT_LIB_PATH "$(LIBDIR)"#' \ buildopts.h.in > buildopts.h $(filter-out $(EXT_OBJS),$(OBJS)) $(EXT_OBJS): $(HEADS) config.mk $(filter-out $(EXT_OBJS),$(OBJS)) : %.o : %.c @echo $(CC) -c $< -o $@ @$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ $(EXT_OBJS) : %.o : %.c @echo $(CC) -c $< -o $@ @$(CC) -c $(CFLAGS) -DLUAKIT_WEB_EXTENSION -fPIC $(CPPFLAGS) $< -o $@ widgets/webview.o: $(wildcard widgets/webview/*.c) luakit: $(OBJS) @echo $(CC) -o $@ $(OBJS) @$(CC) -o $@ $(OBJS) $(LDFLAGS) luakit.so: $(EXT_OBJS) @echo $(CC) -o $@ $(EXT_OBJS) @$(CC) -o $@ $(EXT_OBJS) -shared $(LDFLAGS) luakit.1: luakit.1.in @sed "s|LUAKITVERSION|$(VERSION)|" $< > $@ doc/apidocs/index.html: $(DOC_SRCS) $(wildcard build-utils/docgen/*) rm -rf doc/apidocs mkdir doc/apidocs $(LUA_BIN_NAME) ./build-utils/docgen/makedoc.lua apidoc: doc/apidocs/index.html doc: buildopts.h $(THEAD) $(TSRC) doxygen -s doc/luakit.doxygen clean: rm -rf doc/apidocs doc/html luakit $(OBJS) $(EXT_OBJS) $(TSRC) $(THEAD) buildopts.h luakit.1 luakit.so install: all install -d $(DESTDIR)$(DOCDIR)/classes install -d $(DESTDIR)$(DOCDIR)/modules install -d $(DESTDIR)$(DOCDIR)/pages install -m644 README.md AUTHORS COPYING.GPLv3 $(DESTDIR)$(DOCDIR) install -m644 doc/apidocs/classes/* $(DESTDIR)$(DOCDIR)/classes install -m644 doc/apidocs/modules/* $(DESTDIR)$(DOCDIR)/modules install -m644 doc/apidocs/pages/* $(DESTDIR)$(DOCDIR)/pages install -m644 doc/apidocs/*.html $(DESTDIR)$(DOCDIR) install -d $(DESTDIR)$(PREFIX)/share/luakit/lib/lousy/widget install -m644 lib/*.* $(DESTDIR)$(PREFIX)/share/luakit/lib install -m644 lib/lousy/*.* $(DESTDIR)$(PREFIX)/share/luakit/lib/lousy install -m644 lib/lousy/widget/*.* $(DESTDIR)$(PREFIX)/share/luakit/lib/lousy/widget install -d $(DESTDIR)$(LIBDIR) install -m644 luakit.so $(DESTDIR)$(LIBDIR)/luakit.so install -d $(DESTDIR)$(PREFIX)/bin install luakit $(DESTDIR)$(PREFIX)/bin/luakit install -d $(DESTDIR)$(XDGPREFIX)/luakit/ install -m644 config/*.lua $(DESTDIR)$(XDGPREFIX)/luakit/ install -d $(DESTDIR)$(PIXMAPDIR) install -m644 extras/luakit.png $(DESTDIR)$(PIXMAPDIR) install -m644 extras/luakit.svg $(DESTDIR)$(PIXMAPDIR) install -d $(DESTDIR)$(APPDIR) install -m644 extras/luakit.desktop $(DESTDIR)$(APPDIR) install -d $(DESTDIR)$(MANPREFIX)/man1/ install -m644 luakit.1 $(DESTDIR)$(MANPREFIX)/man1/ install -d $(DESTDIR)$(PREFIX)/share/luakit/resources/icons for i in resources/icons/*; do install -m644 "$$i" "$(DESTDIR)$(PREFIX)/share/luakit/resources/icons"; done uninstall: rm -rf $(DESTDIR)$(PREFIX)/bin/luakit $(DESTDIR)$(PREFIX)/share/luakit $(DESTDIR)$(PREFIX)/lib/luakit rm -rf $(DESTDIR)$(MANPREFIX)/man1/luakit.1 $(DESTDIR)$(XDGPREFIX)/luakit rm -rf $(DESTDIR)$(APPDIR)/luakit.desktop $(DESTDIR)$(PIXMAPDIR)/luakit.png tests/util.so: tests/util.c Makefile $(CC) -fPIC $(CFLAGS) $(CPPFLAGS) -shared $< $(LDFLAGS) -o $@ run-tests: luakit luakit.so tests/util.so @$(LUA_BIN_NAME) tests/run_test.lua newline: options;@echo .PHONY: all clean options install newline apidoc doc default luakit-2.4.0/README.md000066400000000000000000000131561475363222200143010ustar00rootroot00000000000000# Luakit luakit is a fast, light and simple to use micro-browser framework extensible by Lua using the WebKit web content engine and the GTK+ toolkit. ### Don't Panic! You don't have to be a developer to use luakit on a daily basis. If you are familiar with quitebrowser, vim-browser, jumanji or extensions like Vimium or cVim etc, you will find luakit behaves similarly out of the box. ## Requirements * GTK+ 3 * Lua 5.1 or LuaJIT 2 * lfs (lua file system) * webkit2gtk * sqlite3 * gstreamer (for video playback) * lua-socket (for gopher support) ## Installing Luakit is available on most Linux Distributions and BSD system via their package managers. A few examples below: * Debian/Ubuntu: apt-get install luakit * Gentoo: emerge luakit * Guix: guix install luakit * Arch: pacman -S luakit * FreeBSD: pkg install luakit * OpenBSD: pkg\_add luakit * Void Linux: xbps-install luakit Packaging status: [![Packaging Status](https://repology.org/badge/vertical-allrepos/luakit.svg?header=)](https://repology.org/project/luakit/versions) ## Installing from source Make sure you system fulfills the requirements listed above, then install luakit with the following commands: $ git clone https://github.com/luakit/luakit.git $ cd luakit $ make $ sudo make install Uninstall with: $ sudo make uninstall Note: If you are on BSD, you might need to use `gmake`. ## Use Luakit Just run: $ luakit [URI..] Or to see the full list of luakit launch options run: $ luakit -h Luakit works with vim-style bindings. To find out more, type `:help` within luakit. ## Configuration Luakit configuration files are written in `lua`. This means you can program within the config files, which make the configuration options endless. There are three ways to customize luakit. **1. within luakit** After starting luakit, type `:settings`. This page shows you webkit engine related settings. **2. userconf.lua** Create a file called `$HOME/.config/luakit/userconf.lua`. Then add your configuration there. Configuration in this file supersedes configuration set in `:settings` **3. copy rc.lua** The most powerful customization is to copy `rc.lua` from `/etc/xdg/luakit/rc.lua` to `$HOME/.config/luakit/rc.lua` When this file is found, `/etc/xdg/luakit/rc.lua` is ignored. Be informed that when luakit is updated, you may need to adapt changes from `/etc/xdg/luakit/rc.lua` to your own copy. ## Colors and fonts Copy the `/etc/xdg/luakit/theme.lua` to `$HOME/.config/luakit/theme.lua`. You can change fonts and colors there. ## Development Information This section contains information about the compile and testing process. Luakit honors the PREFIX variable. The default is `/usr/local`. $ make PREFIX=/usr $ sudo make PREFIX=/usr install Notes: - You also have to set the PREFIX when uninstalling. - If you want to change PREFIX after a previous build, you need to `make clean` first. Luakit uses `luajit` by default, to use `lua` you can turn off luajit with: $ make USE_LUAJIT=0 To build with local paths (interesting for package maintainer and contributers). You may wish to build luakit with: $ make DEVELOPMENT_PATHS=1 This lets you start luakit from the build directory, using the config and libraries within the same. Take a look at `config.mk` for more options. If you made changes and want to know if luakit is still working properly, you can execute the test suite with: $ make test ## Debugging Luakit **Enable debug output** Debug output can be turned on by starting luakit with the `--log` option. $ luakit --log=verbose # or: -v $ luakit --log=all=debug # or: --log=debug $ luakit --log=lua/adblock=debug # turn on debug on for the adblock module. Console output an also be seen within luakit using `:log` when the [log_chrome](https://luakit.github.io/docs/modules/log_chrome.html) module is loaded (default). Note that debug log level may lead to a race condition when the update of the log page leads to debug output, which causes an update of the :log page. **Execute lua within Luakit** If you want to examine the lua state at runtime, you can do so by running lua script within luakit. This will print "Hello World!" on the console. :lua print("Hello World!"); w:notify() can be used to show the output in the input/status bar. This example shows the configuration directory in the input/status bar. :lua w:notify(luakit.config_dir) Print the lua package search paths: :lua w:notify(package.path:gsub(";","\n")) To extend the lua debugging capabilities, you can copy "inspect.lua" from [here](https://github.com/kikito/inspect.lua) into the luakit configuration directory. Now you can inspect complex data types like tables: :lua local inspect = require "inspect"; w:notify(inspect(webview)) ## Tips and Fixes: **Video playback** If you're having issues with video playback, this is often related to buggy graphic drivers. It often helps to set LIBGL\_DRI3\_DISABLE before starting luakit: $ export LIBGL_DRI3_DISABLE=1 **HiDPI Monitor Configuration** If you have a HiDPI monitor (> 1920x1080) and find that web pages are too small, you can change the `webview.zoom_level` on the settings page (luakit://settings/) to 150 or 200 as per your taste. ## Reporting Bugs Please note that most rendering related issues come from the used webkit engine and can not be fixed by luakit. If you think your issue is luakit related, please use the bug tracker at: https://github.com/luakit/luakit/issues Coming from a very old luakit version? Look at the [MIGRATION](MIGRATE.md) document. ## IRC Join us in `#luakit` on the `irc.oftc.net` network. luakit-2.4.0/build-utils/000077500000000000000000000000001475363222200152515ustar00rootroot00000000000000luakit-2.4.0/build-utils/docgen/000077500000000000000000000000001475363222200165105ustar00rootroot00000000000000luakit-2.4.0/build-utils/docgen/gen.lua000077500000000000000000000425211475363222200177730ustar00rootroot00000000000000local lfs = require "lfs" local lousy = { util = require "lib.lousy.util" } local markdown = require "lib.markdown" local index = {} local text_macros = { available = function (arg) return ({ ui = '
This module is only available from the UI process Lua state.
', web = '
This module is only available from web process Lua states.
', })[arg] or error("available macro: expected ui, web, or both as argument") end, alert = function (str) return '
' .. str .. '
' end, } local format_text = function (text) local ret = text:gsub("DOCMACRO%((%w+):?([^%)]-)%)", function (macro, args) if not text_macros[macro] then error("Bad macro '" .. macro .. "'") end return (text_macros[macro])(args) end) ret = ret:gsub('@ref{([^}]+)}', function (ref) local r, reftext = ref:match("^(.*)%|(.*)$") ref, reftext = r or ref, reftext or ref:gsub(".*/", "") assert(index[ref] ~= false, "ambiguous ref '" .. ref .. "', prefix with doc/") assert(index[ref], "invalid ref '" .. ref .. "'") local doc, item = index[ref].doc, index[ref].item local group, name = doc.module and "modules" or "classes", doc.name local fragment = item and ("#%s-%s"):format(item.type, item.name) or "" return ('`%s`'):format(group, name, fragment, reftext) end) -- Format with markdown ret = markdown(ret) ret = ret:gsub("
(.-)
", function (code) -- Add syntax highlighting if lxsh is installed local ok, lxsh = pcall(require, "lxsh") if ok then code = lxsh.highlighters.lua( lousy.util.unescape(code), -- Fix < and > being escaped inside code -_- fail { formatter = lxsh.formatters.html, external = true } ) else code = "
" .. code .. "
" end return code end) return ret end local shift_hdr = function (text, n) return ("\n"..text):gsub("\n#", "\n" .. string.rep("#", n+1)):sub(2) end local generate_typestr_html = function (typestr) if not typestr then return "any type" end local alts = {} for sub in typestr:gmatch("[^%|]+") do table.insert(alts, "" .. sub .. "") end return table.concat(alts, " or ") end local html_unwrap_first_p = function (html) return html:gsub("", "", 2) -- Remove first two open/closing p tags end local generate_deprecated_html = function (item) if not item.deprecated then return "" end return text_macros.alert("Deprecated: " .. html_unwrap_first_p(format_text(item.deprecated))) end local generate_function_param_html = function (param) local html_template = [==[
  • {name}
    {typestr}
    {optional}
    {default}
    {desc}
  • ]==] local html = string.gsub(html_template, "{(%w+)}", { name = param.name, typestr = "Type: " .. generate_typestr_html(param.typestr), desc = html_unwrap_first_p(format_text(shift_hdr(param.desc, 3))), optional = param.optional and "Optional" or "", default = param.default and "Default: " .. html_unwrap_first_p(format_text(param.default)) or "", }) return html end local generate_function_return_html = function (ret) local html_template = [==[
  • {typestr}
    {desc}
  • ]==] local html = string.gsub(html_template, "{(%w+)}", { typestr = generate_typestr_html(ret.typestr), desc = html_unwrap_first_p(format_text(shift_hdr(ret.desc, 3))), }) return html end local generate_function_params_html = function (params) if #params == 0 then return "" end local param_html = {"

    Parameters

    "} table.insert(param_html, "
      ") for _, param in ipairs(params) do table.insert(param_html, generate_function_param_html(param)) end table.insert(param_html, "
    ") return table.concat(param_html, "\n") end local generate_function_returns_html = function (returns) if #returns == 0 then return "" end local return_html = {"

    Return Values

    "} table.insert(return_html, "
      ") for _, ret in ipairs(returns) do table.insert(return_html, generate_function_return_html(ret)) end table.insert(return_html, "
    ") return table.concat(return_html, "\n") end local generate_function_body_html = function (func) local html_template = [==[
    {deprecated} {desc} {params} {returns}
    ]==] local html = string.gsub(html_template, "{([%w_]+)}", { deprecated = generate_deprecated_html(func), desc = format_text(shift_hdr(func.desc, 3)), params = generate_function_params_html(func.params), returns = generate_function_returns_html(func.returns), }) return html end local generate_function_html = function (func, prefix) if func.name == "__call" then prefix = prefix:gsub(".$", "") func.name = "" end local html_template = [==[ ]==] local param_names = {} for _, param in ipairs(func.params) do table.insert(param_names, (param.opt and "["..param.name.."]" or param.name)) end local html = string.gsub(html_template, "{([%w_]+)}", { prefix = prefix, type = func.type, name = func.name, param_names = table.concat(param_names, ", "), body = generate_function_body_html(func), }) return html end local generate_signal_html = function (func) local html_template = [==[

    "{name}"

    {body}
    ]==] local html = string.gsub(html_template, "{([%w_]+)}", { name = func.name, body = generate_function_body_html(func), }) return html end local generate_attribution_html = function (doc) if #doc.copyright == 0 then return "" end local html = { "

    Attribution

    " } table.insert(html, "
    ") table.insert(html, "

    Copyright

    ") table.insert(html, "
    ") return table.concat(html, "\n") end local generate_property_html = function (prop, prefix) local html_template = [==[

    {prefix}{name}

    {deprecated}
    {typestr}
    {default}
    {readwrite}
    {desc}
    ]==] assert(prop.readonly or prop.readwrite, "Property " .. prop.name .. " missing RO/RW annotation") local html = string.gsub(html_template, "{([%w_]+)}", { prefix = prefix, name = prop.name, deprecated = generate_deprecated_html(prop), typestr = "Type: " .. generate_typestr_html(prop.typestr), desc = html_unwrap_first_p(format_text(shift_hdr(prop.desc, 3))), default = prop.default and "Default: " .. html_unwrap_first_p(format_text(prop.default)) or "", readwrite = (prop.readonly and "Read-only") or (prop.readwrite and "Read-write") }) return html end local generate_list_html = function (heading, list, item_func, ...) if not list or #list == 0 then return "" end local html = "

    " .. heading .. "

    \n" for _, item in ipairs(list) do html = html .. "\n" .. item_func(item, ...) end return html end local generate_doc_html = function (doc) local html_template = [==[

    {type} {title}

    {tagline} {desc} {functions} {attribution} ]==] -- Determine function name prefix local prefix = doc.prefix or doc.name local func_prefix, method_prefix = prefix .. ".", prefix .. ":" local fhtml = "" .. "" .. generate_list_html("Functions", doc.functions, generate_function_html, func_prefix) .. generate_list_html("Methods", doc.methods, generate_function_html, method_prefix) .. generate_list_html("Properties", doc.properties, generate_property_html, func_prefix) .. generate_list_html("Signals", doc.signals, generate_signal_html) .. generate_list_html("Callback Types", doc.callbacks, generate_function_html, "") local html = string.gsub(html_template, "{(%w+)}", { type = doc.module and "Module" or "Class", title = doc.name, tagline = doc.tagline:gsub("%.$",""), desc = format_text(shift_hdr(doc.desc, 1)), functions = fhtml, attribution = generate_attribution_html(doc) }) return html end local generate_sidebar_html = function (docs, current_doc) local html = "" for _, name in ipairs{"pages", "modules", "classes"} do local section = assert(docs[name], "Missing " .. name .. " section") html = html .. ("

    %s

    \n"):format(name:gsub("^%l", string.upper)) html = html .. "
      \n" for _, doc in ipairs(section) do if doc == current_doc then html = html .. ('
    • %s
    • \n'):format(doc.name) else html = html .. ('
    • %s
    • \n'):format(name, doc.filename or doc.name, doc.name) end end html = html .. "
    \n" end return html end local generate_pagination_html = function (pagination) return ("
    {prv}{nxt}
    "):gsub("{(%w+)}", { prv = pagination.prv and ("") or "", nxt = pagination.nxt and ("") or "", }) end local generate_module_html = function (doc, style, docs, pagination) local html_template = [==[ {title} - Luakit

    Luakit Documentation   /   {section}   /   {title}

    {body}
    {pagination}
    ]==] local html = string.gsub(html_template, "{(%w+)}", { title = doc.name, style = style, section = doc.module and "Modules" or doc.class and "Classes" or "Pages", body = generate_doc_html(doc), sidebar = generate_sidebar_html(docs, doc), pagination = generate_pagination_html(pagination), }) return html end local generate_page_html = function (doc, style, docs, pagination) local html_template = [==[ {title} - Luakit

    Luakit Documentation   /   Pages   /   {title}

    {body}
    {pagination}
    ]==] local html = string.gsub(html_template, "{(%w+)}", { title = doc.name, style = style, body = format_text(assert(doc.text, "No page text")), sidebar = generate_sidebar_html(docs, doc), pagination = generate_pagination_html(pagination), }) return html end local generate_index_html = function (style, docs) local html_template = [==[ Luakit Documentation

    Luakit Documentation   /   Index

    Pages

    {pages}

    Modules

    {modules}

    Classes

    {classes}
    ]==] local lists = {} for _, name in ipairs{"pages", "modules", "classes"} do local html = "" local section = assert(docs[name], "Missing " .. name .. " section") html = html .. "
      \n" for _, doc in ipairs(section) do html = html .. ('
    • %s
    • \n'):format(name, doc.filename or doc.name, doc.name) end for _=1,(3-#section%3)%3 do html = html .. '
    • \n' end lists[name] = html .. "
    \n" end local html = string.gsub(html_template, "{(%w+)}", { style = style, pages = lists.pages, modules = lists.modules, classes = lists.classes, }) return html end local generate_documentation = function (docs, out_dir) -- Utility functions local mkdir = function (path) if lfs.attributes(path, "mode") == "directory" then return end assert(lfs.mkdir(path)) end local write_file = function (path, html) local f = assert(io.open(path, "w")) f:write(html) f:close() end -- Load doc stylesheet local f = assert(io.open(docs.stylesheet, "r"), "no stylesheet found") local style = f:read("*a") f:close() -- Create output directory assert(out_dir, "no output directory specified") out_dir = out_dir:match("/$") and out_dir or out_dir .. "/" mkdir(out_dir) -- Build symbol index do local add_index_obj = function (doc, item) local name = item and (item.type == "signal" and '"'..item.name..'"' or item.name) local short_name = name or doc.name local long_name = doc.name .. "/" .. (name or "") -- Always allow using the long name assert(not index[long_name], "Name conflict for " .. long_name) index[long_name] = { doc = doc, item = item } -- Allow using the short name, but blacklist it on collision if index[short_name] == nil then index[short_name] = { doc = doc, item = item } else index[short_name] = false end end for _, doc in ipairs(lousy.util.table.join(docs.modules, docs.classes)) do add_index_obj(doc) for _, t in ipairs {"functions", "methods", "properties", "signals", "callbacks"} do for _, item in ipairs(doc[t] or {}) do add_index_obj(doc, item) end end end end local pages = {} mkdir(out_dir .. "pages") for _, page in ipairs(docs.pages) do pages[#pages+1] = { doc = page, gen = generate_page_html, path = "pages/" .. page.filename .. ".html", } end for _, section_name in ipairs{"modules", "classes"} do mkdir(out_dir .. section_name) local section_docs = docs[section_name] for _, doc in ipairs(section_docs) do pages[#pages+1] = { doc = doc, gen = generate_module_html, path = section_name .. "/" .. doc.name .. ".html", } end end for i, page in ipairs(pages) do print("Generating '" .. page.path .. "'...") local pagination = { nxt = (pages[i+1] or {}).path, prv = (pages[i-1] or {}).path, } write_file(out_dir .. page.path, page.gen(page.doc, style, docs, pagination)) end -- Generate index local path = out_dir .. "index.html" print("Generating '" .. path .. "'...") write_file(path, generate_index_html(style, docs)) end return { generate_documentation = generate_documentation, } -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/docgen/makedoc.lua000077500000000000000000000044121475363222200206220ustar00rootroot00000000000000#!/usr/bin/env luajit local docgen_dir = "build-utils/" local config_path = "doc/docgen.ld" package.path = package.path .. ";" .. docgen_dir .. "?.lua" local parse = require "docgen.parse" local gen = require "docgen.gen" local find_files = require("find_files").find_files -- Load helpers local load_file = function (path) local f = assert(io.open(path, "r")) local contents = f:read("*all") f:close() return contents end local function load_config_ld (path) local code = load_file(path) local conf = assert(loadstring(code)) -- execute in sandbox local env = {} setfenv(conf, env) assert(pcall(conf)) return env end local parse_module_files = function (files) local docs = {} for _, filename in ipairs(files) do print("Parsing '" .. filename .. "'...") local parsed = parse.parse_file(filename) docs[#docs+1] = parsed end table.sort(docs, function(a, b) return a.name < b.name end) return docs end local parse_pages_files = function (files) local pages = {} for _, filename in ipairs(files) do print("Reading '" .. filename .. "'...") local text = load_file(filename) local name, idx = text:match("^@name (.-)\n()") filename = filename:gsub(".*/([%w-]+).md", "%1") assert(filename:gmatch("[%w-]+.md"), "Bad page filename " .. filename) pages[#pages+1] = { name = assert(name, "no @name line found"), filename = filename, text = text:sub(idx), } end table.sort(pages, function(a, b) return a.filename < b.filename end) return pages end --- local config = load_config_ld(config_path) local files = find_files(config.file, "%.lua$", config.file.exclude) local docs = parse_module_files(files) local pages = parse_pages_files(find_files(config.pages, "%.md$")) -- Split into modules and classes local module_docs = {} local class_docs = {} for _, doc in ipairs(docs) do if doc.module then table.insert(module_docs, doc) end if doc.class then table.insert(class_docs, doc) end end docs = { pages = pages, modules = module_docs, classes = class_docs, stylesheet = docgen_dir .. "docgen/style.css", } gen.generate_documentation(docs, config.dir) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/docgen/parse.lua000077500000000000000000000230271475363222200203340ustar00rootroot00000000000000#!/usr/bin/env luajit local function_set local check_unique = function (name, t) local prefix = t == "signal" and "s" or "-" assert(not function_set[name .. prefix], "Duplicate name " .. name) function_set[name .. prefix] = true end local advance = function (block) local ret = table.remove(block, 1) block.i = block.i + 1 return ret end local peel_off_text_block = function (block) local lines = {} while block[1] and not (block[1]:match("^@%w+") and not block[1]:match("^@ref{")) do table.insert(lines, advance(block)) end return table.concat(lines, "\n") end local peel_off_type_string = function (line) local start, typestr, rem = line:match("^%@t(%w+ )(%S+)%s?(.*)$") if start and typestr then return "@" .. start .. rem, typestr else return line, nil end end local parse_error = function (block, ...) assert(block[1]) local hdr = ("Error at line %s: "):format(block.start + block.i) error(hdr .. string.format(...)) end local expect = function (block, pat, err, should_advance) if not block[1] then error("Early EOF: " .. err) end local rets = {} for _, p in ipairs(type(pat) == "string" and {pat} or pat) do rets = #rets > 0 and rets or {block[1]:match(p)} end if #rets == 0 then parse_error(block, err) end if should_advance ~= false then advance(block) end return unpack(rets) end local parse_doc_header_line = function (doc, line) local key, val = line:match("^%@([%w_]+) (.+)$") if not (key and val) then return end if key == "module" or key == "class" or key == "submodule" then doc.name = val doc[key] = true elseif not doc[key] then doc[key] = val elseif type(doc[key]) == "table" then table.insert(doc[key], val) else return "Duplicate header line @"..key end end local parse_optional_at_type_line = function (item, block) if not block[1] or not block[1]:find("^@type") then return end assert(not item.typestr, "Type already set") item.typestr = expect(block, "^%@type (%S+)$", "Bad @type line") end local parse_at_read_write_line = function (item, block) local line = advance(block):sub(2) item[line] = true end local parse_at_default_line = function (item, block) if not (block[1] and block[1]:find("^@default")) then return end local default = expect(block, "^%@default (.+)", "Missing @default") item.default = (default .. peel_off_text_block(block)):gsub("^%s+",""):gsub("[%s%.]+$","") end local parse_at_deprecated_line = function (item, block) if not block[1]:find("^@deprecated") then return end local deprecated = expect(block, "^%@deprecated (.+)", "Missing @deprecated") item.deprecated = (deprecated .. peel_off_text_block(block)):gsub("^%s+",""):gsub("[%s%.]+$","") end local function parse_first_file_block(doc, block) doc.author = {} doc.copyright = {} doc.tagline = advance(block) -- Separate block into markdown and @-lines local atlines = {} for i, line in ipairs(block) do if line:match("^@%w+") and not line:match("^@ref{") then table.insert(atlines, line) block[i] = "" end end -- Parse atlines for _, line in ipairs(atlines) do local err = parse_doc_header_line(doc, line) if err then error(err) end end doc.desc = table.concat(block, "\n") assert(doc.module or doc.submodule or doc.class, "Missing @module / @submodule / @class") -- assert(doc.author[1], "Missing @author") -- assert(doc.copyright[1], "Missing @copyright") end local function parse_at_function_line(item, block, func_type) if item.type then parse_error(block, "@%s line inside %s block", func_type, item.type) end item.type = func_type -- "function" / "method" / "signal" / "callback" item.params = item.params or {} item.returns = item.returns or {} item.name = block[1]:match("^%@function (%S+)$") or block[1]:match("^%@method (%S+)$") or block[1]:match("^%@signal (%S+)$") or block[1]:match("^%@callback (%S+)$") or parse_error(block, "Missing %s name", func_type) check_unique(item.name, item.type) assert((not not item.name:match("_cb$")) == (item.type == "callback"), "Only/all callback names end in _cb") advance(block) end local function parse_at_param_line(item, block) local param, line = {}, advance(block) -- Handle [opt] if line:match("^%@t?param%[opt%] ") then line = line:gsub("^%@(t?param)%[opt%] ", "@%1 ") param.optional = true end local typestr, name, prefix line, typestr = peel_off_type_string(line) name, prefix = line:match("^%@param (%S+)%s?(.*)$") param.typestr = typestr param.name = name param.desc = (prefix or "") .. "\n" .. peel_off_text_block(block) parse_optional_at_type_line(param, block) if param.optional then parse_at_default_line(param, block) end item.params = item.params or {} table.insert(item.params, param) end local function parse_at_property_line(item, block) assert(not item.type, "Misplaced @property line") item.type = "property" item.name = advance(block):match("^%@property (%S+)$") check_unique(item.name, item.type) end local function parse_at_return_line(item, block) local ret, line = {}, advance(block) -- Handle [n] ret.group = tonumber(line:match("^%@t?return%[(%d+)%] ")) if ret.group then line = line:gsub("^%@(t?return)%[%d+%] ", "@%1 ") end line, ret.typestr = peel_off_type_string(line) local prefix = line:match("^%@return (.*)$") ret.desc = (prefix or "") .. "\n" .. peel_off_text_block(block) item.returns = item.returns or {} table.insert(item.returns, ret) end local function parse_file_block_part(item, block) local at = block[1]:match("^@(%w+)") if at == "function" or at == "method" or at == "signal" or at == "callback" then parse_at_function_line(item, block, at) elseif at == "param" or at == "tparam" then parse_at_param_line(item, block) elseif at == "return" or at == "treturn" then parse_at_return_line(item, block) elseif at == "property" then parse_at_property_line(item, block) elseif at == "readonly" or at == "readwrite" then parse_at_read_write_line(item, block) elseif at == "default" then parse_at_default_line(item, block) elseif at == "type" then parse_optional_at_type_line(item, block) elseif at == "deprecated" then parse_at_deprecated_line(item, block) elseif at then parse_error(block, "Unexpected at-line '" .. at .. "'") else item.desc = peel_off_text_block(block) end end local function parse_file_block(block) local item = {} while block[1] do parse_file_block_part(item, block) end return item end local function check_parsed_file_block(item) -- TODO: handle missing name! assert(item.desc, item.type .. " " .. item.name .. " missing description") end local function parse_file_blocks (blocks) local doc = {} parse_first_file_block(doc, blocks[1]) for i=2,#blocks do local item = parse_file_block(blocks[i]) check_parsed_file_block(item) local groupname = item.type == "property" and "properties" or item.type .. "s" doc[groupname] = doc[groupname] or {} table.insert(doc[groupname], item) end return doc end local function read_file_comment_blocks(path) local blocks, block = {}, nil local f = io.open(path, "r") local line_i = 0 for line in f:lines() do line_i = line_i + 1 if (not block and line:match("^%-%-%-")) or (line_i == 1 and line:match("^%-%-")) then block = { start = line_i, i = 0 } table.insert(blocks, block) end if block then table.insert(block, line) if not line:match("^%-%-") then block = nil end end end f:close() return blocks end local function convert_comment_block_last_line(block) local line = block[#block] if line == "" then return nil end local name = line:match("_M%.([%w%d_.]+)") or line:match("^function ([%w%d_.]+)") local is_function = line:match("^function ") or line:match("= ?function") local type = is_function and "function" or "property" if name then return string.format("-- @%s %s", type, name) end parse_error(block, "Couldn't parse name from line") end local function convert_file_comment_blocks(blocks) for i=#blocks,1,-1 do local block = blocks[i] local line = block[#block] if not line:match("^%-%-") and i == 1 then block[#block] = nil elseif line:match("^local ") then table.remove(blocks, i) elseif not line:match("^%-%-") then block[#block] = convert_comment_block_last_line(block) end end -- Strip comment dashes and trailing spaces, drop @local blocks for i=#blocks,1,-1 do local block = blocks[i] for j, line in ipairs(block) do block[j] = line:gsub("^%-%-%-? ?", ""):gsub("%s+$","") if block[j] == "@local" then table.remove(blocks, i) break end if block[j] == "@usage" then block[j] = "### Usage" end end end end return { parse_file = function (path) function_set = {} local blocks = read_file_comment_blocks(path) if #blocks == 0 then error("no comment blocks found in file") end convert_file_comment_blocks(blocks) return parse_file_blocks(blocks) end, } -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/docgen/process.lua000066400000000000000000000033241475363222200206730ustar00rootroot00000000000000assert(luakit, "This file must be run as a luakit config file") local find_files = require "build-utils.find_files" -- Restrict Lua search path to just the build dir for luakit-related files -- This is done by removing anything that looks like a luakit include dir local paths = { "./config/?.lua", "./lib/?.lua", "./lib/?/init.lua" } package.path:gsub("([^;]+)", function(path) if not path:find("/luakit/", 1, true) then table.insert(paths, path) end end) package.path = table.concat(paths, ";") require "window" require "webview" require "binds" local modes = require "modes" local lousy = require "lousy" local clear_all_mode_bindings = function () local mode_list = modes.get_modes() for mode_name in pairs(mode_list) do local mode = modes.get_mode(mode_name) mode.binds = nil end end local get_mode_bindings_for_module = function (mod) require(mod) clear_all_mode_bindings() package.loaded[mod] = nil require(mod) local ret = {} local mode_list = modes.get_modes() for mode_name in pairs(mode_list) do local mode = modes.get_mode(mode_name) ret[mode_name] = {} for _, b in pairs(mode.binds or {}) do table.insert(ret[mode_name], { name = lousy.bind.bind_to_string(b) or "???", desc = b.desc, }) end end return ret end local files = find_files.find_files({"lib/"}, ".+%.lua$", {"_wm%.lua$"}) local output = {} for _, file in ipairs(files) do local pkg = file:gsub("^%a+/", ""):gsub("%.lua$", ""):gsub("/", ".") output[pkg] = get_mode_bindings_for_module(pkg) end io.stdout:write(lousy.pickle.pickle(output)) luakit.quit() -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/docgen/style.css000066400000000000000000000126731475363222200203730ustar00rootroot00000000000000* { box-sizing: border-box; } body { margin: 0; font-family: "Arial", sans-serif; color: #333; line-height: 1.4; } #hdr { border-bottom: 1px solid #e0e5e6; background-color: #f6f8f8; } #hdr > h1 { color: #555; margin: 0 auto; padding: 1rem; font-size: 1rem; line-height: 1; } #hdr > h1, #wrap { max-width: 55rem; } #wrap { padding: 1rem; margin: 0 auto; justify-content: center; } .tagline { font-size: 1.5rem; color: #555; margin: 0 0 1rem; font-weight: normal; } div.function > h3, div.property > h3 { margin-top: 0; } div.function > h3 > a, div.property > h3 > a { font-family: monospace; color: #2B5782; text-decoration: none; font-size: 1rem; } div.function > div > h4, div.property > div > h4 { margin: 0.9em 0 0.5em; } div.function > div > ul, div.property > div > ul, .property-body { margin: 0.5em 0; } span.parameter, span.type { font-family: monospace; color: #2B5782; text-decoration: none; } span.parameter { font-weight: bold; } li.parameter { margin-bottom: 1rem; } span.optional, span.any_type { font-style: italic; color: #777; } div.function, div.property { margin: 0 -0.5rem 1rem; padding: 0 0.5rem; overflow: auto; } div.function > div, div.property > div { margin-left: 2rem; } div.function:target, div.property:target { background: #ffffb2; border-radius: 0.3em; } span.info { margin-right: 0.5rem; } div.alert { padding: 8px 35px 8px 14px; margin-bottom: 20px; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); position: relative; font-style: italic; } div.warn.alert { color: #635b31; background-color: #fcf8e3; border-color: #bdceaf; border-left: 2px solid #7c723e; } div.good.alert { color: #376d38; background-color: #dff0d8; border-color: #bdceaf; border-left: 2px solid #376d38; } #wrap { display: flex; flex: 1; } #content { flex: 1; position: relative; } #sidebar { flex: 0 0 13rem; margin-right: 1rem; font-family: "Open Sans", "DejaVu Sans", sans-serif; } @media (max-width: 55rem) { #sidebar { display: none; } } #sidebar > h2 { margin: 0 0 1rem; font-size: 1.3rem; font-weight: normal; } #sidebar > h3 { margin: 1rem 0 0.5rem; font-size: 1.1rem; font-weight: normal; color: #444; } #sidebar > ul { list-style-type: none; padding: 0; margin: 0 0 0.5rem; } #sidebar > ul > li { margin: 0.2rem 0.2rem 0.2rem 0.5rem; font-size: 0.9rem; } #sidebar > ul > li > a, #sidebar > ul > li > span { text-decoration: none; font-size: 0.9rem; } #sidebar > ul > li > span { color: black; font-weight: 500; } #sidebar > ul.modules > li, #sidebar > ul.classes > li, #sidebar > ul.modules > li > a, #sidebar > ul.modules > li > span, #sidebar > ul.classes > li > a, #sidebar > ul.classes > li > span { font-family: monospace; } #content li:not(:last-child) { margin-bottom: 0.5rem; } /* * Content typography */ #content > h1 { font-size: 1.7rem; color: #000; margin: 0 0 0.7rem; } #content > h2 { font-size: 1.3rem; color: #000; margin: 1.5rem 0 1rem; } #content > h3 { font-size: 1.0rem; color: #000; } /* Links */ a { color: #204a87; text-decoration: none; } a:visited { color: #2B2087; } a:hover { text-decoration: underline; } a:active { color: #0c1d36; } li.dummy { list-style-type: none; -webkit-user-select: none; } /* Attribution styling */ .attr-wrap { display: flex; flex: 1; } .attr-wrap > h4 { flex: 0 0 8rem; margin: 0; } .attr-wrap > ul { flex: 1; padding: 2px 0; list-style-type: none; margin: 0; } .attr-wrap > ul > li:before { width: 20px; margin-right: 5px; display: inline-block; text-align: center; } ul.authors > li:before { content: "•"; } ul.copyright > li:before { content: "©"; } /* * Code blocks and inline code */ .sourcecode .comment { color: #558817; } .sourcecode .constant { color: #a8660d; } .sourcecode .escape { color: #844631; } .sourcecode .keyword { color: #2239a8; font-weight: bold; } .sourcecode .library { color: #0e7c6b !important; text-decoration: none !important; } .sourcecode .marker { color: #512b1e; background: #fedc56; font-weight: bold; } .sourcecode .number { color: #a8660d; } .sourcecode .operator { color: #2239a8; font-weight: bold; } .sourcecode .preprocessor { color: #a33243; } .sourcecode .prompt { color: #558817; } .sourcecode a:link, .sourcecode a:visited { color: #272fc2; } code, pre { background: #f5f7f9; color: #334; } code { border: 1px solid #dfdfe9; padding: 0px 4px; } pre > code { border: none; padding: 0; } pre { padding: 1rem 1.2rem; border-left: 2px solid #69c; overflow-x: scroll; } /* * Binding styles */ kbd, .cmd { border-radius: .2rem; display: inline-block; line-height: 1; padding: .3rem .4rem; vertical-align: baseline; font-size: 0.95rem; font-weight: 500; } kbd { background: #444b5b; color: #fff; } .cmd { background: #ffeab5; color: #40495c; } .two-col { display: flex; } .two-col > :first-child { flex: 0 0 200px; margin-right: 10px; } .two-col > :first-child > div:not(:first-child) { margin-top: 0.3rem; } ul.triggers { list-style-type: none; padding: 0; } ul.triggers > li { font-family: monospace, sans-serif; color: #2B5782; font-weight: bold; font-size: 1rem; } div.info > div { margin-top: 0.3rem; } luakit-2.4.0/build-utils/find_files.lua000066400000000000000000000064541475363222200200670ustar00rootroot00000000000000local filter_array = require("lib.lousy.util").table.filter_array local lua_escape = require("lib.lousy.util").lua_escape local luakit_files local function get_luakit_files () if not luakit_files then luakit_files = {} local f = io.popen("find . -type f | grep -v '\\./\\.' | sed 's#^\\./##'") for line in f:lines() do -- Check file against filters derived from .gitignore local ok = true ok = ok and not line:match("^doc/apidocs/") ok = ok and not line:match("^doc/html/") ok = ok and not line:match("%.o$") ok = ok and not line:match("%.1$") ok = ok and not line:match("%.swp$") ok = ok and not line:match("~$") ok = ok and not line:match("^common/tokenize.[ch]$") ok = ok and not ({ ["luakit"] = true, ["luakit.so"] = true, ["luakit.1.gz"] = true, ["tests/util.so"] = true, ["buildopts.h"] = true, ["tags"] = true, })[line] if ok then table.insert(luakit_files, line) end end f:close() end return luakit_files end local function path_is_in_directory(path, dir) if path == "." then return true end return string.find(path, "^"..lua_escape(dir)) end local function find_files(dirs, patterns, excludes) assert(dirs ~= ".", "Bad pattern '.'; use empty string instead") assert(type(dirs) == "string" or type(dirs) == "table", "Bad search location: expected string or table") assert(type(patterns) == "string" or type(patterns) == "table", "Bad patterns: expected string or table") assert(excludes == nil or type(excludes) == "table", "Bad exclusion list: expected nil or table") if type(dirs) == "string" then dirs = { dirs } end if type(patterns) == "string" then patterns = { patterns } end if excludes == nil then excludes = {} end for _, dir in ipairs(dirs) do assert(type(dir) == "string", "Each search location must be a string") end for _, pattern in ipairs(patterns) do assert(type(pattern) == "string", "Each pattern must be a string") end for _, exclude in ipairs(excludes) do assert(type(exclude) == "string", "Each exclude must be a string") end -- Get list of files tracked by luakit get_luakit_files() -- Filter to those inside the given directories local file_list = {} for _, file in ipairs(luakit_files) do local dir_match = false for _, dir in ipairs(dirs) do dir_match = dir_match or path_is_in_directory(file, dir) end local pat_match = false for _, pattern in ipairs(patterns) do pat_match = pat_match or string.find(file, pattern) end if dir_match and pat_match then table.insert(file_list, file) end end -- Remove all files in excludes if excludes then file_list = filter_array(file_list, function (_, file) for _, exclude_pat in ipairs(excludes) do if string.find(file, exclude_pat) then return false end end return true end) end -- Return filtered list return file_list end return { get_luakit_files = get_luakit_files, find_files = find_files, } -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/gentokens.lua000077500000000000000000000064751475363222200177700ustar00rootroot00000000000000#!/usr/bin/env lua -- build-utils/gentokens.lua - gen tokenize lib -- -- Copyright © 2010 Mason Larobina -- -- 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 . local tokenize_h = [[ /* This file is autogenerated by build-utils/gentokens.lua */ #ifndef LUAKIT_COMMON_TOKENIZE_H #define LUAKIT_COMMON_TOKENIZE_H #include typedef enum luakit_token_t { L_TK_UNKNOWN=0, /* (luakit_token_t) 0 == L_TK_UNKNOWN */ %s } luakit_token_t; __attribute__((pure)) enum luakit_token_t l_tokenize(const gchar *); __attribute__((pure)) const gchar * token_tostring(luakit_token_t); #endif ]] local tokenize_c = [[ /* This file is autogenerated by build-utils/gentokens.lua */ #include #include "common/tokenize.h" typedef struct { luakit_token_t tok; const gchar *name; } token_map_t; static token_map_t tokens_table[] = { %s { 0, NULL }, }; luakit_token_t l_tokenize(const gchar *s) { static GHashTable *tokens = NULL; if (!tokens) { tokens = g_hash_table_new(g_str_hash, g_str_equal); for (token_map_t *t = tokens_table; t->name; t++) g_hash_table_insert(tokens, (gpointer) t->name, (gpointer) t->tok); } return (luakit_token_t) g_hash_table_lookup(tokens, s); } const gchar * token_tostring(luakit_token_t tok) { if (tok == L_TK_UNKNOWN) return NULL; return tokens_table[((gint)tok) - 1].name; } ]] if #arg ~= 2 then error("invalid args, usage: gentokens.lua [tokens.list] [out.c/out.h]") end -- Load list of tokens local tokens = {} for token in io.lines(arg[1]) do if #token > 0 then if not string.match(token, "^[%w_]+$") then error(string.format("invalid token: %q", token)) end local enum = "L_TK_"..string.upper(token) tokens[enum] = { enum = enum, token = token } end end -- Order tokens local order = {} for k, _ in pairs(tokens) do table.insert(order, k) end table.sort(order) if string.match(arg[2], "%.h$") then -- Gen list of tokens local enums = {} for _, k in ipairs(order) do table.insert(enums, string.format("%s,", k)) end -- Write header file local fh = io.open(arg[2], "w") fh:write(string.format(tokenize_h, table.concat(enums, "\n "))) fh:close() elseif string.match(arg[2], "%.c$") then -- Gen table of { token, "literal" } local tokmap = {} for _, k in ipairs(order) do local t = tokens[k] table.insert(tokmap, string.format('{ %s, %q },', t.enum, t.token)) end -- Write source file local fh = io.open(arg[2], "w") fh:write(string.format(tokenize_c, table.concat(tokmap, "\n "))) fh:close() else error("Unknown action for file: " .. arg[2]) end -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/build-utils/getversion.sh000077500000000000000000000026761475363222200200100ustar00rootroot00000000000000#!/bin/sh # Get the current version using various methods. # The following will serve as a fallback version string (which is the short # hash of the latest commit before the application was packaged (if it was # packaged)). You will find that this file is listed inside the .gitattributes # file like so: # # ./build-utils/getversion.sh export-subst # # This tells git to replace the format string in the following line with the # current hash upon the calling of the `git archive ` command. VERSION_FROM_ARCHIVE=636637cdbe734c615dbbf2fd9c5533295d0a79c1 # The preferred method is to use the git describe command but this is only # possible if the .git directory is present. if [ -d .git -a -r .git ] then VERSION_FROM_GIT=`git describe --tags --always` fi # In case of a worktree checkout, there is not .git directory, but a # .git file, which contains the path to the branch information within # the git repository. In this case, "git describe" works too. if [ -f .git -a -r .git ] && grep -q ^gitdir .git then VERSION_FROM_GIT=`git describe --tags --always` fi if [ x"$VERSION_FROM_GIT" != x ]; then echo $VERSION_FROM_GIT; exit 0; fi if [ "$VERSION_FROM_ARCHIVE" != ':%H$' ]; then echo $VERSION_FROM_ARCHIVE | cut -b1-8; exit 0; fi echo "ERROR: Git commit hash detection failed: Please check why " "build-utils/getversion.sh is failing in your setup and get in touch with us." >&2 exit 2 # vim: ft=sh:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/buildopts.h.in000066400000000000000000000030751475363222200156040ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_BUILDOPTS_H #define LUAKIT_BUILDOPTS_H /** Install location of luakit shared files. This serves as the base path for * all system Lua module loading paths. */ #define LUAKIT_INSTALL_PATH "/usr/local/share/luakit" /** Install location of default configuration files */ #define LUAKIT_CONFIG_PATH "/etc/xdg" /** Install location of luakit's documentation */ #define LUAKIT_DOC_PATH "/usr/local/share/luakit/doc" /** Install location of luakit's man pages */ #define LUAKIT_MAN_PATH "/usr/local/share/man" /** Install location of luakit's pixmaps */ #define LUAKIT_PIXMAP_PATH "/usr/local/share/pixmaps" /** Install location of luakit's application file */ #define LUAKIT_APP_PATH "/usr/local/share/applications" /** Install location of luakit's compiled lib files */ #define LUAKIT_LIB_PATH "/usr/local/lib/luakit" #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/000077500000000000000000000000001475363222200137255ustar00rootroot00000000000000luakit-2.4.0/clib/download.c000066400000000000000000000507131475363222200157060ustar00rootroot00000000000000/* * clib/download.c - wrapper for the WebKitDownload class * * Copyright © 2011 Fabian Streitel * Copyright © 2011 Mason Larobina * * 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 . * */ #include "common/luaobject.h" #include "common/luauniq.h" #include "clib/download.h" #include "luah.h" #include "globalconf.h" #include "web_context.h" #include #include #define REG_KEY "luakit.uniq.registry.download" /** Internal data structure for luakit's downloads. */ typedef struct { /** Common \ref lua_object_t header. \see LUA_OBJECT_HEADER */ LUA_OBJECT_HEADER /** \privatesection */ /** The \c WebKitDownload that handles the actual data transfer. */ WebKitDownload* webkit_download; /** The reference to the Lua object representing the download. * As long as the download is running, the object will be reffed to * prevent its garbage-collection. */ gpointer ref; /** The URI that is being downloaded. */ gchar *uri; /** The destination path in the filesystem where the file is save to. */ gchar *destination; /** The error message in case of a download failure. */ gchar *error; enum luakit_download_status_t { LUAKIT_DOWNLOAD_STATUS_FINISHED, LUAKIT_DOWNLOAD_STATUS_CREATED, LUAKIT_DOWNLOAD_STATUS_STARTED, LUAKIT_DOWNLOAD_STATUS_CANCELLED, LUAKIT_DOWNLOAD_STATUS_FAILED } status; } download_t; static lua_class_t download_class; LUA_OBJECT_FUNCS(download_class, download_t, download) static download_t *current_destination_cb; #define luaH_checkdownload(L, idx) luaH_checkudata(L, idx, &(download_class)) /** * Allow garbage collection of the download. * * This function unrefs the download from the object registry. * It also deletes the backup files that may be created by WebKit while * downloading. * * \param L The Lua VM state. * \param download The \ref download_t to unref. */ static void luaH_download_unref(lua_State *L, download_t *download) { if (download->ref) { luaH_object_unref(L, download->ref); download->ref = NULL; } /* delete the annoying backup file generated while downloading */ gchar *backup = g_strdup_printf("%s~", download->destination); g_unlink(backup); g_free(backup); } /** * Frees all data associated with the download and disposes * of the Lua object. * * \param L The Lua VM state. * * \luastack * \lparam A \c download object to free. */ static gint luaH_download_gc(lua_State *L) { download_t *download = luaH_checkdownload(L, 1); g_object_unref(G_OBJECT(download->webkit_download)); g_free(download->destination); g_free(download->uri); g_free(download->error); return luaH_object_gc(L); } /** * Callback from the \c WebKitDownload when a destination needs to be * selected. * * \returns whether or not other handlers should be invoked for the * \c decide-destination signal */ static gboolean decide_destination_cb(WebKitDownload* UNUSED(dl), gchar *suggested_filename, download_t *download) { lua_State *L = common.L; luaH_object_push(L, download->ref); lua_pushstring(L, suggested_filename); current_destination_cb = download; gint ret = luaH_object_emit_signal(L, -2, "decide-destination", 1, 1); gboolean handled = (ret && lua_toboolean(L, -1)); lua_pop(L, 1 + ret); current_destination_cb = NULL; /* Prevent segfault when download cancelled without setting destination. * https://github.com/aidanholm/luakit/issues/402 */ if (download->status == LUAKIT_DOWNLOAD_STATUS_CANCELLED) webkit_download_set_destination(download->webkit_download, "/tmp/"); return handled; } /** * Callback from the \c WebKitDownload after a destination has been * selected. */ static void created_destination_cb(WebKitDownload* UNUSED(dl), gchar *destination, download_t *download) { lua_State *L = common.L; luaH_object_push(L, download->ref); lua_pushstring(L, destination); download->status = LUAKIT_DOWNLOAD_STATUS_CREATED; /* clear last download error message */ if (download->error) { g_free(download->error); download->error = NULL; } luaH_object_emit_signal(L, -2, "created-destination", 1, 0); lua_pop(L, 1); return; } /** * Callback from the \c WebKitDownload in case of failure. * * Fills the \c error member of \ref download_t. */ static void failed_cb(WebKitDownload* UNUSED(d), GError *error, download_t *download) { // TODO does the GError error need a g_error_free(error)? /* save error message */ if (download->error) g_free(download->error); download->error = g_strdup(error->message); if (error->code == WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER) { download->status = LUAKIT_DOWNLOAD_STATUS_CANCELLED; } else { warn("download %p failed: %s", download, error->message); download->status = LUAKIT_DOWNLOAD_STATUS_FAILED; /* emit error signal if able */ if (download->ref) { lua_State *L = common.L; luaH_object_push(L, download->ref); lua_pushstring(L, error->message); luaH_object_emit_signal(L, -2, "error", 1, 0); lua_pop(L, 1); /* unreffing of download happens in finished_cb */ } } } static void progress_cb(WebKitDownload *UNUSED(dl), GParamSpec *UNUSED(ps), download_t *download) { download->status = LUAKIT_DOWNLOAD_STATUS_STARTED; } /** * Callback from the \c WebKitDownload once the download is complete * and after the failed callback (if there is a failure) */ static void finished_cb(WebKitDownload* UNUSED(dl), download_t *download) { lua_State *L = common.L; luaH_object_push(L, download->ref); if (download->status != LUAKIT_DOWNLOAD_STATUS_CANCELLED && download->status != LUAKIT_DOWNLOAD_STATUS_FAILED) download->status = LUAKIT_DOWNLOAD_STATUS_FINISHED; gint ret = luaH_object_emit_signal(L, -1, "finished", 0, 0); lua_pop(L, 1 + ret); luaH_download_unref(L, download); return; } gint luaH_download_new(lua_State *L) { luaH_checktable(L, 2); const gchar *uri = NULL; gint top = lua_gettop(L); if (luaH_rawfield(L, 2, "uri") && lua_isstring(L, -1)) uri = lua_tostring(L, -1); lua_settop(L, top); if (!uri) return luaL_error(L, "download requires a URI"); WebKitDownload *d = webkit_web_context_download_uri(web_context_get(), uri); return luaH_download_push(L, d); } /** * Pushes the given download onto the Lua stack. * * Obtains a GTK reference on the \c WebKitDownload. * * \param L The Lua VM state. * \param d The \c WebKitDownload to push onto the stack. * * \luastack * \lreturn A \c download object. */ gint luaH_download_push(lua_State *L, WebKitDownload *d) { if (luaH_uniq_get_ptr(L, REG_KEY, d)) return 1; download_class.allocator(L); download_t *download = luaH_checkdownload(L, -1); WebKitURIRequest *r = webkit_download_get_request(d); download->uri = g_strdup(webkit_uri_request_get_uri(r)); download->webkit_download = d; g_object_ref(G_OBJECT(download->webkit_download)); /* raise corresponding luakit signals when the webkit signals are * emitted */ g_signal_connect(G_OBJECT(download->webkit_download), "decide-destination", G_CALLBACK(decide_destination_cb), download); g_signal_connect(G_OBJECT(download->webkit_download), "created-destination", G_CALLBACK(created_destination_cb), download); g_signal_connect(G_OBJECT(download->webkit_download), "notify::estimated-progress", G_CALLBACK(progress_cb), download); g_signal_connect(G_OBJECT(download->webkit_download), "finished", G_CALLBACK(finished_cb), download); /* raise failed signal on failure or cancellation */ g_signal_connect(G_OBJECT(download->webkit_download), "failed", G_CALLBACK(failed_cb), download); /* save ref to the lua class instance */ lua_pushvalue(L, -1); download->ref = luaH_object_ref(L, -1); /* uniq mapping is weak-valued, so no need to manually delete */ luaH_uniq_add_ptr(L, REG_KEY, d, -1); /* return download */ return 1; } static gint luaH_download_set_allow_overwrite(lua_State *L, download_t *download) { gboolean allow = lua_toboolean(L, -1); webkit_download_set_allow_overwrite(download->webkit_download, allow); luaH_object_emit_signal(L, -3, "property::allow-overwrite", 0, 0); return 0; } static gint luaH_download_get_allow_overwrite(lua_State *L, download_t *download) { lua_pushboolean(L, webkit_download_get_allow_overwrite(download->webkit_download)); return 1; } /** * Sets the destination of a download. * * Converts the given destination to a \c file:// URI. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lvalue A string containing the new destination for the download. */ static gint luaH_download_set_destination(lua_State *L, download_t *download) { if (download != current_destination_cb) { luaH_warn(L, "cannot set destination outside decide-destination handler"); return 0; } const gchar *destination = luaL_checkstring(L, -1); gchar *uri = g_filename_to_uri(destination, NULL, NULL); if (uri) { download->destination = g_strdup(destination); webkit_download_set_destination(download->webkit_download, uri); g_free(uri); luaH_object_emit_signal(L, -3, "property::destination", 0, 0); /* g_filename_to_uri failed on destination path */ } else { lua_pushfstring(L, "invalid destination: '%s'", destination); lua_error(L); } return 0; } /** * Returns the destination URI of the given download. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. */ LUA_OBJECT_EXPORT_PROPERTY(download, download_t, destination, lua_pushstring) /** * Returns the current progress in percent of the given download. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The progress of the download as a number between 0.0 and 1.0 */ static gint luaH_download_get_progress(lua_State *L, download_t *download) { gdouble progress = webkit_download_get_estimated_progress(download->webkit_download); lua_pushnumber(L, progress); return 1; } /** * Returns the value of the Content-Type webkit network reponse header. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The network request Content-Type. */ static gint luaH_download_get_mime_type(lua_State *L, download_t *download) { WebKitURIResponse *response = webkit_download_get_response( download->webkit_download); if (!response) return 0; const gchar *mime_type = webkit_uri_response_get_mime_type(response); if (mime_type) { lua_pushstring(L, mime_type); return 1; } return 0; } /** * Returns the status of the given download. * * The status will be one of the following: * - \c finished * - \c created * - \c started * - \c cancelled * - \c failed * * Returns nothing if an error occurs. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The status of the download as a string or \c nil. */ static gint luaH_download_get_status(lua_State *L, download_t *download) { switch(download->status) { case LUAKIT_DOWNLOAD_STATUS_FINISHED: lua_pushstring(L, "finished"); break; case LUAKIT_DOWNLOAD_STATUS_CREATED: lua_pushstring(L, "created"); break; case LUAKIT_DOWNLOAD_STATUS_STARTED: lua_pushstring(L, "started"); break; case LUAKIT_DOWNLOAD_STATUS_CANCELLED: lua_pushstring(L, "cancelled"); break; case LUAKIT_DOWNLOAD_STATUS_FAILED: lua_pushstring(L, "failed"); break; default: luaH_warn(L, "unknown download status"); return 0; } return 1; } /* \fn static gint luaH_download_get_error(lua_State *L) * Returns the message of the last error that occurred for the given download. * * If no error occurred so far, returns \c nil. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The message of the last download error as a string or \c nil. */ LUA_OBJECT_EXPORT_PROPERTY(download, download_t, error, lua_pushstring) /** * Returns the expected total size of the download. * * May vary during downloading as not all servers send this correctly. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The total size of the download in bytes as a number. */ static gint luaH_download_get_content_length(lua_State *L, download_t *download) { gdouble total_size = webkit_uri_response_get_content_length( webkit_download_get_response(download->webkit_download)); lua_pushnumber(L, total_size); return 1; } /** * Returns the current size of the download, i.e. the bytes already downloaded. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The current size of the download in bytes as a number. */ static gint luaH_download_get_received_data_length(lua_State *L, download_t *download) { gdouble current_size = webkit_download_get_received_data_length( download->webkit_download); lua_pushnumber(L, current_size); return 1; } /** * Returns the elapsed time since starting the download. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The elapsed time since starting the download in seconds as a number. */ static gint luaH_download_get_elapsed_time(lua_State *L, download_t *download) { gdouble elapsed_time = webkit_download_get_elapsed_time( download->webkit_download); lua_pushnumber(L, elapsed_time); return 1; } /** * Returns the suggested filename for the download. * This is provided by \c WebKit and inferred from the URI and response headers. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The filename that WebKit suggests for the download as a string. */ static gint luaH_download_get_suggested_filename(lua_State *L, download_t *download) { /* webkit_download_get_response() returns NULL if the response hasn't been * received yet. This function should only be called after the * decide-destination signal is raised. */ const gchar *suggested_filename = webkit_uri_response_get_suggested_filename( webkit_download_get_response(download->webkit_download)); lua_pushstring(L, suggested_filename); return 1; } /** * Sets the URI of the download. * This does not have any effect if the download is already running. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lvalue The new URI to download. */ static gint luaH_download_set_uri(lua_State *L, download_t *download) { gchar *uri = (gchar*) luaL_checkstring(L, -1); /* use http protocol if none specified */ if (g_strrstr(uri, "://")) uri = g_strdup(uri); else uri = g_strdup_printf("http://%s", uri); download->uri = uri; return 0; } /** * Returns the URI that is being downloaded. * * \param L The Lua VM state. * \param download The \ref download_t of the download. * * \luastack * \lparam A \c download object. * \lreturn The URI of this download as a string. */ LUA_OBJECT_EXPORT_PROPERTY(download, download_t, uri, lua_pushstring) /** * Starts the download. * * Will produce a warning if the download is already running. * References the download to prevent its garbage collection. * Will raise a Lua error if the start failed. * * \param L The Lua VM state. * * \luastack * \lparam A \c download object to start. */ static gint luaH_download_start(lua_State* UNUSED(L)) { /* all this stuff moved to download_start_cb() in * widgets/webview/downloads.c */ return 0; } /** * Aborts the download. * * Will produce a warning if the download is not running. * Unreferences the download to allow its garbage collection. * * \param L The Lua VM state. * * \luastack * \lparam A \c download object to abort. */ static gint luaH_download_cancel(lua_State *L) { download_t *download = luaH_checkdownload(L, 1); webkit_download_cancel(download->webkit_download); download->status = LUAKIT_DOWNLOAD_STATUS_CANCELLED; return 0; } /** * Creates the Lua download class. * * \param L The Lua VM state. */ void download_class_setup(lua_State *L) { static const struct luaL_Reg download_methods[] = { LUA_CLASS_METHODS(download) { "__call", luaH_download_new }, { NULL, NULL } }; static const struct luaL_Reg download_meta[] = { LUA_OBJECT_META(download) LUA_CLASS_META { "start", luaH_download_start }, { "cancel", luaH_download_cancel }, { "__gc", luaH_download_gc }, { NULL, NULL }, }; luaH_class_setup(L, &download_class, "download", (lua_class_allocator_t) download_new, NULL, NULL, download_methods, download_meta); luaH_class_add_property(&download_class, L_TK_ALLOW_OVERWRITE, (lua_class_propfunc_t) luaH_download_set_allow_overwrite, (lua_class_propfunc_t) luaH_download_get_allow_overwrite, (lua_class_propfunc_t) luaH_download_set_allow_overwrite); luaH_class_add_property(&download_class, L_TK_DESTINATION, (lua_class_propfunc_t) luaH_download_set_destination, (lua_class_propfunc_t) luaH_download_get_destination, (lua_class_propfunc_t) luaH_download_set_destination); luaH_class_add_property(&download_class, L_TK_PROGRESS, NULL, (lua_class_propfunc_t) luaH_download_get_progress, NULL); luaH_class_add_property(&download_class, L_TK_STATUS, NULL, (lua_class_propfunc_t) luaH_download_get_status, NULL); luaH_class_add_property(&download_class, L_TK_ERROR, NULL, (lua_class_propfunc_t) luaH_download_get_error, NULL); // TODO rename token, possibly to L_TK_CONTENT_LENGTH? luaH_class_add_property(&download_class, L_TK_TOTAL_SIZE, NULL, (lua_class_propfunc_t) luaH_download_get_content_length, NULL); luaH_class_add_property(&download_class, L_TK_CURRENT_SIZE, NULL, (lua_class_propfunc_t) luaH_download_get_received_data_length, NULL); luaH_class_add_property(&download_class, L_TK_ELAPSED_TIME, NULL, (lua_class_propfunc_t) luaH_download_get_elapsed_time, NULL); luaH_class_add_property(&download_class, L_TK_MIME_TYPE, NULL, (lua_class_propfunc_t) luaH_download_get_mime_type, NULL); luaH_class_add_property(&download_class, L_TK_SUGGESTED_FILENAME, NULL, (lua_class_propfunc_t) luaH_download_get_suggested_filename, NULL); luaH_class_add_property(&download_class, L_TK_URI, (lua_class_propfunc_t) luaH_download_set_uri, (lua_class_propfunc_t) luaH_download_get_uri, (lua_class_propfunc_t) NULL); luaH_uniq_setup(L, REG_KEY, "v"); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/download.h000066400000000000000000000021021475363222200157000ustar00rootroot00000000000000/* * clib/download.h - WebKitDownload wrapper header * * Copyright © 2011 Fabian Streitel * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_DOWNLOAD_H #define LUAKIT_CLIB_DOWNLOAD_H #include #include void download_class_setup(lua_State*); gint luaH_download_push(lua_State*, WebKitDownload*); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/ipc.c000066400000000000000000000047701475363222200146540ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include "clib/widget.h" #include "common/ipc.h" #include "widgets/webview.h" #include "common/luauniq.h" #include "common/clib/ipc.h" #include "common/luaserialize.h" #define REG_KEY "luakit.registry.ipc_channel" gint ipc_channel_send(lua_State *L) { ipc_channel_t *ipc_channel = luaH_check_ipc_channel(L, 1); guint64 page_id = 0; ipc_endpoint_t *ipc = NULL; /* Optional first argument: view or view id to send message to */ if (lua_isuserdata(L, 2)) { widget_t *w = luaH_checkwebview(L, 2); page_id = webkit_web_view_get_page_id(WEBKIT_WEB_VIEW(w->widget)); ipc = webview_get_endpoint(w); lua_remove(L, 2); } else if (lua_isnumber(L, 2)) { page_id = lua_tointeger(L, 2); widget_t *w = webview_get_by_id(page_id); ipc = webview_get_endpoint(w); lua_remove(L, 2); } luaL_checkstring(L, 2); lua_pushstring(L, ipc_channel->name); lua_pushinteger(L, page_id); if (ipc) ipc_send_lua(ipc, IPC_TYPE_lua_ipc, L, 2, lua_gettop(L)); else { const GPtrArray *endpoints = ipc_endpoints_get(); for (unsigned i = 0; i < endpoints->len; i++) { ipc_endpoint_t *ipc = g_ptr_array_index(endpoints, i); ipc_send_lua(ipc, IPC_TYPE_lua_ipc, L, 2, lua_gettop(L)); } } return 0; } void ipc_channel_recv(lua_State *L, const gchar *arg, guint arglen) { gint top = lua_gettop(L); int n = lua_deserialize_range(L, (guint8*)arg, arglen); const char *signame = lua_tostring(L, -n); luaH_uniq_get(L, REG_KEY, -1); lua_remove(L, -n-1); lua_insert(L, -n); lua_remove(L, -1); if (!lua_isnil(L, -n+1)) luaH_object_emit_signal(L, -n+1, signame, n-2, 0); lua_settop(L, top); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/luakit.c000066400000000000000000000725361475363222200153770ustar00rootroot00000000000000/* * clib/luakit.c - Generic functions for Lua scripts * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "clib/luakit.h" #include "common/clib/luakit.h" #include "clib/widget.h" #include "common/luaserialize.h" #include "common/luayield.h" #include "common/ipc.h" #include "common/resource.h" #include "common/signal.h" #include "ipc.h" #include "luah.h" #include "log.h" #include "web_context.h" #include "globalconf.h" #include #include #include #include #include #include /* setup luakit module signals */ static lua_class_t luakit_class; LUA_CLASS_FUNCS(luakit, luakit_class) GtkClipboard * luaH_clipboard_get(lua_State *L, gint idx) { #define CB_CASE(t) case L_TK_##t: return gtk_clipboard_get(GDK_SELECTION_##t); switch(l_tokenize(luaL_checkstring(L, idx))) { CB_CASE(PRIMARY) CB_CASE(SECONDARY) CB_CASE(CLIPBOARD) default: break; } return NULL; #undef CB_CASE } /** __index metamethod for the luakit.selection table which * returns text from an X selection. * \see http://en.wikipedia.org/wiki/X_Window_selection * \see http://developer.gnome.org/gtk/stable/gtk-Clipboards.html#gtk-clipboard-wait-for-text * * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_luakit_selection_index(lua_State *L) { GtkClipboard *selection = luaH_clipboard_get(L, 2); if (selection) { gchar *text = gtk_clipboard_wait_for_text(selection); if (text) { lua_pushstring(L, text); g_free(text); return 1; } } return 0; } /** __newindex metamethod for the luakit.selection table which * sets an X selection. * \see http://en.wikipedia.org/wiki/X_Window_selection * \see http://developer.gnome.org/gtk/stable/gtk-Clipboards.html#gtk-clipboard-set-text * * \param L The Lua VM state. * \return The number of elements pushed on stack (0). * * \lcode * luakit.selection.primary = "Malcolm Reynolds" * luakit.selection.clipboard = "John Crichton" * print(luakit.selection.primary) // outputs "Malcolm Reynolds" * luakit.selection.primary = nil // clears the primary selection * print(luakit.selection.primary) // outputs nothing * \endcode */ static gint luaH_luakit_selection_newindex(lua_State *L) { GtkClipboard *selection = luaH_clipboard_get(L, 2); if (selection) { const gchar *text = !lua_isnil(L, 3) ? luaL_checkstring(L, 3) : NULL; if (text && *text) gtk_clipboard_set_text(selection, text, -1); else gtk_clipboard_clear(selection); } return 0; } static gint luaH_luakit_selection_table_push(lua_State *L) { /* create selection table */ lua_newtable(L); /* setup metatable */ lua_createtable(L, 0, 2); lua_pushliteral(L, "__index"); lua_pushcfunction(L, luaH_luakit_selection_index); lua_rawset(L, -3); lua_pushliteral(L, "__newindex"); lua_pushcfunction(L, luaH_luakit_selection_newindex); lua_rawset(L, -3); lua_setmetatable(L, -2); return 1; } /** Shows a Gtk save dialog. * \see http://developer.gnome.org/gtk/stable/GtkDialog.html * * \param L The Lua VM state. * \return The number of elements pushed on stack. * * \luastack * \lparam title The title of the dialog window. * \lparam parent The parent window of the dialog or \c nil. * \lparam default_folder The folder to initially display in the file dialog. * \lparam default_name The filename to preselect in the dialog. * \lreturn The name of the selected file or \c nil if the * dialog was cancelled. */ static gint luaH_luakit_save_file(lua_State *L) { const gchar *title = luaL_checkstring(L, 1); /* get window to display dialog over */ GtkWindow *parent_window = NULL; if (!lua_isnil(L, 2)) { widget_t *parent = luaH_checkudata(L, 2, &widget_class); if (!GTK_IS_WINDOW(parent->widget)) luaL_argerror(L, 2, "window widget"); parent_window = GTK_WINDOW(parent->widget); } const gchar *default_folder = luaL_checkstring(L, 3); const gchar *default_name = luaL_checkstring(L, 4); #if GTK_CHECK_VERSION(3,10,0) GtkWidget *dialog = gtk_file_chooser_dialog_new(title, parent_window, GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL); #else GtkWidget *dialog = gtk_file_chooser_dialog_new(title, parent_window, GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); #endif /* set default folder, name and overwrite confirmation policy */ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), default_folder); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), default_name); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); lua_pushstring(L, filename); g_free(filename); } else lua_pushnil(L); gtk_widget_destroy(dialog); return 1; } /** Executes a child synchronously (waits for the child to exit before * returning). The exit status and all stdout and stderr output from the * child is returned. * \see http://developer.gnome.org/glib/stable/glib-Spawning-Processes.html#g-spawn-command-line-sync * * \param L The Lua VM state. * \return The number of elements pushed on stack (3). * * \luastack * \lparam cmd The command to run (from a shell). * \lreturn The exit status of the child. * \lreturn The childs stdout. * \lreturn The childs stderr. */ static gint luaH_luakit_spawn_sync(lua_State *L) { GError *e = NULL; gchar *_stdout = NULL; gchar *_stderr = NULL; gint rv; struct sigaction sigact; struct sigaction oldact; const gchar *command = luaL_checkstring(L, 1); /* Note: we have to temporarily clear the SIGCHLD handler. Otherwise * g_spawn_sync wouldn't be able to read subprocess' return value. */ sigact.sa_handler = SIG_DFL; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; if (sigaction(SIGCHLD, &sigact, &oldact)) fatal("Can't clear SIGCHLD handler"); g_spawn_command_line_sync(command, &_stdout, &_stderr, &rv, &e); /* restore SIGCHLD handler */ if (sigaction(SIGCHLD, &oldact, NULL)) fatal("Can't restore SIGCHLD handler"); /* raise error on spawn function error */ if (e) { lua_pushstring(L, e->message); g_clear_error(&e); lua_error(L); } /* push exit status, stdout, stderr on to stack and return */ lua_pushinteger(L, WEXITSTATUS(rv)); lua_pushstring(L, _stdout); lua_pushstring(L, _stderr); g_free(_stdout); g_free(_stderr); return 3; } /* Calls the Lua function defined as callback for a (async) spawned process * The called Lua function receives 2 arguments: * Exit type: one of: "exit" (normal exit), "signal" (terminated by * signal), "unknown" (another reason) * Exit number: When normal exit happened, the exit code of the process. When * finished by a signal, the signal number. -1 otherwise. */ void async_callback_handler(GPid pid, gint status, gpointer cb_ref) { g_spawn_close_pid(pid); if (!cb_ref) return; lua_State *L = common.L; /* push exit reason & exit status onto lua stack */ if (WIFEXITED(status)) { lua_pushliteral(L, "exit"); lua_pushinteger(L, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { lua_pushliteral(L, "signal"); lua_pushinteger(L, WTERMSIG(status)); } else { lua_pushliteral(L, "unknown"); lua_pushinteger(L, -1); } /* push callback function onto stack */ luaH_object_push(L, cb_ref); luaH_dofunction(L, 2, 0); luaH_object_unref(L, cb_ref); } /** Executes a child program asynchronously (your program will not block waiting * for the child to exit). * * \see \ref async_callback_handler * \see http://developer.gnome.org/glib/stable/glib-Shell-related-Utilities.html#g-shell-parse-argv * \see http://developer.gnome.org/glib/stable/glib-Spawning-Processes.html#g-spawn-async * * \param L The Lua VM state. * \return The number of elements pushed on stack (0). * * \luastack * \lparam command The command to execute a child program. * \lparam callback Optional Lua callback function. * \lreturn The child pid. * * \lcode * local editor = "gvim" * local filename = "config" * * function editor_callback(exit_reason, exit_status) * if exit_reason == "exit" then * print(string.format("Contents of %q:", filename)) * for line in io.lines(filename) do * print(line) * end * else * print("Editor exited with status: " .. exit_status) * end * end * * luakit.spawn(string.format("%s %q", editor, filename), editor_callback) * \endcode */ static gint luaH_luakit_spawn(lua_State *L) { GError *e = NULL; GPid pid = 0; const gchar *command = luaL_checkstring(L, 1); gint argc = 0; gchar **argv = NULL; gpointer cb_ref = NULL; /* check callback function type */ if (lua_gettop(L) > 1 && !lua_isnil(L, 2)) { if (lua_isfunction(L, 2)) cb_ref = luaH_object_ref(L, 2); else luaL_typerror(L, 2, lua_typename(L, LUA_TFUNCTION)); } /* parse arguments */ if (!g_shell_parse_argv(command, &argc, &argv, &e)) goto spawn_error; /* spawn command */ if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &e)) goto spawn_error; /* call Lua callback (if present), and free GLib resources */ g_child_watch_add(pid, async_callback_handler, cb_ref); g_strfreev(argv); lua_pushnumber(L, pid); return 1; spawn_error: luaH_object_unref(L, cb_ref); lua_pushstring(L, e->message); g_clear_error(&e); g_strfreev(argv); lua_error(L); return 0; } /** Wrapper around the execl POSIX function. The exec family of functions * replaces the current process image with a new process image. This function * will only return if there was an error with the execl call. * \see http://en.wikipedia.org/wiki/Execl * * \param L The Lua VM state. * \return The number of elements pushed on the stack (0). */ static gint luaH_luakit_exec(lua_State *L) { static const gchar *shell = NULL; if (!shell && !(shell = g_getenv("SHELL"))) shell = "/bin/sh"; ipc_remove_socket_file(); execl(shell, shell, "-c", luaL_checkstring(L, 1), NULL); return 0; } /** Pushes the command-line options parsed by luakit * * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_luakit_push_options_table(lua_State *L) { lua_newtable(L); for (guint i = 0; i < globalconf.argv->len; ++i) { lua_pushstring(L, g_ptr_array_index(globalconf.argv, i)); lua_rawseti(L, -2, i + 1); } return 1; } static WebKitWebsiteDataTypes luaH_parse_website_data_types_table(lua_State *L, gint idx) { WebKitWebsiteDataTypes types = 0; idx = luaH_absindex(L, idx); luaH_checktable(L, idx); size_t len = lua_objlen(L, idx); for (size_t i = 1; i <= len; i++) { lua_rawgeti(L, idx, i); if (!lua_isstring(L, -1)) luaL_error(L, "website data types must be strings"); const char *type = lua_tostring(L, -1); #define TYPE(upper, lower) \ if (g_str_equal(type, #lower)) types |= WEBKIT_WEBSITE_DATA_##upper; TYPE(MEMORY_CACHE, memory_cache) TYPE(DISK_CACHE, disk_cache) TYPE(OFFLINE_APPLICATION_CACHE, offline_application_cache) TYPE(SESSION_STORAGE, session_storage) TYPE(LOCAL_STORAGE, local_storage) TYPE(INDEXEDDB_DATABASES, indexeddb_databases) TYPE(PLUGIN_DATA, plugin_data) TYPE(COOKIES, cookies) #if WEBKIT_CHECK_VERSION(2,24,0) TYPE(DEVICE_ID_HASH_SALT, device_id_hash_salt) #endif #if WEBKIT_CHECK_VERSION(2,26,0) TYPE(HSTS_CACHE, hsts_cache) #endif TYPE(ALL, all) #undef TYPE lua_pop(L, 1); } return types; } static void website_data_fetch_finish(WebKitWebsiteDataManager *manager, GAsyncResult *result, lua_State *L) { g_assert_cmpint(lua_status(L),==,LUA_YIELD); GError *error = NULL; GList *items = webkit_website_data_manager_fetch_finish(manager, result, &error); if (error) { lua_pushnil(L); lua_pushstring(L, error->message); g_error_free(error); } else { lua_newtable(L); GList *item = items; while (item) { WebKitWebsiteData *website_data = item->data; WebKitWebsiteDataTypes present = webkit_website_data_get_types(website_data); lua_pushstring(L, webkit_website_data_get_name(website_data)); lua_newtable(L); #define TYPE(upper, lower) \ if (present & WEBKIT_WEBSITE_DATA_##upper) { \ lua_pushstring(L, #lower); \ lua_pushinteger(L, webkit_website_data_get_size(website_data, \ WEBKIT_WEBSITE_DATA_##upper)); \ lua_rawset(L, -3); \ } TYPE(MEMORY_CACHE, memory_cache) TYPE(DISK_CACHE, disk_cache) TYPE(OFFLINE_APPLICATION_CACHE, offline_application_cache) TYPE(SESSION_STORAGE, session_storage) TYPE(LOCAL_STORAGE, local_storage) TYPE(INDEXEDDB_DATABASES, indexeddb_databases) TYPE(PLUGIN_DATA, plugin_data) TYPE(COOKIES, cookies) #if WEBKIT_CHECK_VERSION(2,24,0) TYPE(DEVICE_ID_HASH_SALT, device_id_hash_salt) #endif #if WEBKIT_CHECK_VERSION(2,26,0) TYPE(HSTS_CACHE, hsts_cache) #endif #undef TYPE lua_rawset(L, -3); webkit_website_data_unref(website_data); item = item->next; } } g_list_free(items); luaH_resume(L, lua_gettop(L)); } static gint luaH_luakit_website_data_fetch(lua_State *L) { WebKitWebsiteDataTypes data_types = luaH_parse_website_data_types_table(L, 1); if (data_types == 0) return luaL_error(L, "no website data types specified"); WebKitWebContext *web_context = web_context_get(); WebKitWebsiteDataManager *data_manager = webkit_web_context_get_website_data_manager(web_context); webkit_website_data_manager_fetch(data_manager, data_types, NULL, (GAsyncReadyCallback)website_data_fetch_finish, L); return luaH_yield(L); } typedef struct _website_data_remove_task_t { lua_State *L; WebKitWebsiteDataTypes data_types; char *domain; } website_data_remove_task_t; static void website_data_remove_finish(WebKitWebsiteDataManager *manager, GAsyncResult *result, website_data_remove_task_t *wdrt) { lua_State *L = wdrt->L; g_assert_cmpint(lua_status(L),==,LUA_YIELD); GError *error = NULL; webkit_website_data_manager_remove_finish(manager, result, &error); if (error) { lua_pushnil(L); lua_pushstring(L, error->message); g_error_free(error); } else lua_pushboolean(L, TRUE); g_free(wdrt->domain); g_slice_free(website_data_remove_task_t, wdrt); luaH_resume(L, lua_gettop(L)); } static void luaH_luakit_website_data_remove_cont(WebKitWebsiteDataManager *manager, GAsyncResult *result, website_data_remove_task_t *wdrt) { lua_State *L = wdrt->L; g_assert_cmpint(lua_status(L),==,LUA_YIELD); GError *error = NULL; GList *items = webkit_website_data_manager_fetch_finish(manager, result, &error); if (error) { lua_pushstring(L, error->message); g_error_free(error); g_free(wdrt->domain); g_slice_free(website_data_remove_task_t, wdrt); luaL_error(L, lua_tostring(L, -1)); } GList *item = items; while (item) { WebKitWebsiteData *website_data = item->data; GList *next = item->next; if (!g_str_equal(webkit_website_data_get_name(website_data), wdrt->domain)) { webkit_website_data_unref(website_data); items = g_list_delete_link(items, item); } item = next; } if (!items) { g_free(wdrt->domain); g_slice_free(website_data_remove_task_t, wdrt); lua_pushboolean(L, TRUE); luaH_resume(L, 1); return; } WebKitWebContext *web_context = web_context_get(); WebKitWebsiteDataManager *data_manager = webkit_web_context_get_website_data_manager(web_context); webkit_website_data_manager_remove(data_manager, wdrt->data_types, items, NULL, (GAsyncReadyCallback)website_data_remove_finish, wdrt); item = items; while (item) { webkit_website_data_unref(item->data); item = item->next; } g_list_free(items); } static gint luaH_luakit_website_data_remove(lua_State *L) { WebKitWebsiteDataTypes data_types = luaH_parse_website_data_types_table(L, 1); if (data_types == 0) return luaL_error(L, "no website data types specified"); const char *domain = luaL_checkstring(L, 2); website_data_remove_task_t *wdrt = g_slice_new0(website_data_remove_task_t); wdrt->L = L; wdrt->domain = g_strdup(domain); wdrt->data_types = data_types; WebKitWebContext *web_context = web_context_get(); WebKitWebsiteDataManager *data_manager = webkit_web_context_get_website_data_manager(web_context); webkit_website_data_manager_fetch(data_manager, data_types, NULL, (GAsyncReadyCallback)luaH_luakit_website_data_remove_cont, wdrt); return luaH_yield(L); } static void website_data_clear_finish(WebKitWebsiteDataManager *manager, GAsyncResult *result, lua_State *L) { g_assert_cmpint(lua_status(L),==,LUA_YIELD); GError *error = NULL; webkit_website_data_manager_clear_finish(manager, result, &error); if (error) { lua_pushnil(L); lua_pushstring(L, error->message); g_error_free(error); } else lua_pushboolean(L, TRUE); luaH_resume(L, lua_gettop(L)); } static gint luaH_luakit_website_data_clear(lua_State *L) { WebKitWebsiteDataTypes data_types = luaH_parse_website_data_types_table(L, 1); if (data_types == 0) return luaL_error(L, "no website data types specified"); GTimeSpan timespan = luaL_optinteger(L, 2, 0); WebKitWebContext *web_context = web_context_get(); WebKitWebsiteDataManager *data_manager = webkit_web_context_get_website_data_manager(web_context); webkit_website_data_manager_clear(data_manager, data_types, timespan, NULL, (GAsyncReadyCallback)website_data_clear_finish, L); return luaH_yield(L); } static gint luaH_luakit_website_data_index(lua_State *L) { const gchar *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); switch (token) { case L_TK_FETCH: lua_pushcfunction(L, luaH_luakit_website_data_fetch); luaH_yield_wrap_function(L); return 1; case L_TK_REMOVE: lua_pushcfunction(L, luaH_luakit_website_data_remove); luaH_yield_wrap_function(L); return 1; case L_TK_CLEAR: lua_pushcfunction(L, luaH_luakit_website_data_clear); luaH_yield_wrap_function(L); return 1; default: return 0; } } static gint luaH_luakit_push_website_data_table(lua_State *L) { lua_newtable(L); /* setup metatable */ lua_createtable(L, 0, 2); /* push __index metafunction */ lua_pushliteral(L, "__index"); lua_pushvalue(L, 1); /* copy webview userdata */ lua_pushcclosure(L, luaH_luakit_website_data_index, 1); lua_rawset(L, -3); lua_setmetatable(L, -2); return 1; } static gint luaH_string_wch_convert_case(lua_State *L, const char *key, gboolean upper) { guint kval = gdk_keyval_from_name(key); if (kval == GDK_KEY_VoidSymbol) { debug("unrecognized key symbol '%s'", key); lua_pushstring(L, key); return 1; } guint cased; gdk_keyval_convert_case(kval, upper ? NULL : &cased, upper ? &cased : NULL); luaH_keystr_push(L, cased); return 1; } static gint luaH_luakit_wch_lower(lua_State *L) { return luaH_string_wch_convert_case(L, luaL_checkstring(L, 1), FALSE); } static gint luaH_luakit_wch_upper(lua_State *L) { return luaH_string_wch_convert_case(L, luaL_checkstring(L, 1), TRUE); } static gint luaH_luakit_clear_favicon_database(lua_State *UNUSED(L)) { WebKitWebContext *ctx = web_context_get(); WebKitFaviconDatabase *fdb = webkit_web_context_get_favicon_database(ctx); webkit_favicon_database_clear(fdb); return 0; } static gint luaH_luakit_push_install_paths_table(lua_State *L) { lua_createtable(L, 0, 6); lua_pushliteral(L, LUAKIT_INSTALL_PATH); lua_setfield(L, -2, "install_dir"); lua_pushliteral(L, LUAKIT_CONFIG_PATH); lua_setfield(L, -2, "config_dir"); lua_pushliteral(L, LUAKIT_DOC_PATH); lua_setfield(L, -2, "doc_dir"); lua_pushliteral(L, LUAKIT_MAN_PATH); lua_setfield(L, -2, "man_dir"); lua_pushliteral(L, LUAKIT_PIXMAP_PATH); lua_setfield(L, -2, "pixmap_dir"); lua_pushliteral(L, LUAKIT_APP_PATH); lua_setfield(L, -2, "app_dir"); return 1; } /** luakit module index metamethod. * * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_luakit_index(lua_State *L) { if (luaH_usemetatable(L, 1, 2)) return 1; widget_t *w; const gchar *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); switch (token) { /* push string properties */ PS_CASE(CACHE_DIR, globalconf.cache_dir) PS_CASE(CONFIG_DIR, globalconf.config_dir) PS_CASE(DATA_DIR, globalconf.data_dir) PS_CASE(EXECPATH, globalconf.execpath) PS_CASE(CONFPATH, globalconf.confpath) PS_CASE(RESOURCE_PATH, resource_path_get()) /* push boolean properties */ PB_CASE(VERBOSE, log_get_verbosity("all") >= LOG_LEVEL_verbose) PB_CASE(NOUNIQUE, globalconf.nounique) PB_CASE(ENABLE_SPELL_CHECKING, webkit_web_context_get_spell_checking_enabled(web_context_get())) /* push integer properties */ PI_CASE(PROCESS_LIMIT, web_context_process_limit_get()) case L_TK_OPTIONS: return luaH_luakit_push_options_table(L); case L_TK_WEBSITE_DATA: return luaH_luakit_push_website_data_table(L); PB_CASE(WEBKIT2, true) case L_TK_WINDOWS: lua_newtable(L); for (guint i = 0; i < globalconf.windows->len; i++) { w = globalconf.windows->pdata[i]; luaH_object_push(L, w->ref); lua_rawseti(L, -2, i + 1); } return 1; case L_TK_WEBKIT_VERSION: lua_pushfstring(L, "%d.%d.%d", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); return 1; case L_TK_WEBKIT_USER_AGENT_VERSION: lua_pushfstring(L, "%d.%d", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION); return 1; case L_TK_SELECTION: return luaH_luakit_selection_table_push(L); case L_TK_INSTALL_PATH: warn("luakit.install_path is deprecated: use luakit.install_paths.install_dir instead"); lua_pushliteral(L, LUAKIT_INSTALL_PATH); return 1; case L_TK_INSTALL_PATHS: return luaH_luakit_push_install_paths_table(L); case L_TK_VERSION: lua_pushliteral(L, VERSION); return 1; case L_TK_DEV_PATHS: #ifdef DEVELOPMENT_PATHS lua_pushboolean(L, TRUE); #else lua_pushboolean(L, FALSE); #endif return 1; case L_TK_SPELL_CHECKING_LANGUAGES: luaH_push_strv(L, webkit_web_context_get_spell_checking_languages(web_context_get())); return 1; default: break; } return 0; } /** luakit module newindex metamethod. * * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_luakit_newindex(lua_State *L) { if (!lua_isstring(L, 2)) return 0; luakit_token_t token = l_tokenize(lua_tostring(L, 2)); switch (token) { case L_TK_PROCESS_LIMIT: if (!web_context_process_limit_set(lua_tointeger(L, 3))) return luaL_error(L, "Too late to set WebKit process limit"); break; case L_TK_ENABLE_SPELL_CHECKING: webkit_web_context_set_spell_checking_enabled(web_context_get(), luaH_checkboolean(L, 3)); break; case L_TK_SPELL_CHECKING_LANGUAGES: { const gchar ** langs = luaH_checkstrv(L, 3); WebKitWebContext *ctx = web_context_get(); webkit_web_context_set_spell_checking_languages(ctx, langs); const gchar * const * accepted = webkit_web_context_get_spell_checking_languages(ctx); for (const gchar ** lang = langs; *lang; lang++) if (!g_strv_contains(accepted, *lang)) warn("unrecognized language code '%s'", *lang); g_free(langs); break; } case L_TK_RESOURCE_PATH: resource_path_set(luaL_checkstring(L, 3)); break; default: return 0; } return 0; } /** Quit the main GTK loop. * \see http://developer.gnome.org/gtk/stable/gtk-General.html#gtk-main-quit * * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_luakit_quit(lua_State *UNUSED(L)) { if (gtk_main_level()) gtk_main_quit(); else exit(EXIT_SUCCESS); return 0; } /** Defined in widgets/webview.c */ void luakit_uri_scheme_request_cb(WebKitURISchemeRequest *, gpointer); static gint luaH_luakit_register_scheme(lua_State *L) { const gchar *scheme = luaL_checkstring(L, 1); if (g_str_equal(scheme, "")) return luaL_error(L, "scheme cannot be empty"); if (g_str_equal(scheme, "http") || g_str_equal(scheme, "https")) return luaL_error(L, "scheme cannot be 'http' or 'https'"); if (!g_regex_match_simple("^[a-z][a-z0-9\\+\\-\\.]*$", scheme, 0, 0)) return luaL_error(L, "scheme must match [a-z][a-z0-9\\+\\-\\.]*"); webkit_web_context_register_uri_scheme(web_context_get(), scheme, (WebKitURISchemeRequestCallback) luakit_uri_scheme_request_cb, g_strdup(scheme), g_free); return 0; } gint luaH_luakit_allow_certificate(lua_State *L) { const gchar *host = luaL_checkstring(L, 1); size_t len; const gchar *cert_pem = luaL_checklstring(L, 2, &len); GError *err = NULL; GTlsCertificate *cert = g_tls_certificate_new_from_pem(cert_pem, len, &err); if (err) { lua_pushnil(L); lua_pushstring(L, err->message); return 2; } WebKitWebContext *ctx = web_context_get(); webkit_web_context_allow_tls_certificate_for_host(ctx, cert, host); g_object_unref(G_OBJECT(cert)); lua_pushboolean(L, TRUE); return 1; } gint luaH_class_index_miss_property(lua_State *L, lua_object_t* UNUSED(obj)) { signal_object_emit(L, luakit_class.signals, "debug::index::miss", 2, 0); return 0; } gint luaH_class_newindex_miss_property(lua_State *L, lua_object_t* UNUSED(obj)) { signal_object_emit(L, luakit_class.signals, "debug::newindex::miss", 3, 0); return 0; } /** Setup luakit module. * * \param L The Lua VM state. */ void luakit_lib_setup(lua_State *L) { static const struct luaL_Reg luakit_lib[] = { LUA_CLASS_METHODS(luakit) LUAKIT_LIB_COMMON_METHODS { "__index", luaH_luakit_index }, { "__newindex", luaH_luakit_newindex }, { "exec", luaH_luakit_exec }, { "quit", luaH_luakit_quit }, { "save_file", luaH_luakit_save_file }, { "spawn", luaH_luakit_spawn }, { "spawn_sync", luaH_luakit_spawn_sync }, { "register_scheme", luaH_luakit_register_scheme }, { "allow_certificate", luaH_luakit_allow_certificate }, { "wch_lower", luaH_luakit_wch_lower }, { "wch_upper", luaH_luakit_wch_upper }, { "clear_favicon_database", luaH_luakit_clear_favicon_database }, { NULL, NULL } }; /* create signals array */ luakit_class.signals = signal_new(); /* export luakit lib */ luaH_openlib(L, "luakit", luakit_lib, luakit_lib); } lua_class_t * luakit_lib_get_luakit_class(void) { return &luakit_class; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/luakit.h000066400000000000000000000025271475363222200153750ustar00rootroot00000000000000/* * clib/luakit.h - Generic functions for Lua scripts * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_LUAKIT_H #define LUAKIT_CLIB_LUAKIT_H #include "common/luaclass.h" #include "common/ipc.h" #include void luakit_lib_setup(lua_State *L); void luaH_register_functions_on_endpoint(ipc_endpoint_t *ipc, lua_State *L); lua_class_t * luakit_lib_get_luakit_class(void); gint luaH_class_index_miss_property(lua_State *, lua_object_t *); gint luaH_class_newindex_miss_property(lua_State *, lua_object_t *); /* Referenced from deprecated webview:allow_certificate() */ gint luaH_luakit_allow_certificate(lua_State *L); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/msg.c000066400000000000000000000031351475363222200146610ustar00rootroot00000000000000/* * clib/msg.c - Lua logging interface * * Copyright © 2016 Aidan Holm * * 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 . * */ #include "clib/msg.h" #include "common/clib/msg.h" /* setup luakit module signals */ static lua_class_t msg_class; LUA_CLASS_FUNCS(msg, msg_class) lua_class_t * msg_lib_get_msg_class(void) { return &msg_class; } void msg_lib_setup(lua_State *L) { static const struct luaL_Reg msg_lib[] = { LUA_CLASS_METHODS(msg) #define X(name) \ { #name, luaH_msg_##name }, LOG_LEVELS #undef X { NULL, NULL } }; luaH_openlib(L, "msg", msg_lib, msg_lib); msg_class.signals = signal_new(); /* Store ref to string.format() */ lua_getglobal(L, "string"); lua_getfield(L, -1, "format"); string_format_ref = luaH_object_ref(L, -1); lua_pop(L, 1); /* Store ref to tostring() */ lua_getglobal(L, "tostring"); tostring_ref = luaH_object_ref(L, -1); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/msg.h000066400000000000000000000017231475363222200146670ustar00rootroot00000000000000/* * clib/msg.h - Lua logging interface * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_CLIB_MSG_H #define LUAKIT_CLIB_MSG_H #include #include "common/luaclass.h" void msg_lib_setup(lua_State *L); lua_class_t * msg_lib_get_msg_class(void); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/request.c000066400000000000000000000073761475363222200155760ustar00rootroot00000000000000/* * clib/request.c - wrapper for the WebKitURISchemeRequest class * * Copyright © 2016 Aidan Holm * * 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 . * */ #include "common/luaobject.h" #include "common/luauniq.h" #include "clib/request.h" #include "luah.h" #include "globalconf.h" #include "web_context.h" #include #include #define REG_KEY "luakit.uniq.registry.request" typedef struct { LUA_OBJECT_HEADER WebKitURISchemeRequest *request; gboolean finished; } request_t; static lua_class_t request_class; LUA_OBJECT_FUNCS(request_class, request_t, request) static request_t* luaH_check_request(lua_State *L, gint idx) { request_t *request = luaH_checkudata(L, idx, &(request_class)); if (request->finished) luaL_error(L, "request has already been finished"); return request; } gint luaH_request_push_uri_scheme_request(lua_State *L, WebKitURISchemeRequest *r) { if (luaH_uniq_get_ptr(L, REG_KEY, r)) return 1; request_t *request = request_new(L); request->request = g_object_ref(r); request->finished = FALSE; luaH_uniq_add_ptr(L, REG_KEY, r, -1); return 1; } static gint luaH_request_finish(lua_State *L) { request_t *request = luaH_check_request(L, 1); request->finished = TRUE; /* Argument errors cause direct termination of the request */ const gchar *error_message; if (!lua_isstring(L, 2)) { error_message = "data isn't a string"; goto error; } if (lua_type(L, 3) == LUA_TNONE) lua_pushnil(L); if ((lua_type(L, 3) != LUA_TSTRING) && (lua_type(L, 3) != LUA_TNIL)) { error_message = "MIME type isn't a string or nil"; goto error; } size_t length; const gchar *data = lua_tolstring(L, 2, &length); const gchar *mime = lua_tostring(L, 3) ?: "text/html"; GInputStream *stream = g_memory_input_stream_new_from_data(g_memdup2(data, length), length, g_free); webkit_uri_scheme_request_finish(request->request, stream, length, mime); g_object_unref(stream); return 0; error: g_assert(error_message); GError *error = g_error_new_literal(g_quark_from_static_string("luakit"), 0, error_message); webkit_uri_scheme_request_finish_error(request->request, error); return luaL_error(L, error_message); } static int luaH_request_get_finished(lua_State *L, request_t *request) { lua_pushboolean(L, request->finished); return 1; } void request_class_setup(lua_State *L) { static const struct luaL_Reg request_methods[] = { LUA_CLASS_METHODS(request) { NULL, NULL } }; static const struct luaL_Reg request_meta[] = { LUA_OBJECT_META(request) LUA_CLASS_META { "finish", luaH_request_finish }, { NULL, NULL }, }; luaH_class_setup(L, &request_class, "request", (lua_class_allocator_t) request_new, NULL, NULL, request_methods, request_meta); luaH_class_add_property(&request_class, L_TK_FINISHED, NULL, (lua_class_propfunc_t) luaH_request_get_finished, NULL); luaH_uniq_setup(L, REG_KEY, ""); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/request.h000066400000000000000000000022201475363222200155620ustar00rootroot00000000000000/* * clib/request.h - WebKitRequest wrapper header * * Copyright © 2016 Aidan Holm * * Copyright © 2011 Fabian Streitel * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_REQUEST_H #define LUAKIT_CLIB_REQUEST_H #include #include void request_class_setup(lua_State*); gint luaH_request_push_uri_scheme_request(lua_State*, WebKitURISchemeRequest*); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/soup.c000066400000000000000000000124421475363222200150620ustar00rootroot00000000000000/* * clib/soup.c - soup library * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "luah.h" #include "clib/soup.h" #include "common/property.h" #include "common/signal.h" #include "web_context.h" #include #include #include static lua_class_t soup_class; static gchar *proxy_uri; static gchar *accept_policy; static gchar *cookies_storage; LUA_CLASS_FUNCS(soup, soup_class); #include "common/clib/soup.h" static gint luaH_soup_index(lua_State *L) { const gchar *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); switch (token) { PS_CASE(PROXY_URI, proxy_uri) PS_CASE(ACCEPT_POLICY, accept_policy) PS_CASE(COOKIES_STORAGE, cookies_storage) default: break; } return 0; } static void luaH_soup_set_proxy_uri(lua_State *L) { WebKitWebContext *ctx = web_context_get(); WebKitWebsiteDataManager *dm = webkit_web_context_get_website_data_manager(ctx); const gchar *new_proxy_uri = lua_isnil(L, 3) ? "default" : luaL_checkstring(L, 3); g_free(proxy_uri); proxy_uri = g_strdup(new_proxy_uri); if (!proxy_uri || g_str_equal(proxy_uri, "default")) { webkit_website_data_manager_set_network_proxy_settings(dm, WEBKIT_NETWORK_PROXY_MODE_DEFAULT, NULL); } else if (g_str_equal(proxy_uri, "no_proxy")) { webkit_website_data_manager_set_network_proxy_settings(dm, WEBKIT_NETWORK_PROXY_MODE_NO_PROXY, NULL); } else { WebKitNetworkProxySettings *proxy_settings = webkit_network_proxy_settings_new(proxy_uri, NULL); webkit_website_data_manager_set_network_proxy_settings(dm, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxy_settings); webkit_network_proxy_settings_free(proxy_settings); } } static void luaH_soup_set_accept_policy(lua_State *L) { const gchar *new_policy = luaL_checkstring(L, 3); if (!g_str_equal(new_policy, "always")) if (!g_str_equal(new_policy, "never")) if (!g_str_equal(new_policy, "no_third_party")) luaL_error(L, "accept_policy must be one of 'always', 'never', 'no_third_party'"); g_free(accept_policy); accept_policy = g_strdup(new_policy); WebKitWebContext * web_context = web_context_get(); WebKitCookieManager *cookie_mgr = webkit_web_context_get_cookie_manager(web_context); WebKitCookieAcceptPolicy policy; if (g_str_equal(new_policy, "always")) policy = WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS; else if (g_str_equal(new_policy, "never")) policy = WEBKIT_COOKIE_POLICY_ACCEPT_NEVER; else if (g_str_equal(new_policy, "no_third_party")) policy = WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY; else g_assert_not_reached(); webkit_cookie_manager_set_accept_policy(cookie_mgr, policy); } static void luaH_soup_set_cookies_storage(lua_State *L) { const gchar *new_path = luaL_checkstring(L, 3); FILE *f; if (g_str_equal(new_path, "")) luaL_error(L, "cookies_storage cannot be empty"); g_free(cookies_storage); cookies_storage = g_strdup(new_path); if ((f = g_fopen(cookies_storage, "a")) != NULL) { g_chmod(cookies_storage, 0600); fclose(f); } WebKitWebContext * web_context = web_context_get(); WebKitCookieManager *cookie_mgr = webkit_web_context_get_cookie_manager(web_context); webkit_cookie_manager_set_persistent_storage(cookie_mgr, cookies_storage, WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); } static gint luaH_soup_newindex(lua_State *L) { const gchar *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); switch (token) { case L_TK_PROXY_URI: luaH_soup_set_proxy_uri(L); break; case L_TK_ACCEPT_POLICY: luaH_soup_set_accept_policy(L); break; case L_TK_COOKIES_STORAGE: luaH_soup_set_cookies_storage(L); break; default: return 0; } return 0; } void soup_lib_setup(lua_State *L) { soup_lib_setup_common(); static const struct luaL_Reg soup_lib[] = { LUA_CLASS_METHODS(soup) { "__index", luaH_soup_index }, { "__newindex", luaH_soup_newindex }, { "parse_uri", luaH_soup_parse_uri }, { "uri_tostring", luaH_soup_uri_tostring }, { NULL, NULL }, }; /* create signals array */ soup_class.signals = signal_new(); /* export soup lib */ luaH_openlib(L, "soup", soup_lib, soup_lib); /* Initial settings */ proxy_uri = g_strdup("default"); accept_policy = g_strdup("no_third_party"); /* Must match default set in web_context.c */ } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/soup.h000066400000000000000000000016171475363222200150710ustar00rootroot00000000000000/* * clib/soup.h - soup library * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_SOUP_H #define LUAKIT_CLIB_SOUP_H #include void soup_lib_setup(lua_State *L); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/sqlite3.c000066400000000000000000000307641475363222200154670ustar00rootroot00000000000000/* * clib/sqlite3.c - luakit sqlite3 wrapper * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "clib/sqlite3.h" #include "common/luaobject.h" #include "common/luaclass.h" #include "globalconf.h" #include /** Internal data structure for all Lua \c sqlite3 object instances. */ typedef struct { /** Common \ref lua_object_t header. \see LUA_OBJECT_HEADER */ LUA_OBJECT_HEADER /** \privatesection */ /** File path used to open the SQLite3 database connection handle. */ char *filename; /** Internal SQLite3 connection handle object. \see http://www.sqlite.org/c3ref/sqlite3.html */ sqlite3 *db; } sqlite3_t; typedef struct { sqlite3_t *sqlite; sqlite3_stmt *stmt; gpointer parent_ref; } sqlite3_stmt_t; #define luaH_checksqlite3(L, idx) luaH_checkudata(L, idx, &sqlite3_class) #define luaH_checkstmtud(L, idx) luaH_checkudata(L, idx, &sqlite3_stmt_class) static lua_class_t sqlite3_class, sqlite3_stmt_class; LUA_OBJECT_FUNCS(sqlite3_class, sqlite3_t, sqlite3) static inline void luaH_sqlite3_checkopen(lua_State *L, sqlite3_t *sqlite) { if (!sqlite->db) { lua_pushliteral(L, "sqlite3: database handle closed"); lua_error(L); } } static gint luaH_sqlite3_stmt_gc(lua_State *L) { sqlite3_stmt_t *stmt = luaH_checkstmtud(L, 1); /* release hold over parent sqlite3 object */ luaH_object_unref(L, stmt->parent_ref); sqlite3_finalize(stmt->stmt); return 0; } /* create userdata object for executing prepared/compiled SQL statements */ sqlite3_stmt_t* sqlite3_stmt_new(lua_State *L) { sqlite3_stmt_t *p = lua_newuserdata(L, sizeof(sqlite3_stmt_t)); p_clear(p, 1); luaH_settype(L, &sqlite3_stmt_class); lua_newtable(L); lua_newtable(L); lua_setmetatable(L, -2); lua_setfenv(L, -2); return p; } /* return compliled SQL statement userdata object */ static gint luaH_sqlite3_compile(lua_State *L) { sqlite3_t *sqlite = luaH_checksqlite3(L, 1); luaH_sqlite3_checkopen(L, sqlite); const gchar *sql = luaL_checkstring(L, 2); /* compile SQL statement */ const gchar *tail; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(sqlite->db, sql, -1, &stmt, &tail)) { lua_pushfstring(L, "sqlite3: statement compilation failed (%s)", sqlite3_errmsg(sqlite->db)); sqlite3_finalize(stmt); lua_error(L); } else if (!stmt) { lua_pushfstring(L, "sqlite3: no SQL found in string: \"%s\"", sql); lua_error(L); } /* create userdata object */ sqlite3_stmt_t *p = sqlite3_stmt_new(L); p->sqlite = sqlite; p->stmt = stmt; /* store reference to parent sqlite3 object to prevent it being collected * while a sqlite3_stmt object is still around */ p->parent_ref = luaH_object_ref(L, 1); /* sqlite3_prepare_v2 only compiles the first statement found in the sql * query, if there are several `tail` points to the first character of the * next query */ if (tail && *tail) { lua_pushstring(L, tail); return 2; } return 1; } static gint luaH_sqlite3_close(lua_State *L) { sqlite3_t *sqlite = luaH_checksqlite3(L, 1); if (sqlite->filename) { g_free(sqlite->filename); sqlite->filename = NULL; } if (sqlite->db) { sqlite3_close(sqlite->db); sqlite->db = NULL; } return 0; } static gint luaH_sqlite3_gc(lua_State *L) { luaH_sqlite3_close(L); return luaH_object_gc(L); } static gint luaH_sqlite3_set_filename(lua_State *L, sqlite3_t *sqlite) { const gchar *filename = luaL_checkstring(L, -1); /* open database */ if (sqlite3_open(filename, &sqlite->db)) { lua_pushfstring(L, "sqlite3: failed to open \"%s\" (%s)", filename, sqlite3_errmsg(sqlite->db)); sqlite3_close(sqlite->db); lua_error(L); } sqlite->filename = g_strdup(filename); return 0; } static gint luaH_sqlite3_get_filename(lua_State *L, sqlite3_t *sqlite) { lua_pushstring(L, sqlite->filename); return 1; } static gint luaH_sqlite3_changes(lua_State *L) { sqlite3_t *sqlite = luaH_checksqlite3(L, 1); luaH_sqlite3_checkopen(L, sqlite); lua_pushnumber(L, sqlite3_changes(sqlite->db)); return 1; } static gint luaH_param_index(lua_State *L, sqlite3_stmt *stmt, gint idx) { int type = lua_type(L, idx); if (type == LUA_TNUMBER) return lua_tointeger(L, idx); else if (type == LUA_TSTRING) return sqlite3_bind_parameter_index(stmt, lua_tostring(L, idx)); return 0; } static gint luaH_bind_value(lua_State *L, sqlite3_stmt *stmt, gint bidx, gint idx) { switch (lua_type(L, idx)) { case LUA_TNUMBER: return sqlite3_bind_double(stmt, bidx, lua_tonumber(L, idx)); case LUA_TBOOLEAN: return sqlite3_bind_int(stmt, bidx, lua_toboolean(L, idx) ? 1 : 0); case LUA_TSTRING: return sqlite3_bind_text(stmt, bidx, lua_tostring(L, idx), -1, SQLITE_TRANSIENT); default: warn("sqlite3: unable to bind Lua value (type %s)", lua_typename(L, lua_type(L, idx))); break; } return SQLITE_OK; /* ignore invalid types */ } static gint luaH_sqlite3_do_exec(lua_State *L, sqlite3_stmt *stmt) { gint ret = sqlite3_step(stmt), rows = 0, ncol = 0; /* determine if this statement returns rows */ if (ret == SQLITE_DONE || ret == SQLITE_ROW) { if ((ncol = sqlite3_column_count(stmt))) /* user will expect table even if SQLITE_DONE */ lua_newtable(L); else /* statement doesn't return rows */ lua_pushnil(L); } check_next_step: switch (ret) { case SQLITE_DONE: goto exec_done; case SQLITE_ROW: lua_newtable(L); for (gint i = 0; i < ncol; i++) { /* push column name */ lua_pushstring(L, sqlite3_column_name(stmt, i)); /* push column value */ switch (sqlite3_column_type(stmt, i)) { case SQLITE_INTEGER: case SQLITE_FLOAT: lua_pushnumber(L, sqlite3_column_double(stmt, i)); lua_rawset(L, -3); break; case SQLITE_BLOB: case SQLITE_TEXT: lua_pushstring(L, sqlite3_column_blob(stmt, i)); lua_rawset(L, -3); break; case SQLITE_NULL: default: lua_pop(L, 1); break; } } lua_rawseti(L, -2, ++rows); break; /* there was an error */ default: return -1; } /* process next row (or check if done) */ ret = sqlite3_step(stmt); goto check_next_step; exec_done: return 1; } static gint luaH_sqlite3_exec(lua_State *L) { sqlite3_t *sqlite = luaH_checksqlite3(L, 1); luaH_sqlite3_checkopen(L, sqlite); /* get SQL query */ const gchar *sql = luaL_checkstring(L, 2), *tail; /* check type before we prepare statement */ if (!lua_isnoneornil(L, 3)) luaH_checktable(L, 3); gint top = lua_gettop(L), ret = 0; /* compile SQL statement */ sqlite3_stmt *stmt; next_statement: if (sqlite3_prepare_v2(sqlite->db, sql, -1, &stmt, &tail)) { lua_pushfstring(L, "sqlite3: statement compilation failed (%s)", sqlite3_errmsg(sqlite->db)); sqlite3_finalize(stmt); lua_error(L); } else if (!stmt) return 0; /* is there values to bind to this statement? */ if (!lua_isnoneornil(L, 3)) { /* iterate through table and bind values to the compiled statement */ lua_pushnil(L); gint idx; while (lua_next(L, 3)) { /* check valid parameter index */ if ((idx = luaH_param_index(L, stmt, -2)) == 0) { lua_pop(L, 1); continue; } /* bind value at index */ ret = luaH_bind_value(L, stmt, idx, -1); /* check for sqlite3_bind_* error */ if (!(ret == SQLITE_OK || ret == SQLITE_RANGE)) { lua_pushfstring(L, "sqlite3: sqlite3_bind_* failed (%s)", sqlite3_errmsg(sqlite->db)); sqlite3_finalize(stmt); lua_error(L); } /* pop value */ lua_pop(L, 1); } } ret = luaH_sqlite3_do_exec(L, stmt); sqlite3_finalize(stmt); /* check for error */ if (ret == -1) { lua_pushfstring(L, "sqlite3: exec error (%s)", sqlite3_errmsg(sqlite->db)); lua_error(L); } /* the sqlite3_prepare_*() functions only compile the first statement * in the input string. If there are more `tail` points to the first * character of the next statement (valid or not). */ if (tail && *tail) { sql = tail; lua_settop(L, top); goto next_statement; } return 1; } static gint luaH_sqlite3_stmt_exec(lua_State *L) { sqlite3_stmt_t *stmt = luaH_checkstmtud(L, 1); sqlite3_t *sqlite = stmt->sqlite; luaH_sqlite3_checkopen(L, sqlite); /* reset prepared statement back to original state */ sqlite3_reset(stmt->stmt); gint ret; /* is there values to bind to this statement? */ if (!lua_isnoneornil(L, 2)) { luaH_checktable(L, 2); /* clear bound values */ sqlite3_clear_bindings(stmt->stmt); /* iterate through table and bind values to the compiled statement */ lua_pushnil(L); gint idx; while (lua_next(L, 2)) { /* check valid parameter index */ if ((idx = luaH_param_index(L, stmt->stmt, -2)) == 0) { lua_pop(L, 1); continue; } /* bind value at index */ ret = luaH_bind_value(L, stmt->stmt, idx, -1); /* check for sqlite3_bind_* error */ if (!(ret == SQLITE_OK || ret == SQLITE_RANGE)) { lua_pushfstring(L, "sqlite3: sqlite3_bind_* failed (%s)", sqlite3_errmsg(sqlite->db)); sqlite3_finalize(stmt->stmt); lua_error(L); } /* pop value */ lua_pop(L, 1); } } ret = luaH_sqlite3_do_exec(L, stmt->stmt); /* check for error */ if (ret == -1) { lua_pushfstring(L, "sqlite3: exec error (%s)", sqlite3_errmsg(sqlite->db)); lua_error(L); } return 1; } static gint luaH_sqlite3_new(lua_State *L) { luaH_class_new(L, &sqlite3_class); sqlite3_t *sqlite = luaH_checksqlite3(L, -1); if (!sqlite->db) { lua_pushliteral(L, "sqlite3: database not opened, forgot filename?"); lua_error(L); } return 1; } void sqlite3_class_setup(lua_State *L) { static const struct luaL_Reg sqlite3_methods[] = { LUA_CLASS_METHODS(sqlite3) { "__call", luaH_sqlite3_new }, { NULL, NULL }, }; static const struct luaL_Reg sqlite3_meta[] = { LUA_OBJECT_META(sqlite3) LUA_CLASS_META { "exec", luaH_sqlite3_exec }, { "close", luaH_sqlite3_close }, { "compile", luaH_sqlite3_compile }, { "changes", luaH_sqlite3_changes }, { "__gc", luaH_sqlite3_gc }, { NULL, NULL }, }; luaH_class_setup(L, &sqlite3_class, "sqlite3", (lua_class_allocator_t) sqlite3_new, NULL, NULL, sqlite3_methods, sqlite3_meta); luaH_class_add_property(&sqlite3_class, L_TK_FILENAME, (lua_class_propfunc_t) luaH_sqlite3_set_filename, (lua_class_propfunc_t) luaH_sqlite3_get_filename, NULL); static const struct luaL_Reg sqlite3_stmt_meta[] = { { "exec", luaH_sqlite3_stmt_exec }, { "__gc", luaH_sqlite3_stmt_gc }, { NULL, NULL }, }; luaH_class_setup(L, &sqlite3_stmt_class, "sqlite3::statement", NULL, NULL, NULL, NULL, sqlite3_stmt_meta); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/sqlite3.h000066400000000000000000000016451475363222200154700ustar00rootroot00000000000000/* * clib/sqlite3.h - luakit sqlite3 wrapper * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_SQLITE3_H #define LUAKIT_CLIB_SQLITE3_H #include void sqlite3_class_setup(lua_State*); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/stylesheet.c000066400000000000000000000074661475363222200162770ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "clib/luakit.h" #include "clib/widget.h" #include "clib/stylesheet.h" #include "globalconf.h" static lua_class_t stylesheet_class; LUA_OBJECT_FUNCS(stylesheet_class, lstylesheet_t, stylesheet) gpointer luaH_checkstylesheet(lua_State *L, gint idx) { return luaH_checkudata(L, idx, &(stylesheet_class)); } /* Defined in widgets/webview/stylesheets.c */ int webview_stylesheet_set_enabled(widget_t *w, lstylesheet_t *stylesheet, gboolean enable); static gint luaH_stylesheet_gc(lua_State *L) { lstylesheet_t *stylesheet = luaH_checkstylesheet(L, 1); if (stylesheet->stylesheet && globalconf.webviews) { /* Need to remove stylesheet from all webviews */ for (unsigned i=0; ilen; i++) { widget_t *w = g_ptr_array_index(globalconf.webviews, i); webview_stylesheet_set_enabled(w, stylesheet, FALSE); } webkit_user_style_sheet_unref(stylesheet->stylesheet); } g_free (stylesheet->source); return luaH_object_gc(L); } static gint luaH_stylesheet_new(lua_State *L) { luaH_class_new(L, &stylesheet_class); return 1; } static void regenerate_stylesheet(lstylesheet_t *stylesheet) { WebKitUserStyleSheet *old = stylesheet->stylesheet; if (old) webkit_user_style_sheet_unref(old); stylesheet->stylesheet = webkit_user_style_sheet_new(stylesheet->source, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL); if (old && globalconf.webviews) { /* Any web views which had this stylesheet enabled need to be regenerated */ for (unsigned i=0; ilen; i++) { widget_t *w = g_ptr_array_index(globalconf.webviews, i); webview_stylesheets_regenerate_stylesheet(w, stylesheet); } } } static int luaH_stylesheet_set_source(lua_State *L, lstylesheet_t *stylesheet) { const gchar *new_source = luaL_checkstring(L, -1); g_free(stylesheet->source); stylesheet->source = g_strdup(new_source); regenerate_stylesheet(stylesheet); return 0; } static int luaH_stylesheet_get_source(lua_State *L, lstylesheet_t *stylesheet) { lua_pushstring(L, stylesheet->source); return 1; } void stylesheet_class_setup(lua_State *L) { static const struct luaL_Reg stylesheet_methods[] = { LUA_CLASS_METHODS(stylesheet) { "__call", luaH_stylesheet_new }, { NULL, NULL } }; static const struct luaL_Reg stylesheet_meta[] = { LUA_OBJECT_META(stylesheet) LUA_CLASS_META { "__gc", luaH_stylesheet_gc }, { NULL, NULL }, }; luaH_class_setup(L, &stylesheet_class, "stylesheet", (lua_class_allocator_t) stylesheet_new, luaH_class_index_miss_property, luaH_class_newindex_miss_property, stylesheet_methods, stylesheet_meta); luaH_class_add_property(&stylesheet_class, L_TK_SOURCE, (lua_class_propfunc_t) luaH_stylesheet_set_source, (lua_class_propfunc_t) luaH_stylesheet_get_source, (lua_class_propfunc_t) luaH_stylesheet_set_source); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/stylesheet.h000066400000000000000000000023701475363222200162710ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_CLIB_STYLESHEET_H #define LUAKIT_CLIB_STYLESHEET_H #include "common/luaobject.h" #include #include #include typedef struct { LUA_OBJECT_HEADER WebKitUserStyleSheet *stylesheet; gchar *source; } lstylesheet_t; void stylesheet_class_setup(lua_State *); gpointer luaH_checkstylesheet(lua_State *L, gint idx); /* Declared in widgets/webview/stylesheets.c */ void webview_stylesheets_regenerate_stylesheet(widget_t *w, lstylesheet_t *stylesheet); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/unique.c000066400000000000000000000156021475363222200154030ustar00rootroot00000000000000/* * clib/unique.c - libunique bindings for writing single instance * applications if built against GTK2, and the equivalent using * GApplications if built against GTK3. * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "clib/unique.h" #include "globalconf.h" #include "luah.h" #include #include /* setup unique module signals */ static lua_class_t unique_class; LUA_CLASS_FUNCS(unique, unique_class); static void message_cb(GSimpleAction* UNUSED(a), GVariant *message_data, lua_State *L) { if (message_data && g_variant_is_of_type(message_data, G_VARIANT_TYPE_STRING)) { const gchar *text = g_variant_get_string (message_data, NULL); lua_pushstring(L, text); GtkWindow *window = gtk_application_get_active_window(globalconf.application); if (!window) warn("It's not a window!!!"); GdkScreen *screen = gtk_window_get_screen(window); lua_pushlightuserdata(L, screen); signal_object_emit(L, unique_class.signals, "message", 2, 0); } } static gboolean unique_is_registered(void) { if (!globalconf.application) return FALSE; if (!g_application_get_is_registered(G_APPLICATION(globalconf.application))) return FALSE; return TRUE; } static gint luaH_unique_new(lua_State *L) { const gchar *name = luaL_checkstring(L, 1); if (!g_application_id_is_valid(name)) return luaL_error(L, "invalid application name"); if (unique_is_registered()) { const gchar *other = g_application_get_application_id( G_APPLICATION(globalconf.application)); if (!g_str_equal(name, other)) luaL_error(L, "GApplication '%s' already setup", other); else verbose("GApplication '%s' already setup", name); return 0; } GError *error = NULL; if (!globalconf.application) { #if GLIB_CHECK_VERSION(2,74,0) globalconf.application = gtk_application_new(name, G_APPLICATION_DEFAULT_FLAGS); #else globalconf.application = gtk_application_new(name, G_APPLICATION_FLAGS_NONE); #endif } g_application_register(G_APPLICATION(globalconf.application), NULL, &error); if (error != NULL) { luaL_error(L, "unable to register GApplication: %s", error->message); g_error_free(error); g_object_unref(G_OBJECT(globalconf.application)); globalconf.application = NULL; return 0; } const GActionEntry entries[] = {{ .name = "message", .activate = (void (*) (GSimpleAction *, GVariant *, gpointer)) message_cb, .parameter_type = "s" }}; g_action_map_add_action_entries (G_ACTION_MAP(globalconf.application), entries, G_N_ELEMENTS(entries), L); return 0; } static gint luaH_unique_is_running(lua_State *L) { if (!unique_is_registered()) luaL_error(L, "GApplication is not registered"); gboolean running = g_application_get_is_remote(G_APPLICATION(globalconf.application)); lua_pushboolean(L, running); return 1; } static gint luaH_unique_send_message(lua_State *L) { if (!unique_is_registered()) luaL_error(L, "GApplication is not registered"); if (!g_application_get_is_remote(G_APPLICATION(globalconf.application))) luaL_error(L, "no other instances running"); GVariant *text = g_variant_new_string(luaL_checkstring(L, 1)); g_action_group_activate_action(G_ACTION_GROUP(globalconf.application), "message", text); return 0; } static void luaH_open_luakit_unique(lua_State *L, const struct luaL_Reg methods[], const struct luaL_Reg meta[]) { luaL_newmetatable(L, "unique"); /* 1 */ lua_pushvalue(L, -1); /* dup metatable 2 */ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable 1 */ luaL_register(L, NULL, meta); /* 1 */ /* No point checking for package.loaded["luakit.unique"] here */ lua_newtable(L); luaL_register(L, NULL, methods); /* Set package.loaded["luakit.unique"] */ lua_getfield(L, LUA_GLOBALSINDEX, "package"); lua_getfield(L, -1, "loaded"); lua_pushvalue(L, -3); lua_setfield(L, -2, "luakit.unique"); lua_pop(L, 2); /* Set luakit.unique: rawset since lib has an __index metamethod */ lua_getfield(L, LUA_GLOBALSINDEX, "luakit"); lua_pushliteral(L, "unique"); lua_pushvalue(L, -3); lua_rawset(L, -3); lua_pop(L, 1); lua_pushvalue(L, -1); /* dup self as metatable 3 */ lua_setmetatable(L, -2); /* set self as metatable 2 */ lua_pop(L, 2); } static gboolean warned = FALSE; static int luaH_unique_proxy_index(lua_State *L) { if (!warned) { warned = TRUE; warn("the unique library has been moved to luakit.unique"); warn("this compatibility wrapper will be removed in a future version"); warn("you should remove the two `if unique then ... end` blocks from your rc.lua"); warn("then, at the start of your rc.lua, add `require \"unique_instance\"`"); } lua_getfield(L, LUA_GLOBALSINDEX, "luakit"); lua_getfield(L, -1, "unique"); lua_pushvalue(L, 2); lua_gettable(L, -2); return 1; } static void luaH_open_unique_proxy(lua_State *L) { /* Construct proxy metatable */ lua_newtable(L); lua_newtable(L); lua_pushcfunction(L, &luaH_unique_proxy_index); lua_setfield(L, -2, "__index"); lua_setmetatable(L, -2); /* Set package.loaded["unique"] */ lua_getfield(L, LUA_GLOBALSINDEX, "package"); lua_getfield(L, -1, "loaded"); lua_pushvalue(L, -3); lua_setfield(L, -2, "unique"); lua_pop(L, 2); /* Set luakit.unique: rawset since lib has an __index metamethod */ lua_setglobal(L, "unique"); } void unique_lib_setup(lua_State *L) { static const struct luaL_Reg unique_lib[] = { LUA_CLASS_METHODS(unique) { "new", luaH_unique_new }, { "send_message", luaH_unique_send_message }, { "is_running", luaH_unique_is_running }, { NULL, NULL } }; /* create signals array */ unique_class.signals = signal_new(); luaH_open_luakit_unique(L, unique_lib, unique_lib); luaH_open_unique_proxy(L); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/unique.h000066400000000000000000000017461475363222200154140ustar00rootroot00000000000000/* * clib/unique.c - libunique bindings for writing single instance * applications * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_UNIQUE_H #define LUAKIT_CLIB_UNIQUE_H #include void unique_lib_setup(lua_State*); #endif /* #if LUAKIT_CLIB_UNIQUE_H */ // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/web_module.c000066400000000000000000000034301475363222200162130ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "clib/web_module.h" #include "common/clib/ipc.h" static GPtrArray *required_web_modules; static int luaH_require_web_module(lua_State *L) { const char *name = luaL_checkstring(L, -1); g_ptr_array_add(required_web_modules, g_strdup(name)); /* Return an IPC channel with the same name for convenience */ return luaH_ipc_channel_new(L); } void web_module_load_modules_on_endpoint(ipc_endpoint_t *ipc) { for (unsigned i = 0; i < required_web_modules->len; i++) { const gchar *module_name = required_web_modules->pdata[i]; ipc_header_t header = { .type = IPC_TYPE_lua_require_module, .length = strlen(module_name)+1 }; ipc_send(ipc, &header, module_name); } } void web_module_lib_setup(lua_State *L) { static const struct luaL_Reg web_module_methods[] = { { "__call", luaH_require_web_module }, { NULL, NULL } }; luaH_openlib(L, "require_web_module", web_module_methods, web_module_methods); required_web_modules = g_ptr_array_new(); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/web_module.h000066400000000000000000000016741475363222200162300ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_CLIB_WEB_MODULE_H #define LUAKIT_CLIB_WEB_MODULE_H #include "common/ipc.h" void web_module_lib_setup(lua_State *); void web_module_load_modules_on_endpoint(ipc_endpoint_t *ipc); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/widget.c000066400000000000000000000201451475363222200153560ustar00rootroot00000000000000/* * clib/widget.c - widget managing * * Copyright © 2010 Mason Larobina * Copyright © 2007-2009 Julien Danjou * * 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 . * */ #include "clib/widget.h" #include "common/property.h" static property_t widget_properties[] = { { L_TK_MARGIN, "margin", INT, TRUE }, { L_TK_MARGIN_TOP, "margin-top", INT, TRUE }, { L_TK_MARGIN_BOTTOM, "margin-bottom", INT, TRUE }, { L_TK_MARGIN_LEFT, "margin-left", INT, TRUE }, { L_TK_MARGIN_RIGHT, "margin-right", INT, TRUE }, { L_TK_CAN_FOCUS, "can-focus", BOOL, TRUE }, { 0, NULL, 0, 0 }, }; static widget_info_t widgets_list[] = { { L_TK_ENTRY, "entry", widget_entry }, { L_TK_EVENTBOX, "eventbox", widget_eventbox }, { L_TK_HBOX, "hbox", widget_box }, { L_TK_HPANED, "hpaned", widget_paned }, { L_TK_LABEL, "label", widget_label }, { L_TK_NOTEBOOK, "notebook", widget_notebook }, { L_TK_VBOX, "vbox", widget_box }, { L_TK_VPANED, "vpaned", widget_paned }, { L_TK_WEBVIEW, "webview", widget_webview }, { L_TK_WINDOW, "window", widget_window }, { L_TK_OVERLAY, "overlay", widget_overlay }, { L_TK_SCROLLED, "scrolled", widget_scrolled }, { L_TK_IMAGE, "image", widget_image }, { L_TK_SPINNER, "spinner", widget_spinner }, { L_TK_DRAWING_AREA, "drawing_area", widget_drawing_area }, { L_TK_STACK, "stack", widget_stack }, }; LUA_OBJECT_FUNCS(widget_class, widget_t, widget); /** Collect a widget structure. * \param L The Lua VM state. * \return 0 */ static gint luaH_widget_gc(lua_State *L) { widget_t *w = luaH_checkudata(L, 1, &widget_class); if (w->info) debug("collecting widget at %p of type '%s'", w, w->info->name); g_assert(!w->destructor); return luaH_object_gc(L); } /** Create a new widget. * \param L The Lua VM state. * * \luastack * \lparam A table with at least a type value. * \lreturn A brand new widget. */ gint luaH_widget_new(lua_State *L) { luaH_class_new(L, &widget_class); widget_t *w = lua_touserdata(L, -1); if (!w->info) { lua_pop(L, 1); /* Allow garbage collection */ luaL_error(L, "widget does not have a type"); } /* save ref to the lua class instance */ lua_pushvalue(L, -1); w->ref = luaH_object_ref_class(L, -1, &widget_class); return 1; } #if GTK_CHECK_VERSION(3,16,0) static inline void widget_set_css(widget_t *w, const gchar *properties) { gchar *old_css = gtk_css_provider_to_string(w->provider); gchar *css = g_strdup_printf("%s\n#widget { %s }", old_css, properties); gtk_css_provider_load_from_data(w->provider, css, strlen(css), NULL); g_free(css); g_free(old_css); } void widget_set_css_properties(widget_t *w, ...) { va_list argp; va_start(argp, w); gchar *css = g_strdup(""); const gchar *prop; while ((prop = va_arg(argp, gchar *))) { const gchar *value = va_arg(argp, gchar *); g_assert(strlen(prop) > 0); if (!value || strlen(value) == 0) continue; gchar *tmp = css; css = g_strdup_printf("%s%s: %s;", css, prop, value); g_free(tmp); } va_end(argp); widget_set_css(w, css); g_free(css); } #endif /** Generic widget. * \param L The Lua VM state. * \return The number of elements pushed on stack. * \luastack * \lfield visible The widget visibility. * \lfield mouse_enter A function to execute when the mouse enter the widget. * \lfield mouse_leave A function to execute when the mouse leave the widget. */ static gint luaH_widget_index(lua_State *L) { const char *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); /* Try standard method */ if(luaH_class_index(L)) return 1; if (token == L_TK_IS_ALIVE) { widget_t *w = luaH_checkudata(L, 1, &widget_class); lua_pushboolean(L, !!w); return 1; } /* Then call special widget index */ gint ret; widget_t *widget = luaH_checkwidget(L, 1); /* but only if it's not a GtkWidget property */ if ((ret = luaH_gobject_index(L, widget_properties, token, G_OBJECT(widget->widget)))) { return ret; } return widget->index ? widget->index(L, widget, token) : 0; } /** Generic widget newindex. * \param L The Lua VM state. * \return The number of elements pushed on stack. */ static gint luaH_widget_newindex(lua_State *L) { const char *prop = luaL_checkstring(L, 2); luakit_token_t token = l_tokenize(prop); /* Try standard method */ luaH_class_newindex(L); /* Then call special widget newindex */ widget_t *widget = luaH_checkwidget(L, 1); #if GTK_CHECK_VERSION(3,16,0) if (token == L_TK_CSS) { widget_set_css(widget, luaL_checkstring(L, 3)); return 0; } #endif /* but only if it's not a GtkWidget property */ gboolean emit = luaH_gobject_newindex(L, widget_properties, token, 3, G_OBJECT(widget->widget)); if (emit) return luaH_object_property_signal(L, 1, token); return widget->newindex ? widget->newindex(L, widget, token) : 0; } static gint luaH_widget_set_type(lua_State *L, widget_t *w) { if (w->info) luaL_error(L, "widget is already of type: %s", w->info->name); const gchar *type = luaL_checkstring(L, -1); luakit_token_t tok = l_tokenize(type); widget_info_t *winfo; #if GTK_CHECK_VERSION(3,16,0) w->provider = gtk_css_provider_new(); #endif for (guint i = 0; i < LENGTH(widgets_list); i++) { if (widgets_list[i].tok != tok) continue; winfo = &widgets_list[i]; w->info = winfo; winfo->wc(L, w, tok); #if GTK_CHECK_VERSION(3,16,0) gtk_widget_set_name(GTK_WIDGET(w->widget), "widget"); GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(w->widget)); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(w->provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); #endif /* store pointer to lua widget struct in gobject data */ g_object_set_data(G_OBJECT(w->widget), GOBJECT_LUAKIT_WIDGET_DATA_KEY, (gpointer)w); verbose("created widget of type: %s", w->info->name); lua_pushvalue(L, -3); luaH_class_emit_signal(L, &widget_class, "create", 1, 0); return 0; } luaL_error(L, "unknown widget type: %s", type); return 0; } static gint luaH_widget_get_type(lua_State *L, widget_t *w) { if (!w->info) return 0; luaH_checkwidget(L, 1); lua_pushstring(L, w->info->name); return 1; } void widget_class_setup(lua_State *L) { static const struct luaL_Reg widget_methods[] = { LUA_CLASS_METHODS(widget) { "__call", luaH_widget_new }, { NULL, NULL } }; static const struct luaL_Reg widget_meta[] = { LUA_OBJECT_META(widget) { "__index", luaH_widget_index }, { "__newindex", luaH_widget_newindex }, { "__gc", luaH_widget_gc }, { NULL, NULL } }; luaH_class_setup(L, &widget_class, "widget", (lua_class_allocator_t) widget_new, NULL, NULL, widget_methods, widget_meta); luaH_class_add_property(&widget_class, L_TK_TYPE, (lua_class_propfunc_t) luaH_widget_set_type, (lua_class_propfunc_t) luaH_widget_get_type, NULL); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/widget.h000066400000000000000000000065621475363222200153720ustar00rootroot00000000000000/* * clib/widget.h - widget managing header * * Copyright © 2010 Mason Larobina * Copyright © 2007-2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_CLIB_WIDGET_H #define LUAKIT_CLIB_WIDGET_H typedef struct widget_t widget_t; #include "common/util.h" #include "common/luaclass.h" #include "common/luaobject.h" #include "luah.h" #include "globalconf.h" #include #define GOBJECT_LUAKIT_WIDGET_DATA_KEY "luakit_widget_data" #define GOBJECT_TO_LUAKIT_WIDGET(gtk_widget) ((widget_t*)g_object_get_data(G_OBJECT(gtk_widget), \ GOBJECT_LUAKIT_WIDGET_DATA_KEY)) typedef widget_t *(widget_constructor_t)(lua_State *L, widget_t *, luakit_token_t); typedef void (widget_destructor_t)(widget_t *); widget_constructor_t widget_box; widget_constructor_t widget_entry; widget_constructor_t widget_eventbox; widget_constructor_t widget_label; widget_constructor_t widget_notebook; widget_constructor_t widget_paned; widget_constructor_t widget_webview; widget_constructor_t widget_window; widget_constructor_t widget_overlay; widget_constructor_t widget_scrolled; widget_constructor_t widget_image; widget_constructor_t widget_spinner; widget_constructor_t widget_drawing_area; widget_constructor_t widget_stack; typedef const struct { luakit_token_t tok; const gchar *name; widget_constructor_t *wc; } widget_info_t; /* Widget */ struct widget_t { LUA_OBJECT_HEADER /* Widget type information */ widget_info_t *info; /* Widget destructor */ widget_destructor_t *destructor; /* Index function */ gint (*index)(lua_State *, widget_t *, luakit_token_t); /* Newindex function */ gint (*newindex)(lua_State *, widget_t *, luakit_token_t); /* Lua object ref */ gpointer ref; /* Main gtk widget */ GtkWidget *widget; #if GTK_CHECK_VERSION(3,16,0) /* CSS provider for this widget */ GtkCssProvider *provider; #endif /* Previous width and height, for resize signal */ gint prev_width, prev_height; /* Misc private data */ gpointer data; }; extern lua_class_t widget_class; void widget_class_setup(lua_State *); void widget_set_css_properties(widget_t *, ...); gint luaH_widget_new(lua_State *L); static inline widget_t* luaH_checkwidget(lua_State *L, gint udx) { widget_t *w = luaH_checkudata(L, udx, &widget_class); if (!w->widget) luaL_error(L, "widget %p (%s) has been destroyed", w, w->info->name); g_assert(GTK_IS_WIDGET(w->widget)); return w; } static inline widget_t* luaH_checkwidgetornil(lua_State *L, gint udx) { if (lua_isnil(L, udx)) return NULL; return luaH_checkwidget(L, udx); } #define luaH_towidget(L, udx) luaH_toudata(L, udx, &widget_class) #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/xdg.c000066400000000000000000000052761475363222200146650ustar00rootroot00000000000000/* * clib/xdg.c - XDG Base Directory Specification paths * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "clib/xdg.h" #include "luah.h" static void str_chomp_slashes(gchar *path) { if (!path) return; gint last = strlen(path) - 1; while (last > 0 && path[last] == '/') path[last--] = '\0'; } static int luaH_push_path(lua_State *L, const gchar *path) { gchar *p = g_strdup(path); str_chomp_slashes(p); lua_pushstring(L, p); g_free(p); return 1; } static int luaH_push_path_array(lua_State *L, const gchar * const * paths) { lua_newtable(L); for (gint n = 0; paths[n]; ++n) { luaH_push_path(L, paths[n]); lua_rawseti(L, -2, n+1); } return 1; } static gint luaH_xdg_index(lua_State *L) { if (!lua_isstring(L, 2)) /* ignore non-string indexes */ return 0; switch(l_tokenize(lua_tostring(L, 2))) { #define PP_CASE(t, s) case L_TK_##t: luaH_push_path(L, s); return 1; PP_CASE(CACHE_DIR, g_get_user_cache_dir()) PP_CASE(CONFIG_DIR, g_get_user_config_dir()) PP_CASE(DATA_DIR, g_get_user_data_dir()) #define UD_CASE(TOK) \ case L_TK_##TOK##_DIR: \ luaH_push_path(L, g_get_user_special_dir(G_USER_DIRECTORY_##TOK)); \ return 1; UD_CASE(DESKTOP) UD_CASE(DOCUMENTS) UD_CASE(DOWNLOAD) UD_CASE(MUSIC) UD_CASE(PICTURES) UD_CASE(PUBLIC_SHARE) UD_CASE(TEMPLATES) UD_CASE(VIDEOS) case L_TK_SYSTEM_DATA_DIRS: return luaH_push_path_array(L, g_get_system_data_dirs()); case L_TK_SYSTEM_CONFIG_DIRS: return luaH_push_path_array(L, g_get_system_config_dirs()); default: break; } return 0; } void xdg_lib_setup(lua_State *L) { static const struct luaL_Reg xdg_lib[] = { { "__index", luaH_xdg_index }, { NULL, NULL }, }; luaH_openlib(L, "xdg", xdg_lib, xdg_lib); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/clib/xdg.h000066400000000000000000000016431475363222200146640ustar00rootroot00000000000000/* * clib/xdg.h - XDG Base Directory Specification paths * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_CLIB_XDG_H #define LUAKIT_CLIB_XDG_H #include void xdg_lib_setup(lua_State*); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/000077500000000000000000000000001475363222200143045ustar00rootroot00000000000000luakit-2.4.0/common/clib/000077500000000000000000000000001475363222200152155ustar00rootroot00000000000000luakit-2.4.0/common/clib/ipc.c000066400000000000000000000047061475363222200161430ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include "common/clib/ipc.h" #include "common/ipc.h" #include "luah.h" #include "common/tokenize.h" #include "common/luaserialize.h" #include "common/luauniq.h" #define REG_KEY "luakit.registry.ipc_channel" static lua_class_t ipc_channel_class; LUA_OBJECT_FUNCS(ipc_channel_class, ipc_channel_t, ipc_channel); ipc_channel_t * luaH_check_ipc_channel(lua_State *L, gint idx) { return luaH_checkudata(L, idx, &ipc_channel_class); } int luaH_ipc_channel_new(lua_State *L) { const char *name = luaL_checkstring(L, -1); if (luaH_uniq_get(L, REG_KEY, -1)) return 1; lua_newtable(L); luaH_class_new(L, &ipc_channel_class); lua_remove(L, -2); ipc_channel_t *ipc_channel = luaH_check_ipc_channel(L, -1); ipc_channel->name = g_strdup(name); luaH_uniq_add(L, REG_KEY, -2, -1); return 1; } static gint luaH_ipc_channel_gc(lua_State *L) { ipc_channel_t *ipc_channel = luaH_check_ipc_channel(L, -1); g_free(ipc_channel->name); return luaH_object_gc(L); } void ipc_channel_class_setup(lua_State *L) { static const struct luaL_Reg ipc_channel_methods[] = { LUA_CLASS_METHODS(ipc_channel) { "__call", luaH_ipc_channel_new }, { NULL, NULL } }; static const struct luaL_Reg ipc_channel_meta[] = { LUA_OBJECT_META(ipc_channel) { "emit_signal", ipc_channel_send }, { "__gc", luaH_ipc_channel_gc }, { NULL, NULL } }; luaH_class_setup(L, &ipc_channel_class, "ipc_channel", (lua_class_allocator_t) ipc_channel_new, NULL, NULL, ipc_channel_methods, ipc_channel_meta); lua_pushstring(L, REG_KEY); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/ipc.h000066400000000000000000000025171475363222200161460ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_IPC_H #define LUAKIT_COMMON_CLIB_IPC_H #include #include #include "common/util.h" #include "common/luaclass.h" #include "common/luaobject.h" typedef struct _ipc_channel_t { LUA_OBJECT_HEADER char *name; } ipc_channel_t; ipc_channel_t *luaH_check_ipc_channel(lua_State *L, gint idx); gint luaH_ipc_channel_new(lua_State *L); gint ipc_channel_send(lua_State *L); void ipc_channel_recv(lua_State *L, const gchar *arg, guint arglen); void ipc_channel_set_module(lua_State *L, const gchar *module_name); void ipc_channel_class_setup(lua_State *); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/luakit.c000066400000000000000000000120141475363222200166500ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include #include #include #include #include "common/clib/luakit.h" #include "common/util.h" #include "common/luaobject.h" /** Get seconds from unix epoch with nanosecond precision (or nearest * supported by the users system). * \see http://www.kernel.org/doc/man-pages/online/pages/man2/clock_gettime.2.html * * \param L The Lua VM state. * \return The number of elements pushed on the stack (1). */ gint luaH_luakit_time(lua_State *L) { lua_pushnumber(L, l_time()); return 1; } /** Escapes a string for use in a URI. * \see http://developer.gnome.org/glib/stable/glib-URI-Functions.html#g-uri-escape-string * * \param L The Lua VM state. * \return The number of elements pushed on stack. * * \luastack * \lparam string The string to escape for use in a URI. * \lparam allowed Optional string of allowed characters to leave unescaped in * the \c string. * \lreturn The escaped string. */ gint luaH_luakit_uri_encode(lua_State *L) { const gchar *string = luaL_checkstring(L, 1); const gchar *allowed = NULL; /* get list of reserved characters that are allowed in the string */ if (1 < lua_gettop(L) && !lua_isnil(L, 2)) allowed = luaL_checkstring(L, 2); gchar *res = g_uri_escape_string(string, allowed, true); lua_pushstring(L, res); g_free(res); return 1; } /** Unescapes an escaped string used in a URI. * \see http://developer.gnome.org/glib/stable/glib-URI-Functions.html#g-uri-unescape-string * * \param L The Lua VM state. * \return The number of elements pushed on stack. * * \luastack * \lparam string The string to unescape. * \lparam illegal Optional string of illegal chars which should not appear in * the unescaped string. * \lreturn The unescaped string or \c nil if illegal chars found. */ gint luaH_luakit_uri_decode(lua_State *L) { const gchar *string = luaL_checkstring(L, 1); const gchar *illegal = NULL; /* get list of illegal chars not to be found in the unescaped string */ if (1 < lua_gettop(L) && !lua_isnil(L, 2)) illegal = luaL_checkstring(L, 2); gchar *res = g_uri_unescape_string(string, illegal); if (!res) return 0; lua_pushstring(L, res); g_free(res); return 1; } /** Calls the idle callback function. If the callback function returns false the * idle source is removed, the Lua function is unreffed and will not be called * again. * \see luaH_luakit_idle_add * * \param func Lua callback function. * \return TRUE to keep source alive, FALSE to remove. */ gboolean idle_cb(gpointer func) { lua_State *L = common.L; /* get original stack size */ gint top = lua_gettop(L); /* call function */ luaH_object_push(L, func); gboolean ok = luaH_dofunction(L, 0, 1); /* keep the source alive? */ gboolean keep = lua_toboolean(L, -1); /* allow collection of idle callback func */ if (!keep || !ok) luaH_object_unref(L, func); /* leave stack how we found it */ lua_settop(L, top); return keep && ok; } /** Adds a function to be called whenever there are no higher priority GTK * events pending in the default main loop. If the function returns false it * is automatically removed from the list of event sources and will not be * called again. * \see http://developer.gnome.org/glib/unstable/glib-The-Main-Event-Loop.html#g-idle-add * * \param L The Lua VM state. * \return The number of elements pushed on the stack (0). * * \luastack * \lparam func The callback function. */ gint luaH_luakit_idle_add(lua_State *L) { luaH_checkfunction(L, 1); gpointer func = luaH_object_ref(L, 1); g_idle_add(idle_cb, func); return 0; } /** Removes an idle callback by function. * \see http://developer.gnome.org/glib/unstable/glib-The-Main-Event-Loop.html#g-idle-remove-by-data * * \param L The Lua VM state. * \return The number of elements pushed on the stack (0). * * \luastack * \lparam func The callback function. * \lreturn true if callback removed. */ gint luaH_luakit_idle_remove(lua_State *L) { luaH_checkfunction(L, 1); gpointer func = (gpointer)lua_topointer(L, 1); lua_pushboolean(L, g_idle_remove_by_data(func)); luaH_object_unref(L, func); return 1; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/luakit.h000066400000000000000000000026511475363222200166630ustar00rootroot00000000000000/* * common/clib/luakit.h - Generic functions for Lua scripts * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_LUAKIT_H #define LUAKIT_COMMON_CLIB_LUAKIT_H #include #include #include #define LUAKIT_LIB_COMMON_METHODS \ { "time", luaH_luakit_time }, \ { "uri_encode", luaH_luakit_uri_encode }, \ { "uri_decode", luaH_luakit_uri_decode }, \ { "idle_add", luaH_luakit_idle_add }, \ { "idle_remove", luaH_luakit_idle_remove }, \ gint luaH_luakit_time(lua_State *L); gint luaH_luakit_uri_encode(lua_State *L); gint luaH_luakit_uri_decode(lua_State *L); gint luaH_luakit_idle_add(lua_State *L); gint luaH_luakit_idle_remove(lua_State *L); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/msg.h000066400000000000000000000045021475363222200161550ustar00rootroot00000000000000/* * common/clib/msg.h - Lua logging shared functionality * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_MSG_H #define LUAKIT_COMMON_CLIB_MSG_H #include "luah.h" #include #include #include #include #include #include #include static gpointer string_format_ref; static gpointer tostring_ref; static const gchar * luaH_msg_string_from_args(lua_State *L) { gint nargs = lua_gettop(L); /* Pre-convert all non-numerical arguments to strings */ for (gint i = 1; i <= nargs; ++i) { if (lua_type(L, i) != LUA_TNUMBER) { /* Convert to a string with tostring() ... */ luaH_object_push(L, tostring_ref); lua_pushvalue(L, i); lua_pcall(L, 1, 1, 0); /* ... And replace the original value */ lua_remove(L, i); lua_insert(L, i); } } luaH_object_push(L, string_format_ref); lua_insert(L, 1); if (lua_pcall(L, nargs, 1, 0)) luaL_error(L, "failed to format message: %s", lua_tostring(L, -1)); return lua_tostring(L, -1); } static gint luaH_msg(lua_State *L, log_level_t lvl) { lua_Debug ar; lua_getstack(L, 1, &ar); lua_getinfo(L, "Sln", &ar); /* Use .source if it's a file, since short_src is truncated for long paths */ const char *src = ar.source[0] == '@' ? ar.source+1 : ar.short_src; _log(lvl, src, "%s", luaH_msg_string_from_args(L)); return 0; } #define X(name) \ static gint \ luaH_msg_##name(lua_State *L) \ { \ return luaH_msg(L, LOG_LEVEL_##name); \ } \ LOG_LEVELS #undef X #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/regex.c000066400000000000000000000067251475363222200165050ustar00rootroot00000000000000/* * common/clib/regex.c - Small wrapper around GRegex * * Copyright © 2017 Aidan Holm * * 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 . * */ #include "common/clib/regex.h" #include "common/luaobject.h" #include "luah.h" #include typedef struct { LUA_OBJECT_HEADER GRegex *reg; gchar *pattern; GRegexCompileFlags compile_options; GRegexMatchFlags match_options; } lregex_t; static lua_class_t regex_class; LUA_OBJECT_FUNCS(regex_class, lregex_t, regex) #define REGEX_STOPPED -1 #define luaH_checkregex(L, idx) luaH_checkudata(L, idx, &(regex_class)) static gint luaH_regex_gc(lua_State *L) { lregex_t *regex = luaH_checkregex(L, 1); if (regex->reg) g_regex_unref(regex->reg); g_free(regex->pattern); return luaH_object_gc(L); } static void luaH_regenerate_regex(lua_State *L, lregex_t *regex) { g_assert(regex->pattern); if (regex->reg) g_regex_unref(regex->reg); GError *error = NULL; regex->reg = g_regex_new(regex->pattern, G_REGEX_DOTALL, 0, &error); if (error) { lua_pushstring(L, error->message); g_error_free(error); luaL_error(L, lua_tostring(L, -1)); } } static int luaH_regex_new(lua_State *L) { luaH_class_new(L, ®ex_class); lregex_t *regex = lua_touserdata(L, -1); if (!regex->pattern) return luaL_error(L, "pattern not set"); return 1; } static int luaH_regex_match(lua_State *L) { lregex_t *regex = luaH_checkregex(L, 1); const gchar *haystack = luaL_checkstring(L, 2); g_assert(regex->reg); gboolean matched = g_regex_match(regex->reg, haystack, 0, NULL); lua_pushboolean(L, matched); return 1; } static int luaH_regex_get_pattern(lua_State *L, lregex_t *regex) { lua_pushstring(L, regex->pattern); return 1; } static int luaH_regex_set_pattern(lua_State *L, lregex_t *regex) { gchar *new_pattern = g_strdup(luaL_checkstring(L, -1)); g_free(regex->pattern); regex->pattern = new_pattern; luaH_regenerate_regex(L, regex); return 0; } void regex_class_setup(lua_State *L) { static const struct luaL_Reg regex_methods[] = { LUA_CLASS_METHODS(regex) { "__call", luaH_regex_new }, { NULL, NULL } }; static const struct luaL_Reg regex_meta[] = { LUA_OBJECT_META(regex) LUA_CLASS_META { "match", luaH_regex_match }, { "__gc", luaH_regex_gc }, { NULL, NULL }, }; luaH_class_setup(L, ®ex_class, "regex", (lua_class_allocator_t) regex_new, NULL, NULL, regex_methods, regex_meta); luaH_class_add_property(®ex_class, L_TK_PATTERN, (lua_class_propfunc_t) luaH_regex_set_pattern, (lua_class_propfunc_t) luaH_regex_get_pattern, (lua_class_propfunc_t) luaH_regex_set_pattern); } #undef luaH_checkregex // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/regex.h000066400000000000000000000016571475363222200165110ustar00rootroot00000000000000/* * common/clib/regex.h - Regex class header * * Copyright © 2010 Fabian Streitel * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_REGEX_H #define LUAKIT_COMMON_CLIB_REGEX_H #include void regex_class_setup(lua_State *); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/soup.h000066400000000000000000000111051475363222200163520ustar00rootroot00000000000000/* * common/clib/soup.h - soup library * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_SOUP_H #define LUAKIT_COMMON_CLIB_SOUP_H #include #if SOUP_CHECK_VERSION(3,0,0) #include #else #include #define SOUP_HTTP_URI_FLAGS (G_URI_FLAGS_HAS_PASSWORD |\ G_URI_FLAGS_ENCODED_PATH |\ G_URI_FLAGS_ENCODED_QUERY |\ G_URI_FLAGS_ENCODED_FRAGMENT |\ G_URI_FLAGS_SCHEME_NORMALIZE) #endif static GRegex *scheme_reg; static gint luaH_soup_uri_tostring(lua_State *L) { const gchar *p; gint port; /* check for uri table */ luaH_checktable(L, 1); const gchar * scheme = "http"; const gchar * user = NULL; const gchar * host = NULL; const gchar * path = NULL; const gchar * query = NULL; const gchar * fragment = NULL; gchar * uri; #define GET_PROP(prop) \ lua_pushliteral(L, #prop); \ lua_rawget(L, 1); \ if (!lua_isnil(L, -1) && (p = lua_tostring(L, -1)) && p[0]) \ prop = p; \ lua_pop(L, 1); GET_PROP(scheme) /* If this is a file:// uri, set a default host of "" * Without host set, a path of "/home/..." will become "file:/home/..." * instead of "file:///home/..." */ if (!g_strcmp0(scheme, "file")) { /* I assume that this is strdup()ed by g_uri_join(), so use * some space on the stack rather than calloc'ing */ host = ""; } GET_PROP(user) GET_PROP(host) GET_PROP(path) GET_PROP(query) GET_PROP(fragment) lua_pushliteral(L, "port"); lua_rawget(L, 1); if (lua_isnil(L, -1) || !(port = lua_tonumber(L, -1))) port = -1; lua_pop(L, 1); uri = g_uri_join_with_user (SOUP_HTTP_URI_FLAGS, scheme, user, NULL, // omit password to retain soup_uri_to_string()'s behaviour NULL, // auth_params host, port, path, query, fragment); lua_pushstring(L, uri); g_free(uri); return 1; } static gint luaH_soup_push_uri(lua_State *L, GUri *uri) { const gchar *p; gint port; /* create table for uri properties */ lua_newtable(L); #define PUSH_PROP(prop) \ if ((p = g_uri_get_##prop(uri)) && p[0]) { \ lua_pushliteral(L, #prop); \ lua_pushstring(L, p); \ lua_rawset(L, -3); \ } PUSH_PROP(scheme) PUSH_PROP(user) PUSH_PROP(password) PUSH_PROP(host) PUSH_PROP(path) PUSH_PROP(query) PUSH_PROP(fragment) port = g_uri_get_port(uri); if (port > 0) { lua_pushliteral(L, "port"); lua_pushnumber(L, port); lua_rawset(L, -3); } return 1; } static gint luaH_soup_parse_uri(lua_State *L) { gchar *str = (gchar*)luaL_checkstring(L, 1); /* check for blank uris */ if (!str[0]) return 0; /* default to http:// scheme */ if (!g_regex_match(scheme_reg, str, 0, 0)) str = g_strdup_printf("http://%s", str); else str = g_strdup(str); /* parse & push uri */ GUri *uri = g_uri_parse (str, SOUP_HTTP_URI_FLAGS, NULL); g_free(str); if (uri) { luaH_soup_push_uri(L, uri); g_uri_unref(uri); return 1; } return 0; } static void soup_lib_setup_common(void) { scheme_reg = g_regex_new("^[a-z][a-z0-9\\+\\-\\.]*:", 0, 0, NULL); } #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/timer.c000066400000000000000000000106071475363222200165050ustar00rootroot00000000000000/* * common/clib/timer.c - Simple timer class * * Copyright © 2010 Fabian Streitel * Copyright © 2010 Mason Larobina * * 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 . * */ #include "common/clib/timer.h" #include "common/luaobject.h" #include "luah.h" #include typedef struct { LUA_OBJECT_HEADER gpointer ref; int id; int interval; } ltimer_t; static lua_class_t timer_class; LUA_OBJECT_FUNCS(timer_class, ltimer_t, timer) #define TIMER_STOPPED -1 #define luaH_checktimer(L, idx) luaH_checkudata(L, idx, &(timer_class)) static void luaH_timer_destroy(lua_State *L, ltimer_t *timer) { GSource *source = g_main_context_find_source_by_id(NULL, timer->id); if (source != NULL) g_source_destroy(source); /* allow timer to be garbage collected */ luaH_object_unref(L, timer->ref); timer->ref = NULL; timer->id = TIMER_STOPPED; } static gboolean timer_handle_timeout(gpointer data) { ltimer_t *timer = (ltimer_t *) data; luaH_object_push(common.L, timer->ref); luaH_object_emit_signal(common.L, -1, "timeout", 0, 0); lua_pop(common.L, 1); return TRUE; } static int luaH_timer_new(lua_State *L) { luaH_class_new(L, &timer_class); ltimer_t *timer = luaH_checktimer(L, -1); timer->id = TIMER_STOPPED; return 1; } static int luaH_timer_start(lua_State *L) { ltimer_t *timer = luaH_checktimer(L, 1); if (!timer->interval) luaL_error(L, "interval not set"); if (timer->id == TIMER_STOPPED) { /* ensure timer isn't collected while running */ timer->ref = luaH_object_ref(L, 1); timer->id = g_timeout_add(timer->interval, timer_handle_timeout, timer); } else luaH_warn(L, "timer already started"); return 0; } static int luaH_timer_stop(lua_State *L) { ltimer_t *timer = luaH_checktimer(L, 1); if (timer->id == TIMER_STOPPED) luaH_warn(L, "timer already stopped"); else luaH_timer_destroy(L, timer); return 0; } static int luaH_timer_set_interval(lua_State *L, ltimer_t *timer) { timer->interval = luaL_checkint(L, -1); return 0; } static int luaH_timer_get_interval(lua_State *L, ltimer_t *timer) { lua_pushinteger(L, timer->interval); return 1; } static int luaH_timer_get_started(lua_State *L, ltimer_t *timer) { lua_pushboolean(L, (timer->id != TIMER_STOPPED)); return 1; } gint luaH_timer_index_miss_property(lua_State *L, lua_object_t* UNUSED(obj)) { return luaL_error(L, "timer index miss; key %s", lua_tostring(L, 2)); } gint luaH_timer_newindex_miss_property(lua_State *L, lua_object_t* UNUSED(obj)) { return luaL_error(L, "timer newindex miss; key %s", lua_tostring(L, 2)); } void timer_class_setup(lua_State *L) { static const struct luaL_Reg timer_methods[] = { LUA_CLASS_METHODS(timer) { "__call", luaH_timer_new }, { NULL, NULL } }; static const struct luaL_Reg timer_meta[] = { LUA_OBJECT_META(timer) LUA_CLASS_META { "start", luaH_timer_start }, { "stop", luaH_timer_stop }, { "__gc", luaH_object_gc }, { NULL, NULL }, }; luaH_class_setup(L, &timer_class, "timer", (lua_class_allocator_t) timer_new, luaH_timer_index_miss_property, luaH_timer_newindex_miss_property, timer_methods, timer_meta); luaH_class_add_property(&timer_class, L_TK_INTERVAL, (lua_class_propfunc_t) luaH_timer_set_interval, (lua_class_propfunc_t) luaH_timer_get_interval, (lua_class_propfunc_t) luaH_timer_set_interval); luaH_class_add_property(&timer_class, L_TK_STARTED, NULL, (lua_class_propfunc_t) luaH_timer_get_started, NULL); } #undef luaH_checktimer // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/timer.h000066400000000000000000000016571475363222200165170ustar00rootroot00000000000000/* * common/clib/timer.h - Timer class header * * Copyright © 2010 Fabian Streitel * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_TIMER_H #define LUAKIT_COMMON_CLIB_TIMER_H #include void timer_class_setup(lua_State *); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/utf8.c000066400000000000000000000105751475363222200162570ustar00rootroot00000000000000/* * common/clib/utf8.c - Basic UTF8 character counting (wrapper for glib) * * Copyright © 2017 Dennis Hofheinz * * 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 . * */ #include "common/clib/utf8.h" #include "luah.h" #include /* Convert 1-based into 0-based byte offset, * counted from back of string if negative * return (size_t) -1 if offset is out of range */ static size_t abspos(ssize_t offset, size_t length) { if (offset == 0) return (size_t) -1; offset = (offset > 0) ? offset - 1 : offset + (ssize_t) length; if (offset < 0 || (size_t) offset > length) return (size_t) -1; return (size_t) offset; } /* UTF8 aware string length computing. * Returns the number of elements pushed on the stack. */ static gint luaH_utf8_len(lua_State *L) { size_t blen; const gchar *str = luaL_checklstring(L, 1, &blen); /* parse optional begin/end parameters * raise an error if out of bounds */ size_t bbeg = abspos(luaL_optinteger(L, 2, 1), blen); luaL_argcheck(L, bbeg != (size_t) -1, 2, "initial position out of string"); /* setting end position requires extra work to imitate Lua 5.3 */ size_t bend = bbeg; ssize_t sbend = luaL_optinteger(L, 3, blen); /* may be negative */ sbend = (sbend >= 0) ? sbend - 1 : sbend + (ssize_t) blen; luaL_argcheck(L, sbend < (ssize_t) blen, 3, "final position out of string"); if (sbend >= (ssize_t) bbeg && (size_t) sbend < blen) bend = g_utf8_find_next_char(str + (size_t) sbend, NULL) - str; /* is the string valid UTF8? */ gchar *valend; if (!g_utf8_validate(str + bbeg, bend - bbeg, (const gchar **) &valend)) { lua_pushnil(L); lua_pushinteger(L, (ssize_t) (valend - str) + 1); return 2; } lua_pushinteger(L, (ssize_t) g_utf8_strlen(str + bbeg, bend - bbeg)); return 1; } /* UTF8 aware string offset conversion. * Converts (1-based) UTF8 offset to (1-based) byte offset. * Returns the number of elements pushed on the stack. */ static gint luaH_utf8_offset(lua_State *L) { size_t blen; const gchar *str = luaL_checklstring(L, 1, &blen); ssize_t widx = luaL_checkinteger(L, 2); if (widx > 0) widx--; /* adjust to 0-based */ /* parse optional parameter (base index) * raise an error if out of bounds * or if initial position points inside a UTF8 encoding */ size_t bbase; bbase = luaL_optinteger(L, 3, (widx>=0) ? 1 : blen + 1); bbase = abspos(bbase, blen); luaL_argcheck(L, bbase != (size_t) -1, 3, "position out of range"); if (g_utf8_get_char_validated(str + bbase, -1) == (gunichar) -1) luaL_error(L, "initial position is a continuation byte"); /* convert negative index parameter to positive */ size_t wseglen; size_t bbeg = 0; if (widx < 0) { wseglen = g_utf8_strlen(str, bbase); widx += wseglen; } else { wseglen = g_utf8_strlen(str + bbase, blen - bbase); bbeg = bbase; } /* convert positive UTF8 offset to byte offset */ ssize_t ret = 0; if (widx >= 0 && (size_t) widx <= wseglen) { gchar *pos = g_utf8_offset_to_pointer(str + bbeg, widx); if (pos != NULL) ret = (ssize_t) (pos - str) + 1; } /* if conversion was successful, output result (else output nil) */ if (ret > 0) lua_pushinteger(L, ret); else lua_pushnil(L); return 1; } void utf8_lib_setup(lua_State *L) { static const struct luaL_Reg utf8_lib[] = { { "len", luaH_utf8_len }, { "offset", luaH_utf8_offset }, { NULL, NULL } }; luaH_openlib(L, "utf8", utf8_lib, utf8_lib); lua_getglobal(L, "utf8"); lua_pushstring(L, "[%z\1-\x7F\xC2-\xF4][\x80-\xBF]*"); lua_setfield(L, -2, "charpattern"); lua_pop(L, 1); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/clib/utf8.h000066400000000000000000000016371475363222200162630ustar00rootroot00000000000000/* * common/clib/utf8.h - UTF8 class header * * Copyright © 2017 Dennis Hofheinz * * 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 . * */ #ifndef LUAKIT_COMMON_CLIB_UTF8_H #define LUAKIT_COMMON_CLIB_UTF8_H #include void utf8_lib_setup(lua_State *); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/common.h000066400000000000000000000016711475363222200157520ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_COMMON_H #define LUAKIT_COMMON_COMMON_H #include typedef struct _common_t { /** Main Lua VM state */ lua_State *L; } common_t; extern common_t common; #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/ipc.c000066400000000000000000000253661475363222200152370ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include "common/lualib.h" #include "common/luaserialize.h" #include "common/ipc.h" #include "log.h" /* Prototypes for ipc_recv_... functions */ #define X(name) void ipc_recv_##name(ipc_endpoint_t *ipc, const void *msg, guint length); IPC_TYPES #undef X /* * Default process name is "UI" because the UI process currently sends IPC * messages before the IPC channel is opened (these messages are queued), * and sending IPC messages writes log messages which include the process * name. */ static GThread *send_thread; static GAsyncQueue *send_queue; /** IPC endpoints for all webviews */ static GPtrArray *endpoints; typedef struct _queued_ipc_t { ipc_header_t header; ipc_endpoint_t *ipc; char payload[0]; } queued_ipc_t; const GPtrArray * ipc_endpoints_get(void) { if (!endpoints) endpoints = g_ptr_array_sized_new(1); return endpoints; } static void ipc_dispatch(ipc_endpoint_t *ipc, ipc_header_t header, gpointer payload) { if (header.type != IPC_TYPE_log) debug("Process '%s': recv " ANSI_COLOR_BLUE "%s" ANSI_COLOR_RESET " message", ipc->name, ipc_type_name(header.type)); switch (header.type) { #define X(name) case IPC_TYPE_##name: ipc_recv_##name(ipc, payload, header.length); break; IPC_TYPES #undef X default: fatal("Received message with invalid type 0x%x", header.type); } } static gpointer ipc_send_thread(gpointer UNUSED(user_data)) { while (TRUE) { queued_ipc_t *out = g_async_queue_pop(send_queue); ipc_endpoint_t *ipc = out->ipc; ipc_header_t *header = &out->header; gpointer data = out->payload; /* On any step here, the channel can disappear */ if((ipc->channel != NULL) && (ipc->status == IPC_ENDPOINT_CONNECTED)) g_io_channel_write_chars(ipc->channel, (gchar*)header, sizeof(*header), NULL, NULL); if((ipc->channel != NULL) && (ipc->status == IPC_ENDPOINT_CONNECTED)) g_io_channel_write_chars(ipc->channel, (gchar*)data, header->length, NULL, NULL); if((ipc->channel != NULL) && (ipc->status == IPC_ENDPOINT_CONNECTED)) ipc_endpoint_decref(ipc); else error("Trying to send an ipc message, but the endpoint went away."); g_free(out); } return NULL; } void ipc_send(ipc_endpoint_t *ipc, const ipc_header_t *header, const void *data) { if (!send_thread) { send_queue = g_async_queue_new(); send_thread = g_thread_new("send_thread", ipc_send_thread, NULL); } /* Keep the endpoint alive while the message is being sent */ if (!ipc_endpoint_incref(ipc)) return; if (header->type != IPC_TYPE_log) debug("Process '%s': send " ANSI_COLOR_BLUE "%s" ANSI_COLOR_RESET " message", ipc->name, ipc_type_name(header->type)); g_assert((header->length == 0) == (data == NULL)); /* Alloc and push a queued message; the send thread frees it */ queued_ipc_t *msg = g_malloc(sizeof(*msg) + header->length); msg->ipc = ipc; msg->header = *header; if (header->length) memcpy(msg->payload, data, header->length); if (ipc->channel) g_async_queue_push(send_queue, msg); else g_queue_push_tail(ipc->queue, msg); } static void ipc_recv_and_dispatch_or_enqueue(ipc_endpoint_t *ipc) { g_assert(ipc); ipc_recv_state_t *state = &ipc->recv_state; GIOChannel *channel = ipc->channel; gchar *buf = (state->hdr_done ? state->payload : &state->hdr) + state->bytes_read; gsize remaining = (state->hdr_done ? state->hdr.length : sizeof(state->hdr)) - state->bytes_read; gsize bytes_read; GError *error = NULL; switch (g_io_channel_read_chars(channel, buf, remaining, &bytes_read, &error)) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_AGAIN: return; case G_IO_STATUS_EOF: verbose("g_io_channel_read_chars(): End Of File received"); /* OSX and NetBSD are sending EOF on nonblocking channels first. * These requests can be ignored. They should end up in * recv_hup(), but unfortunately they do not. * * If we do not close the socket, glib will continue to * call the G_IO_IN handler. * * We decrement the refcount to 1 here, and when ipc_recv * decrements the refcount to zero, the socket will be * disconnected. */ g_atomic_int_dec_and_test(&ipc->refcount); return; case G_IO_STATUS_ERROR: if (!g_str_equal(ipc->name, "UI")) if (!g_str_equal(error->message, "Connection reset by peer")) error("g_io_channel_read_chars(): %s", error->message); g_error_free(error); return; default: g_assert_not_reached(); } /* Update ipc_recv state */ state->bytes_read += bytes_read; remaining -= bytes_read; if (remaining > 0) return; /* If we've just finished downloading the header... */ if (!state->hdr_done) { /* ... update state, and try to download payload */ state->hdr_done = TRUE; state->bytes_read = 0; state->payload = g_malloc(state->hdr.length); ipc_recv_and_dispatch_or_enqueue(ipc); return; } /* Otherwise, we finished downloading the message */ ipc_dispatch(ipc, state->hdr, state->payload); g_free(state->payload); /* Reset state for the next message */ state->payload = NULL; state->bytes_read = 0; state->hdr_done = FALSE; } /* Callback function for channel watch */ static gboolean ipc_recv(GIOChannel *UNUSED(channel), GIOCondition UNUSED(cond), ipc_endpoint_t *ipc) { if (!ipc_endpoint_incref(ipc)) return TRUE; ipc_recv_and_dispatch_or_enqueue(ipc); ipc_endpoint_decref(ipc); return TRUE; } static gboolean ipc_hup(GIOChannel *UNUSED(channel), GIOCondition UNUSED(cond), ipc_endpoint_t *ipc) { g_assert(ipc->status == IPC_ENDPOINT_CONNECTED); g_assert(ipc->channel); ipc_endpoint_decref(ipc); return TRUE; } void ipc_send_lua(ipc_endpoint_t *ipc, ipc_type_t type, lua_State *L, gint start, gint end) { GByteArray *buf = g_byte_array_new(); lua_serialize_range(L, buf, start, end); ipc_header_t header = { .type = type, .length = buf->len }; ipc_send(ipc, &header, buf->data); g_byte_array_unref(buf); } ipc_endpoint_t * ipc_endpoint_new(const gchar *name) { ipc_endpoint_t *ipc = g_slice_new0(ipc_endpoint_t); ipc->name = (gchar*)name; ipc->queue = g_queue_new(); ipc->status = IPC_ENDPOINT_DISCONNECTED; ipc->refcount = 1; ipc->creation_notified = FALSE; return ipc; } WARN_UNUSED gboolean ipc_endpoint_incref(ipc_endpoint_t *ipc) { /* Prevents incref/decref race */ int old; do { old = g_atomic_int_get(&ipc->refcount); if (old < 1) return FALSE; } while (!g_atomic_int_compare_and_exchange(&ipc->refcount, old, old+1)); return TRUE; } static void ipc_endpoint_incref_no_check(ipc_endpoint_t *ipc) { g_atomic_int_inc(&ipc->refcount); } void ipc_endpoint_decref(ipc_endpoint_t *ipc) { if (!g_atomic_int_dec_and_test(&ipc->refcount)) return; if (ipc->status == IPC_ENDPOINT_CONNECTED) ipc_endpoint_disconnect(ipc); if (ipc->queue) { while (!g_queue_is_empty(ipc->queue)) { queued_ipc_t *msg = g_queue_pop_head(ipc->queue); g_free(msg); } g_queue_free(ipc->queue); } ipc->status = IPC_ENDPOINT_FREED; g_slice_free(ipc_endpoint_t, ipc); } void ipc_endpoint_connect_to_socket(ipc_endpoint_t *ipc, int sock) { g_assert(ipc); g_assert(ipc->status == IPC_ENDPOINT_DISCONNECTED); ipc_recv_state_t *state = &ipc->recv_state; state->queued_ipcs = g_ptr_array_new(); GIOChannel *channel = g_io_channel_unix_new(sock); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); state->watch_in_id = g_io_add_watch(channel, G_IO_IN, (GIOFunc)ipc_recv, ipc); state->watch_hup_id = g_io_add_watch(channel, G_IO_HUP, (GIOFunc)ipc_hup, ipc); /* Atomically update ipc->channel. This is done because on the web extension * thread, logging spawns a message send thread, which may attempt to write * to the uninitialized channel after it has been created with * g_io_channel_unix_new(), but before it has been set up fully */ g_atomic_pointer_set(&ipc->channel, channel); ipc->status = IPC_ENDPOINT_CONNECTED; if (!endpoints) endpoints = g_ptr_array_sized_new(1); /* Add the endpoint; it should never be present already */ g_assert(!g_ptr_array_remove_fast(endpoints, ipc)); g_ptr_array_add(endpoints, ipc); } ipc_endpoint_t * ipc_endpoint_replace(ipc_endpoint_t *orig, ipc_endpoint_t *new) { g_assert(orig); g_assert(new); g_assert(orig->status == IPC_ENDPOINT_DISCONNECTED); g_assert(new->status == IPC_ENDPOINT_CONNECTED); /* Incref always succeeds because this is called from a message * handler, which holds a temporary ref to the ipc channel */ ipc_endpoint_incref_no_check(new); /* Send all queued messages */ if (orig->queue) { while (!g_queue_is_empty(orig->queue)) { queued_ipc_t *msg = g_queue_pop_head(orig->queue); msg->ipc = new; ipc_endpoint_incref_no_check(new); g_async_queue_push(send_queue, msg); } g_queue_free(orig->queue); orig->queue = NULL; } ipc_endpoint_decref(orig); return new; } void ipc_endpoint_disconnect(ipc_endpoint_t *ipc) { g_assert(ipc->status == IPC_ENDPOINT_CONNECTED); g_assert(ipc->channel); g_ptr_array_remove_fast(endpoints, ipc); /* Remove watches */ ipc_recv_state_t *state = &ipc->recv_state; g_source_remove(state->watch_in_id); g_source_remove(state->watch_hup_id); /* Close channel */ g_io_channel_shutdown(ipc->channel, TRUE, NULL); ipc->status = IPC_ENDPOINT_DISCONNECTED; ipc->channel = NULL; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/ipc.h000066400000000000000000000100711475363222200152270ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_IPC_H #define LUAKIT_COMMON_IPC_H #include #include "common/util.h" #define IPC_TYPES \ X(lua_require_module) \ X(lua_ipc) \ X(scroll) \ X(extension_init) \ X(eval_js) \ X(log) \ X(page_created) \ X(crash) \ #define X(name) IPC_TYPE_EXPONENT_##name, typedef enum { IPC_TYPES } _ipc_type_exponent_t; #undef X /* Automatically defines all IPC_TYPE_foo as powers of two */ #define X(name) IPC_TYPE_##name = (1 << IPC_TYPE_EXPONENT_##name), typedef enum { IPC_TYPES } ipc_type_t; #undef X #define IPC_TYPE_ANY (-1) /** Fixed size header prepended to each message */ typedef struct _ipc_header_t { /** The length of the message in bytes, not including the header */ guint length; /** The type of the message, fairly self-explanatory... */ ipc_type_t type; } ipc_header_t; /* Structure of messages for all message types */ typedef struct _ipc_lua_require_module_t { gchar module_name[0]; } ipc_lua_require_module_t; typedef struct _ipc_lua_ipc_t { gchar arg[0]; } ipc_lua_ipc_t; typedef enum { IPC_SCROLL_TYPE_docresize, IPC_SCROLL_TYPE_winresize, IPC_SCROLL_TYPE_scroll } ipc_scroll_subtype_t; typedef struct _ipc_scroll_t { gint h, v; guint64 page_id; ipc_scroll_subtype_t subtype; } ipc_scroll_t; typedef struct _ipc_page_created_t { guint64 page_id; pid_t pid; } ipc_page_created_t; /* Message names */ static inline const char * ipc_type_name(ipc_type_t type) { switch (type) { #define X(name) case IPC_TYPE_##name: return #name; IPC_TYPES #undef X default: return "UNKNOWN"; } } typedef struct _ipc_recv_state_t { guint watch_in_id, watch_hup_id; GPtrArray *queued_ipcs; ipc_header_t hdr; gpointer payload; gsize bytes_read; gboolean hdr_done; } ipc_recv_state_t; typedef enum { IPC_ENDPOINT_DISCONNECTED, IPC_ENDPOINT_CONNECTED, IPC_ENDPOINT_FREED, } ipc_endpoint_status_t; typedef struct _ipc_endpoint_t { /** Statically-allocated endpoint name; used for debugging */ gchar *name; /* Endpoint status */ ipc_endpoint_status_t status; /** Channel for IPC with web process */ GIOChannel *channel; /** Queued data for when channel is not yet open */ GQueue *queue; /** Incoming message bookkeeping data */ ipc_recv_state_t recv_state; /** Refcount: number of webviews + number of unsent messages */ gint refcount; /** Whether the endpoint creation signal has been emitted */ gboolean creation_notified; } ipc_endpoint_t; ipc_endpoint_t *ipc_endpoint_new(const gchar *name); void ipc_endpoint_connect_to_socket(ipc_endpoint_t *ipc, int sock); ipc_endpoint_t * ipc_endpoint_replace(ipc_endpoint_t *orig, ipc_endpoint_t *new); void ipc_endpoint_disconnect(ipc_endpoint_t *ipc); WARN_UNUSED gboolean ipc_endpoint_incref(ipc_endpoint_t *ipc); void ipc_endpoint_decref(ipc_endpoint_t *ipc); const GPtrArray *ipc_endpoints_get(void); void ipc_send_lua(ipc_endpoint_t *ipc, ipc_type_t type, lua_State *L, gint start, gint end); void ipc_send(ipc_endpoint_t *ipc, const ipc_header_t *header, const void *data); #define IPC_NO_HANDLER(type) \ void \ ipc_recv_##type(ipc_endpoint_t *ipc, const gpointer UNUSED(msg), guint UNUSED(length)) \ { \ fatal("process '%s': should never receive message of type %s", ipc->name, #type); \ } \ #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/log.h000066400000000000000000000040431475363222200152370ustar00rootroot00000000000000/* * common/log.h - logging functions * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LOG_H #define LUAKIT_COMMON_LOG_H #include #include #define LOG_LEVELS \ X(fatal) \ X(error) \ X(warn) \ X(info) \ X(verbose) \ X(debug) \ #define X(name) LOG_LEVEL_##name, typedef enum { LOG_LEVELS } log_level_t; #undef X /* ANSI term color codes */ #define ANSI_COLOR_RESET "\x1b[0m" #define ANSI_COLOR_BLACK "\x1b[30m" #define ANSI_COLOR_RED "\x1b[31m" #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_YELLOW "\x1b[33m" #define ANSI_COLOR_BLUE "\x1b[34m" #define ANSI_COLOR_MAGENTA "\x1b[35m" #define ANSI_COLOR_CYAN "\x1b[36m" #define ANSI_COLOR_GRAY "\x1b[37m" #define ANSI_COLOR_BG_RED "\x1b[41m" #define log(lvl, string, ...) _log(lvl, __FILE__, string, ##__VA_ARGS__) void _log(log_level_t lvl, const gchar *, const gchar *, ...) __attribute__ ((format (printf, 3, 4))); void va_log(log_level_t lvl, const gchar *, const gchar *, va_list); #define fatal(...) log(LOG_LEVEL_fatal, ##__VA_ARGS__) #define error(...) log(LOG_LEVEL_error, ##__VA_ARGS__) #define warn(...) log(LOG_LEVEL_warn, ##__VA_ARGS__) #define info(...) log(LOG_LEVEL_info, ##__VA_ARGS__) #define verbose(...) log(LOG_LEVEL_verbose, ##__VA_ARGS__) #define debug(...) log(LOG_LEVEL_debug, ##__VA_ARGS__) #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaclass.c000066400000000000000000000335201475363222200162620ustar00rootroot00000000000000/* * common/luaclass.c - useful functions for handling Lua classes * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #include "common/luaclass.h" #include "common/luaobject.h" #include "common/luayield.h" #include struct lua_class_property { /** Callback function called when the property is found in object creation. */ lua_class_propfunc_t new; /** Callback function called when the property is found in object __index. */ lua_class_propfunc_t index; /** Callback function called when the property is found in object __newindex. */ lua_class_propfunc_t newindex; }; static GPtrArray *luaH_classes = NULL; /** Convert an object to a userdata if possible. * * \param L The Lua VM state. * \param ud The index of the object to convert. * \param class The to try and convert it to. * \return A pointer to the converted object, NULL otherwise. */ gpointer luaH_toudata(lua_State *L, gint ud, lua_class_t *class) { gpointer p = lua_touserdata(L, ud); if(p) /* value is a userdata? */ if(lua_getmetatable(L, ud)) /* does it have a metatable? */ { lua_pushlightuserdata(L, class); lua_rawget(L, LUA_REGISTRYINDEX); if(!lua_rawequal(L, -1, -2)) /* does it have the correct mt? */ p = NULL; lua_pop(L, 2); /* remove both metatables */ } return p; } /** Checks if the object at the given index is a userdata of the given class. * Raises a Lua type error if it is not. * * \param L The Lua VM state. * \param ud The object index on the stack. * \param class The wanted class. * \return A pointer to the converted object. */ gpointer luaH_checkudata(lua_State *L, gint ud, lua_class_t *class) { gpointer p = luaH_toudata(L, ud, class); if(!p) luaL_typerror(L, ud, class->name); return p; } /** Get an object's \ref lua_class_t. * * \param L The Lua VM state. * \param idx The index of the object on the stack. * \return The \ref lua_class_t of the object, if it has one. \c NULL otherwise. */ lua_class_t * luaH_class_get(lua_State *L, gint idx) { gint type = lua_type(L, idx); lua_class_t *class; if(type == LUA_TUSERDATA && luaH_classes) for (guint i = 0; i < luaH_classes->len; i++) { class = luaH_classes->pdata[i]; if(luaH_toudata(L, idx, class)) return class; } return NULL; } /** Enhanced version of lua_typename that recognizes setup Lua classes. * * \param L The Lua VM state. * \param idx The index of the object on the stack. */ const gchar * luaH_typename(lua_State *L, gint idx) { gint type = lua_type(L, idx); if(type == LUA_TUSERDATA) { lua_class_t *lua_class = luaH_class_get(L, idx); if(lua_class) return lua_class->name; } return lua_typename(L, type); } /** Registers a library under a global name in Lua. * * \param L The Lua VM state. * \param name The name under which the library should be accessible in Lua. * \param methods The methods of the library. * \param meta The methods of the library's metatable. */ void luaH_openlib(lua_State *L, const gchar *name, const struct luaL_Reg methods[], const struct luaL_Reg meta[]) { luaL_newmetatable(L, name); /* 1 */ lua_pushvalue(L, -1); /* dup metatable 2 */ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable 1 */ luaL_register(L, NULL, meta); /* 1 */ luaL_register(L, name, methods); /* 2 */ lua_pushvalue(L, -1); /* dup self as metatable 3 */ lua_setmetatable(L, -2); /* set self as metatable 2 */ lua_pop(L, 2); } /** Adds a property to a \ref lua_class_t. * All callbacks functions can also be NULL, in which case the property either * cannot be initialized, set or read. * * \param lua_class The class to add the property to. * \param token The property's name. * \param cb_new The function to call when the property is set on * initialization. * \param cb_index The function to call when the property is read. * \param cb_newindex The function to call when the property is set. */ void luaH_class_add_property(lua_class_t *lua_class, luakit_token_t token, lua_class_propfunc_t cb_new, lua_class_propfunc_t cb_index, lua_class_propfunc_t cb_newindex) { lua_class_property_t *prop; g_assert(token != L_TK_UNKNOWN); prop = g_new0(lua_class_property_t, 1); /* populate property */ prop->new = cb_new; prop->index = cb_index; prop->newindex = cb_newindex; /* add property to class properties tree */ g_hash_table_insert((GHashTable*) lua_class->properties, (gpointer) token, prop); } /** Creates a new Lua class. * * \param L The Lua VM state. * \param class The \ref lua_class_t that identifies the class. * \param name The name of the class and it's constructor in Lua. * \param allocator The constructor of the class. * \param index_miss_property The function to call when an unknown property was * indexed. Can be NULL. * \param newindex_miss_property The function to call when an unknown property was * set. Can be NULL. * \param methods The methods of the class's objects. * \param meta The methods of the class itself. */ void luaH_class_setup(lua_State *L, lua_class_t *class, const gchar *name, lua_class_allocator_t allocator, lua_class_propfunc_t index_miss_property, lua_class_propfunc_t newindex_miss_property, const struct luaL_Reg methods[], const struct luaL_Reg meta[]) { /* Create the metatable */ lua_newtable(L); /* Register it with class pointer as key in the registry */ lua_pushlightuserdata(L, class); /* Duplicate the metatable */ lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushvalue(L, -1); /* dup metatable 2 */ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable 1 */ luaL_register(L, NULL, meta); /* 1 */ if (methods) { luaL_register(L, name, methods); /* 2 */ lua_pushvalue(L, -1); /* dup self as metatable 3 */ lua_setmetatable(L, -2); /* set self as metatable 2 */ lua_pop(L, 2); } else lua_pop(L, 1); class->allocator = allocator; class->name = name; class->index_miss_property = index_miss_property; class->newindex_miss_property = newindex_miss_property; class->signals = signal_new(); class->properties = (lua_class_property_array_t*) g_hash_table_new( g_direct_hash, g_direct_equal); if (!luaH_classes) luaH_classes = g_ptr_array_new(); g_ptr_array_add(luaH_classes, class); } /** Generic wrapper around GTK's \c signal_add. * Will be used to implement the Lua \c add_signal method of a class. * * \param L The Lua VM state. * \param lua_class The class that handles the signal. * \param name The name of the signal. * \param ud The index of the handler function. */ void luaH_class_add_signal(lua_State *L, lua_class_t *lua_class, const gchar *name, gint ud) { luaH_checkfunction(L, ud); gchar *origin = luaH_callerinfo(L); debug("add " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on %p from " ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET, name, lua_class, origin); g_free(origin); signal_add(lua_class->signals, name, luaH_object_ref(L, ud)); } /** Generic wrapper around GTK's \c signal_remove. * Will be used to implement the Lua \c remove_signal method of a class. * * \param L The Lua VM state. * \param lua_class The class that handles the signal. * \param name The name of the signal. * \param ud The index of the handler function. */ void luaH_class_remove_signal(lua_State *L, lua_class_t *lua_class, const gchar *name, gint ud) { luaH_checkfunction(L, ud); gpointer ref = (gpointer) lua_topointer(L, ud); signal_remove(lua_class->signals, name, ref); luaH_object_unref(L, (gpointer) ref); lua_remove(L, ud); } /** Generic wrapper around GTK's \c signal_emit. * Will be used to implement the Lua \c emit_signal method of a class. * * \param L The Lua VM state. * \param lua_class The class that handles the signal. * \param name The name of the signal. * \param nargs The number of arguments the signal expects. * \param nret The number of return values the signal expects. */ gint luaH_class_emit_signal(lua_State *L, lua_class_t *lua_class, const gchar *name, gint nargs, gint nret) { return signal_object_emit(L, lua_class->signals, name, nargs, nret); } gint luaH_class_property_signal(lua_State *L, lua_class_t *lua_class, luakit_token_t tok) { gchar *signame = g_strdup_printf("property::%s", token_tostring(tok)); signal_object_emit(L, lua_class->signals, signame, 0, 0); g_free(signame); return 0; } /** Try to use the metatable of an object. * * \param L The Lua VM state. * \param idxobj The index of the object to index. * \param idxfield The index of the field (attribute) to get. * \return The number of element pushed on the stack. */ gint luaH_usemetatable(lua_State *L, gint idxobj, gint idxfield) { /* Get metatable of the object. */ lua_getmetatable(L, idxobj); /* Get the field */ lua_pushvalue(L, idxfield); lua_rawget(L, -2); /* Do we have a field like that? */ if(!lua_isnil(L, -1)) { /* Yes, so return it! */ lua_remove(L, -2); return 1; } /* No, so remove everything. */ lua_pop(L, 2); return 0; } /** Get a property of an object. * * \param L The Lua VM state. * \param lua_class The class of the object. * \param fieldidx The index of the field name. * \return The object property if found, NULL otherwise. */ static lua_class_property_t * luaH_class_property_get(lua_State *L, lua_class_t *lua_class, gint fieldidx) { const gchar *attr = luaL_checkstring(L, fieldidx); luakit_token_t token = l_tokenize(attr); return g_hash_table_lookup((GHashTable*) lua_class->properties, (gpointer) token); } /* Generic index meta function for objects. * * \param L The Lua VM state. * \return The number of elements pushed on the stack. * * \luastack * \lvalue The object to index. * \lvalue The property to get. */ gint luaH_class_index(lua_State *L) { /* Try to use metatable first. */ if(luaH_usemetatable(L, 1, 2)) return 1; lua_class_t *class = luaH_class_get(L, 1); lua_class_property_t *prop = luaH_class_property_get(L, class, 2); /* Property does exist and has an index callback */ if(prop) { if(prop->index) return prop->index(L, luaH_checkudata(L, 1, class)); } else { if(class->index_miss_property) return class->index_miss_property(L, luaH_checkudata(L, 1, class)); } return 0; } /* Generic newindex meta function for objects. * * \param L The Lua VM state. * \return The number of elements pushed on the stack. * * \luastack * \lvalue The object to index. * \lvalue The property to set. * \lvalue The value to set it to. */ gint luaH_class_newindex(lua_State *L) { /* Try to use metatable first. */ if(luaH_usemetatable(L, 1, 2)) return 1; lua_class_t *class = luaH_class_get(L, 1); lua_class_property_t *prop = luaH_class_property_get(L, class, 2); /* Property does exist and has a newindex callback */ if(prop) { if(prop->newindex) return prop->newindex(L, luaH_checkudata(L, 1, class)); } else { if(class->newindex_miss_property) return class->newindex_miss_property(L, luaH_checkudata(L, 1, class)); } return 0; } /** Generic constructor function for objects. * Returns the number of elements pushed on stack. * * \param L The Lua VM state. * \param lua_class The class of the new object. * * \luastack * \lvalue A table that contains properties of the new object. * \lreturn A new object derived from the given class. */ gint luaH_class_new(lua_State *L, lua_class_t *lua_class) { gint idx = lua_gettop(L); /* Check we have a table that should contains some properties */ luaH_checktable(L, idx); /* Create a new object */ lua_object_t *object = lua_class->allocator(L); /* Push the first key before iterating */ lua_pushnil(L); /* Iterate over the property keys */ while(lua_next(L, idx)) { /* Check that the key is a string. * We cannot call tostring blindly or Lua will convert a key that is a * number TO A STRING, confusing lua_next() */ if(lua_isstring(L, -2)) { /* Lookup the property */ const char *attr = lua_tostring(L, -2); lua_class_property_t *prop = g_hash_table_lookup( (GHashTable*) lua_class->properties, (gpointer) l_tokenize(attr)); if(prop && prop->new) prop->new(L, object); } /* Remove value */ lua_pop(L, 1); } return 1; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaclass.h000066400000000000000000000117211475363222200162660ustar00rootroot00000000000000/* * common/luaclass.h - useful functions for handling Lua classes * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_COMMON_LUACLASS_H #define LUAKIT_COMMON_LUACLASS_H #include "common/signal.h" #include "common/tokenize.h" #include #include typedef struct lua_class_property lua_class_property_t; typedef GHashTable lua_class_property_array_t; #define LUA_OBJECT_HEADER \ signal_t *signals; /* Generic type for all objects. All Lua objects can be casted * to this type. */ typedef struct { LUA_OBJECT_HEADER } lua_object_t; typedef lua_object_t *(*lua_class_allocator_t)(lua_State *); typedef gint (*lua_class_propfunc_t)(lua_State *, lua_object_t *); typedef struct { /** Class name */ const gchar *name; /** Class signals */ signal_t *signals; /** Allocator for creating new objects of that class */ lua_class_allocator_t allocator; /** Class properties */ lua_class_property_array_t *properties; /** Function to call when a indexing an unknown property */ lua_class_propfunc_t index_miss_property; /** Function to call when a indexing an unknown property */ lua_class_propfunc_t newindex_miss_property; } lua_class_t; const gchar *luaH_typename(lua_State *, gint); lua_class_t *luaH_class_get(lua_State *, gint); void luaH_class_add_signal(lua_State *, lua_class_t *, const gchar *name, gint ud); void luaH_class_remove_signal(lua_State *, lua_class_t *, const gchar *name, gint ud); gint luaH_class_emit_signal(lua_State *, lua_class_t *, const gchar *name, gint nargs, gint nret); gint luaH_class_property_signal(lua_State *, lua_class_t *, luakit_token_t); void luaH_openlib(lua_State *, const gchar *, const struct luaL_Reg[], const struct luaL_Reg[]); void luaH_class_setup(lua_State *, lua_class_t *, const gchar *, lua_class_allocator_t, lua_class_propfunc_t, lua_class_propfunc_t, const struct luaL_Reg[], const struct luaL_Reg[]); void luaH_class_add_property(lua_class_t *, luakit_token_t token, lua_class_propfunc_t, lua_class_propfunc_t, lua_class_propfunc_t); gint luaH_usemetatable(lua_State *, gint, gint); gint luaH_class_index(lua_State *); gint luaH_class_newindex(lua_State *); gint luaH_class_new(lua_State *, lua_class_t *); gpointer luaH_checkudata(lua_State *, gint, lua_class_t *); gpointer luaH_toudata(lua_State *L, gint ud, lua_class_t *); static inline gpointer luaH_checkudataornil(lua_State *L, gint udx, lua_class_t *class) { if(lua_isnil(L, udx)) return NULL; return luaH_checkudata(L, udx, class); } #define LUA_CLASS_FUNCS(prefix, lua_class) \ static inline gint \ luaH_##prefix##_class_add_signal(lua_State *L) { \ luaH_class_add_signal(L, &(lua_class), luaL_checkstring(L, 1), 2); \ return 0; \ } \ \ static inline gint \ luaH_##prefix##_class_remove_signal(lua_State *L) { \ luaH_class_remove_signal(L, &(lua_class), luaL_checkstring(L, 1), 2); \ return 0; \ } \ \ static inline gint \ luaH_##prefix##_class_emit_signal(lua_State *L) { \ return luaH_class_emit_signal(L, &(lua_class), luaL_checkstring(L, 1), \ lua_gettop(L) - 1, LUA_MULTRET); \ } #define LUA_CLASS_METHODS(class) \ { "add_signal", luaH_##class##_class_add_signal }, \ { "remove_signal", luaH_##class##_class_remove_signal }, \ { "emit_signal", luaH_##class##_class_emit_signal }, #define LUA_CLASS_META \ { "__index", luaH_class_index }, \ { "__newindex", luaH_class_newindex }, #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luah.c000066400000000000000000000150261475363222200154050ustar00rootroot00000000000000/* * common/luah.c - Lua helper functions * * Copyright © 2010-2011 Mason Larobina * Copyright © 2008-2009 Julien Danjou * * 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 . * */ #include "common/luah.h" #include "common/luautil.h" #include "common/luaclass.h" #include #include /* UTF-8 aware string length computing. * Returns the number of elements pushed on the stack. */ static gint luaH_utf8_strlen(lua_State *L) { const gchar *cmd = luaL_checkstring(L, 1); lua_pushnumber(L, (ssize_t) g_utf8_strlen(NONULL(cmd), -1)); return 1; } /* Overload standard Lua next function to use __next key on metatable. * Returns the number of elements pushed on stack. */ static gint luaHe_next(lua_State *L) { if(luaL_getmetafield(L, 1, "__next")) { lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 2); if(lua_next(L, 1)) return 2; lua_pushnil(L); return 1; } /* Overload lua_next() function by using __next metatable field to get * next elements. `idx` is the index number of elements in stack. * Returns 1 if more elements to come, 0 otherwise. */ gint luaH_mtnext(lua_State *L, gint idx) { if(luaL_getmetafield(L, idx, "__next")) { /* if idx is relative, reduce it since we got __next */ if(idx < 0) idx--; /* copy table and then move key */ lua_pushvalue(L, idx); lua_pushvalue(L, -3); lua_remove(L, -4); lua_pcall(L, 2, 2, 0); /* next returned nil, it's the end */ if(lua_isnil(L, -1)) { /* remove nil */ lua_pop(L, 2); return 0; } return 1; } else if(lua_istable(L, idx)) return lua_next(L, idx); /* remove the key */ lua_pop(L, 1); return 0; } /* Generic pairs function. * Returns the number of elements pushed on stack. */ static gint luaH_generic_pairs(lua_State *L) { lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ lua_pushvalue(L, 1); /* state, */ lua_pushnil(L); /* and initial value */ return 3; } /* Overload standard pairs function to use __pairs field of metatables. * Returns the number of elements pushed on stack. */ static gint luaHe_pairs(lua_State *L) { if(luaL_getmetafield(L, 1, "__pairs")) { lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } luaL_checktype(L, 1, LUA_TTABLE); return luaH_generic_pairs(L); } static gint luaH_ipairs_aux(lua_State *L) { gint i = luaL_checkint(L, 2) + 1; luaL_checktype(L, 1, LUA_TTABLE); lua_pushinteger(L, i); lua_rawgeti(L, 1, i); return (lua_isnil(L, -1)) ? 0 : 2; } /* Overload standard ipairs function to use __ipairs field of metatables. * Returns the number of elements pushed on stack. */ static gint luaHe_ipairs(lua_State *L) { if(luaL_getmetafield(L, 1, "__ipairs")) { lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } luaL_checktype(L, 1, LUA_TTABLE); lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); lua_pushinteger(L, 0); /* and initial value */ return 3; } /* Enhanced type() function which recognize luakit objects. * \param L The Lua VM state. * \return The number of arguments pushed on the stack. */ static gint luaHe_type(lua_State *L) { luaL_checkany(L, 1); lua_pushstring(L, luaH_typename(L, 1)); return 1; } /** Returns the absolute version of a relative file path, if that file exists. * * \param L The Lua VM state. * \return The number of elements pushed on the stack. * * \luastack * \lparam rel_path The relative file path to convert. * \lreturn Returns the full path of the given file. */ static gint luaH_abspath(lua_State *L) { const gchar *path = luaL_checkstring(L, 1); GFile *file = g_file_new_for_path(path); if (!file) return 0; gchar *absolute = g_file_get_path(file); if (!absolute) return 0; lua_pushstring(L, absolute); g_free(absolute); return 1; } static gint luaH_debug_traceback(lua_State *L) { lua_State *thread; if ((thread = lua_tothread(L, 1))) lua_remove(L, 1); const gchar *msg = luaL_optstring(L, 1, NULL); int level = luaL_optnumber(L, msg ? 2 : 1, 1); lua_pushstring(L, msg ?: ""); lua_pushstring(L, msg ? "\nTraceback:\n" : "Traceback:\n"); luaH_traceback(L, thread ?: L, level); gchar *stripped = strip_ansi_escapes(lua_tostring(L, -1)); lua_pop(L, 1); lua_pushstring(L, stripped); lua_concat(L, 3); g_free(stripped); return 1; } /* Fix up and add handy standard lib functions */ void luaH_fixups(lua_State *L) { /* export string.wlen */ lua_getglobal(L, "string"); lua_pushcfunction(L, &luaH_utf8_strlen); lua_setfield(L, -2, "wlen"); lua_pop(L, 1); /* export os.abspath */ lua_getglobal(L, "os"); lua_pushcfunction(L, &luaH_abspath); lua_setfield(L, -2, "abspath"); lua_pop(L, 1); /* replace next */ lua_pushliteral(L, "next"); lua_pushcfunction(L, luaHe_next); lua_settable(L, LUA_GLOBALSINDEX); /* replace pairs */ lua_pushliteral(L, "pairs"); lua_pushcfunction(L, luaHe_next); lua_pushcclosure(L, luaHe_pairs, 1); /* pairs get next as upvalue */ lua_settable(L, LUA_GLOBALSINDEX); /* replace ipairs */ lua_pushliteral(L, "ipairs"); lua_pushcfunction(L, luaH_ipairs_aux); lua_pushcclosure(L, luaHe_ipairs, 1); lua_settable(L, LUA_GLOBALSINDEX); /* replace type */ lua_pushliteral(L, "type"); lua_pushcfunction(L, luaHe_type); lua_settable(L, LUA_GLOBALSINDEX); /* replace debug.traceback */ lua_getglobal(L, "debug"); lua_pushcfunction(L, &luaH_debug_traceback); lua_setfield(L, -2, "traceback"); lua_pop(L, 1); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luah.h000066400000000000000000000116141475363222200154110ustar00rootroot00000000000000/* * common/luah.h - Lua helper functions * * Copyright © 2010-2011 Mason Larobina * Copyright © 2008-2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_COMMON_LUAH_H #define LUAKIT_COMMON_LUAH_H #include "common/luaobject.h" #include "common/lualib.h" #include "common/log.h" #include #define luaH_deprecate(L, repl) \ do { \ luaH_warn(L, "%s: This function is deprecated and will be removed, see %s", \ __FUNCTION__, repl); \ lua_pushlstring(L, __FUNCTION__, sizeof(__FUNCTION__)); \ signal_object_emit(L, luakit_class.signals, "debug::deprecation", 1, 0); \ } while(0) static inline gboolean luaH_checkboolean(lua_State *L, gint n) { if(!lua_isboolean(L, n)) luaL_typerror(L, n, "boolean"); return lua_toboolean(L, n); } static inline gboolean luaH_optboolean(lua_State *L, gint idx, gboolean def) { return luaL_opt(L, luaH_checkboolean, idx, def); } static inline lua_Number luaH_getopt_number(lua_State *L, gint idx, const gchar *name, lua_Number def) { lua_getfield(L, idx, name); if (lua_isnil(L, -1) || lua_isnumber(L, -1)) def = luaL_optnumber(L, -1, def); lua_pop(L, 1); return def; } static inline const gchar * luaH_getopt_lstring(lua_State *L, gint idx, const gchar *name, const gchar *def, size_t *len) { lua_getfield(L, idx, name); const gchar *s = luaL_optlstring(L, -1, def, len); lua_pop(L, 1); return s; } static inline gboolean luaH_getopt_boolean(lua_State *L, gint idx, const gchar *name, gboolean def) { lua_getfield(L, idx, name); gboolean b = luaH_optboolean(L, -1, def); lua_pop(L, 1); return b; } /* Register an Lua object. * \param L The Lua stack. * \param idx Index of the object in the stack. * \param ref A gint address: it will be filled with the gint * registered. If the address points to an already registered object, it will * be unregistered. * \return Always 0. */ static inline gint luaH_register(lua_State *L, gint idx, gint *ref) { lua_pushvalue(L, idx); if(*ref != LUA_REFNIL) luaL_unref(L, LUA_REGISTRYINDEX, *ref); *ref = luaL_ref(L, LUA_REGISTRYINDEX); return 0; } /* Unregister a Lua object. * \param L The Lua stack. * \param ref A reference to an Lua object. */ static inline void luaH_unregister(lua_State *L, gint *ref) { luaL_unref(L, LUA_REGISTRYINDEX, *ref); *ref = LUA_REFNIL; } /* Register a function. * \param L The Lua stack. * \param idx Index of the function in the stack. * \param fct A gint address: it will be filled with the gint * registered. If the address points to an already registered function, it will * be unregistered. * \return luaH_register value. */ static inline gint luaH_registerfct(lua_State *L, gint idx, gint *fct) { luaH_checkfunction(L, idx); return luaH_register(L, idx, fct); } /* Grab a function from the registry and execute it. * \param L The Lua stack. * \param ref The function reference. * \param nargs The number of arguments for the Lua function. * \param nret The number of returned value from the Lua function. * \return True on no error, false otherwise. */ static inline gboolean luaH_dofunction_from_registry(lua_State *L, gint ref, gint nargs, gint nret) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); return luaH_dofunction(L, nargs, nret); } /* Print a warning about some Lua code. * This is less mean than luaL_error() which setjmp via lua_error() and kills * everything. This only warn, it's up to you to then do what's should be done. * \param L The Lua VM state. * \param fmt The warning message. */ static inline void __attribute__ ((format(printf, 2, 3))) luaH_warn(lua_State *L, const gchar *fmt, ...) { gint top = lua_gettop(L); lua_Debug ar; lua_getstack(L, 1, &ar); lua_getinfo(L, "Sln", &ar); g_assert_cmpint(top, ==, lua_gettop(L)); va_list ap; va_start(ap, fmt); va_log(LOG_LEVEL_warn, ar.short_src, fmt, ap); va_end(ap); } static inline gint luaH_rawfield(lua_State *L, gint idx, const gchar *field) { lua_pushstring(L, field); lua_rawget(L, idx); gint type = lua_type(L, -1); if (type == LUA_TNIL) lua_pop(L, 1); return type; } void luaH_fixups(lua_State *L); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luajs.c000066400000000000000000000127431475363222200155750ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "common/luajs.h" /* * Converts Lua value referenced by idx to the corresponding JavaScript type. * Returns NULL on failure. */ JSCValue *luajs_tovalue(lua_State *L, int idx, JSCContext *ctx) { switch (lua_type(L, idx)) { case LUA_TBOOLEAN: return jsc_value_new_boolean(ctx, lua_toboolean(L, idx)); case LUA_TNUMBER: return jsc_value_new_number(ctx, lua_tonumber(L, idx)); case LUA_TNIL: return jsc_value_new_null(ctx); case LUA_TNONE: return jsc_value_new_undefined(ctx); case LUA_TSTRING: return jsc_value_new_string(ctx, lua_tostring(L, idx)); case LUA_TTABLE: ; size_t len = lua_objlen(L, idx); int top = lua_gettop(L); JSCValue *res, *val; if (idx < 0) idx += top + 1; if (len) { res = jsc_value_new_array(ctx, G_TYPE_NONE); lua_pushnil(L); int i = 0; while (lua_next(L, idx)) { val = luajs_tovalue(L, -1, ctx); if (!val) { lua_settop(L, top); g_object_unref(res); return NULL; } jsc_value_object_set_property_at_index(res, i++, val); lua_pop(L, 1); g_object_unref(val); } } else { res = jsc_value_new_object(ctx, NULL, NULL); lua_pushnil(L); while (lua_next(L, idx)) { if (lua_type(L, -2) != LUA_TSTRING) continue; val = luajs_tovalue(L, -1, ctx); if (!val) { lua_settop(L, top); g_object_unref(res); return NULL; } jsc_value_object_set_property(res, lua_tostring(L, -2), val); lua_pop(L, 1); g_object_unref(val); } } return res; } return NULL; } /* * Converts JS value to the corresponding Lua type and pushes the result onto * the Lua stack. Returns the number of pushed values, 0 thus signals error. */ int luajs_pushvalue(lua_State *L, JSCValue *value) { if (jsc_value_is_undefined(value) || jsc_value_is_null(value)) lua_pushnil(L); else if (jsc_value_is_boolean(value)) lua_pushboolean(L, jsc_value_to_boolean(value)); else if (jsc_value_is_number(value)) lua_pushnumber(L, jsc_value_to_double(value)); else if (jsc_value_is_string(value)) { char *str = jsc_value_to_string(value); lua_pushstring(L, str); free(str); } else if (jsc_value_is_object(value)) { char **keys = jsc_value_object_enumerate_properties(value); int top = lua_gettop(L); JSCValue *val; char *eptr; char *key; int i = 0; long n; lua_newtable(L); while (keys && (key = keys[i++])) { if (*key && (n = strtol(key, &eptr, 10), !*eptr)) lua_pushinteger(L, ++n); else lua_pushstring(L, key); val = jsc_value_object_get_property(value, key); if (!luajs_pushvalue(L, val)) { g_object_unref(val); lua_settop(L, top); g_strfreev(keys); return 0; } g_object_unref(val); lua_rawset(L, -3); } g_strfreev(keys); } else return 0; return 1; } /* * Executes the given JS code in the provided context and pushes the last * generated value onto the Lua stack, unless no_return is set. Code must be a * NUL-terminated string. If error occurs, pushes a nil value and an error * string instead. source and line are only used in the JS execution error * message to specify its origin, they do not affect the execution itself. * Returns the number of values pushed onto the Lua stack. */ int luajs_eval_js(lua_State *L, JSCContext *ctx, const char *code, const char *source, guint line, bool no_return) { JSCValue *result = jsc_context_evaluate_with_source_uri(ctx, code, -1, source, line); JSCException *exception = jsc_context_get_exception(ctx); if (exception) { char *e = jsc_exception_to_string(exception); lua_pushnil(L); lua_pushstring(L, e); free(e); return 2; } if (no_return) return 0; int ret = luajs_pushvalue(L, result); g_object_unref(result); if (!ret) { lua_pushnil(L); lua_pushstring(L, "unable to push the result onto the Lua stack"); return 2; } return ret; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luajs.h000066400000000000000000000022371475363222200155770ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LUAJS_H #define LUAKIT_COMMON_LUAJS_H #include #include #include #include int luajs_eval_js(lua_State *L, JSCContext *ctx, const char *code, const char *source, guint line, bool no_return); int luajs_pushvalue(lua_State *L, JSCValue *value); JSCValue *luajs_tovalue(lua_State *L, int idx, JSCContext *ctx); #endif /* end of include guard: LUAKIT_COMMON_LUAJS_H */ // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/lualib.c000066400000000000000000000043261475363222200157250ustar00rootroot00000000000000/* * common/lualib.c - useful functions and type for Lua * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #include "common/lualib.h" /** Dump the Lua stack. Useful for debugging. * \param L The Lua VM state. */ void luaH_dump_stack(lua_State *L) { g_fprintf(stderr, "-------- Lua stack dump ---------\n"); for(int i = lua_gettop(L); i; i--) { int t = lua_type(L, i); switch (t) { case LUA_TSTRING: g_fprintf(stderr, "%d: string: `%s'\n", i, lua_tostring(L, i)); break; case LUA_TBOOLEAN: g_fprintf(stderr, "%d: bool: %s\n", i, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: g_fprintf(stderr, "%d: number: %g\n", i, lua_tonumber(L, i)); break; case LUA_TNIL: g_fprintf(stderr, "%d: nil\n", i); break; case LUA_TUSERDATA: g_fprintf(stderr, "%d: <%s>\t\t%p\n", i, luaH_typename(L, i), lua_topointer(L, i)); break; case LUA_TTABLE: g_fprintf(stderr, "%d: table\t#%zu\t%p\n", i, lua_objlen(L, i), lua_topointer(L, i)); luaH_dump_table_keys(L, i); break; default: g_fprintf(stderr, "%d: %s\t#%d\t%p\n", i, lua_typename(L, t), (gint) lua_objlen(L, i), lua_topointer(L, i)); break; } } g_fprintf(stderr, "------- Lua stack dump end ------\n"); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/lualib.h000066400000000000000000000073401475363222200157310ustar00rootroot00000000000000/* * common/lualib.h - useful functions and type for Lua * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_COMMON_LUALIB_H #define LUAKIT_COMMON_LUALIB_H #include #include #include #include #include "common/util.h" #include "common/luautil.h" #include "common/luaclass.h" #define luaH_checkfunction(L, n) do { \ if(!lua_isfunction(L, n)) \ luaL_typerror(L, n, "function"); \ } while(0) /** Dump the Lua function call stack. Useful for debugging. * \param L The Lua VM state. */ static inline void luaH_dump_traceback(lua_State *L) { g_fprintf(stderr, "--------- Lua traceback ---------\n"); luaH_traceback(L, L, 0); g_fprintf(stderr, "%s\n", lua_tostring(L, -1)); lua_pop(L, 1); g_fprintf(stderr, "-------- Lua traceback end ------\n"); } static inline void luaH_dump_table_keys(lua_State *L, gint idx) { gint len = (gint)lua_objlen(L, idx); guint limit = 5, rem = 0; g_fprintf(stderr, " Keys: "); lua_pushvalue(L, idx); lua_pushnil(L); while (lua_next(L, -2)) { if (limit == 0) rem++; else { limit --; gint key_type = lua_type(L, -2); if (key_type == LUA_TNUMBER && lua_tointeger(L, -2) > len) g_fprintf(stderr, "%zd, ", lua_tointeger(L, -2)); else if (key_type == LUA_TSTRING) g_fprintf(stderr, "%s, ", lua_tostring(L, -2)); else g_fprintf(stderr, "[%s]", lua_typename(L, key_type)); } lua_pop(L, 1); } lua_pop(L, 1); g_fprintf(stderr, "and %d more\n", rem); } void luaH_dump_stack(lua_State *L); /** Convert s stack index to positive. * \param L The Lua VM state. * \param ud The index. * \return A positive index. */ static inline gint luaH_absindex(lua_State *L, gint ud) { return (ud >= 0 || ud <= LUA_REGISTRYINDEX) ? ud : lua_gettop(L) + ud + 1; } /** Execute an Lua function on top of the stack. * \param L The Lua stack. * \param nargs The number of arguments for the Lua function. * \param nret The number of returned value from the Lua function. * \return True on no error, false otherwise. */ static inline gboolean luaH_dofunction(lua_State *L, gint nargs, gint nret) { /* Move function before arguments */ lua_insert(L, - nargs - 1); /* Push error handling function */ lua_pushcfunction(L, luaH_dofunction_on_error); /* Move error handling function before args and function */ lua_insert(L, - nargs - 2); gint error_func_pos = lua_gettop(L) - nargs - 1; if(lua_pcall(L, nargs, nret, - nargs - 2)) { error("%s", lua_tostring(L, -1)); /* Remove error function and error string */ lua_pop(L, 2); return FALSE; } /* Remove error function */ lua_remove(L, error_func_pos); return TRUE; } #define luaH_checktable(L, n) do { \ if(!lua_istable(L, n)) \ luaL_typerror(L, n, "table"); \ } while(0) #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaobject.c000066400000000000000000000363101475363222200164230ustar00rootroot00000000000000/* * common/luaobject.c - useful functions for handling Lua objects * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #include "common/luaobject.h" /* Setup the object system at startup. */ void luaH_object_setup(lua_State *L) { /* Push identification string */ lua_pushliteral(L, LUAKIT_OBJECT_REGISTRY_KEY); /* Create an empty table */ lua_newtable(L); /* Create an empty metatable */ lua_newtable(L); /* Set this empty table as the registry metatable. * It's used to store the number of reference on stored objects. */ lua_setmetatable(L, -2); /* Register table inside registry */ lua_rawset(L, LUA_REGISTRYINDEX); } /* Increment a object reference in its store table. * Removes the referenced object from the stack. * `tud` is the table index on the stack. * `oud` is the object index on the stack. * Returns a pointer to the object. */ gpointer luaH_object_incref(lua_State *L, gint tud, gint oud) { /* Get pointer value of the item */ gpointer p = (gpointer) lua_topointer(L, oud); /* Not reference able. */ if(!p) { lua_remove(L, oud); return NULL; } /* Push the pointer (key) */ lua_pushlightuserdata(L, p); /* Push the data (value) */ lua_pushvalue(L, oud < 0 ? oud - 1 : oud); /* table.lightudata = data */ lua_rawset(L, tud < 0 ? tud - 2 : tud); /* refcount++ */ /* Get the metatable */ lua_getmetatable(L, tud); /* Push the pointer (key) */ lua_pushlightuserdata(L, p); /* Get the number of references */ lua_rawget(L, -2); /* Get the number of references and increment it */ gint count = lua_tonumber(L, -1) + 1; lua_pop(L, 1); /* Push the pointer (key) */ lua_pushlightuserdata(L, p); /* Push count (value) */ lua_pushinteger(L, count); /* Set metatable[pointer] = count */ lua_rawset(L, -3); /* Pop metatable */ lua_pop(L, 1); /* Remove referenced item */ lua_remove(L, oud); return p; } /** Decrement a object reference in its store table. * `tud` is the table index on the stack. * `oud` is the object index on the stack. * Returns a pointer to the object. */ void luaH_object_decref(lua_State *L, gint tud, gpointer p) { if(!p) return; /* First, refcount-- */ /* Get the metatable */ lua_getmetatable(L, tud); /* Push the pointer (key) */ lua_pushlightuserdata(L, p); /* Get the number of references */ lua_rawget(L, -2); /* Get the number of references and decrement it */ gint count = lua_tonumber(L, -1) - 1; lua_pop(L, 1); /* Push the pointer (key) */ lua_pushlightuserdata(L, p); /* Hasn't the ref reached 0? */ if(count) lua_pushinteger(L, count); else /* Yup, delete it, set nil as value */ lua_pushnil(L); /* Set meta[pointer] = count/nil */ lua_rawset(L, -3); /* Pop metatable */ lua_pop(L, 1); /* Wait, no more ref? */ if(!count) { /* Yes? So remove it from table */ lua_pushlightuserdata(L, p); /* Push nil as value */ lua_pushnil(L); /* table[pointer] = nil */ lua_rawset(L, tud < 0 ? tud - 2 : tud); } } gint luaH_settype(lua_State *L, lua_class_t *lua_class) { lua_pushlightuserdata(L, lua_class); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); return 1; } /* Add a signal to an object. * `oud` is the object index on the stack. * `name` is the name of the signal. * `ud` is the index of function to call when signal is emitted. */ void luaH_object_add_signal(lua_State *L, gint oud, const gchar *name, gint ud) { luaH_checkfunction(L, ud); lua_object_t *obj = lua_touserdata(L, oud); if (!obj) { warn("object add signal on non object"); return; } gchar *origin = luaH_callerinfo(L); debug("add " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on %p from " ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET, name, obj, origin); g_free(origin); signal_add(obj->signals, name, luaH_object_ref_item(L, oud, ud)); } /* Remove a signal to an object. * `oud` is the object index on the stack. * `name` is the name of the signal. * `ud` is the index of function to call when signal is emitted. */ void luaH_object_remove_signal(lua_State *L, gint oud, const gchar *name, gint ud) { luaH_checkfunction(L, ud); lua_object_t *obj = lua_touserdata(L, oud); if (!obj) { warn("object remove signal on non object"); return; } gpointer ref = (gpointer) lua_topointer(L, ud); signal_remove(obj->signals, name, ref); luaH_object_unref_item(L, oud, ref); lua_remove(L, ud); } /* Remove all signals of a given name to an object. * `oud` is the object index on the stack. * `name` is the name of the signal. */ void luaH_object_remove_signals(lua_State *L, gint oud, const gchar *name) { lua_object_t *obj = lua_touserdata(L, oud); if (!obj) { warn("object remove signals on non object"); return; } signal_array_t *sigfuncs = signal_lookup(obj->signals, name); if (!sigfuncs) return; for (guint i = 0; i < sigfuncs->len; i++) { gpointer ref = g_ptr_array_index(sigfuncs, i); luaH_object_unref_item(L, oud, ref); } signals_remove(obj->signals, name); } /* Similar to signal_object_emit(), but allows you to choose the signal array * by name. */ gint signal_array_emit(lua_State *L, signal_t *signals, const gchar *array_name, const gchar *name, gint nargs, gint nret) { signal_array_t *sigfuncs = signal_lookup(signals, array_name); gchar *origin = luaH_callerinfo(L); debug("emit " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on %p from " ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET " (%d args, %d nret)", name, signals, origin ? origin : "", nargs, nret); g_free(origin); if (sigfuncs) { gint nbfunc = sigfuncs->len; luaL_checkstack(L, lua_gettop(L) + nbfunc + nargs + 1, "too many signal handlers; need a new implementation!"); /* Push all functions and then execute, because this list can change * while executing funcs. */ for (gint i = 0; i < nbfunc; i++) { luaH_object_push(L, sigfuncs->pdata[i]); } for (gint i = 0; i < nbfunc; i++) { gint stacksize = lua_gettop(L); /* push all args */ for (gint j = 0; j < nargs; j++) lua_pushvalue(L, - nargs - nbfunc + i); /* push first function */ lua_pushvalue(L, - nargs - nbfunc + i); /* remove this first function */ lua_remove(L, - nargs - nbfunc - 1 + i); luaH_dofunction(L, nargs, LUA_MULTRET); gint ret = lua_gettop(L) - stacksize + 1; /* Signal execution stops when: * - there's an expected number of return values (>0 or LUA_MULTRET) * - at least one return value (ret) * - the first return value is non-nil */ if (nret && ret && !lua_isnil(L, -ret)) { /* remove all args and functions */ for (gint j = 0; j < nargs + nbfunc - i - 1; j++) { lua_remove(L, - ret - 1); } /* Adjust the number of results to match nret */ if (nret != LUA_MULTRET && ret != nret) { /* Pad with nils */ for (; ret < nret; ret++) lua_pushnil(L); /* Or truncate stack */ if (ret > nret) { lua_pop(L, ret - nret); ret = nret; } } /* Return the number of returned arguments */ return ret; } else if (nret == 0) { /* ignore all return values */ lua_pop(L, ret); } } } /* remove args */ lua_pop(L, nargs); return 0; } /* Emit a signal from a signals array and return the results of the first * handler that returns something non-nil. * `signals` is the signals array. * `name` is the name of the signal. * `nargs` is the number of arguments to pass to the called functions. * `nret` is the number of return values this function pushes onto the stack. * A positive number means that any missing values will be padded with nil * and any superfluous values will be removed. * LUA_MULTRET means that any number of values is returned without any * adjustment. * 0 means that all return values are removed and that ALL handler functions are * executed. * Returns the number of return values pushed onto the stack. */ gint signal_object_emit(lua_State *L, signal_t *signals, const gchar *name, gint nargs, gint nret) { return signal_array_emit(L, signals, name, name, nargs, nret); } /* Emit a signal to an object. * `oud` is the object index on the stack. * `name` is the name of the signal. * `nargs` is the number of arguments to pass to the called functions. * `nret` is the number of return values this function pushes onto the stack. * A positive number means that any missing values will be padded with nil * and any superfluous values will be removed. * LUA_MULTRET means that any number of values is returned without any * adjustment. * 0 means that all return values are removed and that ALL handler functions are * executed. * Returns the number of return values pushed onto the stack. */ gint luaH_object_emit_signal(lua_State *L, gint oud, const gchar *name, gint nargs, gint nret) { gint ret, top, bot = lua_gettop(L) - nargs + 1; gint oud_abs = luaH_absindex(L, oud); lua_object_t *obj = lua_touserdata(L, oud); if (!obj) return luaL_error(L, "trying to emit " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on non-object", name); gchar *origin = luaH_callerinfo(L); debug("emit " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on %p from " ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET " (%d args, %d nret)", name, obj, origin ? origin : "", nargs, nret); g_free(origin); if(!obj) return luaL_error(L, "trying to emit " ANSI_COLOR_BLUE "\"%s\"" ANSI_COLOR_RESET " on non-object", name); signal_array_t *sigfuncs = signal_lookup(obj->signals, name); if (sigfuncs) { guint nbfunc = sigfuncs->len; luaL_checkstack(L, lua_gettop(L) + nbfunc + nargs + 2, "too many signal handlers; need a new implementation!"); /* Push all functions and then execute, because this list can change * while executing funcs. */ for (guint i = 0; i < nbfunc; i++) luaH_object_push_item(L, oud_abs, sigfuncs->pdata[i]); for (guint i = 0; i < nbfunc; i++) { /* push object */ lua_pushvalue(L, oud_abs); /* push all args */ for (gint j = 0; j < nargs; j++) lua_pushvalue(L, - nargs - nbfunc - 1 + i); /* push first function */ lua_pushvalue(L, - nargs - nbfunc - 1 + i); /* remove this first function */ lua_remove(L, - nargs - nbfunc - 2 + i); top = lua_gettop(L) - 2 - nargs; luaH_dofunction(L, nargs + 1, LUA_MULTRET); ret = lua_gettop(L) - top; /* Signal execution stops when: * - there's an expected number of return values (>0 or LUA_MULTRET) * - at least one return value (ret) * - the first return value is non-nil */ if (nret && ret && !lua_isnil(L, -ret)) { /* Adjust the number of results to match nret (including 0) */ if (nret != LUA_MULTRET && ret != nret) { /* Pad with nils */ for (; ret < nret; ret++) lua_pushnil(L); /* Or truncate stack */ if (ret > nret) { lua_pop(L, ret - nret); ret = nret; } } /* Remove all signal functions and args from the stack */ for (gint i = bot; i <= top; i++) lua_remove(L, bot); /* Return the number of returned arguments */ return ret; } else if (nret == 0) { /* ignore all return values */ lua_pop(L, ret); } } } lua_pop(L, nargs); return 0; } gint luaH_object_property_signal(lua_State *L, gint oud, luakit_token_t tok) { gchar *signame = g_strdup_printf("property::%s", token_tostring(tok)); luaH_object_emit_signal(L, oud, signame, 0, 0); g_free(signame); return 0; } gint luaH_object_add_signal_simple(lua_State *L) { luaH_object_add_signal(L, 1, luaL_checkstring(L, 2), 3); return 0; } gint luaH_object_remove_signal_simple(lua_State *L) { luaH_object_remove_signal(L, 1, luaL_checkstring(L, 2), 3); return 0; } gint luaH_object_remove_signals_simple(lua_State *L) { luaH_object_remove_signals(L, 1, luaL_checkstring(L, 2)); return 0; } gboolean luaH_object_collect_signal_keys(gpointer key, gpointer UNUSED(value), GPtrArray *keys) { g_ptr_array_add(keys, key); return FALSE; } gint luaH_object_remove_all_signals(signal_t *signals) { if (signals) { lua_State *L = common.L; GPtrArray *keys = g_ptr_array_new(); g_tree_foreach(signals, (GTraverseFunc)luaH_object_collect_signal_keys, keys); for (guint i = 0; i < keys->len; i++) { char *type = g_ptr_array_index(keys, i); lua_pushstring(L, type); luaH_object_remove_signals_simple(L); } g_ptr_array_free(keys, FALSE); } return 0; } gint luaH_object_emit_signal_simple(lua_State *L) { return luaH_object_emit_signal(L, 1, luaL_checkstring(L, 2), lua_gettop(L) - 2, LUA_MULTRET); } gint luaH_object_tostring(lua_State *L) { lua_class_t *lua_class = luaH_class_get(L, 1); lua_pushfstring(L, "%s: %p", lua_class->name, luaH_checkudata(L, 1, lua_class)); return 1; } /** Garbage collect a Lua object. * \param L The Lua VM state. * \return The number of elements pushed on stack. */ gint luaH_object_gc(lua_State *L) { lua_object_t *item = lua_touserdata(L, 1); if (!item) { warn("garbage collect on non-object"); return 0; } if (item->signals) { luaH_object_remove_all_signals(item->signals); signal_destroy(item->signals); } return 0; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaobject.h000066400000000000000000000172221475363222200164310ustar00rootroot00000000000000/* * common/luaobject.h - useful functions for handling Lua objects * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_COMMON_LUAOBJECT_H #define LUAKIT_COMMON_LUAOBJECT_H #include #include "common/luaclass.h" #include "common/lualib.h" #include "common/signal.h" #include "common/common.h" /** Registry key for the Lua registry API to store a private reference counting * table. This table prevents garbage collection of objects (userdata or * tables) while in use by C functions or objects. * \see http://www.lua.org/manual/5.1/manual.html#3.5 */ #define LUAKIT_OBJECT_REGISTRY_KEY "luakit.object.registry" gint luaH_settype(lua_State *L, lua_class_t *lua_class); void luaH_object_setup(lua_State *L); gpointer luaH_object_incref(lua_State *L, gint tud, gint oud); void luaH_object_decref(lua_State *L, gint tud, gpointer oud); /* Store an item in the environment table of an object. * Removes the stored object from the stack. * `ud` is the index of the object on the stack. * `iud` is the index of the item on the stack. * Return the item reference. */ static inline gpointer luaH_object_ref_item(lua_State *L, gint ud, gint iud) { /* Get the env table from the object */ lua_getfenv(L, ud); gpointer p = luaH_object_incref(L, -1, iud < 0 ? iud - 1 : iud); /* Remove env table */ lua_pop(L, 1); return p; } /* Unref an item from the environment table of an object. * `ud` is the index of the object on the stack. * `p` is the item. */ static inline void luaH_object_unref_item(lua_State *L, gint ud, gpointer p) { /* Get the env table from the object */ lua_getfenv(L, ud); /* Decrement */ luaH_object_decref(L, -1, p); /* Remove env table */ lua_pop(L, 1); } /* Push an object item on the stack. * `ud` is the object index on the stack. * `p` is the item pointer. * Returns the number of element pushed on stack. */ static inline gint luaH_object_push_item(lua_State *L, gint ud, gpointer p) { /* Get env table of the object */ lua_getfenv(L, ud); /* Push key */ lua_pushlightuserdata(L, p); /* Get env.pointer */ lua_rawget(L, -2); /* Remove env table */ lua_remove(L, -2); return 1; } static inline void luaH_object_registry_push(lua_State *L) { lua_pushliteral(L, LUAKIT_OBJECT_REGISTRY_KEY); lua_rawget(L, LUA_REGISTRYINDEX); } /* Reference an object and return a pointer to it. That only works with * userdata, table, thread or function. * Removes the referenced object from the stack. * `oud` is the object index on the stack. * Returns the object reference, or NULL if not referenceable. */ static inline gpointer luaH_object_ref(lua_State *L, gint oud) { luaH_object_registry_push(L); gpointer p = luaH_object_incref(L, -1, oud < 0 ? oud - 1 : oud); lua_pop(L, 1); return p; } /* Reference an object and return a pointer to it checking its type. That only * works with userdata. * `oud` is the object index on the stack. * `class` is the class of object expected * Return the object reference, or NULL if not referenceable. */ static inline gpointer luaH_object_ref_class(lua_State *L, gint oud, lua_class_t *class) { luaH_checkudata(L, oud, class); return luaH_object_ref(L, oud); } /* Unreference an object and return a pointer to it. That only works with * userdata, table, thread or function. * `oud` is the object index on the stack. */ static inline void luaH_object_unref(lua_State *L, gpointer p) { luaH_object_registry_push(L); luaH_object_decref(L, -1, p); lua_pop(L, 1); } /* Push a referenced object onto the stack. * `p` is the object to push. * Returns is the number of element pushed on stack. */ static inline gint luaH_object_push(lua_State *L, gpointer p) { luaH_object_registry_push(L); lua_pushlightuserdata(L, p); lua_rawget(L, -2); lua_remove(L, -2); return 1; } static inline void luaH_gobject_destroy_cb(gpointer ref) { luaH_object_unref(common.L, ref); } static inline void luaH_bind_gobject_ref(lua_State *L, gpointer gobject, int idx) { lua_pushvalue(L, idx); gpointer ref = luaH_object_ref(L, -1); g_object_set_data_full(G_OBJECT(gobject), "dummy-destroy-notify", ref, (GDestroyNotify)luaH_gobject_destroy_cb); } gint signal_array_emit(lua_State *L, signal_t *signals, const gchar *array_name, const gchar *name, gint nargs, gint nret); gint signal_object_emit(lua_State *, signal_t *signals, const gchar *name, gint nargs, gint nret); void luaH_object_add_signal(lua_State *L, gint oud, const gchar *name, gint ud); void luaH_object_remove_signal(lua_State *L, gint oud, const gchar *name , gint ud); gint luaH_object_emit_signal(lua_State *L, gint oud, const gchar *name, gint nargs, gint nret); gint luaH_object_add_signal_simple(lua_State *L); gint luaH_object_remove_signal_simple(lua_State *L); gint luaH_object_remove_signals_simple(lua_State *L); gint luaH_object_emit_signal_simple(lua_State *L); gint luaH_object_property_signal(lua_State *, gint, luakit_token_t); #define LUA_OBJECT_FUNCS(lua_class, type, prefix) \ LUA_CLASS_FUNCS(prefix, lua_class) \ static inline type * \ prefix##_new(lua_State *L) { \ type *p = lua_newuserdata(L, sizeof(type)); \ p_clear(p, 1); \ p->signals = signal_new(); \ luaH_settype(L, &(lua_class)); \ lua_newtable(L); \ lua_newtable(L); \ lua_setmetatable(L, -2); \ lua_setfenv(L, -2); \ lua_pushvalue(L, -1); \ luaH_class_emit_signal(L, &(lua_class), "new", 1, 0); \ return p; \ } #define OBJECT_EXPORT_PROPERTY(pfx, type, field) \ fieldtypeof(type, field) \ pfx##_get_##field(type *object) { \ return object->field; \ } #define LUA_OBJECT_EXPORT_PROPERTY(pfx, type, field, pusher) \ static gint \ luaH_##pfx##_get_##field(lua_State *L, type *object) { \ pusher(L, object->field); \ return 1; \ } gint luaH_object_tostring(lua_State *); gint luaH_object_gc(lua_State *); #define LUA_OBJECT_META(prefix) \ { "__tostring", luaH_object_tostring }, \ { "add_signal", luaH_object_add_signal_simple }, \ { "remove_signal", luaH_object_remove_signal_simple }, \ { "remove_signals", luaH_object_remove_signals_simple }, \ { "emit_signal", luaH_object_emit_signal_simple }, #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaserialize.c000066400000000000000000000152541475363222200171500ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "common/luaserialize.h" #include "common/lualib.h" #include static GByteArray *bytecode_buf; /* Used only for serializing functions */ static size_t bytecode_len; /* Used only for de-serializing functions */ static int lua_function_writer(lua_State *UNUSED(L), const void *p, size_t sz, void *UNUSED(ud)) { g_byte_array_append(bytecode_buf, (guint8*)p, sz); return 0; } static const char * lua_function_reader(lua_State *UNUSED(L), const guint8** bytes, size_t *sz) { if (bytecode_len == 0) return NULL; const char *ret = (const char *)*bytes; *bytes += bytecode_len; *sz = bytecode_len; return ret; } static void lua_serialize_value(lua_State *L, GByteArray *out, int index) { gint8 type = lua_type(L, index); int top = lua_gettop(L); switch (type) { case LUA_TUSERDATA: case LUA_TTHREAD: luaL_error(L, "cannot serialize variable of type %s", lua_typename(L, type)); return; default: break; } g_byte_array_append(out, (guint8*)&type, sizeof(type)); switch (type) { case LUA_TNIL: break; case LUA_TNUMBER: { lua_Number n = lua_tonumber(L, index); g_byte_array_append(out, (guint8*)&n, sizeof(n)); break; } case LUA_TBOOLEAN: { gint8 b = lua_toboolean(L, index); g_byte_array_append(out, (guint8*)&b, sizeof(b)); break; } case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(L, index, &len); g_byte_array_append(out, (guint8*)&len, sizeof(len)); g_byte_array_append(out, (guint8*)s, len+1); break; } case LUA_TTABLE: { /* Serialize all key-value pairs */ index = index > 0 ? index : lua_gettop(L) + 1 + index; lua_pushnil(L); while (lua_next(L, index) != 0) { lua_serialize_value(L, out, -2); lua_serialize_value(L, out, -1); lua_pop(L, 1); } /* Finish with a LUA_TNONE sentinel */ gint8 end = LUA_TNONE; g_byte_array_append(out, (guint8*)&end, sizeof(end)); break; } case LUA_TLIGHTUSERDATA: { gpointer p = lua_touserdata(L, index); if (!p) { warn("serialize lua lightuserdata on non object"); break; } g_byte_array_append(out, (guint8*)&p, sizeof(p)); break; } case LUA_TFUNCTION: { /* Serialize bytecode */ bytecode_buf = bytecode_buf ?: g_byte_array_new(); g_byte_array_set_size(bytecode_buf, 0); lua_pushvalue(L, index); lua_dump(L, lua_function_writer, NULL); lua_pop(L, 1); size_t len = bytecode_buf->len; g_byte_array_append(out, (guint8*)&len, sizeof(len)); g_byte_array_append(out, bytecode_buf->data, len); g_byte_array_set_size(bytecode_buf, 0); /* Serialize upvalues */ lua_Debug ar; lua_pushvalue(L, index); lua_getinfo(L, ">u", &ar); g_byte_array_append(out, (guint8*)&ar.nups, sizeof(ar.nups)); for (int i = 1; i <= ar.nups; i++) { lua_getupvalue(L, -1, i); lua_serialize_value(L, out, -1); lua_pop(L, 1); } break; } } g_assert_cmpint(lua_gettop(L), ==, top); } static int lua_deserialize_value(lua_State *L, const guint8 **bytes) { #define TAKE(dst, length) \ memcpy(&(dst), *bytes, (length)); \ *bytes += (length); gint8 type; TAKE(type, sizeof(type)); int top = lua_gettop(L); switch (type) { case LUA_TNIL: lua_pushnil(L); break; case LUA_TNUMBER: { lua_Number n; TAKE(n, sizeof(n)); lua_pushnumber(L, n); break; } case LUA_TBOOLEAN: { gint8 b; TAKE(b, sizeof(b)); lua_pushboolean(L, b); break; } case LUA_TSTRING: { size_t len; TAKE(len, sizeof(len)); lua_pushlstring(L, (char*)*bytes, len); *bytes += len+1; break; } case LUA_TTABLE: { lua_newtable(L); /* Deserialize key-value pairs and set them */ while (lua_deserialize_value(L, bytes) == 1) { lua_deserialize_value(L, bytes); lua_rawset(L, -3); } break; } case LUA_TLIGHTUSERDATA: { gpointer p; TAKE(p, sizeof(p)); lua_pushlightuserdata(L, p); break; } case LUA_TFUNCTION: { /* Deserialize bytecode */ TAKE(bytecode_len, sizeof(bytecode_len)); int status = lua_load(L, (lua_Reader)lua_function_reader, bytes, NULL); if (status != 0) return luaL_error(L, "deserialize error: %s", lua_tostring(L, -1)); /* Deserialize upvalues */ int nups; TAKE(nups, sizeof(nups)); for (int i = 1; i <= nups; i++) { lua_deserialize_value(L, bytes); lua_setupvalue(L, -2, i); } break; } case LUA_TNONE: return 0; } g_assert_cmpint(lua_gettop(L), ==, top + 1); return 1; } void lua_serialize_range(lua_State *L, GByteArray *out, int start, int end) { start = luaH_absindex(L, start); end = luaH_absindex(L, end); for (int i = start; i <= end; i++) lua_serialize_value(L, out, i); } int lua_deserialize_range(lua_State *L, const guint8 *in, guint length) { const guint8 *bytes = in; int i = 0; while (bytes < in + length) { lua_deserialize_value(L, &bytes); i++; } return i; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luaserialize.h000066400000000000000000000020001475363222200171360ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LUASERIALIZE_H #define LUAKIT_COMMON_LUASERIALIZE_H #include #include void lua_serialize_range(lua_State *L, GByteArray *out, gint start, gint end); int lua_deserialize_range(lua_State *L, const guint8 *in, guint length); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luauniq.c000066400000000000000000000071521475363222200161330ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "common/luauniq.h" #include "common/lualib.h" #define LUAKIT_UNIQ_REGISTRY_KEY "luakit.uniq.registry" /* Setup the unique object system at startup. */ void luaH_uniq_setup(lua_State *L, const gchar *reg, const gchar *mode) { /* Push identification string */ lua_pushstring(L, reg ?: LUAKIT_UNIQ_REGISTRY_KEY); /* Create an empty table */ lua_newtable(L); /* Set metatable specifying weak-values mode */ lua_newtable(L); lua_pushstring(L, "__mode"); lua_pushstring(L, mode); lua_rawset(L, -3); lua_setmetatable(L, -2); /* Register table inside registry */ lua_rawset(L, LUA_REGISTRYINDEX); } /* Adds a key -> Lua value mapping. * The key must not already be mapped to a value. * The stack is left unmodified, * `oud` is the Lua value index on the stack. */ int luaH_uniq_add(lua_State *L, const gchar *reg, int k, int oud) { /* Push the registry */ lua_pushstring(L, reg ?: LUAKIT_UNIQ_REGISTRY_KEY); lua_rawget(L, LUA_REGISTRYINDEX); /* Assert that the value is not already there */ lua_pushvalue(L, k > 0 ? k : k-1); lua_rawget(L, -2); g_assert(lua_isnil(L, -1)); lua_pop(L, 1); /* Add the Lua value */ lua_pushvalue(L, k > 0 ? k : k-1); lua_pushvalue(L, oud < 0 ? oud - 2 : oud); lua_rawset(L, -3); /* Remove the registry */ lua_pop(L, 1); return 0; } int luaH_uniq_add_ptr(lua_State *L, const gchar *reg, gpointer key, int oud) { lua_pushlightuserdata(L, key); luaH_uniq_add(L, reg, -1, oud > 0 ? oud : oud-1); lua_pop(L, 1); return 0; } /* Given a key, pushes its associated Lua value onto the stack, * if it exists */ int luaH_uniq_get(lua_State *L, const gchar *reg, int k) { /* Push the registry */ lua_pushstring(L, reg ?: LUAKIT_UNIQ_REGISTRY_KEY); lua_rawget(L, LUA_REGISTRYINDEX); /* Get the Lua value */ lua_pushvalue(L, k > 0 ? k : k-1); lua_rawget(L, -2); /* Remove the registry */ lua_remove(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 1); return 0; } return 1; } int luaH_uniq_get_ptr(lua_State *L, const gchar *reg, gpointer key) { lua_pushlightuserdata(L, key); int n = luaH_uniq_get(L, reg, -1); lua_remove(L, -1-n); return n; } void luaH_uniq_del(lua_State *L, const gchar *reg, int k) { /* Push the registry */ lua_pushstring(L, reg ?: LUAKIT_UNIQ_REGISTRY_KEY); lua_rawget(L, LUA_REGISTRYINDEX); /* Assert that the value is there */ lua_pushvalue(L, k > 0 ? k : k-1); lua_rawget(L, -2); g_assert(!lua_isnil(L, -1)); lua_pop(L, 1); /* Remove the Lua value */ lua_pushvalue(L, k > 0 ? k : k-1); lua_pushnil(L); lua_rawset(L, -3); /* Remove the registry */ lua_pop(L, 1); } void luaH_uniq_del_ptr(lua_State *L, const gchar *reg, gpointer key) { lua_pushlightuserdata(L, key); luaH_uniq_del(L, reg, -1); lua_pop(L, 1); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luauniq.h000066400000000000000000000031421475363222200161330ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LUAUNIQ_H #define LUAKIT_COMMON_LUAUNIQ_H #include #include /* Registry system for unique Lua objects. * In contrast to the luaobject system, useful when a unique Lua instance is * owned by a C object, this system should be used when the C instance lifetime * depends on the Lua instance lifetime. */ void luaH_uniq_setup(lua_State *L, const gchar *reg, const gchar *mode); int luaH_uniq_add(lua_State *L, const gchar *reg, int k, int oud); int luaH_uniq_add_ptr(lua_State *L, const gchar *reg, gpointer key, int oud); int luaH_uniq_get(lua_State *L, const gchar *reg, int k); int luaH_uniq_get_ptr(lua_State *L, const gchar *reg, gpointer key); void luaH_uniq_del(lua_State *L, const gchar *reg, int k); void luaH_uniq_del_ptr(lua_State *L, const gchar *reg, gpointer key); #endif /* end of include guard: LUAKIT_COMMON_LUAUNIQ_H */ // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luautil.c000066400000000000000000000162021475363222200161300ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include #include #include "common/luautil.h" #include "common/lualib.h" #include "common/log.h" #include "buildopts.h" gint luaH_traceback(lua_State *L, lua_State *T, gint min_level) { lua_Debug ar; gint max_level; gint loc_pad = 0; #define AR_SRC(ar) \ (g_strstr_len((ar).source, 3, "@./") ? (ar).source+3 : \ (ar).source[0] == '@' ? (ar).source+1 : \ (ar).short_src) #define LENF(fmt, ...) \ (snprintf(NULL, 0, fmt, ##__VA_ARGS__)) if (!lua_getstack(T, min_level, &ar)) { lua_pushliteral(L, ""); return 1; } /* Traverse the stack to determine max level and padding sizes */ for (gint level = min_level; lua_getstack(T, level, &ar); level++) { lua_getinfo(T, "Sl", &ar); max_level = level; gint cur_pad = LENF("%s:%d", AR_SRC(ar), ar.currentline); if (cur_pad > loc_pad) loc_pad = cur_pad; } GString *tb = g_string_new(""); gint level_pad = LENF("%d", max_level); for (gint level = min_level; level <= max_level; level++) { lua_getstack(T, level, &ar); lua_getinfo(T, "Sln", &ar); /* Current stack level */ gint shown_level = level - min_level + 1; g_string_append_printf(tb, ANSI_COLOR_GRAY "(%*d)" ANSI_COLOR_RESET " ", level_pad, shown_level); /* Current location, padded */ if (g_str_equal(ar.what, "C")) { g_string_append_printf(tb, "%-*s", loc_pad, "[C]"); } else { const char *src = AR_SRC(ar); int n; char cl[8] = ""; snprintf(cl, sizeof(cl), "%d", ar.currentline); n = strlen(src) + strlen(cl) + 1; g_string_append_printf(tb, "%s:%d", src, ar.currentline); g_string_append_printf(tb, "%*.*s", loc_pad-n, loc_pad-n, ""); } /* Function name */ if (g_str_equal(ar.what, "main")) { g_string_append(tb, ANSI_COLOR_GRAY " in main chunk" ANSI_COLOR_RESET); } else { g_string_append_printf(tb, ANSI_COLOR_GRAY " in function " ANSI_COLOR_RESET "%s", ar.name ?: "[anonymous]"); } if (level != max_level) { g_string_append(tb, "\n"); } } lua_pushstring(L, tb->str); g_string_free(tb, TRUE); return 1; } static const gchar * extract_error_message(lua_State *L, const gchar *message) { lua_Debug ar; for (gint level = 0; ; level++) { if (!lua_getstack(L, level, &ar)) return message; lua_getinfo(L, "Sl", &ar); if (!g_str_equal(ar.what, "C")) break; } if (strncmp(message, ar.short_src, strlen(ar.short_src))) return message; const gchar *tail = message + strlen(ar.short_src); if (*tail != ':') return message; tail ++; return strchr(tail, ' ') + 1; } gint luaH_dofunction_on_error(lua_State *L) { /* Guaranteed stack availability: LUA_MINSTACK = 20 * This function's stack use is five items, so even on stack overflow * there should be no problem producing a full stack trace. */ g_assert(lua_checkstack(L, 5)); lua_pushliteral(L, "Lua error: "); lua_pushstring(L, extract_error_message(L, lua_tostring(L, -2))); lua_pushliteral(L, "\nTraceback:\n"); luaH_traceback(L, L, 1); lua_concat(L, 4); return 1; } void luaH_add_paths(lua_State *L, const gchar *config_dir) { lua_getglobal(L, "package"); if(LUA_TTABLE != lua_type(L, -1)) { warn("package is not a table"); return; } lua_getfield(L, -1, "path"); if(LUA_TSTRING != lua_type(L, -1)) { warn("package.path is not a string"); lua_pop(L, 1); return; } /* compile list of package search paths */ GPtrArray *paths = g_ptr_array_new_with_free_func(g_free); #if DEVELOPMENT_PATHS /* allows for testing luakit in the project directory */ g_ptr_array_add(paths, g_strdup("./lib")); g_ptr_array_add(paths, g_strdup("./config")); #endif /* add luakit install path */ g_ptr_array_add(paths, g_build_filename(LUAKIT_INSTALL_PATH, "lib", NULL)); /* add users config dir (see: XDG_CONFIG_DIR) */ if (config_dir) g_ptr_array_add(paths, g_strdup(config_dir)); /* add system config dirs (see: XDG_CONFIG_DIRS) */ const gchar* const *config_dirs = g_get_system_config_dirs(); for (; *config_dirs; config_dirs++) g_ptr_array_add(paths, g_build_filename(*config_dirs, "luakit", NULL)); const gchar *path; for (guint i = 0; i < paths->len; i++) { path = paths->pdata[i]; /* Search for file */ lua_pushliteral(L, ";"); lua_pushstring(L, path); lua_pushliteral(L, "/?.lua"); lua_concat(L, 3); /* Search for lib */ lua_pushliteral(L, ";"); lua_pushstring(L, path); lua_pushliteral(L, "/?/init.lua"); lua_concat(L, 3); /* concat with package.path */ lua_concat(L, 3); } g_ptr_array_free(paths, TRUE); /* package.path = "concatenated string" */ lua_setfield(L, -2, "path"); /* remove package module from stack */ lua_pop(L, 1); } gint luaH_push_gerror(lua_State *L, GError *error) { g_assert(error); lua_createtable(L, 0, 2); lua_pushfstring(L, "%s-%d", g_quark_to_string(error->domain), error->code); lua_setfield(L, -2, "code"); lua_pushstring(L, error->message); lua_setfield(L, -2, "message"); return 1; } gint luaH_push_strv(lua_State *L, const gchar * const *strv) { lua_newtable(L); if (!strv) return 1; gint n = 1; while (*strv) { lua_pushstring(L, *strv); lua_rawseti(L, -2, n++); strv++; } return 1; } const gchar ** luaH_checkstrv(lua_State *L, gint idx) { luaH_checktable(L, idx); gint len = lua_objlen(L, idx); GPtrArray *langs = g_ptr_array_new(); for (gint i = 1; i <= len; ++i) { lua_rawgeti(L, idx, i); if (!lua_isstring(L, -1)) { g_ptr_array_free(langs, TRUE); luaL_error(L, "bad argument %d ({string} expected, but array item %d has type %s)", idx, i, lua_typename(L, lua_type(L, -1))); } g_ptr_array_add(langs, (gchar*)lua_tostring(L, -1)); lua_pop(L, 1); } g_ptr_array_add(langs, NULL); const gchar ** strv = (const gchar **)langs->pdata; g_ptr_array_free(langs, FALSE); return strv; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luautil.h000066400000000000000000000023411475363222200161340ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LUAUTIL_H #define LUAKIT_COMMON_LUAUTIL_H #include #include gint luaH_traceback(lua_State *L, lua_State *T, gint level); gint luaH_dofunction_on_error(lua_State *L); void luaH_add_paths(lua_State *L, const gchar *config_dir); gint luaH_push_gerror(lua_State *L, GError *error); gint luaH_push_strv(lua_State *L, const gchar * const *strv); const gchar ** luaH_checkstrv(lua_State *L, gint idx); #endif /* end of include guard: LUAKIT_COMMON_LUAUTIL_H */ // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luayield.c000066400000000000000000000132171475363222200162640ustar00rootroot00000000000000/* * common/luayield.c - Lua yield support * * Copyright © 2017 Aidan Holm * * 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 . * */ #include "common/luayield.h" #include "common/lualib.h" #include "common/luaobject.h" #include static void *wrap_function_ref; static void *yield_ref; static void *unlock_ref; static const char * sched_src = \ \ " local y = {} \n" \ " \n" \ " local wrap_function = function (fn) \n" \ " return function (...) \n" \ " assert(coroutine.running(), 'cannot call asynchronous function from main thread!') \n" \ " y.yieldable = true \n" \ " local ret = {fn(...)} \n" \ " y.yieldable = false \n" \ " if y.yield then \n" \ " y.yield = false \n" \ " y[coroutine.running()] = true \n" \ " repeat \n" \ " ret = {coroutine.yield()} \n" \ " until not y[coroutine.running()] \n" \ " end \n" \ " return unpack(ret) \n" \ " end \n" \ " end \n" \ " \n" \ " local yield = function () \n" \ " assert(y.yieldable, 'attempted to yield from unwrapped operation!') \n" \ " y.yield = true \n" \ " end \n" \ " \n" \ " local unlock = function () \n" \ " y[coroutine.running()] = nil \n" \ " end \n" \ " \n" \ " return { \n" \ " wrap_function = wrap_function, \n" \ " yield = yield, \n" \ " unlock = unlock, \n" \ " } \n" \ ; void luaH_yield_setup(lua_State *L) { gint top = lua_gettop(L); luaL_loadbuffer(L, sched_src, strlen(sched_src), "luakit_yield_handler"); luaH_dofunction(L, 0, 1); lua_getfield(L, -1, "yield"); yield_ref = luaH_object_ref(L, -1); lua_getfield(L, -1, "wrap_function"); wrap_function_ref = luaH_object_ref(L, -1); lua_getfield(L, -1, "unlock"); unlock_ref = luaH_object_ref(L, -1); lua_settop(L, top); } void luaH_yield_wrap_function(lua_State *L) { luaH_checkfunction(L, -1); luaH_object_push(L, wrap_function_ref); luaH_dofunction(L, 1, 1); } int luaH_yield(lua_State *L) { luaH_object_push(L, yield_ref); luaH_dofunction(L, 0, 0); return 0; } /** Continue a suspended Lua thread. * \param L The Lua stack. * \param nret The number of values to return to the suspended thread. * \return True on no error, false otherwise. */ gboolean luaH_resume(lua_State *L, gint nret) { luaH_object_push(L, unlock_ref); luaH_dofunction(L, 0, 0); gint top = lua_gettop(L) - nret; gint ret = lua_resume(L, nret); if (ret == 0 || ret == LUA_YIELD) return TRUE; lua_pushcfunction(L, luaH_dofunction_on_error); lua_insert(L, -2); lua_call(L, 1, 1); error("%s", lua_tostring(L, -1)); lua_settop(L, top); return FALSE; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/luayield.h000066400000000000000000000020571475363222200162710ustar00rootroot00000000000000/* * common/luayield.h - Lua yield support * * Copyright © 2017 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_LUAYIELD_H #define LUAKIT_COMMON_LUAYIELD_H #include #include void luaH_yield_setup(lua_State *L); void luaH_yield_wrap_function(lua_State *L); int luaH_yield(lua_State *L); gboolean luaH_resume(lua_State *L, gint nret); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/property.c000066400000000000000000000117031475363222200163360ustar00rootroot00000000000000/* * common/property.c - GObject property set/get lua functions * * Copyright © 2011 Mason Larobina * * 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 . * */ #include "luah.h" #include #include "common/property.h" #include #include #if SOUP_CHECK_VERSION(3,0,0) #include #else #include #define SOUP_HTTP_URI_FLAGS (G_URI_FLAGS_HAS_PASSWORD |\ G_URI_FLAGS_ENCODED_PATH |\ G_URI_FLAGS_ENCODED_QUERY |\ G_URI_FLAGS_ENCODED_FRAGMENT |\ G_URI_FLAGS_SCHEME_NORMALIZE) #endif static gint luaH_gobject_get(lua_State *L, property_t *p, GObject *object) { GUri *u; property_tmp_t tmp; #define TG_CASE(type, dest, pfunc) \ case type: \ g_object_get(object, p->name, &(dest), NULL); \ pfunc(L, dest); \ return 1; switch(p->type) { TG_CASE(BOOL, tmp.b, lua_pushboolean) TG_CASE(INT, tmp.i, lua_pushnumber) TG_CASE(FLOAT, tmp.f, lua_pushnumber) TG_CASE(DOUBLE, tmp.d, lua_pushnumber) case CHAR: g_object_get(object, p->name, &tmp.c, NULL); lua_pushstring(L, tmp.c); g_free(tmp.c); return 1; case URI: g_object_get(object, p->name, &u, NULL); tmp.c = u ? g_uri_to_string_partial (u, G_URI_HIDE_PASSWORD) : NULL; lua_pushstring(L, tmp.c); if (u) g_uri_unref(u); g_free(tmp.c); return 1; default: break; } /* unhandled property type */ g_assert_not_reached(); } static gboolean luaH_gobject_set(lua_State *L, property_t *p, gint vidx, GObject *object) { GUri *u; property_tmp_t tmp; size_t len; #define TS_CASE(type, cast, dest, cfunc) \ case type: \ dest = (cast)cfunc(L, vidx); \ g_object_set(object, p->name, dest, NULL); \ break; switch(p->type) { TS_CASE(BOOL, gboolean, tmp.b, luaH_checkboolean); TS_CASE(INT, gint, tmp.i, luaL_checknumber); TS_CASE(FLOAT, gfloat, tmp.f, luaL_checknumber); TS_CASE(DOUBLE, gdouble, tmp.d, luaL_checknumber); case CHAR: if (lua_isnil(L, vidx)) tmp.c = NULL; else tmp.c = (gchar*) luaL_checkstring(L, vidx); g_object_set(object, p->name, tmp.c, NULL); break; case URI: if (lua_isnil(L, vidx)) { g_object_set(object, p->name, NULL, NULL); break; } tmp.c = (gchar*) luaL_checklstring(L, vidx, &len); /* use http protocol if none specified */ if (!len || g_strrstr(tmp.c, "://")) tmp.c = g_strdup(tmp.c); else tmp.c = g_strdup_printf("http://%s", tmp.c); u = g_uri_parse(tmp.c, SOUP_HTTP_URI_FLAGS, NULL); gboolean valid = !u || ( (!g_strcmp0(g_uri_get_scheme(u), "http") || !g_strcmp0(g_uri_get_scheme(u), "https") ) && g_uri_get_host(u) && g_uri_get_path(u) ); if (valid) { g_object_set(object, p->name, u, NULL); g_free(tmp.c); } if (u) g_uri_unref(u); if (!valid) { lua_pushfstring(L, "invalid uri: %s", tmp.c); g_free(tmp.c); lua_error(L); } break; default: /* unhandled property type */ g_assert_not_reached(); } return TRUE; } gint luaH_gobject_index(lua_State *L, property_t *props, luakit_token_t tok, GObject *object) { for (property_t *p = props; p->tok; p++) if (p->tok == tok) return luaH_gobject_get(L, p, object); return 0; } gboolean luaH_gobject_newindex(lua_State *L, property_t *props, luakit_token_t tok, gint vidx, GObject *object) { for (property_t *p = props; p->tok; p++) { if (p->tok != tok) continue; if (p->writable) return luaH_gobject_set(L, p, vidx, object); else warn("read-only property: %s", p->name); break; } return FALSE; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/property.h000066400000000000000000000027161475363222200163470ustar00rootroot00000000000000/* * common/property.h - GObject property set/get lua functions * * Copyright © 2011 Mason Larobina * * 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 . * */ #ifndef LUAKIT_COMMON_PROPERTY_H #define LUAKIT_COMMON_PROPERTY_H #include #include #include "common/tokenize.h" typedef enum { BOOL, CHAR, DOUBLE, FLOAT, INT, URI, } property_value_t; typedef union { gchar *c; gboolean b; gdouble d; gfloat f; gint i; } property_tmp_t; typedef struct { luakit_token_t tok; const gchar *name; property_value_t type; gboolean writable; } property_t; gint luaH_gobject_index(lua_State *, property_t *, luakit_token_t, GObject *); gboolean luaH_gobject_newindex(lua_State *, property_t *, luakit_token_t, gint, GObject *); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/resource.c000066400000000000000000000037451475363222200163100ustar00rootroot00000000000000/* * Copyright © 2017 Aidan Holm * * 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 . * */ #include #include #include "buildopts.h" #include "common/resource.h" #include "common/log.h" static gchar *resource_path; static gchar **resource_paths; void resource_path_set(const gchar *path) { verbose("setting resource path '%s'", path); g_free(resource_path); resource_path = g_strdup(path); resource_paths = NULL; } gchar * resource_path_get() { return resource_path; } gchar * resource_find_file(const gchar *path) { g_assert(path); verbose("finding resource file '%s'", path); if (path[0] == '/') return g_strdup(path); if (!resource_paths) resource_paths = g_strsplit(resource_path, ";", 0); for (char **p = resource_paths; *p; p++) { gchar *full_path = g_build_filename(*p, path, NULL); if (access(full_path, R_OK)) debug("tried path '%s': %s", full_path, g_strerror(errno)); else { verbose("found resource file at '%s'", full_path); return full_path; } g_free(full_path); } verbose("no resource file found for '%s'", path); return NULL; } __attribute__((constructor)) static void resource_init(void) { resource_path = g_strdup("./resources;"LUAKIT_INSTALL_PATH"/resources"); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/resource.h000066400000000000000000000017041475363222200163060ustar00rootroot00000000000000/* * Copyright © 2017 Aidan Holm * * 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 . * */ #ifndef LUAKIT_COMMON_RESOURCE_H #define LUAKIT_COMMON_RESOURCE_H #include void resource_path_set(const gchar *path); gchar *resource_path_get(); gchar *resource_find_file(const gchar *path); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/signal.h000066400000000000000000000056711475363222200157430ustar00rootroot00000000000000/* * common/signal.h - Signal handling functions * * Copyright © 2010 Mason Larobina * Copyright © 2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_COMMON_SIGNAL_H #define LUAKIT_COMMON_SIGNAL_H #include #include "common/util.h" typedef GTree signal_t; typedef GPtrArray signal_array_t; /* signals tree key compare function */ static inline gint signal_cmp(gconstpointer a, gconstpointer b, gpointer UNUSED(p)) { return g_strcmp0(a, b); } /* signals tree data destroy function */ static inline void signal_array_destroy(gpointer *sigfuncs) { g_ptr_array_free((GPtrArray*) sigfuncs, TRUE); } /* create binary search tree for fast signal array lookups */ static inline signal_t* signal_new(void) { return (signal_t*) g_tree_new_full((GCompareDataFunc) signal_cmp, NULL, (GDestroyNotify) g_free, (GDestroyNotify) signal_array_destroy); } /* destory signals tree */ static inline void signal_destroy(signal_t *signals) { g_tree_destroy((GTree*) signals); } static inline signal_array_t* signal_lookup(signal_t *signals, const gchar *name) { return (signal_array_t*) g_tree_lookup((GTree*) signals, (gpointer) name); } /* add a signal inside a signal array */ static inline void signal_add(signal_t *signals, const gchar *name, gpointer func) { signal_array_t *sigfuncs = signal_lookup(signals, name); if (!sigfuncs) { sigfuncs = (signal_array_t*) g_ptr_array_new(); g_tree_insert((GTree*) signals, (gpointer) g_strdup(name), sigfuncs); } g_ptr_array_add((GPtrArray*) sigfuncs, func); } /* remove a signal inside a signal array */ static inline void signal_remove(signal_t *signals, const gchar *name, gpointer func) { signal_array_t *sigfuncs = signal_lookup(signals, name); if (sigfuncs) { g_ptr_array_remove((GPtrArray*) sigfuncs, func); /* prune empty sigfuncs array from the tree */ if (!sigfuncs->len) g_tree_remove((GTree*) signals, (gpointer) name); } } /* remove all signal inside a signal array */ static inline void signals_remove(signal_t *signals, const gchar *name) { signal_array_t *sigfuncs = signal_lookup(signals, name); if (sigfuncs) { g_tree_remove((GTree*) signals, (gpointer) name); } } #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/tokenize.list000066400000000000000000000063651475363222200170430ustar00rootroot00000000000000accept_policy align append auto_load_images bg bottom cache_dir can_go_back can_go_forward child children clear clear_search clipboard close_inspector config_dir confpath count current current_size cursive_font_family data_dir decorated default_font_family default_font_size default_monospace_font_size desktop_dir destination destroy dev_paths documents_dir download_dir editable elapsed_time enable_caret_browsing enable_developer_extras enable_html5_database enable_html5_local_storage enable_page_cache enable_plugins enable_scripts enable_site_specific_quirks enable_spatial_navigation enable_spell_checking enable_xss_auditor end entry error eval_js eventbox execpath fantasy_font_family fg filename focus focused font fullscreen get_title go_back go_forward hbox hide history homogeneous hovered_uri hpaned icon id indexof insert inspector install_path install_paths interval javascript_can_access_clipboard javascript_can_open_windows_automatically label left loading load_string margin margin_top margin_bottom margin_left margin_right maximized mime_type minimum_font_size monospace_font_family music_dir name notebook nounique pack pack1 pack2 pictures_dir plugged position primary print_backgrounds progress proxy_uri public_share_dir reload reload_bypass_cache remove reorder right sans_serif_font_family save screen scroll search search_next search_previous secondary selectable selection select_region serif_font_family send_key set_dark_mode set_default_size set_pdfjs set_title show show_border show_frame show_inspector show_tabs socket spacing source spell_checking_languages ssl_trusted start started status stop stylesheets suggested_filename switch templates_dir text title top total_size type urgency_hint uri user_agent value vbox verbose version videos_dir visible vpaned webkit_user_agent_version webkit_version webview width height textwidth window windows x xmax xpage_size y ymax ypage_size zoom_level is_loading allow_modal_dialogs default_charset draw_compositing_indicators enable_accelerated_2d_canvas enable_dns_prefetching enable_frame_flattening enable_fullscreen enable_hyperlink_auditing enable_java enable_javascript enable_media_stream enable_mediasource enable_resizable_text_areas enable_smooth_scrolling enable_tabs_to_links enable_webaudio enable_webgl enable_write_console_messages_to_stdout media_playback_allows_inline media_playback_requires_gesture pictograph_font_family zoom_text_only body create_element query rect tag_name attr click text_content scroll_x scroll_y inner_width inner_height webkit2 style parent inner_html src href submit checked css session_state tooltip overlay fill center baseline allow_overwrite document scrolled scrollbars min_size child_count first_child last_child prev_sibling next_sibling image scale spinner element_from_point owner_document add_event_listener remove_event_listener certificate allow_certificate options crash process_limit wrap_js editable is_playing_audio drawing_area invalidate pattern web_process_id cookies_storage private finished set_favicon_for_uri website_data fetch system_data_dirs system_config_dirs get_source is_alive can_focus resource_path hardware_acceleration_policy allow_file_access_from_file_urls allow_universal_access_from_file_urls client_rects root_win_xid win_xid replace stack visible_child luakit-2.4.0/common/util.c000066400000000000000000000042101475363222200154220ustar00rootroot00000000000000/* * common/util.c - useful functions * * Copyright © 2010 Mason Larobina * Copyright © 2007-2008 Julien Danjou * Copyright © 2006 Pierre Habouzit * * 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 . * */ #include "common/util.h" #include #include #include #include gboolean file_exists(const gchar *filename) { return (access(filename, F_OK) == 0); } /* Pretty-format calling function filename, function name & line number for * debugging purposes */ gchar* luaH_callerinfo(lua_State *L) { lua_Debug ar; /* get information about calling lua function */ if (lua_getstack(L, 1, &ar) && lua_getinfo(L, "Sln", &ar)) return g_strdup_printf("%s%s%s:%d", ar.short_src, ar.name ? ":" : "", ar.name ? ar.name : "", ar.currentline); return NULL; } gint luaH_panic(lua_State *L) { error("unprotected error in call to Lua API (%s)", lua_tostring(L, -1)); return 0; } gchar * strip_ansi_escapes(const gchar *in) { static GRegex *reg; if (!reg) { const gchar *expr = "[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]"; GError *err = NULL; reg = g_regex_new(expr, G_REGEX_DOTALL | G_REGEX_EXTENDED | G_REGEX_RAW, 0, &err); g_assert_no_error(err); } return g_regex_replace_literal (reg, in, -1, 0, "", 0, NULL); } GQuark luakit_error_quark(void) { return g_quark_from_static_string("LuakitError"); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/common/util.h000066400000000000000000000051651475363222200154410ustar00rootroot00000000000000/* * common/util.h - useful functions * * Copyright © 2010 Mason Larobina * Copyright © 2007-2008 Julien Danjou * Copyright © 2006 Pierre Habouzit * * 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 . * */ #ifndef LUAKIT_COMMON_UTIL_H #define LUAKIT_COMMON_UTIL_H #include #include #include #include #include #include "common/log.h" /* Useful macros */ #define NONULL(x) (x ? x : "") #define LENGTH(x) sizeof(x)/sizeof((x)[0]) #ifdef UNUSED #elif defined(__GNUC__) # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) #elif defined(__LCLINT__) # define UNUSED(x) /*@unused@*/ x #else # define UNUSED(x) x #endif #define WARN_UNUSED __attribute__ ((warn_unused_result)) /* stack pushing macros */ #define PB_CASE(t, b) case L_TK_##t: lua_pushboolean (L, b); return 1; #define PF_CASE(t, f) case L_TK_##t: lua_pushcfunction (L, f); return 1; #define PI_CASE(t, i) case L_TK_##t: lua_pushinteger (L, i); return 1; #define PN_CASE(t, n) case L_TK_##t: lua_pushnumber (L, n); return 1; #define PS_CASE(t, s) case L_TK_##t: lua_pushstring (L, s); return 1; #define PD_CASE(t, d) case L_TK_##t: lua_pushlightuserdata (L, d); return 1; /* A NULL resistant strlen. Unlike it's libc sibling, l_strlen returns a * ssize_t, and supports its argument being NULL. */ static inline ssize_t l_strlen(const gchar *s) { return s ? strlen(s) : 0; } static inline gdouble l_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + (tv.tv_usec / 1e6); } #define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count))) gboolean file_exists(const gchar*); void l_exec(const gchar*); gchar *luaH_callerinfo(lua_State*); gint luaH_panic(lua_State *L); gchar *strip_ansi_escapes(const gchar *in); /* Error codes */ GQuark luakit_error_quark(void); #define LUAKIT_ERROR luakit_error_quark() enum LuakitError { LUAKIT_ERROR_TLS, }; #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/config.mk000066400000000000000000000067751475363222200146310ustar00rootroot00000000000000# === Luakit Makefile Configuration ========================================== # Compile/link options. CC ?= gcc CFLAGS += -std=c11 -D_XOPEN_SOURCE=600 -W -Wall -Wextra -Werror=unused-result LDFLAGS += CPPFLAGS += PKG_CONFIG ?= pkg-config # Get current luakit version. VERSION := $(shell ./build-utils/getversion.sh) CPPFLAGS += -DVERSION=\"$(VERSION)\" # === Default build options ================================================== DEVELOPMENT_PATHS ?= 0 USE_LUAJIT ?= 1 # === Paths ================================================================== PREFIX ?= /usr/local MANPREFIX ?= $(PREFIX)/share/man DOCDIR ?= $(PREFIX)/share/luakit/doc XDGPREFIX ?= /etc/xdg PIXMAPDIR ?= $(PREFIX)/share/pixmaps APPDIR ?= $(PREFIX)/share/applications LIBDIR ?= $(PREFIX)/lib/luakit # Should luakit be built to load relative config paths (./lib ./config) ? # (Useful when running luakit from it's source directory, disable otherwise). ifneq ($(DEVELOPMENT_PATHS),0) CPPFLAGS += -DDEVELOPMENT_PATHS CFLAGS += -ggdb endif # === Platform specific ====================================================== uname_s := $(shell uname -s) # Mac OSX ifeq ($(uname_s),Darwin) LINKER_EXPORT_DYNAMIC = 0 endif # Solaris Derivates ifeq ($(uname_s),SunOS) LINKER_EXPORT_DYNAMIC = 0 LDFLAGS += -lsocket endif # Some systems need the --export-dynamic linker option to load other # dynamically linked C Lua modules (for example lua-filesystem). ifneq ($(LINKER_EXPORT_DYNAMIC),0) LDFLAGS += -Wl,--export-dynamic endif # === Lua package name detection ============================================= LUA_PKG_NAMES += lua-5.1 lua5.1 lua51 # Force linking against Lua's Just-In-Time compiler. # See http://luajit.org/ for more information. ifeq ($(USE_LUAJIT),1) LUA_PKG_NAME := luajit else # User hasn't specificed, use LuaJIT if we can find it. ifneq ($(USE_LUAJIT),0) LUA_PKG_NAMES := luajit $(LUA_PKG_NAMES) endif endif # Search for Lua package name if not forced by user. ifeq ($(LUA_PKG_NAME),) LUA_PKG_NAME = $(shell sh -c '(for name in $(LUA_PKG_NAMES); do \ $(PKG_CONFIG) --exists $$name && echo $$name; done) | head -n 1') endif # === Lua binary name detection ============================================= LUA_BIN_NAMES += lua-5.1 lua5.1 lua51 ifneq ($(USE_LUAJIT),0) LUA_BIN_NAMES := luajit luajit51 $(LUA_BIN_NAMES) endif # Search for Lua binary name if not forced by user. ifeq ($(LUA_BIN_NAME),) LUA_BIN_NAME := $(shell sh -c '(for name in $(LUA_BIN_NAMES); do \ hash $$name 2>/dev/null && ($$name -v 2>&1 | grep -Eq "^Lua 5\.1|^LuaJIT") && echo $$name; done) | head -n 1') endif ifeq ($(LUA_BIN_NAME),) $(error Cannot find the Lua binary name. \ Tried the following: $(LUA_BIN_NAMES). \ Manually override by setting LUA_BIN_NAME) endif # === Required build packages ================================================ # Packages required to build luakit. PKGS += gtk+-3.0 PKGS += gthread-2.0 PKGS += webkit2gtk-4.1 PKGS += sqlite3 PKGS += $(LUA_PKG_NAME) PKGS += javascriptcoregtk-4.1 # Check user has correct packages installed (and found by pkg-config). PKGS_OK := $(shell $(PKG_CONFIG) --print-errors --exists $(PKGS) && echo 1) ifneq ($(PKGS_OK),1) $(error Cannot find required package(s\) to build luakit. Please \ check you have the above packages installed and try again) endif # Add pkg-config options to compile flags. CFLAGS += $(shell $(PKG_CONFIG) --cflags $(PKGS)) CFLAGS += -I./ # Add pkg-config options to linker flags. LDFLAGS += $(shell $(PKG_CONFIG) --libs $(PKGS)) luakit-2.4.0/config/000077500000000000000000000000001475363222200142615ustar00rootroot00000000000000luakit-2.4.0/config/rc.lua000066400000000000000000000146571475363222200154050ustar00rootroot00000000000000------------------------------------------------------------------------------ -- luakit configuration file, more information at https://luakit.github.io/ -- ------------------------------------------------------------------------------ require "lfs" -- Check for lua configuration files that will never be loaded because they are -- shadowed by builtin modules. table.insert(package.loaders, 2, function (modname) if not package.searchpath then return end local f = package.searchpath(modname, package.path) if not f or f:find(luakit.install_paths.install_dir .. "/", 0, true) ~= 1 then return end local lf = luakit.config_dir .. "/" .. modname:gsub("%.","/") .. ".lua" if f == lf then msg.warn("Loading local version of '" .. modname .. "' module: " .. lf) elseif lfs.attributes(lf) then msg.warn("Found local version " .. lf .. " for core module '" .. modname .. "', but it won't be used, unless you update 'package.path' accordingly.") end end) require "unique_instance" -- Set the number of web processes to use. A value of 0 means 'no limit'. This -- has no effect since WebKit 2.26 luakit.process_limit = 4 -- Set the cookie storage location soup.cookies_storage = luakit.data_dir .. "/cookies.db" -- Load library of useful functions for luakit local lousy = require "lousy" -- Load users theme -- ("$XDG_CONFIG_HOME/luakit/theme.lua" or "/etc/xdg/luakit/theme.lua") lousy.theme.init(lousy.util.find_config("theme.lua")) assert(lousy.theme.get(), "failed to load theme") -- Load users window class -- ("$XDG_CONFIG_HOME/luakit/window.lua" or "/etc/xdg/luakit/window.lua") local window = require "window" -- Load users webview class -- ("$XDG_CONFIG_HOME/luakit/webview.lua" or "/etc/xdg/luakit/webview.lua") local webview = require "webview" -- Add luakit://log/ chrome page local log_chrome = require "log_chrome" window.add_signal("build", function (w) local widgets, l, r = require "lousy.widget", w.sbar.l, w.sbar.r -- Left-aligned status bar widgets l.layout:pack(widgets.uri()) l.layout:pack(widgets.hist()) l.layout:pack(widgets.progress()) -- Right-aligned status bar widgets r.layout:pack(widgets.buf()) r.layout:pack(log_chrome.widget()) r.layout:pack(widgets.ssl()) r.layout:pack(widgets.tabi()) r.layout:pack(widgets.scroll()) end) -- Load luakit binds and modes local modes = require "modes" local binds = require "binds" local settings = require "settings" require "settings_chrome" ---------------------------------- -- Optional user script loading -- ---------------------------------- -- Add adblock local adblock = require "adblock" local adblock_chrome = require "adblock_chrome" local webinspector = require "webinspector" -- Add uzbl-like form filling local formfiller = require "formfiller" -- Add proxy support & manager local proxy = require "proxy" -- Add cache control (clear-data, clear-favicon-db) local clear_data = require "clear_data" -- Add quickmarks support & manager local quickmarks = require "quickmarks" -- Add session saving/loading support local session = require "session" -- Add command to list closed tabs & bind to open closed tabs local undoclose = require "undoclose" -- Add command to list tab history items local tabhistory = require "tabhistory" -- Add command to list open tabs local tabmenu = require "tabmenu" -- Allow for tabs to be grouped together. -- One tab group is displayed in a window at any given time. --local tabgroups = require "tabgroups" -- Add gopher protocol support (this module needs luasocket) -- local gopher = require "gopher" -- Add greasemonkey-like javascript userscript support local userscripts = require "userscripts" -- Add bookmarks support local bookmarks = require "bookmarks" local bookmarks_chrome = require "bookmarks_chrome" -- Add download support local downloads = require "downloads" local downloads_chrome = require "downloads_chrome" -- Add automatic PDF downloading and opening local viewpdf = require "viewpdf" -- Example using xdg-open for opening downloads / showing download folders downloads.add_signal("open-file", function (file) luakit.spawn(string.format("xdg-open %q", file)) return true end) -- Add vimperator-like link hinting & following local follow = require "follow" -- Add command history local cmdhist = require "cmdhist" -- Add search mode & binds local search = require "search" -- Add ordering of new tabs local taborder = require "taborder" -- Save web history local history = require "history" local history_chrome = require "history_chrome" local help_chrome = require "help_chrome" local binds_chrome = require "binds_chrome" -- Add command completion local completion = require "completion" -- Press Control-E while in insert mode to edit the contents of the currently -- focused ]==] local main_js = [=[ function empty ($el) { while ($el.firstChild) $el.removeChild($el.firstChild) } window.addEventListener('load', () => { const limit = 100 let resultsLen = 0 const $editDialog = document.getElementById('edit-dialog') const $editView = document.getElementById('edit-view') const $next = document.getElementById('nav-next') const $page = document.getElementById('page') const $prev = document.getElementById('nav-prev') const $results = document.getElementById('results') const $search = document.getElementById('search') $page.value = $page.value || 1 function makeBookmark (b) { b.tags = b.tags || '' let tagArray = b.tags.split(' ').filter(tag => tag) function escapeHTML(string) { let entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' } return String(string).replace(/[&<>"'`=\/]/g, s => entityMap[s]); } return `
    ${b.uri}
    ${b.markdown_desc}
    ${b.date} ${ tagArray.map(tag => ''+tag+'').join('') } edit delete
    ` } function search () { bookmarks_search({ query: $search.value, limit: limit, page: parseInt($page.value, 10) }).then(results => { resultsLen = results.length || 0 empty($results) if (results.length == null) { updateNavButtons() return } $results.innerHTML = results.map(makeBookmark).join('') updateNavButtons() }) } function updateNavButtons () { $next.style.display = resultsLen === limit ? 'inline' : 'none' $prev.style.display = parseInt($page.value, 10) > 1 ? 'inline' : 'none' } function getID ($el) { do { $el = $el.parentNode } while ($el && !$el.classList.contains('bookmark')) return parseInt($el.dataset.id, 10) } function showEdit (b) { b = b || {} $editDialog.id.value = b.id || '' $editDialog.created.value = b.created || '' $editDialog.title.value = b.title || '' $editDialog.uri.value = b.uri || '' $editDialog.tags.value = b.tags || '' $editDialog.desc.value = b.desc || '' $editView.style.display = 'block' $editDialog.title.focus() } $search.addEventListener('keydown', event => { if (event.which === 13) { // 13 is the code for the 'Return' key $page.value = 1 search() $search.blur() reset_mode() } }) $editDialog.addEventListener('submit', event => { event.preventDefault() let id = $editDialog.id.value let created = $editDialog.created.value ? parseInt($editDialog.created.value) : undefined let title = $editDialog.title.value let uri = $editDialog.uri.value let tags = $editDialog.tags.value let desc = $editDialog.desc.value bookmarks_add(uri, { title, tags, desc, created }) if (id) bookmarks_remove(parseInt(id)) search() $editView.style.display = 'none' }) document.getElementsByClassName('cancel-button')[0] .addEventListener('click', () => { $editView.style.display = 'none' }) document.getElementById('new-button').addEventListener('click', showEdit) document.getElementById('clear-button') .addEventListener('click', () => { $search.value = '' $page.value = 1 search() }) document.getElementById('search-button') .addEventListener('click', () => { $page.value = 1 search() }) $next.addEventListener('click', () => { let page = parseInt($page.value, 10) $page.value = page + 1 search() }) $prev.addEventListener('click', () => { let page = parseInt($page.value, 10) $page.value = Math.max(page - 1, 1) search() }) document.addEventListener('click', event => { if (event.target.matches('.tags > a')) { $search.value = event.target.textContent search() } else if (event.target.matches('.controls > .edit')) { bookmarks_get(getID(event.target)).then(showEdit) } else if (event.target.matches('.controls > .delete')) { bookmarks_remove(getID(event.target)) search() } }) search() new_bookmark_values().then(values => { if (values) showEdit(values) }) }) ]=] local new_bookmark_values local export_funcs = { bookmarks_search = function (_, opts) if not bookmarks.db then bookmarks.init() end local sql = { "SELECT", "*", "FROM bookmarks" } local where, args, argc = {}, {}, 1 string.gsub(opts.query or "", "(-?)([^%s]+)", function (notlike, term) if term ~= "" then table.insert(where, (notlike == "-" and "NOT " or "") .. string.format("(text GLOB ?%d)", argc, argc)) argc = argc + 1 table.insert(args, "*"..string.lower(term).."*") end end) if #where ~= 0 then sql[2] = [[ *, lower(uri||title||desc||tags) AS text ]] table.insert(sql, "WHERE " .. table.concat(where, " AND ")) end local order_by = [[ ORDER BY created DESC LIMIT ?%d OFFSET ?%d ]] table.insert(sql, string.format(order_by, argc, argc+1)) local limit, page = opts.limit or 100, opts.page or 1 table.insert(args, limit) table.insert(args, limit > 0 and (limit * (page - 1)) or 0) sql = table.concat(sql, " ") if #where ~= 0 then local wrap = [[SELECT id, uri, title, desc, tags, created, modified FROM (%s)]] sql = string.format(wrap, sql) end local rows = bookmarks.db:exec(sql, args) local date = os.date for _, row in ipairs(rows) do row.date = date("%d %B %Y", row.created) local desc = row.desc if desc and string.find(desc, "%S") then row.markdown_desc = markdown(desc) end end return rows end, bookmarks_add = function (_, ...) return bookmarks.add(...) end, bookmarks_get = function (_, ...) return bookmarks.get(...) end, bookmarks_remove = function (_, ...) return bookmarks.remove(...) end, new_bookmark_values = function (_) local values = new_bookmark_values new_bookmark_values = nil return values end, } chrome.add("bookmarks", function () local style = chrome.stylesheet .. _M.stylesheet if not _M.show_uri then style = style .. " .bookmark .uri { display: none !important; } " end local html = string.gsub(html_template, "{%%(%w+)}", { stylesheet = style, javascript = main_js, }) return html end, nil, export_funcs) --- URI of the bookmarks chrome page. -- @type string -- @readonly _M.chrome_page = "luakit://bookmarks/" add_binds("normal", { { "B", "Add a bookmark for the current URL.", function(w) new_bookmark_values = { uri = w.view.uri, title = w.view.title } w:new_tab(_M.chrome_page) end }, { "^gb$", "Open the bookmarks manager in the current tab.", function(w) w:navigate(_M.chrome_page) end }, { "^gB$", "Open the bookmarks manager in a new tab.", function(w) w:new_tab(_M.chrome_page) end } }) add_cmds({ { ":bookmarks", "Open the bookmarks manager in a new tab.", { func = function (w) local view for _, tab in ipairs(w.tabs.children) do if tab.uri == _M.chrome_page then msg.verbose("bookmarks: using existing bookmarks manager tab") view = tab break end end if view then w.tabs:switch(w.tabs:indexof(view)) else w:new_tab(_M.chrome_page) end end, }}, { ":bookmark", "Add a bookmark for the current URL.", { func = function (w, o) local a = o.arg if not a then new_bookmark_values = { uri = w.view.uri, title = w.view.title } else a = lousy.util.string.split(a) new_bookmark_values = { uri = a[1], tags = table.concat(a, " ", 2) } end w:new_tab(_M.chrome_page) end, format = "{uri}", }}, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/chrome.lua000066400000000000000000000233341475363222200155470ustar00rootroot00000000000000--- Add custom luakit:// scheme rendering functions. -- -- This module provides a convenient interface for other modules to add -- `luakit://` chrome pages, with features like a shared theme, error reporting, -- and Lua to JavaScript function bridge management. -- -- @module chrome -- @copyright 2010-2012 Mason Larobina -- @copyright 2010 Fabian Streitel local error_page = require("error_page") local lousy = require("lousy") local webview = require("webview") local window = require("window") local wm = require_web_module("chrome_wm") local _M = {} --- Common stylesheet that can be sourced from several chrome modules -- for a consitent looking theme. -- @type string -- @readwrite _M.stylesheet = [===[ * { box-sizing: border-box; } body { background-color: white; color: #222; display: block; margin: 0; padding: 0; font-family: sans-serif; } #page-header { display: flex; -webkit-align-items: center; background-color: #eee; position: fixed; top: 0; left: 0; width: 100%; margin: 0; padding: 0 1.5em; height: 3.5em; border-bottom: 1px solid #ddd; -webkit-user-select: none; overflow-y: hidden; z-index: 100000; } #page-header > h1 { font-size: 1.4rem; margin: 0 1em; color: #445; cursor: default; } #page-header > h1:first-child { margin-left: 0; } .content-margin { padding: 3.5em 1.5em 0 1.5em; } h2 { font-size: 1.2rem; } h3 { font-size: 1.1rem; color: #666; } #page-header input { font-size: 0.8rem; padding: 0.5rem 0.75rem; border: none; outline: none; margin-top: 0; margin-bottom: 0; background-color: #fff; } #page-header #search-box { display: flex; padding: 0; background-color: #fff; border-radius: 0.25rem; box-shadow: 0 1px 1px #888; } #page-header #search { width: 20em; font-weight: normal; border-radius: 0.25rem 0 0 0.25rem; margin: 0; padding-right: 0; } #page-header #clear-button { margin: 0; padding: 0.5rem 0.55rem; border-radius: 0 0.25rem 0.25rem 0; box-shadow: none; font-size: 1rem; line-height: 1rem; } #page-header #clear-button:hover { color: #000; } #page-header #clear-button:active { background-color: #eee; } .button { box-shadow: 0 1px 1px #888; margin: 1rem 0 1rem 0.5rem; border-radius: 0.25em; color: #888; display: inline-block; line-height: 1.25; text-align: center; white-space: nowrap; vertical-align: middle; -webkit-user-select: none; border: 1px solid transparent; padding: .5rem 1rem; font-size: 1rem; border-radius: .25rem; transition: color .1s ease-in-out, background-color .1s ease-in-out; cursor: pointer; } #page-header .button:hover, .button:hover { color: #000; } #page-header .button:active, .button:active { background-color: #eee; } #page-header .button[disabled], .button[disabled] { color: #888; background-color: #eee; cursor: not-allowed; } #page-header .rhs { display: flex; -webkit-align-items: center; position: fixed; top: 0; right: 0; margin: 0; padding-right: 1.5em; height: 3.5em; margin: 0; background-color: inherit; box-shadow: -1em 0 1em #eee; } #page-header .rhs .button { margin-bottom: 0; } .license { font-family: monospace; } .hidden { display: none; } ]===] -- luakit:// page handlers local handlers = {} local on_first_visual_handlers = {} local page_funcs = {} --- Retrieve a list of the currently registered luakit:// handlers. -- @treturn {string} A list of `luakit://` handler names, in alphabetical order. function _M.available_handlers() return lousy.util.table.keys(handlers) end --- Register a chrome page URI with an associated handler function. -- @tparam string page The name of the chrome page to register. -- @tparam function func The handler function for the chrome page. -- @tparam function on_first_visual_func An optional handler function -- for the chrome page, called when the page first finishes loading. -- @tparam table export_funcs An optional table of functions to -- export to JavaScript. function _M.add(page, func, on_first_visual_func, export_funcs) -- Do some sanity checking assert(type(page) == "string", "invalid chrome page name (string expected, got "..type(page)..")") assert(string.match(page, "^[%w%-]+$"), "illegal characters in chrome page name: " .. page) assert(type(func) == "function", "invalid chrome handler (function expected, got "..type(func)..")") assert(type(on_first_visual_func) == "nil" or type(on_first_visual_func) == "function", "invalid chrome handler (function/nil expected, got "..type(on_first_visual_func)..")") for name, export_func in pairs(export_funcs or {}) do assert(type(name) == "string") assert(type(export_func) == "function") end handlers[page] = func on_first_visual_handlers[page] = on_first_visual_func page_funcs[page] = export_funcs if page_funcs[page] then page_funcs[page].reset_mode = function (view) for _, w in pairs(window.bywidget) do if w.view == view then w:set_mode() end end end end end --- Remove a regeistered chrome page. -- @tparam string page The name of the chrome page to remove. function _M.remove(page) handlers[page] = nil on_first_visual_handlers[page] = nil end luakit.register_scheme("luakit") -- Catch all navigations to the luakit:// scheme webview.add_signal("init", function (view) view:add_signal("scheme-request::luakit", function (v, uri, request) -- Match "luakit://page/path" local page, path = string.match(uri, "^luakit://([^/]+)/?(.*)") if not page then return end local func = handlers[page] if func then -- Give the handler function everything it may need local w = webview.window(v) local meta = { page = page, path = path, w = w, uri = "luakit://" .. page .. "/" .. path, request = request } -- Render error output in webview with traceback local function error_handler(err) error_page.show_error_page(v, { heading = "Chrome handler error", content = [==[

    An error occurred in the luakit://{page}/ handler function:

    {traceback}
    ]==], buttons = {}, page = page, traceback = debug.traceback(err, 2), request = request, }) end -- Call luakit:// page handler local ok, html, mime = xpcall(function () return func(v, meta) end, error_handler) if ok and not request.finished then request:finish(html, mime) end return end -- Load error page error_page.show_error_page(v, { heading = "Chrome handler error", content = [==[

    No chrome handler for luakit://{page}/

    ]==], buttons = {}, page = page, request = request, }) end) view:add_signal("load-status", function (v, status) -- Wait for new page to be created if status ~= "finished" then return end -- Match "luakit://page/path" local page, path = string.match(v.uri, "^luakit://([^/]+)/?(.*)") if not page then return end -- Ensure we have a hook to call local on_first_visual_func = on_first_visual_handlers[page] if not on_first_visual_func then return end local w = webview.window(v) local meta = { page = page, path = path, w = w, uri = "luakit://" .. page .. "/" .. path } -- Call the supplied handler on_first_visual_func(v, meta) end) -- Always enable JavaScript on luakit:// pages; without this, chrome -- pages which depend upon javascript will break view:add_signal("enable-scripts", function (v) if v.uri:match("^luakit://") then return true end end) end) wm:add_signal("function-call", function (_, page_id, page_name, func_name, id, args) local func = assert(page_funcs[page_name][func_name]) -- Find view local view for _, w in pairs(window.bywidget) do for _, v in pairs(w.tabs.children) do if v.id == page_id then view = v end end end -- Call Lua function, return result local ok, ret = xpcall( function () return func(view, unpack(args)) end, function (err) msg.error(debug.traceback(err, 3)) end) wm:emit_signal(view, "function-return", id, ok, ret) end) luakit.add_signal("web-extension-created", function () for page, export_funcs in pairs(page_funcs) do for name in pairs(export_funcs or {}) do wm:emit_signal("register-function", page, name) end end end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/chrome_wm.lua000066400000000000000000000015331475363222200162470ustar00rootroot00000000000000-- Add custom luakit:// scheme rendering functions. -- @submodule chrome -- @copyright 2017 Aidan Holm local ui = ipc_channel("chrome_wm") local _M = {} local pending = {} local next_id = 0 ui:add_signal("function-return", function (_, _, id, ok, ret) local callbacks = assert(pending[id]) pending[id] = nil (callbacks[ok and "resolve" or "reject"])(ret) end) ui:add_signal("register-function", function (_, _, page_name, func_name) local pattern = "^luakit://" .. page_name .. "/?(.*)" luakit.register_function(pattern, func_name, function (page, resolve, reject, ...) pending[next_id] = { resolve = resolve, reject = reject } ui:emit_signal("function-call", page.id, page_name, func_name, next_id, {...}) next_id = next_id + 1 end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/clear_data.lua000066400000000000000000000073031475363222200163470ustar00rootroot00000000000000--- Clear website data. -- -- This module provides an interface to clear website data, such as cache, -- cookies, local storage and databases. -- -- @module clear_data -- @copyright 2019 Ulrik de Muelenaere local binds = require("binds") local lousy = require("lousy") local modes = require("modes") local _M = {} local function format_bytes(bytes) if bytes < 1024 then return string.format("%d B", bytes) elseif bytes < 1024 * 1024 then return string.format("%.1f KiB", bytes / 1024) else return string.format("%.1f MiB", bytes / (1024 * 1204)) end end local data_type_binds = {} local function list_data_types(data) local s = "" for _, data_type in ipairs(lousy.util.table.keys(data)) do s = s .. ", " .. data_type:gsub(data_type_binds[data_type], "%0", 1) if data[data_type] ~= 0 then s = s .. " (" .. format_bytes(data[data_type]) .. ")" end end return s:sub(3) end modes.new_mode("clear-data", { enter = function (w) coroutine.wrap(function () local rows = { {"Domain", "Data", title = true}, {"all", "all", clear_all = true}, } local website_data = luakit.website_data.fetch({"all"}) for _, domain in ipairs(lousy.util.table.keys(website_data)) do table.insert(rows, {domain, list_data_types(website_data[domain])}) end w.menu:build(rows) w:notify("Use j/k to move, Return to clear all data, or c/d/e/h/i/l/m/o/p/s/w for a specific type.", false) end)() end, leave = function (w) w.menu:hide() end, }) local function add_clear_bind(bind, data_type) data_type_binds[data_type] = bind modes.add_binds("clear-data", { { bind, "Clear `"..data_type.."` for the focused domain.", function (w) local focused_row = w.menu:get() if not focused_row then return end coroutine.wrap(function() if focused_row.clear_all then luakit.website_data.clear({data_type}) else luakit.website_data.remove({data_type}, focused_row[1]) end local website_data = luakit.website_data.fetch({"all"}) local i = 3 -- Skip title and "all" row while w.menu:get(i) do local row = w.menu:get(i) local domain = row[1] if website_data[domain] then row[2] = list_data_types(website_data[domain]) i = i + 1 else w.menu:del(i) end end w.menu:update() end)() end }, }) end add_clear_bind("", "all") for _, args in ipairs({ {"m", "memory_cache"}, {"d", "disk_cache"}, {"o", "offline_application_cache"}, {"s", "session_storage"}, {"l", "local_storage"}, {"i", "indexeddb_databases"}, {"p", "plugin_data"}, {"c", "cookies"}, {"e", "device_id_hash_salt"}, {"h", "hsts_cache"}, }) do add_clear_bind(unpack(args)) end modes.add_binds("clear-data", binds.menu_binds) modes.add_cmds({ { ":clear-data", "Open menu to clear website data.", function (w) w:set_mode("clear-data") end }, { ":clear-favicon-db", "Clear the favicon cache database.", function () luakit.clear_favicon_database() end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/cmdhist.lua000066400000000000000000000074441475363222200157310ustar00rootroot00000000000000--- Enables command history in modes that support it. -- -- This module adds support for modes to specify that user input on the -- command line should be recorded, so that users can scroll back through -- previous input with the arrow keys. It is used to implement history for the -- `command` mode. -- -- @module cmdhist -- @copyright 2010 Mason Larobina local window = require("window") local lousy = require("lousy") local modes = require("modes") local _M = {} -- Input bar history binds, these are only present in modes with a history -- table so we can make some assumptions. This auto-magic is present when -- a mode contains a `history` table item (with history settings therein). --- Key binding for history prev command -- @type string -- @readwrite _M.history_prev = "" --- Key binding for history next command -- @type string -- @readwrite _M.history_next = "" local function filter (t, f) local T = {} for _, v in ipairs(t) do if v:find(f, 1, true) then table.insert(T, v) end end return T end --- Go to prev cmdhist item _M.history_prev_func = function (w) local h = w.mode.history h.filtered = h.filtered or filter(h.items, w.ibar.input.text) local lc = h.cursor if not h.cursor and #h.filtered > 0 then h.cursor = #h.filtered elseif (h.cursor or 0) > 1 then h.cursor = h.cursor - 1 end if h.cursor and h.cursor ~= lc then if not h.orig then h.orig = w.ibar.input.text end w:set_input(h.filtered[h.cursor]) end end --- Go to next cmdhist item _M.history_next_func = function (w) local h = w.mode.history if not h.cursor then return end if h.cursor >= #h.filtered then w:set_input(h.orig) h.cursor = nil h.orig = nil h.filtered = nil else h.cursor = h.cursor + 1 w:set_input(h.filtered[h.cursor]) end end modes.new_mode("cmdhist", [[A meta-mode to allow `modes.add_binds()` style key bindings for command history. They will be applied to any other mode which has a `history` table. The cmdhist mode should not be entered/activated.]], { enter = function (w) w:error("cmdhist mode was entered") end, }) modes.add_binds("cmdhist", { { _M.history_prev, "Previous in command history.", _M.history_prev_func }, { _M.history_next, "Next in command history.", _M.history_next_func }, }) window.add_signal("init", function (w) -- Add Prev & Next (and other user-specified) keybindings to modes which support command history for name,m in pairs(modes.get_modes()) do if m.history then modes.add_binds(name, modes.get_mode("cmdhist").binds) end end w:add_signal("mode-entered", function () local mode = w.mode -- Setup history state if mode and mode.history then local h = mode.history -- Load history if not h.items then local f = io.open(luakit.data_dir .. "/command-history") if f then h.items = lousy.pickle.unpickle(f:read("*a"))[mode.name] f:close() end -- The function could return if history is empty h.items = h.items or {} end h.len = #(h.items) h.cursor = nil h.orig = nil h.filtered = nil -- Trim history if h.maxlen and h.len > (h.maxlen * 1.5) then local items = {} for i = (h.len - h.maxlen), h.len do table.insert(items, h.items[i]) end h.items = items h.len = #items end end end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/completion.lua000066400000000000000000000315541475363222200164460ustar00rootroot00000000000000--- Command completion. -- -- This module provides tab completion for luakit commands. Currently, it -- supports completing URLs from the user's bookmarks and history, and also -- supports completing partially typed commands. -- -- @module completion -- @copyright 2010-2011 Mason Larobina -- @copyright 2010 Fabian Streitel local lousy = require("lousy") local history = require("history") local bookmarks = require("bookmarks") local modes = require("modes") local settings = require("settings") local new_mode, get_mode = modes.new_mode, modes.get_mode local add_binds = modes.add_binds local escape = lousy.util.escape local _M = {} -- Store completion state (indexed by window) local data = setmetatable({}, { __mode = "k" }) -- Add completion start trigger add_binds("command", { { "", "Open completion menu.", function (w) w:set_mode("completion") end }, }) --- Return to command mode with original text and with original cursor position. function _M.exit_completion(w) local state = data[w] w:enter_cmd(state.orig_text) end local parse_completion_format = function (fmt) if type(fmt) == "table" then return fmt end local parts, ret = lousy.util.string.split(fmt, "%s+"), {} for i, part in ipairs(parts) do local grp = part:match("^{([%w-]+)}$") if i > 1 then ret[#ret+1] = { lit = "%s+", pattern = true } end ret[#ret+1] = grp and { grp = grp } or { lit = part } end return ret end local completers = {} local function parse(buf) local function match_step (state, matches) local new_states = {} for _, s in ipairs(state) do local nup = s[s.pos] -- next unmatched part if not nup then -- fully parsed table.insert(matches.full, s) elseif nup.lit then -- literal (with possible %s+) local m = nup.pattern and s.buf:match(nup.lit) or (s.buf:find(nup.lit, 1, true) and nup.lit or nil) if not m then if #s.buf < #nup.lit and nup.lit:sub(1,#s.buf) == s.buf then table.insert(matches.partial, s) end else table.insert(new_states, lousy.util.table.join(s, { buf = s.buf:sub(#m+1), pos = s.pos+1 })) end elseif nup.grp then -- completion group name local cgroup = assert(completers[nup.grp], "No completion group '".. nup.grp .. "'") local cresults = assert(cgroup.func(s.buf)) for _, cr in ipairs(cresults) do local crf = type(cr) == "table" and cr.format or cr local parts = parse_completion_format(crf) local ns = lousy.util.table.join(s) -- Replace current completion part with all returned parts table.remove(ns, ns.pos) for i, part in ipairs(parts) do table.insert(ns, ns.pos+i-1, part) end ns[ns.pos].row = cr ns[ns.pos].orig_grp = nup.grp if cr.buf then -- to complete from this state, we need to change the buffer -- so it's a partial match ns.buf = cr.buf table.insert(matches.partial, ns) else table.insert(new_states, ns) end end else error "Bad parsing part (expected lit or grp)" end end return new_states end -- Generate completion options with format strings local matches = { full = {}, partial = {} } local states = {{ { lit = ":"}, { grp = "command" }, buf = buf, pos = 1, }} repeat states = match_step(states, matches) until #states == 0 return matches end local function complete(buf) local matches, rows = parse(buf).partial, {} local pat2lit = function (p) return p == "%s+" and " " or p end local prev_grp for _, m in ipairs(matches) do if m[m.pos].lit == "%s+" and m.pos > 1 then m.pos = m.pos-1 end local grp = m[m.pos].orig_grp if prev_grp ~= grp then prev_grp = grp table.insert(rows, lousy.util.table.join(completers[grp].header, { title = true })) end local whole = "" for i=1,m.pos do whole = whole .. pat2lit(m[i].lit) end table.insert(rows, { m[m.pos].row[1], m[m.pos].row[2], text = whole }) end return rows end --- Update the list of completions for some input text. -- @tparam table w The current window table. -- @tparam string text The current input text. -- @tparam number pos The current input cursor position. function _M.update_completions(w, text, pos) local state = data[w] -- Other parts of the code are triggering input changed events if state.lock then return end local input = w.ibar.input text, pos = text or input.text, pos or input.position if pos ~= #text then _M.exit_completion(w) return end -- Don't rebuild the menu if the text & cursor position are the same if text == state.text and pos == state.pos then return end -- Update left and right strings state.text, state.pos = text, pos local rows = complete(text) if rows[2] then -- Prevent callbacks triggering recursive updates. state.lock = true w.menu:build(rows) w.menu:show() if not state.built then state.built = true if rows[2] then w.menu:move_down() end end state.lock = false else _M.exit_completion(w) end end local function input_change_cb (w) if not data[w].lock then local input = w.ibar.input data[w].orig_text = input.text data[w].orig_pos = input.position if input.position == 0 then _M.exit_completion(w) else _M.update_completions(w) end end end new_mode("completion", { enter = function (w) -- Clear state local state = {} data[w] = state -- Save original text and cursor position local input = w.ibar.input state.orig_text = input.text state.orig_pos = input.position -- Update input text when scrolling through completion menu items w.menu:add_signal("changed", function (_, row) state.lock = true if row then input.text = row.text input.position = #row.text else input.text = state.orig_text input.position = state.orig_pos end state.lock = false end) _M.update_completions(w) end, changed = input_change_cb, move_cursor = input_change_cb, leave = function (w) w.menu:hide() w.menu:remove_signals("changed") end, activate = function (w, text) _M.exit_completion(w) w:enter_cmd(text) w:activate() end, }) -- Command completion binds add_binds("completion", { { "", "Select next matching completion item.", function (w) w.menu:move_down() end }, { "", "Select previous matching completion item.", function (w) w.menu:move_up() end }, { "", "Select next matching completion item.", function (w) w.menu:move_up() end }, { "", "Select previous matching completion item.", function (w) w.menu:move_down() end }, { "", "Select next matching completion item.", function (w) w.menu:move_down() end }, { "", "Select previous matching completion item.", function (w) w.menu:move_up() end }, { "", "Stop completion and restore original command.", _M.exit_completion }, { "", "Stop completion and restore original command.", _M.exit_completion }, }) completers.command = { header = { "Command", "Description" }, func = function (rem) local prefix, rets = rem:match("^([%w-]*)"), {} -- Check each command binding for matches local cmds = {} for _, m in ipairs(get_mode("command").binds) do local b, a = unpack(m) if m.cmds or (b and b:match("^:")) then local c = m.cmds or {} if not m.cmds then for _, cmd in ipairs(lousy.util.string.split(b:gsub("^:", ""), ",%s+:")) do if string.match(cmd, "^([%-%w]+)%[(%w+)%]") then local l, r = string.match(cmd, "^([%-%w]+)%[(%w+)%]") table.insert(c, l..r) table.insert(c, l) else table.insert(c, cmd) end end end for i, cmd in ipairs(c) do if string.find(cmd, prefix, 1, true) == 1 then if i == 1 then cmd = ":" .. cmd else cmd = string.format(":%s (:%s)", cmd, c[1]) end local format = c[1] .. (a.format and (" "..a.format) or "") cmds[cmd] = { escape(cmd), escape(m[2].desc) or "", format = format } break end end end end local keys = lousy.util.table.keys(cmds) for _, k in ipairs(keys) do rets[#rets+1] = cmds[k] end return rets end, } local function sql_like_globber(term) local escaped = term:gsub("[\\%%_]", { ["\\"] = "\\\\", ["%"] = "\\%", ["_"] = "\\_" }) return "%" .. escaped:gsub("%s+", "%%") .. "%" end settings.register_settings({ ["completion.history.order"] = { type = "string", default = "visits", desc = [=[ A string indicating how history items should be sorted in completion. Possible values are: - `visits`: most visited websites first - `last_visit`: most recent websites first - `title`: sort by title, alphabetically - `uri`: sort by website address, alphabetically ]=], validator = function (v) local t = {visits = true, last_visit = true, title = true, uri = true} return t[v] end }, ["completion.max_items"] = { type = "number", min = 1, default = 25, desc = "Number of completion items for history and bookmarks." } }) completers.history = { header = { "History", "URI" }, func = function (buf) local order = settings.get_setting("completion.history.order") local desc = (order == "visits" or order == "last_visit") and " DESC" or "" local term, ret, sql = buf, {}, [[ SELECT uri, title, lower(uri||ifnull(title,'')) AS text FROM history WHERE text LIKE ? ESCAPE '\' ORDER BY ]] .. order .. desc .. " LIMIT " .. settings.get_setting("completion.max_items") local rows = history.db:exec(sql, { sql_like_globber(term) }) if not rows[1] then return {} end for _, row in ipairs(rows) do table.insert(ret, { escape(row.title) or "", escape(row.uri), format = {{ lit = row.uri }}, buf = row.uri }) end return ret end, } completers.bookmarks = { header = { "Bookmarks", "URI" }, func = function (buf) local term, ret, sql = buf, {}, [[ SELECT uri, title, lower(uri||ifnull(title,'')||ifnull(tags,'')) AS text FROM bookmarks WHERE text LIKE ? ESCAPE '\' ORDER BY title DESC LIMIT ]] .. settings.get_setting("completion.max_items") local rows = bookmarks.db:exec(sql, { sql_like_globber(term) }) if not rows[1] then return {} end for _, row in ipairs(rows) do local title = row.title ~= "" and row.title or row.uri table.insert(ret, { escape(title), escape(row.uri), format = {{ lit = row.uri }}, buf = row.uri }) end return ret end, } completers.uri = { func = function () return { { format = "{history}" }, { format = "{bookmarks}" }, } end, } completers.setting = { header = { "Setting", "Current value" }, func = function () local ret = {} for key, setting in pairs(settings.get_settings()) do table.insert(ret, { key, tostring(setting.value), format = key, }) end return ret end, } completers.domain = { header = { "Domain", "" }, func = function (buf) local prefix = buf:match("^%S+") return prefix and {{ prefix, "", format = {{ lit = prefix }} }} or {} end, } return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/domain_props.lua000066400000000000000000000071541475363222200167660ustar00rootroot00000000000000--- Automatically apply per-domain webview properties. -- -- This module allows you to have site-specific settings. For example, you can -- choose to enable WebGL only on certain specific sites, or enable JavaScript -- only on a sub-domain of a website without enabling JavaScript for the root -- domain. -- -- # Example `domain_props` rules -- -- globals.domain_props = { -- ["all"] = { -- enable_scripts = false, -- enable_plugins = false, -- }, -- ["youtube.com"] = { -- enable_scripts = true, -- }, -- ["m.youtube.com"] = { -- enable_scripts = false, -- }, -- } -- -- ## Explanation -- -- There are three rules in the example. From top to bottom, they are -- least-specific to most-specific: -- -- - [m.youtube.com](https://m.youtube.com): Any webpages on this domain will -- have JavaScript disabled. -- - [youtube.com](https://youtube.com): Any webpages on this -- domain will have JavaScript enabled,_except for webpages on -- [m.youtube.com](https://m.youtube.com)_. This is because the rule -- for [m.youtube.com](https://m.youtube.com) is more specific than the -- rule for [youtube.com](https://youtube.com), so its value for -- `enable_scripts` is used for those sites. -- - `all`: Any other webpages will have JavaScript disabled. In addition, -- _all_ web pages will have plugins disabled, since no more-specific rules -- specified a value for `enable_plugins`. This rule is less specific than all other rules. -- -- # Rule application -- -- The order that rules are specified in the file does not matter, although -- in the default the "all" rule is listed first. All properties in _any_ matching -- rules are applied, but the value that is used is the one specified in the -- most specific rule. If a property is not applied in any rule, it is not -- changed. -- -- # Available properties -- -- - `allow_modal_dialogs` -- - `auto_load_images` -- - `cursive_font_family` -- - `default_charset` -- - `default_font_family` -- - `default_font_size` -- - `default_monospace_font_size` -- - `draw_compositing_indicators` -- - `editable` -- - `enable_accelerated_2d_canvas` -- - `enable_caret_browsing` -- - `enable_developer_extras` -- - `enable_dns_prefetching` -- - `enable_frame_flattening` -- - `enable_fullscreen` -- - `enable_html5_database` -- - `enable_html5_local_storage` -- - `enable_hyperlink_auditing` -- - `enable_java` -- - `enable_javascript` -- - `enable_mediasource` -- - `enable_media_stream` -- - `enable_page_cache` -- - `enable_plugins` -- - `enable_private_browsing` -- - `enable_resizable_text_areas` -- - `enable_site_specific_quirks` -- - `enable_smooth_scrolling` -- - `enable_spatial_navigation` -- - `enable_tabs_to_links` -- - `enable_webaudio` -- - `enable_webgl` -- - `enable_write_console_messages_to_stdout` -- - `enable_xss_auditor` -- - `fantasy_font_family` -- - `javascript_can_access_clipboard` -- - `javascript_can_open_windows_automatically` -- - `media_playback_allows_inline` -- - `media_playback_requires_user_gesture` -- - `minimum_font_size` -- - `monospace_font_family` -- - `pictograph_font_family` -- - `print_backgrounds` -- - `sans_serif_font_family` -- - `serif_font_family` -- - `user_agent` -- - `zoom_level` -- - `zoom_text_only` -- -- @module domain_props -- @author Mason Larobina -- @copyright 2012 Mason Larobina local _M = {} msg.warn("domain_props.lua is deprecated, and will be removed in the next release!") msg.warn("all functionality (and more!) has been moved to settings.lua") return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/downloads.lua000066400000000000000000000254051475363222200162650ustar00rootroot00000000000000--- Downloads for luakit. -- -- This module adds support for downloading files from websites, and provides a -- Lua API to monitor and control the file download process. -- -- Enabling this module is sufficient for starting downloads, but users will -- probably wish to also enable the `downloads_chrome` module. -- -- @module downloads -- @copyright 2010-2012 Mason Larobina -- @copyright 2010 Fabian Streitel -- Grab environment from luakit libs local lousy = require("lousy") local webview = require("webview") local window = require("window") local modes = require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local _M = {} --- Path to downloads database. -- @readwrite _M.db_path = luakit.data_dir .. "/downloads.db" local query_insert -- Setup signals on downloads module lousy.signal.setup(_M, true) -- Unique ids for downloads in this luakit instance local id_count = 0 local function next_download_id() id_count = id_count + 1 return tostring(id_count) end --- Default download directory. -- @readwrite -- @type string _M.default_dir = xdg.download_dir or (os.getenv("HOME") .. "/downloads") -- Private data for the download instances (speed tracking) local dls = {} --- Connect to and initialize the downloads database. function _M.init() -- Return if database handle already open if _M.db then return end _M.db = sqlite3{ filename = _M.db_path } _M.db:exec [[ PRAGMA synchronous = OFF; PRAGMA secure_delete = 1; CREATE TABLE IF NOT EXISTS downloads ( finished_time INTEGER PRIMARY KEY, created_time INTEGER, uri TEXT, destination TEXT, total_size INTEGER ); ]] query_insert = _M.db:compile [[ INSERT INTO downloads VALUES (?, ?, ?, ?, ?) ]] local rows = _M.db:exec("SELECT * FROM downloads") for _, row in ipairs(rows) do local d = {uri = rawget(row, "uri"), destination = rawget(row, "destination"), total_size = rawget(row, "total_size"), status = "finished"} local data = { created = rawget(row, "created_time"), id = next_download_id(), old = true, } dls[d] = data end end luakit.idle_add(_M.init) --- Get all download objects. -- @treturn table The table of all download objects. function _M.get_all() return lousy.util.table.clone(dls) end --- Get download object from ID (passthrough if already given download object). -- @tparam download|number id The download object or the ID of a download object. -- @treturn download The download object. -- @treturn table The download object's private data. function _M.to_download(id) if type(id) == "download" then return id end id = tostring(id) for d, data in pairs(dls) do if id == data.id then return d end end end --- Get private data for a download object. -- @tparam download|number id The download object or the ID of a download object. function _M.get(id) local d = assert(_M.to_download(id), "download.get() expected valid download object or id") return d, dls[d] end local function is_running(d) local status = d.status return status == "created" or status == "started" end --- Attempt to open a downloaded file. -- @tparam download d The download object. -- @tparam table w The current window table. function _M.do_open(d, w) if _M.emit_signal("open-file", d.destination, d.mime_type, w) ~= true then if w then w:error(string.format("Couldn't open: %q (%s)", d.destination, d.mime_type)) end end end local status_timer = timer{interval=300} status_timer:add_signal("timeout", function () local running = 0 for d, data in pairs(dls) do -- Create list of running downloads if is_running(d) then running = running + 1 end -- Raise "download::status" signals local status = d.status if (not data.old) and (status ~= data.last_status) then data.last_status = status _M.emit_signal("download::status", d, data) -- Open download if status == "finished" and data.opening then _M.do_open(d) end end end -- Stop the status_timer after all downloads finished if running == 0 then status_timer:stop() end -- Update window download status widget for _, w in pairs(window.bywidget) do w.sbar.r.downloads.text = (running == 0 and "") or running.."↓" end _M.emit_signal("status-tick", running) end) --- Add a new download. -- @tparam string uri The URI to download. -- @tparam table opts A table of options. function _M.add(uri, opts) opts = opts or {} local d = (type(uri) == "string" and download{uri=uri}) or uri assert(type(d) == "download", string.format("download.add() expected uri or download object " .. "(got %s)", type(d) or "nil")) d:add_signal("decide-destination", function(dd, suggested_filename) -- Emit signal to get initial download location local fn = opts.filename or _M.emit_signal("download-location", dd.uri, opts.suggested_filename or suggested_filename, dd.mime_type) assert(fn == nil or type(fn) == "string" and #fn > 1, string.format("invalid filename: %q", tostring(fn))) -- Ask the user where we should download the file to if not fn then fn = luakit.save_file("Save file", opts.window, _M.default_dir, suggested_filename) end dd.allow_overwrite = true if fn then dd.destination = fn dd:add_signal("created-destination", function(ddd) local data = { created = luakit.time(), id = next_download_id(), } dls[ddd] = data if not status_timer.started then status_timer:start() end _M.emit_signal("download::status", ddd, dls[ddd]) end) else dd:cancel() end return true end) d:add_signal("finished", function(dd) query_insert:exec{os.time(), dls[dd].created, dd.uri, dd.destination, dd.total_size} end) end --- Cancel a download. -- @tparam download|number id The download object or the ID of a download object. function _M.cancel(id) local d = assert(_M.to_download(id), "download.cancel() expected valid download object or id") d:cancel() _M.emit_signal("download::status", d, dls[d]) end --- Remove a download. -- If the download is running, it will be cancelled. -- @tparam download|number id The download object or the ID of a download object. function _M.remove(id) local d = assert(_M.to_download(id), "download.remove() expected valid download object or id") if is_running(d) then _M.cancel(d) end _M.emit_signal("removed-download", d, dls[d]) dls[d] = nil end --- Restart a download. -- A new download with the same source URI as `id` is created, and the original -- download `id` is removed. -- @tparam download|number id The download object or the ID of a download object. -- @treturn download The download object. function _M.restart(id) local d = assert(_M.to_download(id), "download.restart() expected valid download object or id") local new_d = _M.add(d.uri) -- TODO use soup message from old download if new_d then _M.remove(d) end return new_d end --- Attempt to open a downloaded file, as soon as the download completes. -- If the download is already completed, this is equivalent to `do_open()`. -- @tparam download|number id The download object or the ID of a download object. -- @tparam table w The current window table. function _M.open(id, w) local d = assert(_M.to_download(id), "download.open() expected valid download object or id") local data = assert(dls[d], "download removed") if d.status == "finished" then data.opening = false _M.do_open(d, w) else -- Set open flag to open file when download finishes data.opening = true end end --- Clear all finished, cancelled or aborted downloads. function _M.clear() for d, _ in pairs(dls) do if not is_running(d) then dls[d] = nil end end _M.emit_signal("cleared-downloads") end -- If undoclose is loaded, then additionally block these ephemeral tabs from -- being saved in the undolist. local download_views luakit.idle_add(function () local undoclose = package.loaded.undoclose if not undoclose then return end download_views = setmetatable({}, { __mode = "k" }) undoclose.add_signal("save", function (v) if download_views[v] then return false end end) end) -- Catch "download-started" webcontext widget signals (webkit2 API) -- returned d is a download_t luakit.add_signal("download-start", function (d, v) local w if v then w = webview.window(v) if v.uri == "about:blank" and #v.history.items == 1 then if download_views then download_views[v] = true end w:close_tab(v) end else -- Fall back to currently focused window for _, ww in pairs(window.bywidget) do if ww.win.focused then w, v = ww, ww.view break end end end _M.add(d, { window = w.win }, v) return true end) window.add_signal("init", function (w) local r = w.sbar.r r.downloads = widget{type="label"} r.layout:pack(r.downloads) r.layout:reorder(r.downloads, 1) -- Apply theme local theme = lousy.theme.get() r.downloads.fg = theme.downloads_sbar_fg r.downloads.font = theme.downloads_sbar_font end) -- Prevent luakit from soft-closing if there are downloads still running luakit.add_signal("can-close", function () local count = 0 for d, _ in pairs(dls) do if is_running(d) then count = count + 1 end end if count > 0 then return count .. " download(s) still running" end end) -- Download normal mode binds. add_binds("normal", { { "", "Generate `:download` command with current URI.", function (w) w:enter_cmd(":download " .. (w.view.uri or "http://")) end }, }) -- Download commands add_cmds({ { ":down[load]", "Download a webpage by URI, defaulting to the current page.", { func = function (w, o) local uri = o.arg or w.view.uri if uri and not uri:match("^luakit://") then _M.add(uri, { window = w.win }) elseif uri then w:error("cannot download URI '"..uri.."'") else w:error("cannot retrieve current page URI") end end, format = "{uri}", }}, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/downloads_chrome.lua000066400000000000000000000227441475363222200176250ustar00rootroot00000000000000--- Downloads for luakit - chrome page. -- -- This module allows you to monitor the progress of ongoing downloads through a -- webpage at . -- -- @module downloads_chrome -- @copyright 2010-2012 Mason Larobina -- @copyright 2010 Fabian Streitel -- Grab the luakit environment we need local downloads = require("downloads") local chrome = require("chrome") local modes = require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local webview = require("webview") local window = require("window") local _M = {} local html_template = [==[ Downloads
    ]==] --- CSS for downloads chrome page. -- @type string -- @readwrite _M.stylesheet = [==[ .download { padding-left: 10px; position: relative; display: block; margin: 0.4em 0 0.4em 90px; } .download:first-child { margin-top: 1em; } .download .date { left: -90px; width: 90px; position: absolute; display: block; color: #888; } .download .title a { color: #3F6EC2; padding-right: 16px; } .download .status { display: inline; color: #999; white-space: nowrap; } .download .uri a { color: #56D; text-overflow: ellipsis; display: inline-block; white-space: nowrap; text-decoration: none; overflow: hidden; max-width: 500px; } .download .controls a { color: #777; margin-right: 16px; } ]==] local main_js = [=[ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] const downloads_stats = ["status", "speed", "current_size", "total_size", "destination", "created", "uri"] function empty ($el) { while ($el.firstChild) $el.removeChild($el.firstChild) } function readableSize (bytes, precision) { const prefixes = ['B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] bytes = bytes || 0 precision = precision || 0 let i for (i = 0; i < prefixes.length && 1023 < bytes; i++, bytes /= 1024); return `${bytes.toFixed(precision)} ${prefixes[i]}` } function getId ($el) { do { $el = $el.parentNode } while ($el && !$el.classList.contains('download')) return $el.dataset.id } function makeDownloadHTML (d) { let dt = new Date(1000 * d.created) let dateStr = `${dt.getDate()} ${months[dt.getMonth()]} ${dt.getFullYear()}` let href = d.destination.substring(d.destination.lastIndexOf('/') + 1) let uri = encodeURI(d.uri) let showCancel = d.status !== 'finished' && d.status !== 'cancelled' let RS = readableSize let status_text = d.status == 'started' ? `downloading - ${RS(d.current_size)}/${RS(d.total_size)} @ ${RS(d.speed)}/s` : d.status == 'finished' ? `finished - ${RS(d.total_size)}` : d.status == 'created' ? 'waiting' : d.status function escapeHTML(string) { let entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' } return String(string).replace(/[&<>"'`=\/]/g, s => entityMap[s]); } return `
    ${dateStr}
    ${escapeHTML(href)}
    ${status_text}
    ` } function updateListFinish (downloads) { const $list = document.getElementById('downloads-list') empty($list) if (downloads.length == null) return $list.innerHTML = downloads .sort((a, b) => b.created - a.created) .map(makeDownloadHTML).join('') } function updateList () { downloads_get_all(downloads_stats).then(updateListFinish) } document.addEventListener('click', event => { if (!event.target.matches('.controls > a')) return let id = getId(event.target) if (event.target.matches('.show')) download_show(id) else if (event.target.matches('.restart')) download_restart(id) else if (event.target.matches('.remove')) { download_remove(id) document.querySelector(`.download[data-id="${id}"]`).style.display = 'none' } else if (event.target.matches('.cancel')) { download_cancel(id) updateList() } }) window.addEventListener('load', () => { updateList() }) ]=] local update_list_js = [=[updateList();]=] -- default filter local default_filter = { destination = true, status = true, created = true, current_size = true, total_size = true, mime_type = true, uri = true, opening = true } local function collate_download_data(d, data, filter) local f = filter or default_filter local ret = { id = data.id } -- download object properties if rawget(f, "destination") then rawset(ret, "destination", d.destination) end if rawget(f, "status") then rawset(ret, "status", d.status) end if rawget(f, "uri") then rawset(ret, "uri", d.uri) end if rawget(f, "current_size") then rawset(ret, "current_size", d.current_size) end if rawget(f, "total_size") then rawset(ret, "total_size", d.total_size) end if rawget(f, "mime_type") then rawset(ret, "mime_type", d.mime_type) end -- data table properties if rawget(f, "created") then rawset(ret, "created", data.created) end if rawget(f, "opening") then rawset(ret, "opening", not not data.opening) end if rawget(f, "speed") then rawset(ret, "speed", data.speed) end return ret end local export_funcs = { download_get = function (_, id, filter) local d, data = downloads.get(id) if filter then assert(type(filter) == "table", "invalid filter table") for _, key in ipairs(filter) do rawset(filter, key, true) end end return collate_download_data(d, data, filter) end, downloads_get_all = function (_, filter) local ret = {} if filter then assert(type(filter) == "table", "invalid filter table") for _, key in ipairs(filter) do rawset(filter, key, true) end end for d, data in pairs(downloads.get_all()) do table.insert(ret, collate_download_data(d, data, filter)) end return ret end, download_show = function (view, id) local d = downloads.get(id) local dirname = string.gsub(d.destination, "(.*/)(.*)", "%1") if downloads.emit_signal("open-file", dirname, "inode/directory") ~= true then local w = webview.window(view) w:error("Couldn't show download directory (no inode/directory handler)") end end, download_cancel = function (_, id) return downloads.cancel(id) end, download_restart = function (_, id) return downloads.restart(id) end, download_open = function (_, id) return downloads.open(id) end, download_remove = function (_, id) return downloads.remove(id) end, downloads_clear = function (_, id) return downloads.clear(id) end, } downloads.add_signal("status-tick", function (running) if running == 0 then for _, data in pairs(downloads.get_all()) do data.speed = nil end end for d, data in pairs(downloads.get_all()) do if d.status == "started" then local last, curr = rawget(data, "last_size") or 0, d.current_size rawset(data, "speed", curr - last) rawset(data, "last_size", curr) end end -- Update all download pages when a change occurrs for _, w in pairs(window.bywidget) do for _, v in ipairs(w.tabs.children) do if string.match(v.uri or "", "^luakit://downloads/?") then v:eval_js(update_list_js, { no_return = true }) end end end end) chrome.add("downloads", function () local html_subs = { style = chrome.stylesheet .. _M.stylesheet, javascript = main_js, } local html = string.gsub(html_template, "{(%w+)}", html_subs) return html end, nil, export_funcs) --- URI of the downloads chrome page. -- @type string -- @readonly _M.chrome_page = "luakit://downloads/" add_binds("normal", { { "gd", [[Open in current tab.]], function (w) w:navigate(_M.chrome_page) end }, { "gD", [[Open in new tab.]], function (w) w:new_tab(_M.chrome_page) end }, }) add_cmds({ { ":downloads", [[Open in new tab.]], function (w) w:new_tab(_M.chrome_page) end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/editor.lua000066400000000000000000000047341475363222200155630ustar00rootroot00000000000000--- Text editor launching functionality. -- -- This module is primarily for use by other Lua modules that wish to -- allow the user to edit a particular text file. The default is to guess at the -- shell command to open a text editor from environment variables. To override -- the guess, replace `editor.cmd_string`. This can be done manually, as follows: -- -- local editor = require "editor" -- editor.editor_cmd = "urxvt -e nvim {file} +{line}" -- -- Before running the command, `{file}` will be replaced by the name of the file -- to be edited, and `{line}` will be replaced by the number of the line at -- which to begin editing. This module also supplies several builtin command -- strings, which can be used like this: -- -- local editor = require "editor" -- editor.editor_cmd = editor.builtin.urxvt -- -- @module editor -- @copyright 2017 Graham Leach-Krouse -- @copyright 2017 Aidan Holm local _M = {} -- substitute in common values from the environment. local env_sub = function (s) local subs = { term = os.getenv("TERMINAL") or "xterm", editor = os.getenv("EDITOR") or "vim" } return string.gsub(s,"{(%w+)}", subs) end --- Built in substitution strings. Includes -- -- * `autodetect` (attempts to extract a terminal and editor from environment -- variables, and otherwise falls back to xterm and vim) -- * `xterm` -- * `urxvt` -- * `xdg_open` -- -- @type table -- @readonly _M.builtin = { autodetect = env_sub("{term} -e '{editor} {file} +{line}'"), xterm = env_sub("xterm -e {editor} {file} +{line}"), urxvt = env_sub("urxvt -e {editor} {file} +{line}"), xdg_open = env_sub("xdg-open {file}"), } --- The shell command used to open the editor. The default setting is to -- use `editor.builtin.xdg_open`. -- -- @type string -- @readwrite _M.editor_cmd = _M.builtin.xdg_open --- Edit a file in a terminal editor in a new window. -- -- * Can't yet handle files with special characters in their name. -- -- @tparam string file The path of the file to edit. -- @tparam[opt] number line The line number at which to begin editing. -- @tparam[opt] function callback A callback that fires when the process spawned -- by the editor command exits, of type @ref{process_exit_cb}. _M.edit = function (file, line, callback) local subs = { file = file, line = line or 1, } local cmd = string.gsub(_M.editor_cmd, "{(%w+)}", subs) luakit.spawn(cmd, callback) end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/error_page.lua000066400000000000000000000335221475363222200164170ustar00rootroot00000000000000--- Error pages. -- -- This module provides an improved version of the default WebKit error page, -- and allows other Lua modules to show error pages as necessary, with a -- consistent theme. User customization of the error page and its visual style -- is also supported. -- -- @module error_page -- @copyright 2016 Aidan Holm local window = require("window") local webview = require("webview") local lousy = require("lousy") local history = require("history") local _M = {} local error_page_wm = require_web_module("error_page_wm") --- Path to the whitelist of allowed invalid certificates. -- @type string -- @readwrite _M.cert_db_path = luakit.data_dir .. "/allowed_certificates.db" --- Connect to and initialize the bookmarks database. local function init_cert_db() _M.cert_db = sqlite3{ filename = _M.cert_db_path } _M.cert_db:exec [[ PRAGMA synchronous = OFF; PRAGMA secure_delete = 1; CREATE TABLE IF NOT EXISTS allowed_certificates ( id INTEGER PRIMARY KEY, host TEXT NOT NULL, cert TEXT NOT NULL, created INTEGER NOT NULL, allowed INTEGER ); CREATE UNIQUE INDEX IF NOT EXISTS idx_host ON allowed_certificates (host); ]] end init_cert_db() _M.cert_db:exec("UPDATE allowed_certificates SET allowed = 0") --- HTML template for error page content. -- @type string -- @readwrite _M.html_template = [==[ {title}

    {heading}

    {content} {buttons}
    ]==] --- CSS applied to error pages. -- @type string -- @readwrite _M.style = [===[ body { margin: 0; padding: 0; display: flex; align-items: center; justify-content: center; background: url('data:image/gif;base64,R0lGODlhHAAcAPAAANra2t3d3SH5BAAAAAAALAAAA \ AAcABwAAAI+DI6Zwe2vInrUSVnzjblu1VHfElrjUZpn2pwoa7hwvMIuMN+5zt54z0v5bMHSEChD1 \ oTF0JGZhC6Nzc6zVAAAOw=='); } #errorContainer { background: #fff; min-width: 35em; max-width: 35em; padding: 2.5em; border: 2px solid #aaa; -webkit-border-radius: 5px; } #errorContainer > h1 { font-size: 120%; font-weight: bold; margin-top: 0; } #errorContainer > p { font-size: 90%; word-wrap: break-word; } form { margin: 1em 0 0; } ]===] --- CSS applied to certificate error pages. -- @type string -- @readwrite _M.cert_style = [===[ body { background: url('data:image/gif;base64,R0lGODlhHAAcAPAAAL9ZWbtVVSH5BAAAAAAALAAAA \ AAcABwAAAI+RI6ZwO2vInrUSVnzjblu1VHfElrjUZpn2pwoa7hwvMJuMN+5zt54z0v5bMHSEChD1 \ oTF0JGZhC6Nzc6zVAAAOw=='); } #errorContainer { border: 2px solid #666; } ]===] local function false_cb() return false end local function true_cb() return true end local view_state = setmetatable({}, { __mode = "k" }) -- Not fired if the error page is just closed local function on_navigate_away(v, status) if status ~= "provisional" then return end v:remove_signal("load-status", on_navigate_away) view_state[v].is_error_page = nil -- Remove userscripts, stylesheet, javascript overrides v:remove_signal("enable-styles", false_cb) v:remove_signal("enable-scripts", true_cb) v:remove_signal("enable-userscripts", false_cb) end -- Clean up only when error page has finished since sometimes multiple -- load-status provisional signals are dispatched local function on_finish(v, status) if status ~= "finished" then return end local vs = view_state[v] -- Skip the appropriate number of signals assert(type(vs.finished) == "number") vs.finished = vs.finished - 1 if vs.finished > 0 then return end vs.finished = nil v:remove_signal("load-status", on_finish) history.frozen[v] = false -- Start listening for button clicks error_page_wm:emit_signal(v, "listen") -- Mark current history index as showing an error page vs.history[v.history.index] = true -- Listen for a page navigation away from the error page v:add_signal("load-status", on_navigate_away) end local error_views = setmetatable({}, { __mode = "k" }) error_page_wm:add_signal("click", function (_, view_id, button_idx) -- Get error_views entry with matching view_id local view for _, w in pairs(window.bywidget) do if w.view.id == view_id then view = w.view end end if not view then return end if not error_views[view] then return end -- Call button callback error_views[view].buttons[button_idx].callback(view) end) local function make_button_html(v, buttons) local html = "" local tmpl = '' if #buttons == 0 then return "" end for _, button in ipairs(buttons) do assert(button.label) assert(button.callback) button.class = button.class or "" html = html .. string.gsub(tmpl, "{(%w+)}", button) end error_views[v] = { buttons = buttons } local function error_page_on_navigation_request(vv) error_views[vv] = nil vv:remove_signal("navigation-request", error_page_on_navigation_request) end v:add_signal("navigation-request", error_page_on_navigation_request) return '
    ' .. html .. '
    ' end local function attach_error_page_signals(v, skip_count) assert(skip_count == 1 or skip_count == 2) if view_state[v] and view_state[v].finished then -- View is still loading an error page... -- HACK: force early cleanup of the error page by calling the -- load-status signal handler directly; it detaches everything so it -- cannot be called again in response to the actual signal. view_state[v].finished = 0 on_finish(v, "finished") end view_state[v] = view_state[v] or { history = {} } view_state[v].is_error_page = true view_state[v].finished = skip_count v:add_signal("enable-styles", false_cb) v:add_signal("enable-scripts", true_cb) v:add_signal("enable-userscripts", false_cb) v:add_signal("load-status", on_finish) end local function load_error_page(v, error_page_info) -- Set default values local defaults = { title = "Error", error_icon = "error", heading = "Unable to load page", content = [==[

    A problem occurred while loading the URL {uri}

    {msg} ]==], style = _M.style, buttons = {{ label = "Try again", callback = function(vv) vv:reload() end }}, } if error_page_info.style then error_page_info.style = _M.style .. error_page_info.style end error_page_info = lousy.util.table.join(defaults, error_page_info) error_page_info.buttons = make_button_html(v, error_page_info.buttons) -- Make msg html if error_page_info.msg then local msg = error_page_info.msg if type(msg) == "string" then msg = {msg} end error_page_info.msg = "

    " .. table.concat(msg, "

    ") .. "

    " end -- Substitute values recursively local html, nsub = _M.html_template repeat html, nsub = string.gsub(html, "{([%w_]+)}", error_page_info) until nsub == 0 -- If v.is_loading = true then the load will first be stopped, causing a finish -- event to fire. The error page will then be loaded; so the _second_ finish -- event to fire indicates that the error page has finished loading. -- If v.is_loading = false, then there is no ongoing load to stop and so the -- subsequent load finish event will be caused by the error page finishing. -- -- The above doesn't hold if we're given a request to finish(). If that's -- the case, the first finish event to fire indicates the load has finished. local skip_count = (v.is_loading and not error_page_info.request) and 2 or 1 attach_error_page_signals(v, skip_count) history.frozen[v] = true if error_page_info.request then error_page_info.request:finish(html) else v:load_string(html, error_page_info.uri) end end local function get_cert_error_desc(err) local strings = { ["unknown-ca"] = "The signing certificate authority is not known.", ["bad-identity"] = "The certificate does not match the expected identity of the" .. " site that it was retrieved from.", ["not-activated"] = "The certificate's activation time is still in the future.", expired = "The certificate has expired.", insecure = "The certificate has been revoked.", ["generic-error"] = "Error not specified.", } local msg = err.message .. ": " for _, e in ipairs(err.certificate_flags) do local emsg = strings[e] or ("Unknown error code " .. e) msg = msg .. emsg .. " " end msg = msg return msg end local function handle_error(v, uri, err) local error_category_lut = { ["WebKitNetworkError-302"] = "ignore", -- Load request cancelled ["WebKitPluginError-204"] = "ignore", -- Plugin will handle load ["WebKitPolicyError-102"] = "ignore", -- Frame load was interrupted ["LuakitError-0"] = "security", -- Unacceptable TLS certificate ["crash"] = "crash", } local category = error_category_lut[err.code] or "generic" msg.verbose("showing error page for error '%s', code '%s', category '%s'", err.message, err.code, category) if category == "ignore" then return end local error_page_info if category == "generic" then error_page_info = { msg = err.message, } -- Add proxy info on generic pages local p = soup.proxy_uri if p ~= "no_proxy" then p = p == "default" and "system default" or "" .. p .. "" error_page_info.msg = {error_page_info.msg, "Proxy in use: " .. p} end elseif category == "security" then -- if we got cert error -- let's try to scan our cert db and -- allow any certificates for this host that are not allowed -- yet. if there's no such certificates in db -- show error -- page local host = lousy.uri.parse(v.uri).host local certs = _M.cert_db:exec("SELECT cert AS cert FROM allowed_certificates WHERE host=? and allowed=0", {host}) if certs and #certs > 0 then luakit.allow_certificate(host, certs[1].cert) _M.cert_db:exec("UPDATE allowed_certificates SET allowed = ? WHERE host=?", {os.time(), host}) webview.set_location(v, uri) return end local cert = v.certificate error_page_info = { title = "Security Error", error_icon = "security-error", msg = get_cert_error_desc(err), style = _M.cert_style, heading = "Your connection may be insecure!", buttons = {{ label = "Ignore danger until luakit restart", callback = function(vv) luakit.allow_certificate(host, cert) vv:reload() end, }, { label = "Ignore danger permanently", callback = function(vv) luakit.allow_certificate(host, cert) -- save certificate to trusted store _M.cert_db:exec("INSERT OR REPLACE INTO allowed_certificates VALUES (NULL, ?, ?, ?, ?)", {host, cert, os.time(), os.time()}) vv:reload() end, }}, } elseif category == "crash" then error_page_info = { title = "Web Process Crashed", error_icon = "crash", heading = "Web process crashed", content = [==[

    Reload the page to continue

    ]==], buttons = {{ label = "Reload page", callback = function(vv) vv:reload() end }}, } end error_page_info.uri = uri load_error_page(v, error_page_info) end --- Replace the current contents of a webview with an error page. -- @tparam widget v The webview in which to show an error page. -- @tparam table error_page_info A table of options specifying the error page -- content. _M.show_error_page = function(v, error_page_info) assert(type(v) == "widget" and v.type == "webview") assert(type(error_page_info) == "table") assert(type(error_page_info.request) == "request") if not error_page_info.uri then error_page_info.uri = v.uri end load_error_page(v, error_page_info) end webview.add_signal("init", function (view) view:add_signal("load-status", function (v, status, ...) if status == "finished" then -- Update view history table local vs = view_state[v] if vs and not vs.is_error_page then vs.history[v.history.index] = nil end elseif status == "failed" then handle_error(v, ...) return true end end) view:add_signal("crashed", function(v) handle_error(v, v.uri or "about:blank", { code = "crash", message = "Web process crashed" }) end) view:add_signal("go-back-forward", function (v, n) local vs = view_state[v] if vs and vs.history[v.history.index + n] then attach_error_page_signals(v, 1) end end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/error_page_wm.lua000066400000000000000000000007121475363222200171150ustar00rootroot00000000000000-- Error pages - web module. -- -- @submodule error_page -- @copyright 2016 Aidan Holm local ui = ipc_channel("error_page_wm") ui:add_signal("listen", function(_, page) local doc = page.document for i, elem in ipairs(doc.body:query("input[type=button]")) do elem:add_event_listener("click", true, function (_) ui:emit_signal("click", page.id, i) end) end end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/follow.lua000066400000000000000000000512271475363222200155760ustar00rootroot00000000000000--- Link hinting for luakit. -- -- Link hints allow interacting with web pages without the use of a -- mouse. When `follow` mode is entered, all clickable elements are -- highlighted and labeled with a short number. Typing either an element -- number or part of the element text will "follow" that hint, issuing a -- mouse click. This is most commonly used to click links without using -- the mouse and focus text input boxes. In addition, the `ex-follow` -- mode offers several variations on this behavior. For example, instead -- of clicking, the URI of a followed link can be copied into the clipboard. -- Another example would be hinting all images on the page, and opening the -- followed image in a new tab. -- -- # Customizing hint labels -- -- If you prefer to use letters instead of numbers for hint labels (useful if -- you use a non-qwerty keyboard layout), this can be done by replacing the -- @ref{label_maker} function: -- -- local select = require "select" -- -- select.label_maker = function (s) -- local chars = s.charset("asdfqwerzxcv") -- return s.trim(s.sort(s.reverse(chars))) -- end -- -- Here, the `charset()` function generates hints using the specified letters. -- For a full explanation of what the `trim(sort(reverse(...)))` construction -- does, see the @ref{select} module documentation; the short explanation is -- that it makes hints as short as possible, saving you typing. -- -- Note: this requires modifying the @ref{select} module because the actual -- link hinting interface is implemented in the `select` module; the -- `follow` module provides the `follow` and `ex-follow` user interface on top -- of that. -- -- ## Hinting with non-latin letters -- -- If you use a keyboard layout with non-latin keys, you may prefer to use -- non-latin letters to hint. For example, using the Cyrillic alphabet, the -- above code could be changed to the following: -- -- ... -- local chars = s.charset("ФЫВАПРОЛДЖЭ") -- ... -- -- ## Hint text direction -- -- Hints consisting entirely of characters which are drawn Left-to-Right -- (eg Latin, Cyrillic) or characters drawn Right-to-Left (eg Arabic, Hebrew), -- will render intuitively in the appropriate direction. -- Hints will be drawn non-intuitively if they contain a mix of Left-to-Right -- and Right-to-Left characters. -- -- Punctuation characters do not have an intrinsic direction, and will be drawn -- using the direction specified by the HTML/CSS context in which they appear. -- This leads to corner cases if the hint charset contains punctuation characters, -- for example: -- -- ... -- local chars = s.charset("fjdksla;ghutnvir") -- ... -- -- In this case, hints will display intuitively if used on pages which are -- drawn Left-to-Right, but not on pages drawn Right-to-Left. -- -- To guard against this, it is recommended that if punctuation characters -- are used in hints, a clause should be added to a user stylesheet giving -- an explicit text direction eg: -- -- ... -- #luakit_select_overlay .hint_label { direction: ltr; } -- ... -- -- ## Alternating between left- and right-handed letters -- -- To make link hints easier to type, you may prefer to have them alternate -- between letters on the left and right side of your keyboard. This is easy to -- do with the `interleave()` label composer function. -- -- ... -- local chars = s.interleave("qwertasdfgzxcvb", "yuiophjklnm") -- ... -- -- # Matching only hint labels, not element text -- -- If you prefer not to match element text, and wish to select hints only by -- their label, this can be done by specifying the @ref{pattern_maker}: -- -- -- Match only hint label text -- follow.pattern_maker = follow.pattern_styles.match_label -- -- # Ignoring element text case -- -- To ignore element text case when filtering hints, set the following option: -- -- -- Uncomment if you want to ignore case when matching -- follow.ignore_case = true -- -- @module follow -- @copyright 2010-2012 Mason Larobina -- @copyright 2010-2011 Fabian Streitel local window = require("window") local new_mode = require("modes").new_mode local modes = require("modes") local add_binds = modes.add_binds local lousy = require("lousy") local theme = lousy.theme.get() local _M = {} local follow_wm = require_web_module("follow_wm") --- Duration to ignore keypresses after following a hint. 200ms by default. -- -- After each follow ignore all keys pressed by the user to prevent the -- accidental activation of other key bindings. -- @type number -- @readwrite _M.ignore_delay = 200 --- CSS applied to the follow mode overlay. -- @type string -- @readwrite _M.stylesheet = [[ #luakit_select_overlay { position: absolute; left: 0; top: 0; z-index: 2147483647; /* Maximum allowable on WebKit */ } #luakit_select_overlay .hint_overlay { display: block; position: absolute; background-color: ]] .. (theme.hint_overlay_bg or "rgba(255,255,153,0.3)") .. [[; border: ]] .. (theme.hint_overlay_border or "1px dotted #000") .. [[; opacity: ]] .. (theme.hint_opacity or "0.3") .. [[; } #luakit_select_overlay .hint_label { display: block; position: absolute; background-color: ]] .. (theme.hint_bg or "#000088") .. [[; border: ]] .. (theme.hint_border or "1px dashed #000") .. [[; color: ]] .. (theme.hint_fg or "#fff") .. [[; font: ]] .. (theme.hint_font or "10px monospace, courier, sans-serif") .. [[; } #luakit_select_overlay .hint_selected { background-color: ]] .. (theme.hint_overlay_selected_bg or "rgba(0,255,0,0.3)") .. [[ !important; border: ]] .. (theme.hint_overlay_selected_border or "1px dotted #000") .. [[; } ]] -- Lua regex escape function local function regex_escape(s) local escape_chars = "%^$().[]*+-?" local escape_pat = '([' .. escape_chars:gsub("(.)", "%%%1") .. '])' return s:gsub(escape_pat, "%%%1") end local re_match_text = function (text) return nil, text end local re_match_both = function (text) return text, text end local match_label_re_text = function (text) return #text > 0 and "^"..regex_escape(text) or "", text end local match_label = function (text) return #text > 0 and "^"..regex_escape(text) or "", nil end --- Table of functions used to select a hint matching style. -- @type {[string]=function} -- @readonly _M.pattern_styles = { re_match_text = re_match_text, -- Regex match target text only. re_match_both = re_match_both, -- Regex match both hint label or target text match_label_re_text = match_label_re_text, -- String match hint label & regex match text match_label = match_label, -- String match hint label only } --- Hint matching style functions. -- @type function -- @readwrite _M.pattern_maker = _M.pattern_styles.match_label_re_text --- Whether text case should be ignored in follow mode. True by default. -- @type boolean -- @readwrite _M.ignore_case = true local function focus(w, step) follow_wm:emit_signal(w.view, "focus", step) end local hit_nop = function () return true end local function ignore_keys(w) local delay = _M.ignore_delay if not delay or delay == 0 then return end -- Replace w:hit(..) with a no-op w.hit = hit_nop local timer = timer{ interval = delay } timer:add_signal("timeout", function (t) t:stop() w.hit = nil end) timer:start() end local function do_follow(w, all) follow_wm:emit_signal(w.view, "follow", all) end local function follow_all_hints(w) do_follow(w, true) end local function follow_func_cb(w, ret) local mode = w.follow_state.mode if mode.func then mode.func(ret) end -- don't set mode if func() changed it (e.g. to command mode) if w:is_mode("follow") or w:is_mode("ex-follow") then if mode.persist then w:set_input("") w:set_mode("follow", mode) elseif ret ~= "form-active" and ret ~= "root-active" then w:set_mode() end end ignore_keys(w) end local function matches_cb(w, n) w:set_ibar_theme(n > 0 and "ok" or "error") end follow_wm:add_signal("follow_func", function(_, page_id, ret) for _, w in pairs(window.bywidget) do if w.view.id == page_id then follow_func_cb(w, ret) end end end) follow_wm:add_signal("matches", function(_, page_id, n) for _, w in pairs(window.bywidget) do if w.view.id == page_id then matches_cb(w, n) end end end) follow_wm:add_signal("click_a_target_blank", function(_, page_id, href) for _, w in pairs(window.bywidget) do if w.view.id == page_id then w:new_tab(href, { private = w.view.private }) end end end) new_mode("follow", { enter = function (w, mode) assert(type(mode) == "table", "invalid follow mode") if mode.label_maker then msg.warn("Custom label maker not yet implemented!") end assert(type(mode.pattern_maker or _M.pattern_maker) == "function", "invalid pattern_maker function") local view = w.view local selector = mode.selector_func or _M.selectors[mode.selector] assert(type(selector) == "string", "invalid follow selector") -- Append site-specific selector local domain = lousy.uri.parse(view.uri).host local sss = _M.site_specific_selectors[domain] if sss and sss[mode.selector] then selector = selector .. ", " .. sss[mode.selector] end mode.selector = selector local stylesheet = mode.stylesheet or _M.stylesheet assert(type(stylesheet) == "string", "invalid stylesheet") mode.stylesheet = stylesheet if w.follow_persist then mode.persist = true w.follow_persist = nil end w.follow_state = { mode = mode, view = view, evaluator = mode.evaluator, } if mode.prompt then w:set_prompt(string.format("Follow (%s):", mode.prompt)) else w:set_prompt("Follow:") end w:set_input("") w:set_ibar_theme() -- Cut func out of mode, since we can't send functions local func = mode.func mode.func = nil follow_wm:emit_signal(w.view, "enter", mode, _M.ignore_case) mode.func = func end, changed = function (w, text) local mode = w.follow_state.mode -- Make the hint label/text matching patterns local pattern_maker = mode.pattern_maker or _M.pattern_maker local hint_pat, text_pat = pattern_maker(text) follow_wm:emit_signal(w.view, "changed", hint_pat, text_pat, text) end, leave = function (w) w:set_ibar_theme() follow_wm:emit_signal(w.view, "leave") end, }) add_binds("follow", { { "", "Focus the next element hint.", function (w) focus(w, 1) end }, { "", "Focus the previous element hint.", function (w) focus(w, -1) end }, { "", "Activate the currently focused element hint.", function (w) do_follow(w) end }, { "", "Activate all currently visible element hints.", function (w) follow_all_hints(w) end }, }) --- Element selectors used to filter elements to follow. -- @type {[string]=string} -- @readwrite _M.selectors = { clickable = 'a, area, textarea, select, input:not([type=hidden]), button, label, summary', -- Elements that can be clicked. focus = 'a, area, textarea, select, input:not([type=hidden]), button, body, applet, object', -- Elements that can be given input focus. uri = 'a, area', -- Elements that have a URI (e.g. hyperlinks). desc = '*[title], img[alt], applet[alt], area[alt], input[alt]', -- Elements that can have a description. image = 'img, input[type=image]', -- Image elements. thumbnail = "a img", -- Image elements within a hyperlink. } --- Site specific element selectors used to extend @ref{selectors}. -- Table keys should be website domains. Values are tables with the same -- structure as @ref{selectors}. -- @type {[string]=table} -- @readwrite _M.site_specific_selectors = { ["github.com"] = { clickable = "svg.js-menu-close, div.select-menu-item" }, } add_binds("normal", { { "^f$", [[Start `follow` mode. Hint all clickable elements (as defined by the `follow.selectors.clickable` selector) and open links in the current tab.]], function (w) w:set_mode("follow", { selector = "clickable", evaluator = "click", func = function (s) w:emit_form_root_active_signal(s) end, }) end }, -- Open new tab { "^F$", [[Start follow mode. Hint all links (as defined by the `follow.selectors.uri` selector) and open links in a new tab.]], function (w) w:set_mode("follow", { prompt = "background tab", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:new_tab(uri, { switch = false, private = w.view.private }) end }) end }, -- Start extended follow mode { "^;$", [[Start `ex-follow` mode. See the [ex-follow](#mode-ex-follow) help section for the list of follow modes.]], function (w) w:set_mode("ex-follow") end }, { "^g;$", [[Start `ex-follow` mode and stay there until `` is pressed.]], function (w) w:set_mode("ex-follow", true) end }, }) -- Extended follow mode new_mode("ex-follow", { enter = function (w, persist) w.follow_persist = persist end, }) add_binds("ex-follow", { { ";", [[Hint all focusable elements (as defined by the `follow.selectors.focus` selector) and focus the matched element.]], function (w) w:set_mode("follow", { prompt = "focus", selector = "focus", evaluator = "focus", func = function (s) w:emit_form_root_active_signal(s) end, }) end }, -- Yank element uri or description into primary selection { "y", [[Hint all links (as defined by the `follow.selectors.uri` selector) and set the primary selection to the matched elements URI.]], function (w) w:set_mode("follow", { prompt = "yank", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") uri = uri:gsub(" ", "%%20"):gsub("^mailto:", "") luakit.selection.primary = uri w:notify("Yanked uri: " .. uri, false) end }) end }, -- Yank element description { "Y", [[Hint all links (as defined by the `follow.selectors.uri` selector) and set the primary selection to the matched elements URI.]], function (w) w:set_mode("follow", { prompt = "yank desc", selector = "desc", evaluator = "desc", func = function (desc) assert(type(desc) == "string") luakit.selection.primary = desc w:notify("Yanked desc: " .. desc) end }) end }, -- Open image src { "i", [[Hint all images (as defined by the `follow.selectors.image` selector) and open matching image location in the current tab.]], function (w) w:set_mode("follow", { prompt = "open image", selector = "image", evaluator = "src", func = function (src) assert(type(src) == "string") w:navigate(src) end }) end }, -- Open image src in new tab { "I", [[Hint all images (as defined by the `follow.selectors.image` selector) and open matching image location in a new tab.]], function (w) w:set_mode("follow", { prompt = "tab image", selector = "image", evaluator = "src", func = function (src) assert(type(src) == "string") w:new_tab(src, { private = w.view.private }) end }) end }, -- Open thumbnail link { "x", [[Hint all thumbnails (as defined by the `follow.selectors.thumbnail` selector) and open link in current tab.]], function (w) w:set_mode("follow", { prompt = "open image link", selector = "thumbnail", evaluator = "parent_href", func = function (uri) assert(type(uri) == "string") w:navigate(uri) end }) end }, -- Open thumbnail link in new tab { "X", [[Hint all thumbnails (as defined by the `follow.selectors.thumbnail` selector) and open link in a new tab.]], function (w) w:set_mode("follow", { prompt = "tab image link", selector = "thumbnail", evaluator = "parent_href", func = function (uri) assert(type(uri) == "string") w:new_tab(uri, { switch = false, private = w.view.private }) end }) end }, -- Open link { "o", [[Hint all links (as defined by the `follow.selectors.uri` selector) and open its location in the current tab.]], function (w) w:set_mode("follow", { prompt = "open", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:navigate(uri) end }) end }, -- Open link in new tab { "t", [[Hint all links (as defined by the `follow.selectors.uri` selector) and open its location in a new tab.]], function (w) w:set_mode("follow", { prompt = "open tab", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:new_tab(uri, { private = w.view.private }) end }) end }, -- Open link in background tab { "b", [[Hint all links (as defined by the `follow.selectors.uri` selector) and open its location in a background tab.]], function (w) w:set_mode("follow", { prompt = "background tab", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:new_tab(uri, { switch = false, private = w.view.private }) end }) end }, -- Open link in new window { "w", [[Hint all links (as defined by the `follow.selectors.uri` selector) and open its location in a new window.]], function (w) w:set_mode("follow", { prompt = "open window", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") window.new{uri} end }) end }, -- Set command `:open ` { "O", [[Hint all links (as defined by the `follow.selectors.uri` selector) and generate a `:open` command with the elements URI.]], function (w) w:set_mode("follow", { prompt = ":open", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:enter_cmd(":open " .. uri) end }) end }, -- Set command `:tabopen ` { "T", [[Hint all links (as defined by the `follow.selectors.uri` selector) and generate a `:tabopen` command with the elements URI.]], function (w) w:set_mode("follow", { prompt = ":tabopen", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:enter_cmd(":tabopen " .. uri) end }) end }, -- Set command `:winopen ` { "W", [[Hint all links (as defined by the `follow.selectors.uri` selector) and generate a `:winopen` command with the elements URI.]], function (w) w:set_mode("follow", { prompt = ":winopen", selector = "uri", evaluator = "uri", func = function (uri) assert(type(uri) == "string") w:enter_cmd(":winopen " .. uri) end }) end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/follow_selected.lua000066400000000000000000000037171475363222200174470ustar00rootroot00000000000000--- Add {A,C,S,}-Return binds to follow selected link (or link in selection). -- -- This module allows you to follow links that are part of the currently -- selected text. This is useful as an alternative to the follow mode: search -- for the text of the link, and then press `` to follow it. -- -- @module follow_selected -- @copyright 2010 Chris van Dijk -- @copyright 2010 Mason Larobina -- @copyright 2010 Paweł Zuzelski -- @copyright 2009 israellevin local window = require("window") local modes = require("modes") local add_binds = modes.add_binds local _M = {} local wm = require_web_module("follow_selected_wm") local function get_w_by_view_id(view_id) for _, w in pairs(window.bywidget) do if w.view.id == view_id then return w end end end wm:add_signal("navigate", function(_, uri, view_id) get_w_by_view_id(view_id):navigate(uri) end) wm:add_signal("new_tab", function(_, uri, view_id) get_w_by_view_id(view_id):new_tab(uri) end) wm:add_signal("new_window", function(_, uri) window.new({uri}) end) wm:add_signal("download", function(_, uri, view_id) get_w_by_view_id(view_id):download(uri) end) -- Add binding to normal mode to follow selected link add_binds("normal", { { "", "Follow the selected link in the current tab.", function (w) wm:emit_signal(w.view, "follow_selected", "navigate", w.view.id) end }, { "", "Follow the selected link in a new tab.", function (w) wm:emit_signal(w.view, "follow_selected", "new_tab", w.view.id) end }, { "", "Follow the selected link in a new window.", function (w) wm:emit_signal(w.view, "follow_selected", "new_window", w.view.id) end }, { "", "Download the selected link.", function (w) wm:emit_signal(w.view, "follow_selected", "download", w.view.id) end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/follow_selected_wm.lua000066400000000000000000000033151475363222200201440ustar00rootroot00000000000000-- Add {A,C,S,}-Return binds to follow selected link (or link in selection) - web module. -- -- @submodule follow_selected_wm -- @copyright 2016 Aidan Holm -- @copyright 2010 Chris van Dijk -- @copyright 2010 Mason Larobina -- @copyright 2010 Paweł Zuzelski -- @copyright 2009 israellevin local ui = ipc_channel("follow_selected_wm") -- Return selected uri or first uri in selection local return_selected = [=[ (function(document) { var selection = window.getSelection(), container = document.createElement('div'), range, elements, i = 0; if (selection.toString() !== "") { range = selection.getRangeAt(0); // Check for links contained within the selection container.appendChild(range.cloneContents()); var elements = container.getElementsByTagName('a'), len = elements.length, i = 0, href; for (; i < len;) if ((href = elements[i++].href)) return href; // Check for links which contain the selection container = range.startContainer; while (container !== document) { if ((href = container.href)) return href; container = container.parentNode; } } // Check for active links var element = document.activeElement; return element.src || element.href; })(document); ]=] ui:add_signal("follow_selected", function(_, _, action, view_id) local p = page(view_id) local uri = p:eval_js(return_selected) if not uri then return end assert(type(uri) == "string") ui:emit_signal(action, uri, view_id) end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/follow_wm.lua000066400000000000000000000102551475363222200162750ustar00rootroot00000000000000-- Link hinting for luakit - web module. -- -- @submodule follow_wm -- @copyright 2016 Aidan Holm local select = require("select_wm") local lousy = require("lousy") local ui = ipc_channel("follow_wm") local evaluators = { click = function(element, page) local tag = element.tag_name if tag == "INPUT" or tag == "TEXTAREA" then local t = element.attr.type if t == "radio" or t == "checkbox" or t == "submit" or t == "reset" or t == "button" then element:click() return else element:focus() return "form-active" end end -- Handle indirectly; WebKit prevents opening a new -- window if not initiated by the user directly if tag == "A" and element.attr.target == "_blank" then ui:emit_signal("click_a_target_blank", page.id, element.href) return end -- Find the element directly in the centre of the link if element.child_count > 0 then local r = element.rect local doc = element.owner_document local scroll = page:eval_js([=[ window.scrollX + ' ' + window.scrollY; ]=]) local scrollX, scrollY = scroll:match("^(%S+) (%S+)$") element = doc:element_from_point(r.left - scrollX + r.width/2, r.top - scrollY + r.height/2) end element:click() end, focus = function(element) element:focus() local tag = element.tag_name if tag == "INPUT" or tag == "TEXTAREA" then return "form-active" else return "root-active" end end, uri = function(element) return element.src or element.href end, desc = function(element) local attrs = element.attr return attrs.title or attrs.alt end, src = function(element) return element.src end, parent_href = function(element) return element.parent.href end, } local page_mode = {} local function follow_hint(page, mode, hint) local evaluator if type(mode.evaluator) == "string" then evaluator = evaluators[mode.evaluator] elseif type(mode.evaluator) == "function" then evaluator = mode.evaluator else error("bad evaluator type '%s'", type(mode.evaluator)) end local overlay_style = hint.overlay_elem.attr.style hint.overlay_elem.attr.style = "display: none;" local ret = evaluator(hint.elem, page) hint.overlay_elem.attr.style = overlay_style ui:emit_signal("follow_func", page.id, ret) end local function follow(page, all) -- Build array of hints to follow local hints = all and select.hints(page) or { select.focused_hint(page) } hints = lousy.util.table.filter_array(hints, function (_, hint) return not hint.hidden end) -- Close hint select UI first if not persisting in follow mode local mode = page_mode[page] if not mode.persist then select.leave(page) page_mode[page] = nil end -- Follow hints in idle cb to ensure select UI is closed if necessary luakit.idle_add(function () for _, hint in pairs(hints) do follow_hint(page, mode, hint) end end) end ui:add_signal("follow", function(_, page, all) follow(page, all) end) ui:add_signal("focus", function(_, page, step) select.focus(page, step) end) ui:add_signal("enter", function(_, page, mode, ignore_case) page_mode[page] = mode select.enter(page, mode.selector, mode.stylesheet, ignore_case) local num_visible_hints = #(select.hints(page)) ui:emit_signal("matches", page.id, num_visible_hints) end) ui:add_signal("changed", function(_, page, hint_pat, text_pat, text) local _, num_visible_hints = select.changed(page, hint_pat, text_pat, text) ui:emit_signal("matches", page.id, num_visible_hints) if num_visible_hints == 1 and text ~= "" then follow(page, false) end end) ui:add_signal("leave", function (_, page) if page_mode[page] then page_mode[page] = nil select.leave(page) end end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/formfiller.lua000066400000000000000000000312661475363222200164360ustar00rootroot00000000000000--- Provides functionality to auto-fill forms based on a Lua DSL. -- -- The formfiller provides support for filling out forms based on the contents -- of a forms file, which uses a domain-specific language to specify the content to -- fill forms with. -- -- The following is an example for a formfiller definition: -- -- on "example.com" { -- form "profile1" { -- method = "post", -- action = "/login", -- className = "someFormClass", -- id = "form_id", -- input { -- name = "username", -- type = "text", -- className = "someClass", -- id = "username_field", -- value = "myUsername", -- }, -- input { -- name = "password", -- value = "myPassword", -- }, -- input { -- name = "autologin", -- type = "checkbox", -- checked = true, -- }, -- submit = true, -- autofill = false, -- }, -- } -- -- * The form function's string argument is optional. -- It allows you to define multiple profiles for use with the -- zL binding. -- -- * All entries are matched top to bottom, until one fully matches -- or calls submit(). -- -- * The submit attribute of a form can also be a number, which -- gives index of the submit button to click (starting with 1). -- If there is no such button or the argument is true, -- form.submit() will be called instead. -- -- * Instead of submit, you can also use focus = true -- inside an input to focus that element or select = true -- to select the text inside it. -- focus will trigger input mode. -- -- * The string argument to the on function (example.com -- in the example above) takes a Lua pattern! -- BEWARE its escaping! -- -- * All of the attributes of the form and input tables -- are matched as plain text. -- -- * Setting autofill = true on a form definition will -- automatically fill and possibly submit any matching forms when a web page -- with a matching URI finishes loading. This is useful if you wish to have -- login pages for various web services filled out automatically. It is -- critically important, however, to verify that the URI pattern of the rule is -- correct! -- -- As a basic precaution, autofill only works if the web page domain -- is present within the URI pattern. -- -- There is a conversion script in the luakit repository that converts -- from the old formfiller format to the new one. For more information, -- see the converter script under extras/convert_formfiller.rb. -- -- # Files and Directories -- -- - The formfiller configuration is loaded from the `forms.lua` file stored in -- the luakit data directory. -- -- @module formfiller -- @copyright 2011 Fabian Streitel -- @copyright 2011 Mason Larobina local lousy = require("lousy") local window = require("window") local webview = require("webview") local editor = require("editor") local new_mode = require("modes").new_mode local binds, modes = require("binds"), require("modes") local add_binds = modes.add_binds local menu_binds = binds.menu_binds local _M = {} local formfiller_wm = require_web_module("formfiller_wm") -- The Lua DSL file containing the formfiller rules local file = luakit.data_dir .. "/forms.lua" -- The function environment for the formfiller script local DSL = { print = function (_, ...) print(...) end, -- DSL method to match a page by its URI on = function (s, pattern) return function (forms) table.insert(s.rules, { pattern = pattern, forms = forms, }) end end, -- DSL method to match a form by its attributes form = function (_, data) local transform = function (inputs, profile) local form = { profile = profile, inputs = {}, } for k, v in pairs(inputs) do if type(k) == "number" then form.inputs[k] = v else form[k] = v end end return form end if type(data) == "string" then local profile = data return function (inputs) return transform(inputs, profile) end else return transform(data) end end, -- DSL method to match an input element by its attributes input = function (_, attrs) return attrs end, } local dsl_extensions = {} formfiller_wm:add_signal("dsl_extension_query", function (_, k, arg, view_id) local reply = dsl_extensions[k](unpack(arg)) formfiller_wm:emit_signal(view_id,"dsl_extension_reply", reply, view_id) end) --- Extend the formfiller DSL with additional functions. This takes a table of -- functions. For example, to use the `pass` storage manager: -- -- formfiller.extend({ -- pass = function(s) return io.popen("pass " .. s):read() end -- }) -- -- which will then be usable in the fields of `form.lua`: -- -- input { -- name = "username", -- value = pass("emailpassword"), -- } -- -- Functions used to extend the DSL will be called only when needed: when -- matching for attributes used in matching, or once a form is applied, for -- attributes used in form application. -- -- @tparam table extensions The table of functions extending the formfiller DSL. _M.extend = function (extensions) for k, v in pairs(extensions) do assert(type(v) == "function", "bad DSL extension: values must be functions") assert(type(k) == "string", "bad DSL extension: keys must be strings") assert(k ~= "on" and k ~= "form" and k ~= "input", "bad DSL extension: don't shadow core DSL functions") end dsl_extensions = extensions for k, _ in pairs(extensions) do DSL[k] = function (_, ...) return {sentinel = true, arg = {...}, key = k} end end end --- Reads the rules from the formfiller DSL file local function read_formfiller_rules_from_file() local state = { rules = {}, } -- the environment of the DSL script -- load the script local f = io.open(file, "r") if not f then return {} end -- file doesn't exist local code = f:read("*all") f:close() local dsl, message = loadstring(code) if not dsl then msg.warn(string.format("loading formfiller data failed: %s", message)) return end -- execute in sandbox local env = {} -- wrap the DSL functions so they can access the state for k in pairs(DSL) do env[k] = function (...) return DSL[k](state, ...) end end setfenv(dsl, env) local success, err = pcall(dsl) if not success then msg.warn("error in %s: %s", file, err) end return state.rules end local function form_specs_for_uri (all_rules, uri) -- Filter rules to the given uri local rules = lousy.util.table.filter_array(all_rules, function(_, rule) return string.find(uri, rule.pattern) end) -- Get list of all form specs that can be matched local form_specs = {} for _, rule in ipairs(rules) do for _, form in ipairs(rule.forms) do form.pattern = rule.pattern form_specs[#form_specs + 1] = form end end return form_specs end --- Edits the formfiller rules. local function edit() editor.edit(file) end local function w_from_view_id(view_id) assert(type(view_id) == "number", type(view_id)) for _, w in pairs(window.bywidget) do if w.view.id == view_id then return w end end end formfiller_wm:add_signal("failed", function (_, view_id, msg) local w = w_from_view_id(view_id) w:error(msg) w:set_mode() end) formfiller_wm:add_signal("add", function (_, view_id, str) local w = w_from_view_id(view_id) w:set_mode() local f = io.open(file, "a") f:write(str) f:close() edit() end) --- Fills the current page from the formfiller rules. -- @tparam table w The window on which to fill the forms. local function fill_form_fast(w) local rules = read_formfiller_rules_from_file(w) local form_specs = form_specs_for_uri(rules, w.view.uri) if #form_specs == 0 then w:error("no rules matched") return end formfiller_wm:emit_signal(w.view, "fill-fast", form_specs) end -- Support for choosing a form with a menu local function fill_form_menu(w) local rules = read_formfiller_rules_from_file(w) local form_specs = form_specs_for_uri(rules, w.view.uri) if #form_specs == 0 then w:error("no rules matched") return end formfiller_wm:emit_signal(w.view, "filter", form_specs) end formfiller_wm:add_signal("filter", function (_, view_id, form_specs) local w = w_from_view_id(view_id) -- Build menu local menu = {} for _, form in ipairs(form_specs) do if form.profile then table.insert(menu, { form.profile, form = form }) end end -- show menu if necessary if #menu == 0 then w:error("no forms with profile names found") else w:set_mode("formfiller-menu", menu) end end) webview.add_signal("init", function (view) view:add_signal("load-status", function (v, status) if status ~= "finished" then return end local rules = read_formfiller_rules_from_file() local form_specs = form_specs_for_uri(rules, v.uri) for _, form_spec in ipairs(form_specs) do if type(form_spec.autofill) == "table" and form_spec.autofill.sentinel then form_spec.autofill = dsl_extensions[form_spec.autofill.key](unpack(form_spec.autofill.arg)) end if form_spec.autofill then -- Precaution: pattern must contain full domain of page URI local uri = lousy.uri.parse(v.uri) local domain = uri.host if uri.port ~= 80 and uri.port ~= 443 then domain = domain .. ":" .. uri.port end domain = lousy.util.lua_escape(domain .. "/") if form_spec.pattern:find(domain, 1, true) then msg.info("auto-filling form profile '%s'", form_spec.profile) formfiller_wm:emit_signal(view, "apply_form", form_spec) else local w = webview.window(view) w:error("refusing to autofill: URI pattern does not contain current page domain") end end end end) end) -- Add formfiller menu mode new_mode("formfiller-menu", { enter = function (w, menu) local rows = {{ "Profile", title = true }} for _, m in ipairs(menu) do table.insert(rows, m) end w.menu:build(rows) end, leave = function (w) w.menu:hide() end, }) add_binds("formfiller-menu", lousy.util.table.join({ -- use profile { "", "Select formfiller profile.", function (w) local row = w.menu:get() local form = row.form w:set_mode() formfiller_wm:emit_signal(w.view, "apply_form", form) end }, }, menu_binds)) -- Visual form selection for adding a form new_mode("formfiller-add", { enter = function (w) w:set_prompt("Add form:") w:set_input("") w:set_ibar_theme() formfiller_wm:emit_signal(w.view, "enter") end, changed = function (w, text) formfiller_wm:emit_signal(w.view, "changed", text) end, leave = function (w) w:set_ibar_theme() formfiller_wm:emit_signal(w.view, "leave") end, }) add_binds("formfiller-add", { { "", "Focus the next form hint.", function (w) formfiller_wm:emit_signal(w.view, "focus", 1) end }, { "", "Focus the previous form hint.", function (w) formfiller_wm:emit_signal(w.view, "focus", -1) end }, { "", "Add the currently focused form to the formfiller file.", function (w) formfiller_wm:emit_signal(w.view, "select") end }, }) -- Setup formfiller binds add_binds("normal", { { "za", "Add formfiller form.", function (w) w:set_mode("formfiller-add") end }, { "ze", "Edit formfiller forms for current domain.", function (_) edit() end }, { "zl", "Load formfiller form (use first profile).", fill_form_fast }, { "zL", "Load formfiller form.", fill_form_menu }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/formfiller_wm.lua000066400000000000000000000231051475363222200171320ustar00rootroot00000000000000-- Luakit formfiller - web module. -- -- @submodule formfiller_wm -- @copyright 2016 Aidan Holm local select = require("select_wm") local lousy = require("lousy") local ui = ipc_channel("formfiller_wm") local filter = lousy.util.table.filter_array local function element_attributes_match(element, attrs) for attr, value in pairs(attrs) do if not string.find(value, element.attr[attr] or "", 1, true) then return false end end return true end local function attribute_matches(tag, attrs, parents) local documents = {} parents = parents and parents or documents local elements = {} for _, parent in ipairs(parents) do local e = parent:query(tag) for _, element in ipairs(e) do if element_attributes_match(element, attrs) then elements[#elements+1] = element end end end return elements end local function match(tag, attrs, form, parents) assert(type(parents) == "table") local attr_table = {} for _, v in ipairs(attrs) do if form[v] then attr_table[v] = form[v] end end local matches = attribute_matches(tag, attr_table, parents) return matches end local function fill_input(inputs, value) assert(type(inputs) == "table") for _, input in pairs(inputs) do assert(type(input) == "dom_element") if input.type == "radio" or input.type == "checkbox" then -- Click the input if it isn't already in the desired state local checked = input.checked == "checked" if value and value ~= checked then input:click() end else input.value = value end end end local function submit_form(form, n) assert(type(form) == "dom_element" and form.tag_name == "FORM") assert(type(n) == "number") local submits = form:query("input[type=submit]") local submit = submits[n == 0 and 1 or n] assert(submit) -- Fall back to clicking if submit input has onclick handler if n > 0 or submit.attr.onclick then submit:click() else form:submit() end end local function contains(tbl, item) for _, v in ipairs(tbl) do if v == item then return true end end return false end local stylesheet = [===[ #luakit_select_overlay { position: absolute; left: 0; top: 0; z-index: 2147483647; /* Maximum allowable on WebKit */ } #luakit_select_overlay .hint_overlay { display: block; position: absolute; background-color: #ffff99; border: 1px dotted #000; opacity: 0.3; } #luakit_select_overlay .hint_label { display: block; position: absolute; background-color: #000088; border: 1px dashed #000; color: #fff; font-size: 10px; font-family: monospace, courier, sans-serif; opacity: 0.4; } #luakit_select_overlay .hint_selected { background-color: #00ff00 !important; } ]===] local dsl_coroutines = {} ui:add_signal("dsl_extension_reply", function(_, _, v, view_id) coroutine.resume(dsl_coroutines[view_id],v) end) local function traverse(view_id, t) if type(t) == "table" and t.sentinel then ui:emit_signal("dsl_extension_query", t.key,t.arg,view_id) return coroutine.yield(dsl_coroutines[view_id]) elseif type(t) == "table" then for k,v in pairs(t) do t[k] = traverse(view_id, v) end end return t end local function apply (form, form_spec, page) local co = coroutine.create(function () -- Map of attr -> value that form has to match local attrs = {} for _, v in ipairs({"method", "name", "id", "action", "className"}) do attrs[v] = traverse(page.id, form_spec[v]) -- traverse and evaluate attributes form_spec[v] = attrs[v] -- write them back to the form_spec end if not element_attributes_match(form, attrs) then return false end traverse(page.id, form_spec) -- traverse the rest of the form_spec for _, input_spec in ipairs(form_spec.inputs) do local matches = match("input", {"name", "id", "className", "type"}, input_spec, {form}) if #matches > 0 then local val = input_spec.value or input_spec.checked if val then fill_input(matches, val) end if input_spec.focus then matches[1]:focus() end if input_spec.select then matches[1]:select() end end end if form_spec.submit then submit_form(form, type(form_spec.submit) == "number" and form_spec.submit or 0) end dsl_coroutines[page.id] = nil return true end) dsl_coroutines[page.id] = co coroutine.resume(co) end local function formfiller_fill (page, form, form_specs) assert(type(page) == "page") assert(type(form) == "dom_element" and form.tag_name == "FORM") for _, form_spec in ipairs(form_specs) do if apply(form, form_spec, page) then break end end ui:emit_signal("finished") end local function get_form_spec_matches_on_page(page, form_specs) assert(type(page) == "page") assert(type(form_specs) == "table") local forms = {} for _, form_spec in ipairs(form_specs) do local attrs = {"method", "name", "id", "action", "className"} local matches = match("form", attrs, form_spec, { page.document.body }) for _, form in ipairs(matches) do forms[#forms+1] = form end end return forms end local function formfiller_fill_fast (page, form_specs) -- Build list of matchable form elements local forms = get_form_spec_matches_on_page(page, form_specs) if #forms == 0 then ui:emit_signal("failed", page.id, "page has no matchable forms") return end if #forms > 1 then ui:emit_signal("failed", page.id, "page has more than one matchable form") return end formfiller_fill(page, forms[1], form_specs) end local function formfiller_add (page, form) assert(type(page) == "page") assert(type(form) == "dom_element" and form.tag_name == "FORM") local function to_lua_str(str) return "'" .. str:gsub("([\\'])", "\\%1").. "'" end local function to_lua_pat(str) return to_lua_str(lousy.util.lua_escape(str)) end local function add_attr(elem, attr, indent, tail) local a = elem.attr[attr] if type(a) == "string" and a ~= "" then return indent .. attr .. " = " .. to_lua_str(a) .. tail else return "" end end local inputs = filter(form:query("input"), function(_, input) return not contains({"button", "submit", "hidden"}, input.type) end) -- Build formfiller config for form local str = { "on " .. to_lua_pat(page.uri) .. " {\n"} table.insert(str, " form {\n") for _, attr in ipairs({"method", "action", "id", "className", "name"}) do table.insert(str, add_attr(form, attr, " ", ",\n")) end for _, input in ipairs(inputs) do table.insert(str, " input {\n ") for _, attr in ipairs({"id", "className", "name", "type"}) do table.insert(str, add_attr(input, attr, "", ", ")) end if contains({"radio", "checkbox"}, input.type) then table.insert(str, "\n checked = " .. (input.checked or "false") .. ",\n") else table.insert(str, "\n value = " .. to_lua_str(input.value or "") .. ",\n") end table.insert(str, " },\n") end table.insert(str, " submit = true,\n") table.insert(str, " autofill = true,\n") table.insert(str, " },\n") table.insert(str, "}\n\n") str = table.concat(str) ui:emit_signal("add", page.id, str) end ui:add_signal("fill-fast", function(_, page, form_specs) formfiller_fill_fast(page, form_specs) end) ui:add_signal("apply_form", function(_, page, form) formfiller_fill_fast(page, {form}) end) ui:add_signal("leave", function (_, page) select.leave(page) end) ui:add_signal("focus", function (_, page, step) select.focus(page, step) end) -- Visual formfiller add ui:add_signal("enter", function (_, page) -- Filter forms to those with valid inputs local forms = page.document.body:query("form") forms = filter(forms, function(_, form) local inputs = form:query("input") inputs = filter(inputs, function(_, input) return not contains({"button", "submit", "hidden"}, input.type) end) return #inputs > 0 end) -- Error out if there aren't any forms to add if #forms == 0 then ui:emit_signal("failed", page.id, "page has no forms that can be added") end select.enter(page, forms, stylesheet, true) end) ui:add_signal("changed", function (_, page, text) local _, num_visible_hints = select.changed(page, "^" .. text, nil, text) if num_visible_hints == 1 and text ~= "" then local hint = select.focused_hint(page) formfiller_add(page, hint.elem) end end) ui:add_signal("select", function (_, page) local hint = select.focused_hint(page) formfiller_add(page, hint.elem) end) ui:add_signal("filter", function (_, page, form_specs) local matching_form_specs = {} local roots = { page.document.body } for _, form_spec in ipairs(form_specs) do local matches = match("form", {"method", "name", "id", "action", "className"}, form_spec, roots) if #matches > 0 then matching_form_specs[#matching_form_specs+1] = form_spec end end ui:emit_signal("filter", page.id, matching_form_specs) end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/go_input.lua000066400000000000000000000040031475363222200161060ustar00rootroot00000000000000--- Go to the first input on a page and enter insert mode. -- -- This module adds a key binding to quickly focus the first text input on a -- page and enter insert mode. A count is also accepted, which allows choosing a -- specific text input other than the first. -- -- @module go_input -- @copyright 2009 Aldrik Dunbar -- @copyright 2010 Paweł Zuzelski local webview = require("webview") local modes = require("modes") local add_binds = modes.add_binds local _M = {} local go_input = [=[ (function (count) { var elements = document.querySelectorAll("textarea, input" + [ ":not([type='button'])", ":not([type='checkbox'])", ":not([type='hidden'])", ":not([type='image'])", ":not([type='radio'])", ":not([type='reset'])", ":not([type='submit'])", ":not([type='file'])"].join("")); if (elements) { var el, i = 0, n = 0; while((el = elements[i++])) { var style = getComputedStyle(el, null); if (style.display !== 'none' && style.visibility === 'visible') { n++; if (n == count) { if (el.type === "file") { el.click(); } else { el.focus(); el.setSelectionRange(el.value.length, el.value.length); el.scrollIntoViewIfNeeded(); } return "form-active"; } } } } return "root-active"; })]=] -- Add `w:go_input()` webview method webview.methods.go_input = function(_, w, count) local js = string.format("%s(%d);", go_input, count or 1) w.view:eval_js(js, { callback = function(ret) w:emit_form_root_active_signal(ret) end}) end -- Add `gi` binding to normal mode add_binds("normal", { { "gi", "Focus the first text input on the current page and enter insert mode.", function (w, m) w:go_input(m.count) end, {count=1} } }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/go_next_prev.lua000066400000000000000000000061231475363222200167660ustar00rootroot00000000000000--- Follow "next" or "prev" links on a page. -- -- Many web pages make use of pagination. This module does away with the need to -- hunt for the next- and previous-page buttons by automatically detecting them -- and clicking them for you on demand. -- -- @module go_next_prev -- @copyright 2009 Aldrik Dunbar -- @copyright 2010 Mason Larobina local modes = require("modes") local add_binds = modes.add_binds local _M = {} local go_next = [=[ (function() { function click(e) { if (e.href) document.location = e.href; else { var ev = document.createEvent("MouseEvent"); ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); e.dispatchEvent(ev); } } var e = document.querySelector("[rel='next']"); if (e) // Wow a developer that knows what he's doing! click(e); else { // Search from the bottom of the page up for a next link. var els = Array.from(document.getElementsByTagName("a")).filter( elem => elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); var res = "^\\s*(下一页|下一章|下一张|下一篇|下页|后页)>?\\s*$,\\bnext\\b," + "^>$,^(>>|»|→|≫)$,^(>|»),(>|»)$,\\bmore\\b,\\bnewer\\b" for (let r of res.split(",").map(r => new RegExp(r, "i"))) { var i = els.length; while ((e = els[--i])) { if (e.text.search(r) > -1) { click(e); return; } } } } })(); ]=] local go_prev = [=[ (function() { function click(e) { if (e.href) document.location = e.href; else { var ev = document.createEvent("MouseEvent"); ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); e.dispatchEvent(ev); } } var e = document.querySelector("[rel='prev']"); if (e) click(e); else { var els = Array.from(document.getElementsByTagName("a")).filter( elem => elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); var res = "^\\s* new RegExp(r, "i"))) { var i = els.length; while ((e = els[--i])) { if (e.text.search(r) > -1) { click(e); return; } } } } })(); ]=] -- Add `[[` & `]]` bindings to the normal mode. add_binds("normal", { { "%]%]", "Open the next page in the current tab.", function (w) w.view:eval_js(go_next, { no_return = true }) end }, { "%[%[", "Open the previous page in the current tab.", function (w) w.view:eval_js(go_prev, { no_return = true }) end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/go_up.lua000066400000000000000000000075401475363222200154040ustar00rootroot00000000000000--- Go one step upward in the URI path structure. -- -- This module adds keybindings that allow you to easily navigate upwards in the -- URI hierarchy of the current page. For example, if the current page is -- `www.example.com/photos/pets/`, then going up once will navigate to -- `www.example.com/photos/`, and going up once more will navigate to -- `www.example.com`. -- -- It is possible to go up multiple steps at once. -- -- # Finer details -- -- When using this module to navigate websites, generally you don't need to -- worry about the finer details of how the current page URI is transformed. -- The steps taken to transform the curent page URI, however, are listed here for -- completeness. -- -- When going up a single step, several checks are done on the current page URI: -- -- 1. If there is a URI fragment, that is removed. -- For example: `www.example.com/photos/#cool-photos-section` will become `www.example.com/photos/`. -- 2. Otherwise, if there are any query parameters, they are removed. -- For example: `www.example.com/photos/?cool=very` will become `www.example.com/photos/`. -- 3. Otherwise, if there is a sub-path, one section of that is removed. -- For example: `www.example.com/photos/` will become `www.example.com/`. -- 4. Finally, if there are sub-domains in the host, the most specific -- will be removed. This also applies to `www`. -- For example: `www.example.com` will become `example.com`. -- -- @module go_up -- @copyright 2010-2012 Mason Larobina -- @copyright 2012 LokiChaos -- TODO check host against public TLD list to prevent returning only -- top-level domain. local modes = require("modes") local add_binds = modes.add_binds local match = string.match local _M = {} local function go_up_step(u) -- Step 1: remove fragment if u.fragment then u.fragment = nil return end -- Step 2: remove query params if u.query then u.query = nil return end -- Step 3: remove sub-path from uri local path = u.path if path and path ~= "/" then u.path = match(path, "(.*/)[^/]*/$") or match(path, "(.*/)[^/]+$") return end -- Step 4: remove sub-domains from host local host = u.host if host then u.user = nil u.password = nil u.host = match(host, "%.(.+)$") or host return end end --- Go up a number of steps in the path structure for a given URI. -- @tparam string uri The initial URI. -- @tparam number n The number of steps to traverse up the path structure of the -- URI. -- @treturn string The modified URI. function _M.go_up(uri, n) local u = soup.parse_uri(uri) if not u then error("invalid uri: " .. tostring(uri)) end for _ = 1, (n or 1) do go_up_step(u) end return soup.uri_tostring(u) end --- Remove any fragment and query from a given URI, and set the path to `/`. -- @tparam string uri The initial URI. -- @treturn string The modified URI. function _M.go_upmost(uri) local u = soup.parse_uri(uri) if not u then error("invalid uri: " .. tostring(uri)) end u.path = "/" u.fragment = nil u.query = nil return soup.uri_tostring(u) end -- Add `gu` & `gU` binds to the normal mode. add_binds("normal", { { "^gu$", "Go `[count=1]` step upward in the URI path structure.", function (w, m) local uri = w.view.uri if not uri or uri == "about:blank" then return end w.view.uri = _M.go_up(uri, m.count or 1) end }, { "^gU$", "Go to up-most URI (maintains host).", function (w) local uri = w.view.uri if not uri or uri == "about:blank" then return end w.view.uri = _M.go_upmost(uri) end }, }) -- Return module table return setmetatable(_M, { __call = function (_, ...) return _M.go_up(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/gopher.lua000066400000000000000000000373061475363222200155620ustar00rootroot00000000000000--- Add gopher:// scheme support. -- -- The module adds support for Gopher network with basic rendering. -- -- @module gopher -- @author Ygrex local lousy = require("lousy") local settings = require("settings") local theme = lousy.theme.get() local socket_loaded, socket = pcall(require, "socket") if not socket_loaded then msg.error("Failed to load LuaSocket: %s", tostring(socket)) return end local error_page = require("error_page") local webview = require("webview") local _M = {} luakit.register_scheme("gopher") -- menu entry representing the type of the item local function gophertype_to_text(gophertype) return ({ -- canonical types according to rfc1436 ["0"] = 'TXT ', -- text file ["1"] = 'DIR ', -- submenu ["2"] = 'CCSO', -- CCSO Nameserver, not really supported type -- ["3"] = 'ERR ", -- Error ["4"] = 'HEX ', -- BinHex-encoded ["5"] = 'DOS ', -- DOS file ["6"] = 'UENC', -- uuencoded data ["7"] = 'FIND', -- search ["8"] = 'TEL ', -- telnet ["9"] = 'BIN ', -- binary -- ["+"] = 'SRV ', -- redundant server ["T"] = 'TEL ', -- telnet ["g"] = 'GIF ', -- gif ["I"] = 'IMG ', -- image -- well known types ["h"] = 'HTML', -- html -- additional types seen in the wild ["d"] = 'DOC ', -- any document format ["M"] = 'MBOX', -- mbox ["p"] = 'IMG ', -- image ["P"] = 'PDF ', -- binary pdf document ["s"] = 'SND ', -- sound })[gophertype] or "??? " end --- Parse Gopher menu entry. -- @tparam string line Literal string line representing a Gopher menu entry. -- @tparam table url Parsed URL structure from parse_url(). -- @treturn table Structure representing the menu entry tokens. _M.parse_gopher_line = function(line, url) local ret = { line = line, item_type = nil, display_string = nil, selector = nil, host = nil, port = nil, scheme = nil, } if not line:match("\t") then ret.item_type = "i" ret.display_string = line return ret end ret.item_type = line:sub(1, 1) local fields = {}; for chunk in line:sub(2):gmatch("([^\t]*)\t?") do fields[#fields + 1] = chunk end ret.display_string = fields[1] or "" ret.selector = fields[2] or "" ret.host = fields[3] or url.host ret.port = (fields[4] or tostring(url.port)):gsub("[^0-9]", "") ret.scheme = "gopher" if ret.item_type == "T" or ret.item_type == "8" then ret.scheme = "telnet" end return ret end local parse_gopher_line = _M.parse_gopher_line --- Evaluate hyperlink for a gopher menu entry. -- @tparam table entry Gopher menu entry structure from parse_gopher_line(). -- @treturn string Valid URL for the menu entry. _M.href_source = function(entry) local src = entry.selector:match("^/?URL:(.+)$") if not src then if entry.scheme == "telnet" then src = ([[telnet://%s:%s/]]):format(entry.host, entry.port) else src = ([[%s://%s:%s/%s%s]]):format( entry.scheme, entry.host, entry.port, entry.item_type, luakit.uri_encode(entry.selector, "/") ) end end return src end local href_source = _M.href_source -- chop periods per RFC1436 local function chop_periods(data) -- trailing \n is always expected if data:sub(-1) ~= "\n" then data = data .. "\n" end -- the trailing period on a line itself is chopped off local dot_pos, _, prefix = data:find("(\r?\n)%.\r?\n$") if dot_pos then data = data:sub(1, dot_pos - 1 + #prefix) elseif data:match("^%.\r?\n$") then -- even if there is nothing else data = "\n" end -- on every line any leading period is chopped off data = data:gsub("(\r?\n)%.", "%1") if data:sub(1, 1) == "." then -- and on the first line data = data:sub(2) end return data end local function stylesheet() local bg, fg, link; if not (theme.gopher_dark and theme.gopher_light) then msg.warn("Cannot find gopher styles. Please sync your theme.lua. Loading defaults.") bg = "#E8E8E8"; fg = "#17181C"; link = "#03678D" else if settings.get_setting("application.prefer_dark_mode") then bg = theme.gopher_dark.bg; fg = theme.gopher_dark.fg; link = theme.gopher_dark.link else bg = theme.gopher_light.bg; fg = theme.gopher_light.fg; link = theme.gopher_light.link end end return [[ ]]; end; local function text_to_html(data, url) return [[ ]] .. url.title .. [[ ]] .. stylesheet() .. [[
    ]] .. lousy.util.escape(chop_periods(data)) .. [[
    ]]; end; local function menu_entry_input(anchor_name) return [[
    | ]] end local function menu_html_header(title) return [[ ]] .. title .. [[ ]] .. stylesheet() .. [[ ]] end -- present Gopher menu as an HTML page local function menu_to_html(data, url) data = chop_periods(data) local html = { "", menu_html_header(url.title), "
    "
        };
        local line_num = 0
        for line in data:gmatch("(.-)\r?\n") do
            line_num = line_num + 1
            local entry = parse_gopher_line(line, url)
            if entry.item_type == "i" then
                html[#html + 1] = ("    | %s"):format(lousy.util.escape(entry.display_string))
            else
                local src = href_source(entry)
                local type_text = gophertype_to_text(entry.item_type) .. "+"
                local anchor_name = "anchor_" .. tostring(line_num)
                local input = menu_entry_input(anchor_name)
                if entry.item_type ~= "7" then input = "" end
                html[#html + 1] = ([[%s %s%s]]):format(
                    type_text,
                    src,
                    anchor_name,
                    lousy.util.escape(entry.display_string),
                    input
                )
            end
        end
        html[#html + 1] = "
    " return table.concat(html, "\n") end --- Guess the image MIME type by a filename suffix. -- @tparam string ext The filename suffix (without leading dot). -- @treturn string Appropriate MIME type. _M.image_mime_type = function(ext) return ({ gif = "image/gif", jpeg = "image/jpeg", jpg = "image/jpeg", pcx = "image/pcx", png = "image/png", svg = "image/svg+xml", svgz = "image/svg+xml", tif = "image/tiff", tiff = "image/tiff", bmp = "image/x-ms-bmp", pbm = "image/x-portable-bitmap", pgm = "image/x-portable-graymap", ppm = "image/x-portable-pixmap", xwd = "image/x-xwindowdump", })[tostring(ext):lower()] or "application/octet-stream" end local image_mime_type = _M.image_mime_type -- convert raw data received from server into the browser's representation local function data_to_browser(data, url) local mime = "text/html" local converted = data if url.gophertype == "1" or url.gophertype == "7" then converted = menu_to_html(data, url) elseif url.gophertype == "0" then converted = text_to_html(data, url) elseif url.gophertype == "4" then mime = "application/mac-binhex40" elseif url.gophertype == "5" then mime = "application/octet-stream" elseif url.gophertype == "6" then mime = "text/x-uuencode" elseif url.gophertype == "9" then mime = "application/octet-stream" elseif url.gophertype == "d" then mime = "application/octet-stream" elseif url.gophertype == "g" then mime = "image/gif" elseif url.gophertype == "M" then mime = "application/mbox" elseif url.gophertype == "p" then mime = "image/png" elseif url.gophertype == "P" then mime = "application/pdf" elseif url.gophertype == "I" then mime = image_mime_type(url.selector:match("%.(.-)$")) elseif url.gophertype ~= "h" then msg.error("Unsupported Gopher item type: '%s'", url.gophertype) error("Unsupported Gopher item type", 0) end return converted, mime end --- Parse Gopher URL. -- @tparam string url Gopher URL starting with gopher:// -- @treturn table A structure representing the URL tokens. _M.parse_url = function(url) local host_port, gopher_path = url:match("gopher://([^/]+)/?(.-)$") if not host_port then return end local host = host_port local port = host_port:match(":([0-9]+)$") if port then host = host_port:match("^(.+):[0-9]+$") port = tonumber(port) else port = 70 end local gophertype = gopher_path:sub(1, 1) if not gophertype or #gophertype < 1 then gophertype = "1" end local selector, after_selector = gopher_path:sub(2):match("^(.-)%%09(.*)$") if not selector then selector = gopher_path:sub(2) end selector = luakit.uri_decode(selector) local search, gopher_plus_string if after_selector then search, gopher_plus_string = after_selector:match("^(.-)%%09(.*)$") if not search then search = after_selector end search = luakit.uri_decode(search) if gopher_plus_string then gopher_plus_string = luakit.uri_decode(gopher_plus_string) end end local title = selector if title:sub(1, 1) ~= "/" then title = "/" .. title end title = ("/%s%s"):format(gophertype, title) return { host = host, port = port, gopher_path = gopher_path, gophertype = gophertype, selector = selector, search = search, gopher_plus_string = gopher_plus_string, title = title, } end local parse_url = _M.parse_url -- establish connection, wait for the socket to become writable local function _net_establish_connection(host, port) local conn = socket.tcp() conn:settimeout(0) local res, err, _ = conn:connect(host, port) if not res then if err ~= "timeout" then return error("Socket error: " .. tostring(err), 0) end while true do if coroutine.yield() then conn:shutdown("both") return end _, res, err = socket.select(nil, {conn}, 0) if (res or {})[conn] then break end if err ~= "timeout" then return error("Socket error: " .. tostring(err), 0) end end end return conn end -- non-blocking sending local function _net_send_message(conn, message) local res, err, last local sent = 0 while true do res, err, last = conn:send(message, sent + 1) if res == #message then break end if not res then if err ~= "timeout" then return error("Socket error: " .. tostring(err), 0) end end sent = res or last if coroutine.yield() then conn:shutdown("both") return end end return sent end -- non-blocking reading local function _net_read_data(conn) local chunks = {} while true do local res, err, last = conn:receive("*a") if err == "closed" then res = last end if res then chunks[#chunks + 1] = res break end if err ~= "timeout" then return error("Socket error: " .. tostring(err), 0) end chunks[#chunks + 1] = last if coroutine.yield() then conn:shutdown("both") return end end return table.concat(chunks) end -- perform network transaction in non-blocking mode local function net_request(host, port, message) local conn = _net_establish_connection(host, port) if not conn then return end local sent = _net_send_message(conn, message) if not sent then return end local data = _net_read_data(conn) conn:shutdown("both") return data end local load_timer = timer{interval = 50} local loads = {} load_timer:add_signal("timeout", function () if not next(loads) then load_timer:stop() end end) -- remove a background loader local function remove_loader(v, loader) local corou = loads[v] if corou then loads[v] = nil coroutine.resume(corou, "stop") end if loader then load_timer:remove_signal("timeout", loader) end end -- forward request to error_page module local function show_error_page(v, request, reason) pcall(error_page.show_error_page, v, { heading = "Gopher Site Loading Failed", content = reason, request = request }) end -- finish request with appropriate page content local function send_data_to_browser(v, request, data, url) local res, html, mime = pcall(data_to_browser, data, url) if res then request:finish(html, mime) else show_error_page(v, request, html) end end webview.add_signal("init", function (view) view:add_signal("scheme-request::gopher", function (v, uri, request) local url = assert(parse_url(uri)) local message = table.concat({url.selector, url.search}, "\t") local net = coroutine.create(function () return net_request(url.host, url.port, message .. "\r\n") end) if not load_timer.started then load_timer:start() end loads[v] = net local function loader () if loads[v] ~= net then remove_loader(v, loader) return end local alive, _ = pcall(function() return v.is_loading end) local status, res = coroutine.resume(net, not alive) if not status then if not request.finished then show_error_page(v, request, res) end return remove_loader(v, loader) end if not res then return end if not request.finished then send_data_to_browser(v, request, res, url) end remove_loader(v, loader) end load_timer:add_signal("timeout", loader) end) view:add_signal("load-status", function (v, status) if status == "failed" then remove_loader(v) end end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/help_chrome.lua000066400000000000000000000332151475363222200165560ustar00rootroot00000000000000--- Provides luakit://help/ page. -- -- This module provides the page and all of its sub-pages, -- including the built-in documentation browser. -- -- @module help_chrome -- @copyright 2016 Aidan Holm -- @copyright 2012 Mason Larobina local lousy = require("lousy") local chrome = require("chrome") local history = require("history") local add_cmds = require("modes").add_cmds local error_page = require("error_page") local get_modes = require("modes").get_modes local markdown = require("markdown") local _M = {} local index_html_template = [==[ Luakit Help

    About Luakit

    Luakit is a highly configurable, browser framework based on the WebKit web content engine and the GTK+ toolkit. It is very fast, extensible with Lua and licensed under the GNU GPLv3 license. It is primarily targeted at power users, developers and any people with too much time on their hands who want to have fine-grained control over their web browser’s behaviour and interface.

    Useful (though outdated) documentation can be found here:

    Configuration

    Settings

    The available settings are displayed at:

    Key bindings

    Currently active bindings are listed in the following page.

    {chromepageshtml}

    API Documentation

    Questions, Bugs, and Contributions

    Please report any bugs or issues you find at the GitHub issue tracker.

    If you have any feature requests or questions, feel free to open an issue for those as well. Pull requests and patches are both welcome, and there are plenty of areas that could be improved, especially tests and documentation.

    License

    Luakit is licensed under the GNU General Public License version 3 or later. The abbreviated text of the license is as follows:

    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 https://www.gnu.org/licenses/.

    ]==] local gen_html_chrome_pages = function() local links = "" for _, v in ipairs(chrome.available_handlers()) do links = links .. "
  • " .. v .. "
  • \n" end return [==[

    luakit:// pages

    These are all the available luakit:// pages:

      ]==] .. links .. "
    " end local help_index_page = function () local html_subs = { style = chrome.stylesheet, version = luakit.version, webkitversion = luakit.webkit_version, chromepageshtml = gen_html_chrome_pages(), } local html = string.gsub(index_html_template, "{(%w+)}", html_subs) return html end local builtin_module_set = { extension = true, ipc = true, luakit = true, msg = true, soup = true, utf8 = true, } local help_doc_index_page_preprocess = function (inner, style) -- Mark each list with the section heading just above it inner = inner:gsub("

    (%S+)

    %s*
      ", "

      %1

        ") -- Customize each module link bullet inner = inner:gsub('
      • ', function (pkg) local class = package.loaded[pkg] and "enabled" or "disabled" if builtin_module_set[pkg] then class = "builtin" end return '
      • ' end) style = style .. [===[ div#wrap { padding-top: 0; } h2 { margin: 1em 0 0.75em; } h2 + ul { margin: 0.5em 0; } ul { display: flex; flex-wrap: wrap; padding-left: 1rem; list-style-type: none; } ul > li { flex: 1 0 14rem; padding: 0.2em 0.2rem 0.2rem 1.5rem; margin: 0px !important; position: relative; } ul > li:not(.dummy):before { font-weight: bold; width: 1.5rem; text-align: center; left: 0; position: absolute; } ul > li:not(.dummy):before { content: "●"; transform: translate(1px, -1px); z-index: 0; } ul.Modules > li.enabled:before { content: "\2713 "; color: darkgreen; } ul.Modules > li.disabled:before { content: "\2717 "; color: darkred; } ul.Modules > li.enabled:before, ul.Modules > li.disabled:before { transform: none; } #page-header { z-index: 100; } ]===] return inner, style end local help_doc_page = function (v, path, request) -- Generate HTML documenting the additional bindings added by module `m` local generate_mode_doc_html = function (m) local fmt = function (str) -- Fix < and > being escaped inside code -_- fail return markdown(str):gsub("
        (.-)
        ", lousy.util.unescape) end local bind_to_html = function (b) b = lousy.bind.bind_to_string(b) or "???" if b:match("^:.") then local cmds = {} for _, c in ipairs(lousy.util.string.split(b, ", ")) do c = lousy.util.escape(c) table.insert(cmds, ("
      • %s"):format(c)) end return "
          " .. table.concat(cmds, "") .. "
        " elseif b:match("^^.") then b = ("%s"):format(lousy.util.escape(b)) else b = ("%s"):format(lousy.util.escape(b)) end return "
        • " .. b .. "
        " end local modes, parts = get_modes(), {} for name, mode in pairs(modes) do local binds = {} for _, bm in pairs(mode.binds or {}) do local _, a = unpack(bm) local src_m = debug.getinfo(a.func, "S").source:match("lib/(.*)%.lua") if src_m == m then binds[#binds+1] = bm end end if #binds > 0 then parts[#parts+1] = string.format("

        %s mode

        ", name) parts[#parts+1] = "
          \n" for _, bm in ipairs(binds) do local b, a = unpack(bm) local b_desc = a.desc or "No description" b_desc = fmt(lousy.util.string.dedent(b_desc)):gsub("", "", 2) parts[#parts+1] = "
        • " .. bind_to_html(b) parts[#parts+1] = "
          " .. b_desc .. "
          " end parts[#parts+1] = "
        " end end return #parts > 0 and "

        Binds and Modes

        " .. table.concat(parts, "") or "" end local extract_doc_html = function (file) local prefix = luakit.dev_paths and "doc/apidocs/" or (luakit.install_paths.doc_dir .. "/") local ok, blob = pcall(lousy.load, prefix .. file) if not ok then return nil, prefix .. file end local style = blob:match("") local inner = blob:match("(
        .*
        )%s*") if file == "index.html" then inner, style = help_doc_index_page_preprocess(inner, style) else style = style .. [===[ #wrap { padding: 1rem; } header#page-header { position: static; } div.content-margin { padding: 0; } .status_indicator { position: absolute; top: 0; right: 0; border-radius: .3125em; padding: .3em 0.5em; -webkit-user-select: none; cursor: default; line-height: 1.1rem; font-weight: bold; } .status_indicator.active { border: 2px solid #008800; color: #008800; } .status_indicator.inactive { border: 2px solid #880000; color: #880000; } .status_indicator.builtin { border: 2px solid #444444; color: #444444; } .status_indicator.active::before { content: "✓ "; } .status_indicator.inactive::before { content: "✗ "; } .status_indicator.builtin::before { content: "● "; vertical-align: top; line-height: 1.2; } ]===] end local m = file:match("^modules/(.*)%.html$") if m then local modes_binds_html = generate_mode_doc_html(m) local i = inner:find("", 1, true) inner = inner:sub(1, i-1) .. modes_binds_html .. inner:sub(i) local m_status = package.loaded[m] and "active" or "inactive" if builtin_module_set[m] then m_status = "builtin" end local tooltip = ({ active = "This module is active and currently running.", inactive = "This module is inactive.", builtin = "This module is builtin.", })[m_status] local status_indicator_html = ([==[ %s ]==]):format(m_status, tooltip, m_status:gsub("^%l", string.upper)) i = inner:find("", 1, true) inner = inner:sub(1, i-1) .. status_indicator_html .. inner:sub(i) end return inner, style end local doc_html_template = [==[ Luakit API Documentation
        {doc_html}
        ]==] local doc_html, doc_style = extract_doc_html(path:gsub("[?#].*", "")) if not doc_html then local file = doc_style error_page.show_error_page(v, { heading = "Documentation not found", content = "Opening " .. file .. " failed", buttons = { path ~= "index.html" and { label = "Return to API Index", callback = function (vv) vv.uri = "luakit://help/doc/index.html" end } or nil }, request = request, }) return end local html_subs = { style = chrome.stylesheet .. doc_style, doc_html = doc_html, } local html = string.gsub(doc_html_template, "{([%w_]+)}", html_subs) return html end chrome.add("help", function (v, meta) if meta.path:match("^/?$") then return help_index_page() elseif meta.path:match("^doc/?") then return help_doc_page(v, ({meta.path:match("^doc/?(.*)$")})[1], meta.request) end end, nil, {}) add_cmds({ { ":help", "Open in a new tab.", function (w) w:new_tab("luakit://help/") end }, }) -- Prevent history items from turning up in history history.add_signal("add", function (uri) if string.match(uri, "^luakit://help/") then return false end end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/hide_scrollbars.lua000066400000000000000000000010231475363222200174200ustar00rootroot00000000000000--- Hide scrollbars. -- -- Hides all element scrollbars. Elements can still be scrolled as usual. -- -- @module hide_scrollbars -- @copyright 2016 Aidan Holm local webview = require("webview") local _M = {} local disable_scrollbar_ss = stylesheet{ source = [===[ ::-webkit-scrollbar { width: 0 !important; height: 0 !important; } ]===] } webview.add_signal("init", function (view) view.stylesheets[disable_scrollbar_ss] = true end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/history.lua000066400000000000000000000067161475363222200160000ustar00rootroot00000000000000--- Save history in sqlite3 database. -- -- This module provides browsing history support. Pages are saved to an on-disk -- database automatically as the user browses. -- -- The page is provided by the `history_chrome` module. -- -- @module history -- @copyright 2010-2011 Mason Larobina local os = require("os") local webview = require("webview") local lousy = require("lousy") local _M = {} --- Path to history database. -- @readwrite _M.db_path = luakit.data_dir .. "/history.db" local query_find_last local query_insert local query_update_visits local query_update_title -- Setup signals on history module lousy.signal.setup(_M, true) --- Connect to and initialize the history database. function _M.init() -- Return if database handle already open if _M.db then return end _M.db = sqlite3{ filename = _M.db_path } _M.db:exec [[ PRAGMA synchronous = OFF; PRAGMA secure_delete = 1; CREATE TABLE IF NOT EXISTS history ( id INTEGER PRIMARY KEY, uri TEXT, title TEXT, visits INTEGER, last_visit INTEGER ); ]] query_find_last = _M.db:compile [[ SELECT id FROM history WHERE uri = ? ORDER BY last_visit DESC LIMIT 1 ]] query_insert = _M.db:compile [[ INSERT INTO history VALUES (NULL, ?, ?, ?, ?) ]] query_update_visits = _M.db:compile [[ UPDATE history SET visits = visits + 1, last_visit = ? WHERE id = ? ]] query_update_title = _M.db:compile [[ UPDATE history SET title = ? WHERE id = ? ]] end luakit.idle_add(_M.init) --- Add a URI to the user's history. -- @tparam string uri The URI to add to the user's history. -- @tparam string title The title to associate with the URI. -- @tparam[opt] boolean update_visits `false` if the last visit time for this URI -- should not be updated. -- @default `true` function _M.add(uri, title, update_visits) if not _M.db then _M.init() end -- Ignore blank uris if not uri or uri == "" or uri == "about:blank" then return end -- Ignore luakit:// urls if string.find(uri, "^luakit://") then return end -- Ask user if we should ignore uri if _M.emit_signal("add", uri, title) == false then return end -- Find existing item local item = (query_find_last:exec{uri})[1] if item then if update_visits ~= false then query_update_visits:exec{os.time(), item.id} end if title then query_update_title:exec{title, item.id} end else query_insert:exec{uri, title, 1, os.time()} end end --- Set of webviews on which to freeze history collection. -- @type {[string]=boolean} -- @readwrite _M.frozen = setmetatable({}, { __mode = "k" }) webview.add_signal("init", function (view) -- Add items & update visit count view:add_signal("load-status", function (_, status) if view.private then return end if _M.frozen[view] then return end if status == "committed" then _M.add(view.uri) end end) -- Update titles view:add_signal("property::title", function () if view.private then return end if _M.frozen[view] then return end local title = view.title if title and title ~= "" then _M.add(view.uri, title, false) end end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/history_chrome.lua000066400000000000000000000270761475363222200173370ustar00rootroot00000000000000--- Save history in sqlite3 database - chrome page. -- -- This module provides the luakit://history/ chrome page - a user interface for -- searching the web browsing history. -- -- @module history_chrome -- @copyright 2010-2011 Mason Larobina -- Grab the luakit environment we need local history = require("history") local chrome = require("chrome") local modes = require("modes") local add_cmds = modes.add_cmds local _M = {} --- CSS applied to the history chrome page. -- @readwrite _M.stylesheet = [===[ .day-heading { font-size: 1.3em; font-weight: 100; margin: 1em 0 0.5em 0; -webkit-user-select: none; cursor: default; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .day-sep { height: 1em; } .item { font-weight: 400; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .item span { padding: 0.2em; } .item .time { -webkit-user-select: none; cursor: default; color: #888; display: inline-block; width: 5em; text-align: right; border-right: 1px solid #ddd; padding-right: 0.5em; margin-right: 0.1em; } .item a { text-decoration: none; } .item .domain a { color: #aaa; } .item .domain a:hover { color: #666; } .item.selected { background-color: #eee; } .nav-button-box { margin: 2em; } .nav-button-box a { display: none; border: 1px solid #aaa; padding: 0.4em 1em; } ]===] local html_template = [==[ History
        ]==] local main_js = [=[ function createElement (tag, attributes, children, events) { let $node = document.createElement(tag) for (let a in attributes) { $node.setAttribute(a, attributes[a]) } for (let $child of children) { $node.appendChild($child) } if (events) { for (let eventType in events) { let action = events[eventType] $node.addEventListener(eventType, action) } } return $node } function empty ($el) { while ($el.firstChild) $el.removeChild($el.firstChild) } window.addEventListener('load', () => { const limit = 100 let resultsLen = 0 const $clearAll = document.getElementById('clear-all-button') const $clearResults = document.getElementById('clear-results-button') const $clearSelected = document.getElementById('clear-selected-button') const $next = document.getElementById('nav-next') const $page = document.getElementById('page') const $prev = document.getElementById('nav-prev') const $results = document.getElementById('results') const $search = document.getElementById('search') $page.value = $page.value || 1 function makeHistoryItem (h) { let domain = /https?:\/\/([^/]+)\//.exec(h.uri) domain = domain ? domain[1] : '' function escapeHTML(string) { let entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' } return String(string).replace(/[&<>"'`=\/]/g, s => entityMap[s]); } return ` ` // return createElement('div', { class: 'item', 'data-id': h.id }, [ // createElement('span', { class: 'time' }, [ // document.createTextNode(h.time) // ]), // createElement('span', { class: 'title' }, [ // createElement('a', { href: h.uri }, [ // document.createTextNode(h.title || h.uri) // ]) // ]), // createElement('span', { class: 'domain' }, [ // createElement('a', { href: '#' }, [ // document.createTextNode(domain) // ], { // click: event => { // $search.value = event.target.textContent // search() // } // }) // ]) // ], { // click: event => { // event.target.classList.toggle('selected') // $clearSelected.disabled = $results.getElementsByClassName('selected').length === 0 // } // }) } function updateClearButtons (all, results, selected) { $clearAll.disabled = !!all $clearResults.disabled = !!results $clearSelected.disabled = !!selected } function updateNavButtons () { $next.style.display = resultsLen === limit ? 'inline-block' : 'none' $prev.style.display = parseInt($page.value, 10) > 1 ? 'inline-block' : 'none' } function search () { let query = $search.value history_search({ query: query, limit: limit, page: parseInt($page.value, 10) }).then(results => { resultsLen = results.length || 0 updateClearButtons(query, !query, true) empty($results) if (!results.length) { updateNavButtons() return } $results.innerHTML = results.map((item, i) => { let lastItem = results[i - 1] || {} let html = item.date !== lastItem.date ? `
        ${item.date}
        ` : (lastItem.last_visit - item.last_visit) > 3600 ? `
        ` : ""; return html + makeHistoryItem(item) }).join('') updateNavButtons() }) } function clearEls (className) { let ids = Array.from(document.getElementsByClassName(className)) .map($el => $el.dataset.id) if (ids.length > 0) history_clear_list(ids) search() } $search.addEventListener('keydown', event => { if (event.which === 13) { // 13 is the code for the 'Return' key $page.value = 1 search() $search.blur() reset_mode() } }) document.getElementById('clear-button') .addEventListener('click', () => { $search.value = '' $page.value = 1 search() }) document.getElementById('search-button') .addEventListener('click', () => { $page.value = 1 search() }) $clearAll.addEventListener('click', () => { if (!window.confirm('Clear all browser history?')) return history_clear_all() search() $clearAll.blur() }) $clearResults.addEventListener('click', () => { clearEls('item') $clearResults.blur() }) $clearSelected.addEventListener('click', () => { clearEls('selected') $clearSelected.blur() }) $next.addEventListener('click', () => { let page = parseInt($page.value, 10) $page.value = page + 1 search() }) $prev.addEventListener('click', () => { let page = parseInt($page.value, 10) $page.value = Math.max(page - 1, 1) search() }) document.addEventListener('click', event => { if (event.target.matches(".item > .domain > a")) { $search.value = event.target.textContent search() } else if (event.target.matches(".item")) { event.target.classList.toggle('selected') $clearSelected.disabled = $results.getElementsByClassName('selected').length === 0 } }) initial_search_term().then(query => { if (query) $search.value = query search(query) }) }) ]=] local initial_search_term local export_funcs = { history_search = function (_, opts) local sql = { "SELECT", "*", "FROM history" } local where, args, argc = {}, {}, 1 string.gsub(opts.query or "", "(-?)([^%s]+)", function (notlike, term) if term ~= "" then table.insert(where, (notlike == "-" and "NOT " or "") .. string.format("(text GLOB ?%d)", argc, argc)) argc = argc + 1 table.insert(args, "*"..string.lower(term).."*") end end) if #where ~= 0 then sql[2] = [[ *, lower(uri||title) AS text ]] table.insert(sql, "WHERE " .. table.concat(where, " AND ")) end local order_by = [[ ORDER BY last_visit DESC LIMIT ?%d OFFSET ?%d ]] table.insert(sql, string.format(order_by, argc, argc+1)) local limit, page = opts.limit or 100, opts.page or 1 table.insert(args, limit) table.insert(args, limit > 0 and (limit * (page - 1)) or 0) sql = table.concat(sql, " ") if #where ~= 0 then local wrap = [[SELECT id, uri, title, last_visit FROM (%s)]] sql = string.format(wrap, sql) end local rows = history.db:exec(sql, args) for _, row in ipairs(rows) do local time = rawget(row, "last_visit") rawset(row, "date", os.date("%A, %d %B %Y", time)) rawset(row, "time", os.date("%H:%M", time)) end return rows end, history_clear_all = function (_) history.db:exec [[ DELETE FROM history ]] end, history_clear_list = function (_, ids) if not ids or #ids == 0 then return end local marks = {} for i=1,#ids do marks[i] = "?" end history.db:exec("DELETE FROM history WHERE id IN (" .. table.concat(marks, ",") .. " )", ids) end, initial_search_term = function (_) local term = initial_search_term initial_search_term = nil return term end, } chrome.add("history", function () local html = string.gsub(html_template, "{%%(%w+)}", { -- Merge common chrome stylesheet and history stylesheet stylesheet = chrome.stylesheet .. _M.stylesheet, javascript = main_js, }) return html end, nil, export_funcs) -- Prevent history items from turning up in history history.add_signal("add", function (uri) if string.match(uri, "^luakit://history/") then return false end end) add_cmds({ { ":history", "Open in a new tab.", function (w, o) initial_search_term = o.arg w:new_tab("luakit://history/") end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/image_css.lua000066400000000000000000000052321475363222200162210ustar00rootroot00000000000000--- Customize how single images are displayed in the browser. -- -- This module provides an improved version of the default WebKit image view. -- Images are now centered within the page, and an option is provided for -- specifying the page background color. -- -- @module image_css -- @copyright 2017 Aidan Holm local webview = require("webview") local window = require("window") local settings = require("settings") local wm = require_web_module("image_css_wm") local _M = {} --- The background color to use when showing images. -- @readwrite -- @type string _M.background = "#222" -- Drawn from Firefox's TopLevelImageDocument.css, with some simplifications local css_tmpl = [===[ @media not print { body { margin: 0; background-color: {background} !important; } img { text-align: center; position: absolute; margin: auto; top: 0; right: 0; bottom: 0; left: 0; } /* Prevent clipping the top part of the image */ img.verticalOverflow { margin-top: 0 !important; } } ]===] local css = string.gsub(css_tmpl, "{(%w+)}", { background = _M.background }) --- Stylesheet that is applied to webviews that contain only a single image. -- @readonly -- @type stylesheet _M.stylesheet = stylesheet{ source = css } webview.add_signal("init", function (view) local top_level = {} local uri_mime_cache = {} view:add_signal("load-status", function (v, status) if status == "provisional" then top_level[v] = true settings.override_setting_for_view(view, "webview.zoom_level", nil) elseif status == "committed" then top_level[v] = nil local mime = uri_mime_cache[v.uri] local is_image = mime and mime:match("^image/") view.stylesheets[_M.stylesheet] = is_image if is_image then wm:emit_signal(view, "image") view.zoom_level = 1.0 settings.override_setting_for_view(view, "webview.zoom_level", 100) end end end) view:add_signal("mime-type-decision", function (v, uri, mime) if top_level[v] then uri_mime_cache[uri] = mime end end) local recalc_cb = function (v) local w = window.ancestor(v) if w and w.view == v then wm:emit_signal(view, "recalc") end end view:add_signal("resize", recalc_cb) view:add_signal("switched-page", recalc_cb) view:add_signal("property::zoom_level", recalc_cb) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/image_css_wm.lua000066400000000000000000000017701475363222200167270ustar00rootroot00000000000000-- Customize how single images are displayed in the browser. -- -- @submodule image_css -- @copyright 2017 Aidan Holm local ui = ipc_channel("image_css_wm") local recalc_funcs = setmetatable({}, { __mode = "k" }) ui:add_signal("image", function (_, page) local body = page.document.body -- do nothing if loaded document is not HTML if not body then return end local img = body:query("img")[1] if not img then return end recalc_funcs[page] = function () local body_height = body.rect.height local img_height = img.rect.height local vert_overflow = img_height > body_height img.attr.class = vert_overflow and "verticalOverflow" or "" end img:add_signal("destroy", function () recalc_funcs[page] = nil end) img:add_event_listener("click", true, recalc_funcs[page]) end) ui:add_signal("recalc", function (_, page) return recalc_funcs[page] and recalc_funcs[page](); end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/introspector_chrome.lua000066400000000000000000000012431475363222200203550ustar00rootroot00000000000000--- Provided luakit://introspector/ page. -- -- **DEPRECTATION NOTICE** -- This module has been moved to binds_chrome.lua and should not be used. -- -- This module provides the luakit://introspector/ page. It is useful for -- viewing all keybindings and modes on a single page, as well as searching for -- a keybinding for a particular task. -- -- @module introspector_chrome -- @copyright 2016 Aidan Holm -- @copyright 2012 Mason Larobina msg.warn("'require \"introspector_chrome\"' is deprecated!") msg.warn("Please use 'require \"binds_chrome\"' instead.") return require "binds_chrome" -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/keysym.lua000066400000000000000000000062171475363222200156140ustar00rootroot00000000000000--- Send keys for luakit. -- -- This module parses a vim-like keystring into single keys and sends -- them to the window. A keystring is a string of keys to press, with -- special keys denoted in between angle brackets: -- -- keysym.send(w, "") -- -- See gdk/gdkkeysyms.h for a complete list of recognized key names. -- -- @module keysym -- @author Amos Bird amosbird@gmail.com -- @author Fabian Streitel luakit@rottenrei.be -- @author Mason Larobina mason.larobina@gmail.com -- @copyright 2017 Amos Bird amosbird@gmail.com -- @copyright 2010 Fabian Streitel luakit@rottenrei.be -- @copyright 2010 Mason Larobina mason.larobina@gmail.com local _M = {} --- Send synthetic keys to given widget. -- This function parses a vim-like keystring into single keys and sends -- them to the widget. A keystring is a string of keys to press, with -- special keys denoted in between angle brackets: -- -- keysym.send(w, "") -- keysym.send(w, "") -- -- Sending special unicode characters needs related keyboard layout to be set. -- keysym.send(w, "Приветствую, мир") -- -- When `window.act_on_synthetic_keys` is disabled, synthetic key events sent to -- a window widget will not trigger other key bindings. -- @tparam w The widget to send keys to. -- @tparam string keystring The key string representing synthetic keys. _M.send = function (w, keystring) assert(w) -- _M.send previously took a window object/table, and sent keys to w.win. -- It can now take any widget, and send keys to that. -- If w is a table, assume that the old interface is desired, -- and re-assign w to w.win to retain backwards compatibility. if type(w) == "table" then w = w.win end assert(type(keystring) == "string", "string expected, found "..type(keystring)) local symbol = nil local modifiers = {} local keys = {} for char in keystring:gmatch(utf8.charpattern) do if char == "<" then symbol = "" elseif char == ">" and symbol then if #symbol == 0 then error("bad keystring: " .. keystring) else table.insert(keys, { key = symbol, mods = modifiers, }) end symbol = nil modifiers = {} elseif symbol and char == "-" then if symbol:match("^[Ss]hift$") or symbol:match("^[Cc]ontrol$") or symbol:match("^[Ll]ock$") or symbol:match("^[Mm]od[1-5]$") then table.insert(modifiers, symbol:lower()) symbol = "" else error("bad modifier in keystring: " .. symbol) return end elseif not symbol then table.insert(keys, { key = char, mods = {}, }) else symbol = symbol .. char end end if symbol then error("unterminated symbol: " .. symbol) end for _, key in ipairs(keys) do w:send_key(key.key, key.mods) end end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/log_chrome.lua000066400000000000000000000221011475363222200163770ustar00rootroot00000000000000--- Luakit log viewer. -- -- This module supplies the chrome page, which displays the most -- recent log messages. -- -- @module log_chrome -- @author Aidan Holm -- @copyright 2017 Aidan Holm local chrome = require "chrome" local window = require "window" local lousy = require "lousy" local theme = lousy.theme.get() local modes = require "modes" local _M = {} --- The size of the buffer in which the most recent log messages are stored. -- This is the maximum number of entries that will be shown on the log page. -- @type number -- @default 500 -- @readwrite _M.buffer_size = 500 local log_views = {} local log_entries = {} local append_timer = timer{interval = 100} --- HTML template for luakit log chrome page content. -- @type string -- @readwrite _M.html_template = [==[ {title}
        {rows}
        Time Level Group Message
        ]==] --- Title for the log chrome page. -- @type string -- @readwrite _M.html_page_title = "Luakit log" --- CSS applied to the log chrome page. -- @type string -- @readwrite _M.html_style = [===[ table { margin: 0; border-collapse:collapse; table-layout: fixed; } th { text-align: left; font-size: 1.3em; font-weight: 100; margin: 1em 0 0.5em 0.5em; -webkit-user-select: none; cursor: default; } th, td { white-space: nowrap; } th { padding: 0.5rem 0.5rem 0.2em; } td { padding: 0.2rem 0.5rem; } th:first-child, td:first-child { padding-left: 1rem; } td:last-child, th:last-child { padding-right: 1rem; } th:nth-child(1), td:nth-child(1) { min-width: 120px; } th:nth-child(2), td:nth-child(2) { min-width: 80px; } th:nth-child(3), td:nth-child(3) { min-width: 250px; } th:nth-child(4), td:nth-child(4) { width: 100%; } tr:hover > td { background: #f8f8f8; } tr > td { background: linear-gradient(180deg, #fafafa 0%, #fff 100%); } tr.level-verbose { color: #666; } tr.level-warn > td { background: #FFB964; } tr.level-warn:hover > td { background: #F9B561; } tr.level-error > td { background: #D87050; } tr.level-error:hover > td { background: #CF6A4C; } td.level { } td { font-family: monospace; } td { vertical-align: top; } td.msg > pre { margin: 0; } .content-margin { padding: 3.5em 0 0 0; } ]===] local log_entry_fmt = ([==[ {time} {level} {group}
        {msg}
        ]==]):gsub("\n +", ""):gsub("^ +", ""):gsub(" +$", "") local build_log_entry_html = function (entry) assert(entry) return log_entry_fmt:gsub("{(%w+)}", { time = string.format("%012f", entry.time), llevel = entry.level, level = entry.level:gsub("^%l", string.upper), group = entry.group, groupkey = entry.group:gsub("/","-"), msg = entry.msg, }) end local sync_view = function (v) local unsynced_count = log_views[v].unsynced_count local rows = {} for i=#log_entries-unsynced_count+1,#log_entries do rows[#rows+1] = build_log_entry_html(assert(log_entries[i])) end log_views[v].unsynced_count = 0 local js = [=[ var html = %s, num_rows = %d; var tbody = document.querySelector("tbody"); tbody.insertAdjacentHTML('beforeend', html); for (var i = tbody.childElementCount; i > num_rows; i-- ) { tbody.firstElementChild.remove(); } ]=] for i, row in ipairs(rows) do -- only the msg contains literal newlines: escape them -- has to be done outside of %q formatting row = row:gsub("\n", "
        ") rows[i] = string.format("%q", row):gsub("\\\n", "\n") end js = js:format(table.concat(rows,"+"), _M.buffer_size) v:eval_js(js, { no_return = true, callback = function (_, err) assert(not err, err) end}) end local log_view_destroy_cb = function (view) log_views[view] = nil end append_timer:add_signal("timeout", function () if append_timer.started then append_timer:stop() end local views = {} for v, _ in pairs(log_views) do if string.match(v.uri or "", "^luakit://log/?") then if not v.is_loading then views[#views+1] = v end else log_views[v] = nil v:remove_signal("destroy", log_view_destroy_cb) end end for _, v in ipairs(views) do sync_view(v) end end) chrome.add("log", function (view) local rows = {} for i, entry in ipairs(log_entries) do rows[i] = build_log_entry_html(entry) end local html_subs = { title = _M.html_page_title, style = chrome.stylesheet .. _M.html_style, rows = table.concat(rows, "\n"), } local html = string.gsub(_M.html_template, "{(%w+)}", html_subs) log_views[view] = { unsynced_count = 0 } view:add_signal("destroy", log_view_destroy_cb) return html end) --- Format string which defines the appearance of the error/warning widget. -- This is passed to `string.format` with the number of errors as a -- numerical argument, the result of which is substituted into @ref{widget_format}. -- @type string -- @readwrite _M.widget_error_format = "E: %d" --- Format string which defines the appearance of the error/warning widget. -- This is passed to `string.format` with the number of warnings as a -- numerical argument, the result of which is substituted into @ref{widget_format}. -- @type string -- @readwrite _M.widget_warning_format = "W: %d" --- Format string which defines the appearance of the error/warning widget. -- This combines the error and warning sub-format strings. `{errors}` will be -- replaced with the result of formatting @ref{widget_error_format}, or the -- empty string if there have been no errors. Likewise, `{warnings}` will be -- replaced with the result of formatting @ref{widget_warning_format}, or the -- empty string if there have been no warnings -- @type string -- @readwrite _M.widget_format = " {errors} {warnings} " local widgets = {} local error_count, warning_count = 0, 0 local function update_widgets() local text = string.gsub(_M.widget_format, "{(%w+)}", { errors = error_count > 0 and string.format(_M.widget_error_format, error_count) or "", warnings = warning_count > 0 and string.format(_M.widget_warning_format, warning_count) or "", }) local tooltip = string.format("Errors: %d, Warnings: %d", error_count, warning_count) for _, notif in ipairs(widgets) do notif.text = text notif.tooltip = tooltip notif:show() end end local function widget_click_cb(notif) error_count, warning_count = 0, 0 for _, n in ipairs(widgets) do n:hide() end local w = window.ancestor(notif) if w then w:new_tab("luakit://log/", { switch = true }) end end msg.add_signal("log", function (time, level, group, msg) table.insert(log_entries, { time = time, level = level, group = group, msg = msg:gsub("^%l", string.upper):gsub(string.char(27) .. '%[%d+m', '') }) if level == "warn" then warning_count = warning_count + 1 update_widgets() elseif level == "error" then error_count = error_count + 1 update_widgets() end for _, t in pairs(log_views) do t.unsynced_count = math.min(t.unsynced_count + 1, _M.buffer_size) end if next(log_views) and not append_timer.started then append_timer:start() end while #log_entries > _M.buffer_size do table.remove(log_entries, 1) end end) --- Construct a new error/warning status bar widget. -- This widget will stay hidden, until a luakit error or warning is logged. -- Once shown, clicking on the widget will hide it and all other such widgets. -- @treturn widget The newly-constructed status bar widget. _M.widget = function () local notif, ebox = widget{type="label"}, widget{type="eventbox"} notif:hide() notif.fg = theme.sbar_notif_fg notif.font = theme.sbar_notif_font table.insert(widgets, notif) notif:add_signal("destroy", function () table.remove(widgets, lousy.util.table.hasitem(widgets, notif)) end) update_widgets() ebox.child = notif ebox:add_signal("button-release", widget_click_cb) return ebox end modes.add_cmds({ { ":log", "Open in a new tab.", function (w) w:new_tab("luakit://log/") end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/000077500000000000000000000000001475363222200147355ustar00rootroot00000000000000luakit-2.4.0/lib/lousy/bind.lua000066400000000000000000000427741475363222200163720ustar00rootroot00000000000000--- lousy.bind library. -- -- Key, buffer and command binding functions. -- -- @module lousy.bind -- @author Mason Larobina -- @copyright 2010 Mason Larobina local util = require("lousy.util") local join = util.table.join local keys = util.table.keys local _M = {} local function convert_bind_syntax(b) -- commands are a no-op if b:match("^:") and b ~= ":" then return b end -- Keys have sorted modifiers and uppercase -> lowercase+shift conversion if utf8.len(b) == 1 or b:match("^<.+>$") then b = b:match("^<(.+)>$") or b local mods = b == "-" and {"Minus"} or util.string.split(b, "%-") local key = table.remove(mods) -- Convert upper-case keys to shift+lower-case local lc = luakit.wch_lower(key) if lc ~= key then key = lc table.insert(mods, "Shift") end mods = _M.parse_mods(mods) return "<".. (mods and (mods.."-") or "") .. key .. ">" end -- Otherwise, make it a buffer bind; wrap in ^$ if necessary b = string.sub(b,1,1) == "^" and b or "^" .. b .. "$" if utf8.len(b) == 3 then local nb = convert_bind_syntax(b:sub(2,-2)) msg.verbose("implicitly converting bind '%s' to '%s'", b, nb) b = nb end return b end local function convert_binds_table(binds) if binds.converted then return binds end local converted = { converted = true } for i, bind in ipairs(binds) do converted[i] = { convert_bind_syntax(bind[1]), bind[2], bind[3] } end return converted end --- Set of modifiers to ignore. -- @readwrite _M.ignore_mask = { Mod2 = true, Mod3 = true, Mod5 = true, Lock = true, } --- A table that contains mappings for key names. -- @readwrite _M.map = { ISO_Left_Tab = "Tab", PgUp = "Page_Up", PgDn = "Page_Down", ["-"] = "Minus", } --- A table that contains mappings for modifier names. -- @readwrite _M.mod_map = { C = "Control", S = "Shift", A = "Mod1", Ctrl = "Control", Alt = "Mod1", } --- Parse a table of modifier keys into a string. -- @tparam table mods The table of modifier keys. -- @tparam[opt] boolean remove_shift Remove the shift key from the modifier -- table. -- @default `false` -- @treturn string A string of key names, separated by hyphens (-). function _M.parse_mods(mods, remove_shift) local t = {} local recognized_mods = { "shift", "lock", "control", "mod1", "mod2", "mod3", "mod4", "mod5" } for _, mod in ipairs(mods) do if not _M.ignore_mask[mod] then mod = string.lower(_M.mod_map[mod] or _M.map[mod] or mod) assert(util.table.hasitem(recognized_mods, mod), "unrecognized modifier '"..mod.."'") t[mod] = true end end -- For single character bindings shift is not processed as it should -- have already transformed the keycode within gdk. if remove_shift then t.shift = nil end mods = keys(t) table.sort(mods) mods = table.concat(mods, "-") return mods ~= "" and mods or nil end --- Match any 'any' bindings in a given table of bindings. -- -- The bindings' callback functions are called in the order that they -- occur in the given table of bindings. If any callback function -- returns a value other than `false`, then matching stops and this -- function immediately returns `true`. Otherwise, if the callback -- returns `false`, matching continues. -- -- @param object An object passed through to any 'any' bindings called. -- @tparam table binds A table of bindings to search. -- @tparam table args A table of arguments passed through to any 'any' bindings -- called. -- @treturn boolean `true` if an 'any' binding was ran successfully. function _M.match_any(object, binds, args) binds = convert_binds_table(binds) for _, m in ipairs(binds) do local b, a, o = unpack(m) if b == "" then if a.func(object, join(o, args), o) ~= false then return true end end end return false end --- Match any key binding in a given table of bindings. -- -- The bindings' callback functions are called in the order that they -- occur in the given table of bindings. If any callback function -- returns a value other than `false`, then matching stops and this -- function immediately returns `true`. Otherwise, if the callback -- returns `false`, matching continues. -- -- @param object An object passed through to any key bindings called. -- @tparam table binds A table of bindings to search. -- @tparam string mods The string of modifier keys. -- @tparam string key The key name. -- @tparam table args A table of arguments passed through to any key bindings -- called. -- @treturn boolean `true` if a key binding was ran successfully. function _M.match_key(object, binds, mods, key, args) binds = convert_binds_table(binds) for _, m in ipairs(binds) do local b, a, o = unpack(m) if b == "<".. (mods and (mods.."-") or "") .. key .. ">" then if a.func(object, join(o, args), o) ~= false then return true end end end return false end --- Match any button binding in a given table of bindings. -- -- The bindings' callback functions are called in the order that they -- occur in the given table of bindings. If any callback function -- returns a value other than `false`, then matching stops and this -- function immediately returns `true`. Otherwise, if the callback -- returns `false`, matching continues. -- -- @param object An object passed through to any key bindings called. -- @tparam table binds A table of bindings to search. -- @tparam string mods The table of modifier keys. -- @tparam string button The button name. -- @tparam table args A table of arguments passed through to any button bindings -- called. -- @treturn boolean `true` if a key binding was ran successfully. function _M.match_but(object, binds, mods, button, args) binds = convert_binds_table(binds) for _, m in ipairs(binds) do local b, a, o = unpack(m) if b == "<" .. (mods and (mods.."-") or "") .. "Mouse" .. button .. ">" then if a.func(object, join(o, args), o) ~= false then return true end end end return false end --- Determine if a string is a partial match for a Lua pattern -- Only a restricted subset of patterns are allowed; it's assumed the -- pattern should match the entire string (^$ is implied) and *+? are not -- permitted. -- @tparam string str The possible partial match -- @tparam string pat The pattern to match against local function is_partial_match(str, pat) -- Strip off any numerical prefix to the buffer; allows count syntax to work str = str:match("^%d*(%D.*)$") or "" if str == "" then return true end pat = pat:match("^%^?(.+)%$?$") local first_char_pat = pat:match("^(%[[^%]]+%])") or pat:match("^(%%.)") or pat:sub(1,1) local remainder = pat:sub(first_char_pat:len()+1) assert(not remainder:match("^[%+%*%?]"), "+*? not supported!") if not str:sub(1,1):find("^" .. first_char_pat) then return false else return is_partial_match(str:sub(2), remainder) end end --- Try and match a buffer binding in a given table of bindings and call that -- bindings callback function. -- @param object The first argument of the bind callback function. -- @tparam table binds The table of binds in which to check for a match. -- @tparam string buffer The buffer string to match. -- @tparam table args The bind options/state/metadata table which is applied over the -- opts table given when the bind was created. -- @treturn boolean `true` if a binding was matched and called. -- @treturn boolean `true` if a partial match exists. function _M.match_buf(object, binds, buffer, args) assert(buffer and string.match(buffer, "%S"), "invalid buffer") binds = convert_binds_table(binds) local has_partial_match = false for _, m in ipairs(binds) do local b, a, o = unpack(m) if b:match("^^") then if buffer:match(b) then local params = {join(o, args, { buffer = buffer }), o} if a.compat == "buffer" then table.insert(params, 1, buffer) end if a.func(object, unpack(params)) ~= false then return true, true end end if is_partial_match(buffer, b) then has_partial_match = true end end end return false, has_partial_match end --- Try and match a command or buffer binding in a given table of bindings -- and call that bindings callback function. -- @param object The first argument of the bind callback function. -- @tparam table binds The table of binds in which to check for a match. -- @tparam string buffer The buffer string to match. -- @tparam table args The bind options/state/metadata table which is applied over the -- opts table given when the bind was created. -- @treturn boolean `true` if either type of binding was matched and called. function _M.match_cmd(object, binds, buffer, args) assert(buffer and string.match(buffer, "%S"), "invalid buffer") binds = convert_binds_table(binds) -- The command is the first word in the buffer string local command = string.match(buffer, "^(%S+)") -- And the argument is the entire string thereafter local argument = string.match(string.sub(buffer, #command + 1), "^%s+([^%s].*)$") -- Set args.cmd to tell buf/any binds they were called from match_cmd args = join(args or {}, { binds = binds, cmd = buffer, arg = argument, }) for _, m in ipairs(binds) do local b, a, o = unpack(m) -- split command binding string into long and short forms local cmds = {} for _, cmd in ipairs(util.string.split(b:gsub("^:", ""), ",%s+:")) do if string.match(cmd, "^([%-%w]+)%[(%w+)%]") then local l, r = string.match(cmd, "^([%-%w]+)%[(%w+)%]") table.insert(cmds, l..r) table.insert(cmds, l) else table.insert(cmds, cmd) end end -- Command matching if b:match("^:") and util.table.hasitem(cmds, command) then local params = {join(o, args, { argument = argument }), o} if a.compat then table.insert(params, 1, argument) end if a.func(object, unpack(params)) ~= false then return true end -- Buffer matching elseif b:match("^%^") and string.match(buffer, b) then local params = {join(o, args, { buffer = buffer }), o} if a.compat then table.insert(params, 1, buffer) end if a.func(object, unpack(params)) ~= false then return true end -- Any matching elseif b == "" then if a.func(object, join(o, args), o) ~= false then return true end end end return false end --- Attempt to match either a key or buffer binding and execute it. This -- function is also responsible for performing operations on the buffer when -- necessary and the buffer is enabled. -- -- When matching key bindings, this function ignores the case of `key`, and uses -- the presence of the Shift modifier to determine which bindings should be matched. -- -- @param object The first argument of the bind callback function. -- @tparam table binds The table of binds in which to check for a match. -- @tparam {string} mods The modifiers to match. -- @tparam string key The key name to match. -- @tparam table args The bind options/state/metadata table which is applied over the -- opts table given when the bind was created. -- @treturn boolean `true` if a key or buffer binding was matched or if a key was added to -- the buffer. -- @treturn string The new buffer truncated to 10 characters (if you need more buffer -- then use the input bar for whatever you are doing). If no buffer binding -- could be matched, the returned buffer will be the empty string. function _M.hit(object, binds, mods, key, args) -- Convert keys using map key = _M.map[key] or key binds = convert_binds_table(binds) if not key then return false end local len = utf8.len(key) -- Compile metadata table args = join(args or {}, { object = object, binds = binds, mods = mods, key = key, }) local omods = mods mods = _M.parse_mods(mods, type(key) == "string" and len == 1) if _M.match_any(object, binds, args) then return true -- Match button bindings elseif type(key) == "number" then return _M.match_but(object, binds, mods, key, args) -- Match key bindings elseif (not args.buffer or not args.enable_buffer) or mods or len ~= 1 then -- Remove tab for single-char keys with no case (like ?, :, etc) local lk = luakit.wch_lower(key) local remove_shift = lk == luakit.wch_upper(key) and len == 1 local m = _M.parse_mods(omods, remove_shift) if _M.match_key(object, binds, m, lk, args) then return true end end -- -- Invert key case on capslock if util.table.hasitem(omods, "Lock") then local uc, lc = luakit.wch_upper(key), luakit.wch_lower(key) key = key == uc and lc or uc table.remove(omods, util.table.hasitem(omods, "Lock")) end mods = _M.parse_mods(omods, type(key) == "string" and len == 1) -- Clear buffer if not args.enable_buffer or mods then return false -- Else match buffer elseif len == 1 then if not args.updated_buf then args.buffer = (args.buffer or "") .. key args.updated_buf = true end local matched, partial = _M.match_buf(object, binds, args.buffer, args) if matched then return true end -- If no partial match, clear the buffer if not partial then args.buffer = nil end end -- Return buffer if valid if args.buffer then return false, string.sub(args.buffer, 1, 10) end return false end --- Produce a string describing the action that triggers a given binding. -- For example, a binding for the down-arrow key would produce `""`. -- -- @tparam table b The binding. -- @treturn string The binding description string. function _M.bind_to_string(b) if b:match("^^") then if string.sub(b,1,1) .. string.sub(b, -1, -1) == "^$" then b = string.sub(b, 2, -2) end return b:gsub("%%([%^%$%(%)%%%.%[%]%*%+%-%?%)])", "%1") elseif b:match("^<.>$") then return b:sub(2,2) else return b end end --- Bind a trigger to an action, adding the resulting binding to an array of -- bindings. -- @tparam table binds The array of bindings to add the new binding to. -- @tparam string bind The trigger that will activate the action associated with -- this bind. -- @tparam table action The action that will be activated. -- @tparam[opt] table opts A table of bind-time options that will be passed to the -- action when it is activated. function _M.add_bind (binds, bind, action, opts) assert(binds and type(binds) == "table", "invalid binds table type: " .. type(binds)) assert(bind and type(bind) == "string", "invalid bind type: " .. type(bind)) assert(action and type(action) == "table", "invalid action type: " .. type(action)) bind = convert_bind_syntax(bind) _M.remove_bind(binds, bind) table.insert(binds, { bind, action, opts or {} }) msg.verbose("added bind %s", bind) end --- Remove any binding with a specific trigger from the given array of bindings. -- @tparam table binds The array of bindings to remove the named binding from. -- @tparam string bind The trigger to unbind. -- @treturn table The associated action of the binding that was removed, or `nil` if -- not found. -- @treturn table The options of the binding that was removed, or `nil` if not found. function _M.remove_bind (binds, bind) assert(binds and type(binds) == "table", "invalid binds table type: " .. type(binds)) assert(bind and type(bind) == "string", "invalid bind type: " .. type(bind)) bind = convert_bind_syntax(bind) for i, m in ipairs(binds) do if m[1] == bind then table.remove(binds, i) msg.verbose("removed bind %s", bind) return m[2], m[3] end end msg.verbose("no bind %s to remove", bind) return nil, nil end --- Remap a binding from a given trigger to a new trigger, optionally keeping -- the original binding. In both cases, the new binding will have the same -- options as the original binding. -- @tparam table binds The array of bindings to remap within. -- @tparam string new The new trigger to map from. -- @tparam string old The existing trigger to remap. -- @tparam[opt] boolean keep Retain the existing binding. -- @default `false` function _M.remap_bind (binds, new, old, keep) assert(binds and type(binds) == "table", "invalid binds table type: " .. type(binds)) assert(new and type(new) == "string", "invalid bind type: " .. type(new)) assert(old and type(old) == "string", "invalid bind type: " .. type(old)) new = convert_bind_syntax(new) old = convert_bind_syntax(old) for _, m in ipairs(binds) do if m[1] == old then msg.verbose("remapping bind to %s, %s %s", new, keep and "keeping" or "removing", old) if keep then _M.add_bind(binds, new, m[2], m[3]) else m[1] = new end return end end msg.verbose("no bind %s to remap", old) end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/init.lua000066400000000000000000000010211475363222200163750ustar00rootroot00000000000000--- lousy library. -- -- @module lousy -- @author Mason Larobina -- @copyright 2010 Mason Larobina return { util = require("lousy.util"), bind = require("lousy.bind"), mode = require("lousy.mode"), theme = require("lousy.theme"), signal = require("lousy.signal"), widget = require("lousy.widget"), uri = require("lousy.uri"), load = require("lousy.load"), pickle = require("lousy.pickle"), } -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/load.lua000066400000000000000000000034541475363222200163650ustar00rootroot00000000000000--- lousy.load library. -- -- This module provides a function to search for and load the contents of -- files. -- -- @module lousy.load -- @author Mason Larobina -- @copyright 2010 Mason Larobina local _M = {} -- Keep loaded resources in memory local data = {} local function load_resource(path, memorize) -- Have we already loaded this resource? if memorize and data[path] then return data[path] end -- Attempt to open & read resource local file = io.open(path) if file then -- Read resource local dat = file:read("*a") file:close() -- Memorize if asked if memorize then data[path] = dat end -- Return file contents return dat end end --- @function __call -- Load the contents of a file, with optional caching. -- @tparam string path The path of the file to load. If the path is a relative -- path, it is relative to the luakit installation path. -- @tparam boolean memorize Whether file loads should be cached. If not `true`, -- the cache will not be queried for an already-loaded copy, nor will the cache -- be populated on a successful load. local function search_load(path, memorize) assert(type(path) == "string", "invalid path") memorize = not not memorize if string.sub(path, 1, 1) ~= "/" then -- Can we search relative paths? if luakit.dev_paths then local dat = load_resource("./"..path, memorize) if dat then return dat end end path = luakit.install_paths.install_dir.."/"..path end return assert(load_resource(path, memorize), "unable to load resource: " .. path) end return setmetatable(_M, { __call = function (_, ...) return search_load(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/mode.lua000066400000000000000000000037531475363222200163740ustar00rootroot00000000000000--- lousy.mode library. -- -- Mode setting and getting operations for objects. -- -- @module lousy.mode -- @author Mason Larobina -- @copyright 2010 Mason Larobina local _M = {} --- The default mode if no default modes are set. local default_mode = "normal" --- Weak table of objects current modes. local current_modes = {} setmetatable(current_modes, { __mode = "k" }) --- Weak table of objects default modes. local default_modes = {} setmetatable(default_modes, { __mode = "k" }) --- Check if the mode can be set on an object. -- An object is considered mode-able if it has an "emit_signal" method. -- @param object The object to check. function _M.is_modeable(object) local t = type(object) return ((t == "table" or t == "userdata" or t == "lightuserdata") and type(object.emit_signal) == "function") end --- Get the current mode for a given object. -- @param object A mode-able object. -- @treturn string The current mode of the given object, or the default mode of that object, -- or "normal". function _M.get(object) if not _M.is_modeable(object) then return error("attempt to get mode on non-modeable object") end return current_modes[object] or default_modes[object] or default_mode end --- Set the mode for a given object. -- @param object A mode-able object. -- @tparam string mode A mode name (e.g. "insert", "command", ...). -- @treturn string The newly set mode. function _M.set(object, mode, ...) if not _M.is_modeable(object) then return error("attempt to set mode on non-modeable object") end mode = mode or default_modes[object] or default_mode local changed = current_modes[object] ~= mode current_modes[object] = mode -- Raises a mode change signal on the object. if changed then object:emit_signal("mode-changed", mode, ...) end return mode end return setmetatable(_M, { __call = function(_, ...) return _M.set(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/pickle.lua000066400000000000000000000054171475363222200167160ustar00rootroot00000000000000--- lousy.pickle library. -- -- A table serialization utility for lua. Freeware. -- -- *Note: The serialization format may change without notice. This -- should be treated as an opaque interface.* -- -- @module lousy.pickle -- @author Steve Dekorte, http://www.dekorte.com -- @copyright 2000 Steve Dekorte local Pickle = { clone = function (t) local nt={}; for i, v in pairs(t) do nt[i]=v end return nt end } function Pickle:pickle_(root) if type(root) ~= "table" then error("can only pickle tables, not ".. type(root).."s") end self._tableToRef = {} self._refToTable = {} local savecount = 0 self:ref_(root) local buf = {} while table.getn(self._refToTable) > savecount do savecount = savecount + 1 local t = self._refToTable[savecount] buf[#buf+1] = "{" for i, v in pairs(t) do buf[#buf+1] = string.format("[%s]=%s,", self:value_(i), self:value_(v)) end buf[#buf+1] = "}," end return string.format("{%s\n}", table.concat(buf,"\n")) end function Pickle:value_(v) local vtype = type(v) if vtype == "string" then return string.format("%q", v) elseif vtype == "number" then return v elseif vtype == "boolean" then return tostring(v) elseif vtype == "table" then return "{"..self:ref_(v).."}" else error("pickle a "..type(v).." is not supported") end end function Pickle:ref_(t) local ref = self._tableToRef[t] if not ref then if t == self then error("can't pickle the pickle class") end table.insert(self._refToTable, t) ref = table.getn(self._refToTable) self._tableToRef[t] = ref end return ref end local _M = {} --- Convert a table into a string that can be saved to disk. -- @tparam table t The table to serialize. -- @treturn string The string representing the table contents. _M.pickle = function(t) return Pickle:clone():pickle_(t) end --- Convert a string previously created with `pickle()` to a table. -- @tparam string s The string previously created with `pickle()`. -- @treturn table A table corresponding to the given string. _M.unpickle = function(s) if type(s) ~= "string" then error("can't unpickle a "..type(s)..", only strings") end local gentables = loadstring("return "..s) local tables = gentables() for tnum = 1, table.getn(tables) do local t = tables[tnum] local tcopy = {}; for i, v in pairs(t) do tcopy[i] = v end for i, v in pairs(tcopy) do local ni, nv if type(i) == "table" then ni = tables[i[1]] else ni = i end if type(v) == "table" then nv = tables[v[1]] else nv = v end t[i] = nil t[ni] = nv end end return tables[1] end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/signal.lua000066400000000000000000000122361475363222200167210ustar00rootroot00000000000000--- lousy.signal library. -- -- Mimic the luakit signal API functions for tables. -- -- @module lousy.signal -- @author Fabian Streitel -- @author Mason Larobina -- @copyright 2010 Fabian Streitel -- @copyright 2010 Mason Larobina local _M = {} local clone_table = (require "lousy.util").table.clone -- Private signal data for objects local data = setmetatable({}, { __mode = "k" }) local methods = { "add_signal", "emit_signal", "remove_signal", "remove_signals", } local function get_data(object) local d = data[object] assert(d, "object isn't setup for signals") return d end --- Add a signal handler to an object, for a particular signal. -- -- When a signal with the given name is emitted on the given object with -- `emit_signal()`, the given callback function will be called, along with all -- other signal handlers for the signal on the object. -- -- The first argument passed to the callback function will be the object this -- signal is emitted on (`object`), *unless* this object was setup for signals -- with `module=true`, i.e. as a module object. In that case, the object will -- not be passed to the callback at all. Subsequently, the callback -- function will receive all additional arguments passed to `emit_signal()`. -- -- `object` must have been set up for signals with `setup()`. -- -- @param object The object on which to listen for signals. -- @tparam string signame The name of the signal to listen for. -- @tparam function func The signal handler callback function. function _M.add_signal(object, signame, func) local signals = get_data(object).signals -- Check signal name assert(type(signame) == "string", "invalid signame type: " .. type(signame)) assert(string.match(signame, "^[%w_%-:]+$"), "invalid chars in signame: " .. signame) -- Check handler function assert(type(func) == "function", "invalid handler function") -- Add to signals table if not signals[signame] then signals[signame] = { func, } else table.insert(signals[signame], func) end end --- Emit a signal on an object. -- -- `object` must have been set up for signals with `setup()`. -- -- @param object The object on which to emit the signal. -- @tparam string signame The name of the signal to emit. -- @param ... Additional arguments are passed any signal handlers called. function _M.emit_signal(object, signame, ...) local d = get_data(object) -- Shallow clone the signal table, since it can change while executing -- signal handlers. local sigfuncs = clone_table(d.signals[signame] or {}) msg.debug("emit_signal: %q on %s", signame, tostring(object)) for _, sigfunc in ipairs(sigfuncs) do local ret if d.module then ret = { sigfunc(...) } else ret = { sigfunc(object, ...) } end if ret[1] ~= nil then return unpack(ret) end end end --- Remove a signal handler function from an object. -- -- `object` must have been set up for signals with `setup()`. -- -- @param object The object on which to remove a signal handler. -- @tparam string signame The name of the signal handler to remove. -- @tparam function func The signal handler callback function to remove. -- @treturn[1] function Returns the removed callback function, if the signal -- handler was found. -- @treturn[2] nil If the signal handler was not found. function _M.remove_signal(object, signame, func) local signals = get_data(object).signals local sigfuncs = signals[signame] or {} for i, sigfunc in ipairs(sigfuncs) do if sigfunc == func then table.remove(sigfuncs, i) -- Remove empty sigfuncs table if #sigfuncs == 0 then signals[signame] = nil end return func end end end --- Remove all signal handlers with a given name from an object. -- @param object The object on which to remove a signal handler. -- @tparam string signame The name of the signal handler to remove. function _M.remove_signals(object, signame) local signals = get_data(object).signals signals[signame] = nil end --- Setup an object for signals. -- -- Sets up the given object for signals, and returns the object. -- -- If `module` is `true`, then the object is not passed to signal callback -- functions as the first parameter when a signal is emitted. -- -- `object` must *not* have been set up for signals with `setup()`. -- -- @param object The object to set up for signals. -- @tparam boolean module Whether this object should be treated as a module. -- @return The given object. function _M.setup(object, module) assert(not data[object], "given object already setup for signals") data[object] = { signals = {}, module = module } for _, fn in ipairs(methods) do assert(not object[fn], "signal object method conflict: " .. fn) if module then local func = _M[fn] object[fn] = function (...) return func(object, ...) end else object[fn] = _M[fn] end end return object end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/theme.lua000066400000000000000000000036601475363222200165470ustar00rootroot00000000000000--- lousy.theme library. -- -- This module provides theme variable lookup for other modules. -- -- @module lousy.theme -- @author Mason Larobina -- @author Damien Leone -- @author Julien Danjou -- @copyright 2008-2009 Damien Leone -- @copyright 2008-2009 Julien Danjou -- @copyright 2010 Mason Larobina local util = require "lousy.util" local theme local _M = {} -- Searches recursively for theme value. -- (I.e. `w.bg = theme.some_thing_bg` -> -- `w.bg = (theme.some_thing_bg or theme.thing_bg or theme.bg)`) local function index(t, k) local v = rawget(t, k) if v then return v end -- Knock a "level_" from the key name if string.find(k, "_") then local ret = index(t, string.sub(k, string.find(k, "_") + 1, -1)) -- Cache result if ret then t[k] = ret end return ret end end -- Minimum default theme local default_theme = { fg = "#fff", bg = "#000", font = "9px monospace", } --- Load the theme table from file. -- @tparam string path The filepath of the theme. function _M.init(path) if not path then return error("error loading theme: no path specified") end -- Load theme table local success success, theme = pcall(function() return dofile(path) end) if not success then return error("error loading theme file " .. theme) elseif not theme then return error("error loading theme file " .. path) elseif type(theme) ~= "table" then return error("error loading theme: not a table") end -- Merge with defaults and set metatable theme = setmetatable(util.table.join(default_theme, theme), { __index = index }) return theme end --- Get the current theme. -- @treturn table The current theme table. function _M.get() return theme end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/uri.lua000066400000000000000000000143721475363222200162460ustar00rootroot00000000000000--- lousy.uri library. -- -- URI parsing functions -- -- @module lousy.uri -- @copyright 2011 Mason Larobina local lfs = require "lfs" -- Get luakit environment local util = require "lousy.util" local uri_encode = luakit.uri_encode local uri_decode = luakit.uri_decode local _M = {} local opts_metatable = { __tostring = function (opts) local ret, done = {}, {} -- Get opt order from metatable local mt = getmetatable(opts) -- Add original args first in order if mt and mt.order then for _, k in ipairs(mt.order) do local v = opts[k] if v and v ~= "" then table.insert(ret, uri_encode(k) .. "=" .. uri_encode(v)) done[k] = true end end end -- Add new args for k, v in pairs(opts) do if not done[k] and v ~= "" then table.insert(ret, uri_encode(k) .. "=" .. uri_encode(v)) end end -- Join query opts return table.concat(ret, "&") end, __add = function (op1, op2) assert(type(op1) == "table" and type(op2) == "table", "non-table operands") local ret = util.table.copy(op1) for k, v in pairs(op2) do ret[k] = v end return ret end, __sub = function (op1, op2) assert(type(op1) == "table" and type(op2) == "table", "non-table operands") local ret = util.table.copy(op1) for _, k in ipairs(op2) do ret[k] = nil end return ret end, } --- Guess if a string is an URI. -- @tparam string s The input string. -- @treturn boolean true if the string could be a URI, false otherwise. function _M.is_uri(s) local get_setting = require("settings").get_setting if get_setting("window.check_filepath") and lfs.attributes(s:gsub("^file://", "")) then return true end -- Valid hostnames to check local ok, hosts if get_setting("window.load_etc_hosts") then ok, hosts = pcall(util.get_etc_hosts) if not ok then hosts = {} end else hosts = {"localhost"} end -- Check hostnames for _, h in pairs(hosts) do if h == s or s:match("^" .. h .. ":%d+$") then return true end end if s == "about:blank" or s:find("^javascript:") then return true end -- Only file URI may have leading punctuation if s:find("^%p") then return false end if s:find("[%./]") == nil then return false end -- String containing a trailing period and no slash isn't an URI local _, count = s:gsub("/", "") return s:find("%.$") == nil or count > 0 end --- Split a string into URIs or group of words. -- @tparam string s The string to split. -- @treturn table A table of strings whose member is either a group of words to -- be searched or a URI function _M.split(s) -- Avoid spliting JS and file URIs if s:find("^javascript:") then return {s} end local get_setting = require("settings").get_setting if get_setting("window.check_filepath") and lfs.attributes(s:gsub("^file://", "")) then return {s} end local tmp, ret = nil, {} for p in s:gmatch("%S+") do local uri = p:gsub("^[{%[%(<\"']+", ""):gsub("[:,;%.!%?'\">%)%]}]+$", "") if _M.is_uri(uri) then if tmp then table.insert(ret, tmp) tmp = nil end table.insert(ret, uri) elseif tmp then tmp = tmp .. " " .. p else tmp = p end end if tmp then table.insert(ret, tmp) end return ret end --- Parse the query component of a URI and return it as a table. -- @tparam string query The query component of a URI. -- @treturn table The parsed table of query options. function _M.parse_query(query) local opts, order = {}, {} string.gsub(query or "", "&*([^&=]+)=([^&]+)", function (k, v) opts[k] = uri_decode(v) table.insert(order, k) end) -- Put order table in opts metatable local mt = util.table.clone(opts_metatable) mt.order = order return setmetatable(opts, mt) end -- Allowed URI table properties local uri_allowed = { scheme = true, user = true, password = true, port = true, host = true, path = true, query = true, fragment = true, opts = true } -- URI table metatable local uri_metatable = { __tostring = function (uri) local t = util.table.clone(uri) t.query = tostring(t.opts) return soup.uri_tostring(t) end, __add = function (op1, op2) assert(type(op1) == "table" and type(op2) == "table", "non-table operands") local ret = util.table.copy(op1) for k, v in pairs(op2) do assert(uri_allowed[k], "invalid property: " .. k) if k == "query" and type(v) == "string" then ret.opts = _M.parse_query(v) else ret[k] = v end end return ret end, } --- Parse a URI string and return a URI table. -- @tparam string uri The URI as a string. -- @treturn table The URI as a table. function _M.parse(uri) -- Get uri table uri = soup.parse_uri(uri) if not uri then return end -- Parse uri.query and set uri.opts uri.opts = _M.parse_query(uri.query) uri.query = nil return setmetatable(uri, uri_metatable) end --- Duplicate a URI table. -- @tparam table uri The URI as a table. -- @treturn table A new copy of the URI table. function _M.copy(uri) assert(type(uri) == "table", "not a table") return _M.parse(tostring(uri)) end --- Find the domains that a given URI belongs to. -- @tparam string uri The URI. -- @treturn {string} An array of domains. function _M.domains_from_uri(uri) local domain = soup.parse_uri(uri).host -- Strip leading www. domain = string.match(domain or "", "^www%.(.+)") or domain if not domain then return {} end -- I.e. for example.com load { .example.com, example.com, .com } local domains = { domain } repeat table.insert(domains, "."..domain) domain = string.match(domain, "%.(.+)") until not domain -- Sort by specificity table.sort(domains, function (a, b) return #a > #b end) return domains end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/util.lua000066400000000000000000000331211475363222200164150ustar00rootroot00000000000000--- lousy.util library. -- --- Utility functions for lousy. -- -- @module lousy.util -- @author Mason Larobina -- @author Julien Danjou -- @copyright 2010 Mason Larobina -- @copyright 2008 Julien Danjou --- Grab environment we need local rstring = string local rtable = table local math = require "math" local _M = {} local table = {} local string = {} --- @local _M.table = table --- @local _M.string = string local xml_entity_names = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; local xml_entity_chars = { lt = "<", gt = ">", nbsp = " ", quot = "\"", apos = "'", ndash = "-", mdash = "-", amp = "&" }; --- Escape a string from XML characters. -- @tparam string text The text to escape. -- @treturn string A string with all XML characters escaped. function _M.escape(text) return text and text:gsub("['&<>\"]", xml_entity_names) or nil end --- Unescape a string from XML entities. -- @tparam strng text The text to un-escape. -- @treturn string A string with all the XML entities un-escaped. function _M.unescape(text) return text and text:gsub("&(%a+);", xml_entity_chars) or nil end --- Create a directory. -- @tparam string dir The directory. -- @treturn number The status code returned by `mkdir`; 0 indicates success. function _M.mkdir(dir) return os.execute(rstring.format("mkdir -p %q", dir)) end --- Evaluate Lua code. -- @tparam string s The string of Lua code to evaluate. -- @return The return value of Lua code. function _M.eval(s) return assert(loadstring(s))() end --- Check if a file is a Lua valid file. -- This is done by loading the content and compiling it with `loadfile()`. -- @tparam string path The file path. -- @treturn function|nil A function if the file was loaded successfully, -- and a string with the error otherwise. function _M.checkfile(path) local f, e = loadfile(path) -- Return function if function, otherwise return error. if f then return f end return e end --- Return the difference of one table against another. -- @tparam table t The original table. -- @tparam table other The table to perform the difference against. -- @return All elements in the first table that are not in the other table. function table.difference(t, other) local ret = {} for k, v in pairs(t) do if type(k) == "number" then local found = false for _, ov in ipairs(other) do if ov == v then found = true break end end if not found then rtable.insert(ret, v) end else if not other[k] then ret[k] = v end end end return ret end --- Join all tables given as parameters. -- This will iterate all tables and insert all their keys into a new table. -- @tparam {table} args A list of tables to join. -- @treturn table A new table containing all keys from the arguments. function table.join(...) local ret = {} for _, t in ipairs({...}) do for k, v in pairs(t) do if type(k) == "number" then rtable.insert(ret, v) else ret[k] = v end end end return ret end --- Check if a table has an item and return its key. -- @tparam table t The table. -- @param item The item to look for in values of the table. -- @return The key where the item is found, or `nil` if not found. function table.hasitem(t, item) for k, v in pairs(t) do if v == item then return k end end end --- Get a sorted table with all integer keys from a table. -- @tparam table t The table for which the keys to get. -- @treturn table A table with keys. function table.keys(t) local keys = { } for k, _ in pairs(t) do rtable.insert(keys, k) end rtable.sort(keys, function (a, b) return type(a) == type(b) and a < b or false end) return keys end --- Reverse a table. -- @tparam table t The table to reverse. -- @treturn table The reversed table. function table.reverse(t) local tr = { } -- reverse all elements with integer keys for _, v in ipairs(t) do rtable.insert(tr, 1, v) end -- add the remaining elements for k, v in pairs(t) do if type(k) ~= "number" then tr[k] = v end end return tr end --- Clone a table. -- @tparam table t The table to clone. -- @treturn table A clone of `t`. function table.clone(t) local c = { } for k, v in pairs(t) do c[k] = v end return c end --- Clone table and set metatable. -- @tparam table t The table to clone. -- @treturn table A clone of `t` with `t`'s metatable. function table.copy(t) local c = table.clone(t) return setmetatable(c, getmetatable(t)) end --- Check if two tables are identical. -- @tparam table a The first table. -- @tparam table b The second table. -- @treturn boolean `true` if both tables are identical. function table.isclone(a, b) if #a ~= #b then return false end for k, _ in pairs(a) do if a[k] ~= b[k] then return false end end return true end --- Clone a table with all values as array items. -- @tparam table t The table to clone. -- @treturn table All values in `t`. function table.values(t) local ret = {} for _, v in pairs(t) do rtable.insert(ret, v) end return ret end --- Convert a table to an array by removing all keys that are not sequential numbers. -- @tparam table t The table to convert. -- @treturn table A new table with all non-number keys removed. function table.toarray(t) local ret = {} for k, v in ipairs(t) do ret[k] = v end return ret end --- Filters an array with a predicate function. Element indices are shifted down -- to fill gaps. -- @tparam table t The array to filter. -- @tparam function pred The predicate function: called with (key, value); return -- `true` to keep element, `false` to remove. -- @treturn table The filtered array. function table.filter_array(t, pred) local ret = {} for i, v in ipairs(t) do if pred(i, v) then ret[#ret+1] = v end end return ret end --- Check if a file exists and is readable. -- @tparam string f The file path. -- @treturn boolean `true` if the file exists and is readable. function os.exists(f) assert(type(f) == "string", "invalid path") local fh = io.open(f) if fh then fh:close() return f end end --- Python like string split (source: lua wiki). -- @tparam string s The string to split. -- @tparam string pattern The split pattern (I.e. "%s+" to split text by one or more -- whitespace characters). -- @tparam[opt] table ret The table to insert the split items in to or a new table if `nil`. -- @treturn table A table of the string split by the pattern. function string.split(s, pattern, ret) if not pattern then pattern = "%s+" end if not ret then ret = {} end local pos = 1 local fstart, fend = rstring.find(s, pattern, pos) while fstart do rtable.insert(ret, rstring.sub(s, pos, fstart - 1)) pos = fend + 1 fstart, fend = rstring.find(s, pattern, pos) end rtable.insert(ret, rstring.sub(s, pos)) return ret end -- Python like string strip. -- @tparam string s The string to strip. -- @tparam string pattern The pattern to strip from the left-most and right-most of the -- string. -- @return The inner string segment. function string.strip(s, pattern) local p = pattern or "%s*" local sub_start, sub_end -- Find start point local _, f_end = rstring.find(s, "^"..p) if f_end then sub_start = f_end + 1 end -- Find end point local f_start = rstring.find(s, p.."$") if f_start then sub_end = f_start - 1 end return rstring.sub(s, sub_start or 1, sub_end or #s) end function string.dedent(text, first) local min = first and #rstring.match(text, "^(%s*)") or nil rstring.gsub(text, "\n(%s*)", function (spaces) local len = #spaces if not min or len < min then min = len end end) if min and min > 0 then local pat = "\n" .. rstring.rep(" ", min) text = rstring.gsub(text, pat, "\n") end return first and rstring.sub(text, min + 1) or text end --- Find glyph backward (used in readline.lua). -- @tparam string s The string to be searched. -- @tparam number o The starting offset to search a glyph backward. -- @treturn number string Offset and glyph if found, otherwise nil. function string.prev_glyph (s, o) if not o or not s or o > s:len() or o < 1 then return nil end local glen = 0 for i = o, 1, -1 do if s:sub(i):match("^"..utf8.charpattern) then return i - 1, s:sub(i,i+glen) else glen = glen + 1 end end return nil end --- Find glyph forward (used in readline.lua). -- @tparam string s The string to be searched. -- @tparam number o The starting offset to search a glyph forward. -- @treturn number string Offset and glyph if found, otherwise nil. function string.next_glyph (s, o) if not o or not s or o > s:len() or o < 1 then return nil end local m = s:match(utf8.charpattern, o) return o + #m, m end local function find_file(paths) for _, p in ipairs(paths) do if os.exists(p) then return p end end return error(rstring.format("No such file at: \n\t%s\n", rtable.concat(paths, ",\n\t"))) end --- Search and return the filepath of a file in the current working directory, -- the luakit configuration directory, or the system luakit configuration -- directory. -- @tparam string f The relative filepath. -- @treturn string The first valid filepath or an error. function _M.find_config(f) if rstring.match(f, "^/") then return f end -- Search locations local paths = { "config/"..f, luakit.config_dir.."/"..f } for _, path in ipairs(xdg.system_config_dirs) do rtable.insert(paths, path.."/luakit/"..f) end return find_file(paths) end --- Search and return the filepath of a file in the current working directory, -- the luakit data directory, or the luakit installation directory. -- @tparam string f The relative filepath. -- @treturn string The first valid filepath or an error. function _M.find_data(f) if rstring.match(f, "^/") then return f end -- Search locations local paths = { f, luakit.data_dir.."/"..f, luakit.install_paths.install_dir.."/"..f } return find_file(paths) end --- Search and return the filepath of a file in the current working directory -- or the luakit cache directory. -- @tparam string f The relative filepath. -- @treturn string The first valid filepath or an error. function _M.find_cache(f) -- Ignore absolute paths if rstring.match(f, "^/") then return f end -- Search locations local paths = { luakit.cache_dir.."/"..f } return find_file(paths) end --- Search for and return the filepath of a file in luakit's resource directories. -- @tparam string f The relative filepath. -- @treturn string The first valid filepath or an error. function _M.find_resource(f) -- Ignore absolute paths if rstring.match(f, "^/") then return f end -- Search locations local paths = string.split(luakit.resource_path, ";") for i in ipairs(paths) do paths[i] = paths[i]:gsub("/*$", "") .. "/" .. f end return find_file(paths) end --- Recursively traverse widget tree and return all widgets. -- @tparam widget wi The widget. function _M.recursive_remove(wi) local ret = {} -- Empty other container widgets for _, child in ipairs(wi.children or {}) do wi:remove(child) rtable.insert(ret, child) for _, c in ipairs(_M.recursive_remove(child)) do rtable.insert(ret, c) end end return ret end --- Convert a number to string independent from locale. -- @tparam number num A number. -- @tparam number sigs Signifigant figures (if float). -- @treturn string The string representation of the number. function _M.ntos(num, sigs) local dec = rstring.sub(tostring(num % 1), 3, 2 + (sigs or 4)) num = tostring(math.floor(num)) return (#dec == 0 and num) or (num .. "." .. dec) end --- Escape values for SQL queries. -- In sqlite3: "A string constant is formed by enclosing the string in single -- quotes ('). A single quote within the string can be encoded by putting two -- single quotes in a row - as in Pascal." -- Read: . -- @tparam string s A string. -- @treturn string The escaped string. function _M.sql_escape(s) return "'" .. rstring.gsub(s or "", "'", "''") .. "'" end --- Escape values for lua patterns. -- -- Escapes the magic characters ^$()%.[]*+-?) by prepending a -- %. -- -- @tparam string s A string. -- @treturn string The escaped pattern. function _M.lua_escape(s) return s:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?%)])", "%%%1") end local etc_hosts --- Get all hostnames in `/etc/hosts`. -- @tparam boolean force Force re-load of `/etc/hosts`. -- @treturn {string} Table of all hostnames in `/etc/hosts`. function _M.get_etc_hosts(force) -- Unless forced return previous hostnames if not force and etc_hosts then return etc_hosts end -- Parse /etc/hosts local match, find, gsub = rstring.match, rstring.find, rstring.gsub local h = { localhost = "localhost" } for line in io.lines("/etc/hosts") do if not find(line, "^#") then local names = match(line, "^%S+%s+(.+)$") gsub(names or "", "(%S+)", function (name) h[name] = name -- key add removes duplicates end) end end etc_hosts = table.values(h) return etc_hosts end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/000077500000000000000000000000001475363222200162205ustar00rootroot00000000000000luakit-2.4.0/lib/lousy/widget/buf.lua000066400000000000000000000017661475363222200175110ustar00rootroot00000000000000--- Input buffer - status bar widget. -- -- Shows the current contents of the input buffer in the status bar. -- -- @module lousy.widget.buf -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local window = require("window") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} local widgets = { update = function (w, buf) if w.buffer then buf.text = lousy.util.escape(string.format(" %-3s", w.buffer)) buf:show() else buf:hide() end end, } local function new() local buf = widget{type="label"} buf:hide() buf.fg = theme.buf_sbar_fg buf.font = theme.buf_sbar_font return wc.add_widget(widgets, buf) end window.methods.update_buf = function (w) wc.update_widgets_on_w(widgets, w) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/common.lua000066400000000000000000000022171475363222200202150ustar00rootroot00000000000000--- Common functions for implementing widgets. -- -- @module lousy.widget.common -- @copyright 2017 Aidan Holm local window = require("window") local lousy = require("lousy") local _M = {} --- Add `widget` to `widgets`, and automatically remove it when `widget` is -- destroyed. -- @tparam table widgets A table of widgets -- @tparam widget widget A newly-created widget -- @return Returns `widget`, to allow easy chaining. _M.add_widget = function (widgets, widget) assert(type(widgets) == "table") table.insert(widgets, widget) widget:add_signal("destroy", function (wi) table.remove(widgets, lousy.util.table.hasitem(widgets, wi)) end) return widget end --- Update all widgets in `widgets` on the given window. -- @tparam table widgets A table of widgets -- @tparam table w A window table _M.update_widgets_on_w = function (widgets, w, ...) assert(type(widgets) == "table") assert(w.win.type == "window") for _, widget in ipairs(widgets) do if window.ancestor(widget) == w then widgets.update(w, widget, ...) end end end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/hist.lua000066400000000000000000000041231475363222200176720ustar00rootroot00000000000000--- Web page history - status bar widget. -- -- Indicates whether the current page can go back or go forward. -- -- The widget will not be shown if the current page cannot go back or forward. -- -- @module lousy.widget.hist -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} --- Format string which defines the appearance of the widget. -- The text `{back}` is replaced with the back indicator, and the text -- `{forward}` is replaced with the forward indicator. -- @type string -- @readwrite _M.format = "[{back}{forward}]" --- Text used to indicate that the current page can go back. -- @type string -- @readwrite _M.back_indicator = "+" --- Text used to indicate that the current page can go forward. -- @type string -- @readwrite _M.forward_indicator = "-" local widgets = { update = function (w, hist) local back, forward = w.view:can_go_back(), w.view:can_go_forward() if back or forward then hist.text = string.gsub(_M.format, "{(%w+)}", { back = back and _M.back_indicator or "", forward = forward and _M.forward_indicator or "", }) hist:show() else hist:hide() end end, } webview.add_signal("init", function (view) -- Update widget when current page changes status view:add_signal("load-status", function (v) local w = webview.window(v) if w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local hist = widget{type="label"} hist:hide() hist.fg = theme.hist_sbar_fg hist.font = theme.hist_sbar_font return wc.add_widget(widgets, hist) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/init.lua000066400000000000000000000017621475363222200176740ustar00rootroot00000000000000--- lousy.widget library. -- -- @module lousy.widget -- @author Mason Larobina -- @copyright 2010 Mason Larobina local wrap = function (modname) return function () return require(modname) end end local wrapped = { buf = wrap("lousy.widget.buf"), hist = wrap("lousy.widget.hist"), menu = wrap("lousy.widget.menu"), progress = wrap("lousy.widget.progress"), scroll = wrap("lousy.widget.scroll"), ssl = wrap("lousy.widget.ssl"), tabi = wrap("lousy.widget.tabi"), tablist = wrap("lousy.widget.tablist"), tab = wrap("lousy.widget.tab"), tgname = wrap("lousy.widget.tgname"), uri = wrap("lousy.widget.uri"), zoom = wrap("lousy.widget.zoom"), } local unwrap = function (t, k) if wrapped[k] then t[k] = (wrapped[k])() wrapped[k] = nil return t[k] end end return setmetatable({}, { __index = unwrap }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/menu.lua000066400000000000000000000203461475363222200176740ustar00rootroot00000000000000--- Luakit menu widget. -- -- @module lousy.widget.menu -- @author Mason Larobina -- @copyright 2010 Mason Larobina local signal = require "lousy.signal" local get_theme = require("lousy.theme").get local _M = {} local data = setmetatable({}, { __mode = "k" }) local function update(menu) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") -- Get private menu widget data local d = data[menu] -- Get theme table local theme = get_theme() local fg, bg, font = theme.menu_fg, theme.menu_bg, theme.menu_font local sfg, sbg = theme.menu_selected_fg, theme.menu_selected_bg -- Hide widget while re-drawing menu.widget:hide() -- Build & populate rows for i = 1, math.max(d.max_rows, #(d.table)) do -- Get row local index = i + d.offset - 1 local row = (i <= d.max_rows) and d.rows[index] -- Get row widget table local rw = d.table[i] -- Make new row if row and not rw then -- Row widget struct rw = { ebox = widget{type = "eventbox"}, hbox = widget{type = "hbox"}, cols = {}, } rw.ebox.child = rw.hbox d.table[i] = rw -- Add to main vbox menu.widget:pack(rw.ebox) -- Remove row elseif not row and rw then -- Destroy row columns (label widgets) for _, l in ipairs(rw.cols) do rw.hbox:remove(l) l:destroy() end rw.ebox:remove(rw.hbox) rw.hbox:destroy() menu.widget:remove(rw.ebox) rw.ebox:destroy() d.table[i] = nil end -- Populate columns if row and rw then -- Match up row data with row widget (for callbacks) rw.data = row -- Try to find last off-screen title row and replace with current if i == 1 and not row.title and d.offset > 1 then local j = d.offset - 1 while j > 0 do local r = d.rows[j] -- Only check rows with same number of columns if #r ~= #row then break end -- Check if title row if r.title then row, index = r, j break end j = j - 1 end end -- Is this the selected row? local selected = not row.title and index == d.cursor -- Set row bg local rbg if row.title then rbg = (row.bg or theme.menu_title_bg) or bg else rbg = (selected and (row.selected_bg or sbg)) or row.bg or bg end if rw.ebox.bg ~= rbg then rw.ebox.bg = rbg end for c = 1, math.max(#row, #(rw.cols)) do -- Get column text local text = row[c] text = (type(text) == "function" and text(row)) or text -- Get table cell local cell = rw.cols[c] -- Make new row column widget if text and not cell then cell = widget{type = "label"} rw.hbox:pack(cell, { expand = true, fill = true }) rw.cols[c] = cell cell.font = font cell.textwidth = 1 -- Remove row column widget elseif not text and cell then rw.hbox:remove(cell) rw.cols[c] = nil cell:destroy() end -- Set cell props local cfg if text and cell and row.title then cell.text = text cfg = row.fg or (c == 1 and theme.menu_primary_title_fg or theme.menu_secondary_title_fg) or fg if cell.fg ~= cfg then cell.fg = cfg end elseif text and cell then cell.text = text cfg = (selected and (row.selected_fg or sfg)) or row.fg or fg if cell.fg ~= cfg then cell.fg = cfg end end end end end -- Show widget menu.widget:show() end local function build(menu, rows) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") -- Get private menu widget data local d = data[menu] -- Check rows for _, row in ipairs(rows) do assert(type(row) == "table", "invalid row in rows table") assert(#row >= 1, "empty row") end d.rows = rows d.nrows = #rows -- Initial positions d.cursor = 0 d.offset = 1 update(menu) end local function calc_offset(menu) local d = data[menu] if d.cursor < 1 then return elseif d.cursor <= d.offset then d.offset = math.max(d.cursor - 1, 1) elseif d.cursor > (d.offset + d.max_rows - 1) then d.offset = math.max(d.cursor - d.max_rows + 1, 1) end end local function move_up(menu) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") -- Get private menu widget data local d = data[menu] -- Move cursor if not d.cursor or d.cursor < 1 then d.cursor = d.nrows else d.cursor = d.cursor - 1 end -- Get next non-title row (you can't select titles) while d.cursor > 0 and d.cursor <= d.nrows and d.rows[d.cursor].title do d.cursor = d.cursor - 1 end calc_offset(menu) update(menu) -- Emit changed signals menu:emit_signal("changed", menu:get()) end local function move_down(menu) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") -- Get private menu widget data local d = data[menu] -- Move cursor if d.cursor == d.nrows then d.cursor = 0 else d.cursor = (d.cursor or 0) + 1 end -- Get next non-title row (you can't select titles) while d.cursor > 0 and d.cursor <= d.nrows and d.rows[d.cursor].title do d.cursor = d.cursor + 1 end calc_offset(menu) update(menu) -- Emit changed signals menu:emit_signal("changed", menu:get()) end local function get(menu, index) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") -- Get private menu widget data local d = data[menu] -- Return row at given index or current cursor position. return d.rows[index or d.cursor] end local function del(menu, index) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") assert(not index or type(index) == "number", "invalid index") -- Get private menu widget data local d = data[menu] index = index or d.cursor -- Unable to delete this index, return if index < 1 then return end table.remove(d.rows, index) if index < d.cursor then d.cursor = d.cursor - 1 end -- Update rows count d.nrows = #(d.rows) -- Check cursor d.cursor = math.min(d.cursor, d.nrows) d.offset = math.min(d.offset, math.max(d.nrows - d.max_rows + 1, 1)) update(menu) -- Emit changed signals menu:emit_signal("changed", menu:get()) end local function nrows(menu) assert(data[menu] and type(menu.widget) == "widget", "invalid menu widget") return data[menu].nrows end local function new(args) args = args or {} local menu = { widget = widget{type = "vbox"}, -- Add widget methods build = build, update = update, get = get, del = del, move_up = move_up, move_down = move_down, hide = function(menu) menu.widget:hide() end, show = function(menu) menu.widget:show() end, nrows = nrows, } -- Save private widget data data[menu] = { -- Hold the rows & columns of label widgets which construct the -- menu list widget. table = {}, max_rows = args.max_rows or 10, nrows = 0, rows = {}, } -- Setup class signals signal.setup(menu) return menu end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/progress.lua000066400000000000000000000027761475363222200206030ustar00rootroot00000000000000--- Web page load progress - status bar widget. -- -- Shows the load progress of the current web page as a percentage. -- -- @module lousy.widget.progress -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} local widgets = { update = function (w, progress) local p = w.view.progress if not w.view.is_loading or p == 1 then progress:hide() else progress:show() progress.text = string.format("(%d%%)", p * 100) end end, } webview.add_signal("init", function (view) -- Update widget when current page changes status for _, sig in ipairs({"load-status", "property::progress"}) do view:add_signal(sig, function (v) local w = webview.window(v) if w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) end view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local progress = widget{type="label"} progress:hide() progress.fg = theme.sbar_loaded_fg progress.font = theme.sbar_loaded_font return wc.add_widget(widgets, progress) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/scroll.lua000066400000000000000000000035521475363222200202260ustar00rootroot00000000000000--- Web page scroll position - status bar widget. -- -- Shows the current scroll position of the web page as a percentage. -- -- @module lousy.widget.scroll -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} local widgets = { update = function (w, label) w.view:eval_js([=[ (function () { var y = window.scrollY; var max = Math.max(window.document.documentElement.scrollHeight - window.innerHeight, 0); return y + " " + max; })() ]=], { callback = function (scroll, err) assert(not err, err) local y, max = scroll:match("^(%S+) (%S+)$") y, max = tonumber(y), tonumber(max) local text if max == 0 then text = "All" elseif y <= 2 then text = "Top" elseif y >= (max - 2) then text = "Bot" else text = string.format("%2d%%", (y / max) * 100) end if label.text ~= text then label.text = text end end }) end, } webview.add_signal("init", function (view) view:add_signal("expose", function (v) local w = webview.window(v) if w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local scroll = widget{type="label"} scroll.fg = theme.scroll_sbar_fg scroll.font = theme.scroll_sbar_font return wc.add_widget(widgets, scroll) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/ssl.lua000066400000000000000000000031741475363222200175310ustar00rootroot00000000000000--- Web page security - ssl status bar widget. -- -- Indicates whether the connection used to load the current web page -- was secure. -- -- @module lousy.widget.ssl -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} local widgets = { update = function (w, ssl) local trusted = w.view:ssl_trusted() if trusted == true then ssl.fg = theme.trust_fg ssl.text = "(trust)" ssl:show() elseif string.sub(w.view.uri or "", 1, 4) == "http" then -- Display (notrust) on http/https URLs ssl.fg = theme.notrust_fg ssl.text = "(notrust)" ssl:show() else ssl:hide() end end, } webview.add_signal("init", function (view) -- Update widget when current page changes status view:add_signal("load-status", function (v, status) local w = webview.window(v) if status == "committed" and w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local ssl = widget{type="label"} ssl:hide() ssl.fg = theme.ssl_sbar_fg ssl.font = theme.ssl_sbar_font return wc.add_widget(widgets, ssl) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/tab.lua000066400000000000000000000117311475363222200174740ustar00rootroot00000000000000--- Luakit tab widget. -- -- @module lousy.widget.tab -- @copyright 2016 Aidan Holm local get_theme = require("lousy.theme").get local escape = require("lousy.util").escape local _M = {} require("lousy.signal").setup(_M, true) local data = setmetatable({}, { __mode = "k" }) --- Table of functions used to generate parts of the tab label text -- @readwrite _M.label_subs = { index_fg = function (tl) local view = data[tl].view local theme = get_theme() local nfg, snfg = theme.tab_ntheme, theme.selected_ntheme local lfg, bfg, gfg = theme.tab_loading_fg, theme.tab_notrust_fg, theme.tab_trust_fg if view.is_loading then -- Show loading on all tabs return lfg elseif tl.current then local trusted = view:ssl_trusted() if trusted == false then return bfg elseif trusted then return gfg else return snfg end else return nfg end end, index = function (tl) return data[tl].index end, title = function (tl) return escape(tl.title) end, } --- Format string which defines the text of each tab label -- @type string -- @readwrite _M.label_format = '{index} {title}' local function destroy(tl) -- Destroy tab container widget tl.widget:destroy() -- Remove signal handlers: tab destruction ≠ view destruction (:tabdetach) for sig, func in pairs(data[tl].view_handlers) do data[tl].view:remove_signal(sig, func) end -- Destroy private widget data data[tl] = nil end local function update_label(tl) local label = data[tl].label label.text = string.gsub(_M.label_format, "{([%w_]+)}", function (k) return _M.label_subs[k](tl) end) end local function set_current(tl, current) local theme = get_theme() local ebox = tl.widget local priv = data[tl] local label = priv.label priv.current = current label.fg = (priv.current and theme.tab_selected_fg) or theme.tab_fg if priv.view.private then ebox.bg = (priv.current and theme.selected_private_tab_bg) or theme.private_tab_bg else ebox.bg = (priv.current and theme.tab_selected_bg) or theme.tab_bg end update_label(tl) end local function set_index(tl, index) data[tl].index = index update_label(tl) end local function update_title_and_label(tl) local view = data[tl].view assert(type(view) == "widget" and view.type == "webview") local new_title = (not data[tl].no_title and view.title ~= "" and view.title) or view.uri or (view.is_loading and "Loading…" or "(Untitled)") if new_title == tl.title then return end tl.title = new_title update_label(tl) end local function new(view, index) assert(type(view) == "widget" and view.type == "webview") assert(type(index) == "number") local tl = { widget = widget{type = "eventbox"}, destroy = destroy, } data[tl] = { label = widget{type = "label"}, view = view, index = index, current = false, no_title = false, } local theme = get_theme() local label = data[tl].label tl.widget.child = label label.font = theme.tab_font label.align = { x = 0 } label.margin_left = 10 label.margin_right = 10 -- Bind signals to associated view data[tl].view_handlers = { ["property::title"] = function () data[tl].no_title = false update_title_and_label(tl) end, ["property::uri"] = function () update_title_and_label(tl) end, ["load-status"] = function (_, status) if status == "provisional" then data[tl].no_title = true end update_title_and_label(tl) update_label(tl) end, } for sig, func in pairs(data[tl].view_handlers) do view:add_signal(sig, func) end tl.widget:add_signal("mouse-enter", function (t) t.bg = theme.tab_hover_bg end) tl.widget:add_signal("mouse-leave", function (t) local priv = data[tl] if priv.view.private then t.bg = (priv.current and theme.selected_private_tab_bg) or theme.private_tab_bg else t.bg = (priv.current and theme.tab_selected_bg) or theme.tab_bg end end) _M.emit_signal("build", tl, view) -- Set new title update_title_and_label(tl) set_current(tl, false) -- Setup metatable interface setmetatable(tl, { __newindex = function (tbl, key, val) return ({ current = set_current, index = set_index, })[key](tbl, val) end, __index = function (tbl, key) if key == "current" or key == "index" then return data[tbl][key] end end, }) return tl end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/tabi.lua000066400000000000000000000026351475363222200176500ustar00rootroot00000000000000--- Tab position - status bar widget. -- -- Shows the number of the current tab, as well as the total number of -- tabs in the window. -- -- @module lousy.widget.tabi -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local window = require("window") local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local _M = {} local widgets = { update = function (w, tabi) tabi.text = string.format("[%d/%d]", w.tabs:current(), w.tabs:count()) end, } webview.add_signal("init", function (view) -- Update widget when current page changes status view:add_signal("switched-page", function (v) local w = webview.window(v) wc.update_widgets_on_w(widgets, w) end) end) window.add_signal("init", function (w) w.tabs:add_signal("page-added", function () luakit.idle_add(function () wc.update_widgets_on_w(widgets, w) end) end) w.tabs:add_signal("page-reordered", function () wc.update_widgets_on_w(widgets, w) end) end) local function new() local tabi = widget{type="label"} tabi.fg = theme.tabi_sbar_fg tabi.font = theme.tabi_sbar_font return wc.add_widget(widgets, tabi) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/tablist.lua000066400000000000000000000231561475363222200203740ustar00rootroot00000000000000--- Luakit tablist widget. -- -- @module lousy.widget.tablist -- @copyright 2016 Aidan Holm -- @copyright 2010 Mason Larobina local signal = require "lousy.signal" local get_theme = require("lousy.theme").get local tab = require "lousy.widget.tab" local settings = require "settings" local window = require "window" local _M = {} --- Width that tabs will shrink to before scrolling starts. -- @type number -- @readwrite _M.min_width = 100 local data = setmetatable({}, { __mode = "k" }) local function destroy(tlist) tlist:set_notebook(nil) -- Destroy tablist container widget tlist.widget:destroy() -- Destroy private widget data data[tlist] = nil end local function scroll_current_tab_into_view(tlist) assert(data[tlist]) if not data[tlist].notebook then return end -- switching notebook -- Only queue one scroll operation at a time if data[tlist].scroll_tab_queued then return end data[tlist].scroll_tab_queued = true -- When opening a new webview, the new tab widget's size is not yet allocated -- Queueing the scroll op avoids a lot of nasty hacks luakit.idle_add(function() -- Cancel if tlist already destroyed if not data[tlist] then return end -- Get the currently selected tab local notebook = data[tlist].notebook local view = notebook[notebook:current()] local tl = data[tlist].tabs[view] if not tl then return end local axis = data[tlist].orientation == "horizontal" and "x" or "y" local size = data[tlist].orientation == "horizontal" and "width" or "height" -- Scroll current tab into view local tab_delta = tl.widget[size] local tab_min = tab_delta * (tl.index-1) local tab_max = tab_min + tab_delta local vp_min = tlist.widget.scroll[axis] local vp_max = vp_min + tlist.widget[size] if tab_min < vp_min then -- need to scroll up tlist.widget.scroll = { [axis] = tab_min } end if tab_max > vp_max then -- need to scroll down tlist.widget.scroll = { [axis] = tlist.widget.scroll[axis] + (tab_max - vp_max) } end data[tlist].scroll_tab_queued = false return false end) end local function regenerate_tab_indices(tlist, a, b) if not data[tlist].notebook then return end -- switching notebook local low, high = a or 1, b or data[tlist].notebook:count() local views = data[tlist].notebook.children local max_pad_len = #tostring(data[tlist].notebook:count()) for i=low, high do local view = views[i] local tl = data[tlist].tabs[view] local pad_len = data[tlist].orientation == "vertical" and (max_pad_len - #tostring(i)) or 0 tl.index = (" "):rep(pad_len) .. tostring(i) end end local function update_tablist_visibility(tlist) if not data[tlist].notebook then return end -- switching notebook if not data[tlist].visible then tlist.widget.visible = false elseif settings.get_setting("tablist.always_visible") or settings.get_setting("tablist.visibility") == "always" then tlist.widget.visible = true elseif settings.get_setting("tablist.visibility") == "never" then tlist.widget.visible = false else tlist.widget.visible = data[tlist].notebook:count() > 1 end end local function tablist_nb_page_added_cb(tlist, view, idx) local tl = tab(view, idx) data[tlist].tabs[view] = tl local orientation = data[tlist].orientation if _M.min_width and _M.min_width > 0 and orientation == "horizontal" then tl.widget.min_size = { w = _M.min_width } end data[tlist].box:pack(tl.widget, { expand = orientation == "horizontal", fill = true }) data[tlist].box:reorder(tl.widget, idx-1) regenerate_tab_indices(tlist) tl.widget:add_signal("button-release", function (_, mods, but) return tlist:emit_signal("tab-clicked", tl.index, mods, but) end) tl.widget:add_signal("button-double-click", function (_, mods, but) return tlist:emit_signal("tab-double-clicked", tl.index, mods, but) end) update_tablist_visibility(tlist) end local function tablist_nb_page_removed_cb(tlist, view) local tl = data[tlist].tabs[view] data[tlist].box:remove(tl.widget) tl:destroy() regenerate_tab_indices(tlist) data[tlist].tabs[view] = nil update_tablist_visibility(tlist) end local function tablist_nb_switch_page_cb(tlist, view) local prev_view = data[tlist].prev_view data[tlist].prev_view = view if prev_view then local prev_tl = data[tlist].tabs[prev_view] if prev_tl then prev_tl.current = false end end local tl = data[tlist].tabs[view] tl.current = true scroll_current_tab_into_view(tlist) end local function tablist_nb_page_reordered_cb(tlist, view, idx) local tl = data[tlist].tabs[view] local old_idx = tl.index data[tlist].box:reorder(tl.widget, idx-1) regenerate_tab_indices(tlist, math.min(old_idx, idx), math.max(old_idx, idx)) scroll_current_tab_into_view(tlist) end local function set_notebook(tlist, nb) assert(data[tlist]) assert(nb == nil or (type(nb) == "widget" and nb.type == "notebook")) if data[tlist].notebook then -- Remove existing notebook signals for signame, func in pairs(data[tlist].handlers) do data[tlist].notebook:remove_signal(signame, func) end -- Destroy all tabs for _, tl in pairs(data[tlist].tabs) do data[tlist].box:remove(tl.widget) tl:destroy() end assert(#data[tlist].box.children == 0) data[tlist].notebook = nil data[tlist].tabs = nil end if nb then -- Attach notebook signal handlers data[tlist].handlers = { ["page-added"] = function (_, ...) tablist_nb_page_added_cb(tlist, ...) end, ["page-removed"] = function (_, ...) tablist_nb_page_removed_cb(tlist, ...) end, ["switch-page"] = function (_, ...) tablist_nb_switch_page_cb(tlist, ...) end, ["page-reordered"] = function (_, ...) tablist_nb_page_reordered_cb(tlist, ...) end, } for signame, func in pairs(data[tlist].handlers) do nb:add_signal(signame, func) end -- Make new tabs data[tlist].tabs = setmetatable({}, { __mode = "k" }) local current_view = nb[nb:current()] for _, view in ipairs(nb.children) do tablist_nb_page_added_cb(tlist, view, nb:indexof(view)) if view == current_view then tablist_nb_switch_page_cb(tlist, view, nb:indexof(view)) end end data[tlist].notebook = nb update_tablist_visibility(tlist) end end --- Create a new tablist widget connected to a given notebook widget. -- -- `orientation` should be one of `"horizontal"` or `"vertical"`. -- -- @tparam widget notebook The notebook widget to connect to. -- @tparam string orientation The orientation of the new tablist widget. -- @treturn table A table containing the new widget and its interface. function _M.new(notebook, orientation) assert(type(notebook) == "widget" and notebook.type == "notebook") assert(orientation == "horizontal" or orientation == "vertical") -- Create tablist widget table local tlist = { widget = widget{type = "scrolled"}, destroy = destroy, set_notebook = set_notebook, } local box = widget{type = orientation == "horizontal" and "hbox" or "vbox"} local theme = get_theme() box.bg = theme.tab_list_bg tlist.widget.child = box -- Hide scrollbar on horizontal tablist, since it covers the tabs if orientation == "horizontal" then tlist.widget.scrollbars = { h = "external", v = "never" } end -- Save private widget data data[tlist] = { box = box, orientation = orientation, visible = true, } -- Setup class signals signal.setup(tlist) tlist.widget:hide() -- Setup metatable interface setmetatable(tlist, { __newindex = function (tbl, key, val) if key == "visible" then data[tbl].visible = val update_tablist_visibility(tlist) end end, __index = function (tbl, key) if key == "visible" then return data[tbl][key] end end, }) tlist:set_notebook(notebook) return tlist end settings.register_settings({ ["tablist.always_visible"] = { type = "boolean", default = false, domain_specific = false, desc = "Whether the tab list should be visible with only a single tab open. ".. "This is deprecated in favour of tablist.visibility and may be removed in the future.", }, ["tablist.visibility"] = { type = "enum", options = { ["always"] = { desc = "Always display the tab list.", label = "Always", }, ["multiple"] = { desc = "Hide tab list if there's only one tab.", label = "If multiple tabs", }, ["never"] = { desc = "Never display tab list.", label = "Never", }, }, default = "multiple", domain_specific = false, desc = "When should the tab list be visible?", }, }) settings.add_signal("setting-changed", function (e) if e.key == "tablist.always_visible" or e.key == "tablist.visibility" then -- Hack: cause update_tablist_visibility() to be called for all windows for _, w in pairs(window.bywidget) do w.tablist.visible = w.tablist.visible end end end) return setmetatable(_M, { __call = function(_, ...) return _M.new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/tgname.lua000066400000000000000000000023241475363222200201770ustar00rootroot00000000000000--- Tabgroup name - status bar widget. -- -- Shows the name of the current tabgroup. -- -- @module lousy.widget.tgname -- @copyright 2021 Tao Nelson -- Derived from lousy.widget.uri -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local _M = {} local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local tabgroups = require('tabgroups') local widgets = { update = function (w,tgname) tgname.text = lousy.util.escape('['..tabgroups.current_tabgroup(w)..']') end, } webview.add_signal("init", function (view) -- `switch_tabgroup()` and `tabgroup-menu-rename` emit `switched-page` view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local tgname = widget{type="label"} tgname.selectable = true tgname.can_focus = false tgname.fg = theme.sbar_fg tgname.font = theme.sbar_font return wc.add_widget(widgets, tgname) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/uri.lua000066400000000000000000000033441475363222200175260ustar00rootroot00000000000000--- Web page URI - status bar widget. -- -- Shows the URI of the current web page. If a link is hovered over with -- the mouse, the target URI of that link will be shown temporarily. -- -- @module lousy.widget.uri -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local _M = {} local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local widgets = { update = function (w, uri, link) local text = (link and "Link: " .. link) or (w.view and w.view.uri) or "about:blank" uri.text = lousy.util.escape(text) end, } webview.add_signal("init", function (view) view:add_signal("property::uri", function (v) local w = webview.window(v) if w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("link-hover", function (v, link) local w = webview.window(v) if w and w.view == v and link then wc.update_widgets_on_w(widgets, w, link) end end) view:add_signal("link-unhover", function (v) local w = webview.window(v) if w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local uri = widget{type="label"} uri.selectable = true uri.can_focus = false uri.fg = theme.uri_sbar_fg uri.font = theme.uri_sbar_font return wc.add_widget(widgets, uri) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/lousy/widget/zoom.lua000066400000000000000000000031511475363222200177070ustar00rootroot00000000000000--- Web page zoom level - status bar widget. -- -- Shows the zoom levle of the current web page as a percentage. -- -- @module lousy.widget.zoom -- @copyright 2017 Aidan Holm -- @copyright 2014 Justin Forest local webview = require("webview") local lousy = require("lousy") local theme = lousy.theme.get() local wc = require("lousy.widget.common") local settings = require("settings") local _M = {} --- Format string which defines the appearance of the widget. -- This is passed to `string.format` the the zoom level as a numerical argument. -- @type string -- @readwrite _M.format = "[zoom:%d%%]" local widgets = { update = function (w, zoom) local zl = w.view.zoom_level if zl == settings.get_setting("webview.zoom_level") / 100 then zoom:hide() else zoom:show() zoom.text = string.format(_M.format, zl * 100) end end, } webview.add_signal("init", function (view) -- Update widget when current page changes status view:add_signal("property::zoom_level", function (v) local w = webview.window(v) if w and w.view == v then wc.update_widgets_on_w(widgets, w) end end) view:add_signal("switched-page", function (v) wc.update_widgets_on_w(widgets, webview.window(v)) end) end) local function new() local zoom = widget{type="label"} zoom:hide() zoom.fg = theme.sbar_zoom_fg zoom.font = theme.sbar_zoom_font return wc.add_widget(widgets, zoom) end return setmetatable(_M, { __call = function(_, ...) return new(...) end }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/markdown.lua000066400000000000000000001153221475363222200161130ustar00rootroot00000000000000--[[ # markdown.lua -- version 0.32 **Author:** Niklas Frykholm, **Date:** 31 May 2008 This is an implementation of the popular text markup language Markdown in pure Lua. Markdown can convert documents written in a simple and easy to read text format to well-formatted HTML. For a more thourough description of Markdown and the Markdown syntax, see . The original Markdown source is written in Perl and makes heavy use of advanced regular expression techniques (such as negative look-ahead, etc) which are not available in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground up. It is probably not completely bug free. If you notice any bugs, please report them to me. A unit test that exposes the error is helpful. ## Usage require "markdown" markdown(source) ``markdown.lua`` exposes a single global function named ``markdown(s)`` which applies the Markdown transformation to the specified string. ``markdown.lua`` can also be used directly from the command line: lua markdown.lua test.md Creates a file ``test.html`` with the converted content of ``test.md``. Run: lua markdown.lua -h For a description of the command-line options. ``markdown.lua`` uses the same license as Lua, the MIT license. ## License Copyright © 2008 Niklas Frykholm. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Version history - **0.32** -- 31 May 2008 - Fix for links containing brackets - **0.31** -- 1 Mar 2008 - Fix for link definitions followed by spaces - **0.30** -- 25 Feb 2008 - Consistent behavior with Markdown when the same link reference is reused - **0.29** -- 24 Feb 2008 - Fix for
         blocks with spaces in them
        -	**0.28** -- 18 Feb 2008
        	-	Fix for link encoding
        -	**0.27** -- 14 Feb 2008
        	-	Fix for link database links with ()
        -	**0.26** -- 06 Feb 2008
        	-	Fix for nested italic and bold markers
        -	**0.25** -- 24 Jan 2008
        	-	Fix for encoding of naked <
        -	**0.24** -- 21 Jan 2008
        	-	Fix for link behavior.
        -	**0.23** -- 10 Jan 2008
        	-	Fix for a regression bug in longer expressions in italic or bold.
        -	**0.22** -- 27 Dec 2007
        	-	Fix for crash when processing blocks with a percent sign in them.
        -	**0.21** -- 27 Dec 2007
        	- 	Fix for combined strong and emphasis tags
        -	**0.20** -- 13 Oct 2007
        	-	Fix for < as well in image titles, now matches Dingus behavior
        -	**0.19** -- 28 Sep 2007
        	-	Fix for quotation marks " and ampersands & in link and image titles.
        -	**0.18** -- 28 Jul 2007
        	-	Does not crash on unmatched tags (behaves like standard markdown)
        -	**0.17** -- 12 Apr 2007
        	-	Fix for links with %20 in them.
        -	**0.16** -- 12 Apr 2007
        	-	Do not require arg global to exist.
        -	**0.15** -- 28 Aug 2006
        	-	Better handling of links with underscores in them.
        -	**0.14** -- 22 Aug 2006
        	-	Bug for *`foo()`*
        -	**0.13** -- 12 Aug 2006
        	-	Added -l option for including stylesheet inline in document.
        	-	Fixed bug in -s flag.
        	-	Fixed emphasis bug.
        -	**0.12** -- 15 May 2006
        	-	Fixed several bugs to comply with MarkdownTest 1.0 
        -	**0.11** -- 12 May 2006
        	-	Fixed bug for escaping `*` and `_` inside code spans.
        	-	Added license terms.
        	-	Changed join() to table.concat().
        -	**0.10** -- 3 May 2006
        	-	Initial public release.
        
        // Niklas
        ]]
        
        
        -- Set up a table for holding local functions to avoid polluting the global namespace
        local M = {}
        local MT = {__index = _G}
        setmetatable(M, MT)
        setfenv(1, M)
        
        ----------------------------------------------------------------------
        -- Utility functions
        ----------------------------------------------------------------------
        
        -- Locks table t from changes, writes an error if someone attempts to change the table.
        -- This is useful for detecting variables that have "accidently" been made global. Something
        -- I tend to do all too much.
        function lock(t)
        	function lock_new_index(t, k, v)
        		error("module has been locked -- " .. k .. " must be declared local", 2)
        	end
        
        	local mt = {__newindex = lock_new_index}
        	if getmetatable(t) then mt.__index = getmetatable(t).__index end
        	setmetatable(t, mt)
        end
        
        -- Returns the result of mapping the values in table t through the function f
        function map(t, f)
        	local out = {}
        	for k,v in pairs(t) do out[k] = f(v,k) end
        	return out
        end
        
        -- The identity function, useful as a placeholder.
        function identity(text) return text end
        
        -- Functional style if statement. (NOTE: no short circuit evaluation)
        function iff(t, a, b) if t then return a else return b end end
        
        -- Splits the text into an array of separate lines.
        function split(text, sep)
        	sep = sep or "\n"
        	local lines = {}
        	local pos = 1
        	while true do
        		local b,e = text:find(sep, pos)
        		if not b then table.insert(lines, text:sub(pos)) break end
        		table.insert(lines, text:sub(pos, b-1))
        		pos = e + 1
        	end
        	return lines
        end
        
        -- Converts tabs to spaces
        function detab(text)
        	local tab_width = 4
        	local function rep(match)
        		local spaces = -match:len()
        		while spaces<1 do spaces = spaces + tab_width end
        		return match .. string.rep(" ", spaces)
        	end
        	text = text:gsub("([^\n]-)\t", rep)
        	return text
        end
        
        -- Applies string.find for every pattern in the list and returns the first match
        function find_first(s, patterns, index)
        	local res = {}
        	for _,p in ipairs(patterns) do
        		local match = {s:find(p, index)}
        		if #match>0 and (#res==0 or match[1] < res[1]) then res = match end
        	end
        	return unpack(res)
        end
        
        -- If a replacement array is specified, the range [start, stop] in the array is replaced
        -- with the replacement array and the resulting array is returned. Without a replacement
        -- array the section of the array between start and stop is returned.
        function splice(array, start, stop, replacement)
        	if replacement then
        		local n = stop - start + 1
        		while n > 0 do
        			table.remove(array, start)
        			n = n - 1
        		end
        		for i,v in ipairs(replacement) do
        			table.insert(array, start, v)
        		end
        		return array
        	else
        		local res = {}
        		for i = start,stop do
        			table.insert(res, array[i])
        		end
        		return res
        	end
        end
        
        -- Outdents the text one step.
        function outdent(text)
        	text = "\n" .. text
        	text = text:gsub("\n  ? ? ?", "\n")
        	text = text:sub(2)
        	return text
        end
        
        -- Indents the text one step.
        function indent(text)
        	text = text:gsub("\n", "\n    ")
        	return text
        end
        
        -- Does a simple tokenization of html data. Returns the data as a list of tokens.
        -- Each token is a table with a type field (which is either "tag" or "text") and
        -- a text field (which contains the original token data).
        function tokenize_html(html)
        	local tokens = {}
        	local pos = 1
        	while true do
        		local start = find_first(html, {"", start)
        		elseif html:match("^<%?", start) then
        			_,stop = html:find("?>", start)
        		else
        			_,stop = html:find("%b<>", start)
        		end
        		if not stop then
        			-- error("Could not match html tag " .. html:sub(start,start+30))
        		 	table.insert(tokens, {type="text", text=html:sub(start, start)})
        			pos = start + 1
        		else
        			table.insert(tokens, {type="tag", text=html:sub(start, stop)})
        			pos = stop + 1
        		end
        	end
        	return tokens
        end
        
        ----------------------------------------------------------------------
        -- Hash
        ----------------------------------------------------------------------
        
        -- This is used to "hash" data into alphanumeric strings that are unique
        -- in the document. (Note that this is not cryptographic hash, the hash
        -- function is not one-way.) The hash procedure is used to protect parts
        -- of the document from further processing.
        
        local HASH = {
        	-- Has the hash been inited.
        	inited = false,
        
        	-- The unique string prepended to all hash values. This is to ensure
        	-- that hash values do not accidently coincide with an actual existing
        	-- string in the document.
        	identifier = "",
        
        	-- Counter that counts up for each new hash instance.
        	counter = 0,
        
        	-- Hash table.
        	table = {}
        }
        
        -- Inits hashing. Creates a hash_identifier that doesn't occur anywhere
        -- in the text.
        function init_hash(text)
        	HASH.inited = true
        	HASH.identifier = ""
        	HASH.counter = 0
        	HASH.table = {}
        
        	local s = "HASH"
        	local counter = 0
        	local id
        	while true do
        		id  = s .. counter
        		if not text:find(id, 1, true) then break end
        		counter = counter + 1
        	end
        	HASH.identifier = id
        end
        
        -- Returns the hashed value for s.
        function hash(s)
        	assert(HASH.inited)
        	if not HASH.table[s] then
        		HASH.counter = HASH.counter + 1
        		local id = HASH.identifier .. HASH.counter .. "X"
        		HASH.table[s] = id
        	end
        	return HASH.table[s]
        end
        
        ----------------------------------------------------------------------
        -- Protection
        ----------------------------------------------------------------------
        
        -- The protection module is used to "protect" parts of a document
        -- so that they are not modified by subsequent processing steps.
        -- Protected parts are saved in a table for later unprotection
        
        -- Protection data
        local PD = {
        	-- Saved blocks that have been converted
        	blocks = {},
        
        	-- Block level tags that will be protected
        	tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote",
        	"pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset",
        	"iframe", "math", "ins", "del"}
        }
        
        -- Pattern for matching a block tag that begins and ends in the leftmost
        -- column and may contain indented subtags, i.e.
        -- 
        -- A nested block. --
        -- Nested data. --
        --
        function block_pattern(tag) return "\n<" .. tag .. ".-\n[ \t]*\n" end -- Pattern for matching a block tag that begins and ends with a newline function line_pattern(tag) return "\n<" .. tag .. ".-[ \t]*\n" end -- Protects the range of characters from start to stop in the text and -- returns the protected string. function protect_range(text, start, stop) local s = text:sub(start, stop) local h = hash(s) PD.blocks[h] = s text = text:sub(1,start) .. h .. text:sub(stop) return text end -- Protect every part of the text that matches any of the patterns. The first -- matching pattern is protected first, etc. function protect_matches(text, patterns) while true do local start, stop = find_first(text, patterns) if not start then break end text = protect_range(text, start, stop) end return text end -- Protects blocklevel tags in the specified text function protect(text) -- First protect potentially nested block tags text = protect_matches(text, map(PD.tags, block_pattern)) -- Then protect block tags at the line level. text = protect_matches(text, map(PD.tags, line_pattern)) -- Protect
        and comment tags text = protect_matches(text, {"\n]->[ \t]*\n"}) text = protect_matches(text, {"\n[ \t]*\n"}) return text end -- Returns true if the string s is a hash resulting from protection function is_protected(s) return PD.blocks[s] end -- Unprotects the specified text by expanding all the nonces function unprotect(text) for k,v in pairs(PD.blocks) do v = v:gsub("%%", "%%%%") text = text:gsub(k, v) end return text end ---------------------------------------------------------------------- -- Block transform ---------------------------------------------------------------------- -- The block transform functions transform the text on the block level. -- They work with the text as an array of lines rather than as individual -- characters. -- Returns true if the line is a ruler of (char) characters. -- The line must contain at least three char characters and contain only spaces and -- char characters. function is_ruler_of(line, char) if not line:match("^[ %" .. char .. "]*$") then return false end if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end return true end -- Identifies the block level formatting present in the line function classify(line) local info = {line = line, text = line} if line:match("^ ") then info.type = "indented" info.outdented = line:sub(5) return info end for _,c in ipairs({'*', '-', '_', '='}) do if is_ruler_of(line, c) then info.type = "ruler" info.ruler_char = c return info end end if line == "" then info.type = "blank" return info end if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") info.type = "header" info.level = m1:len() info.text = m2 return info end if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") info.type = "list_item" info.list_type = "numeric" info.number = 0 + number info.text = text return info end if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") info.type = "list_item" info.list_type = "bullet" info.bullet = bullet info.text= text return info end if line:match("^>[ \t]?(.*)") then info.type = "blockquote" info.text = line:match("^>[ \t]?(.*)") return info end if is_protected(line) then info.type = "raw" info.html = unprotect(line) return info end info.type = "normal" return info end -- Find headers constisting of a normal line followed by a ruler and converts them to -- header entries. function headers(array) local i = 1 while i <= #array - 1 do if array[i].type == "normal" and array[i+1].type == "ruler" and (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then local info = {line = array[i].line} info.text = info.line info.type = "header" info.level = iff(array[i+1].ruler_char == "=", 1, 2) table.remove(array, i+1) array[i] = info end i = i + 1 end return array end -- Find list blocks and convert them to protected data blocks function lists(array, sublist) local function process_list(arr) local function any_blanks(arr) for i = 1, #arr do if arr[i].type == "blank" then return true end end return false end local function split_list_items(arr) local acc = {arr[1]} local res = {} for i=2,#arr do if arr[i].type == "list_item" then table.insert(res, acc) acc = {arr[i]} else table.insert(acc, arr[i]) end end table.insert(res, acc) return res end local function process_list_item(lines, block) while lines[#lines].type == "blank" do table.remove(lines) end local itemtext = lines[1].text for i=2,#lines do itemtext = itemtext .. "\n" .. outdent(lines[i].line) end if block then itemtext = block_transform(itemtext, true) if not itemtext:find("
        ") then itemtext = indent(itemtext) end
        				return "    
      • " .. itemtext .. "
      • " else local lines = split(itemtext) lines = map(lines, classify) lines = lists(lines, true) lines = blocks_to_html(lines, true) itemtext = table.concat(lines, "\n") if not itemtext:find("
        ") then itemtext = indent(itemtext) end
        				return "    
      • " .. itemtext .. "
      • " end end local block_list = any_blanks(arr) local items = split_list_items(arr) local out = "" for _, item in ipairs(items) do out = out .. process_list_item(item, block_list) .. "\n" end if arr[1].list_type == "numeric" then return "
          \n" .. out .. "
        " else return "
          \n" .. out .. "
        " end end -- Finds the range of lines composing the first list in the array. A list -- starts with (^ list_item) or (blank list_item) and ends with -- (blank* $) or (blank normal). -- -- A sublist can start with just (list_item) does not need a blank... local function find_list(array, sublist) local function find_list_start(array, sublist) if array[1].type == "list_item" then return 1 end if sublist then for i = 1,#array do if array[i].type == "list_item" then return i end end else for i = 1, #array-1 do if array[i].type == "blank" and array[i+1].type == "list_item" then return i+1 end end end return nil end local function find_list_end(array, start) local pos = #array for i = start, #array-1 do if array[i].type == "blank" and array[i+1].type ~= "list_item" and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then pos = i-1 break end end while pos > start and array[pos].type == "blank" do pos = pos - 1 end return pos end local start = find_list_start(array, sublist) if not start then return nil end return start, find_list_end(array, start) end while true do local start, stop = find_list(array, sublist) if not start then break end local text = process_list(splice(array, start, stop)) local info = { line = text, type = "raw", html = text } array = splice(array, start, stop, {info}) end -- Convert any remaining list items to normal for _,line in ipairs(array) do if line.type == "list_item" then line.type = "normal" end end return array end -- Find and convert blockquote markers. function blockquotes(lines) local function find_blockquote(lines) local start for i,line in ipairs(lines) do if line.type == "blockquote" then start = i break end end if not start then return nil end local stop = #lines for i = start+1, #lines do if lines[i].type == "blank" or lines[i].type == "blockquote" then elseif lines[i].type == "normal" then if lines[i-1].type == "blank" then stop = i-1 break end else stop = i-1 break end end while lines[stop].type == "blank" do stop = stop - 1 end return start, stop end local function process_blockquote(lines) local raw = lines[1].text for i = 2,#lines do raw = raw .. "\n" .. lines[i].text end local bt = block_transform(raw) if not bt:find("
        ") then bt = indent(bt) end
        		return "
        \n " .. bt .. "\n
        " end while true do local start, stop = find_blockquote(lines) if not start then break end local text = process_blockquote(splice(lines, start, stop)) local info = { line = text, type = "raw", html = text } lines = splice(lines, start, stop, {info}) end return lines end -- Find and convert codeblocks. function codeblocks(lines) local function find_codeblock(lines) local start for i,line in ipairs(lines) do if line.type == "indented" then start = i break end end if not start then return nil end local stop = #lines for i = start+1, #lines do if lines[i].type ~= "indented" and lines[i].type ~= "blank" then stop = i-1 break end end while lines[stop].type == "blank" do stop = stop - 1 end return start, stop end local function process_codeblock(lines) local raw = detab(encode_code(outdent(lines[1].line))) for i = 2,#lines do raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) end return "
        " .. raw .. "\n
        " end while true do local start, stop = find_codeblock(lines) if not start then break end local text = process_codeblock(splice(lines, start, stop)) local info = { line = text, type = "raw", html = text } lines = splice(lines, start, stop, {info}) end return lines end -- Convert lines to html code function blocks_to_html(lines, no_paragraphs) local out = {} local i = 1 while i <= #lines do local line = lines[i] if line.type == "ruler" then table.insert(out, "
        ") elseif line.type == "raw" then table.insert(out, line.html) elseif line.type == "normal" then local s = line.line while i+1 <= #lines and lines[i+1].type == "normal" do i = i + 1 s = s .. "\n" .. lines[i].line end if no_paragraphs then table.insert(out, span_transform(s)) else table.insert(out, "

        " .. span_transform(s) .. "

        ") end elseif line.type == "header" then local s = "" .. span_transform(line.text) .. "" table.insert(out, s) else table.insert(out, line.line) end i = i + 1 end return out end -- Perform all the block level transforms function block_transform(text, sublist) local lines = split(text) lines = map(lines, classify) lines = headers(lines) lines = lists(lines, sublist) lines = codeblocks(lines) lines = blockquotes(lines) lines = blocks_to_html(lines) local text = table.concat(lines, "\n") return text end -- Debug function for printing a line array to see the result -- of partial transforms. function print_lines(lines) for i, line in ipairs(lines) do print(i, line.type, line.text or line.line) end end ---------------------------------------------------------------------- -- Span transform ---------------------------------------------------------------------- -- Functions for transforming the text at the span level. -- These characters may need to be escaped because they have a special -- meaning in markdown. escape_chars = "'\\`*_{}[]()>#+-.!'" escape_table = {} function init_escape_table() escape_table = {} for i = 1,#escape_chars do local c = escape_chars:sub(i,i) escape_table[c] = hash(c) end end -- Adds a new escape to the escape table. function add_escape(text) if not escape_table[text] then escape_table[text] = hash(text) end return escape_table[text] end -- Escape characters that should not be disturbed by markdown. function escape_special_chars(text) local tokens = tokenize_html(text) local out = "" for _, token in ipairs(tokens) do local t = token.text if token.type == "tag" then -- In tags, encode * and _ so they don't conflict with their use in markdown. t = t:gsub("%*", escape_table["*"]) t = t:gsub("%_", escape_table["_"]) else t = encode_backslash_escapes(t) end out = out .. t end return out end -- Encode backspace-escaped characters in the markdown source. function encode_backslash_escapes(t) for i=1,escape_chars:len() do local c = escape_chars:sub(i,i) t = t:gsub("\\%" .. c, escape_table[c]) end return t end -- Unescape characters that have been encoded. function unescape_special_chars(t) local tin = t for k,v in pairs(escape_table) do k = k:gsub("%%", "%%%%") t = t:gsub(v,k) end if t ~= tin then t = unescape_special_chars(t) end return t end -- Encode/escape certain characters inside Markdown code runs. -- The point is that in code, these characters are literals, -- and lose their special Markdown meanings. function encode_code(s) s = s:gsub("%&", "&") s = s:gsub("<", "<") s = s:gsub(">", ">") for k,v in pairs(escape_table) do s = s:gsub("%"..k, v) end return s end -- Handle backtick blocks. function code_spans(s) s = s:gsub("\\\\", escape_table["\\"]) s = s:gsub("\\`", escape_table["`"]) local pos = 1 while true do local start, stop = s:find("`+", pos) if not start then return s end local count = stop - start + 1 -- Find a matching numbert of backticks local estart, estop = s:find(string.rep("`", count), stop+1) local brstart = s:find("\n", stop+1) if estart and (not brstart or estart < brstart) then local code = s:sub(stop+1, estart-1) code = code:gsub("^[ \t]+", "") code = code:gsub("[ \t]+$", "") code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) code = "" .. encode_code(code) .. "" code = add_escape(code) s = s:sub(1, start-1) .. code .. s:sub(estop+1) pos = start + code:len() else pos = stop + 1 end end return s end -- Encode alt text... enodes &, and ". function encode_alt(s) if not s then return s end s = s:gsub('&', '&') s = s:gsub('"', '"') s = s:gsub('<', '<') return s end -- Handle image references function images(text) local function reference_link(alt, id) alt = encode_alt(alt:match("%b[]"):sub(2,-2)) id = id:match("%[(.*)%]"):lower() if id == "" then id = text:lower() end link_database[id] = link_database[id] or {} if not link_database[id].url then return nil end local url = link_database[id].url or id url = encode_alt(url) local title = encode_alt(link_database[id].title) if title then title = " title=\"" .. title .. "\"" else title = "" end return add_escape ('' .. alt .. '") end local function inline_link(alt, link) alt = encode_alt(alt:match("%b[]"):sub(2,-2)) local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") url = url or link:match("%(?%)") url = encode_alt(url) title = encode_alt(title) if title then return add_escape('' .. alt .. '') else return add_escape('' .. alt .. '') end end text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) text = text:gsub("!(%b[])(%b())", inline_link) return text end -- Handle anchor references function anchors(text) local function reference_link(text, id) text = text:match("%b[]"):sub(2,-2) id = id:match("%b[]"):sub(2,-2):lower() if id == "" then id = text:lower() end link_database[id] = link_database[id] or {} if not link_database[id].url then return nil end local url = link_database[id].url or id url = encode_alt(url) local title = encode_alt(link_database[id].title) if title then title = " title=\"" .. title .. "\"" else title = "" end return add_escape("") .. text .. add_escape("") end local function inline_link(text, link) text = text:match("%b[]"):sub(2,-2) local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") title = encode_alt(title) url = url or link:match("%(?%)") or "" url = encode_alt(url) if title then return add_escape("") .. text .. "" else return add_escape("") .. text .. add_escape("") end end text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) text = text:gsub("(%b[])(%b())", inline_link) return text end -- Handle auto links, i.e. . function auto_links(text) local function link(s) return add_escape("") .. s .. "" end -- Encode chars as a mix of dec and hex entitites to (perhaps) fool -- spambots. local function encode_email_address(s) -- Use a deterministic encoding to make unit testing possible. -- Code 45% hex, 45% dec, 10% plain. local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} local plain = {code = function(c) return c end, count = 0, rate = 0.1} local codes = {hex, dec, plain} local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end local out = "" for i = 1,s:len() do for _,code in ipairs(codes) do code.count = code.count + code.rate end if codes[1].count < codes[2].count then swap(codes,1,2) end if codes[2].count < codes[3].count then swap(codes,2,3) end if codes[1].count < codes[2].count then swap(codes,1,2) end local code = codes[1] local c = s:sub(i,i) -- Force encoding of "@" to make email address more invisible. if c == "@" and code == plain then code = codes[2] end out = out .. code.code(c) code.count = code.count - 1 end return out end local function mail(s) s = unescape_special_chars(s) local address = encode_email_address("mailto:" .. s) local text = encode_email_address(s) return add_escape("") .. text .. "" end -- links text = text:gsub("<(https?:[^'\">%s]+)>", link) text = text:gsub("<(ftp:[^'\">%s]+)>", link) text = text:gsub("<(luakit:[^'\">%s]+)>", link) -- mail text = text:gsub("%s]+)>", mail) text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) return text end -- Encode free standing amps (&) and angles (<)... note that this does not -- encode free >. function amps_and_angles(s) -- encode amps not part of &..; expression local pos = 1 while true do local amp = s:find("&", pos) if not amp then break end local semi = s:find(";", amp+1) local stop = s:find("[ \t\n&]", amp+1) if not semi or (stop and stop < semi) or (semi - amp) > 15 then s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) pos = amp+1 else pos = amp+1 end end -- encode naked <'s s = s:gsub("<([^a-zA-Z/?$!])", "<%1") s = s:gsub("<$", "<") -- what about >, nothing done in the original markdown source to handle them return s end -- Handles emphasis markers (* and _) in the text. function emphasis(text) for _, s in ipairs {"%*%*", "%_%_"} do text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "%1") text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "%1") end for _, s in ipairs {"%*", "%_"} do text = text:gsub(s .. "([^%s_])" .. s, "%1") text = text:gsub(s .. "([^%s_])" .. s, "%1") text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "%1") text = text:gsub(s .. "([^<>_]-[^<>_]-[^<>_]-)" .. s, "%1") end return text end -- Handles line break markers in the text. function line_breaks(text) return text:gsub(" +\n", "
        \n") end -- Perform all span level transforms. function span_transform(text) text = code_spans(text) text = escape_special_chars(text) text = images(text) text = anchors(text) text = auto_links(text) text = amps_and_angles(text) text = emphasis(text) text = line_breaks(text) return text end ---------------------------------------------------------------------- -- Markdown ---------------------------------------------------------------------- -- Cleanup the text by normalizing some possible variations to make further -- processing easier. function cleanup(text) -- Standardize line endings text = text:gsub("\r\n", "\n") -- DOS to UNIX text = text:gsub("\r", "\n") -- Mac to UNIX -- Convert all tabs to spaces text = detab(text) -- Strip lines with only spaces and tabs while true do local subs text, subs = text:gsub("\n[ \t]+\n", "\n\n") if subs == 0 then break end end return "\n" .. text .. "\n" end -- Strips link definitions from the text and stores the data in a lookup table. function strip_link_definitions(text) local linkdb = {} local function link_def(id, url, title) id = id:match("%[(.+)%]"):lower() linkdb[id] = linkdb[id] or {} linkdb[id].url = url or linkdb[id].url linkdb[id].title = title or linkdb[id].title return "" end local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*]+)>?[ \t]*" local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" text = text:gsub(def_title1, link_def) text = text:gsub(def_title2, link_def) text = text:gsub(def_title3, link_def) text = text:gsub(def_no_title, link_def) return text, linkdb end link_database = {} -- Main markdown processing function function markdown(text) init_hash(text) init_escape_table() text = cleanup(text) text = protect(text) text, link_database = strip_link_definitions(text) text = block_transform(text) text = unescape_special_chars(text) return text end ---------------------------------------------------------------------- -- End of module ---------------------------------------------------------------------- setfenv(1, _G) M.lock(M) -- Expose markdown function to the world markdown = M.markdown -- Class for parsing command-line options local OptionParser = {} OptionParser.__index = OptionParser -- Creates a new option parser function OptionParser:new() local o = {short = {}, long = {}} setmetatable(o, self) return o end -- Calls f() whenever a flag with specified short and long name is encountered function OptionParser:flag(short, long, f) local info = {type = "flag", f = f} if short then self.short[short] = info end if long then self.long[long] = info end end -- Calls f(param) whenever a parameter flag with specified short and long name is encountered function OptionParser:param(short, long, f) local info = {type = "param", f = f} if short then self.short[short] = info end if long then self.long[long] = info end end -- Calls f(v) for each non-flag argument function OptionParser:arg(f) self.arg = f end -- Runs the option parser for the specified set of arguments. Returns true if all arguments -- where successfully parsed and false otherwise. function OptionParser:run(args) local pos = 1 while pos <= #args do local arg = args[pos] if arg == "--" then for i=pos+1,#args do if self.arg then self.arg(args[i]) end return true end end if arg:match("^%-%-") then local info = self.long[arg:sub(3)] if not info then print("Unknown flag: " .. arg) return false end if info.type == "flag" then info.f() pos = pos + 1 else param = args[pos+1] if not param then print("No parameter for flag: " .. arg) return false end info.f(param) pos = pos+2 end elseif arg:match("^%-") then for i=2,arg:len() do local c = arg:sub(i,i) local info = self.short[c] if not info then print("Unknown flag: -" .. c) return false end if info.type == "flag" then info.f() else if i == arg:len() then param = args[pos+1] if not param then print("No parameter for flag: -" .. c) return false end info.f(param) pos = pos + 1 else param = arg:sub(i+1) info.f(param) end break end end pos = pos + 1 else if self.arg then self.arg(arg) end pos = pos + 1 end end return true end -- Handles the case when markdown is run from the command line local function run_command_line(arg) -- Generate output for input s given options local function run(s, options) s = markdown(s) if not options.wrap_header then return s end local header = "" if options.header then local f = io.open(options.header) or error("Could not open file: " .. options.header) header = f:read("*a") f:close() else header = [[ TITLE ]] local title = options.title or s:match("

        (.-)

        ") or s:match("

        (.-)

        ") or s:match("

        (.-)

        ") or "Untitled" header = header:gsub("TITLE", title) if options.inline_style then local style = "" local f = io.open(options.stylesheet) if f then style = f:read("*a") f:close() else error("Could not include style sheet " .. options.stylesheet .. ": File not found") end header = header:gsub('', "") else header = header:gsub("STYLESHEET", options.stylesheet) end header = header:gsub("CHARSET", options.charset) end local footer = "" if options.footer then local f = io.open(options.footer) or error("Could not open file: " .. options.footer) footer = f:read("*a") f:close() end return header .. s .. footer end -- Generate output path name from input path name given options. local function outpath(path, options) if options.append then return path .. ".html" end local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end return path .. ".html" end -- Default commandline options local options = { wrap_header = true, header = nil, footer = nil, charset = "utf-8", title = nil, stylesheet = "default.css", inline_style = false } local help = [[ Usage: markdown.lua [OPTION] [FILE] Runs the markdown text markup to HTML converter on each file specified on the command line. If no files are specified, runs on standard input. No header: -n, --no-wrap Don't wrap the output in ... tags. Custom header: -e, --header FILE Use content of FILE for header. -f, --footer FILE Use content of FILE for footer. Generated header: -c, --charset SET Specifies charset (default utf-8). -i, --title TITLE Specifies title (default from first

        tag). -s, --style STYLE Specifies style sheet file (default default.css). -l, --inline-style Include the style sheet file inline in the header. Generated files: -a, --append Append .html extension (instead of replacing). Other options: -h, --help Print this help text. -t, --test Run the unit tests. ]] local run_stdin = true local op = OptionParser:new() op:flag("n", "no-wrap", function () options.wrap_header = false end) op:param("e", "header", function (x) options.header = x end) op:param("f", "footer", function (x) options.footer = x end) op:param("c", "charset", function (x) options.charset = x end) op:param("i", "title", function(x) options.title = x end) op:param("s", "style", function(x) options.stylesheet = x end) op:flag("l", "inline-style", function(x) options.inline_style = true end) op:flag("a", "append", function() options.append = true end) op:flag("t", "test", function() local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") local f = io.open(n) if f then f:close() dofile(n) else error("Cannot find markdown-tests.lua") end run_stdin = false end) op:flag("h", "help", function() print(help) run_stdin = false end) op:arg(function(path) local file = io.open(path) or error("Could not open file: " .. path) local s = file:read("*a") file:close() s = run(s, options) file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) file:write(s) file:close() run_stdin = false end ) if not op:run(arg) then print(help) run_stdin = false end if run_stdin then local s = io.read("*a") s = run(s, options) io.write(s) end end -- If we are being run from the command-line, act accordingly if arg and arg[0]:find("markdown%.lua$") then run_command_line(arg) else return markdown end luakit-2.4.0/lib/modes.lua000066400000000000000000000305001475363222200153720ustar00rootroot00000000000000--- Default mode configuration for luakit. -- -- This module defines a core set of modes each luakit window can be in. -- Different modes recognize different keybindings. -- -- @module modes -- @author Aidan Holm -- @author Mason Larobina -- @copyright 2017 Aidan Holm -- @copyright 2010 Mason Larobina local _M = {} local window = require("window") local webview = require("webview") -- Table of modes and their callback hooks local modes = {} local lousy = require "lousy" local join = lousy.util.table.join local order = 0 --- Add a new mode table (optionally merges with original mode) -- @tparam string name The name of the mode. -- @tparam[opt] string desc The description of the mode. -- @tparam table mode A table that defines the mode. -- @tparam[opt] boolean replace `true` if any existing mode with the same name should -- be replaced, and `false` if the pre-existing mode should be extended. -- @default `false` _M.new_mode = function (name, desc, mode, replace) assert(string.match(name, "^[%w-_]+$"), "invalid mode name: " .. name) -- Detect optional description if type(desc) == "table" then desc, mode, replace = nil, desc, mode end local traceback = debug.traceback("Creation traceback:", 2) order = order + 1 modes[name] = join({ order = order, traceback = traceback }, (not replace and modes[name]) or {}, mode or {}, { name = name, desc = desc }) end --- Get a mode by name. -- @tparam string name The name of the mode to retrieve. -- @treturn table The mode table for the named mode. _M.get_mode = function(name) return modes[name] end --- Get all modes. -- @treturn table A clone of the full table of modes. _M.get_modes = function () return lousy.util.table.clone(modes) end -- Attach window & input bar signals for mode hooks window.add_signal("init", function (w) -- Calls the `enter` and `leave` mode hooks. w:add_signal("mode-changed", function (_, name, ...) local leave = (w.mode or {}).leave -- Get new modes functions/hooks/data local mode = assert(modes[name], "invalid mode: " .. name) -- Call last modes leave hook. if leave then leave(w) end -- Create w.mode object w.mode = mode -- Update window binds w:update_binds(name) -- Call new modes enter hook. if mode.enter then mode.enter(w, ...) end w.last_mode_entered = mode w:emit_signal("mode-entered", mode) end) local input = w.ibar.input -- Calls the changed hook on input widget changed. input:add_signal("changed", function () local changed = w.mode.changed -- the w:set_input() in normal mode's enter function would create a -- changed signal which would run before the next mode's enter -- function, usually causing a change back to normal mode before the -- next mode's enter function actually ran. -- here, we only run the changed callback if the current mode matches -- the last mode entered. if changed and w.last_mode_entered == w.mode then changed(w, input.text) end end) input:add_signal("property::position", function () local move_cursor = w.mode.move_cursor if move_cursor then move_cursor(w, input.position) end end) -- Calls the `activate` hook on input widget activate. input:add_signal("activate", function () local mode = w.mode if mode and mode.activate then local text = input.text -- Activates the mode. if mode.activate(w, text) == false then return end -- Prevents recording command history if in a private tab, or -- recording the `:priv-tabopen` command itself. if w.view and w.view.private then return end local hist = mode.history -- Check if last history is identical if hist and hist.items and hist.items[hist.len or -1] ~= text then table.insert(hist.items, text) -- Dump history local t = {} for k, v in pairs(modes) do if v.history then t[k] = v.history.items end end local f = io.open(luakit.data_dir .. "/command-history", "w") if f then f:write(lousy.pickle.pickle(t)) f:close() end end end end) end) -- Add mode related window methods window.methods.set_mode = lousy.mode.set local mget = lousy.mode.get window.methods.is_mode = function (w, name) return name == mget(w) end webview.add_signal("init", function (view) view.can_focus = false end) -- Setup normal mode _M.new_mode("normal", [[When luakit first starts you will find yourself in this mode.]], { enter = function (w) w:set_prompt() w:set_input() if w.view then w.view.can_focus = false end w.win:focus() end, }) _M.new_mode("all", "Special meta-mode in which the bindings for this mode are present in all modes.") -- Setup insert mode _M.new_mode("insert", [[When clicking on form fields luakit will enter the insert mode which allows you to enter text in form fields without accidentally triggering normal mode bindings.]], { enter = function (w) w:set_prompt("-- INSERT --") w:set_input() w.view.can_focus = true w.view:focus() end, -- Send key events to webview passthrough = true, }) _M.new_mode("passthrough", [[Luakit will pass every key event to the WebView until the user presses Escape.]], { enter = function (w) w:set_prompt("-- PASS THROUGH --") w:set_input() w.view.can_focus = true w.view:focus() end, -- Send key events to webview passthrough = true, -- Don't exit mode when clicking outside of form fields reset_on_focus = false, -- Don't exit mode on navigation reset_on_navigation = false, }) -- Setup command mode _M.new_mode("command", [[Enter commands.]], { enter = function (w) w:set_prompt() w:set_input(":") end, changed = function (w, text) -- Auto-exit command mode if user backspaces ":" in the input bar. if not string.match(text, "^:") then w:set_mode() end end, activate = function (w, text) w:set_mode() local cmd = string.sub(text, 2) if not string.find(cmd, "%S") then return end local success, match = xpcall( function () return w:match_cmd(cmd) end, function (err) w:error(debug.traceback(err, 3)) end) if success and not match then w:error(string.format("Not a browser command: %q", cmd)) end end, history = {maxlen = 50}, }) _M.new_mode("lua", [[Execute arbitrary Lua commands within the luakit environment.]], { enter = function (w) w:set_prompt(">") w:set_input("") end, activate = function (w, text) w:set_input("") local ret = assert(loadstring("return function(w) return "..text.." end"))()(w) if ret then print(ret) end end, history = {maxlen = 50}, }) --- Callback type for a bind action. -- @callback bind_action_cb -- @tparam table w The window table for the window on which the binding was -- activated. -- @tparam table opts Combined table of bind-time and invocation-time options. -- @tparam table bind_opts Table of invocation-time options. -- @treturn boolean `false` if bind matching should be continued, as if this -- action were not bound. Any other value stops bind matching, which is usually -- what you want. --- Add a set of binds to one or more modes. Any pre-existing binds with the -- same trigger will be removed automatically. -- -- # Example -- -- The following code snippet will rebind `Control-c` to copy text selected with -- the mouse, and the default binding for `Control-c` will be removed. -- -- modes.add_binds("normal", { -- { "", "Copy selected text.", function () -- luakit.selection.clipboard = luakit.selection.primary -- end}, -- }) -- -- # Bind format -- -- Every item in the `binds` array must be a table that defines a single binding -- between a trigger and an action. Each entry must have the following form: -- -- { trigger, description, action, options } -- -- - `trigger` is a string describing the combination of keys/modifiers/buttons -- that will trigger the associated action. -- - `description` is an optional string. Any description will show up in the -- introspector. -- - `action` is a function that will be called when the binding is -- activated, of type @ref{bind_action_cb}. -- - `options` is a table of bind-time options passed to the `action` callback. -- -- @tparam table|string mode The name of the mode, or an array of mode names. -- @tparam table binds An array of binds to add to each of the named modes. _M.add_binds = function (mode, binds) mode = type(mode) ~= "table" and {mode} or mode for _, name in ipairs(mode) do local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist") mdata.binds = mdata.binds or {} for _, m in ipairs(binds) do local bind, desc, action, opts = unpack(m) if type(desc) ~= "string" then desc, action, opts = nil, desc, action end if type(desc) == "string" or type(action) == "function" then -- Make ad-hoc action action = type(action) == "table" and lousy.util.table.clone(action) or { func = action } action.desc = desc end lousy.bind.add_bind(mdata.binds, bind, action, opts) end end end --- Remove a set of binds from one or more modes. -- -- # Example -- -- -- Disable extra zooming commands -- modes.remove_binds("normal", { "zi", "zo", "zz" }) -- -- -- Disable passthrough mode -- modes.remove_binds({"normal", "insert"}, { "" }) -- -- @tparam table|string mode The name of the mode, or an array of mode names. -- @tparam table binds An array of binds to remove from each of the named modes. _M.remove_binds = function (mode, binds) mode = type(mode) ~= "table" and {mode} or mode for _, name in ipairs(mode) do local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist") for _, bind in ipairs(binds) do lousy.bind.remove_bind(mdata.binds or {} , bind) end end end --- Bind an existing key or command to a new binding. -- -- # Example -- -- -- Add an additional zooming command binding -- modes.remap_binds("normal", { -- { "", "zi", true }, -- }) -- -- # Bind format -- -- Every item in the `binds` array must be a table that defines a single rebind -- from an existing trigger to a new one. Each entry must have the following form: -- -- { new, old, keep } -- -- - `new` is a string describing the combination of keys/modifiers/buttons -- that will trigger the associated action. -- - `old` is a string describing the previous trigger of the action. -- - `keep` is an optional argument that determines whether the existing binding -- should remain. Defaults to `false` if not present. -- -- @tparam table|string mode The name of the mode, or an array of mode names. -- @tparam table binds An array of binds to remap _M.remap_binds = function(mode, binds) mode = type(mode) ~= "table" and {mode} or mode for _, name in ipairs(mode) do local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist") for _, bind in ipairs(binds) do local new, old, keep = unpack(bind) lousy.bind.remap_bind(mdata.binds or {}, new, old, keep) end end end --- Add a set of commands to the built-in `command` mode. -- @tparam table|string mode The name of the mode, or an array of mode names. -- @tparam table binds An raray of binds to add to each of the named modes. _M.add_cmds = function (binds) for _, m in ipairs(binds) do local b = m[1] if b and b:match("^[^<^]") then for _, c in ipairs(lousy.util.string.split(b, ",%s+")) do assert(c:match("^:[%[%]%w%-!]+$"), "Bad command binding '" .. b .. "'") end end end _M.add_binds("command", binds) end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/newtab_chrome.lua000066400000000000000000000045161475363222200171100ustar00rootroot00000000000000--- New tab page for luakit. -- -- This module provides , the luakit new -- tab page. This page is opened by default when opening a new tab without -- specifying a URL to open. -- -- # Customization -- -- The easiest way to customize what is shown at -- is to create a HTML file at the -- path specified by `newtab_chrome.new_tab_file`. By default, this is the -- `newtab.html` file located in the luakit data directory. -- -- If this file exists, then its contents will be used to provide the new tab -- page. Otherwise, the value of `newtab_chrome.new_tab_src` is used. -- -- # Files and Directories -- -- - The default path for the new-tab file is `newtab.html`, located in the luakit data directory. -- -- @module newtab_chrome -- @author Aidan Holm -- @copyright 2016 Aidan Holm local chrome = require "chrome" local theme = require "theme" local luakit = require "luakit" local settings = require "settings" local _M = {} --- Path to a HTML file to use for the new tab page. --The default value is `$XDG_DATA_DIR/luakit/newtab.html`. -- @type string -- @readwrite _M.new_tab_file = luakit.data_dir .. "/newtab.html" --- HTML string to use for the new tab page, if no HTML file is specified. -- The default value produces a page with no content and a single solid -- background color. `theme.bg` is used as the background color. -- @type string -- @readwrite _M.new_tab_src = ([==[ New Tab ]==]):gsub("{bgcolor}", theme.bg) local function load_file_contents(file) if not file then return nil end local f = io.open(file, "rb") if not f then return nil end local content = f:read("*all") f:close() return content end chrome.add("newtab", function () return load_file_contents(_M.new_tab_file) or _M.new_tab_src end) luakit.idle_add(function () local undoclose = package.loaded.undoclose if not undoclose then return end undoclose.add_signal("save", function (view) local uri, hist = view.uri or "", view.history if uri:match("^luakit://newtab/?") and #hist.items == 1 then return false end end) end) require "window" settings.override_setting("window.new_tab_page", "luakit://newtab/") return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/noscript.lua000066400000000000000000000157131475363222200161350ustar00rootroot00000000000000--- NoScript plugin for luakit. -- -- This module provides a method of restricting web page access to JavaScript -- and plugins. -- -- This module provides keybindings for enabling/disabling either plugins or -- JavaScript for the current web page, as well as a status bar widget that -- indicates whether JavaScript is enabled for the current web page. -- -- @module noscript -- @copyright 2011 Mason Larobina local window = require("window") local webview = require("webview") local modes = require("modes") local add_binds = modes.add_binds local lousy = require("lousy") local sql_escape = lousy.util.sql_escape local theme = require("theme") local _M = {} --- Whether JavaScript should be enabled by default. -- @type boolean -- @readwrite -- @default `true` _M.enable_scripts = true --- Whether plugins should be enabled by default. -- @type boolean -- @readwrite -- @default `true` _M.enable_plugins = true local create_table = [[ CREATE TABLE IF NOT EXISTS by_domain ( id INTEGER PRIMARY KEY, domain TEXT, enable_scripts INTEGER, enable_plugins INTEGER );]] local db = sqlite3{ filename = luakit.data_dir .. "/noscript.db" } db:exec("PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;") db:exec(create_table) local function btoi(bool) return bool and 1 or 0 end local function itob(int) return tonumber(int) ~= 0 end local function get_domain(uri) uri = lousy.uri.parse(uri) -- uri parsing will fail on some URIs, e.g. "about:blank" return (uri and uri.host) and string.lower(uri.host) or nil end local function match_domain(domain) local rows = db:exec(string.format("SELECT * FROM by_domain " .. "WHERE domain == %s;", sql_escape(domain))) if rows[1] then return rows[1] end end local function update(id, field, value) db:exec(string.format("UPDATE by_domain SET %s = %d WHERE id == %d;", field, btoi(value), id)) end local function insert(domain, enable_scripts, enable_plugins) db:exec(string.format("INSERT INTO by_domain VALUES (NULL, %s, %d, %d);", sql_escape(domain), btoi(enable_scripts), btoi(enable_plugins))) end function webview.methods.toggle_scripts(view, w) local domain = get_domain(view.uri) local enable_scripts = _M.enable_scripts local row = match_domain(domain) if row then enable_scripts = itob(row.enable_scripts) update(row.id, "enable_scripts", not enable_scripts) else insert(domain, not enable_scripts, _M.enable_plugins) end w:notify(string.format("%sabled scripts for domain: %s", enable_scripts and "Dis" or "En", domain)) end function webview.methods.toggle_plugins(view, w) local domain = get_domain(view.uri) local enable_plugins = _M.enable_plugins local row = match_domain(domain) if row then enable_plugins = itob(row.enable_plugins) update(row.id, "enable_plugins", not enable_plugins) else insert(domain, _M.enable_scripts, not enable_plugins) end w:notify(string.format("%sabled plugins for domain: %s", enable_plugins and "Dis" or "En", domain)) end function webview.methods.toggle_remove(view, w) local domain = get_domain(view.uri) db:exec(string.format("DELETE FROM by_domain WHERE domain == %s;", sql_escape(domain))) w:notify("Removed rules for domain: " .. domain) end local function string_starts(a, b) return string.sub(a, 1, string.len(b)) == b end local function lookup_domain(uri) if not uri then uri = "" end local enable_scripts, enable_plugins = _M.enable_scripts, _M.enable_plugins local domain = get_domain(uri) -- Enable everything for local pages if string_starts(uri, "file://") then return true, true, "file://" end -- Look up this domain and all parent domains, returning the first match -- E.g. querying a.b.com will lookup a.b.com, then b.com, then com while domain do local row = match_domain(domain) if row then return itob(row.enable_scripts), itob(row.enable_plugins), row.domain end domain = string.match(domain, "%.(.+)") end return enable_scripts, enable_plugins, nil end -- NoScript indicator local view_noscript_state = setmetatable({}, { __mode = "k" }) local function noscript_indicator_update(v) local vns = view_noscript_state[v] local w = webview.window(v) if not vns or not w then return end local ns = w.sbar.r.noscript local es, matched_domain = vns.enable_scripts, vns.enable_scripts_domain local state = es and "enabled" or "disabled" if es then ns.text = "S" or "S" ns.fg = theme.trust_fg else ns.text = "S" ns.fg = theme.notrust_fg end if matched_domain == "override" then ns.tooltip = "JavaScript " .. state elseif matched_domain then ns.tooltip = "JavaScript " .. state .. ": URI matched domain '" .. matched_domain .. "'" else ns.tooltip = "JavaScript " .. state .. ": default setting" end end local noscript_ss = stylesheet{ source = [===[noscript { display: none !important; }]===] } window.add_signal("init", function (w) local r = w.sbar.r r.noscript = widget{type="label"} r.layout:pack(r.noscript) r.layout:reorder(r.noscript, 1) r.noscript.font = theme.font end) local update_webview_blocking = function (v) local es = v:emit_signal("enable-scripts") local ep = v:emit_signal("enable-plugins") local vns = { enable_scripts_domain = es and "override" or nil, enable_plugins_domain = ep and "override" or nil, } if es == nil or ep == nil then local s, p, matched_domain = lookup_domain(v.uri) if es == nil then es = s; vns.enable_scripts_domain = matched_domain end if ep == nil then ep = p; vns.enable_plugins_domain = matched_domain end end vns.enable_scripts = es vns.enable_plugins = ep v.enable_scripts = es v.enable_plugins = ep -- Update indicator view_noscript_state[v] = vns noscript_indicator_update(v) -- Workaround for https://github.com/aidanholm/luakit/issues/250 v.stylesheets[noscript_ss] = es end webview.add_signal("init", function (view) -- Update on new resource load view:add_signal("navigation-request", function (v, _, _) update_webview_blocking(v) end) -- Update on history navigation view:add_signal("load-status", function (v, status) if status == "committed" then update_webview_blocking(v) end end) view:add_signal("switched-page", function (v) noscript_indicator_update(v) end) end) add_binds("normal", { { "^,ts$", "Enable/disable JavaScript for the current domain.", function (w) w:toggle_scripts() end }, { "^,tp$", "Enable/disable plugins for the current domain.", function (w) w:toggle_plugins() end }, { "^,tr$", "Remove all previously added rules for the current domain.", function (w) w:toggle_remove() end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/open_editor.lua000066400000000000000000000044321475363222200165770ustar00rootroot00000000000000--- Edit the contents of text inputs in an external editor. -- -- This module allows you to edit the contents of the currently focused -- text input in your preferred text editor. The focused text input is -- disabled, and a text editor window will open with the current input -- contents. After you have finished editing, save the file and quit the -- editor; the text input will be enabled and its contents will be set -- to that of the saved file. -- -- @module open_editor local modes = require("modes") local editor = require("editor") local add_binds = modes.add_binds local _M = {} local function edit_externally(w) local time = os.time() local marker = "luakit_extedit_" .. time local file = luakit.cache_dir .. "/" .. marker .. ".txt" local function editor_callback() local f = io.open(file, "r") local s = f:read("*all") f:close() os.remove(file) -- Strip the string s = s:gsub("^%s*(.-)%s*$", "%1") -- Escape it but remove the quotes s = string.format("%q", s):sub(2, -2) -- lua escaped newlines (slash+newline) into js newlines (slash+n) s = s:gsub("\\\n", "\\n") w.view:eval_js(string.format([=[ var e = document.getElementsByClassName('%s'); if (1 == e.length && e[0].disabled) { e[0].focus(); e[0].value = "%s"; e[0].disabled = false; e[0].className = e[0].className.replace(/\b %s\b/,''); } ]=], marker, s, marker), { no_return = true }) end w.view:eval_js(string.format([=[ var e = document.activeElement; if (e && ('TEXTAREA' === e.tagName || 'text' === e.type)) { var s = e.value; e.className += " %s"; e.disabled = true; e.value = 'Editing externally...'; s; } else 'false'; ]=], marker, file), { callback = function(s) if "false" ~= s then local f = io.open(file, "w") f:write(s) f:flush() f:close() editor.edit(file, 1, editor_callback) end end }) end add_binds("insert", { { "", "Edit currently focused input in external editor.", edit_externally }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/proxy.lua000066400000000000000000000214671475363222200154600ustar00rootroot00000000000000--- Dynamic proxy settings. -- -- This module offers a simple and convenient user interface for using a proxy -- while browsing the web. Users can add entries for specific proxy addresses, -- and easily switch between using any of these proxies to redirect traffic. It -- is also possible to use the system proxy, or disable proxy use altogether. -- -- # Adding a new proxy entry -- -- To add a new proxy entry, use the `:proxy` command, with the name of the -- proxy and the web address of the proxy as arguments:`:proxy
        `. -- -- ## Example -- -- To add a proxy entry for a local proxy running on port 8000, run the -- following: -- -- :proxy proxy-name socks://localhost:8000 -- -- # Viewing and changing the current proxy -- -- It is currently easiest to view the current proxy by opening the proxy menu -- with the `:proxy` command. The current proxy will be shown in black text, while any -- inactive proxies will be shown in light gray text. -- -- @module proxy -- @copyright Piotr Husiatyński local lousy = require("lousy") local theme = lousy.theme.get() local window = require("window") local binds, modes = require("binds"), require("modes") local new_mode = require("modes").new_mode local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local _, minor = luakit.webkit_version:match("^(%d+)%.(%d+)%.") if tonumber(minor) < 16 then msg.error("proxy support in luakit requires WebKit2GTK 2.16 or later") msg.error("this version: %s", luakit.webkit_version) return {} end local _M = {} --- Module global variables local proxies_file = luakit.data_dir .. '/proxymenu' local proxies = {} local noproxy = { address = '' } local active = noproxy -- Helper function to update text in proxy indicator local update_proxy_indicator = function (w) local name = _M.get_active().name local proxyi = w.sbar.r.proxyi if name and name ~= "None" then local text = string.format("[%s]", name) if proxyi.text ~= text then proxyi.text = text end proxyi:show() else proxyi:hide() end end local update_proxy_indicators = function () for _, w in pairs(window.bywidget) do update_proxy_indicator(w) end end --- Get an ordered list of proxy names. -- @treturn table List of proxy names. function _M.get_names() return lousy.util.table.keys(proxies) end --- Get the address of proxy given by name. -- @tparam string name The name of a proxy. -- @treturn string The address of the proxy. function _M.get(name) return proxies[name] end --- Get the active proxy configuration. -- @treturn table The active proxy configuration. Two fields are present: -- `name` and `address`. function _M.get_active() return active end --- Load the proxies list from a file. -- @tparam string fd_name Custom proxy storage or `nil` to use default. function _M.load(fd_name) -- always add default entries -- they will be overwritten when loaded from the file proxies["None"] = "no_proxy" proxies["System"] = "default" -- load file, if it exists fd_name = fd_name or proxies_file if not os.exists(fd_name) then return end local strip = lousy.util.string.strip for line in io.lines(fd_name) do local status, name, address = string.match(line, "^(.)%s(.+)%s(.+)$") if address then name, address = strip(name), strip(address) if status == '*' then active = { address = address, name = name } end proxies[name] = address end end if active and active.address ~= '' then soup.proxy_uri = active.address update_proxy_indicators() end end --- Save the proxies list to a file. -- @tparam string fd_name Custom proxy storage or `nil` to use default. function _M.save(fd_name) local fd = io.open(fd_name or proxies_file, "w") for name, address in pairs(proxies) do if address ~= "" then local status = (active.name == name and '*') or ' ' fd:write(string.format("%s %s %s\n", status, name, address)) end end io.close(fd) end --- Add a new proxy server to current list. -- @tparam string name Proxy configuration name. -- @tparam string address Proxy server address. -- @tparam boolean save_file Do not save configuration if `false`. function _M.set(name, address, save_file) name = lousy.util.string.strip(name) if not string.match(name, "^([%w%p]+)$") then error("Invalid proxy name: " .. name) end proxies[name] = lousy.util.string.strip(address) if save_file ~= false then _M.save() end end --- Delete a proxy from the proxy list. -- @tparam string name Proxy server name. function _M.del(name) name = lousy.util.string.strip(name) if proxies[name] then -- if deleted proxy was the active one, turn proxy off if name == active.name then active = noproxy end proxies[name] = nil _M.save() end end --- Set given proxy to active. Return true on success, else false. -- @tparam string name Proxy configuration name or `nil` to unset proxy. function _M.set_active(name) if name then name = lousy.util.string.strip(name) if not proxies[name] then error("Unknown proxy: " .. name) end active = { name = name, address = proxies[name] } else active = noproxy end soup.proxy_uri = active.address _M.save() return true end -- Create a proxy indicator widget and add it to the status bar window.add_signal("init", function (w) local r = w.sbar.r r.proxyi = widget{type="label"} r.layout:pack(r.proxyi) r.layout:reorder(r.proxyi, 2) r.proxyi.fg = theme.proxyi_sbar_fg r.proxyi.font = theme.proxyi_sbar_font update_proxy_indicators() end) new_mode("proxymenu", { enter = function (w) local rows = {{ "Proxy Name", " Server address", title = true }} for _, name in ipairs(_M.get_names()) do local address = _M.get(name) table.insert(rows, { " " .. name, " " .. address, name = name, address = lousy.util.escape(address), }) end -- Color menu rows according to the currently used proxy local current_proxy = soup.proxy_uri local afg, ifg = theme.proxy_active_menu_fg, theme.proxy_inactive_menu_fg local abg, ibg = theme.proxy_active_menu_bg, theme.proxy_inactive_menu_bg for i=2,#rows do rows[i].fg = (rows[i].address == current_proxy) and afg or ifg rows[i].bg = (rows[i].address == current_proxy) and abg or ibg end w.menu:build(rows) w:notify("Use j/k to move, d delete, e edit, a add, Return activate.", false) end, leave = function (w) w.menu:hide() end, }) add_cmds({ { ":proxy", "Change the current proxy or add a new proxy entry.", function (w, o) local a = o.arg local params = lousy.util.string.split(a or '') if not a then w:set_mode("proxymenu") elseif #params == 2 then local name, address = unpack(params) _M.set(name, address) else w:error("Bad usage. Correct format :proxy or :proxy
        ") end end }, }) add_binds("proxymenu", lousy.util.table.join({ -- Select proxy { "", "Use the currently highlighted proxy.", function (w) local row = w.menu:get() if row and row.address then if row.name then _M.set_active(row.name) else soup.proxy_uri = row.address end w:set_mode() update_proxy_indicators() if row.name then w:notify(string.format("Using proxy: %s (%s)", row.name, row.address)) elseif row.address == "default" then w:notify("Using system default proxy.") else w:notify("Unset proxy.") end end end }, -- Delete proxy { "d", "Delete the currently highlighted proxy entry.", function (w) local row = w.menu:get() if row and row.name then _M.del(row.name) w.menu:del() end end }, -- Edit proxy { "e", "Edit the currently highlighted proxy entry.", function (w) local row = w.menu:get() if row and row.name then w:enter_cmd(string.format(":proxy %s %s", row.name, row.address)) end end }, -- New proxy { "a", "Begin adding a new proxy entry.", function (w) w:enter_cmd(":proxy ") end }, }, menu_binds)) -- Initialize module _M.load() return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/quickmarks.lua000066400000000000000000000245331475363222200164460ustar00rootroot00000000000000--- Vimperator-style quickmarking. -- -- Inspired by vimperator's quickmarks feature, this module allows you to -- associate up to sixty-two websites with a set of easy-to-type keybindings. -- Users can then type a three-keystroke command to open any of these websites -- in the current tab, a new tab, or a new window. -- -- # Adding a new quickmark -- -- You can mark any url by pressing `M{a-zA-Z0-9}`. This will save the url -- of the current page, creating a new shortcut or overwriting an existing -- one. -- -- Every quickmark mapping is saved in the `quickmarks` file in the luakit data -- directory, and is shared between multiple luakit instances. -- -- # Jumping to a marked url -- -- After adding a quickmark, you can open it in the current window with -- `go{a-zA-Z0-9}`, or in a new tab with `gn{a-zA-Z0-9}`. To list all -- quickmarks, run `:qmarks`. -- -- # Managing quickmarks -- -- As well as using the included quickmarks manager and various commands, you -- can directly edit the `quickmarks` file in the luakit data directory. -- -- # Files and Directories -- -- - The quickmarks file is called `quickmarks` and is in the luakit data -- directory. -- -- @module quickmarks -- @author Piotr Husiatyński -- @author Mason Larobina -- Get luakit environment local lousy = require("lousy") local window = require("window") local new_mode = require("modes").new_mode local binds, modes = require("binds"), require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local _M = {} local qmarks local quickmarks_file = luakit.data_dir .. '/quickmarks' local function check_token(token) assert(string.match(tostring(token), "^(%w)$"), "invalid token: " .. tostring(token)) return token end --- Load quick bookmarks from storage file into memory. -- @tparam string fd_name Bookmarks storage file path or `nil` to use default one. function _M.load(fd_name) if not qmarks then qmarks = {} end fd_name = fd_name or quickmarks_file if not os.exists(fd_name) then return end for line in io.lines(fd_name) do local token, uris = string.match(lousy.util.string.strip(line), "^(%w)%s+(.+)$") if token then qmarks [token] = lousy.util.string.split(uris, ",%s+") end end end --- Save quick bookmarks to file. -- @tparam string fd_name Bookmarks storage file path or `nil` to use default one. function _M.save(fd_name) -- Quickmarks init check if not qmarks then _M.load() end local fd = io.open(fd_name or quickmarks_file, "w") for _, token in ipairs(lousy.util.table.keys(qmarks )) do local uris = table.concat(qmarks [token], ", ") fd:write(string.format("%s %s\n", token, uris)) end io.close(fd) end --- Return URI related to given key or nil if does not exist. -- @tparam string token Quick bookmarks mapping token. -- @tparam boolean load_file Call `quickmark.load()` before retrieving the URI. function _M.get(token, load_file) -- Load quickmarks from other sessions if not qmarks or load_file ~= false then _M.load() end return qmarks[check_token(token)] end --- Return a list of all the tokens in the quickmarks table. function _M.get_tokens() if not qmarks then _M.load() end return lousy.util.table.keys(qmarks ) end --- Set new quick bookmarks mapping. -- @tparam string token The token under which given uris will be available. -- @tparam string|{string} uris List of locations to quickmark. -- @tparam boolean load_file Call `quickmark.load()` before adding the mapping. -- @tparam boolean save_file Call `quickmark.save()` after adding the mapping. function _M.set(token, uris, load_file, save_file) -- Load quickmarks from other sessions if not qmarks or load_file ~= false then _M.load() end -- Parse uris: "http://forum1.com, google.com, imdb some artist" if uris and type(uris) == "string" then uris = lousy.util.string.split(uris, ",%s+") elseif uris and type(uris) ~= "table" then error("invalid locations type: ", type(uris)) end qmarks[check_token(token)] = uris -- By default, setting new quickmark saves them to if save_file ~= false then _M.save() end end --- Delete a quickmark. -- @tparam string token The quickmark token. -- @tparam boolean load_file Call `quickmark.load()` before deletion. -- @tparam boolean save_file Call `quickmark.save()` after deletion. function _M.del(token, load_file, save_file) -- Load quickmarks from other sessions if not qmarks or load_file ~= false then _M.load() end qmarks[check_token(token)] = nil if save_file ~= false then _M.save() end end --- Delete all quickmarks. -- @tparam boolean save_file Call quickmark.save() function. function _M.delall(save_file) qmarks = {} if save_file ~= false then _M.save() end end local actions = { quickmark_delete = { desc = "Delete a quickmark or all quickmarks.", func = function (w, o) if o.bang then _M.delall() return end local a = o.arg if not a or a == "" then w:error("missing argument") return end -- Find and del all range specifiers string.gsub(a, "(%w%-%w)", function (range) range = "["..range.."]" for _, token in ipairs(_M.get_tokens()) do if string.match(token, range) then _M.del(token, false) end end end) -- Delete lone tokens string.gsub(a, "(%w)", function (token) _M.del(token, false) end) _M.save() end, }, } -- Add quickmarking binds to normal mode add_binds("normal", { { "^g[onw][a-zA-Z0-9]$", [[Jump to quickmark in current tab with `go{a-zA-Z0-9}`, `gn{a-zA-Z0-9}` to open in new tab and or `gw{a-zA-Z0-9}` to open a quickmark in a new window.]], function (w, o, m) local mode, token = string.match(o.buffer, "^g(.)(.)$") local uris = lousy.util.table.clone(_M.get(token) or {}) for c=1,m.count do if mode == "w" then window.new(uris) else for i, uri in ipairs(uris or {}) do if mode == "o" and c == 1 and i == 1 then w:navigate(uri) else w:new_tab(uri, {switch = i == 1}) end end end end end, {count=1} }, { "^M[a-zA-Z0-9]$", "Add quickmark for current URL.", function (w, o) local token = string.match(o.buffer, "^M(.)$") local uri = w.view.uri _M.set(token, {uri}) w:notify(string.format("Quickmarked %q: %s", token, uri)) end }, }) -- Add quickmarking commands add_cmds({ -- Quickmark add (`:qmark f http://forum1.com, forum2.com, imdb some artist`) { ":qma[rk]", "Add a quickmark.", function (w, o) local a = o.arg local token, uris = string.match(lousy.util.string.strip(a), "^(%w)%s+(.+)$") assert(token, "invalid token") uris = lousy.util.string.split(uris, ",%s+") _M.set(token, uris) w:notify(string.format("Quickmarked %q: %s", token, table.concat(uris, ", "))) end }, -- Quickmark edit (`:qmarkedit f` -> `:qmark f furi1, furi2, ..`) { ":qmarkedit, :qme", "Edit a quickmark.", function (w, o) local a = o.arg local token = lousy.util.string.strip(a) assert(#token == 1, "invalid token length: " .. token) local uris = _M.get(token) w:enter_cmd(string.format(":qmark %s %s", token, table.concat(uris or {}, ", "))) end }, -- Quickmark del (`:delqmarks b-p Aa z 4-9`) { ":delqm[arks]", actions.quickmark_delete }, -- View all quickmarks in an interactive menu { ":qmarks", "List all quickmarks.", function (w) w:set_mode("qmarklist") end }, }) -- Add mode to display all quickmarks in an interactive menu new_mode("qmarklist", { enter = function (w) local rows = {{ "Quickmarks", " URI(s)", title = true }} for _, qmark in ipairs(_M.get_tokens()) do local uris = lousy.util.escape(table.concat(_M.get(qmark, false), ", ")) table.insert(rows, { " " .. qmark, " " .. uris, qmark = qmark }) end w.menu:build(rows) w:notify("Use j/k to move, d delete, e edit, t tabopen, w winopen.", false) end, leave = function (w) w.menu:hide() end, }) -- Add additional binds to quickmarks menu mode add_binds("qmarklist", lousy.util.table.join({ -- Delete quickmark { "d", "Delete the currently highlighted quickmark entry.", function (w) local row = w.menu:get() if row and row.qmark then _M.del(row.qmark) w.menu:del() end end }, -- Edit quickmark { "e", "Edit the currently highlighted quickmark entry.", function (w) local row = w.menu:get() if row and row.qmark then local uris = _M.get(row.qmark) w:enter_cmd(string.format(":qmark %s %s", row.qmark, table.concat(uris or {}, ", "))) end end }, -- Open quickmark { "", "Open the currently highlighted quickmark entry in the current tab.", function (w) local row = w.menu:get() if row and row.qmark then for i, uri in ipairs(_M.get(row.qmark) or {}) do if i == 1 then w:navigate(uri) else w:new_tab(uri, { switch = false }) end end end end }, -- Open quickmark in new tab { "t", "Open the currently highlighted quickmark entry in a new tab.", function (w) local row = w.menu:get() if row and row.qmark then for _, uri in ipairs(_M.get(row.qmark) or {}) do w:new_tab(uri, { switch = false }) end end end }, -- Open quickmark in new window { "w", "Open the currently highlighted quickmark entry in a new window.", function (w) local row = w.menu:get() w:set_mode() if row and row.qmark then window.new(_M.get(row.qmark) or {}) end end }, }, menu_binds)) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/readline.lua000066400000000000000000000241071475363222200160540ustar00rootroot00000000000000--- Add readline bindings to the input bar. -- -- This module adds a set of readline-inspired bindings to the input bar. These -- bindings are not bound to any specific mode, but are automatically activated -- whenever the input bar has focus. -- -- @module readline -- @copyright 2017 Aidan Holm local window = require "window" local lousy = require "lousy" local prev_glyph = lousy.util.string.prev_glyph local next_glyph = lousy.util.string.next_glyph local _M = {} local yank_ring = "" local actions = { paste = { func = function (w) local str = luakit.selection.primary if not str then return end local i = w.ibar.input local text = i.text local pos = i.position local left, right = string.sub(text, 1, pos), string.sub(text, pos+1) i.text = left .. str .. right i.position = pos + #str end, desc = "Insert contents of primary selection at cursor position.", }, del_word = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if text and utf8.len(text) > 1 and pos > 1 then local left = string.sub(text, 2, utf8.offset(text, pos)) local right = string.sub(text, utf8.offset(text, pos + 1)) if not string.find(left, "%s") then left = "" elseif string.find(left, "%S+%s*$") then left = string.sub(left, 0, string.find(left, "%S+%s*$") - 1) elseif string.find(left, "%W+%s*$") then left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1) end i.text = string.sub(text, 1, 1) .. left .. right i.position = utf8.len(left) + 1 end end, desc = "Delete previous word.", }, del_word_backward = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if text and utf8.len(text) > 1 and pos > 1 then local right = string.sub(text, utf8.offset(text, pos + 1)) pos = utf8.offset(text, pos) - 1 while true do local new_pos, glyph = prev_glyph(text, pos) if not new_pos or (glyph:len() == 1 and not glyph:find("%w")) then break end pos = new_pos end local left = "" if pos then left = text:sub(2, pos) end i.text = text:sub(1, 1) .. left .. right i.position = utf8.len(left) + 1 end end, desc = "Delete word backward.", }, del_word_forward = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if text and utf8.len(text) > 1 and pos < utf8.len(text) then -- include current character local left = text:sub(1, utf8.offset(text, pos + 1) - 1) -- at least delete one character pos = utf8.offset(text, pos + 2) while true do local new_pos, glyph = next_glyph(text, pos) if not new_pos or (glyph:len() == 1 and not glyph:find("%w")) then break end pos = new_pos end local right if pos then right = text:sub(pos) else right = "" end i.text = left .. right i.position = utf8.len(left) end end, desc = "Delete word forward.", }, del_line = { func = function (w) local i = w.ibar.input if not string.match(i.text, "^[:/?]$") then yank_ring = string.sub(i.text, 2) i.text = string.sub(i.text, 1, 1) i.position = -1 end end, desc = "Delete until beginning of current line.", }, del_to_eol = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if not string.match(text, "^[:/?]$") then i.text = string.sub(text, 1, pos) i.position = pos end end, desc = "Delete to the end of current line.", }, del_backward_char = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if pos > 1 then i.text = string.sub(text, 0, pos - 1) .. string.sub(text, pos + 1) i.position = pos - 1 end end, desc = "Delete character to the left.", }, del_forward_char = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position i.text = string.sub(text, 0, pos) .. string.sub(text, pos + 2) i.position = pos end, desc = "Delete character to the right.", }, beg_line = { func = function (w) local i = w.ibar.input i.position = 1 end, desc = "Move cursor to beginning of current line.", }, end_line = { func = function (w) local i = w.ibar.input i.position = -1 end, desc = "Move cursor to end of current line.", }, forward_char = { func = function (w) local i = w.ibar.input i.position = i.position + 1 end, desc = "Move cursor forward one character.", }, backward_char = { func = function (w) local i = w.ibar.input local pos = i.position if pos > 1 then i.position = pos - 1 end end, desc = "Move cursor backward one character.", }, forward_word = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if text and utf8.len(text) > 1 then pos = pos + 1 local raw_pos = utf8.offset(text, pos + 1) while true do local glyph raw_pos, glyph = next_glyph(text, raw_pos) if not raw_pos or (glyph:len() == 1 and not glyph:find("%w")) then break end pos = pos + 1 end i.position = pos end end, desc = "Move cursor forward one word.", }, backward_word = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position if text and utf8.len(text) > 1 and pos > 1 then local raw_pos = utf8.offset(text, pos) - 1 while true do local glyph raw_pos, glyph = prev_glyph(text, raw_pos) pos = pos - 1 if not raw_pos or (glyph:len() == 1 and not glyph:find("%w")) then break end end if not pos then i.position = 1 else i.position = pos end end end, desc = "Move cursor backward one word.", }, yank_text = { func = function (w) local i = w.ibar.input local text = i.text local pos = i.position local left, right = string.sub(text, 1, pos), string.sub(text, pos+1) i.text = left .. yank_ring .. right i.position = pos + #yank_ring end, desc = "Yank the most recently killed text into the input bar, at the cursor.", }, } --- Table of bindings that are added to the input bar. -- @readwrite -- @type table _M.bindings = { { "", actions.paste , {} }, { "", actions.del_word , {} }, { "", actions.del_word_backward , {} }, { "", actions.del_word_forward , {} }, { "", actions.del_line , {} }, { "", actions.del_to_eol , {} }, { "", actions.del_backward_char , {} }, { "", actions.del_forward_char , {} }, { "", actions.beg_line , {} }, { "", actions.end_line , {} }, { "", actions.forward_char , {} }, { "", actions.backward_char , {} }, { "", actions.forward_word , {} }, { "", actions.backward_word , {} }, { "", actions.yank_text , {} }, } window.add_signal("init", function (w) w.ibar.input:add_signal("key-press", function (input, mods, key) local ww = assert(window.ancestor(input)) -- Unlikely, but just in case local success, match = xpcall( function () return lousy.bind.hit(ww, _M.bindings, mods, key, {}) end, function (err) w:error(debug.traceback(err, 2)) end) if success and match then return true end end) end) -- Check for old config/window.lua for k in pairs(actions) do k = k == "paste" and "insert_cmd" or k for wm in pairs(window.methods) do if k == wm then msg.warn("detected old window.lua: method '%s'", wm) msg.warn(" readline bindings have been moved to readline.lua") msg.warn(" you should remove this method from your config/window.lua") end end end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/referer_control_wm.lua000066400000000000000000000035111475363222200201620ustar00rootroot00000000000000--- Only send Referer header if coming from the same domain - web module. -- -- The Referer HTTP header is sent automatically to websites to inform them of -- the referring website; i.e. the website that you were just on. This allows -- website owners to see where web traffic is coming from, but can also be a -- privacy concern. -- -- To help mitigate this concern, this module prevents this information -- from being sent whenever you navigate between two different domains. -- For example, if you navigate from `https://example.com/test/` to -- `https://google.com`, no Referer hreader will be sent. If you navigate -- from `https://example.com/test/` to `https://example.com/`, however, the -- Referer header will be sent. This is because some websites depend on -- this functionality. -- -- *Note: the word 'referer' is intentionally misspelled for historic reasons.* -- -- # Usage -- -- As this is a web module, it will not function if loaded on the main UI Lua -- process through `require()`. Instead, it should be loaded with -- `require_web_module()`: -- -- require_web_module("referer_control_wm") -- -- @module referer_control_wm -- @copyright 2016 Aidan Holm local _M = {} local function domain_from_uri(uri) local domain = (uri and string.match(string.lower(uri), "^%a+://([^/]*)/?")) -- Strip leading www. www2. etc domain = string.match(domain or "", "^www%d?%.(.+)") or domain return domain or "" end luakit.add_signal("page-created", function(page) page:add_signal("send-request", function(p, _, headers) if not headers.Referer then return end if domain_from_uri(p.uri) ~= domain_from_uri(headers.Referer) then msg.verbose("Removing referer '%s'", headers.Referer) headers.Referer = nil end end) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/search.lua000066400000000000000000000134621475363222200155400ustar00rootroot00000000000000--- Search for a string in the current webview. -- -- This module allows you to search for a string of text in the currently -- visible web page. A history of search terms is kept while luakit is running. -- -- *Note: regular expressions are not supported.* -- -- @module search -- @copyright 2010 Mason Larobina local webview = require("webview") local new_mode = require("modes").new_mode local add_binds = require("modes").add_binds local _M = {} -- Add searching binds to normal mode add_binds("normal", { { "/", "Search for string on current page.", function (w) w:start_search("/") end }, { "?", "Reverse search for string on current page.", function (w) w:start_search("?") end }, { "n", "Find next search result.", function (w, m) for _=1,m.count do w:search(nil, true) if w.search_state.by_view[w.view].ret == false then break end end end, {count=1} }, { "N", "Find previous search result.", function (w, m) for _=1,m.count do w:search(nil, false) if w.search_state.by_view[w.view].ret == false then break end end end, {count=1} }, }) local function new_search_state() return { by_view = setmetatable({}, { __mode = "k" }) } end -- Setup search mode new_mode("search", { enter = function (w) -- Clear old search state w.search_state = new_search_state() w:set_prompt() w:set_input("/") end, leave = function (w) w:set_ibar_theme() -- Check if search was aborted and return to original position local s = w.search_state if s.marker then w:scroll(s.marker) s.marker = nil end end, changed = function (w, text) -- Check that the first character is '/' or '?' and update search if string.match(text, "^[?/]") then local prefix = string.sub(text, 1, 1) local search = string.sub(text, 2) w:search(search, (prefix == "/")) else w:clear_search() w:set_mode() end end, activate = function (w, text) w.search_state.marker = nil if text == "/" or text == "?" then w:clear_search() end w:set_mode() end, history = {maxlen = 50}, }) -- Add binds to search mode add_binds("search", { { "", "Select next search result.", function (w) w:search(w.search_state.last_search, true) end }, { "", "Select previous result.", function (w) w:search(w.search_state.last_search, false) end }, }) -- Add search functions to webview for k, m in pairs({ start_search = function (_, w, text) if string.match(text, "^[?/]") then w:set_mode("search") if not string.match(text, "^/$") then w:set_input(text) end else return error("invalid search term, must start with '?' or '/'") end end, search = function (view, w, text, forward, wrap) -- Get search state (or new state) if not w.search_state then w.search_state = new_search_state() end local s = w.search_state if not s.by_view[view] then s.by_view[view] = {} end -- Default values if forward == nil then forward = true end text = text or s.last_search or "" -- Check if wrapping should be performed if wrap == nil then if s.wrap ~= nil then wrap = s.wrap else wrap = true end end if text == "" then if w:is_mode("search") then return w:clear_search() else return w:notify("No search term specified") end end if not s.searched then -- Haven't searched before, save some state. s.forward = forward s.wrap = wrap local scroll = view.scroll s.marker = { x = scroll.x, y = scroll.y } end s.searched = true -- Invert direction if originally searching in reverse forward = (s.forward == forward) if text == s.by_view[view].last_search then if forward then view:search_next() else view:search_previous() end else s.by_view[view].search = text s.last_search = text view:search(text, text ~= string.lower(text), forward, wrap) end end, clear_search = function (view, w, clear_state) w:set_ibar_theme() view:clear_search() if clear_state ~= false then w.search_state = new_search_state() else w.search_state.searched = false w.search_state.last_search = nil end end, }) do webview.methods[k] = m end webview.add_signal("init", function (view) view:add_signal("found-text", function (v) local w = webview.window(v) w.search_state.by_view[v].ret = true w:set_ibar_theme() end) view:add_signal("failed-to-find-text", function (v) local w = webview.window(v) w.search_state.by_view[v].ret = false w:set_ibar_theme("error") if not w:is_mode("search") then w:error("not found: " .. w.search_state.last_search) end local s = w.search_state if s.marker then w:scroll(s.marker) end end) -- Clear start search marker on button press/release local clear_start_search_marker = function (v) local w = webview.window(v) if w.search_state then w.search_state.marker = nil end end view:add_signal("button-press", clear_start_search_marker) view:add_signal("button-release", clear_start_search_marker) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/select.lua000066400000000000000000000113451475363222200155500ustar00rootroot00000000000000--- Select a page element with a visual interface. -- -- This module allows you to change the style of hints used to hint elements for -- selection in `follow` mode, as well as other modes that use the visual hint -- overlay. -- -- # Using a custom character set for hint labels -- -- To customize the characters used in hint labels, you can -- specify a _label maker function_ by assigning a function to the -- @ref{label_maker} property. This label maker function composes -- several _label composer functions_ into a chain, which it returns. -- Then, when a hinting mode (such as @ref{follow} mode) is entered, -- this chain of functions is called to construct the labels. The label -- maker function itself is only called once. -- -- ## Default label maker -- -- To see how this works in practice, let's examine the default label maker -- function. `trim()`, `sort()`, `reverse()`, and `charset()` are all label -- composer functions, and all the label maker function does is chain them -- together and return the result. -- -- select.label_maker = function (s) -- return s.trim(s.sort(s.reverse(s.numbers()))) -- end -- -- Conceptually, `numbers()` produces produces an array of numerical hints: -- -- { "01", "02", "03", "04", "05", ... , "10", "11", "12", ... } -- -- Next, `reverse()` reverses each individual hint. This makes typing to -- match hints quicker: by moving the variation in hints from the last -- character to the first character, which is the first character typed -- when matching, we make the first character typed filter many more hints: -- -- { "10", "20", "30", "40", "50", ... , "01", "11", "21", ... } -- -- Next, `sort()` sorts the hints. This step doesn't affect matching speed, but -- makes the hints shown on a page appear more orderly: -- -- { "01", "10", "11", "20", "21", "30", "31", "40", "41", ... } -- -- Last, `trim()` will remove any unnecessary hint suffixes. For -- example, `"01"` is the only hint beginning with `0`; therefore, once -- the user has typed `0`, this is the only matchable hint, and the `1` -- contributes nothing: -- -- { "0", "10", "11", "20", "21", "30", "31", "40", "41", ... } -- -- This is the final list of hint labels that will be used for selection. -- -- # Label composer functions -- -- All label composer functions return a function that takes a single -- argument `n` and produces an array of `n` hints. The nature of the -- hints will vary based on the arguments provided to the composer -- function. For example, the function returned by `charset()` will -- use the provided set of characters to generate its hints, while the -- function returned by `sort()` will first call the label composer -- function passed to `sort()` to obtain a set of hints to sort. -- -- ## Available functions -- -- These label composer functions produce an initial generator function. -- -- - `charset(str)`: Generates hints using the characters in `str`. Non-latin -- characters are supported. -- - `numbers()`: Generates hints using numbers. This is equivalent to -- calling `charset()` with the parameter `"0123456789"`. -- - `interleave(left, right)`: Similar to `charset()`, this generates -- hints that alternate between letters of the `left` and `right` strings. It is -- mostly useful for alternating between letters on the left and right sides of -- one's keyboard, as this makes hints easier to type quickly. -- -- These label composer functions accept a single generator function, and -- return another function. This allows them to be chained. -- -- - `reverse(func)`: Reverses the letters of each hint generated by `func()`. -- - `sort(func)`: Sorts the hints generated by `func()`. -- - `trim(func)`: Trims extra letters off the hints generated by -- `func()`. Specifically, if a prefix of any hint is not a prefix of any other -- hint, then the hint is shortened to that prefix. -- -- @module select -- @copyright 2017 Aidan Holm local _M = {} local wrapped = { label_maker = nil } --- @property label_maker -- Function that specifies how to generate hint labels. -- -- This function is executed on the web process, with a custom environment that -- provides access to the label composer functions. -- @readwrite -- @type function local wm = require_web_module("select_wm") luakit.add_signal("web-extension-created", function (view) if wrapped.label_maker then wm:emit_signal(view, "set_label_maker", wrapped.label_maker) end end) local mt = { __index = wrapped, __newindex = function (_, k, v) assert(type(v) == "function", "property 'label_maker' must be a function") if k == "label_maker" then wrapped.label_maker = v wm:emit_signal("set_label_maker", v) end end, } return setmetatable(_M, mt) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/select_wm.lua000066400000000000000000000464671475363222200162700ustar00rootroot00000000000000--- Select a page element with a visual interface. -- -- This web module allows other Lua modules to select page elements with the -- same interface as that used by the follow mode plugin: a visual overlay is -- shown that allows the users to type to filter visible hints. For example, -- this module is used by the `formfiller` module when selecting a form to add. -- -- @module select_wm -- @copyright 2017 Aidan Holm local ceil, floor, max = math.ceil, math.floor, math.max local _M = {} local ui = ipc_channel("select_wm") local has_client_rects_api = tonumber(luakit.webkit_version:match("^2%.(%d+)%.")) > 16 -- Label making -- Calculates the minimum number of characters needed in a hint given a -- charset of a certain length (I.e. the base) local function max_hint_len(size, base) local len = 0 if base == 1 then return size end while size > 0 do size, len = floor(size / base), len + 1 end return len end -- Reverse a UTF8 string: multibyte sequences are reversed twice local function utf8_rev (s) s = s:gsub(utf8.charpattern, function (ch) return #ch > 1 and ch:reverse() end) return s:reverse() end local function charset(seq, size) local base, digits, labels = utf8.len(seq), {}, {} for ch in seq:gmatch(utf8.charpattern) do digits[#digits+1] = ch end local maxlen = max_hint_len(size, base) for n = 1, size do local t, i, j, d = {}, 1, n repeat d, n = (n % base) + 1, floor(n / base) rawset(t, i, rawget(digits, d)) i = i + 1 until n == 0 rawset(labels, j, string.rep(digits[1], maxlen-i+1) .. utf8_rev(table.concat(t, ""))) end return labels end -- Different hint label styles local label_styles = { charset = function (seq) assert(type(seq) == "string" and #seq > 0, "invalid sequence") return function (size) return charset(seq, size) end end, numbers = function () return function (size) return charset("0123456789", size) end end, -- Interleave style interleave = function (left, right) assert(type(left) == "string" and type(right) == "string", "left and right parameters must be strings") assert(#left > 1 or #right > 1, "either left or right parameters' length must be greater than 1") local cmap = {} for ch in (left..right):gmatch(utf8.charpattern) do if cmap[ch] then error("duplicate characters %s in hint strings %s, %s", ch, left, right) else cmap[ch] = 1 end end return function (size) local function allstrings(n, t, k, s) k, s = k or 1, s or {} if k > n then coroutine.yield(table.concat(s)) else for i = 1, #t do s[k] = t[i] allstrings(n, t, k+1, s) end end end local function permute(n, t) return coroutine.wrap(allstrings), n, t end -- calculate the hinting length local hint_len = 1 while true do local lo, hi = floor(hint_len/2), ceil(hint_len/2) if (#left)^lo * (#right)^hi + (#left)^hi * (#right)^lo >= size then break end hint_len = hint_len + 1 end local tleft, tright = {}, {} left:gsub(utf8.charpattern, function(c) table.insert(tleft, c) end) right:gsub(utf8.charpattern, function(c) table.insert(tright, c) end) local labels = {} local lo, hi = floor(hint_len/2), ceil(hint_len/2) for a in permute(hi, tleft) do for b in permute(lo, tright) do rawset(labels, size, a:gsub('()('..utf8.charpattern..')', function(p, c) return c..b:sub(p, p+#c-1) end)) size = size - 1 if size == 0 then return labels end end end for a in permute(hi, tright) do for b in permute(lo, tleft) do rawset(labels, size, a:gsub('()('..utf8.charpattern..')', function(p, c) return c..b:sub(p, p+#c-1) end)) size = size - 1 if size == 0 then return labels end end end return labels end end, -- Chainable style: sorts labels sort = function (make_labels) return function (size) local labels = make_labels(size) table.sort(labels) return labels end end, -- Chainable style: reverses label strings reverse = function (make_labels) return function (size) local labels = make_labels(size) for i = 1, #labels do rawset(labels, i, utf8_rev(rawget(labels, i))) end return labels end end, trim = function (make_labels) return function (size) local labels = make_labels(size) local P = {} for _, l in ipairs(labels) do local p = l:gsub(utf8.charpattern.."$", "") if #p > 0 then P[p] = (P[p] or 0) + 1 end end for p, count in pairs(P) do if count == 1 then for i, l in ipairs(labels) do if l:sub(1, #p) == p then labels[i] = p end end end end return labels end end, } -- Default label style local label_maker do local s = label_styles label_maker = s.trim(s.sort(s.interleave("12345", "67890"))) end local function bounding_boxes_intersect(a, b) if a.x + a.w < b.x then return false end if b.x + b.w < a.x then return false end if a.y + a.h < b.y then return false end if b.y + b.h < a.y then return false end return true end local function get_element_bb_if_visible(element, wbb, client_rects) -- Find the element bounding box local r if has_client_rects_api and not element.first_child then r = element:client_rects() for i=#r,1,-1 do if r[i].width == 0 or r[i].height == 0 then table.remove(r, i) end end if #r == 0 then return nil end r = r[1] else r = client_rects(element) or element.rect end local rbb = { x = wbb.x + r.left, y = wbb.y + r.top, w = r.width, h = r.height, } if rbb.w == 0 or rbb.h == 0 then return nil end local style = element.style local display = style.display local visibility = style.visibility if display == 'none' or visibility == 'hidden' then return nil end -- Clip bounding box! if display == "inline" then local parent = element.parent local pd = parent.style.display if pd == "block" or pd == "inline-block" then local w = parent.rect.width w = w - (r.left - parent.rect.left) if rbb.w > w then rbb.w = w end end end if not bounding_boxes_intersect(wbb, rbb) then return nil end -- If a link element contains one image, use the image dimensions if element.tag_name == "A" then local first = element.first_child if first and first.tag_name == "IMG" and not first.next_sibling then return get_element_bb_if_visible(first, wbb, client_rects) or rbb end end return rbb end local function frame_find_hints(client_rects, frame, elements) local hints = {} if type(elements) == "string" then elements = frame.body:query(elements) else local elems = {} for _, e in ipairs(elements) do if e.owner_document == frame.doc then elems[#elems + 1] = e end end elements = elems end -- Find the visible bounding box local w = frame.doc.window local wbb = { x = w.scroll_x, y = w.scroll_y, w = w.inner_width, h = w.inner_height, } for _, element in ipairs(elements) do local rbb = get_element_bb_if_visible(element,wbb, client_rects) if rbb then local text = "" if element.type ~= "password" then text = element.text_content if text == "" then text = element.value or "" end end if text == "" then text = element.attr.placeholder or "" end hints[#hints+1] = { elem = element, bb = rbb, text = text } end end return hints end local function sort_hints_top_left(a, b) local dtop = a.bb.y - b.bb.y if dtop ~= 0 then return dtop < 0 else return a.bb.x - b.bb.x < 0 end end local function make_labels(num) return label_maker(num) end local function find_frames(root_frame) if not root_frame.body then return {} end local subframes = root_frame.body:query("frame, iframe") local frames = { root_frame } -- For each frame/iframe element, recurse for _, frame in ipairs(subframes) do local f = { doc = frame.document, body = frame.document.body } local s = find_frames(f) for _, sf in ipairs(s) do frames[#frames + 1] = sf end end return frames end local page_states = {} local function init_frame(frame, stylesheet) assert(frame.doc) assert(frame.body) frame.overlay = frame.doc:create_element("div", { id = "luakit_select_overlay" }) frame.stylesheet = frame.doc:create_element("style", { id = "luakit_select_stylesheet" }, stylesheet) frame.body.parent:append(frame.overlay) frame.body.parent:append(frame.stylesheet) end local function cleanup_frame(frame) if frame.overlay then frame.overlay:remove() frame.overlay = nil end if frame.stylesheet then frame.stylesheet:remove() frame.stylesheet = nil end end local function hint_matches(hint, hint_pat, text_pat) if hint_pat ~= nil and string.find(hint.label, hint_pat) then return true end if text_pat ~= nil and string.find(hint.text, text_pat) then return true end return false end local function filter(state, hint_pat, text_pat) state.num_visible_hints = 0 for _, hint in pairs(state.hints) do local old_hidden = hint.hidden hint.hidden = not hint_matches(hint, hint_pat, text_pat) if not hint.hidden then state.num_visible_hints = state.num_visible_hints + 1 end if not old_hidden and hint.hidden then -- Save old style, set new style to "display: none" hint.overlay_style = hint.overlay_elem.attr.style hint.label_style = hint.label_elem.attr.style hint.overlay_elem.attr.style = "display: none;" hint.label_elem.attr.style = "display: none;" elseif old_hidden and not hint.hidden then -- Restore saved style hint.overlay_elem.attr.style = hint.overlay_style hint.label_elem.attr.style = hint.label_style end end end local function focus(state, step) local last = state.focused local index local function sign(n) return n > 0 and 1 or n < 0 and -1 or 0 end if state.num_visible_hints == 0 then return end -- Advance index to the first non-hidden item if step == 0 then index = last and last or 1 while state.hints[index].hidden do index = index + 1 if index > #state.hints then index = 1 end end if index == last then return end end -- Which hint to focus? if step ~= 0 and last then index = last while step ~= 0 do repeat index = index + sign(step) if index < 1 then index = #state.hints end if index > #state.hints then index = 1 end until not state.hints[index].hidden step = step - sign(step) end end local new_hint = state.hints[index] -- Save and update class for the new hint new_hint.orig_class = new_hint.overlay_elem.attr.class new_hint.overlay_elem.attr.class = new_hint.orig_class .. " hint_selected" -- Restore the original class for the old hint if last then local old_hint = state.hints[last] old_hint.overlay_elem.attr.class = old_hint.orig_class old_hint.orig_class = nil end state.focused = index return new_hint end --- Enter element selection mode on a web page. -- -- The web page must not already be in element selection mode. -- -- @tparam page page The web page in which to enter element selection. -- @tparam string|{dom_element} elements A selector to filter elements, or an array of elements. -- @tparam string stylesheet The stylesheet to apply. -- @tparam boolean ignore_case `true` if text case should be ignored. -- @treturn {...} Table with data for the currently focused hint. -- @treturn number The number of currently visible hints. function _M.enter(page, elements, stylesheet, ignore_case) assert(type(page) == "page") assert(type(elements) == "string" or type(elements) == "table") assert(type(stylesheet) == "string") local page_id = page.id assert(page_states[page_id] == nil) local root = page.document local root_frame = { doc = root, body = root.body } local state = {} page_states[page_id] = state state.frames = find_frames(root_frame) state.focused = nil state.hints = {} state.ignore_case = ignore_case or false local client_rects = page:wrap_js([=[ var rects = element.getClientRects(); if (rects.length == 0) return undefined; var rect = { "top": rects[0].top, "bottom": rects[0].bottom, "left": rects[0].left, "right": rects[0].right, }; for (var i = 1; i < rects.length; i++) { rect.top = Math.min(rect.top, rects[i].top); rect.bottom = Math.max(rect.bottom, rects[i].bottom); rect.left = Math.min(rect.left, rects[i].left); rect.right = Math.max(rect.right, rects[i].right); } rect.width = rect.right - rect.left; rect.height = rect.bottom - rect.top; return rect; ]=], {"element"}) -- Find all hints in the viewport for _, frame in ipairs(state.frames) do -- Set up the frame, and find hints init_frame(frame, stylesheet) frame.hints = frame_find_hints(client_rects, frame, elements) -- Build an array of all hints for _, hint in ipairs(frame.hints) do state.hints[#state.hints+1] = hint end end -- Sort them by on-screen position, and assign labels local labels = make_labels(#state.hints) assert(#state.hints == #labels) table.sort(state.hints, sort_hints_top_left) for i, hint in ipairs(state.hints) do hint.label = labels[i] end for _, frame in ipairs(state.frames) do local fwr = frame.doc.window local fsx, fsy = fwr.scroll_x, fwr.scroll_y for _, hint in ipairs(frame.hints) do -- Append hint elements to overlay local e = hint.elem local r = hint.bb local overlay_style = string.format("left: %dpx; top: %dpx; width: %dpx; height: %dpx;", r.x, r.y, r.w, r.h) local label_style = string.format("left: %dpx; top: %dpx;", max(r.x-10, fsx), max(r.y-10, fsy), r.w, r.h) local overlay_class = "hint_overlay hint_overlay_" .. e.tag_name local label_class = "hint_label hint_label_" .. e.tag_name hint.overlay_elem = frame.doc:create_element("span", {class = overlay_class, style = overlay_style}) hint.label_elem = frame.doc:create_element("span", {class = label_class, style = label_style}, hint.label) frame.overlay:append(hint.overlay_elem) frame.overlay:append(hint.label_elem) end end for _, frame in ipairs(state.frames) do frame.doc:add_signal("destroy", function () cleanup_frame(frame) end) end filter(state, "", "") return focus(state, 0), state.num_visible_hints end --- Leave element selection mode on a web page. -- -- The web page must be in element selection mode. -- -- @tparam page|number page The web page (or the web page id) in which to -- leave element selection. function _M.leave(page) if type(page) == "page" then page = page.id end assert(type(page) == "number") local state = page_states[page] if not state then return end for _, frame in ipairs(state.frames) do cleanup_frame(frame) end page_states[page] = nil end --- Update the element selection interface when user selection text changes. -- -- The web page must be in element selection mode. -- -- @tparam page page The web page. -- @tparam string hint_pat The hint pattern filter. -- @tparam string text_pat The text pattern filter. -- @tparam string text The full text. -- @treturn table The currently focused hint. -- @treturn number The number of currently visible hints. function _M.changed(page, hint_pat, text_pat, text) assert(type(page) == "page") assert(hint_pat == nil or type(hint_pat) == "string") assert(text_pat == nil or type(text_pat) == "string") assert(type(text) == "string") local state = assert(page_states[page.id]) if state.ignore_case then local convert = function(pat) if pat == nil then return nil end local converter = function (ch) return '[' .. string.upper(ch) .. string.lower(ch) .. ']' end return string.gsub(pat, '(%a)', converter) end hint_pat = convert(hint_pat) text_pat = convert(text_pat) end filter(state, hint_pat, text_pat) return focus(state, 0), state.num_visible_hints end --- Update the element selection interface when the user moves the focus. -- -- The web page must be in element selection mode. -- -- @usage -- -- function handle_next (page) -- select_wm.focus(page, 1) -- end -- function handle_prev (page) -- select_wm.focus(page, -1) -- end -- -- @tparam page page The web page. -- @tparam number step Relative number of tags to shift focus by. -- @treturn table The currently focused hint. -- @treturn number The number of currently visible hints. function _M.focus(page, step) assert(type(page) == "page") assert(type(step) == "number") local state = assert(page_states[page.id]) return focus(state, step), state.num_visible_hints end --- Get the current state of element hints on a web page. -- -- The web page must be in element selection mode. -- -- @tparam page page The web page. -- @treturn table The current hint state for `page`. function _M.hints(page) assert(type(page) == "page") local state = assert(page_states[page.id]) return state.hints end --- Get the currently focused element hint on a web page. -- -- The web page must be in element selection mode. -- -- @tparam page page The web page. -- @treturn table The currently focused hint. function _M.focused_hint(page) assert(type(page) == "page") local state = assert(page_states[page.id]) return state.hints[state.focused] end ui:add_signal("set_label_maker", function (_, _, f) setfenv(f, label_styles) label_maker = f(label_styles) end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/session.lua000066400000000000000000000177241475363222200157630ustar00rootroot00000000000000--- Session saving / loading functions. -- -- This module allows you to save your current session when quitting -- luakit, and then restore it again the next time you open luakit. -- -- This module also provides a Lua API to allow other modules to save data to -- the session file and restore it when reopening Luakit. -- -- @module session -- @copyright 2010 Mason Larobina local window = require("window") local webview = require("webview") local lousy = require("lousy") local pickle = lousy.pickle local settings = require("settings") local _M = {} lousy.signal.setup(_M, true) --- Path to session file. -- @type string -- @readwrite _M.session_file = luakit.data_dir .. "/session" --- Path to crash recovery session file. -- @type string -- @readwrite _M.recovery_file = luakit.data_dir .. "/recovery_session" --- Save the current session state to a file. -- -- If no file is specified, the path specified by @ref{session_file} is used. -- -- @tparam[opt] string file The file path in which to save the session state. _M.save = function (file) if not file then file = _M.session_file end local state = {} local wins = lousy.util.table.values(window.bywidget) -- Save tabs from all windows for _, w in ipairs(wins) do local current = w.tabs:current() state[w] = { open = {} } for ti, tab in ipairs(w.tabs.children) do if tab.private then table.insert(state[w].open, { private = true }) else table.insert(state[w].open, { ti = ti, current = (current == ti), uri = tab.uri, session_state = tab.session_state }) end end end _M.emit_signal("save", state) for _, ws in pairs(state) do for i=#ws.open,1,-1 do if ws.open[i].private then table.remove(ws.open, i) end end end -- Convert state keys from w to an index local istate = {} for i, w in ipairs(wins) do assert(type(state[w]) == "table") istate[i] = state[w] end state = istate if #state > 0 then local tempfile = file .. ".new" local fh = io.open(tempfile, "wb") fh:write(pickle.pickle(state)) io.close(fh) os.rename(tempfile, file) else os.remove(file) end end --- Load session state from a file, and optionally delete it. -- -- The session state is *not* restored. This function only loads the state into -- a table and returns it. -- -- If no file is specified, the path specified by @ref{session_file} is used. -- -- If `delete` is not `false` and the session file is not the recovery file, -- then the session file is backed up into the recovery file. -- -- @tparam[opt] boolean delete Whether to delete the file after the session is -- loaded. -- @tparam[opt] string file The file path from which to load the session state. _M.load = function (delete, file) if not file then file = _M.session_file end if not os.exists(file) then return {} end -- Read file local fh = io.open(file, "rb") local state = pickle.unpickle(fh:read("*all")) io.close(fh) -- Backup file on idle (i.e. only if config loads successfully) if delete ~= false and file ~= _M.recovery_file then luakit.idle_add(function() os.rename(file, _M.recovery_file) end) end return state end -- Spawn windows from saved session and return the last window local restore_file = function (file, delete) local ok, wins = pcall(_M.load, delete, file) if not ok or #wins == 0 then return end local state = {} -- Spawn windows local w for _, win in ipairs(wins) do w = nil for _, item in ipairs(win.open) do local v if not w then w = window.new({settings.get_setting("window.new_tab_page")}) v = w.view else v = w:new_tab(settings.get_setting("window.new_tab_page"), { switch = item.current }) end -- Block the tab load, then set its location webview.modify_load_block(v, "session-restore", true) webview.set_location(v, { session_state = item.session_state, uri = item.uri }) local function unblock(vv) webview.modify_load_block(vv, "session-restore", false) vv:remove_signal("switched-page", unblock) end v:add_signal("switched-page", unblock) end -- Convert state keys from index to w table if w then state[w] = win end end _M.emit_signal("restore", state) return w end --- Restore the session state, optionally deleting the session file. -- -- This will first attempt to restore the session saved at @ref{session_file}. If -- that does not succeed, the session saved at `recovery_file` will be loaded. -- -- If `delete` is not `false`, then the loaded session file is deleted. -- -- @tparam[opt] boolean delete Whether to delete the file after the session is -- restored. -- @treturn[1] table The window table for the last window created. -- @treturn[2] nil If no session could be loaded, `nil` is returned. _M.restore = function(delete) return restore_file(_M.session_file, delete) or restore_file(_M.recovery_file, delete) end local session_dirty = true local recovery_save_timer settings.register_settings({ ["session.recovery_save_interval"] = { type = "number", default = 30, validator = function (v) return tonumber(v) >= 0 end, desc = [[ The minimum time to wait in seconds after a browsing action before saving the recovery session. Must be non-negative. ]], }, }) settings.add_signal("setting-changed", function (e) if e.key == "session.recovery_save_interval" then recovery_save_timer.interval = e.value*1000 end end) recovery_save_timer = timer{ interval = settings.get_setting("session.recovery_save_interval")*1000 } -- Save current window session helper window.methods.save_session = function () if not session_dirty then return end session_dirty = false _M.save(_M.session_file) end local function start_timeout() -- Restart the timer if recovery_save_timer.started then recovery_save_timer:stop() end recovery_save_timer:start() session_dirty = true end recovery_save_timer:add_signal("timeout", function () recovery_save_timer:stop() _M.save(_M.recovery_file) end) window.add_signal("init", function (w) w.win:add_signal("destroy", function () -- Hack: should add a luakit shutdown hook... local num_windows = #lousy.util.table.values(window.bywidget) -- Remove the recovery session on a successful exit if num_windows == 0 and os.exists(_M.recovery_file) then os.remove(_M.recovery_file) end end) w:add_signal("close", function () if #lousy.util.table.values(window.bywidget) > 1 then start_timeout() return end if not settings.get_setting("session.always_save") then return end if w.tabs:count() == 0 then return end -- window.close_with_last_tab... w:save_session() end) w.tabs:add_signal("page-reordered", function () start_timeout() end) end) webview.add_signal("init", function (view) -- Save session state after page navigation view:add_signal("load-status", function (_, status) if status == "committed" then start_timeout() end end) -- Save session state after switching page (session includes current tab) view:add_signal("switched-page", function () start_timeout() end) end) settings.register_settings({ ["session.always_save"] = { type = "boolean", default = false, desc = [[ Whether the current browsing session should always be saved just before luakit is exited. ]], }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/settings.lua000066400000000000000000000401431475363222200161270ustar00rootroot00000000000000--- Centralized settings system. -- -- The `settings` module provides a central place to access and modify settings -- for all of luakit's modules. -- -- @module settings -- @author Aidan Holm -- @copyright 2017 Aidan Holm local lousy = require("lousy") local _M = {} lousy.signal.setup(_M, true) local settings_list = {} local settings_groups local S = { domain = { [""] = {}, }, -- keyed by domain, then by setting name source = { [""] = {}, }, -- can be default, persisted, config, or a module name view_overrides = setmetatable({}, { __mode = "k" }), } local persisted_settings do local ok ok, persisted_settings = pcall(function () local path = luakit.data_dir .. "/settings" return lousy.pickle.unpickle(lousy.load(path)) end) if not ok then persisted_settings = { domain = {}, } end -- move .global to .domain[""] local pgs = persisted_settings.domain[""] or {} persisted_settings.domain[""] = pgs if persisted_settings.global then for sn, v in pairs(persisted_settings.global) do pgs[sn] = v end persisted_settings.global = nil end end local function validate_settings_path (k) assert(type(k) == "string", "invalid settings path type: " .. type(k)) local parts = lousy.util.string.split(k, "%.") assert(#parts >= 2, "settings path must have at least two sections") for _, part in ipairs(parts) do assert(part ~= "on" and part:match("^[%w_]+$"), "invalid settings path component '" .. k .."'") end end local function get_settings_path_type (k) if not settings_groups then settings_groups = {} for path in pairs(settings_list) do local parts = lousy.util.string.split(path, "%.") table.remove(parts) local gk for _, part in ipairs(parts) do gk = (gk and (gk..".") or "") .. part settings_groups[gk] = true end end end if settings_groups[k] then return "group" end if settings_list[k] then return "value" end end --- Register a table of settings. -- Entries in the table of settings to register should be keyed by the setting -- path string. -- @tparam {[string]=table} settings The table of settings to register. -- -- ## Parameter table format -- -- Each value elements of the `settings` argument is in the following format: -- -- { type, default } -- -- * `type`: _string_, the type of the setting value. When a container, use -- `conatiner_name:inner_type` (the colon is the important part). Example for -- an array of strings: `"array:string"` -- * `default`: _`type`_, the default setting value when it isn't set manually _M.register_settings = function (list) assert(type(list) == "table") -- Hack: certain tests rely on re-requiring modules, but registered settings -- aren't tied to the lifetime of a module, so prevent repeat-registers failing if package.loaded["tests.lib"] then for k in pairs(list) do settings_list[k] = nil end end for k, s in pairs(list) do validate_settings_path(k) assert(type(s) == "table", "setting '"..k.."' not a table") assert(not settings_list[k], "setting '"..k.."' already registered") assert(type(s.type) == "string", "setting '"..k.."' missing type") end -- Set default or load from persisted_settings local clone = lousy.util.table.clone for k, s in pairs(list) do settings_list[k] = s S.domain[""][k] = s.type:find(":") and clone(s.default) or s.default S.source[""][k] = "default" for domain, settings in pairs(persisted_settings.domain) do if settings[k] ~= nil then if S.domain[domain] == nil then S.domain[domain] = {} S.source[domain] = {} end local v = settings[k] S.domain[domain][k] = type(v) == "table" and clone(v) or v S.source[domain][k] = "persisted" end end end settings_groups = nil end local function setting_validate_new_kv_pair (meta, k, v) local ktype, vtype = meta.type:match("^(.*):(.*)$") for kk, vv in pairs(v) do if ktype ~= "" and type(kk) ~= ktype then error(string.format("Wrong type for setting '%s' key: expected %s, got %s", k, ktype, type(kk))) end if vtype ~= "" and type(vv) ~= vtype then error(string.format("Wrong type for setting '%s' value (key %s): expected %s, got %s", k, kk, vtype, type(vv))) end end end local function setting_validate_new_value (section, k, v) section = section or "" local meta = assert(settings_list[k], "bad setting " .. k) if meta.domain_specific == true and not section then error(string.format("Setting '%s' is domain-specific", k)) elseif meta.domain_specific == false and section ~= "" then error(string.format("Setting '%s' cannot be domain-specific", k)) end if meta.type == "enum" then if not meta.options[v] then local opts = table.concat(lousy.util.table.keys(meta.options), ", ") error(string.format("Wrong type for setting '%s': expected one of %s", k, opts)) end elseif not meta.type:find(":") and type(v) ~= meta.type then error(string.format("Wrong type for setting '%s': expected %s, got %s", k, meta.type, type(v))) elseif meta.type:find(":") then setting_validate_new_kv_pair(meta, k, v) end if meta.type == "number" then if (meta.min and v < meta.min) or (meta.max and v > meta.max) then local range = "[" .. tostring(meta.min or "") .. ".." .. tostring(meta.max or "") .. "]" error(string.format("Value outside accepted range %s for setting '%s': %s", range, k, v)) end end if meta.validator and not meta.validator(v) then error(string.format("Invalid value for setting '%s'", k)) end end local function S_get(domain, key) domain = domain or "" local tree = S.domain[domain] or {} return tree[key] ~= nil and tree[key] or nil end local function get_overriding_module(domain, sn) local om = S.source[domain][sn] if om ~= "persisted" and om ~= "config" and om ~= "default" then return om end end local function S_set(domain, key, val, persist) domain = domain or "" setting_validate_new_value(domain, key, val) local function set(root, d, k, v) local tree = root.domain[d] or {} root.domain[d] = tree tree[k] = v end if persist then set(persisted_settings, domain, key, val) local fh = io.open(luakit.data_dir .. "/settings", "wb") fh:write(lousy.pickle.pickle(persisted_settings)) io.close(fh) end S.source[domain] = S.source[domain] or {} local source = S.source[domain][key] if get_overriding_module(domain, key) then return end if persist and source == "config" then return end set(S, domain, key, val) S.source[domain][key] = persist and "persisted" or "config" _M.emit_signal("setting-changed", { key = key, value = val, domain = domain, }) end local function S_set_table(domain, sn, key, val, persist) assert(not domain, "unimplemented") assert(not persist, "unimplemented") domain = domain or "" local meta = assert(settings_list[sn], "bad setting name "..sn) assert(meta.type:find(":"), sn .. " isn't a table setting") setting_validate_new_kv_pair(meta, sn, { [key]=val }) if persist then local tbl = persisted_settings[domain][sn] tbl[key] = val local fh = io.open(luakit.data_dir .. "/settings", "wb") fh:write(lousy.pickle.pickle(persisted_settings)) io.close(fh) end local source = S.source[domain][sn] if get_overriding_module(domain, sn) then return end if persist and source == "config" then return end local tbl = S.domain[domain][sn] tbl[key] = val S.source[domain][sn] = persist and "persisted" or "config" _M.emit_signal("setting-changed", { key = sn, value = val, domain = domain, }) end local function new_settings_table_node(domain, sn) domain = domain or "" return setmetatable({}, { __index = function (_, k) local tbl = (S.domain[domain] or {})[sn] or {} return tbl[k] end, __newindex = function (_, k, v) S_set_table(nil, sn, k, v) end, __metatable = false, }) end local function S_overwrite_table(domain, k, v) domain = domain or "" local tree, tbl = S.domain[domain] or {}, {} S.domain[domain] = tree tree[k] = tbl -- TODO: add validation for tables for kk, vv in pairs(v) do tbl[kk] = vv end end local uri_domain_cache = {} --- Retrieve the value of a setting for a webview. -- -- This function considers, in order: -- -- 1. any view-specific overrides -- 2. the setting's domain-specific values -- 3. the setting's non-domain-specific value -- 4. the setting's default value -- -- The settings key must be a valid settings key. -- @tparam widget view The webview. -- @tparam string key The key of the setting to retrieve. -- @return The value of the setting. _M.get_setting_for_view = function (view, key) assert(type(view) == "widget" and view.type == "webview") -- view-specific overrides local tree, uri = S.view_overrides[view], view.uri if tree and tree[key] then return tree[key] end -- domain-specific values if uri ~= uri_domain_cache.uri then uri_domain_cache.uri = uri uri_domain_cache.domains = lousy.uri.domains_from_uri(uri) end local domains = uri_domain_cache.domains for _, domain in ipairs(domains) do local value = (S.domain[domain] or {})[key] if value ~= nil then return value, domain end end -- non-domain-specific / default value return S.domain[""][key] end --- Add or remove a view-specific override for a setting. -- Passing `nil` as the `value` will clear any override. -- -- The settings key must be a valid settings key. -- @tparam widget view The webview. -- @tparam string key The key of the setting to override. -- @return The new value of the setting override. _M.override_setting_for_view = function (view, key, value) assert(type(view) == "widget" and view.type == "webview") local vo = S.view_overrides vo[view] = vo[view] or {} vo[view][key] = value end --- Add an override for a setting. -- -- The settings key must be a valid settings key. -- @tparam string key The key of the setting to override. -- @param The value of the setting override. _M.override_setting = function (key, value) local mod = debug.getinfo(2, "S").short_src:gsub(".*/", ""):gsub("%.lua$","") local override = get_overriding_module("", key) if override and override ~= mod then error("already overriden by '"..override.."'") end S.source[""][key] = "default" S_set(nil, key, value) S.source[""][key] = mod end --- Retrieve the value of a setting, whether it's explicitly set or the default. -- -- This does not take into account any domain-specific values. -- -- The settings key must be a valid settings key. -- @tparam string key The key of the setting to retrieve. -- @return The value of the setting. _M.get_setting = function (key) return S_get(nil, key) end --- Assign a value to a setting. Values assigned in this way are persisted to -- disk, and automatically set when luakit starts. -- -- The settings key must be a valid settings key. -- @tparam string key The key of the setting to retrieve. -- @param value The new value of the setting. -- @tparam table opts Table of options. Currently the only valid field is -- `domain`, which allows setting a domain-specific setting value. _M.set_setting = function (key, value, opts) opts = opts or {} S_set(opts.domain, key, value, true) end --- Retrieve information about all registered settings and their values. -- @treturn table A table of records, one for each setting. _M.get_settings = function () local ret = {} for k, meta in pairs(settings_list) do local value, src = _M.get_setting(k), S.source[""][k] if meta.type:find(":") then value = lousy.util.table.clone(value) end ret[k] = { type = meta.type, desc = meta.desc, value = value, src = src, options = meta.options, formatter = meta.formatter, } end return ret end local new_settings_node, root local function new_domain_node() local meta = { __metatable = false, subnodes = {} } meta.__index = function (_, k) if meta.subnodes[k] then return meta.subnodes[k] end assert(type(k) == "string" and #k > 0, "invalid domain name") if k == "all" then msg.warn("settings.on[\"all\"].foo is deprecated: instead, use settings.foo") return root end meta.subnodes[k] = new_settings_node(nil, k) return meta.subnodes[k] end meta.__newindex = function () error("cannot assign a value to a settings group") end return setmetatable({}, meta) end new_settings_node = function (prefix, section) assert(section ~= "") local meta = { __metatable = false, subnodes = {}, section = section } if not prefix and not section then -- True root node generates on[] subnode meta.subnodes.on = new_domain_node() end meta.__index = function (_, k) if meta.subnodes[k] then return meta.subnodes[k] end local full_path = (prefix and (prefix..".") or "") .. k local type = get_settings_path_type(full_path) if type == "value" then local getter = settings_list[full_path].type:find(":") and new_settings_table_node or S_get return (getter)(meta.section, full_path) end if type == "group" then meta.subnodes[k] = new_settings_node(full_path, meta.section) return meta.subnodes[k] end end meta.__newindex = function (_, k, v) local full_path = (prefix and (prefix..".") or "") .. k local type = get_settings_path_type(full_path) if string.match(full_path, "zoom_text_only") or string.match(full_path, "enable_offline_web_application_cache") or string.match(full_path, "load-icons-ignoring-image-load-setting") then msg.verbose("Ignoring setting: %s", full_path) return end if type == "value" then local setter = settings_list[full_path].type:find(":") and S_overwrite_table or S_set setter(meta.section, full_path, v) elseif type == "group" then error("cannot assign a value to a settings group") else error("cannot assign a value to invalid setting path '"..full_path.."'") end end return setmetatable({}, meta) end local migration_warnings = {} --- Migration helper function. -- @deprecated should be used only for existing code. _M.add_migration_warning = function (k, v) if #migration_warnings == 0 then table.insert(migration_warnings, "Globals.lua is deprecated, and will be removed in the next release!") table.insert(migration_warnings, "To migrate, add the following to your rc.lua:") table.insert(migration_warnings, "") luakit.idle_add(function () table.insert(migration_warnings, "") table.insert(migration_warnings, "Warnings have only been printed for settings with non-default values") msg.warn("%s", table.concat(migration_warnings, "\n")) end) end if type(v) == "string" then v = string.format("%q", v) end table.insert(migration_warnings, string.format(" settings.%s = %s", k, v)) end --- Migration helper function. -- @deprecated should be used only for existing code. _M.migrate_global = function (sk, gk) local globals = package.loaded.globals or {} if globals[gk] and (globals[gk] ~= settings_list[sk].default) then _M.add_migration_warning(sk, globals[gk]) S_set(nil, sk, globals[gk]) end end root = new_settings_node() return setmetatable(_M, { __index = root, __newindex = root }) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/settings_chrome.lua000066400000000000000000000224421475363222200174660ustar00rootroot00000000000000--- Luakit settings viewer. -- -- This module supplies the chrome page, which shows all -- settings and their values, and allows adjusting setting values. -- -- @module settings_chrome -- @author Aidan Holm -- @copyright 2017 Aidan Holm local chrome = require "chrome" local modes = require "modes" local settings = require "settings" local markdown = require "markdown" local lousy = require "lousy" local _M = {} --- HTML template for luakit settings chrome page content. -- @type string -- @readwrite _M.html_template = [==[ Luakit settings
        {content}
        ]==] local settings_chrome_JS = [=[ function on_change (event) { let i = event.target; if (!i.matches(".setting input")) return; let root = i.closest(".setting"); let type = root.dataset.type; let key = root.querySelector(".title").innerHTML; let value; if (type == "boolean") { value = i.checked; let span = root.querySelector(".input > label > span"); span.dataset.value = value; span.innerHTML = value ? "Enabled" : "Disabled"; } else value = i.value; set_setting(key, value, type).then(function(error) { root.classList.toggle("has-error", error) root.querySelector(".error-message").innerHTML = error; }); } function on_click (event) { let btn = event.target; if (!btn.matches("td.tbl_row_actions > a")) return; event.preventDefault(); } document.body.addEventListener("input", on_change) document.body.addEventListener("change", on_change) document.body.addEventListener("click", on_click) ]=] --- CSS applied to the settings chrome page. -- @type string -- @readwrite _M.html_style = [===[ #settings, table.input { width: 100%; border-collapse: collapse; } .setting > td { padding: 0.7rem 1rem; } .setting > td.input { -webkit-user-select: none; } .content-margin { padding-left: 0; padding-right: 0; } .setting { padding: 1rem; } .setting .title { font-family: monospace; font-size: 1.3rem; margin-bottom: 0.5em; } .setting .desc { font-size: 0.9rem; } .setting input { padding: 0.3em; } .setting.has-error input { border: 1px solid #f34; background: #fdd; } .setting.has-error .tooltip { display: block; opacity: 1.0; } /* ensures that the gradient connects between */ #settings { background: url('data:image/gif;base64,R0lGODlhHAAcAPAAAPb29vr6+iH5BAAAAAAALAAAA \ AAcABwAAAI+DI6Zwe2vInrUSVnzjblu1VHfElrjUZpn2pwoa7hwvMIuMN+5zt54z0v5bMHSEChD1 \ oTF0JGZhC6Nzc6zVAAAOw=='); } .setting:not(.disabled) { background: white; } .setting:hover { background: #f6f6f6; } .setting.disabled { background: transparent; } .tooltip { background: #121215; color: #f66; font-size: 0.8rem; line-height: 1; padding: 0.6em 0.75rem; border-radius: 4px; position: absolute; display: none; opacity: 0; transition: opacity 0.2s; white-space: nowrap; box-shadow: 0 0 0.2rem black; z-index: 1000; -webkit-backface-visibility: hidden; right: 0; top: 50%; margin-top: -1rem; height: 2rem; } .tooltip::before { content: ' '; display: block; background: inherit; width: 10px; height: 10px; position: absolute; transform: rotate(45deg); right: -3px; top: 50%; margin-top: -5px; } .setting label { display: block; } table.input td { font-family: monospace; padding: 0.1rem 0.3rem; } table.input th { padding: 0.3rem; font-size: 0.9rem; font-weight: normal; text-align: left; padding-bottom: 0.3rem; border-bottom: 1px solid #777; } .boolean > input { margin-left: 0; } .boolean > span { font-weight: bold; } .boolean > span[data-value=true] { color: #799D6A; } .boolean > span[data-value=false] { color: #CF6A4C; } ]===] local function build_settings_entry_table_html(meta) local rows = {} for k, v in pairs(meta.value) do rows[#rows+1] = { key = k, value = v } end table.sort(rows, function (a, b) return a.key < b.key end) local formatter = meta.formatter or function (t, k) return { key = lousy.util.escape(tostring(k)), value = lousy.util.escape(tostring(t[k])), } end local rows_html = "" for _, row in ipairs(rows) do local row_html = [==[ {key}{value} ]==] local subs = formatter(meta.value, row.key) rows_html = rows_html .. row_html:gsub("{(%w+)}", subs) end return ([==[ {rows}
        KeyValue
        ]==]):gsub("{(%w+)}", { rows = rows_html:gsub("%%","%%%%") } ) end local build_settings_entry_html = function (meta) local settings_entry_fmt = [==[
        {key}
        {desc}
        Error: {input} ]==] local settings_table_entry_fmt = [==[
        {key}
        {desc}
        Error: {input} ]==] local desc = meta.desc or "No description." desc = desc:gsub("^\n*", ""):gsub("[\n ]+$","") local fl = #(desc:match("^( +)") or "\n") - 1 desc = ("\n" .. desc):gsub("\n" .. string.rep(" ", fl), "\n"):sub(2) meta.desc = markdown(desc) local disabled_attr = (meta.src ~= "persisted" and meta.src ~= "default") and "disabled" or "" local input if meta.type == "boolean" then local fmt = ([==[ ]==]) input = fmt:gsub("{(%w+)}", { checked = meta.value and "checked=true" or "", text = meta.value and "Enabled" or "Disabled", value = meta.value and "true" or "false", }) elseif meta.type == "enum" then input = "" for k, opt in pairs(meta.options) do local tmpl = [==[]==] input = input .. tmpl:gsub("{(%w+)}", { name = meta.key, value = k, label = opt.label or k, checked = (meta.value == k) and "checked=true " or "", }) end elseif meta.type:find(":") then input = build_settings_entry_table_html(meta) else input = [==[]==] end local fmt = meta.type:find(":") and settings_table_entry_fmt or settings_entry_fmt return fmt:gsub("{input}", input):gsub("{(%w+)}", { disabled = disabled_attr, type = meta.type, key = meta.key, desc = meta.desc, value = tostring(meta.value), }) end chrome.add("settings", function () local rows, sm = {}, {} for k, meta in pairs(settings.get_settings()) do meta.key = k sm[#sm+1] = meta end table.sort(sm, function (a, b) return a.key < b.key end) for i, meta in ipairs(sm) do rows[i] = build_settings_entry_html(meta) end local html_subs = { title = _M.html_page_title, style = chrome.stylesheet .. _M.html_style, content = table.concat(rows, "\n"), script = settings_chrome_JS, } local html = string.gsub(_M.html_template, "{(%w+)}", html_subs) return html end, nil, { set_setting = function (_, key, value, type) if type == "number" then value = tonumber(value) if not value then return "Not a number!" end end local ok, err = pcall(settings.set_setting, key, value) if not ok then err = err:gsub("^.-: ", "") local range_err = err:match("Value outside accepted range (%[[%d%.]+%]) ") if range_err then return "value outside accepted range " .. range_err end return err end end, }) modes.add_cmds({ { ":settings", "Open in a new tab.", function (w) w:new_tab("luakit://settings/") end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/styles.lua000066400000000000000000000371661475363222200156250ustar00rootroot00000000000000--- User stylesheets. -- -- This module provides support for Mozilla-format user stylesheets, as a -- replacement for the old `domain_props`-based `user_stylesheet_uri` method -- (which is no longer supported). User stylesheets from https://userstyles.org -- are supported, giving access to a wide variety of already-made themes. -- -- User stylesheets are automatically detected and loaded when luakit starts up. -- In addition, user stylesheets can be enabled/disabled instantly, without -- refreshing the web pages they affect, and it is possible to reload external -- changes to stylesheets into luakit, without restarting the browser. -- -- # Adding user stylesheets -- -- 1. Ensure the @ref{styles} module is enabled in your `rc.lua`. -- 2. Locate the @ref{styles} sub-directory within luakit's data storage directory. -- Normally, this is located at `~/.local/share/luakit/styles/`. Create the -- directory if it does not already exist. -- 3. Move any CSS rules to a new file within that directory. In order for the -- @ref{styles} module to load the stylesheet, the filename must end in `.css`. -- 4. Make sure you specify which sites your stylesheet should apply to. The way to -- do this is to use `@-moz-document` rules. The Stylish wiki page [Applying styles to specific sites -- ](https://github.com/stylish-userstyles/stylish/wiki/Applying-styles-to-specific-sites) may be helpful. -- 5. Run `:styles-reload` to detect new stylesheet files and reload any changes to -- existing stylesheet files; it isn't necessary to restart luakit. -- -- # Using the styles menu -- -- To open the styles menu, run the command `:styles-list`. Here you can -- enable/disable stylesheets, open stylesheets in your text editor, and view -- which stylesheets are active. -- -- If a stylesheet is disabled for all pages, its state will be listed as -- "Disabled". If a stylesheet is enabled for all pages, but does not apply to -- the current page, its state will be listed as "Enabled". If a stylesheet is -- enbaled for all pages _and_ it applies to the current page, its state will be -- listed as "Active". -- -- @module styles -- @copyright 2017 Aidan Holm local window = require("window") local webview = require("webview") local lousy = require("lousy") local lfs = require("lfs") local editor = require("editor") local binds, modes = require("binds"), require("modes") local new_mode = require("modes").new_mode local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local _M = {} local styles_dir = luakit.data_dir .. "/styles/" local stylesheets = {} local db = sqlite3{ filename = luakit.data_dir .. "/styles.db" } db:exec("PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;") local query_create = db:compile [[ CREATE TABLE IF NOT EXISTS by_file ( id INTEGER PRIMARY KEY, file TEXT, enabled INTEGER );]] query_create:exec() local query_insert = db:compile [[ INSERT INTO by_file VALUES (NULL, ?, ?) ]] local query_update = db:compile [[ UPDATE by_file SET enabled = ? WHERE id == ? ]] local query_select = db:compile [[ SELECT * FROM by_file WHERE file == ? ]] local function db_get(file) assert(file) local rows = query_select:exec{file} return (rows[1] and rows[1].enabled or 1) ~= 0 end local function db_set(file, enabled) assert(file) local rows = query_select:exec{file} if rows[1] then query_update:exec{enabled, rows[1].id} else query_insert:exec{file, enabled} end end -- Routines to extract an array of domain names from a URI local function domain_from_uri(uri) if not uri or uri == "" then return nil end uri = assert(lousy.uri.parse(uri), "invalid uri") -- Return the scheme for non-http/https URIs if uri.scheme ~= "http" and uri.scheme ~= "https" then return uri.scheme else return string.lower(uri.host) end end local function domains_from_uri(uri) local domain = domain_from_uri(uri) local domains = { } while domain do domains[#domains + 1] = domain domain = string.match(domain, "%.(.+)") end return domains end -- Routines to re-apply all stylesheets to a given webview local function update_stylesheet_application(view, domains, stylesheet, enabled) for _, part in ipairs(stylesheet.parts) do local match = false for _, w in ipairs(part.when) do match = match or (w[1] == "url" and w[2] == view.uri) match = match or (w[1] == "url-prefix" and view.uri:find(w[2],1,true) == 1) match = match or (w[1] == "regexp" and w[2]:match(view.uri)) if w[1] == "domain" then for _, domain in ipairs(domains) do match = match or (w[2] == domain) end end end match = match and stylesheet.enabled and enabled view.stylesheets[part.ss] = match end end -- Routines to update the stylesheet menu local function update_stylesheet_applications(v) local enabled = v:emit_signal("enable-styles") enabled = enabled ~= false and true local domains = domains_from_uri(v.uri) for _, s in ipairs(stylesheets or {}) do update_stylesheet_application(v, domains, s, enabled ~= false ) end end local function describe_stylesheet_affected_pages(stylesheet) local affects = {} for _, part in ipairs(stylesheet.parts) do for _, w in ipairs(part.when) do local w2 = w[1] == "regexp" and w[2].pattern:gsub("\\/", "/") or w[2] local desc = w[1] .. " " .. w2 if not lousy.util.table.hasitem(affects, desc) then table.insert(affects, desc) end end end return table.concat(affects, ", ") end local menu_row_for_stylesheet = function (w, stylesheet) local theme = lousy.theme.get() local title = stylesheet.file local view = w.view -- Determine whether stylesheet is active for the current view local enabled, active = stylesheet.enabled, false if enabled then for _, part in ipairs(stylesheet.parts) do active = active or view.stylesheets[part.ss] end end local affects = describe_stylesheet_affected_pages(stylesheet) -- Determine state label and row colours local state, fg, bg if not enabled then state, fg, bg = "Disabled", theme.menu_disabled_fg, theme.menu_disabled_bg elseif not active then state, fg, bg = "Enabled", theme.menu_enabled_fg, theme.menu_enabled_bg else state, fg, bg = "Active", theme.menu_active_fg, theme.menu_active_bg end return { title, state, affects, stylesheet = stylesheet, fg = fg, bg = bg } end -- Routines to build and update stylesheet menus per-window local stylesheets_menu_rows = setmetatable({}, { __mode = "k" }) local function create_stylesheet_menu_for_w(w) local rows = {{ "Stylesheets", "State", "Affects", title = true }} local groups = { Disabled = {}, Enabled = {}, Active = {}, } for _, stylesheet in ipairs(stylesheets) do local row = menu_row_for_stylesheet(w, stylesheet) table.insert(groups[row[2]], row) end rows = lousy.util.table.join(rows, groups.Active, groups.Enabled, groups.Disabled) w.menu:build(rows) stylesheets_menu_rows[w] = rows end local function update_stylesheet_menu_for_w(w) local rows = assert(stylesheets_menu_rows[w]) for i=2,#rows do rows[i] = menu_row_for_stylesheet(w, rows[i].stylesheet) end w.menu:update() end local function update_stylesheet_menus() -- Update any windows in styles-list mode for _, w in pairs(window.bywidget) do if w:is_mode("styles-list") then update_stylesheet_menu_for_w(w) end end end local function update_all_stylesheet_applications() -- Update page appearances for _, ww in pairs(window.bywidget) do for _, v in pairs(ww.tabs.children) do update_stylesheet_applications(v) end end update_stylesheet_menus() end webview.add_signal("init", function (view) view:add_signal("stylesheet", function (v) if not view.uri:match("^view%-source:") then update_stylesheet_applications(v) end end) end) -- Routines to parse the @-moz-document format into CSS chunks local parse_moz_document_subrule = function (file) local word, param, i word, i = file:match("^%s*([%w-]+)%s*()") file = file:sub(i) param, i = file:match("(%b())()") file = file:sub(i) param = param:match("^%(['\"]?(.-)['\"]?%)$") return file, word, param end local parse_moz_document_section = function (file, parts) file = file:gsub("^%s*%@%-moz%-document%f[%W]", "") local when = {} local word, param while true do -- Strip off a subrule file, word, param = parse_moz_document_subrule(file) local valid_words = { url = true, ["url-prefix"] = true, domain = true, regexp = true } if valid_words[word] then if word == "regexp" then param = param:gsub("\\\\", "\\"):gsub("/","\\/") param = regex{pattern=param} end when[#when+1] = {word, param} else msg.warn("Ignoring unrecognized @-moz-document rule '%s'", word) end if file:match("^%s*,%s*") then file = file:sub(file:find(",")+1) else break end end local css, i = file:match("(%b{})()") css = css:sub(2, -2) file = file:sub(i) parts[#parts+1] = { when = when, css = css } return file end local parse_file = function (file) -- First, strip comments and @namespace file = file:gsub("%/%*.-%*%/","") file = file:gsub("%@namespace%s*url%b();?", "") -- Next, match moz document rules local parts = {} while file:find("^%s*%@%-moz%-document") do file = parse_moz_document_section(file, parts) end if file:find("%S") then parts[#parts+1] = { when = {{"url-prefix", ""}}, css = file } end return parts end local global_comment = "/* i really want this to be global */" local file_looks_like_old_format = function (source) return not source:find("@-moz-document",1,true) and not source:lower():find(global_comment, 1, true) end --- Load the contents of a file as a stylesheet for a given domain. -- @tparam string path The path of the file to load. _M.load_file = function (path) if stylesheet == nil then return end local file = io.open(path, "r") local source = file:read("*all") file:close() if file_looks_like_old_format(source) then msg.error("stylesheet '%s' is global, refusing to load", path) msg.error("to load anyway, add %s to the file", global_comment) return true end local parsed = parse_file(source) local parts = {} for _, part in ipairs(parsed) do table.insert(parts, { ss = stylesheet{ source = part.css }, when = part.when }) end stylesheets[#stylesheets+1] = { parts = parts, file = path, enabled = db_get(path), } end --- Detect all files in the stylesheets directory and automatically load them. _M.detect_files = function () -- Create styles directory if it doesn't exist local cwd = lfs.currentdir() if not lfs.chdir(styles_dir) then lfs.mkdir(styles_dir) lfs.chdir(styles_dir) end for _, stylesheet in ipairs(stylesheets or {}) do for _, part in ipairs(stylesheet.parts) do for _, ww in pairs(window.bywidget) do for _, v in pairs(ww.tabs.children) do v.stylesheets[part.ss] = false end end end end stylesheets = {} msg.verbose("searching for user stylesheets in %s", styles_dir) for filename in lfs.dir(styles_dir) do if string.find(filename, ".css$") then msg.verbose("found user stylesheet: " .. filename) _M.load_file(filename) end end msg.info("found " .. #stylesheets .. " user stylesheet" .. (#stylesheets == 1 and "" or "s")) update_all_stylesheet_applications() lfs.chdir(cwd) end --- Watch a stylesheet in the styles directory for changes and apply them immediately. -- @tparam table guard a table that controls the watch process. Set `guard[1] -- = nil` to turn off the watch. -- @tparam string path the path of the watched style. _M.watch_styles = function (guard, path) luakit.spawn("bash -c 'inotifywait -t 10 \"" .. path .. "\" || sleep 1'", function () _M.detect_files() if guard[1] then _M.watch_styles(guard, path) end end) end --- Create and immediately edit a new style for the current uri. -- @tparam table w The window table for the window providing the uri. _M.new_style = function (w) -- Create styles directory if it doesn't exist local cwd = lfs.currentdir() if not lfs.chdir(styles_dir) then lfs.mkdir(styles_dir) lfs.chdir(styles_dir) end local path = string.match(w.view.uri, "//([%w*%.]+)") .. ".css" local exists = io.open(path, "r") if exists then exists:close() local guard = {0} _M.watch_styles(guard, path) editor.edit(path, 1, function() guard[1] = nil end) else local f = io.open(path, "w") if nil == f then w:notify(path) else f:write("@-moz-document url-prefix(\"" .. w.view.uri .. "\") {\n\n}") f:close() local guard = {0} _M.watch_styles(guard, path) editor.edit(path, 2, function() guard[1] = nil end) end end lfs.chdir(cwd) end --- Toggle the enabled status of a style by filename. -- @tparam string title the style to toggle. _M.toggle_sheet = function(title) for _, stylesheet in ipairs(stylesheets) do if stylesheet.file == title then stylesheet.enabled = not stylesheet.enabled db_set(stylesheet.file, stylesheet.enabled) update_all_stylesheet_applications() end end end add_cmds({ { ":styles-reload, :sr", "Reload user stylesheets.", function (w) w:notify("styles: Reloading files...") _M.detect_files() w:notify("styles: Reloading files complete.") end }, { ":styles-list", "List installed userstyles.", function (w) w:set_mode("styles-list") end }, { ":styles-new", "Create new userstyle for this domain.", _M.new_style}, }) -- Add mode to display all userscripts in menu new_mode("styles-list", { enter = function (w) if #stylesheets == 0 then w:notify("No userstyles installed.") else create_stylesheet_menu_for_w(w) w:notify("Use j/k to move, e edit, enable/disable.", false) end end, leave = function (w) w.menu:hide() end, }) add_binds("styles-list", lousy.util.table.join({ -- Delete userscript { "", "Enable/disable the currently highlighted userstyle.", function (w) local row = w.menu:get() if row and row.stylesheet then row.stylesheet.enabled = not row.stylesheet.enabled db_set(row.stylesheet.file, row.stylesheet.enabled) update_all_stylesheet_applications() end end }, { "e", "Edit the currently highlighted userstyle.", function (w) local row = w.menu:get() if row and row.stylesheet then local file = luakit.data_dir .. "/styles/" .. row.stylesheet.file local guard = {0} _M.watch_styles(guard, file) editor.edit(file, 1, function() guard[1] = nil end) end end }, }, menu_binds)) _M.detect_files() return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/tab_favicons.lua000066400000000000000000000057351475363222200167350ustar00rootroot00000000000000--- UI mod: adds favicons to tabs. -- -- This module modifies the default luakit user interface. -- -- By default, tabs are numbered and do not feature the website icon -- associated with the web page that is currently loaded. This module -- removes the tab numbers and replaces them with the web page icon, for -- an alternative appearance. -- -- @module tab_favicons -- @copyright 2017 Aidan Holm local _M = {} local tab = require("lousy.widget.tab") local webview = require("webview") tab.add_signal("build", function (tl, view) local label = tl.widget.child local layout, fav, spin = widget{type = "hbox"}, widget{type="image"}, widget{type="spinner"} tl.widget.child = layout layout.homogeneous = false layout:pack(fav) layout:pack(spin) layout:pack(label, { expand = true, fill = true }) label.margin_left = 6 label.margin_right = 2 layout.margin_left = 5 layout.margin_right = 10 local update_favicon = function (v) local uri = v.uri or "about:blank" local favicon_js = [=[ favicon = document.evaluate('//link[(@rel="icon") or (@rel="shortcut icon")]/@href', document, null, XPathResult.STRING_TYPE, null).stringValue || '/favicon.ico'; ]=] v:eval_js(favicon_js, { callback = function (favicon_uri, err) assert(not err, err) if not fav.is_alive then return end favicon_uri = favicon_uri:match("^luakit://(.*)") if favicon_uri then fav:filename(favicon_uri) elseif v.private then fav:filename("icons/tab-icon-private.png") elseif uri:match("^luakit://") then fav:filename("icons/tab-icon-chrome.png") elseif not fav:set_favicon_for_uri(uri) then fav:filename("icons/tab-icon-page.png") end end}) end view:add_signal("favicon", update_favicon) -- luakit:// URIs don't emit favicon signal view:add_signal("property::uri", function (v) if webview.has_load_block(v) then update_favicon(v) return end if v.uri:match("^luakit://") then update_favicon(v) return end end) local is_loading_cb = function (v) if v.is_loading then fav:hide() spin:show() else fav:show() spin:hide() end end view:add_signal("property::is_loading", is_loading_cb) local finished_cb = function (v, status) if status == "finished" then update_favicon(v) end end view:add_signal("load-status", finished_cb) tl.widget:add_signal("destroy", function () view:remove_signal("favicon", update_favicon) view:remove_signal("property::uri", update_favicon) view:remove_signal("property::is_loading", is_loading_cb) view:remove_signal("load-status", finished_cb) end) spin:start(); (view.is_loading and fav or spin):hide() update_favicon(view) end) -- Remove tab numbers tab.label_format = "{title}" return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/tabgroups.lua000066400000000000000000000754401475363222200163050ustar00rootroot00000000000000--- Tab groups management module. -- -- This module allows you to group opened tabs and switch between different groups -- and tabs in groups -- -- # Capabilities -- -- # Usage -- -- * Add `require "tabgroups"` to your `rc.lua`. -- * Press 'x' to open list of defined tabgroups. -- * Press 'X' to open list of tabs in active tabgroup. -- * (Optional) add the `tgname` widget to your status bar. -- -- # Troubleshooting -- -- # Files and Directories -- -- -- @module tabgroups -- @author Serg Kozhemyakin -- @author Aidan Holm -- @copyright 2017 Serg Kozhemyakin local window = require("window") local webview = require("webview") local binds, modes = require("binds"), require("modes") local add_binds = modes.add_binds local menu_binds = binds.menu_binds local new_mode = require("modes").new_mode local session = require("session") local settings = require("settings") local lousy = require("lousy") local _M = {} local _new_tabgroup_prefix = "Unnamed#" local _default_notify = "n: create new group, d: delete group, r: rename group" -- private hash for storing map uri to tabgroup local w2groups = setmetatable({}, { __mode = "k" }) -- temporary table for storing deleted notebooks, will be cleaned at idle time local _deleted_groups = {} local switch_tabgroup, delete_tabgroup local function _get_next_tabgroup_name(w) local name local i = 1 if w2groups[w] and w2groups[w].groups then repeat i = i + 1 name = _new_tabgroup_prefix .. i until not w2groups[w].groups[name] else name = _new_tabgroup_prefix .. i end return name end local function grouptabs(w, g) assert(type(g) == 'string') local group = assert(w2groups[w].groups[g]) assert(group._notebook) assert(group._notebook.type == "notebook") local i = 0 local n = group._notebook:count() return function() i = i + 1 if i <= n then return i, group._notebook[i] end end end local function webview2idx(view) local nb = assert(view.parent) -- should we have separate handling for case when -- view.parent is not same as w2groups[w].groups[group]._notebook? -- this case means that we messed with webviews somehow and attached it -- to different notebook manually. hope we don't need to workaroudn -- such setups. return nb:indexof(view) end -- return table with tabgroup info local function webview2group(view) local nb = assert(view.parent) local w = assert(window.ancestor(nb)) for _, gv in pairs(w2groups[w].groups) do if gv._notebook == nb then return gv end end return nil end -- return table with tabgroup info local function tablist2group(nb) -- don't use assert here because this sub may be called -- on deleted tabgroup that already detauched from window -- no need to spam log with useless assert messages local w = window.ancestor(nb) if w then for _, gv in pairs(w2groups[w].groups) do if gv._notebook == nb then return gv end end end return nil end local function current_webview_in_group(w, group) local ret = nil if w2groups[w] and w2groups[w].groups[group] then local g = w2groups[w].groups[group] ret = g._notebook[g._notebook:current()] end return ret end local function number_of_tabgroups(w) local n = 0 for _, _ in pairs(w2groups[w].groups) do n = n + 1 end return n end local function _page_added_cb(nb, view) local g = assert(tablist2group(nb)) assert(not g.tabs[view]) g.tabs[view] = { tab_hit = 0, atime = 0, ctime = os.time(), mtime = 0, } g.mtime = os.time(); view:add_signal("property::uri", function (v) local grp = webview2group(v) assert(grp) grp.tabs[v].mtime = os.time() end) end local function _page_removed_cb(nb, view) local g = tablist2group(nb) if g then if g.tabs[view] then g.tabs[view] = nil end g.mtime = os.time(); end end local function _switch_page_cb(nb, view) local g = assert(tablist2group(nb)) local tab = assert(g.tabs[view]) tab.tab_hit = tab.tab_hit + 1 tab.atime = os.time() end local function add_signals_to_notebook(nb) nb:add_signal("page-added", _page_added_cb) nb:add_signal("page-removed", _page_removed_cb) nb:add_signal("switch-page", _switch_page_cb) end -- return tabgroup table, newly created or already existing local function create_tabgroup(w, group_name) if not w2groups[w].groups[group_name] then local nt = widget({type="notebook"}) w.tabs.parent:insert(nt) nt.show_tabs = false w2groups[w].groups[group_name] = { name = group_name, _notebook = nt, group_hit = 0, atime = 0, mtime = 0, ctime = os.time(), tabs = {}, } add_signals_to_notebook(nt) end return w2groups[w].groups[group_name] end local function _select_next_opened_tabgroup(w, group) local switch_to = nil local tg_list, n, idx = {}, 1, -1 for name, _ in pairs(w2groups[w].groups) do tg_list[n] = name if name == group then idx = n end n = n + 1 end if idx > 0 then switch_to = tg_list[idx + (idx == 1 and 1 or -1)] end return switch_to end local function open_new_tab_in_tabgroup (w, group, uri, opts) local tg = create_tabgroup(w, group) opts = opts or {} if tg then local view = webview.new({ private = opts.private }) if opts.session_restore then webview.modify_load_block(view, "tabgroups-restore", true) local function unblock(vv) webview.modify_load_block(vv, "tabgroups-restore", false) vv:remove_signal("switched-page", unblock) end view:add_signal("switched-page", unblock) end -- copy/pasted from attach_tab function in window module local order = opts.order local taborder = package.loaded.taborder if not order and taborder then order = (opts.switch == false and taborder.default_bg) or taborder.default end local pos = tg._notebook:insert((order and order(w, view)) or -1, view) assert(tg.tabs[view]) assert(tg._notebook) webview.set_location(view, { session_state = opts.session_state, uri = uri or opts.uri, }) if opts.switch then tg._notebook:switch(pos) end end end local function _cleaner() for k, g in pairs(_deleted_groups) do if g and g._notebook then g._notebook:remove_signal("page-removed", _page_removed_cb) g._notebook:remove_signal("page-added", _page_added_cb) g._notebook:remove_signal("switch-page", _switch_page_cb) for i = 1, g._notebook:count() do local v = g._notebook[i] if v then g._notebook:remove(v) end end g._notebook = nil end _deleted_groups[k] = nil end return false end window.add_signal("init", function (w) local group_name = _get_next_tabgroup_name(w) w2groups[w] = { active = group_name , groups = {}, } local _nb = w.tabs local group_nb = widget{type="notebook"} group_nb.show_tabs = false _nb:replace(group_nb) group_nb:insert(_nb) w2groups[w].groups[group_name] = { name = group_name, _notebook = w.tabs, group_hit = 0, atime = os.time(), mtime = os.time(), ctime = os.time(), tabs = {}, } add_signals_to_notebook(w.tabs) w:add_signal("detach-tab", function (win, _) local current_tg_name = w2groups[win].active local nb = w2groups[win].groups[current_tg_name]._notebook if nb:count() == 1 then -- if we closing last tab in active tabgroup -- let's switch to other tabgroup -- and remove current one if number_of_tabgroups(win) > 1 then switch_tabgroup(win, _select_next_opened_tabgroup(w, current_tg_name)) end delete_tabgroup(win, current_tg_name) end end) end) -- add to popup menu submenu for opening new tab in different tabgroups local function populate_open_in_tabgroup_menu (view, menu) -- populate this menu only if we hovering some uri local uri = view.hovered_uri if uri then local w = window.ancestor(view) local tabgroups = {} for g, _ in pairs(w2groups[w].groups) do -- skip active tabgroup if g ~= w2groups[w].active then table.insert(tabgroups, g) end end -- if we have more then one tabgroup then let's populate submenu if #tabgroups > 0 then local switch_to = settings.get_setting("tabgroups.switch_to_new_tab") local submenu = {} local n = 1 for _, tg in ipairs(tabgroups) do submenu[n] = { tg, function (_) open_new_tab_in_tabgroup(w, tg, uri, {switch = switch_to }) if switch_to then switch_tabgroup(w, tg) end end} n = n+1 end -- look for menu item "Open Link in New Tab" for i, mi in ipairs(menu) do if type(mi) == 'table' and mi[1] == 'Open Link in New Tab' then n = i break end end -- add submenu after 'Open Link in New Tab' table.insert(menu, n+1, { "Open Link in Tab Group", submenu }) end end end webview.add_signal("init", function (view) view:add_signal("populate-popup", populate_open_in_tabgroup_menu) end) -- session handling session.add_signal("restore", function (state) for w, win_state in pairs(state) do if win_state.tab_groups then -- let's rename default group as active one, if name is different if win_state.tab_groups.active ~= w2groups[w].active then local groups = w2groups[w].groups groups[win_state.tab_groups.active] = groups[w2groups[w].active] groups[w2groups[w].active] = nil w2groups[w].active = win_state.tab_groups.active groups[win_state.tab_groups.active].name = win_state.tab_groups.active end for gn, g in pairs(win_state.tab_groups.groups) do local src = win_state.tab_groups.groups[gn] for i, stat in ipairs(g.tabs) do if gn ~= w2groups[w].active then open_new_tab_in_tabgroup(w, gn, stat.uri, { switch = g.active == i, session_state = stat.session_state, session_restore = true, }) end local group = w2groups[w].groups[gn] group.tabs[i] = { tab_hit = stat.tab_hit, atime = stat.atime, ctime = stat.ctime, mtime = stat.mtime, } end local group = w2groups[w].groups[gn] if group then group.name = src.name group.atime = src.atime or 0 group.mtime = src.mtime or 0 group.ctime = src.ctime or os.time() else create_tabgroup(w, gn) end end end end end) session.add_signal("save", function(state) local wins = lousy.util.table.values(window.bywidget) for _, w in ipairs(wins) do state[w].tab_groups = { active = w2groups[w].active, groups = {}, } for gn, g in pairs(w2groups[w].groups) do local _a = current_webview_in_group(w, gn) state[w].tab_groups.groups[gn] = { name = gn, group_hit = g.group_hit, atime = g.atime, ctime = g.ctime, mtime = g.mtime, tabs = {}, } local dst = state[w].tab_groups.groups[gn] for i, v in grouptabs(w, gn) do local tab = assert(g.tabs[v]) local tab_info = { tab_hit = tab.tab_hit or 0, atime = tab.atime, mtime = tab.mtime, ctime = tab.ctime, } -- we don't store uri and state for tabs in active tg because this info already stored -- by luakit session manager if gn ~= w2groups[w].active then tab_info.uri = v.uri tab_info.session_state = v.session_state end dst.tabs[i] = tab_info if v == _a then dst.active = i end end end end return state end) local function _sort_by_field(field, order, a, b) assert(order == "asc" or order == "desc") assert(field and a[field] and b[field]) if order == "asc" then return a[field] < b[field] else return a[field] > b[field] end end local function _build_tabgroup_menu_grouptabs(w, group_name, field, order) local rows = {{"Group name", "Tab title", "URI", title = true}} local active = current_webview_in_group(w, group_name) local _tmp = {} local _gv = w2groups[w].groups[group_name] for i, v in grouptabs(w, group_name) do local tab = _gv.tabs[v] or { tab_hit = 1, atime = 0, ctime = os.time(), mtime = 0, } _gv.tabs[v] = tab table.insert(_tmp, { v = v, hits = tab.tab_hit, atime = tab.atime, mtime = tab.mtime, ctime = tab.ctime, title = v.title or '', n = i, }) end if field and order then table.sort(_tmp, function(a, b) return _sort_by_field(field, order, a, b) end) end for i, v in ipairs(_tmp) do local title = v.v.title or '*No title*' if v.v == active then table.insert(rows, { ""..((i < 10 and i..' - ') or '')..group_name.."", ""..lousy.util.escape(title).."", lousy.util.escape(v.v.uri), _group = group_name, _tab = v.v, }) else table.insert(rows, { ((i < 10 and i..' - ') or '')..lousy.util.escape(group_name), lousy.util.escape(title), lousy.util.escape(v.v.uri), _group = group_name, _tab = v.v, }) end end return rows end local function _build_tabgroup_menu_grouplist(w, field, order) local rows = {{ "Group name", "Number of tabs", title = true }} local _tmp = {} for g, gv in pairs(w2groups[w].groups) do table.insert(_tmp, { name = g, hits = gv.group_hit, atime = gv.atime, mtime = gv.mtime, ctime = gv.ctime, }) end if field and order then table.sort(_tmp, function(a, b) return _sort_by_field(field, order, a, b) end) end for i, g in ipairs(_tmp) do if g.name == w2groups[w].active then table.insert(rows, { ""..((i < 10 and i..' - ') or '')..lousy.util.escape(g.name).."", w2groups[w].groups[g.name]._notebook:count(), _group = g.name, }) else table.insert(rows, { ((i < 10 and i..' - ') or '')..lousy.util.escape(g.name), w2groups[w].groups[g.name]._notebook:count(), _group = g.name, }) end end return rows end -- visual mode local function build_tabgroup_menu(w, expand_group) local sort = settings.get_setting("tabgroups.sort_groups_by") local field, order = nil, nil if expand_group then sort = settings.get_setting("tabgroups.sort_tabs_by") end if sort then field, order = string.match(sort, "^%s*(%w+)%s+(%w+)") end if expand_group then return _build_tabgroup_menu_grouptabs(w, expand_group, field, order) else return _build_tabgroup_menu_grouplist(w, field, order) end end local _operation = setmetatable({}, {__mode = 'k'}); switch_tabgroup = function (w, group) if group ~= w2groups[w].active then local g = w2groups[w].groups[group] local nb = g._notebook local group_nb = assert(w.tabs.parent) group_nb:switch(group_nb:indexof(nb)) w.tablist:set_notebook(nb) w.tabs = nb -- changing name of active tabgroup and updating stats w2groups[w].active = group g.group_hit = g.group_hit + 1 g.atime = os.time() -- if we switching to empty tabgroup -- let's open new default tab if nb:count() == 0 then w:new_tab(settings.get_setting("window.new_tab_page"), false) end -- copy-paste from window.lua, since that handler is only attached to the initial notebook w.view = nil -- Update widgets after tab switch luakit.idle_add(function () -- Cancel if window already destroyed if not w.win or not w.view then return end w.view:emit_signal("switched-page") w:update_win_title() end) end end delete_tabgroup = function (w, group) assert(group) assert(type(group) == 'string') if number_of_tabgroups(w) == 1 then return nil else -- lets switch to another group if w2groups[w].active == group then switch_tabgroup(w, _select_next_opened_tabgroup(w, group)) end local g = w2groups[w].groups[group] w.tabs.parent:remove(g._notebook) table.insert(_deleted_groups, g) w2groups[w].groups[group] = nil -- idle handler for cleaning removed notebooks luakit.idle_add(_cleaner) end return true end -- View tabgroups in list and switch between then local function new_tabgroup(w) w:set_mode('tabgroup-menu-new') end local function rename_tabgroup(w) local row = w.menu:get() if row and row._group then w:set_mode('tabgroup-menu-rename') end end -- needed for forward declaration local show_tabgroup_content, show_tabgroups, move_tab_to_tabgroup_menu show_tabgroup_content = function (w, tabgroup_name) if type(tabgroup_name) == 'table' then tabgroup_name = nil end if not tabgroup_name then local row = w.menu:get() if row and row._group then tabgroup_name = row._group end end if tabgroup_name then local rows = build_tabgroup_menu(w, tabgroup_name) w.menu:build(rows) w.menu:update() local notify = _default_notify if number_of_tabgroups(w) > 1 then notify = notify ..", ".."m: move selected tab to another tabgroup, -: show list of groups" else notify = notify ..", ".."-: show list of groups" end w:notify(notify, false) end end show_tabgroups = function (w) local rows = build_tabgroup_menu(w) w.menu:build(rows) w.menu:update() local notify = _default_notify ..", ".. "+: show tabs in selected group" w:notify(notify, false) end local function switch_tabgroup_or_tab(w, _, m) local row = w.menu:get((m and m.count) and m.count+1 or nil) if row and row._group then w:set_mode() switch_tabgroup(w, row._group); if row._tab and row._tab ~= current_webview_in_group(w, w2groups[w].active) then local idx = webview2idx(row._tab) if idx then w:goto_tab(idx) end end end end local function open_tabgroup_menu(w, tabgroup_name) local expand = nil if tabgroup_name and type(tabgroup_name) == 'string' then expand = tabgroup_name end w:set_prompt() w:set_mode("tabgroup-menu", expand) end move_tab_to_tabgroup_menu = function (w) if number_of_tabgroups(w) > 1 then local row = w.menu:get() if row and row._group and row._tab then _operation[w] = { op = 'move', _group = row._group, _tab = row._tab, } w:set_mode('tabgroup-menu-select') end end end new_mode("tabgroup-menu-new", { enter = function (w) local groupname = _get_next_tabgroup_name(w) w:set_prompt("Enter name of new tabgroup > ") w:set_input(groupname) end, activate = function (w, name) if not w2groups[w].groups[name] then create_tabgroup(w, name) else w:notify("Tabgroup '"..name.."' already exists") end w:set_mode('tabgroup-menu') end, }) modes.add_binds("tabgroup-menu-new", { { "", "Return to `tabgroup-menu` mode.", open_tabgroup_menu }, }) new_mode("tabgroup-menu-rename", { enter = function (w) local row = w.menu:get() local groupname = row._group w:set_prompt("Enter new name of tabgroup '"..groupname.."' > ") w:set_input(groupname) _operation[w] = { op = 'rename', _group = groupname, } end, activate = function (w, new_name) local old_name = _operation[w]._group if old_name ~= new_name then w2groups[w].groups[new_name] = w2groups[w].groups[old_name] w2groups[w].groups[old_name] = nil if w2groups[w].active == old_name then w2groups[w].active = new_name end w.view:emit_signal("switched-page") -- a `tabgroup-changed` signal may be more appropriate, -- (both here, and in `switch_tabgroup` above).. end w:set_mode('tabgroup-menu') end, }) modes.add_binds("tabgroup-menu-rename", { { "", "Return to `tabgroup-menu` mode.", open_tabgroup_menu }, }) local function move_tab_to_tabgroup(w, view, group) local old_group = webview2group(view) if old_group.name ~= group then local tg = create_tabgroup(w, group) local stats = w2groups[w].groups[old_group.name].tabs[view] w2groups[w].groups[old_group.name]._notebook:remove(view) tg._notebook:insert(view) tg.tabs[view] = { tab_hit = stats.tab_hit, ctime = stats.ctime, atime = stats.atime, mtime = stats.mtime, } end end -- something that manages different operations when tabgroup select in tabgroup-menu-select mode -- (so far only moving tabs between groups) local _moving = false local function select_tabgroup(w) local row = w.menu:get() if row and row._group then local op = assert(_operation[w]) if op.op == 'move' and op._group and op._tab then if row._group ~= op._group then local tab = op._tab _moving = true w:set_mode() move_tab_to_tabgroup(w, tab, row._group) _moving = false else w:notify("Can't move tab to same tabgroup") end w:set_mode('tabgroup-menu') end end end new_mode("tabgroup-menu-select", { enter = function (w) local rows = build_tabgroup_menu(w) w.menu:build(rows) w:notify("Press to select tabgroup", false) end, leave = function (w) if not _moving then w:set_mode('tabgroup-menu') end end, }) add_binds("tabgroup-menu-select", lousy.util.table.join({ { "", "Select tabgroup.", select_tabgroup }, }, menu_binds)) new_mode("tabgroup-menu", { enter = function (w, tabgroup_name) local rows = build_tabgroup_menu(w, tabgroup_name) _operation[w] = nil w.menu:build(rows) w:set_input() local notify = _default_notify if tabgroup_name then if number_of_tabgroups(w) > 1 then notify = notify ..", ".."m: move selected tab to another tabgroup, -: show list of groups" else notify = notify ..", ".."-: show list of groups" end else notify = notify ..", ".. "+: show tabs in selected group" end w:notify(notify, false) end, leave = function (w) w.menu:hide() end, }) local _confirmation = setmetatable({}, {__mode = 'k'}); new_mode("delete-tg-ask-confirmation", { enter = function (w, confirmation_msg, row) w:warning(confirmation_msg..' (y/n)', false) _confirmation[w] = row end, leave = function (w) _confirmation[w] = nil end, }) modes.add_binds("delete-tg-ask-confirmation", { { "y", "Answer 'Yes' on confirmation.", function (w) assert(_confirmation[w]._group) local deleted = delete_tabgroup(w, _confirmation[w]._group) open_tabgroup_menu(w) if not deleted then w:notify("Can't remove last tabgroup") end end }, { "n", "Answer 'No' on confirmation.", function (w) open_tabgroup_menu(w) end }, { "", "Answer 'No' on confirmation.", function (w) open_tabgroup_menu(w) end }, }) add_binds("tabgroup-menu", lousy.util.table.join({ { "", "Switch to tabgroup or tab in tabgroup.", switch_tabgroup_or_tab }, { "n", "Create new tabgroup.", new_tabgroup }, { "r", "Rename tabgroup.", rename_tabgroup }, { "d", "Delete tabgroup.", function(w) local row = w.menu:get() if row and row._group then w:set_mode("delete-tg-ask-confirmation", "Really delete tabgroup '"..row._group.."'?", row) end end}, { "+", "Open list of tabs in tabgroup.", show_tabgroup_content }, { "-", "Hide list of tabs in tabgroup.", show_tabgroups }, { "m", "Move tab to different tabgroup.", move_tab_to_tabgroup_menu }, { "1", "Switch to first tab or tabgroup.", switch_tabgroup_or_tab, { count = 1 } }, { "2", "Switch to second tab or tabgroup.", switch_tabgroup_or_tab, { count = 2 } }, { "3", "Switch to third tab or tabgroup.", switch_tabgroup_or_tab, { count = 3 } }, { "4", "Switch to forth tab or tabgroup.", switch_tabgroup_or_tab, { count = 4 } }, { "5", "Switch to fifth tab or tabgroup.", switch_tabgroup_or_tab, { count = 5 } }, { "6", "Switch to sixth tab or tabgroup.", switch_tabgroup_or_tab, { count = 6 } }, { "7", "Switch to seventh tab or tabgroup.", switch_tabgroup_or_tab, { count = 7 } }, { "8", "Switch to eighth tab or tabgroup.", switch_tabgroup_or_tab, { count = 8 } }, { "9", "Switch to ninth tab or tabgroup.", switch_tabgroup_or_tab, { count = 9 } }, }, menu_binds)) add_binds("normal", { { "x", "Open tabgroup menu.", open_tabgroup_menu }, { "X", "Show tabs in active tabgroup.", function (w) open_tabgroup_menu(w, w2groups[w].active) end }, }) settings.register_settings({ ["tabgroups.sort_groups_by"] = { type = "string", default = "name asc", validator = function(v) local field, order = string.match(v, "^%s*(%w+)%s+(%w+)") if not field or not order then return false end field, order = field:lower(), order:lower() field = ({name = true, atime = true, mtime = true, ctime = true, hits = true})[field] order = order == "asc" or order == "desc" return field and order end, desc = [=[ Sort order of groups in tabgroups menu. Must be in the form "_field_ _order_", where _field_ is one of: - `name`: group name - `ctime`: time of group creation - `mtime`: time of group modification (updated when adding or removing new tabs) - `atime`: time of group accessing (updated when switching to this group) - `hits`: number of times this group has been switched to and _order_ is - `asc`: sort in ascending order - `desc`: sort in descending order ]=] }, ["tabgroups.sort_tabs_by"] = { type = "string", default = "title asc", validator = function(v) local field, order = string.match(v, "^%s*(%w+)%s+(%w+)") if not field or not order then return false end field, order = field:lower(), order:lower() field = ({n = true, title = true, atime = true, mtime = true, ctime = true, hits = true})[field] order = order == "asc" or order == "desc" return field and order end, desc = [=[ Sort order of tabs in tabgroups menu. Must be in the form "_field_ _order_", where _field_ is one of: - `n`: tab index - `title`: tab title - `ctime`: time of tab creation - `mtime`: time of tab modification (updated when URI changed) - `atime`: time of tab accessing (updated when switching to this tab) - `hits`: number of times this group has been switched to and _order_ is - `asc`: sort in ascending order - `desc`: sort in descending order ]=] }, ["tabgroups.switch_to_new_tab"] = { type = "boolean", default = true, desc = "Switch to new tabgroup after opening link to different tabgroup from popup menu or not" }, }) --- Open a given uri in a new tab in the given window. -- -- @tparam table w The window the tab should be opened in. -- @tparam tabgroup group The tabgroup the new tab should be added to. -- @tparam string uri The uri to be opened. -- @tparam table opts Additional options _M.open_new_tab_in_tabgroup = function(...) return open_new_tab_in_tabgroup(...) end --- Move tab to another tabgroup. -- -- @tparam table w The window the tab should be opened in. -- @tparam widget view The webview -- @tparam tabgroup group The tabgroup the new tab should be added to. _M.move_tab_to_tabgroup = function(...) return move_tab_to_tabgroup(...) end --- Create a new tabgroup (or fetch one if `group_name` is in use). -- -- @tparam table w The window to be associated with the tabgroup. -- @tparam string group_name The name of the new group. -- @treturn table The tabgroup, newly created or already existing. _M.create_tabgroup = function(...) return create_tabgroup(...) end --- Switch to specified tabgroup -- -- @tparam table w A window. -- @tparam string group The name of the tabgroup to switch to. _M.switch_tabgroup = function(...) return switch_tabgroup(...) end --- Delete the specified tabgroup -- -- @tparam table w A window. -- @tparam string group The name of the tabgroup to delete. -- @treturn boolean nil if only one tabgroup exists, true otherwise. _M.delete_tabgroup = function(...) return delete_tabgroup(...) end --- Return the name of the current tabgroup. -- -- @param object The object to set up for signals. -- @tparam table w A window. -- @treturn string The name of w's current tabgroup. function _M.current_tabgroup (w) if w2groups and w2groups[w] and w2groups[w].active then return w2groups[w].active else return "No Tabgroup Selected" end end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/tabhistory.lua000066400000000000000000000062271475363222200164640ustar00rootroot00000000000000--- View and open history items in an interactive menu. -- -- This module allows you to access items in the current tab's history. -- You can directly jump backwards or forwards in a tab's history. -- Alternatively, you can open a particular history item in a new tab or -- window, without affecting the current page. -- -- @module tabhistory -- @copyright 2010 Fabian Streitel -- @copyright 2010 Mason Larobina local window = require("window") local new_mode = require("modes").new_mode local binds, modes = require("binds"), require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local util = require("lousy.util") local join = util.table.join local _M = {} -- View history items in an interactive menu. new_mode("tabhistory", { leave = function (w) w.menu:hide() end, enter = function (w) local h = w.view.history local rows = {{"Title", "URI", title = true},} for i, hi in ipairs(h.items) do local title, uri = util.escape(hi.title) or "", util.escape(hi.uri) local marker = (i == h.index and "* " or " ") table.insert(rows, 2, { (marker..title), uri, index=i}) end w.menu:build(rows) w:notify("Use j/k to move, w winopen, t tabopen.", false) end, }) -- Add history menu binds. add_binds("tabhistory", join({ -- Open history item in new tab. { "t", "Open the currently highlighted history item in a new tab.", function (w) local row = w.menu:get() if row and row.index then local v = w.view local uri = v.history.items[row.index].uri w:new_tab(uri, { switch = false }) end end }, -- Open history item in new window. { "w", "Open the currently highlighted history item in a new window.", function (w) local row = w.menu:get() w:set_mode() if row and row.index then local v = w.view local uri = v.history.items[row.index].uri window.new({uri}) end end }, -- Go to history item. { "", "Open the currently highlighted history item in the current tab.", function (w) local row = w.menu:get() w:set_mode() if row and row.index then local v = w.view local offset = row.index - v.history.index if offset < 0 then v:go_back(-offset) elseif offset > 0 then v:go_forward(offset) end end end }, }, menu_binds)) -- Additional window methods. window.methods.tab_history = function (w) if #(w.view.history.items) < 2 then w:notify("No history items to display") else w:set_mode("tabhistory") end end -- Add `:history` command to view all history items for the current tab in an interactive menu. add_cmds({ { ":tabhistory", "List page history for the current tab.", window.methods.tab_history }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/tabmenu.lua000066400000000000000000000041341475363222200157220ustar00rootroot00000000000000--- Switch tabs using a menu widget. -- -- This module adds a command which lists all open tabs. -- -- @module tabmenu -- @author 2012 Alexander Clare local ipairs = ipairs local table = table local lousy = require "lousy" local modes = require "modes" local binds = require "binds" local add_binds = modes.add_binds local add_cmds = modes.add_cmds local new_mode = modes.new_mode local _M = {} --- Whether the tab menu is displayed or not. -- @type boolean -- @readwrite -- @default `false` _M.hide_box = false add_cmds({ { ":tabmenu", [[Open tab menu.]], function (w) w:set_mode("tabmenu") end }, }) local escape = lousy.util.escape new_mode("tabmenu", { enter = function (w) _M.hide_box = not w.sbar.ebox.visible local rows = {} for _, view in ipairs(w.tabs.children) do if not view.uri then view.uri = " " end table.insert(rows, {escape(view.uri), escape(view.title), v = view }) end w.menu:build(rows) local cur = w.tabs:current() local ind = 0 repeat w.menu:move_down(); ind = ind + 1 until ind == cur w.sbar.ebox:show() w:notify("Del - close, Return - switch.", false) end, leave = function (w) if _M.hide_box == true then w.sbar.ebox:hide() end w.menu:hide() end, }) add_binds("tabmenu", lousy.util.table.join({ { "", "Delete tab.", function (w) local row = w.menu:get() if row and row.v then local cur = w.view w:close_tab(w.tabs[w.tabs:indexof(row.v)]) if cur ~= row.v then w.menu:del() else w:set_mode() end end end }, { "", "Open tab.", function (w) local row = w.menu:get() if row and row.v then local cur = w.view if cur ~= row.v then w.tabs:switch((w.tabs:indexof(row.v))) else w:set_mode() end end end }, }, binds.menu_binds)) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/taborder.lua000066400000000000000000000062541475363222200160760ustar00rootroot00000000000000--- Rules for where to put new tabs. -- -- When a new tab is opened in a window, a tab order function is called to -- determine where in the tab list it should be placed. window.new_tab() -- accepts a tab order function as parameter. If this is not sent, -- taborder.default is used if the new tab will be immediately switched to. -- Otherwise, i.e. if a background tab is opened, taborder.bgdefault is used. -- -- A tab order function receives the current window, and the view that is being -- opened as parameters. In return, it gives the index at which the new tab -- should be put. -- -- @module taborder -- @copyright 2010 Henrik Hallberg local lousy = require("lousy") local _M = {} --- Tab order function: Always insert new tabs before all other tabs. _M.first = function() return 1 end --- Tab order function: Always insert new tabs after all other tabs. -- @tparam table w The current window table. _M.last = function(w) return w.tabs:count() + 1 end --- Tab order function: Always insert new tabs after the current tab. -- @tparam table w The current window table. _M.after_current = function (w) return w.tabs:current() + 1 end --- Tab order function: Always insert new tabs before the current tab. -- @tparam table w The current window table. _M.before_current = function (w) return w.tabs:current() end --- Tab order function: Put new child tab next to the parent after unbroken chain of descendants. -- Logical way to use when one "queues" background-followed links. -- @tparam table w The current window table. -- @tparam widget newview The new webview widget. _M.by_origin = function(w, newview) local newindex = 0 local currentview = w.view if not currentview then return 1 end local kids = _M.kidsof local views = w.tabs.children if kids[currentview] then -- Collect all descendants local desc = { currentview } local ii = 1 repeat desc = lousy.util.table.join(desc, kids[desc[ii]]) ii = ii + 1 until ii > #desc -- Find the non-descendant closest after current. This is where -- the new tab should be put. for i = #views, 1, -1 do if not lousy.util.table.hasitem(desc, views[i]) then newindex = i end if views[i] == currentview then break end end -- There were no non-descendants after current. Put new tab last. if newindex == 0 then newindex = _M.last(w, newview) end else kids[currentview] = {} newindex = _M.after_current(w, newview) end table.insert(kids[currentview], newview) return newindex end --- Default tab order function: open regular tabs last. -- @readwrite _M.default = _M.last --- Default tab order function for background tabs: open by origin. -- @readwrite _M.default_bg = _M.by_origin --- Weak table to remember which tab was spawned from which parent. -- Note that family bonds are tied only if tabs are spawned within -- family rules, e.g. from by_origin. Tabs created elsewhere are orphans. -- @readwrite _M.kidsof = {} setmetatable(_M.kidsof, { __mode = "k" }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/undoclose.lua000066400000000000000000000222011475363222200162550ustar00rootroot00000000000000--- View and reopen closed tabs in an interactive menu. -- -- This module provides support for reopening previously-closed tabs. -- The set of closed tabs is saved in the luakit session, so users can -- still reopen tabs after a restart. -- -- This module also provides a menu that allows viewing the full set of closed -- tabs, as well as opening them directly. -- -- @module undoclose -- @copyright 2010 Chris van Dijk -- @copyright 2010 Mason Larobina local window = require("window") local lousy = require("lousy") local binds, modes = require("binds"), require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local new_mode = require("modes").new_mode local session = require("session") local settings = require("settings") local _M = {} lousy.signal.setup(_M, true) local reopening = {} -- Map of view widgets to UIDs that are restored after reopening a web view local view_uids = setmetatable({}, { __mode = "k" }) -- Map of notebook widgets to closed tab lists local closed_tabs = setmetatable({}, { __mode = "k" }) --- Returns a webview widget's UID, creating one if necessary. -- @tparam widget view The webview. -- @treturn number The UID for the webview. local uid_from_view = function (view) assert(type(view) == "widget" and view.type == "webview") local uid = view_uids[view] if not uid then uid = view_uids.next or 0 view_uids.next = uid + 1 view_uids[view] = uid end return uid end --- Returns a webview with the given UID, if one exists. -- @tparam number uid The UID. -- @treturn widget The webview widget. local view_from_uid = function (uid) if not uid then return end assert(type(uid) == "number" and uid >= 0) for v, u in pairs(view_uids) do if u == uid and type(v) == "widget" then return v end end end local on_tab_close = function (w, view) local tab -- Save tab history if reopening[view] then -- If we're still reopening the tab we're closing, then we haven't yet -- initialized it; reuse the restoration data for this tab tab = reopening[view] reopening[view] = nil else local index = w.tabs:indexof(view) local hist = view.history local hist_item = hist.items[hist.index] -- Don't save tabs with no history if not index or not hist_item then return end -- Don't save the "New Tab" page in undoclose history if _M.emit_signal("save", view) == false then return end local title = lousy.util.escape(hist_item.title) or "" tab = { session_state = view.session_state, uri = view.uri, title = title, self_uid = uid_from_view(view), after_uid = (index ~= 1) and uid_from_view(w.tabs[index-1]), } view_uids[view] = nil if view.uri ~= hist_item.uri then tab.next_uri = view.uri end end closed_tabs[w.tabs] = closed_tabs[w.tabs] or {} table.insert(closed_tabs[w.tabs], tab) local max_saved_tabs = settings.get_setting("undoclose.max_saved_tabs") while max_saved_tabs >= 0 and #closed_tabs[w.tabs] > max_saved_tabs do table.remove(closed_tabs[w.tabs], 1) end end -- Undo a closed tab (with complete tab history) window.methods.undo_close_tab = function (w, index) local ctabs = closed_tabs[w.tabs] or {} -- Convert negative indexes if index and index < 0 then index = #ctabs + index + 1 end local tab = table.remove(ctabs, index) if not tab then w:notify("No closed tabs to reopen") return end -- Restore the view local view = w:new_tab({session_state = tab.session_state}) -- If tab was in the middle of a page load when it was closed, continue that now if tab.next_uri then view.uri = tab.next_uri end reopening[view] = tab -- Restore saved view uid view_uids[view] = tab.self_uid -- Attempt to open in last position local after = view_from_uid(tab.after_uid) if after then local i = w.tabs:indexof(after) w.tabs:reorder(view, (i and i+1) or -1) else w.tabs:reorder(view, 1) end -- Emit 'undo-close' after webview init funcs have run view:add_signal("web-extension-loaded", function(v) v:emit_signal("undo-close") reopening[view] = nil end) end session.add_signal("save", function (state) for _, w in pairs(window.bywidget) do -- Save closed tabs for each window assert(state[w]) assert(not state[w].closed) state[w].closed = {} for i, tab in ipairs(closed_tabs[w.tabs] or {}) do state[w].closed[i] = tab end -- Save view uids for each view -- HACK: This is rather brittle; need a better API for session stuff for i, v in ipairs(w.tabs.children) do if not v.private then state[w].open[i].view_uid = view_uids[v] end end end end) session.add_signal("restore", function (state) view_uids.next = 0 for w, win in pairs(state) do -- Restore closed tabs for each window closed_tabs[w.tabs] = win.closed -- Save view uids for each view, reconstruct view_uids.next for i, v in ipairs(w.tabs.children) do local uid = win.open[i].view_uid view_uids[v] = uid if uid and uid >= view_uids.next then view_uids.next = uid + 1 end end end end) window.add_signal("init", function (w) w:add_signal("close-tab", on_tab_close) end) add_binds("normal", { { "u", "Undo closed tab (restoring tab history).", function (w, m) w:undo_close_tab(-m.count) end, {count=1} }, }) -- View closed tabs in a list new_mode("undolist", { enter = function (w) local rows = {{ "Title", " URI", title = true }} for uid, tab in ipairs(closed_tabs[w.tabs] or {}) do tab.uid = uid local title = lousy.util.escape(tab.title) local uri = lousy.util.escape(tab.uri) table.insert(rows, 2, { " " .. title, " " .. uri, uid = uid }) end w.menu:build(rows) w:notify("Use j/k to move, d delete, u undo, w winopen.", false) end, leave = function (w) w.menu:hide() end, }) -- Add undolist menu binds add_binds("undolist", lousy.util.table.join({ -- Delete closed tab history { "d", "Delete closed tab history item.", function (w) local row = w.menu:get() local ctabs = closed_tabs[w.tabs] or {} if row and row.uid then for i, tab in ipairs(ctabs) do if tab.uid == row.uid then table.remove(ctabs, i) break end end w.menu:del() if w.menu:nrows() == 1 then w:notify("No closed tabs to display") end end end }, { "u", "Undo closed tab in new background tab.", function (w) local row = w.menu:get() local ctabs = closed_tabs[w.tabs] or {} if row and row.uid then for i, tab in ipairs(ctabs) do if tab.uid == row.uid then w:new_tab(table.remove(ctabs, i), { switch = false }) break end end w.menu:del() if w.menu:nrows() == 1 then w:notify("No closed tabs to display") end end end }, -- Undo closed tab in new window { "w", "Undo closed tab in new window.", function (w) local row = w.menu:get() local ctabs = closed_tabs[w.tabs] or {} w:set_mode() if row and row.uid then for i, tab in ipairs(ctabs) do if tab.uid == row.uid then window.new({table.remove(ctabs, i)}) return end end end end }, -- Undo closed tab in current tab { "", "Undo closed tab in current tab.", function (w) local row = w.menu:get() w:set_mode() if row and row.uid then for i, tab in ipairs(closed_tabs[w.tabs] or {}) do if tab.uid == row.uid then w:undo_close_tab(i) end end end end }, }, menu_binds)) -- Add `:undolist` command to view all closed tabs in an interactive menu add_cmds({ { ":undolist", "Undo closed tabs menu.", function (w) if #(closed_tabs[w.tabs] or {}) == 0 then w:notify("No closed tabs to display") else w:set_mode("undolist") end end }, }) settings.register_settings({ ["undoclose.max_saved_tabs"] = { type = "number", default = 100, validator = function (v) return tonumber(v) >= -1 end, desc = [[ The maximum number of closed tabs that should be saved, or `-1` for no limit. When the number of closed tabs reaches this limit, the oldest closed tabs are discarded. ]], }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/unique_instance.lua000066400000000000000000000050101475363222200174530ustar00rootroot00000000000000--- Unique instance support for luakit. -- -- This module provides a simple implementation of unique instances. -- With this module enabled, only one instance of luakit will be run; -- opening links from other programs or from the command line will open -- those links in an already-running instance of luakit. -- -- This module should be the first module loaded in your configuration file. -- -- @module unique_instance -- @copyright 2017 Aidan Holm local _M = {} -- Check that this module is loaded first: simple but effective for _, k in ipairs({"window", "webview", "lousy", "globals"}) do assert(not package.loaded[k], "unique_instance should be loaded before all other modules!") end local lfs = require "lfs" local unique = luakit.unique --- Whether links from secondary luakit instances should open in a new -- window; if `true`, links will be opened in a new window, if `false`, -- links will be opened in an existing luakit window. -- @type boolean -- @readwrite -- @default `false` _M.open_links_in_new_window = false if not unique then msg.verbose("luakit started with no-unique") return _M end unique.new("org.luakit") -- Check for a running luakit instance if unique.is_running() then msg.verbose("a primary instance is already running") local pickle = require("lousy.pickle") local u = {} for i, uri in ipairs(uris) do u[i] = lfs.attributes(uri) and ("file://"..os.abspath(uri):gsub(" ","%%20")) or uri end unique.send_message("open-uri-set " .. pickle.pickle(u)) luakit.quit() end unique.add_signal("message", function (message, screen) msg.verbose("received message from secondary instance") local lousy, window = require "lousy", require "window" local cmd, arg = string.match(message, "^(%S+)%s*(.*)") local w if cmd == "open-uri-set" then local u = lousy.pickle.unpickle(arg) -- Get the window to use if #u == 0 or _M.open_links_in_new_window then w = window.new(u) else w = lousy.util.table.values(window.bywidget)[1] end if not _M.open_links_in_new_window then for _, uri in ipairs(u) do w:new_tab(uri) end end elseif cmd == "tabopen" then w = lousy.util.table.values(window.bywidget)[1] w:new_tab(arg) elseif cmd == "winopen" then w = window.new((arg ~= "") and { arg } or {}) end w.win.screen = screen w.win.urgency_hint = true end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/userscripts.lua000066400000000000000000000355661475363222200166720ustar00rootroot00000000000000--- Userscript support for luakit. -- -- Evaluates and manages userscripts. -- JavaScript userscripts must end in .user.js -- -- # Files and Directories -- -- - Userscript files should be placed in the `scripts` sub-directory of the -- luakit data directory, and must have a filename ending in `.user.js`. -- -- @module userscripts -- @copyright 2011 Constantin Schomburg -- @copyright 2010 Fabian Streitel -- @copyright 2010 Mason Larobina local webview = require("webview") local window = require("window") local lousy = require("lousy") local util = require("lousy.util") local lfs = require("lfs") local new_mode = require("modes").new_mode local binds, modes = require("binds"), require("modes") local add_binds, add_cmds = modes.add_binds, modes.add_cmds local menu_binds = binds.menu_binds local editor = require("editor") local _M = {} local _, db = pcall(function () local path = luakit.data_dir .. "/scripts/scripts" return lousy.pickle.unpickle(lousy.load(path)) end) if type(db) == "string" then db = {} end local function db_get(file) assert(file) return db[file] ~= false end local function db_set(file, enabled) assert(file) if enabled then db[file] = nil else db[file] = false end local fh = io.open(luakit.data_dir .. "/scripts/scripts", "wb") fh:write(lousy.pickle.pickle(db)) io.close(fh) end -- Pure JavaScript implementation of greasemonkey methods commonly used -- in chome/firefox userscripts. local gm_functions = [=[ // (C) 2009 Jim Tuttle (http://userscripts.org/users/79247) // Original source: http://userscripts.org/scripts/review/41441 if(typeof GM_getValue === "undefined") { GM_getValue = function(name){ var nameEQ = escape("_greasekit" + name) + "=", ca = document.cookie.split(';'); for (var i = 0, c; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) { var value = unescape(c.substring(nameEQ.length, c.length)); //alert(name + ": " + value); return value; } } return null; } } if(typeof GM_setValue === "undefined") { GM_setValue = function( name, value, options ){ options = (options || {}); if ( options.expiresInOneYear ){ var today = new Date(); today.setFullYear(today.getFullYear()+1, today.getMonth, today.getDay()); options.expires = today; } var curCookie = escape("_greasekit" + name) + "=" + escape(value) + ((options.expires) ? "; expires=" + options.expires.toGMTString() : "") + ((options.path) ? "; path=" + options.path : "") + ((options.domain) ? "; domain=" + options.domain : "") + ((options.secure) ? "; secure" : ""); document.cookie = curCookie; } } if(typeof GM_xmlhttpRequest === "undefined") { GM_xmlhttpRequest = function(/* object */ details) { details.method = details.method.toUpperCase() || "GET"; if(!details.url) { throw("GM_xmlhttpRequest requires an URL."); return; } // build XMLHttpRequest object var oXhr, aAjaxes = []; if(typeof ActiveXObject !== "undefined") { var oCls = ActiveXObject; aAjaxes[aAjaxes.length] = {cls:oCls, arg:"Microsoft.XMLHTTP"}; aAjaxes[aAjaxes.length] = {cls:oCls, arg:"Msxml2.XMLHTTP"}; aAjaxes[aAjaxes.length] = {cls:oCls, arg:"Msxml2.XMLHTTP.3.0"}; } if(typeof XMLHttpRequest !== "undefined") aAjaxes[aAjaxes.length] = {cls:XMLHttpRequest, arg:undefined}; for(var i=aAjaxes.length; i--; ) try{ oXhr = new aAjaxes[i].cls(aAjaxes[i].arg); if(oXhr) break; } catch(e) {} // run it if(oXhr) { if("onreadystatechange" in details) oXhr.onreadystatechange = function() { details.onreadystatechange(oXhr) }; if("onload" in details) oXhr.onload = function() { details.onload(oXhr) }; if("onerror" in details) oXhr.onerror = function() { details.onerror(oXhr) }; oXhr.open(details.method, details.url, true); if("headers" in details) for(var header in details.headers) oXhr.setRequestHeader(header, details.headers[header]); if("data" in details) oXhr.send(details.data); else oXhr.send(); } else { throw ("This Browser is not supported, please upgrade."); } } } if(typeof GM_addStyle === "undefined") { GM_addStyle = function(/* String */ styles) { var oStyle = document.createElement("style"); oStyle.setAttribute("type", "text\/css"); oStyle.appendChild(document.createTextNode(styles)); var parent = document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]; parent.appendChild(oStyle); } } if(typeof GM_log === "undefined") { GM_log = function(log) { if(console) console.log(log); else alert(log); } } ]=] --- Stores all the scripts. local scripts = {} --- Stores information on the currently loaded scripts on a webview widget local lstate = setmetatable({}, { __mode = "k" }) --- The directory in which to search for userscripts. -- By default, this is the `scripts` directory in the luakit data directory. -- @type string -- @readonly _M.dir = luakit.data_dir .. "/scripts" local function match_pat(uri, p) if type(p) == "regex" then uri, p = p, uri end return uri:match(p) end -- Userscript class methods local prototype = { -- Run the userscript on the given webview widget run = function (s, view) -- Load common greasemonkey methods if not lstate[view].gmloaded then view:eval_js(gm_functions, { no_return = true }) lstate[view].gmloaded = true end view:eval_js(s.js, { source = s.file, no_return = true, callback = function (_, err) for _, w in pairs(window.bywidget) do if w.view == view then w:error(string.format("running userscript '%s' failed:\n%s", s.file, err)) end end end}) lstate[view].loaded[s.file] = s end, -- Check if the given uri matches the userscripts include/exclude patterns match = function (s, uri) for _, p in ipairs(s.exclude) do if match_pat(uri, p) then return false end end for _, p in ipairs(s.include) do if match_pat(uri, p) then return true end end end, } -- Parse and convert a simple glob matching pattern in the `@include`, -- `@exclude` or `@match` userscript header options into an RE. local function parse_pattern(pat) if pat:match("^/.+/$") then return regex{pattern = pat:sub(2, -2)} else pat = string.gsub(string.gsub(pat, "[%^%$%(%)%%%.%[%]%+%-%?]", "%%%1"), "*", ".*") return '^' .. pat .. '$' end end local function parse_header(header, file) local ret = { file = file, include = {}, exclude = {} } for _, line in ipairs(util.string.split(header, "\n")) do local singles = { name = true, description = true, version = true, homepage = true } -- Parse `// @key value` lines in header. local key, val = string.match(line, "^// @([%w%-]+)%s+(.+)$") if key then -- XXX: compatibility shim. @match and @include should have -- different behaviour if key == "match" then key = "include" end val = util.string.strip(val or "") if singles[key] then -- Only grab the first of its kind if not ret[key] then ret[key] = val end elseif key == "include" or key == "exclude" then table.insert(ret[key], parse_pattern(val)) elseif key == "run-at" and val == "document-start" then ret.on_start = true end end end return ret end --- Loads a js userscript. local function load_js(file) -- Open script local f = io.open(file, "r") local js = f:read("*all") f:close() -- Inspect userscript header local header = string.match(js, "//%s*==UserScript==%s*\n(.*)\n//%s*==/UserScript==") if header then local script = parse_header(header, file) script.js = js script.file = file script.enabled = db_get(file) scripts[file] = setmetatable(script, { __index = prototype }) else msg.warn("invalid userscript header in file: %s", file) end end --- Loads all userscripts from the _M.dir. local function load_all() if not os.exists(_M.dir) then return end for file in lfs.dir(_M.dir) do if string.match(file, "%.user%.js$") then load_js(_M.dir .. "/" .. file) end end end local function view_has_userscripts(view) local uri = view.uri or "about:blank" for _, script in pairs(scripts) do if script.enabled and script:match(uri) then return true end end return false end -- Invoke all userscripts for a given webviews current uri local function invoke(view, on_start) local uri = view.uri or "about:blank" for _, script in pairs(scripts) do if script.enabled and on_start == script.on_start then if script:match(uri) then script:run(view) end end end end --- Save a userscript to a file. -- @tparam string file The file path in which to save the userscript. -- @tparam string js The userscript contents. function _M.save(file, js) if not os.exists(_M.dir) then util.mkdir(_M.dir) end local f = io.open(_M.dir .. "/" .. file, "w") f:write(js) f:close() load_js(_M.dir .. "/" .. file) end --- Delete a userscript file. -- @tparam string file The file path of the userscript to remove. function _M.del(file) if not scripts[file] then return end os.remove(file) scripts[file] = nil end -- Hook on the webview's load-status signal to invoke the userscripts. webview.add_signal("init", function (view) view:add_signal("load-status", function (v, status) if status == "provisional" then -- Clear last userscript-loaded state lstate[v] = { loaded = {}, gmloaded = false } -- TODO -- elseif status == "first-visual" then -- invoke(v, true) elseif status == "finished" then if view_has_userscripts(view) then if v:emit_signal("enable-userscripts") == false then return end end -- WebKit2 has no first-visual signal, so we can't inject -- userscripts set to run at document start that way. Just -- inject them all when loading has finished for now. invoke(v, true) invoke(v) end end) end) -- Add userscript commands add_cmds({ -- Saves the content of the open view as an userscript { ":userscriptinstall, :usi, :usinstall", "Install the userscript loaded in the current tab.", function (w) local view = w.view local file = string.match(view.uri, "/([^/]+%.user%.js)$") if (not file) then return w:error("URL is not a *.user.js file") end if view:loading() then w:error("Wait for script to finish loading first.") end local js = "document.body.getElementsByTagName('pre')[0].innerHTML" view:eval_js(js, { callback = function(ret) local script = util.unescape(ret) local header = string.match(script, "//%s*==UserScript==%s*\n(.*)\n//%s*==/UserScript==") if not header then return w:error("Could not find userscript header") end _M.save(file, script) w:notify("Installed userscript to: " .. _M.dir .. "/" .. file) end}) end }, { ":userscripts, :uscripts", "List installed userscripts.", function (w) w:set_mode("uscriptlist") end }, { ":userscripts-reload, :uscripts-reload", "Reload installed userscripts.", function () scripts = {}; load_all() end }, }) local scripts_menu_rows = setmetatable({}, { __mode = "k" }) local menu_row_for_script = function (w, script) local theme = lousy.theme.get() local title = (script.name or script.file) .. " " .. (script.version or "") local desc = (script.description or "no description") local enabled = script.enabled local active = enabled and script:match(w.view.uri) -- Determine state label and row colours local state, fg, bg if not enabled then state, fg, bg = "Disabled", theme.menu_disabled_fg, theme.menu_disabled_bg elseif not active then state, fg, bg = "Enabled", theme.menu_enabled_fg, theme.menu_enabled_bg else state, fg, bg = "Active", theme.menu_active_fg, theme.menu_active_bg end return { title, state, desc, script = script, fg = fg, bg = bg } end local function update_scripts_menu_for_w(w) local rows = assert(scripts_menu_rows[w]) for i=2,#rows do rows[i] = menu_row_for_script(w, rows[i].script) end w.menu:update() end local function update_scripts_menus() -- Update any windows in styles-list mode for _, w in pairs(window.bywidget) do if w:is_mode("uscriptlist") then update_scripts_menu_for_w(w) end end end -- Add mode to display all userscripts in menu new_mode("uscriptlist", { enter = function (w) local rows = {{ "Userscripts", "State", "Description", title = true }} local groups = { Disabled = {}, Enabled = {}, Active = {}, } for _, script in pairs(scripts) do local row = menu_row_for_script(w, script) table.insert(groups[row[2]], row) end rows = lousy.util.table.join(rows, groups.Active, groups.Enabled, groups.Disabled) if #rows == 1 then w:notify(string.format("No userscripts installed. Use `:usinstall`" .. "or place .user.js files in %q manually.", _M.dir)) return end w.menu:build(rows) scripts_menu_rows[w] = rows w:notify("Use j/k to move, e edit, enable/disable.", false) end, leave = function (w) w.menu:hide() end, }) add_binds("uscriptlist", util.table.join({ { "", "Enable/disable the currently highlighted userscript.", function (w) local row = w.menu:get() if row and row.script then row.script.enabled = not row.script.enabled db_set(row.script.file, row.script.enabled) update_scripts_menus() end end }, { "e", "Edit the currently highlighted userscript.", function (w) local row = w.menu:get() if row and row.script then editor.edit(row.script.file) end end }, }, menu_binds)) -- Initialize the userscripts load_all() return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/vertical_tabs.lua000066400000000000000000000051761475363222200171200ustar00rootroot00000000000000--- UI mod: vertical tabs. -- -- This module moves the tab bar to the side. -- -- @module vertical_tabs -- @copyright 2017 Aidan Holm local _M = {} local window = require("window") local lousy = require("lousy") local settings = require("settings") local appear_cb = setmetatable({}, { __mode = "k" }) window.add_signal("build", function (w) -- Replace the existing tablist with a vertical one w.tablist:destroy() w.tablist = lousy.widget.tablist(w.tabs, "vertical") -- Add a paned widget: tablist on left, repack w.tabs on right local paned = widget{type="hpaned"} w.tabs.parent.child = nil do local left = settings.get_setting("vertical_tabs.side") == "left" local A, B = left and paned.pack1 or paned.pack2, left and paned.pack2 or paned.pack1 A(paned, w.tablist.widget, { resize = false, shrink = true }) B(paned, w.tabs) end local tlw = w.tablist.widget appear_cb[paned] = function () if not tlw.visible then return end local left = settings.get_setting("vertical_tabs.side") == "left" local sbw = settings.get_setting("vertical_tabs.sidebar_width") paned.position = left and sbw or (paned.width - sbw) tlw:remove_signal("property::visible", appear_cb[paned]) appear_cb[paned] = nil end tlw:add_signal("property::visible", appear_cb[paned]) w.menu_tabs.child = paned end) settings.register_settings({ ["vertical_tabs.sidebar_width"] = { type = "number", min = 0, default = 200, }, ["vertical_tabs.side"] = { type = "enum", options = { ["left"] = { desc = "Left side of the screen.", label = "Left", }, ["right"] = { desc = "Right side of the screen.", label = "Right", }, }, default = "left", desc = "The side of the window that the vertical tabs sidebar should be shown on.", }, }) settings.add_signal("setting-changed", function (e) if e.key == "vertical_tabs.side" then for _, w in pairs(window.bywidget) do local paned = w.menu_tabs.child assert(paned.type == "hpaned") local l, r, width, pos = paned.left, paned.right, paned.width, paned.position paned:remove(l) paned:remove(r) paned:pack1(r) paned:pack2(l) -- don't flip position if the position is unset if not appear_cb[paned] then paned.position = width - pos end end end end) settings.migrate_global("vertical_tabs.sidebar_width", "vertical_tab_width") return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/view_source.lua000066400000000000000000000132201475363222200166150ustar00rootroot00000000000000--- View the HTML source code of web pages. -- -- This module provides support for viewing the source code of web pages. It -- also provides the `view-source:` URI scheme. -- -- @module view_source -- @copyright 2017 Aidan Holm local lousy = require("lousy") local webview = require("webview") local history = require("history") local add_binds = require("modes").add_binds local _M = {} local view_targets = setmetatable({}, { __mode = "k" }) --- Number of spaces to render tabs with. -- @type number -- @default 4 -- @readwrite _M.tab_size = 4 local wait_for_signal_with_arguments = function (obj, sig, ...) assert(type(sig) == "string", "signal name must be a string") local required_args = {...} local co = assert(coroutine.running(), "must be inside coroutine") local function signal_checker(_, ...) local args = {...} for i in ipairs(required_args) do if required_args[i] ~= args[i] then return end end obj:remove_signal(sig, signal_checker) local ok, err = coroutine.resume(co) if not ok then error(("coroutine error %s:\n%s"):format(err, debug.traceback(co))) end end obj:add_signal(sig, signal_checker) coroutine.yield() end -- Webview used to load URI source local loader_view = webview.new("about:blank", { private = true }) local get_source_for_view = function (target_view) local source = target_view:get_source() local lines = lousy.util.string.split(source, "\r?\n") for i, line in ipairs(lines) do lines[i] = lousy.util.escape(line) end local css_vars = ([==[ ]==]):gsub("{([%w%-]+)}", {["line-max-num-chars"] = #tostring(#lines) }) local ret = "" .. table.concat(lines, "\n") .. "" return "
        " .. ret .."
        " .. css_vars end local view_source_queue = {} local load_view_source_uri = function () local v, request = unpack(table.remove(view_source_queue)) local uri = v.uri:match("view%-source:(.*)$") local target_view = view_targets[v] if not target_view then loader_view.uri = uri msg.info("loading in background: %s", uri) wait_for_signal_with_arguments(loader_view, "load-status", "finished") end local source = get_source_for_view(target_view or loader_view) -- Construct page local style = ([===[ pre { tab-size: %d; display: inline-block; margin: 0; min-width: 100%%; box-sizing: border-box; } html, body { margin: 0; width: 100%%; } pre span.line:first-child{ counter-reset: linecounter; } span.line { counter-increment: linecounter; } span.line:before { background: #f8f8f8; content: counter(linecounter); -webkit-user-select: none; width: calc(var(--line-max-num-chars)*1ch); display: inline-block; color: #888; text-align: right; padding: 0.125em 1ch; font-size: 0.8em; } span.lex.tag { color: #a33243; } span.lex.element{ color: #844631; } span.lex.comment { color: #558817; } span.lex.constant { color: #a8660d; } span.lex.escape { color: #844631; } span.lex.keyword { color: #2239a8; font-weight: bold; } span.lex.library { color: #0e7c6b !important; text-decoration: none !important; } span.lex.marker { color: #512b1e; background: #fedc56; font-weight: bold; } span.lex.number { color: #a8660d; } span.lex.operator { color: #2239a8; font-weight: bold; } span.lex.preprocessor { color: #a33243; } span.lex.prompt { color: #558817; } pre a:link, .sourcecode a:visited { color: #272fc2; } ]===]):format(_M.tab_size) local html = ([==[ %s ]==]):format(style, source) request:finish(html) if not target_view then loader_view.uri = "about:blank" wait_for_signal_with_arguments(loader_view, "load-status", "finished") end end local process_queue_items = function () if view_source_queue.lock then return end view_source_queue.lock = true while #view_source_queue > 0 do load_view_source_uri() end view_source_queue.lock = false end luakit.register_scheme("view-source") webview.add_signal("init", function (view) view:add_signal("scheme-request::view-source", function (v, _, request) table.insert(view_source_queue, {v, request}) local co = coroutine.create(process_queue_items) local ok, err = coroutine.resume(co) if not ok then error(("coroutine error %s:\n%s"):format(err, debug.traceback(co))) end end) end) -- Don't add view-source: entries to history history.add_signal("add", function (uri) if uri:match("^view%-source:") then return false end end) add_binds("command", { { ":view-source, :vs", "View the source code of the current document.", function (w) local target = w.view local display = w:new_tab(nil, { private = target.private }) view_targets[display] = target display.uri = "view-source:" .. target.uri end }, }) add_binds("normal", { { "", "View the source code of the current document.", function (w) w:run_cmd("vs") end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/viewpdf.lua000066400000000000000000000022261475363222200157330ustar00rootroot00000000000000--- Automatic PDF viewing. -- -- This module automatically downloads PDF files to the luakit cache directory, -- and opens them with `xdg-open`. You must disable the enable_pdfjs -- setting for this to work. -- -- @module viewpdf -- @copyright 2016 Aidan Holm local downloads = require("downloads") local _M = {} downloads.add_signal("download-location", function(_, filename, mime) if mime == "application/pdf" then local dir = luakit.cache_dir .. "/viewpdf/" local mode = lfs.attributes(dir, "mode") if mode == nil then assert(lfs.mkdir(dir)) elseif mode ~= "directory" then error("Cannot create directory " .. dir) end return dir .. filename end end) downloads.add_signal("download::status", function(dl) if dl.mime_type == "application/pdf" and dl.status == "finished" then downloads.do_open(dl) end end) downloads.add_signal("open-file", function (file, mime_type) if mime_type == "application/pdf" then luakit.spawn(string.format("xdg-open %q", file)) return true end end) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/webinspector.lua000066400000000000000000000016661475363222200170020ustar00rootroot00000000000000--- Bindings for the web inspector. -- -- This module enables developer extras for luakit's web views, and adds a -- command to show/hide the WebKit web inspector. -- -- @module webinspector -- @copyright 2012 Fabian Streitel -- @copyright 2012 Mason Larobina local webview = require("webview") local settings = require("settings") local modes = require("modes") local add_cmds = modes.add_cmds local _M = {} webview.add_signal("init", function (view) settings.override_setting_for_view(view, "webview.enable_developer_extras", true) end) add_cmds({ { ":in[spect]", "Open the DOM inspector.", function (w, o) local v = w.view if o.bang then -- "inspect!" toggles inspector (v.inspector and v.close_inspector or v.show_inspector)(v) else w.view:show_inspector() end end }, }) return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/webview.lua000066400000000000000000000660741475363222200157520ustar00rootroot00000000000000--- Webview widget wrapper. -- -- The webview module wraps the webview widget provided by luakit, adding -- several convenience APIs and providing basic functionality. -- -- @module webview -- @copyright 2017 Aidan Holm -- @copyright 2012 Mason Larobina local window = require("window") local lousy = require("lousy") local settings = require("settings") local _M = {} lousy.signal.setup(_M, true) local web_module = require_web_module("webview_wm") web_module:add_signal("form-active", function (_, page_id) for _, w in pairs(window.bywidget) do if w.view.id == page_id then w.view:emit_signal("form-active") end end end) web_module:add_signal("navigate", function (_, page_id, uri) msg.verbose("Got luakit:// -> file:// navigation: %s", uri) for _, w in pairs(window.bywidget) do if w.view.id == page_id then w.view.uri = uri end end end) local webview_state = setmetatable({}, { __mode = "k" }) -- Table of functions which are called on new webview widgets. local init_funcs = { -- Update window and tab titles title_update = function (view) view:add_signal("property::title", function (v) local w = _M.window(v) if w and w.view == v then w:update_win_title() end end) end, -- Clicking a form field automatically enters insert mode. form_insert_mode = function (view) -- Emit root-active event in button release to prevent "missing" -- buttons or links when the input bar hides. view:add_signal("button-press", function (v, _, button, context) if button == 1 then v:emit_signal(context.editable and "form-active" or "root-active") end end) view:add_signal("form-active", function (v) local w = _M.window(v) if not w.mode.passthrough then w:set_mode("insert") end end) view:add_signal("root-active", function (v) local w = _M.window(v) if w.mode.reset_on_focus ~= false then w:set_mode() end end) view:add_signal("load-status", function (v, status, _, err) if status == "finished" or (status == "failed" and err == "Load request cancelled") then web_module:emit_signal(v, "load-finished") end end) end, -- Try to match a button event to a users button binding else let the -- press hit the webview. button_bind_match = function (view) view:add_signal("button-release", function (v, mods, button, context) local w = _M.window(v) if w:hit(mods, button, { context = context }) then return true end end) view:add_signal("scroll", function (v, mods, dx, dy, context) local w = _M.window(v) if w:hit(mods, "Scroll", { context = context, dx = dx, dy = dy }) then return true end end) end, -- Reset the mode on navigation mode_reset_on_nav = function (view) view:add_signal("load-status", function (v, status) local w = _M.window(v) if status == "provisional" and w and w.view == v then if w.mode.reset_on_navigation ~= false then w:set_mode() end end end) end, -- Action to take on mime type decision request. mime_decision = function (view) -- Return true to accept or false to reject from this signal. view:add_signal("mime-type-decision", function (_, uri, mime) msg.info("Requested link: %s (%s)", uri, mime) -- i.e. block binary files like *.exe --if mime == "application/octet-stream" then -- return false --end end) end, -- Action to take on window open request. --window_decision = function (view, w) -- view:add_signal("new-window-decision", function (v, uri, reason) -- if reason == "link-clicked" then -- window.new({uri}) -- else -- w:new_tab(uri) -- end -- return true -- end) --end, create_webview = function (view) -- Return a newly created webview in a new tab view:add_signal("create-web-view", function (v) local opts = { private = v.private, no_initial_url = true } return _M.window(v):new_tab(nil, opts) end) end, popup_fix_open_link_label = function (view) view:add_signal("populate-popup", function (_, menu) for _, item in ipairs(menu) do if type(item) == "table" then -- Optional underscore represents alt-key shortcut letter item[1] = string.gsub(item[1], "New (_?)Window", "New %1Tab") end end end) end, enable_pdfjs = function (view) local pdfjs_enabled = settings.get_setting("application.enable_pdfjs") view:set_pdfjs(pdfjs_enabled or false) end, } --- These methods are present when you index a window instance and no window -- method is found in `window.methods`. The window then checks if there is an -- active webview and calls the following methods with the given view instance -- as the first argument. All methods must take `view` & `w` as the first two -- arguments. -- @readwrite -- @type {[string]=function} _M.methods = { -- Reload with or without ignoring cache reload = function (view, _, bypass_cache) if bypass_cache then view:reload_bypass_cache() else view:reload() end end, -- Toggle source view toggle_source = function (view, _, show) if show == nil then view.view_source = not view.view_source else view.view_source = show end view:reload() end, -- Zoom functions zoom_in = function (view, _, step) step = step or settings.get_setting("window.zoom_step") view.zoom_level = view.zoom_level + step end, zoom_out = function (view, _, step) step = step or settings.get_setting("window.zoom_step") view.zoom_level = math.max(0.01, view.zoom_level) - step end, zoom_set = function (view, _, level) view.zoom_level = level or 1.0 end, -- History traversing functions back = function (view, _, n) view:go_back(n or 1) view:emit_signal("go-back-forward", -(n or 1)) end, forward = function (view, _, n) view:go_forward(n or 1) view:emit_signal("go-back-forward", (n or 1)) end, } --- Scroll the current webview by a given amount. -- @tparam widget view The webview widget to scroll. -- @tparam table w The window class table for the window containing `view`. -- @tparam table new Table of scroll information. function _M.methods.scroll(view, w, new) local s = view.scroll for _, axis in ipairs{ "x", "y" } do -- Relative px movement if rawget(new, axis.."rel") then s[axis] = s[axis] + new[axis.."rel"] -- Relative page movement elseif rawget(new, axis .. "pagerel") then s[axis] = s[axis] + math.ceil(s[axis.."page_size"] * new[axis.."pagerel"]) -- Absolute px movement elseif rawget(new, axis) then local n = new[axis] if n == -1 then local dir = axis == "x" and "Width" or "Height" local js = string.format([=[ Math.max(window.document.documentElement.scroll%s - window.inner%s, 0) ]=], dir, dir) w.view:eval_js(js, { callback = function (max) s[axis] = max end}) else s[axis] = n end -- Absolute page movement elseif rawget(new, axis.."page") then s[axis] = math.ceil(s[axis.."page_size"] * new[axis.."page"]) -- Absolute percent movement elseif rawget(new, axis .. "pct") then local dir = axis == "x" and "Width" or "Height" local js = string.format([=[ Math.max(window.document.documentElement.scroll%s - window.inner%s, 0) ]=], dir, dir) w.view:eval_js(js, { callback = function (max) s[axis] = math.ceil(max * (new[axis.."pct"]/100)) end}) end end end local wrap_widget_metatable do local wrapped = false wrap_widget_metatable = function (view) if wrapped then return end wrapped = true local mt = getmetatable(view) local oi = mt.__index mt.__index = function (w, k) if (k == "uri" or k == "session_state") and oi(w, "type") == "webview" then local ws = webview_state[w] if not next(ws.blockers) then return oi(w, k) end local ql = ws.queued_location or {} if k == "uri" then return ql.uri or oi(w, k) end if k == "session_state" then return ql.session_state or oi(w, k) end end return oi(w, k) end end end --- Create a new webview instance. -- @tparam table opts Table of options. Currently only `private` is recognized -- as a key. -- @treturn table The newly-created webview widget. function _M.new(opts) assert(opts) local view = widget{type = "webview", private = opts.private} webview_state[view] = { blockers = {} } wrap_widget_metatable(view) -- Call webview init functions for _, func in pairs(init_funcs) do func(view) end _M.emit_signal("init", view) return view end luakit.idle_add(function () local undoclose = package.loaded.undoclose if not undoclose then return end undoclose.add_signal("save", function (view) if view.private then return false end end) end) --- Wrapper for @ref{window/ancestor|window.ancestor}. -- @tparam widget view The webview whose ancestor to find. -- @treturn table|nil The window class table for the window that contains `view`, -- or `nil` if `view` is not contained within a window. function _M.window(view) assert(type(view) == "widget" and view.type == "webview") return window.ancestor(view) end --- Add/remove a load block on the given webview. -- If a block is enabled on a webview, load requests will be suspended until the -- block is removed. This is useful for pausing network operations while a -- module is initializing. -- @tparam widget view The view on which to add/remove the load block. -- @tparam string name The name of the block to add/remove. -- @tparam boolean enable Whether the block should be enabled. function _M.modify_load_block(view, name, enable) assert(type(view) == "widget" and view.type == "webview") assert(type(name) == "string") local ws = webview_state[view] ws.blockers[name] = enable and true or nil msg.verbose("%s %s %s", view, name, enable and "block" or "unblock") if not next(ws.blockers) and ws.queued_location then msg.verbose("fully unblocked %s", view) local queued = ws.queued_location ws.queued_location = nil _M.set_location(view, queued) end end --- Check whether the given webview has a load block. -- @tparam widget view The webview. -- @treturn boolean `true` if the given webview has a load block. function _M.has_load_block(view) assert(type(view) == "widget" and view.type == "webview") return next(webview_state[view].blockers) ~= nil end --- Set the location of the webview. This method will respect any load blocks in -- place (see @ref{modify_load_block}). -- @tparam widget view The view whose location to modify. -- @tparam table arg The new location. Can be a URI, a JavaScript URI, or a -- table with `session_state` and `uri` keys. function _M.set_location(view, arg) assert(type(view) == "widget" and view.type == "webview") assert(type(arg) == "string" or type(arg) == "table") -- Always execute JS URIs immediately, even when webview is blocked if type(arg) == "string" and arg:match("^javascript:") then local js = string.match(arg, "^javascript:(.+)$") return view:eval_js(luakit.uri_decode(js), { no_return = true, callback = function (_, err) local w = window.ancestor(view) w:error(err) end, }) end if type(arg) == "string" then arg = { uri = arg } end assert(arg.uri or arg.session_state) local ws = webview_state[view] if next(ws.blockers) then ws.queued_location = arg if arg.uri then view:emit_signal("property::uri") end return end if arg.session_state then view.session_state = arg.session_state if view.uri == "about:blank" and arg.uri then view.uri = arg.uri end else view.uri = arg.uri end end -- Insert webview method lookup on window structure table.insert(window.indexes, 1, function (w, k) if k == "view" then local view = w.tabs[w.tabs:current()] if view and type(view) == "widget" and view.type == "webview" then w.view = view return view end end -- Lookup webview method local func = _M.methods[k] if not func then return end local view = w.view if view then return function (_, ...) return func(view, w, ...) end end end) local webview_settings = { ["webview.allow_file_access_from_file_urls"] = { type = "boolean", default = false, domain_specific = false, desc = "Whether `file://` URIs are allowed to access other local files via JavaScript.", }, ["webview.allow_modal_dialogs"] = { type = "boolean", default = false, desc = "Whether JavaScript will be able to create and run modal dialogs with `window.showModalDialog`.", }, ["webview.allow_universal_access_from_file_urls"] = { type = "boolean", default = false, domain_specific = false, desc = "Whether `file://` URIs are allowed to access content from any origin via JavaScript.", }, ["webview.auto_load_images"] = { type = "boolean", default = true, desc = "Whether images should be automatically loaded. Disabling this is useful for reducing data transfer.", }, ["webview.cursive_font_family"] = { type = "string", default = "serif", desc = "The font family used for content using the `cursive` font.", }, ["webview.default_charset"] = { type = "string", default = "iso-8859-1", desc = "The default text character set used when content does not explicitly specify a character set.", }, ["webview.default_font_family"] = { type = "string", default = "sans-serif", desc = "The font family used for content that does not specify a font.", }, ["webview.default_font_size"] = { type = "number", min = 0, default = "16", desc = "The default font size (in pixels) to use for web content that does not specify a main font size.", }, ["webview.default_monospace_font_size"] = { type = "number", min = 0, default = "13", desc = ([=[ The default font size (in pixels) to use for monospace web content when no main font size is specified. ]=]) }, ["webview.draw_compositing_indicators"] = { type = "boolean", default = false, desc = [=[ Whether compositing indicators should be shown. These, indicators show composited regions on the page as well as a repaint, counter for each; this is mostly useful for debugging. ]=], }, ["webview.enable_accelerated_2d_canvas"] = { type = "boolean", default = false, desc = [=[ Whether 2d canvas rendering should use hardware acceleration. This setting requires WebKit support that may not be available. ]=], }, ["webview.enable_caret_browsing"] = { type = "boolean", default = false, desc = "Whether keyboard navigation should be enabled.", }, ["webview.enable_developer_extras"] = { type = "boolean", default = false, desc = "Whether developer tools should be enabled.", }, ["webview.enable_dns_prefetching"] = { type = "boolean", default = false, desc = [=[ Whether domain names should be resolved speculatively. If enabled, DNS prefetching attempts to resolve domain names before any links are clicked, making web browsing faster. ]=], }, ["webview.enable_frame_flattening"] = { type = "boolean", default = false, desc = [=[ Whether frame flattening should be enabled. If enabled, the content of all subframes is shown directly in the main page. ]=], }, ["webview.enable_fullscreen"] = { type = "boolean", default = true, desc = [=[ Whether web pages should be allowed to request fullscreen display, via the JavaScript Fullscreen API. ]=], }, ["webview.enable_html5_database"] = { type = "boolean", default = true, desc = [=[ Whether web pages should be allowed access to a client-side SQL databse. This provides structured data storage. Web pages from one site cannot access data stored in the database by pages from other sites. ]=], }, ["webview.enable_html5_local_storage"] = { type = "boolean", default = true, desc = [=[ Whether web pages should be allowed to access HTML5 local storage support. This provides a simple synchronous database. Web pages from one site cannot access data stored in the database by pages from other sites. ]=], }, ["webview.enable_hyperlink_auditing"] = { type = "boolean", default = false, desc = [=[ Whether hyperlink auditing is enabled. See for more information. ]=], }, ["webview.enable_java"] = { type = "boolean", default = true, desc = "Whether the Java plugin is enabled.", }, ["webview.enable_javascript"] = { type = "boolean", default = true, desc = "Whether JavaScript content is executed.", }, ["webview.enable_mediasource"] = { type = "boolean", default = false, desc = "Whether MediaSource content is enabled.", }, ["webview.enable_media_stream"] = { type = "boolean", default = false, desc = "Whether to allow web pages to access audio and video devices for capture.", }, ["webview.enable_page_cache"] = { type = "boolean", default = true, desc = [=[ Whether the page cache should be enabled. This speeds up forward/backward navigation considerably. Disabling this setting is only useful to conserve memory. ]=], }, ["application.enable_pdfjs"] = { type = "boolean", default = true, desc = [=[ Whether to load PDFs in a built-in javascript renderer. The default is to pass them on to the OS. This setting only becomes active in new tabs.]=] }, ["webview.enable_plugins"] = { type = "boolean", default = true, desc = "Whether plugins are enabled." }, ["webview.enable_resizable_text_areas"] = { type = "boolean", default = true, desc = "Whether text areas in web pages can be resized." }, ["webview.enable_site_specific_quirks"] = { type = "boolean", default = true, desc = [=[ Whether WebKit should use site-specific quirks to work around websites with known compatibility issues. ]=], }, ["webview.enable_smooth_scrolling"] = { type = "boolean", default = false, desc = "Whether smooth scrolling should be used." }, ["webview.enable_spatial_navigation"] = { type = "boolean", default = false, desc = "Whether spatial navigation should be enabled.", }, ["webview.enable_tabs_to_links"] = { type = "boolean", default = true, desc = "Whether pressing the `Tab` key on the web page should cycle through link elements.", }, ["webview.enable_webaudio"] = { type = "boolean", default = false, desc = "Whether support for WebAudio should be enabled.", }, ["webview.enable_webgl"] = { type = "boolean", default = false, desc = "Whether support for WebGL should be enabled.", }, ["webview.enable_write_console_messages_to_stdout"] = { type = "boolean", default = false, desc = "Whether console messages from JavaScript should be written to standard output.", }, ["webview.enable_xss_auditor"] = { type = "boolean", default = true, desc = [=[ Whether XSS auditing should be enabled. This helps protect against some attacks on vulnerable websites. ]=], }, ["webview.fantasy_font_family"] = { type = "string", default = "serif", desc = "The font family used for content using the `fantasy` font.", }, ["webview.hardware_acceleration_policy"] = { type = "enum", options = { ["on-demand"] = { desc = "Enable/disable hardware acceleration as necessary.", label = "On-demand", }, ["always"] = { desc = "Always enable hardware acceleration.", label = "Always", }, ["never"] = { desc = "Always disable hardware acceleration.", label = "Never", }, }, default = "always", desc = "The policy used to determine when hardware acceleration should be used to render web content.", }, ["webview.javascript_can_access_clipboard"] = { type = "boolean", default = false, desc = "Whether JavaScript should be able to access the clipboard.", }, ["webview.javascript_can_open_windows_automatically"] = { type = "boolean", default = false, desc = "Whether JavaScript can open windows without user intervention.", }, ["webview.media_playback_allows_inline"] = { type = "boolean", default = true, desc = "Whether media playback is allowed in an inline window; the alternative is fullscreen playback.", }, ["webview.media_playback_requires_gesture"] = { type = "boolean", default = false, desc = "Whether a user gesture is required before media playback/loading can start.", }, ["webview.minimum_font_size"] = { type = "number", min = 0, default = 0, desc = "The minimum font size (in pixels) at which text should be rendered.", }, ["webview.monospace_font_family"] = { type = "string", default = "monospace", desc = "The font family used for content using a monospace font.", }, ["webview.pictograph_font_family"] = { type = "string", default = "serif", desc = "The font family used for content using the `pictograph` font.", }, ["webview.print_backgrounds"] = { type = "boolean", default = true, desc = "Whether background images should be shown when printing a web page.", }, ["webview.sans_serif_font_family"] = { type = "string", default = "sans-serif", desc = "The font family used for content using a sans-serif font.", }, ["webview.serif_font_family"] = { type = "string", default = "serif", desc = "The font family used for content using a serif font.", }, ["webview.zoom_level"] = { type = "number", min = 0, default = 100, desc = "The default zoom level, as a percentage, at which to draw content.", }, -- ["webview.zoom_text_only"] = { -- type = "boolean", -- default = false, -- desc = "Whether zooming the page should affect the size of all elements, or only the text content.", -- }, } settings.register_settings(webview_settings) settings.register_settings({ ["webview.user_agent"] = { type = "string", default = "", desc = [=[ The user agent used when making HTTP requests. If left blank, the default WebKit user agent is used. ]=], }, }) _M.add_signal("init", function (view) local set = function (wv, k, v, match) -- hand through webview.-prefixed settings if v ~= nil and k:find('webview.', 1, true) then k = k:sub(9) -- Strip off prefix if k == "zoom_level" then v = v/100.0 end if k == "user_agent" and v == "" then v = nil end -- bug: when zoom_text_only is loaded after zoom_level, -- the zoom_level resets to 0.5. Keep zoom_text_only ignored -- until the settings can be applied in a controlled order if k == "zoom_text_only" then return end match = match and (" (matched '"..match.."')") or "" msg.verbose("setting property %s = %s" .. match, k, v, match) wv[k] = v end end local set_all = function (vv) for k in pairs(webview_settings) do local v, match = settings.get_setting_for_view(vv, k) set(vv, k, v, match) end end -- Set domain-specific values on page load view:add_signal("load-status", function (v, status) if v.uri == "about:blank" then return elseif status == "provisional" or status == "redirected" then local val, match = settings.get_setting_for_view(v, "webview.user_agent") set(v, "webview.user_agent", val, match) elseif status == "committed" then set_all(v) end end) view:add_signal("web-extension-loaded", function (v) -- Explicitly set the zoom, due to a WebKit bug that resets the -- apparent zoom level to 100% after a crash set(v, "webview.zoom_level", settings.get_setting("webview.zoom_level")) end) end) settings.migrate_global("webview.zoom_level", "default_zoom_level") settings.migrate_global("webview.user_agent", "user_agent") -- Migrate from globals.domain_props local globals = package.loaded.globals or {} local dp = globals.domain_props or {} local dp_all = dp.all or {} dp.all = nil for domain, props in pairs(dp) do for k, v in pairs(props) do if k == "enable_scripts" then k = "enable_javascript" end if k == "zoom_level" then v = v*100 end settings.add_migration_warning(string.format('on["%s"].webview.%s', domain, k), v) settings.on[domain].webview[k] = v end end for k, v in pairs(dp_all) do if k == "enable_scripts" then k = "enable_javascript" end if k == "zoom_level" then v = v*100 end settings.add_migration_warning("webview.".. k, v) settings.webview[k] = v end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/webview_wm.lua000066400000000000000000000017661475363222200164520ustar00rootroot00000000000000-- Webview widget wrapper - web module. -- -- The webview module wraps the webview widget provided by luakit, adding -- several convenience APIs and providing basic functionality. -- -- @submodule webview -- @copyright 2017 Aidan Holm -- @copyright 2012 Mason Larobina local ui = ipc_channel("webview_wm") ui:add_signal("load-finished", function(_, page) if not page then return end local doc = page.document -- do nothing if loaded document is not HTML if not doc.body then return end if page.uri:find("luakit://", 1, true) == 1 then doc.body:add_event_listener("click", true, function (_, event) if event.button ~= 0 then return end if event.target.tag_name ~= "A" then return end if (event.target.attr.href or ""):find("file://", 1, true) ~= 1 then return end ui:emit_signal("navigate", page.id, event.target.attr.href) end) end end) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/lib/window.lua000066400000000000000000000727061475363222200156100ustar00rootroot00000000000000--- Main window UI. -- -- The window module builds the UI for each luakit window, and manages modes, -- keybind state, and common functions like tab navigation and management. -- -- @module window -- @copyright 2017 Aidan Holm -- @copyright 2012 Mason Larobina require "lfs" local lousy = require("lousy") local settings = require("settings") local theme = lousy.theme.get() local _M = {} lousy.signal.setup(_M, true) --- Map of `window` widgets to window class tables. -- @type table -- @readonly _M.bywidget = setmetatable({}, { __mode = "k" }) -- Private data for windows local w_priv = setmetatable({}, { __mode = "k" }) -- Widget construction aliases local function entry() return widget{type="entry"} end local function eventbox() return widget{type="eventbox"} end local function hbox() return widget{type="hbox"} end local function label() return widget{type="label"} end local function notebook() return widget{type="notebook"} end local function vbox() return widget{type="vbox"} end local function overlay() return widget{type="overlay"} end --- Construction function which will build and arrange the window's widgets. -- @tparam table w The initial window class table. function _M.build(w) -- Create a table for widgets and state variables for a window local ww = { win = widget{type="window"}, ebox = eventbox(), layout = vbox(), tabs = notebook(), -- Status bar widgets sbar = { layout = hbox(), ebox = eventbox(), -- Left aligned widgets l = { layout = hbox(), ebox = eventbox(), }, -- Fills space between the left and right aligned widgets sep = eventbox(), -- Right aligned widgets r = { layout = hbox(), ebox = eventbox(), }, }, -- Vertical menu window widget (completion results, bookmarks, qmarks, ..) menu = lousy.widget.menu(), menu_tabs = overlay(), mbar = { ebox = eventbox(), label = label(), }, -- Input bar widgets ibar = { layout = hbox(), ebox = eventbox(), prompt = label(), input = entry(), }, bar_layout = widget{type="stack"}, } -- Replace values in w for k, v in pairs(ww) do w[k] = v end -- Tablist widget w.tablist = lousy.widget.tablist(w.tabs, "horizontal") w.ebox.child = w.layout w.layout:pack(w.tablist.widget) w.menu_tabs.child = w.tabs w.win.child = w.ebox w.layout:pack(w.menu_tabs, { expand = true, fill = true }) -- Pack left-aligned statusbar elements local l = w.sbar.l l.layout.homogeneous = false; l.ebox.child = l.layout -- Pack right-aligned statusbar elements local r = w.sbar.r r.layout.homogeneous = false; r.ebox.child = r.layout -- Pack status bar elements local s = w.sbar s.layout.homogeneous = false; s.layout:pack(l.ebox) s.layout:pack(s.sep, { expand = true, fill = true }) s.layout:pack(r.ebox) s.ebox.child = s.layout w.bar_layout:pack(s.ebox) -- Pack message bar local m = w.mbar m.ebox.child = m.label w.bar_layout:pack(m.ebox) -- Pack menu widget w.menu_tabs:pack(w.menu.widget, { halign = "fill", valign = "end" }) w.menu:hide() -- Pack input bar local i = w.ibar i.layout.homogeneous = false; i.layout:pack(i.prompt) i.layout:pack(i.input, { expand = true, fill = true }) i.ebox.child = i.layout w.bar_layout:pack(i.ebox) i.input.css = "border: 0;" i.layout.css = "transition: 0.0s ease-in-out;" i.input.css = "transition: 0.0s ease-in-out;" m.label.align = { v = "center" } i.prompt.align = { v = "center" } s.layout.align = { v = "center" } w.bar_layout.homogeneous = true w.layout:pack(w.bar_layout) -- Other settings i.input.show_frame = false w.tabs.show_tabs = false w.sbar.layout.margin_left = 3 w.sbar.layout.margin_right = 3 -- Allow error messages to be copied -- TODO: *only* allow copying when showing an error w.ibar.prompt.selectable = true -- Allows indexing of window struct by window widget _M.bywidget[w.win] = w end local function window_notebook_page_switch_cb (nb) local w = _M.ancestor(nb) if not w or w.tabs ~= nb then return end w:set_mode() w.view = nil -- Update widgets after tab switch luakit.idle_add(function () -- Cancel if window already destroyed if not w.win then return end w.view:emit_signal("switched-page") w:update_win_title() end) end local function set_window_notebook(w, nb) assert(w_priv[w], "invalid window table") assert(type(nb) == "widget" and nb.type, "invalid notebook widget") local old_nb = w_priv[w].tabs if old_nb then old_nb:remove_signal("switch-page", window_notebook_page_switch_cb) end nb:add_signal("switch-page", window_notebook_page_switch_cb) w_priv[w].tabs = nb end -- Table of functions to call on window creation. Normally used to add signal -- handlers to the new windows widgets. local init_funcs = { last_win_check = function (w) w.win:add_signal("destroy", function () -- call the quit function if this was the last window left if #luakit.windows == 0 then luakit.quit() end if w.close_win then w:close_win() end end) end, key_press_match = function (w) w.win:add_signal("key-press", function (_, mods, key, synthetic) if synthetic and settings.get_setting("window.act_on_synthetic_keys") then return false end -- Match & exec a bind local success, match = xpcall( function () return w:hit(mods, key) end, function (err) w:error(debug.traceback(err, 3)) end) if success and match then return true end end) end, tablist_tab_click = function (w) w.tablist:add_signal("tab-clicked", function (_, index, _, button) if button == 1 then w.tabs:switch(index) return true elseif button == 2 then w:close_tab(w.tabs[index]) return true end end) end, apply_window_theme = function (w) local s, m, i = w.sbar, w.mbar, w.ibar -- Set foregrounds for wi, v in pairs({ [i.prompt] = theme.prompt_ibar_fg, [i.input] = theme.input_ibar_fg, }) do wi.fg = v end -- Set backgrounds for wi, v in pairs({ [s.l.ebox] = theme.sbar_bg, [s.r.ebox] = theme.sbar_bg, [s.sep] = theme.sbar_bg, [s.ebox] = theme.sbar_bg, [i.ebox] = theme.ibar_bg, [i.input] = theme.input_ibar_bg, }) do wi.bg = v end -- Set fonts for wi, v in pairs({ [m.label] = theme.prompt_ibar_font, [i.prompt] = theme.prompt_ibar_font, [i.input] = theme.input_ibar_font, }) do wi.font = v end end, set_dark_mode = function (w) local dark_mode = settings.get_setting("application.prefer_dark_mode") w.win:set_dark_mode(dark_mode) end, set_default_size = function (w) local size = settings.get_setting("window.new_window_size") if string.match(size, "^%d+x%d+$") then w.win:set_default_size(string.match(size, "^(%d+)x(%d+)$")) else msg.warn("invalid window size: %q", size) end end, set_window_icon = function (w) local path = (luakit.dev_paths and os.exists("./extras/luakit.png")) or os.exists(luakit.install_paths.pixmap_dir .. "/luakit.png") if path then w.win.icon = path end end, clear_urgency_hint = function (w) w.win:add_signal("focus", function () w.win.urgency_hint = false end) end, hide_ui_on_fullscreen = function (w) w.win:add_signal("property::fullscreen", function (win) w:update_sbar_visibility() w.tablist.visible = not win.fullscreen end) end, check_before_closing_last_window = function (w) w.win:add_signal("can-close", function () return w:close_win() == nil and true or false end) end, } --- Helper functions which operate on the window widgets or structure. -- @type {[string]=function} -- @readwrite _M.methods = { -- Wrapper around the bind plugin's hit method hit = function (w, mods, key, opts) opts = lousy.util.table.join(opts or {}, { enable_buffer = w:is_mode("normal"), buffer = w.buffer, }) local caught, newbuf = lousy.bind.hit(w, w.binds, mods, key, opts) if w.win then -- Check binding didn't cause window to exit w.buffer = newbuf w:update_buf() end return caught end, -- Wrapper around the bind plugin's match_cmd method match_cmd = function (w, buffer) local get_mode = require("modes").get_mode return lousy.bind.match_cmd(w, get_mode("command").binds, buffer) end, -- enter command or characters into command line enter_cmd = function (w, cmd, opts) w:set_mode("command") w:set_input(cmd, opts) end, -- run command as if typed into the command line run_cmd = function (w, cmd, opts) cmd = cmd:find("^%:") and cmd or (":" .. cmd) w:enter_cmd(cmd, opts) -- Don't append to the mode's history local mode, hist = w.mode, w.mode.history w.mode.history = nil w:activate() mode.history = hist end, -- Emulates pressing the Return key in input field activate = function (w) w.ibar.input:emit_signal("activate") end, -- Shows a notification until the next keypress of the user. notify = function (w, msg, set_mode) if set_mode ~= false then w:set_mode() end w:set_prompt(msg, { fg = theme.notif_fg, bg = theme.notif_bg }) end, warning = function (w, msg, set_mode) if set_mode ~= false then w:set_mode() end w:set_prompt(msg, { fg = theme.warning_fg, bg = theme.warning_bg }) end, error = function (w, msg, set_mode) if set_mode ~= false then w:set_mode() end w:set_prompt("Error: "..msg, { fg = theme.error_fg, bg = theme.error_bg }) end, update_sbar_visibility = function (w) if (not w.win.fullscreen) or w_priv[w].prompt_text or w_priv[w].input_text then w.bar_layout.visible = true else w.bar_layout.visible = false end if w_priv[w].input_text then w.bar_layout.visible_child = w.ibar.ebox elseif w_priv[w].prompt_text then w.bar_layout.visible_child = w.mbar.ebox else w.bar_layout.visible_child = w.sbar.ebox end end, -- Set and display the prompt set_prompt = function (w, text, opts) opts = opts or {} -- Set theme local fg, bg = opts.fg or theme.ok.fg, opts.bg or theme.ok.bg w.ibar.input.fg = fg local function set_widget (prompt) prompt.fg = fg prompt.parent.bg = bg -- Set text, or hide if text then prompt.text = opts.markup and text or lousy.util.escape(text) prompt:show() else prompt:hide() end end set_widget(w.ibar.prompt) set_widget(w.mbar.label) w_priv[w].prompt_text = text w:update_sbar_visibility() end, -- Set display and focus the input bar set_input = function (w, text, opts) local input = w.ibar.input opts = opts or {} -- Set theme local fg, bg = opts.fg or theme.ibar_fg, opts.bg or theme.ibar_bg if input.fg ~= fg then input.fg = fg end if input.bg ~= bg then input.bg = bg end -- Set text or remain hidden if text then input.text = text input:focus() input.position = opts.pos or -1 end w_priv[w].input_text = text w:update_sbar_visibility() end, set_ibar_theme = function (w, name) name = name or "ok" local th = theme[name] w.ibar.input.fg = th.fg w.ibar.prompt.fg = th.fg w.ibar.layout.bg = th.bg end, update_win_title = function (w) local uri, title = w.view.uri, w.view.title title = (title or "luakit") .. ((uri and " - " .. uri) or "") local max = settings.get_setting("window.max_title_len") if utf8.len(title) > max then local suffix = "..." title = title:sub(1, utf8.offset(title, max+1-#suffix)-1) .. suffix assert(utf8.len(title) == max) end w.win.title = title end, update_buf = function () end, update_binds = function (w, mode) -- Generate the list of active key & buffer binds for this mode local get_mode = require("modes").get_mode w.binds = lousy.util.table.join((get_mode(mode) or {}).binds or {}, get_mode('all').binds or {}) -- Clear & hide buffer w.buffer = nil w:update_buf() end, new_tab = function (w, arg, opts) assert(arg == nil or type(arg) == "string" or type(arg) == "table" or (type(arg) == "widget" and arg.type == "webview")) opts = opts or {} assert(type(opts) == "table") local switch, order = opts.switch, opts.order -- Bit of a hack local webview = require("webview") local view if type(arg) == "widget" and arg.type == "webview" then view = arg local ww = webview.window(view) ww:detach_tab(view) w:attach_tab(view, switch, order) end if not view and settings.get_setting("window.reuse_new_tab_pages") then for _, tab in ipairs(w.tabs.children) do if tab.uri == settings.get_setting("window.new_tab_page") then msg.verbose("new_tab: using existing blank tab, %s", tab.uri) view = tab break end end end if not view then -- Make new webview widget view = webview.new({ private = opts.private }) if not arg and not opts.no_initial_url then view.uri = settings.get_setting("window.new_tab_page") end w:attach_tab(view, switch, order) end if switch ~= false then w.tabs:switch(w.tabs:indexof(view)) end if arg and not (type(arg) == "widget" and arg.type == "webview") then w:search_open_navigate(view, arg) end view:reload() return view end, -- close the current tab close_tab = function (w, view, blank_last) assert(view == nil or (type(view) == "widget" and view.type == "webview")) view = view or w.view w:emit_signal("close-tab", view) w:detach_tab(view, blank_last) view:destroy() end, attach_tab = function (w, view, switch, order) assert(view == nil or (type(view) == "widget" and view.type == "webview")) local taborder = package.loaded.taborder -- Get tab order function if not order and taborder then order = (switch == false and taborder.default_bg) or taborder.default end w.tabs:insert((order and order(w, view)) or -1, view) end, detach_tab = function (w, view, blank_last) assert(view == nil or (type(view) == "widget" and view.type == "webview")) view = view or w.view w:emit_signal("detach-tab", view) view.parent:remove(view) if settings.get_setting("window.close_with_last_tab") == true and w.tabs:count() == 0 then w:close_win() end -- Treat a blank last tab as an empty notebook (if blank_last=true) if blank_last ~= false and w.tabs:count() == 0 then w:new_tab(settings.get_setting("window.new_tab_page"), false) end end, can_quit = function (w) -- Ask plugins if it's OK to close last window local emsg = luakit.emit_signal("can-close") if emsg then assert(type(emsg) == "string", "invalid exit error message") w:error(string.format("Can't close luakit: %s (force close " .. "with :q! or :wq!)", emsg)) return false else return true end end, close_win = function (w, force) if w_priv[w].closing then return end if not force and (#luakit.windows == 1) and not w:can_quit() then return false end w_priv[w].closing = true w:emit_signal("close") -- Close all tabs while w.tabs:count() ~= 0 do w:close_tab(nil, false) end -- Destroy tablist w.tablist:destroy() -- Remove from window index _M.bywidget[w.win] = nil w_priv[w] = nil -- Clear window struct w = setmetatable(w, {}) -- Recursively remove widgets from window local children = lousy.util.recursive_remove(w.win) -- Destroy all widgets for _, c in ipairs(lousy.util.table.join(children, {w.win})) do if c.hide then c:hide() end c:destroy() end -- Remove all window table vars for k, _ in pairs(w) do w[k] = nil end -- Quit if closed last window if #luakit.windows == 0 then luakit.quit() end end, -- Navigate current view or open new tab navigate = function (w, arg, view) assert(arg == nil or type(arg) == "string" or type(arg) == "table") assert(view == nil or (type(view) == "widget" and view.type == "webview")) if not view then view = w.view end if view and arg then w:search_open_navigate(view, arg) else w:new_tab(arg) end end, -- Wrap @ref{set_location} to filter a string argument through @ref{search_open} -- @tparam widget view The view whose location to modify. -- @tparam table arg The new location. Can be a query to search, a URI, -- a JavaScript URI, or a table with `session_state` and `uri` keys. search_open_navigate = function (w, view, arg) assert(type(view) == "widget" and view.type == "webview") assert(type(arg) == "string" or type(arg) == "table" or type(arg) == "widget") if type(arg) == "widget" then assert(arg.type == "webview") end if type(arg) == "string" then arg = w:search_open(arg) end require("webview").set_location(view, arg) end, -- Save, restart luakit and reload session. restart = function (w, force) if not force and not w:can_quit() then return false end -- Generate luakit launch command. local args = {({string.gsub(luakit.execpath, " ", "\\ ")})[1]} for _, arg in ipairs(luakit.options) do table.insert(args, arg) end -- Get new config path local conf = assert(luakit.confpath) -- Check config has valid syntax local cmd = table.concat(args, " ") if luakit.spawn_sync(cmd .. " -k -c " .. conf) ~= 0 then return w:error("Cannot restart, syntax error in configuration file: "..conf) end -- Save session. require("session").save() -- Replace current process with new luakit instance. luakit.exec(cmd) end, -- Intelligent open command which can detect a uri or search argument. search_open = function (_, arg) local lstring = lousy.util.string local search_engines = settings.get_setting("window.search_engines") -- Detect blank uris if not arg or arg:match("^%s*$") then return settings.get_setting("window.new_tab_page") end arg = lstring.strip(arg) -- Handle JS and file URI before splitting arg if arg:find("^javascript:") then return arg end if settings.get_setting("window.check_filepath") then local path = arg:gsub("^file://", "") if lfs.attributes(path) then return "file://" .. path end end local args = lstring.split(arg) -- Guess if single argument is an address, etc. if #args == 1 and not search_engines[arg] and lousy.uri.is_uri(arg) then return arg end -- Find search engine (or use default_search_engine) local engine = settings.get_setting("window.default_search_engine") if args[1] and search_engines[args[1]] then engine = args[1] table.remove(args, 1) end local e = search_engines[engine] or "%s" local terms = table.concat(args, " ") if type(e) == "string" then if e:find("%%", 1, true) then return string.format(e, luakit.uri_encode(terms)) end terms = luakit.uri_encode(terms):gsub("%%", "%%%%") return ({e:gsub("%%s", terms)})[1] else return e(terms) end end, -- Increase (or decrease) the last found number in the current uri inc_uri = function (w, arg) local uri = string.gsub(w.view.uri, "(%d+)([^0-9]*)$", function (num, rest) return string.format("%0"..#num.."d", tonumber(num) + (arg or 1)) .. rest end) return uri end, -- Tab traversing functions next_tab = function (w, n) w.tabs:switch((((n or 1) + w.tabs:current() -1) % w.tabs:count()) + 1) end, prev_tab = function (w, n) w.tabs:switch(((w.tabs:current() - (n or 1) -1) % w.tabs:count()) + 1) end, goto_tab = function (w, n) if n and (n == -1 or n > 0) then return w.tabs:switch((n <= w.tabs:count() and n) or -1) end end, -- For each tab, switches to that tab and calls the given function passing -- it the view contained in the tab. each_tab = function (w, fn) for index = 1, w.tabs:count() do w:goto_tab(index) fn(w.tabs[index]) end end, -- If argument is form-active or root-active, emits signal. Ignores all -- other signals. emit_form_root_active_signal = function (w, s) if s == "form-active" then w.view:emit_signal("form-active") elseif s == "root-active" then w.view:emit_signal("root-active") end end, } --- Ordered list of class index functions. Other classes (E.g. webview) are able -- to add their own index functions to this list. -- @type {function} -- @readwrite _M.indexes = { -- Find function in window.methods first function (_, k) return _M.methods[k] end, function (w, k) return k == "tabs" and w_priv[w].tabs or nil end, } _M.add_signal("build", _M.build) --- Create a new window table instance. -- @tparam table args Array of initial tab arguments. -- @treturn table The newly-created window table. function _M.new(args) local w = {} w_priv[w] = {} -- Set window metatable setmetatable(w, { __index = function (_, k) -- Call each window index function for _, index in ipairs(_M.indexes) do local v = index(w, k) if v then return v end end end, __newindex = function (_, k, v) if k == "tabs" then return set_window_notebook(w, v) end rawset(w, k, v) end }) -- Setup window widget for signals lousy.signal.setup(w) _M.emit_signal("build", w) -- Call window init functions for _, func in pairs(init_funcs) do func(w) end _M.emit_signal("init", w) -- Populate notebook with tabs for _, arg in ipairs(args or {}) do w:new_tab(arg) end -- Show window w.win:show() -- Set initial mode w:set_mode() -- Make sure something is loaded if w.tabs:count() == 0 then w:new_tab(settings.get_setting("window.home_page"), false) end return w end --- Get the window that contains the given widget. -- @tparam widget w The widget whose ancestor to find. -- @treturn table|nil The window class table for the window that contains `w`, -- or `nil` if the given widget is not contained within a window. function _M.ancestor(w) repeat w = w.parent until w == nil or w.type == "window" return w and _M.bywidget[w] or nil end settings.register_settings({ ["application.prefer_dark_mode"] = { type = "boolean", default = false, desc = "Perfer dark CSS when the website supports it (requires restart).", }, ["window.act_on_synthetic_keys"] = { type = "boolean", default = false, desc = [=[ Whether synthetic key events should activate key bindings. Synthetic key events have been generated by a program rather than a physical key press, such as those sent by keysym.send(). ]=], }, ["window.new_window_size"] = { type = "string", default = "800x600", validator = function (v) local x, y = v:match("^(%d+)x(%d+)$") if not x or not y then return false end return tonumber(x) > 0 and tonumber(y) > 0 end, desc = [=[ The size (in pixels) of newly-opened windows. Must be in the form `WxY`, where `W` and `H` are the width and height respectively. ]=], }, ["window.home_page"] = { type = "string", default = "https://luakit.github.io/", desc = "The URI of the home page.", }, ["window.new_tab_page"] = { type = "string", default = "about:blank", desc = "The URI to open when opening a new tab.", }, ["window.reuse_new_tab_pages"] = { type = "boolean", default = false, desc = [=[ Let w:new_tab use an existing view that is on `window.new_tab_page`. Avoids unnecessarily creating new tabs, possibly multiple instances of `window.new_tab_page`. ]=], }, ["window.close_with_last_tab"] = { type = "boolean", default = false, desc = "Luakit windows should close after all of their tabs are closed.", }, ["window.search_engines"] = { type = "string:", default = { duckduckgo = "https://duckduckgo.com/?q=%s", github = "https://github.com/search?q=%s", google = "https://google.com/search?q=%s", imdb = "http://www.imdb.com/find?s=all&q=%s", wikipedia = "https://en.wikipedia.org/wiki/Special:Search?search=%s", default = "https://google.com/search?q=%s", }, desc = "The set of search engine shortcuts.", formatter = function (t, k) local v if type(t[k]) == "string" then v = t[k]:gsub("%%s", [[%%s]]) else v = [[function]] end return { key = [==[]==]..k..[==[]==], value = [==[]==]..v..[==[]==], } end, }, ["window.default_search_engine"] = { type = "string", default = "default", validator = function (v) return settings.get_setting("window.search_engines")[v] end, desc = [=[ The default search engine alias. Must be a key of `window.search_engines`. ]=], }, ["window.scroll_step"] = { type = "number", min = 0, default = 40, desc = "The size (in pixels) of the scroll step.", }, ["window.zoom_step"] = { type = "number", min = 0, default = 0.1, desc = "The size of the zoom step, expressed as a multiplicative factor.", }, ["window.load_etc_hosts"] = { type = "boolean", default = true, desc = "Whether `/etc/hosts` should be used when parsing URIs.", }, ["window.check_filepath"] = { type = "boolean", default = true, desc = "Whether opening a URI should check for local files.", }, ["window.max_title_len"] = { type = "number", default = 80, desc = "The maximum length of the window title.", }, }) settings.migrate_global("window.act_on_synthetic_keys", "act_on_synthetic_keys") settings.migrate_global("window.new_window_size", "default_window_size") settings.migrate_global("window.home_page", "homepage") settings.migrate_global("window.scroll_step", "scroll_step") settings.migrate_global("window.zoom_step", "zoom_step") settings.migrate_global("window.load_etc_hosts", "load_etc_hosts") settings.migrate_global("window.check_filepath", "check_filepath") settings.migrate_global("window.max_title_len", "max_title_len") local globals = package.loaded.globals if globals then settings.window.search_engines = globals.search_engines end local vulnerable_luakit = tonumber(luakit.webkit_version:match("^2%.(%d+)%.")) < 18 if vulnerable_luakit then luakit.idle_add(function () msg.warn([[your version of WebKit (%s) is outdated and vulnerable! See https://webkitgtk.org/security/WSA-2017-0009.html for details.]], luakit.webkit_version) end) end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/log.c000066400000000000000000000227051475363222200137470ustar00rootroot00000000000000/* * log.c - logging functions * * Copyright © 2016 Aidan Holm * * 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 . * */ #include "globalconf.h" #include "common/log.h" #include "common/luaserialize.h" #include "common/luaclass.h" #include "common/ipc.h" #include "clib/msg.h" #include #include #include static GHashTable *group_levels; static GAsyncQueue *queued_emissions; static gboolean block_log = FALSE; void log_set_verbosity(const char *group, log_level_t lvl) { group_levels = group_levels ?: g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); g_hash_table_insert(group_levels, g_strdup(group), GINT_TO_POINTER(lvl+1)); } /* Will modify the group name passed to it, unless that name is "all" */ log_level_t log_get_verbosity(char *group) { if (!group_levels) return LOG_LEVEL_info; gint len = strlen(group); log_level_t lvl; while (TRUE) { lvl = GPOINTER_TO_UINT(g_hash_table_lookup(group_levels, (gpointer)group)); if (lvl > 0) break; char *slash = strrchr(group, '/'); if (slash) *slash = '\0'; else { lvl = GPOINTER_TO_UINT(g_hash_table_lookup(group_levels, (gpointer)"all")); break; } } for (gint i = 0; i < len; ++i) if (group[i] == '\0') group[i] = '/'; return lvl-1; } static char * log_group_from_fct(const char *fct) { /* Strip off installation prefixes */ static GPtrArray *paths; if (!paths) { paths = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(paths, "./"); g_ptr_array_add(paths, g_build_path("/", LUAKIT_INSTALL_PATH, "lib/", NULL)); g_ptr_array_add(paths, g_build_path("/", LUAKIT_CONFIG_PATH, "/luakit/", NULL)); g_ptr_array_add(paths, g_build_path("/", globalconf.config_dir, "/", NULL)); } for (unsigned i = 0; i < paths->len; i++) if (g_str_has_prefix(fct, paths->pdata[i])) { fct += strlen(paths->pdata[i]); break; } int len = strlen(fct); gboolean core = !strcmp(&fct[len-2], ".c") || !strcmp(&fct[len-2], ".h"), lua = !strcmp(&fct[len-4], ".lua") || !strncmp(fct, "[string \"", 9); if (core) /* Strip .c or .lua off the end */ return g_strdup_printf("core/%.*s", len-2, fct); else if (lua) return g_strdup_printf("lua/%.*s", len-4, fct); else return g_strdup(fct); } int log_level_from_string(log_level_t *out, const char *str) { #define X(name) if (!strcmp(#name, str)) { \ *out = LOG_LEVEL_##name; \ return 0; \ } LOG_LEVELS #undef X return 1; } const char* log_string_from_level(log_level_t lvl) { switch (lvl) { #define X(name) case LOG_LEVEL_##name: return #name; LOG_LEVELS #undef X } g_assert_not_reached(); } static void emit_log_signal(double time, log_level_t lvl, const gchar *group, const gchar *msg) { lua_class_t *msg_class = msg_lib_get_msg_class(); lua_pushnumber(common.L, time); lua_pushstring(common.L, log_string_from_level(lvl)); lua_pushstring(common.L, group); lua_pushstring(common.L, msg); block_log = TRUE; luaH_class_emit_signal(common.L, msg_class, "log", 4, 0); block_log = FALSE; } typedef struct _queued_log_t { log_level_t lvl; double time; char *group; char *msg; } queued_log_t; static int consumer_added = FALSE; static gboolean log_emit_pending_signals(void *UNUSED(usedata)) { queued_log_t *entry; while ((entry = g_async_queue_try_pop(queued_emissions))) { emit_log_signal(entry->time, entry->lvl, entry->group, entry->msg); g_free(entry->group); g_free(entry->msg); g_slice_free(queued_log_t, entry); } g_atomic_int_set(&consumer_added, FALSE); return FALSE; } static void queue_log_signal(double time, log_level_t lvl, const gchar *group, const gchar *msg) { queued_log_t *entry = g_slice_new0(queued_log_t); entry->time = time; entry->lvl = lvl; entry->group = g_strdup(group); entry->msg = g_strdup(msg); g_async_queue_push(queued_emissions, entry); /* Add idle function to consume everything in the queue */ if (g_atomic_int_compare_and_exchange(&consumer_added, FALSE, TRUE)) g_idle_add(log_emit_pending_signals, NULL); } void _log(log_level_t lvl, const gchar *fct, const gchar *fmt, ...) { va_list ap; va_start(ap, fmt); va_log(lvl, fct, fmt, ap); va_end(ap); } void va_log(log_level_t lvl, const gchar *fct, const gchar *fmt, va_list ap) { if (block_log) return; char *group = log_group_from_fct(fct); log_level_t verbosity = log_get_verbosity(group); if (lvl > verbosity) goto done; gchar *msg = g_strdup_vprintf(fmt, ap); gint log_fd = STDERR_FILENO; double time = l_time() - globalconf.starttime; queue_log_signal(time, lvl, group, msg); /* Determine logging style */ /* TODO: move to X-macro generated table? */ gchar prefix_char, *style = ""; switch (lvl) { case LOG_LEVEL_fatal: prefix_char = 'F'; style = ANSI_COLOR_BG_RED; break; case LOG_LEVEL_error: prefix_char = 'E'; style = ANSI_COLOR_RED; break; case LOG_LEVEL_warn: prefix_char = 'W'; style = ANSI_COLOR_YELLOW; break; case LOG_LEVEL_info: prefix_char = 'I'; break; case LOG_LEVEL_verbose: prefix_char = 'V'; break; case LOG_LEVEL_debug: prefix_char = 'D'; break; default: g_assert_not_reached(); } /* Log format: [timestamp] level [group]: msg */ #define LOG_FMT "[%#12f] %c [%s]: %s" #define LOG_IND " " /* Indent new lines within the message */ static GRegex *indent_lines_reg; if (!indent_lines_reg) { GError *err = NULL; indent_lines_reg = g_regex_new("\n", 0, 0, &err); g_assert_no_error(err); } gchar *wrapped = g_regex_replace_literal(indent_lines_reg, msg, -1, 0, "\n" LOG_IND, 0, NULL); g_free(msg); msg = wrapped; if (!isatty(log_fd)) { gchar *stripped = strip_ansi_escapes(msg); g_free(msg); msg = stripped; g_fprintf(stderr, LOG_FMT "\n", time, prefix_char, group, msg); } else { g_fprintf(stderr, "%s" LOG_FMT ANSI_COLOR_RESET "\n", style, time, prefix_char, group, msg); } g_free(msg); if (lvl == LOG_LEVEL_fatal) exit(EXIT_FAILURE); done: g_free(group); } void ipc_recv_log(ipc_endpoint_t *UNUSED(ipc), const guint8 *lua_msg, guint length) { lua_State *L = common.L; gint n = lua_deserialize_range(L, lua_msg, length); g_assert_cmpint(n, ==, 3); log_level_t lvl = lua_tointeger(L, -3); const gchar *fct = lua_tostring(L, -2); const gchar *msg = lua_tostring(L, -1); _log(lvl, fct, "%s", msg); lua_pop(L, 3); } void log_init(void) { queued_emissions = g_async_queue_new(); const char *log_dump_file = getenv("LUAKIT_QUEUED_EMISSIONS_FILE"); unsetenv("LUAKIT_QUEUED_EMISSIONS_FILE"); if (!log_dump_file || !file_exists(log_dump_file)) return; char *dump; size_t len; GError *error = NULL; if (!g_file_get_contents(log_dump_file, &dump, &len, &error)) { error("unable to load previous log messages: %s", error->message); g_error_free(error); return; } unlink(log_dump_file); char *end = dump + len; g_async_queue_lock(queued_emissions); while (dump < end) { queued_log_t *entry = g_slice_new0(queued_log_t); entry->lvl = *dump++; entry->time = g_ascii_strtod(dump, &dump); entry->group = g_strdup(dump); dump += strlen(dump)+1; entry->msg = g_strdup(dump); dump += strlen(dump)+1; g_async_queue_push_unlocked(queued_emissions, entry); } g_async_queue_unlock(queued_emissions); } char * log_dump_queued_emissions(void) { GString *dump = g_string_new(NULL); g_async_queue_lock(queued_emissions); queued_log_t *entry; while ((entry = g_async_queue_try_pop_unlocked(queued_emissions))) { g_string_append_c(dump, (char) entry->lvl); g_string_append_printf(dump, "%f", entry->time); g_string_append(dump, entry->group); g_string_append_c(dump, '\0'); g_string_append(dump, entry->msg); g_string_append_c(dump, '\0'); g_free(entry->group); g_free(entry->msg); g_slice_free(queued_log_t, entry); } g_async_queue_unlock(queued_emissions); char *name_used = NULL; int log_dump_fd = g_file_open_tmp("luakit-log-dump.XXXXXX", &name_used, NULL); if (log_dump_fd != -1) { ssize_t written = write(log_dump_fd, dump->str, dump->len); close(log_dump_fd); if (written != (ssize_t)dump->len) { unlink(name_used); g_free(name_used); name_used = NULL; } } g_string_free(dump, TRUE); return name_used; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/log.h000066400000000000000000000021001475363222200137370ustar00rootroot00000000000000/* * log.h - logging functions * * Copyright © 2017 Aidan Holm * * 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 . * */ #ifndef LUAKIT_LOG_H #define LUAKIT_LOG_H #include "common/log.h" void log_init(void); int log_level_from_string(log_level_t *out, const char *str); void log_set_verbosity(const char *group, log_level_t lvl); log_level_t log_get_verbosity(char *group); char * log_dump_queued_emissions(void); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/luah.c000066400000000000000000000156631475363222200141240ustar00rootroot00000000000000/* * luah.c - Lua helper functions * * Copyright © 2010-2011 Mason Larobina * Copyright © 2008-2009 Julien Danjou * * 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 . * */ #include "ipc.h" #include "luah.h" #include "log.h" #include "common/luah.h" #include "common/luautil.h" #include "common/luayield.h" /* include clib headers */ #include "clib/download.h" #include "clib/luakit.h" #include "clib/request.h" #include "clib/sqlite3.h" #include "clib/soup.h" #include "clib/unique.h" #include "clib/widget.h" #include "clib/xdg.h" #include "clib/stylesheet.h" #include "clib/web_module.h" #include "clib/msg.h" #include "common/clib/ipc.h" #include "common/clib/timer.h" #include "common/clib/regex.h" #include "common/clib/utf8.h" #include "globalconf.h" #include #include #include void luaH_modifier_table_push(lua_State *L, guint state) { gint i = 1; lua_newtable(L); if (state & GDK_MODIFIER_MASK) { #define MODKEY(key, name) \ if (state & GDK_##key##_MASK) { \ lua_pushstring(L, name); \ lua_rawseti(L, -2, i++); \ } MODKEY(SHIFT, "Shift"); MODKEY(LOCK, "Lock"); MODKEY(CONTROL, "Control"); MODKEY(MOD1, "Mod1"); MODKEY(MOD2, "Mod2"); MODKEY(MOD3, "Mod3"); MODKEY(MOD4, "Mod4"); MODKEY(MOD5, "Mod5"); #undef MODKEY } } void luaH_keystr_push(lua_State *L, guint keyval) { gchar ucs[7]; guint ulen; guint32 ukval = gdk_keyval_to_unicode(keyval); /* check for printable unicode character */ if (g_unichar_isgraph(ukval)) { ulen = g_unichar_to_utf8(ukval, ucs); ucs[ulen] = 0; lua_pushstring(L, ucs); } /* sent keysym for non-printable characters */ else lua_pushstring(L, gdk_keyval_name(keyval)); } void luaH_init(gchar ** uris) { /* Lua VM init */ lua_State *L = common.L = luaL_newstate(); /* Set panic fuction */ lua_atpanic(L, luaH_panic); luaL_openlibs(L); luaH_fixups(L); luaH_object_setup(L); /* Export luakit lib */ luakit_lib_setup(L); /* Export xdg lib */ xdg_lib_setup(L); /* Export soup lib */ soup_lib_setup(L); if (!globalconf.nounique) /* Export unique lib */ unique_lib_setup(L); /* Export widget */ widget_class_setup(L); /* Export download */ download_class_setup(L); /* Export sqlite3 */ sqlite3_class_setup(L); /* Export timer */ timer_class_setup(L); /* Export regex */ regex_class_setup(L); /* Export utf8 */ utf8_lib_setup(L); /* Export request */ request_class_setup(L); /* Export stylesheet */ stylesheet_class_setup(L); /* Export web module */ web_module_lib_setup(L); ipc_channel_class_setup(L); /* Export web module */ msg_lib_setup(L); luaH_yield_setup(L); /* add Lua search paths */ luaH_add_paths(L, globalconf.config_dir); /* push a table of the startup uris */ const gchar *uri; lua_newtable(L); for (gint i = 0; uris && (uri = uris[i]); i++) { lua_pushstring(L, uri); lua_rawseti(L, -2, i + 1); } lua_setglobal(L, "uris"); } static gboolean luaH_loadrc(const gchar *confpath, gboolean run) { info("Loading rc: %s", confpath); lua_State *L = common.L; if (luaL_loadfile(L, confpath)) { error("Error loading rc: %s", lua_tostring(L, -1)); return FALSE; } if (!run) { lua_pop(L, 1); return TRUE; } return luaH_dofunction(L, 0, 0); } /* Load a configuration file. */ gboolean luaH_parserc(const gchar *confpath, gboolean run) { const gchar* const *config_dirs = NULL; gboolean ret = FALSE; GPtrArray *paths = NULL; /* try to load, return if it's ok */ if (confpath) { ret = luaH_loadrc(confpath, run); goto bailout; } /* compile list of config search paths */ paths = g_ptr_array_new_with_free_func(g_free); #if DEVELOPMENT_PATHS /* allows for testing luakit in the project directory */ g_ptr_array_add(paths, g_strdup("./config/rc.lua")); #endif /* search users config dir (see: XDG_CONFIG_HOME) */ g_ptr_array_add(paths, g_build_filename(globalconf.config_dir, "rc.lua", NULL)); /* search system config dirs (see: XDG_CONFIG_DIRS) */ config_dirs = g_get_system_config_dirs(); for(; *config_dirs; config_dirs++) g_ptr_array_add(paths, g_build_filename(*config_dirs, "luakit", "rc.lua", NULL)); /* get continuation variable; bail out if invalid */ char *i_str = getenv("LUAKIT_NEXT_CONFIG_INDEX"); gint i = i_str ? atoi(i_str) : 0; if (i_str && (i <= 0 || i >= (gint)paths->len)) goto bailout; /* Loop through paths until we have a config that exists: avoid needless execs */ for (; i < (gint)paths->len; i++) { const gchar *path = paths->pdata[i]; if (file_exists(path)) break; verbose("rc file '%s' does not exist", path); } if (i == (gint)paths->len) { warn("couldn't load any rc file"); goto bailout; } /* attempt to load the indicated config file */ const gchar *path = paths->pdata[i++]; if (luaH_loadrc(path, run)) { unsetenv("LUAKIT_NEXT_CONFIG_INDEX"); globalconf.confpath = g_strdup(path); ret = TRUE; goto bailout; } else warn("loading rc '%s' failed, falling back...", path); /* set continuation variable for replacement process */ i_str = g_strdup_printf("%i", i); setenv("LUAKIT_NEXT_CONFIG_INDEX", i_str, TRUE); g_free(i_str); /* exec path: escape spaces (why?) */ gchar **parts = g_strsplit(globalconf.execpath, " ", -1); gchar *escaped_execpath = g_strjoinv("\\ ", parts); g_strfreev(parts); /* rebuild argv */ GPtrArray *argv = globalconf.argv; g_ptr_array_insert(argv, 0, escaped_execpath); g_ptr_array_add(argv, NULL); verbose("exec: %s", g_strjoinv(" ", (gchar**)argv->pdata)); char *log_dump_file = log_dump_queued_emissions(); if (log_dump_file) { setenv("LUAKIT_QUEUED_EMISSIONS_FILE", log_dump_file, TRUE); g_free(log_dump_file); } ipc_remove_socket_file(); execvp(escaped_execpath, (gchar**)argv->pdata); bailout: if (paths) g_ptr_array_free(paths, TRUE); return ret; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/luah.h000066400000000000000000000021701475363222200141160ustar00rootroot00000000000000/* * luah.h - Lua helper functions * * Copyright © 2010 Mason Larobina * Copyright © 2008-2009 Julien Danjou * * 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 . * */ #ifndef LUAKIT_LUAH_H #define LUAKIT_LUAH_H #include "common/luah.h" void luaH_init(gchar **); gboolean luaH_parserc(const gchar *, gboolean); gint luaH_mtnext(lua_State *, gint); void luaH_modifier_table_push(lua_State *, guint); void luaH_keystr_push(lua_State *, guint); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/luakit.1.in000066400000000000000000000244561475363222200150070ustar00rootroot00000000000000.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .\" Luakit man page. .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .ds appname Luakit .ds cmdname luakit .ds manname LUAKIT .ds version LUAKITVERSION .ds year 2012 .ds date \*[year]-10-10 .ds appauthors Mason Larobina .ds manauthors Pierre Neidhardt . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TH \*[manname] 1 "\*[date]" "\*[appname] \*[version]" "User Commands" . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH NAME \*[appname] - Fast, small, WebKit based browser framework extensible by Lua. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH SYNOPSIS . .SY \*[cmdname] .RI [ OPTION ] " " [ URI ] .YS . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH DESCRIPTION \*[appname] is a highly configurable, browser framework based on the WebKit web content engine and the GTK+ toolkit. It is very fast, extensible by Lua and licensed under the GNU GPLv3 license. It is primarily targeted at power users, developers and any people with too much time on their hands who want to have fine-grained control over their web browsers behaviour and interface. . .SS Modes \*[appname] can run in various modes, which specify how the user interacts with the browser. .TP .B Normal mode This is the default mode when \*[appname] is started. You can load URIs, open tabs and windows, access to other modes, etc. .TP .B All mode Special meta-mode in which the bindings are present in all modes. .TP .B Insert mode When selecting form fields \*[appname] will enter the insert mode which allows you to enter text in form fields without accidentally triggering normal mode bindings. .TP .B Passthrough mode \*[appname] will pass every key event to the WebView until the user presses Escape. This is useful for using webpage shortcuts. .TP .B Command mode Enter commands. Every action in \*[appname] is a command, so basically you can do anything from there. .TP .B Lua mode Execute arbitrary Lua commands within the \*[appname] environment. . .SS Userscripts \*[appname] is highly extensible with userscripts written in Lua. These scripts may provide additional modes. Some are embedded by default. You will need to load the userscript to use its features. See the \fBCONFIGURATION\fR section for more details. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH OPTIONS .SS Help options .TP .BR -h ", " --help Show help options. .TP .BR --help-all Show all help options. .TP .BR --help-gtk Show GTK+ help options. . .SS Application options .TP .BR -k ", " --check Check configuration file and exit. .TP .BR -c ", " --config = \fIFILE\fR Configuration file to use. .TP .BR -n ", " --nonblock Fork \*[appname] into the background. .TP .BR -U ", " --nounique Ignore libunique bindings. .TP .BR -u ", " --uri = \fIURI\fR URI(s) to load at startup. .TP .BR -v ", " --verbose Print debugging output. .TP .BR -V ", " --version Print version and exit. .TP .BR --display = \fIDISPLAY\fR X display to use. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH KEYBINDINGS \*[appname] is fully usable with keyboard. Default bindings should be familiar to users of Emacs and Vim; nonetheless a lot of keyboards and mouse shortcuts are shared with popular web browsers. .P Pressing .B : will open the command prompt. You can enter commands from there. You can view the current list of bindings in \*[appname]'s help page with the .IP .B :help .P command. .P Some bindings have an uppercase version which will behave somewhat differently. Some come with userscripts, so you will need to have the appropriate userscript loaded to use it. Here follows a list of some noteworthy default bindings. . .TP .B ZZ Quit and save the session. .TP .B ZQ Quit without saving the session. . .TP .BR o ", " O Open specified URIs. If uppercase, edit current URI. .TP .BR t ", " T Open specified URIs in new tab. If uppercase, edit current URI. .TP .BR w ", " W Open specified URIs in new window. If uppercase, edit current URI. .P .BR ", " ", " ", " .br .BR h ", " j ", " k ", " l .RS 7 Scroll page vertically and horizontally. .RE .P .BR ", " .br .BR gg ", " G .RS 7 Go to top / bottom of the page. .RE .TP .BR [count]% Go to [count] percent of the page. .TP .BR f ", " F Enter follow mode. Use numbers or text to open corresponding links. Use arrow to navigate between links. If uppercase, open in new tab. Requires .I follow userscript. .TP .BR i Enter insert mode. In some case form fields may not receive characters; press an arrow key to insert characters correctly. .TP .BR gi Enter insert mode in the first form field. Requires .I go_input userscript. .TP .BR Toggle fullscreen. .TP .BR + ", " - Change the zoom level. .TP .BR = Restore zoom level. .TP .BR p ", " P Open URI from clipboard. If uppercase, open in new tab. .TP .BR y Yank current URI to clipboard. .TP .BR x ", " a Decrement / increment last number in URI. This is useful for forum threads or any ordered website. .TP .BR H ", " L Go back / forward in the browser history. .P .BR ", " .br .BR ", " .br .BR gT ", " gt .RS 7 Go to previous / next tab. .RE .TP .BR [0-9] Go to tab #, where # is between 0 and 9. .TP .BR < ", " > Reorder tabs. Requires .I taborder userscript. .TP .BR [count]d Close [count] tabs. .TP .BR u Restore last closed tab. .TP .BR gy Duplicate current tab. .TP .BR gh ", " gH Open homepage. If uppercase, open in new tab. .TP .BR r ", " R Reload current page. If uppercase, skip cache. .TP .BR c Stop loading the current page. .TP .BR z Enter passthrough mode. Use ESC to return to normal mode. .TP .BR M[a-zA-Z0-9] Associate current URI to quickmark #, where # is an ASCII letter or a digit. Requires .I quickmarks userscript. .TP .BR go[a-zA-Z0-9] ", " gn[a-zA-Z0-9] ", " gw[a-zA-Z0-9] Open specified quickmark in current tab / new tab / new window. Requires .I quickmarks userscript. .TP .BR B Add current URI to bookmarks. .TP .BR gb ", " gB Open bookmarks manager. If uppercase, open in new tab. .TP .BR / ", " ? Search / reverse search for a string on current page. .TP .BR n ", " N Find next / previous result from search. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH COMMANDS All bindings actually refer to commands. However, some commands do not have bindings associated by default. If .I completion userscript is used, you can autocomplete commands by pressing by default. Once again, you should consult the configuration files to get an exhaustive list. The currently available functions may be displayed from the help view, which you can open with the .B :help command. .P Here follows some noteworthy commands: .TP .B :bookmarks Display and search bookmarks. .TP .B :downloads Open Download page, which displays all downloads along with their status. .TP .B :dump Download current page. .TP .B :save Save the complete page as a single mhtml file. .TP .B :help Display all commands and bindings, along with their description. The help page also features some details about modes. .TP .B :history Display and search history. .TP .B :inspect Launch WebKit inspector. Use \fB:inspect!\fR to toggle off. .TP .B :nohlsearch Disable search highlighting. .TP .B :qmarks Display the quickmarks list. .TP .B :tabhistory Display the tab history. .TP .B :view-source Display page source code. Use \fB:view-source!\fR to toggle off. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH FILES .\" TODO: fix indentation. \*[appname] will load configuration files from the following folders in priority order: .IP .I $XDG_CONFIG_HOME/luakit .IP .I $XDG_CONFIG_DIRS/luakit .P Default configuration files: .TP .I binds.lua The keyboard and mouse bindings. .TP .I globals.lua General configuration, like home page, search engines, user agent, per-domain properties, cookies policy. .TP .I modes.lua All default modes are set in this file. .TP .I rc.lua Main configuration files. All other files are loaded from there. .TP .I theme.lua, Colors definition. .TP .I webview.lua WebKit related. .TP .I window.lua Status bar, windows and tabs behaviour. .P Embedded userscript will be loaded from \fI$XDG_DATA_DIRS/luakit/lib\fR. .P All browsing-related files are stored in \fI$XDG_DATA_HOME/luakit\fR. All of are created if needed and if they do not exist. Depending on the userscripts you are using, you may find: .TP .I bookmarks.db An SQLite3 database containing your complete bookmark list. If you want to synchronise your bookmarks between your different systems, just share this file. You can use a symbolic link if the file is not stored in the required folder. .TP .I cookies.db This file contains all details needed for websites keeping track of your status, like login information. Delete this file once you are finished with browsing if you are not on your personal system. .TP .I history.db Your browsing history. Delete this file once you are finished with browsing if you are not on your personal system. .TP .I quickmarks A plain text file saving your quickmarks. The structure is extremely simple: each line is a single quickmark; first character is the quickmark shortcut and may be one of [a-zA-Z0-9]; second character is a space; the remaining part is the URI. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH CONFIGURATION If you do not want to stick to the default configuration and want to benefit from the flexibility and extensibility of \*[appname], you can fully configure it from the Lua configuration files. .P If you do not want to start from scratch, you may use default configuration files and tweak them to fit your needs. Use the following command to copy configuration file to your home folder: .IP .EX cp -r $XDG_CONFIG_DIRS/luakit $XDG_CONFIG_HOME .EE .P You can now edit the new files. Configuration should be quite straightforward, even if you do not know much about Lua. . .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH AUTHORS \*[appname] was mainly developed by \*[appauthors]. Other contributors are listed in the .I AUTHORS file. . .P This man page was written by \*[manauthors]. luakit-2.4.0/luakit.c000066400000000000000000000223331475363222200144540ustar00rootroot00000000000000/* * luakit.c - luakit main functions * * Copyright © 2010-2011 Mason Larobina * * 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 . * */ #include "common/util.h" #include "globalconf.h" #include "luah.h" #include "ipc.h" #include "log.h" #include "web_context.h" #include #include #include #include #include #include #include #include #if !WEBKIT_CHECK_VERSION(2,16,0) #error Your version of WebKit is outdated! #endif /* Define two globals of the UI side; their extern declarations are in globalconf.h, and clib/widget.h, and so they're visible pretty much everywhere; there's also common for lua communication; there's a second definition of that in extension/extension (which is a separate process). */ common_t common; globalconf_t globalconf; lua_class_t widget_class; static void init_directories(void) { /* create luakit directory */ globalconf.cache_dir = g_build_filename(g_get_user_cache_dir(), "luakit", globalconf.profile, NULL); globalconf.config_dir = g_build_filename(g_get_user_config_dir(), "luakit", globalconf.profile, NULL); globalconf.data_dir = g_build_filename(g_get_user_data_dir(), "luakit", globalconf.profile, NULL); g_mkdir_with_parents(globalconf.cache_dir, 0700); g_mkdir_with_parents(globalconf.config_dir, 0700); g_mkdir_with_parents(globalconf.data_dir, 0700); } static void parse_log_level_option(gchar *log_lvl) { gchar **parts = g_strsplit(log_lvl, ",", 0); for (gchar **part = parts; *part; part++) { log_level_t lvl; if (!log_level_from_string(&lvl, *part)) log_set_verbosity("all", lvl); else { gchar *sep = strchr(*part, '='); if (sep && !log_level_from_string(&lvl, sep+1)) { *sep = '\0'; log_set_verbosity(*part, lvl); } else warn("ignoring unrecognized --log option '%s'", *part); } } g_strfreev(parts); } /* load command line options into luakit and return uris to load */ static gchar ** parseopts(int *argc, gchar *argv[], gboolean **nonblock) { GOptionContext *context; gboolean *version_only = NULL; gboolean *check_only = NULL; gchar **uris = NULL; globalconf.profile = NULL; gboolean verbose = FALSE; gchar *log_lvl = NULL; /* save luakit exec path */ globalconf.execpath = g_strdup(argv[0]); globalconf.nounique = FALSE; /* define command line options */ const GOptionEntry entries[] = { { "check", 'k', 0, G_OPTION_ARG_NONE, &check_only, "check config and exit", NULL }, { "config", 'c', 0, G_OPTION_ARG_STRING, &globalconf.confpath, "configuration file to use", "FILE" }, { "profile", 'p', 0, G_OPTION_ARG_STRING, &globalconf.profile, "profile name to use", "NAME" }, { "nonblock", 'n', 0, G_OPTION_ARG_NONE, nonblock, "run in background", NULL }, { "nounique", 'U', 0, G_OPTION_ARG_NONE, &globalconf.nounique, "ignore libunique bindings", NULL }, { "uri", 'u', 0, G_OPTION_ARG_STRING_ARRAY, &uris, "uri(s) to load at startup", "URI" }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "print verbose output", NULL }, { "log", 'l', 0, G_OPTION_ARG_STRING, &log_lvl, "specify precise log level", "NAME" }, { "version", 'V', 0, G_OPTION_ARG_NONE, &version_only, "print version and exit", NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL }, }; /* Save a copy of argv */ globalconf.argv = g_ptr_array_new_with_free_func(g_free); for (gint i = 0; i < *argc; ++i) g_ptr_array_add(globalconf.argv, g_strdup(argv[i])); /* parse command line options */ context = g_option_context_new("[URI...]"); g_option_context_add_main_entries(context, entries, NULL); g_option_context_add_group(context, gtk_get_option_group(FALSE)); g_option_context_parse(context, argc, &argv, NULL); g_option_context_free(context); /* Trim unparsed arguments off copy of argv */ for (gint i = 0; i < *argc; ++i) { while ((unsigned)i < globalconf.argv->len && !strcmp(g_ptr_array_index(globalconf.argv, i), argv[i])) g_ptr_array_remove_index(globalconf.argv, i); } /* print version and exit */ if (version_only) { g_printf("luakit %s\n", VERSION); g_printf(" built with: webkit %i.%i.%i ", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); g_printf("(installed version: %u.%u.%u)\n", webkit_get_major_version(), webkit_get_minor_version(), webkit_get_micro_version()); g_printf(" GTK %i.%i.%i \n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); g_printf(" GLIB %i.%i.%i \n", GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION); g_printf(" SOUP %i.%i.%i \n", SOUP_MAJOR_VERSION, SOUP_MINOR_VERSION, SOUP_MICRO_VERSION); exit(EXIT_SUCCESS); } if (!log_lvl) log_set_verbosity("all", verbose ? LOG_LEVEL_verbose : LOG_LEVEL_info); else { log_set_verbosity("all", LOG_LEVEL_info); parse_log_level_option(log_lvl); if (verbose) warn("invalid mix of -v and -l, ignoring -v..."); } /* check config syntax and exit */ if (check_only) { init_directories(); luaH_init(NULL); if (!luaH_parserc(globalconf.confpath, FALSE)) { g_fprintf(stderr, "Confiuration file syntax error.\n"); exit(EXIT_FAILURE); } else { g_fprintf(stderr, "Configuration file syntax OK.\n"); exit(EXIT_SUCCESS); } } if (uris && argv[1]) fatal("invalid mix of -u and default uri arguments"); if (uris) return uris; else return g_strdupv(argv + 1); } #if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 50 static GLogWriterOutput glib_log_writer(GLogLevelFlags log_level_flags, const GLogField *fields, gsize n_fields, gpointer UNUSED(user_data)) { const gchar *log_domain = "(unknown)", *message = "(empty)"; for (gsize i = 0; i < n_fields; ++i) { if (!strcmp(fields[i].key, "GLIB_DOMAIN")) log_domain = fields[i].value; if (!strcmp(fields[i].key, "MESSAGE")) message = fields[i].value; } /* Probably not necessary, but just in case... */ if (!(G_LOG_LEVEL_MASK & log_level_flags)) return G_LOG_WRITER_UNHANDLED; log_level_t log_level = ((log_level_t[]){ [G_LOG_LEVEL_ERROR] = LOG_LEVEL_fatal, [G_LOG_LEVEL_CRITICAL] = LOG_LEVEL_warn, [G_LOG_LEVEL_WARNING] = LOG_LEVEL_warn, [G_LOG_LEVEL_MESSAGE] = LOG_LEVEL_info, [G_LOG_LEVEL_INFO] = LOG_LEVEL_verbose, [G_LOG_LEVEL_DEBUG] = LOG_LEVEL_debug, })[log_level_flags]; _log(log_level, "glib", "%s: %s", log_domain, message); return G_LOG_WRITER_HANDLED; } #endif gint main(gint argc, gchar *argv[]) { gboolean *nonblock = NULL; globalconf.starttime = l_time(); log_init(); /* set numeric locale to C (required for compatibility with LuaJIT and luakit scripts) */ gtk_disable_setlocale(); setlocale(LC_ALL, ""); setlocale(LC_NUMERIC, "C"); /* parse command line opts and get uris to load */ gchar **uris = parseopts(&argc, argv, &nonblock); /* hide command line parameters so process lists don't leak (possibly confidential) URLs */ for (gint i = 1; i < argc; i++) memset(argv[i], 0, strlen(argv[i])); globalconf.windows = g_ptr_array_new(); /* if non block mode - respawn, detach and continue in child */ if (nonblock) { pid_t pid = fork(); if (pid < 0) { fatal("Cannot fork: %d", errno); } else if (pid > 0) { exit(EXIT_SUCCESS); } pid_t sid = setsid(); if (sid < 0) { fatal("New SID creation failure: %d", errno); } } gtk_init(&argc, &argv); #if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 50 g_log_set_writer_func(glib_log_writer, NULL, NULL); #endif init_directories(); web_context_init(); ipc_init(); luaH_init(uris); /* parse and run configuration file */ if (!luaH_parserc(globalconf.confpath, TRUE)) fatal("couldn't find rc file"); if (!globalconf.windows->len) fatal("no windows spawned by rc file, exiting"); gtk_main(); return EXIT_SUCCESS; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/resources/000077500000000000000000000000001475363222200150265ustar00rootroot00000000000000luakit-2.4.0/resources/icons/000077500000000000000000000000001475363222200161415ustar00rootroot00000000000000luakit-2.4.0/resources/icons/COPYING000066400000000000000000000015161475363222200171770ustar00rootroot00000000000000The following files are part of the Arc Icon Theme project, available at https://github.com/horst3180/arc-icon-theme, licensed under the GPLv3. See COPYING.GPLv3. resources/icons/tab-icon-chrome.png resources/icons/tab-icon-chrome@2x.png resources/icons/tab-icon-private.png resources/icons/tab-icon-private@2x.png The following files are modified files from the Arc Icon Theme project, available at https://github.com/horst3180/arc-icon-theme, licensed under the GPLv3. See COPYING.GPLv3. Modification date: 24 July 2017. resources/icons/tab-icon-page.png resources/icons/tab-icon-page@2x.png resources/icons/tab-icon-crash.png resources/icons/tab-icon-crash@2x.png resources/icons/tab-icon-error.png resources/icons/tab-icon-error@2x.png resources/icons/tab-icon-security-error.png resources/icons/tab-icon-security-error@2x.png luakit-2.4.0/resources/icons/tab-icon-chrome.png000066400000000000000000000010741475363222200216200ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8QKTQ=7{_Ph2qKEÍ$jI? AR1OНӲ6:cQTLop\}?|n%YYOAqdtK]{v9Uͫ/^Gd{gT2PYj#ͭ ֗Д_ʟ_K]!f4Y` RJuE`k˦ݽr`Yys~f02M}dYr9n|ն -kͭvZioTJo ɤ}g mgN$."TA pixɈSBIENDB`luakit-2.4.0/resources/icons/tab-icon-chrome@2x.png000066400000000000000000000016521475363222200221740ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs^tEXtSoftwarewww.inkscape.org<'IDATXVKSaL̶]V] Evlb~CZB]G6JqQA@X)97SǦSd 'HtQg̃9[AZ UF*PԈ:>xT%kҮ.9NJEU(15E"ֻ kgGVEQ7GY%I?V\)A4(0қJQ'7+LTNX]$7U+NPu !<+cn/K#nn/e]3"t"9ɣ1Lw%8S` CnݨSP:>~TQgLKKC 0֞mx,W_VʚC6Mh:f\R k60R;fœg?xZ_{M]+'N‰X^DW@ 2bӟ*9h{fmw%:TDwj&5oji 1 F^D"?zyyDBK$dEQ#"PȄ6@D3,F8w!`(VL;Frx:RQb8Ƙq8\~.p`Rݢq x=Kkz.(l5->.l= 1Vߔs0 /A|vS/n XTNš,c>,1g70>! ,NNk'I^"IENDB`luakit-2.4.0/resources/icons/tab-icon-crash.png000066400000000000000000000005501475363222200214410ustar00rootroot00000000000000PNG  IHDRabKGD pHYs B(xtIME-#IDAT8͓=N@ ;^WN D"7A@2I  :C\DHL5o4ih px4Tډ($FF`[$ᠩ7j*ZvXL\i'p%:5iU0~;M/Bk +wpƺWa?]a\4r%%XXvKHEMtHJ0ܑWtlgIENDB`luakit-2.4.0/resources/icons/tab-icon-crash@2x.png000066400000000000000000000011141475363222200220100ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs^tIME++LIDATXQKQwMHX/` D(FDDX~>A1*Hlθ:jrZ{И݈<ùK2gvMDR{gk}HP&xۙ+v Jx+SRUwmh%UcRLmt Xje7RrDp]2! jjxAex4[/=׽hK7ΌU3Ȁpp=k'ǝ֕0I8MS>_2w;O\p_syǎ)K 7>mb91)j=޵Y-q!^q2q?ky/AJź^8| kl6{ 楬 3|w4EJ"( iۢ*sP3K?Ri?pIENDB`luakit-2.4.0/resources/icons/tab-icon-error.png000066400000000000000000000003301475363222200214660ustar00rootroot00000000000000PNG  IHDRabKGD pHYs B(xtIME,IeIDAT8c`02000{c`d#InwfH👁 <Ƈya4 F ```'iف]u€?Gs!z+IENDB`luakit-2.4.0/resources/icons/tab-icon-error@2x.png000066400000000000000000000003471475363222200220500ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs^tIME,) UmtIDATXc`ưȠG[3\ - ܻ8hQ:`EDNhx)-DHgDEQ:D8CIENDB`luakit-2.4.0/resources/icons/tab-icon-private.png000066400000000000000000000004271475363222200220160ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8c`02000{c`d#InwfH👁TE4de<|-6!:Ip:ze```81.u{ gqs 9݄b F#z`L AFr`!UZIENDB`luakit-2.4.0/resources/icons/tab-icon-private@2x.png000066400000000000000000000005171475363222200223700ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs^tEXtSoftwarewww.inkscape.org<IDATXc`ưȠG[3\>8zQ:`C$$dQ #0 K$AxIENDB`luakit-2.4.0/tests/000077500000000000000000000000001475363222200141565ustar00rootroot00000000000000luakit-2.4.0/tests/async/000077500000000000000000000000001475363222200152735ustar00rootroot00000000000000luakit-2.4.0/tests/async/run_test.lua000066400000000000000000000121731475363222200176450ustar00rootroot00000000000000--- Test runner for async tests. -- -- @script async.run_test -- @copyright 2017 Aidan Holm -- Adjust paths to work when running with DEVELOPMENT_PATHS=0 dofile("tests/async/wrangle_paths.lua") require_web_module("tests/async/wrangle_paths") local shared_lib = {} local priv = require "tests.priv" local test = require("tests.lib") test.init(shared_lib) --- Launched as the init script of a luakit instance -- -- Loads test_file and runs all tests in it in order local function do_test_file(test_file) local wait_timer = timer() -- Load test table, or abort print("__load__ ") local T, err = priv.load_test_file(test_file) if not T then print("__fail__ " .. test_file) print(err) luakit.quit(0) end -- Convert functions to coroutines for test_name, func in pairs(T) do if type(func) == "function" then T[test_name] = coroutine.create(func) end end local current_test local waiting_signal --- Runs a test untit it passes, fails, or waits for a signal -- Additional arguments: parameters to signal handler -- @treturn string Status of the test; one of "pass", "wait", "fail" local function begin_or_continue_test(func, ...) assert(type(func) == "thread") shared_lib.current_coroutine = func -- Run test until it finishes, pauses, or fails local ok, ret = coroutine.resume(func, ...) local state = coroutine.status(func) if not ok then print("__fail__ " .. current_test) print(tostring(ret)) print(debug.traceback(func)) return "fail" elseif state == "suspended" then print("__wait__ " .. current_test) -- Start timer wait_timer.interval = ret.timeout wait_timer:start() -- wait_for_signal if #ret == 2 then -- Add signal handlers to resume running test local obj, sig = ret[1], ret[2] local function wrapper(...) obj:remove_signal(sig, wrapper) shared_lib.resume_suspended_test(...) end obj:add_signal(sig, wrapper) waiting_signal = sig end -- Return to luakit return "wait" else print("__pass__ " .. current_test) return "pass" end end --- Finds the next test to run and starts it, or quits local function do_next_test() repeat local test_name, func = next(T, current_test) if not test_name then -- Quit if all tests have been run luakit.quit() return end current_test = test_name print("__run__ " .. current_test) local test_status = begin_or_continue_test(func) until test_status == "wait" end --- Resumes a waiting test when a signal occurs shared_lib.resume_suspended_test = function (...) local func = shared_lib.current_coroutine assert(type(func) == "thread") -- Stop the timeout timer wait_timer:stop() waiting_signal = nil -- Continue the test print("__cont__ " .. current_test) local test_status = begin_or_continue_test(func, ...) -- If the test finished, do the next one if test_status ~= "wait" then luakit.idle_add(do_next_test) end end wait_timer:add_signal("timeout", function () wait_timer:stop() print("__fail__ " .. current_test) if waiting_signal then print("Timed out while waiting for signal '" .. waiting_signal .. "'") else print("Timed out while waiting") end print(" interval was " .. tostring(wait_timer.interval) .. "msec") print(" " .. shared_lib.traceback) do_next_test() end) do_next_test() -- If the test hasn't opened any windows, open one to keep luakit happy if #luakit.windows == 0 then local win = widget{type="window"} win:show() end end io.stdout:setvbuf("line") local test_file = uris[1] assert(type(test_file) == "string") -- Setup luakit-test:// URI scheme luakit.register_scheme("luakit-test") widget.add_signal("create", function (w) if w.type == "webview" then w:add_signal("scheme-request::luakit-test", function (_, uri, request) local path = uri:gsub("^luakit%-test://", "tests/html/") local f = assert(io.open(path, "rb")) local contents = f:read("*a") or "" f:close() local mime = "text/plain" if path:match("%.html$") then mime = "text/html" end if path:match("%.png$") then mime = "image/png" end if path:match("%.jpg$") then mime = "image/jpeg" end request:finish(contents, mime) end) end end) require('unique_instance') local lousy = require('lousy') -- Some lib files assume that a theme has been loaded lousy.theme.init(lousy.util.find_config("theme.lua")) do_test_file(test_file) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_basic.lua000066400000000000000000000006211475363222200201150ustar00rootroot00000000000000--- Basic async test functions. -- -- @copyright 2017 Aidan Holm local T = {} local test = require "tests.lib" local window = widget{type="window"} local view = widget{type="webview"} window.child = view window:show() T.test_about_blank_loads_successfully = function () view.uri = "about:blank" test.wait_for_view(view) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_binds_api.lua000066400000000000000000000044661475363222200207770ustar00rootroot00000000000000--- Test binds APIs. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local lousy = require "lousy" local T = {} T.test_binds_are_called = function () local binds = {} local hit_count = 0 local action = { func = function () hit_count = hit_count + 1 end } lousy.bind.add_bind(binds, "a", action) lousy.bind.add_bind(binds, "", action) lousy.bind.add_bind(binds, "", action) lousy.bind.add_bind(binds, "gg", action) lousy.bind.add_bind(binds, ":test", action) lousy.bind.add_bind(binds, ":test-short, :test-loooooooong", action) lousy.bind.add_bind(binds, ":", action) lousy.bind.add_bind(binds, "", action) lousy.bind.add_bind(binds, "", action) lousy.bind.add_bind(binds, "gT", action) lousy.bind.add_bind(binds, "-", action) lousy.bind.add_bind(binds, "", action) assert.equal(12, #binds) lousy.bind.hit(nil, binds, {}, "a", {}) assert.equal(1, hit_count) lousy.bind.hit(nil, binds, {"Control", "Shift"}, "C", {}) assert.equal(2, hit_count) local args = { buffer = "", enable_buffer = true } local _, newbuf = lousy.bind.hit(nil, binds, {}, "g", args) args.buffer = newbuf _, newbuf = lousy.bind.hit(nil, binds, {}, "g", args) args.buffer = newbuf assert.equal(3, hit_count) assert.equal(nil, args.buffer) lousy.bind.match_cmd(nil, binds, "test", {}) lousy.bind.match_cmd(nil, binds, "test-short", {}) lousy.bind.match_cmd(nil, binds, "test-loooooooong", {}) assert.equal(6, hit_count) lousy.bind.hit(nil, binds, {"Control"}, "a", {}) assert.equal(7, hit_count) lousy.bind.hit(nil, binds, {"Shift"}, ":", {}) assert.equal(8, hit_count) lousy.bind.hit(nil, binds, {}, ":", {}) assert.equal(9, hit_count) lousy.bind.hit(nil, binds, {"Shift"}, "Tab", {}) assert.equal(10, hit_count) lousy.bind.hit(nil, binds, {"Shift"}, 1, {}) assert.equal(11, hit_count) lousy.bind.hit(nil, binds, {}, "T", { buffer = "g", enable_buffer = true }) assert.equal(12, hit_count) lousy.bind.hit(nil, binds, {}, "-", {}) assert.equal(13, hit_count) lousy.bind.hit(nil, binds, {"Control", "Shift"}, "d", {}) assert.equal(14, hit_count) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_clib_luakit.lua000066400000000000000000000102211475363222200213130ustar00rootroot00000000000000--- Test luakit clib functionality. -- -- @copyright 2012 Mason Larobina local assert = require "luassert" local test = require "tests.lib" local T = {} T.test_luakit = function () assert.is_table(luakit) -- Check metatable local mt = getmetatable(luakit) assert.is_function(mt.__index, "luakit mt missing __index") end T.test_luakit_index = function () local funcprops = { "exec", "quit", "save_file", "spawn", "spawn_sync", "time", "uri_decode", "uri_encode", "idle_add", "idle_remove" } for _, p in ipairs(funcprops) do assert.is_function(luakit[p], "Missing/invalid function: luakit."..p) end local strprops = { "cache_dir", "config_dir", "data_dir", "execpath", "confpath", "install_path", "version" } for _, p in ipairs(strprops) do assert.is_string(luakit[p], "Missing/invalid property: luakit."..p) end local boolprops = { "dev_paths", "verbose", "nounique" } for _, p in ipairs(boolprops) do assert.is_boolean(luakit[p], "Missing/invalid property: luakit."..p) end assert.is_number(luakit.time(), "Invalid: luakit.time()") end T.test_webkit_version = function () assert.is_match("^%d+%.%d+%.%d+$", luakit.webkit_version, "Invalid format: luakit.webkit_version") assert.is_match("^%d+%.%d+$", luakit.webkit_user_agent_version, "Invalid format: luakit.webkit_user_agent_version") end T.test_windows_table = function () assert.is_table(luakit.windows, "Missing/invalid luakit.windows table.") local baseline = #luakit.windows assert.is_number(baseline, "Invalid number of windows") local win = widget{type="window"} assert.is_equal(baseline+1, #luakit.windows, "luakit.windows not tracking opened windows.") win:destroy() assert.is_equal(baseline, #luakit.windows, "luakit.windows not tracking closed windows.") end T.test_invalid_prop = function () assert.is_nil(luakit.invalid_property) end T.test_idle_add_del = function () local f = function () end assert.is_false(luakit.idle_remove(f), "Function can't be removed before it's been added.") for _ = 1,5 do luakit.idle_add(f) end for _ = 1,5 do assert.is_true(luakit.idle_remove(f), "Error removing callback.") end assert.is_false(luakit.idle_remove(f), "idle_remove removed incorrect number of callbacks.") end T.test_register_scheme = function () assert.has_error(function () luakit.register_scheme("") end) assert.has_error(function () luakit.register_scheme("http") end) assert.has_error(function () luakit.register_scheme("https") end) assert.has_error(function () luakit.register_scheme("ABC") end) assert.has_error(function () luakit.register_scheme(" ") end) luakit.register_scheme("test-scheme-name") luakit.register_scheme("a-.++...--8970d-d-") end T.test_website_data = function () local _, minor = luakit.webkit_version:match("^(%d+)%.(%d+)%.") if tonumber(minor) < 16 then return end local wd = luakit.website_data assert.is_table(wd) assert.is_function(wd.fetch) assert.has_error(function () wd.fetch("") end) assert.has_error(function () wd.fetch({}) end) assert.has_error(function () wd.remove("") end) assert.has_error(function () wd.remove({}) end) assert.has_error(function () wd.remove({"all"}) end) local v = widget{type="webview"} coroutine.wrap(function () assert(not wd.fetch({"all"})["Local files"]) test.continue() end)() test.wait(1000) v.uri = "file:///" test.wait_for_view(v) coroutine.wrap(function () assert(wd.fetch({"all"})["Local files"]) wd.remove({"all"}, "Local files") assert(not wd.fetch({"all"})["Local files"]) test.continue() end)() test.wait() v:destroy() end T.test_luakit_install_paths = function () local paths = assert(luakit.install_paths) assert.equal(paths.install_dir, luakit.install_path) for _, k in ipairs {"install_dir", "config_dir", "doc_dir", "man_dir", "pixmap_dir", "app_dir"} do assert(type(paths[k]) == "string") end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_clib_regex.lua000066400000000000000000000011461475363222200211420ustar00rootroot00000000000000--- Test regex clib functionality. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local T = {} T.test_module = function () assert.is_table(regex) end T.test_regex_with_no_pattern_fails = function () assert.has_error(function () regex() end) end T.test_regex_matches = function () -- Empty string can match ^ and $ assert(regex{pattern="^"}:match("")) assert(regex{pattern="$"}:match("")) -- Case sensitive assert(not regex{pattern="a"}:match("A")) assert(regex{pattern="A"}:match("A")) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_clib_soup.lua000066400000000000000000000036621475363222200210230ustar00rootroot00000000000000--- Test luakit soup functionality. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local T = {} T.test_soup = function () assert.are.same(soup.parse_uri(""), nil) assert.are.same(soup.parse_uri("about:blank"), { scheme = "about", path = "blank", }) assert.are.same(soup.parse_uri("example.com"), { scheme = "http", host = "example.com", path = "/", port = 80, }) assert.are.same(soup.parse_uri("https://www.example.com:900"), { scheme = "https", host = "www.example.com", path = "/", port = 900, }) assert.are.same(soup.parse_uri("luakit://page"), { scheme = "luakit", host = "page", }) assert.are.same(soup.parse_uri("luakit://page/foo"), { scheme = "luakit", host = "page", path = "/foo", }) assert.are.same(soup.parse_uri("view-source:luakit://page/foo"), { scheme = "view-source", path = "luakit://page/foo", }) end T.test_set_proxy_uri = function () soup.proxy_uri = "default" assert.equal(soup.proxy_uri, "default") soup.proxy_uri = "no_proxy" assert.equal(soup.proxy_uri, "no_proxy") soup.proxy_uri = "no_proxy" assert.equal(soup.proxy_uri, "no_proxy") soup.proxy_uri = nil assert.equal(soup.proxy_uri, "default") assert.has_error(function () soup.proxy_uri = true end) end T.test_set_accept_policy = function () soup.accept_policy = "always" assert.equal("always", soup.accept_policy) soup.accept_policy = "never" assert.equal("never", soup.accept_policy) soup.accept_policy = "no_third_party" assert.equal("no_third_party", soup.accept_policy) end T.test_set_cookies_storage = function () local path = luakit.data_dir .. "/cookies2.db" soup.cookies_storage = path assert.equal(path, soup.cookies_storage) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_clib_sqlite3.lua000066400000000000000000000063661475363222200214250ustar00rootroot00000000000000--- Test sqlite clib functionality. -- -- @copyright Mason Larobina local assert = require "luassert" local T = {} T.test_module = function () assert.is_table(sqlite3) end T.test_open_db = function () local db = sqlite3{filename=":memory:"} assert.is_equal("sqlite3", type(db)) -- Should error without constructor table assert.has_error(function () sqlite3() end) -- Should error without filename in constructor table assert.has_error(function () sqlite3{} end) end T.test_sqlite3_exec = function () local ret local db = sqlite3{filename=":memory:"} ret = db:exec([[CREATE TABLE IF NOT EXISTS test ( id INTEGER PRIMARY KEY, uri TEXT, created FLOAT )]]) assert.is_nil(ret) assert.has_no.errors(function () db:exec(";") end) ret = db:exec(";") assert.is_nil(ret) ret = db:exec([[SELECT * FROM test;]]) assert.is_table(ret) assert.is_equal(0, #ret) ret = db:exec([[INSERT INTO test VALUES(NULL, "google.com", 1234.45)]]) assert.is_nil(ret) ret = db:exec([[SELECT * FROM test;]]) assert.is_table(ret) assert.is_equal(1, #ret) ret = ret[1] assert.is_table(ret) assert.is_equal("google.com", ret.uri) assert.is_equal(1234.45, ret.created) ret = db:exec([[INSERT INTO test VALUES(:id, :uri, :created);]], { [":uri"] = "reddit.com", [":created"] = 1000 }) assert.is_nil(ret) ret = db:exec([[SELECT * FROM test;]]) assert.is_table(ret) assert.is_equal(2, #ret) ret = ret[2] assert.is_table(ret) assert.is_equal("reddit.com", ret.uri) assert.is_equal(1000, ret.created) -- for i, row in ipairs(ret) do -- for k,v in pairs(row) do -- print("row", i, k, v) -- end -- end end T.test_compile_statement = function () local db = sqlite3{filename=":memory:"} local ret, insert, tail, select_all ret, tail = db:exec([[CREATE TABLE IF NOT EXISTS test ( id INTEGER PRIMARY KEY, uri TEXT, created FLOAT )]]) assert.is_nil(ret) assert.is_nil(tail) -- Compile some statements insert, tail = db:compile([[INSERT INTO test VALUES(:id, :uri, :created);]]) assert.is_equal("sqlite3::statement", type(insert)) assert.is_nil(tail) select_all, tail = db:compile([[SELECT * FROM test;]]) assert.is_equal("sqlite3::statement", type(select_all)) assert.is_nil(tail) ret = insert:exec{ [":uri"] = "google.com", [":created"] = 1000 } assert.is_nil(ret) ret = insert:exec{ [":uri"] = "reddit.com", [":created"] = 12.34 } assert.is_nil(ret) ret = select_all:exec() assert.is_table(ret) assert.is_equal(2, #ret) assert.is_table(ret[1]) assert.is_equal("google.com", ret[1].uri) assert.is_equal(1000, ret[1].created) assert.is_table(ret[2]) assert.is_equal("reddit.com", ret[2].uri) assert.is_equal(12.34, ret[2].created) -- Re-run last statement with same bound values ret = insert:exec() assert.is_nil(ret) ret = select_all:exec() assert.is_table(ret) assert.is_equal(3, #ret) assert.is_table(ret[3]) assert.is_equal("reddit.com", ret[3].uri) assert.is_equal(12.34, ret[3].created) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_clib_utf8.lua000066400000000000000000000026131475363222200207160ustar00rootroot00000000000000--- Test utf8 clib functionality. -- -- @copyright 2017 Dennis Hofheinz local assert = require "luassert" local T = {} T.test_module = function () assert.is_table(utf8) end T.test_utf8_len = function () assert.equal(0, utf8.len("")) assert.equal(1, utf8.len("ä")) assert.equal(2, utf8.len("äa")) assert.equal(1, utf8.len("äa", -1)) assert.equal(2, utf8.len("äa", -3)) assert.equal(1, utf8.len("äa", 1, 1)) assert.equal(1, utf8.len("äa", 1, 2)) assert.equal(2, utf8.len("äa", 1, 3)) -- corner cases and errors assert.equal(0, utf8.len("", 1, 0)) assert.equal(0, utf8.len("äa", 4)) assert.equal(0, utf8.len("äa", 3, 2)) assert.has.errors(function() utf8.len("", 1, 1) end) assert.has.errors(function() utf8.len("äa", 0) end) assert.has.errors(function() utf8.len("äa", 5) end) end T.test_utf8_offset = function () assert.equal(1, utf8.offset("äaäaä", 1)) assert.equal(3, utf8.offset("äaäaä", 2)) assert.equal(7, utf8.offset("äaäaä", 5)) assert.equal(9, utf8.offset("äaäaä", 6)) assert.equal(4, utf8.offset("äaäaä", 2, 3)) assert.equal(7, utf8.offset("äaäaä", 2, -3)) -- corner cases and errors assert.has.errors(function() utf8.offset("äaäaä", 1, 2) end) assert.has.errors(function() utf8.offset("äaäaä", 1, 5) end) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_completion.lua000066400000000000000000000012041475363222200212030ustar00rootroot00000000000000--- Tests for the input completions module. -- -- @copyright 2017 Aidan Holm local T = {} local assert = require("luassert") uris = {"about:blank"} require "config.rc" local window = require "window" local w = assert(select(2, next(window.bywidget))) T.test_leaving_completion_restores_correct_input_text = function () local input = w.ibar.input w:enter_cmd(":tab") w:set_mode("completion") w.menu:move_up() assert.equal(":tab", input.text) input.text = ":adbl" w.menu:move_down() w.menu:move_up() assert.equal(":adbl", input.text) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_config.lua000066400000000000000000000003511475363222200203010ustar00rootroot00000000000000--- Test that the default config works. -- -- @copyright 2017 Aidan Holm local T = {} T.test__config_rc_loads_successfully = function () require "config.rc" end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_http_test_server.lua000066400000000000000000000015451475363222200224460ustar00rootroot00000000000000--- Tests the http test server. -- -- @copyright 2017 Aidan Holm local T = {} local test = require "tests.lib" T.test_http_server_returns_file_contents = function () -- Read file contents local f = assert(io.open("tests/html/hello_world.html", "rb")) local contents = f:read("*a") or "" f:close() -- Load URI and wait for completion local view = widget{type="webview"} view.uri = test.http_server() .. "hello_world.html" test.wait_for_view(view) -- Wrap view.source get in another coroutine, since auto-suspending the -- test coroutine confuses the test runner coroutine.wrap(function () test.continue(view:get_source()) end)() local source = test.wait() assert(source == contents, "HTTP server returned wrong content for file") end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_image_css.lua000066400000000000000000000036651475363222200210010ustar00rootroot00000000000000--- Test image CSS. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local test = require "tests.lib" local lousy = require "lousy" local T = {} local window = widget{type="window"} local view = widget{type="webview"} window.child = view window:show() -- Stub out webview module package.loaded.webview = lousy.signal.setup({}, true) local image_css = require("image_css") package.loaded.webview.emit_signal("init", view) local view_wait_for_status = function (v, status) repeat local _, s, uri, err = test.wait_for_signal(v, "load-status", 1000) if s == "failed" then local fmt = "tests.wait_for_view() failed loading '%s': %s" local msg = fmt:format(uri, err) assert(false, msg) end until s == status end local function wait_for_view(v) -- mime-type-decision isn't emitted for luakit-test://, so we simulate it view_wait_for_status(v, "provisional") local mime = v.uri:match("%.png$") and "image/png" or "text/html" v:emit_signal("mime-type-decision", v.uri, mime) view_wait_for_status(v, "committed") end T.test_image_css = function () local image_uri = test.http_server() .. "image_css/image.png" local page_uri = test.http_server() .. "image_css/default.html" local image_ss = image_css.stylesheet -- Load HTML page: stylesheet must be inactive view.uri = page_uri wait_for_view(view) assert.is_false(view.stylesheets[image_ss]) -- Load image page: stylesheet must be active view.uri = image_uri wait_for_view(view) assert.is_true(view.stylesheets[image_ss]) view:go_back(1) wait_for_view(view) assert.is_false(view.stylesheets[image_ss]) view:go_forward(1) wait_for_view(view) assert.is_true(view.stylesheets[image_ss]) view:go_back(1) wait_for_view(view) assert.is_false(view.stylesheets[image_ss]) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_lib_gopher.lua000066400000000000000000000046661475363222200211630ustar00rootroot00000000000000--- Test gopher module. local assert = require "luassert" local gopher = require "gopher" local T = {} T.test_image_mime_type = function() local f = gopher.image_mime_type assert.is_equal("image/gif", f("gif")) assert.is_equal("image/jpeg", f("JPG")) assert.is_equal("image/svg+xml", f("svg")) assert.is_equal("application/octet-stream", f()) end T.test_parse_url_simple = function() local url url = gopher.parse_url("gopher://example.com") assert.is_equal(url.host, "example.com") assert.is_equal(url.port, 70) assert.is_equal(url.gophertype, "1") assert.is_equal(url.selector, "") assert.is_nil(url.search) assert.is_nil(url.gopher_plus_string) end T.test_parse_url_complex = function() local url url = gopher.parse_url("gopher://example.com:80/0/file.txt%09please/search%09plus/command") assert.is_equal(url.host, "example.com") assert.is_equal(url.port, 80) assert.is_equal(url.gophertype, "0") assert.is_equal(url.selector, "/file.txt") assert.is_equal(url.search, "please/search") assert.is_equal(url.gopher_plus_string, "plus/command") end T.test_href_source_telnet = function() local entry = { scheme = "telnet", host = "example.com", port = 23, selector = "/" } assert.is_equal("telnet://example.com:23/", gopher.href_source(entry)) end T.test_href_source_gopher_text_file = function() local entry = { scheme = "gopher", host = "example.com", port = 70, item_type = "0", selector = "/abc" } assert.is_equal("gopher://example.com:70/0/abc", gopher.href_source(entry)) end T.test_href_source_https_text_file = function() local entry = { selector = "URL:https://example.com/abc?q=1" } assert.is_equal("https://example.com/abc?q=1", gopher.href_source(entry)) end T.test_parse_gopher_line = function() local line = "0Sample Text\t/selector/part here\texample1.com\t80" local url = { host = "example.com", port = 70 } local entry = gopher.parse_gopher_line(line, url) assert.is_equal(line, entry.line) assert.is_equal("0", entry.item_type) assert.is_equal("Sample Text", entry.display_string) assert.is_equal("/selector/part here", entry.selector) assert.is_equal("example1.com", entry.host) assert.is_equal("80", entry.port) assert.is_equal("gopher", entry.scheme) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_lib_lousy_uri.lua000066400000000000000000000074761475363222200217330ustar00rootroot00000000000000--- Test lousy.uri functionality. -- -- @copyright 2016 Aidan Holm local assert = require "luassert" local lousy = require "lousy" local T = {} T.test_lousy_uri_properties = function () local keys = { is_uri = true, split = true, parse_query = true, parse = true, copy = true, domains_from_uri = true, } for k in pairs(keys) do assert.is_function(lousy.uri[k], "Missing/invalid property: lousy.uri." .. k) end for k in pairs(lousy.uri) do assert.is_true(keys[k], "Extra property: lousy.uri." .. k) end end T.test_lousy_uri_parse_is_uri = function() -- File URIs and /etc/hosts are system-dependant so they won't be tested local is_uri = lousy.uri.is_uri assert.is_true(is_uri("localhost")) assert.is_true(is_uri("localhost:8080")) assert.is_true(is_uri("about:blank")) assert.is_true(is_uri("javascript:alert('message')")) assert.is_false(is_uri(".example.com")) assert.is_true(is_uri("https://github.com/luakit/luakit")) assert.is_true(is_uri("http://localhost:8000/tests/")) assert.is_true(is_uri("luakit.github.io")) assert.is_true(is_uri("http://www.shareprice.co.uk/TW.")) assert.is_false(is_uri("etc.")) end T.test_lousy_uri_parse_split = function() local s = [[github.com Monsters, Inc. (http://i.imgur.com/BxXBmVL.gif), I love localhost ice cream. ]] local t = {"github.com", "Monsters, Inc.", "http://i.imgur.com/BxXBmVL.gif", "I love", "localhost", "ice cream."} assert.are.same(t, lousy.uri.split(s)) local js = "javascript:alert('foo'); confirm('bar')" assert.are.same({js}, lousy.uri.split(js)) end T.test_lousy_uri_parse = function () local uri = "http://test-user:p4ssw0rd@example.com:777/some~path?a=b&foo=bar#frag" local uri_without_password = "http://test-user@example.com:777/some~path?a=b&foo=bar#frag" local parsed = lousy.uri.parse(uri) assert.is_not_equal(nil, parsed, "Failed parsing uri '" .. uri .. "'") local keys = { scheme = "string", user = "string", password = "string", host = "string", path = "string", fragment = "string", opts = "table", port = "number", } for k, v in pairs(parsed) do assert.is_not_equal(nil, keys[k], "Extra lousy.uri uri property: " .. k) assert.is_true(type(v) == keys[k], "Wrong type for lousy.uri uri property: " .. k) end assert.is_equal("http", parsed.scheme, "Parsed uri has wrong scheme") assert.is_equal("test-user", parsed.user, "Parsed uri has wrong user") assert.is_equal("p4ssw0rd", parsed.password, "Parsed uri has wrong password") assert.is_equal("example.com", parsed.host, "Parsed uri has wrong host") assert.is_equal("/some~path", parsed.path, "Parsed uri has wrong path") assert.is_equal("frag", parsed.fragment, "Parsed uri has wrong fragment") local mt = getmetatable(parsed) assert.is_table(mt) assert.is_function(mt.__tostring) assert.is_function(mt.__add) assert.is_equal(tostring(parsed), uri_without_password) local props = { scheme = "luakit", user = "baz", host = "random-domain.com", path = "/", fragment = "", port = 888, } local props_uri = "luakit://baz@random-domain.com:888/?a=b&foo=bar" assert.is_equal(tostring(parsed + props), props_uri) props_uri = "luakit://baz@random-domain.com:888/" assert.is_equal(tostring(parsed + props + {query = ""}), props_uri) end T.test_lousy_uri_domains_from_uri = function () local f = lousy.uri.domains_from_uri assert.are.same({".example.com", "example.com", ".com"}, f("example.com")) assert.are.same({".example.com", "example.com", ".com"}, f("www.example.com")) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_lib_lousy_util.lua000066400000000000000000000056631475363222200221050ustar00rootroot00000000000000--- Test lousy.util functionality. -- -- @copyright 2016 Aidan Holm local assert = require "luassert" local lousy = require "lousy" local T = {} T.test_lousy_util_table_join = function () local a = { 1, 2, "a", "b" } local b = { "foo", "bar", 777 } local c = { "baz" } local sum = { 1, 2, "a", "b", "foo", "bar", 777, "baz" } assert.is_true(lousy.util.table.isclone(sum, lousy.util.table.join(a, b, c))) a = { a = 1, b = 1, c = 1 } b = { b = 2, c = 2 } c = { c = 3 } sum = { a = 1, b = 2, c = 3 } assert.is_true(lousy.util.table.isclone(sum, lousy.util.table.join(a, b, c))) end T.test_lousy_util_table_isclone = function () local a = { 1, 2, 3, x="foo", y="bar" } local b = { 1, 2, false, x="foo", y="bar" } assert.is_false(lousy.util.table.isclone(a, b)) b[3] = 3 assert.is_true(lousy.util.table.isclone(a, b)) assert.is_true(lousy.util.table.isclone(a, a)) a.y = true assert.is_false(lousy.util.table.isclone(a, b)) end T.test_lousy_util_table_reverse = function () local a = { "backwards", "am", "I", x="foo", y="bar" } local b = { "I", "am", "backwards", x="foo", y="bar" } a = lousy.util.table.reverse(a) assert.is_true(lousy.util.table.isclone(a, b)) a = lousy.util.table.reverse(a) b = lousy.util.table.reverse(b) assert.is_true(lousy.util.table.isclone(a, b)) end T.test_lousy_util_table_toarray = function () local a = { "I", "IV", x="foo","IX", "XVI", y="bar" } local b = { "I", "IV", "IX", "XVI" } a = lousy.util.table.toarray(a) assert.is_true(lousy.util.table.isclone(a, b)) end T.test_lousy_util_table_copy_clone = function () local mt = {} local a = { "I", "IV", x="foo","IX", "XVI", y="bar" } setmetatable(a, mt) local c = lousy.util.table.copy(a) assert.is_not_equal(a, c) assert.is_true(lousy.util.table.isclone(a, c)) assert.is_equal(mt, getmetatable(c)) c = lousy.util.table.clone(a) assert.is_not_equal(a, c) assert.is_true(lousy.util.table.isclone(a, c)) assert.is_equal(nil, getmetatable(c), mt) end T.test_lousy_util_table_filter_array = function () local a = { "I", "IV", x="foo","IX", "XVI", y="bar" } -- Non-array items are dropped local b = lousy.util.table.filter_array(a, function () return true end) assert.is_true(lousy.util.table.isclone(b, { "I", "IV", "IX", "XVI" })) local c = lousy.util.table.filter_array(a, function (i, _) return i % 2 == 0 end) assert.is_true(lousy.util.table.isclone(c, { "IV", "XVI" })) local d = lousy.util.table.filter_array(a, function (_, v) return v:len() == 2 end) assert.is_true(lousy.util.table.isclone(d, { "IV", "IX" })) end T.test_lousy_util_lua_escape = function () local magic = "^$()%.[]*+-?)" for i=1,#magic do local ch = magic:sub(i,i) assert.equal(" %" .. ch .. " ", lousy.util.lua_escape(" " .. ch .. " ")) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_lua_files_load_successfully.lua000066400000000000000000000012771475363222200246120ustar00rootroot00000000000000--- Test that all files in config/ and lib/ can be included. -- -- @copyright 2017 Aidan Holm local test = require("tests.lib") local T = {} T.test_all_lua_files_load_successfully = function () local exclude_files = { "config/rc.lua", "_wm%.lua$", "unique_instance%.lua" } local files = test.find_files({"config/", "lib/"}, ".+%.lua$", exclude_files) require "unique_instance" for _, file in ipairs(files) do local pkg = file:gsub("^%a+/", ""):gsub("%.lua$", ""):gsub("/", ".") require(pkg) end -- Wait for config file to finish loading luakit.idle_add(test.continue) test.wait() end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_memory_leaks.lua000066400000000000000000000010211475363222200215160ustar00rootroot00000000000000--- Check for some memory leaks. -- -- @copyright 2017 Aidan Holm uris = {"about:blank"} require "config.rc" local window = require "window" local w = assert(select(2, next(window.bywidget))) local T = {} T.test_webview_from_closed_tab_is_released = function () local refs = setmetatable({}, { __mode = "k" }) refs[w.view] = true w:close_tab() for _=1,100 do collectgarbage() end assert(not next(refs), "webview widget not collected") end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_module_binds_have_descriptions.lua000066400000000000000000000046451475363222200253030ustar00rootroot00000000000000--- Ensure all module bindings have descriptions. -- -- @copyright 2017 Aidan Holm local test = require("tests.lib") local lousy = require("lousy") local T = {} local modes = require "modes" local clear_all_mode_bindings = function () local mode_list = modes.get_modes() for mode_name in pairs(mode_list) do local mode = modes.get_mode(mode_name) mode.binds = nil end end local get_mode_bindings_for_module = function (mod) -- First require() loads the module and all dependencies -- Second require() loads just the module require(mod) clear_all_mode_bindings() package.loaded[mod] = nil require(mod) local ret = {} local mode_list = modes.get_modes() for mode_name in pairs(mode_list) do local mode = modes.get_mode(mode_name) for _, m in pairs(mode.binds or {}) do local b, a = unpack(m) table.insert(ret, { name = lousy.bind.bind_to_string(b), desc = a.desc, }) end end return ret end local function add_file_error(errors, file, error, ...) table.insert(errors, { file = file, err = string.format(error, ...) }) end T.test_module_binds_have_descriptions = function () -- settings.lua must be excluded because re-requiring it will clear all -- registered settings, causing modules tested after it to crash local excludes = {"_wm%.lua$", "modes%.lua", "unique_instance%.lua", "settings%.lua"} local files = test.find_files({"lib/"}, ".+%.lua$", excludes) local errors = {} for _, file in ipairs(files) do local pkg = file:gsub("^%a+/", ""):gsub("%.lua$", ""):gsub("/", ".") for _, b in ipairs(get_mode_bindings_for_module(pkg)) do if not b.desc or b.desc == "" then add_file_error(errors, file, "No description for binding %s", b.name) end if b.desc and not b.desc:match("%.$") then add_file_error(errors, file, "Description for binding %s doesn't end in a full stop.", b.name) end if b.desc and b.desc:match("^%l") then add_file_error(errors, file, "Description for binding %s isn't capitalized.", b.name) end end end if #errors > 0 then error("Some bindings are missing descriptions:\n" .. test.format_file_errors(errors)) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_scroll.lua000066400000000000000000000037341475363222200203420ustar00rootroot00000000000000--- Test page scrolling. -- -- @copyright 2017 Aidan Holm local T = {} local test = require "tests.lib" local assert = require "luassert" uris = { test.http_server() .. "scroll.html" } require "config.rc" local window = require "window" local w = assert(select(2, next(window.bywidget))) T.test_scrolling_works = function () test.wait_for_view(w.view) -- Fetch height of document body w.view:eval_js("document.body.getClientRects()[0].height", { callback = test.continue }) local doc_height = test.wait() local get_scroll_y = function () w.view:eval_js("window.scrollY", { callback = test.continue }) return test.wait() end assert.is_equal(0, get_scroll_y(), "Scroll position should start at top.") w:scroll{ yrel = 100 } assert.is_equal(100, get_scroll_y(), "Relative scrolling failed") w:scroll{ yrel = -100 } assert.is_equal(0, get_scroll_y(), "Relative scrolling failed") w:scroll{ yrel = -100 } assert.is_equal(0, get_scroll_y(), "Scrolling didn't stop when already at end") w:scroll{ yrel = 100 } assert.is_equal(100, get_scroll_y(), "Relative scrolling after scrolling against scroll-end failed") w:scroll{ y = 0 } assert.is_equal(0, get_scroll_y(), "Scrolling to top failed") w:scroll{ y = -1 } -- Scrolling to bottom requires a JS roundtrip to get document height -- first, so wait until that's finished before continuing... test.wait_until(function () return w.view.scroll.y > 0 end) assert.is_equal(doc_height - w.view.height, get_scroll_y(), "Scrolling to bottom failed") w:scroll{ ypct = 0 } test.wait_until(function () return w.view.scroll.y == 0 end) w:scroll{ ypct = 100 } test.wait_until(function () return w.view.scroll.y > 0 end) assert.is_equal(doc_height - w.view.height, get_scroll_y(), "Scrolling to 100% failed") end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_settings.lua000066400000000000000000000021501475363222200206730ustar00rootroot00000000000000--- Test settings. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local settings = require "settings" local T = {} T.test_settings = function () assert.is_table(settings) settings.register_settings({ ["test.setting.with.long.path"] = { default = "foo", type = "string", }, ["foo.bar"] = { type = "number", }, }) assert.equal(settings.test.setting.with.long.path, "foo") settings.test.setting.with.long.path = "bar" assert.equal(settings.test.setting.with.long.path, "bar") assert.has_error(function () settings.test.setting = "baz" end) assert.has_error(function () settings.non_existent_setting = 1 end) assert.has_error(function () return settings.on["foo"].on["foo"] end) settings.foo.bar = 1 assert.equal(settings.foo.bar, 1) settings.on["example.com"].foo.bar = 2 assert.equal(settings.foo.bar, 1) assert.equal(settings.on["example.com"].foo.bar, 2) assert.equal(settings.on[".com"].foo.bar, nil) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_undoclose.lua000066400000000000000000000035201475363222200210300ustar00rootroot00000000000000--- Basic async test functions. -- -- @copyright 2017 Aidan Holm local T = {} local test = require("tests.lib") local assert = require("luassert") local spy = require("luassert.spy") local match = require("luassert.match") uris = {"about:blank"} require "config.rc" local window = require "window" local w = assert(select(2, next(window.bywidget))) T.test_undo_close_restores_tab_history = function () -- Load page in new tab local uri = test.http_server() .. "undoclose_page.html" w:new_tab(uri) assert.is_equal(w.tabs:current(), 2) test.wait_for_view(w.view) -- Try to open the menu local notify_spy = spy.on(window.methods, "notify") w:run_cmd(":undolist") assert.spy(notify_spy).was.called_with(match._, "No closed tabs to display") assert(w:is_mode("normal")) -- Navigate to about:blank w.view.uri = "about:blank" test.wait_for_view(w.view) -- Close w:close_tab() -- Try to open the menu again notify_spy = spy.on(window.methods, "notify") w:run_cmd(":undolist") assert.spy(notify_spy).was_not_called_with(match._, "No closed tabs to display") assert(w:is_mode("undolist")) w:set_mode("normal") -- Undo Close w:undo_close_tab() test.wait_for_view(w.view) assert.is_equal(w.tabs:current(), 2) assert.is_equal(w.view.uri, 'about:blank') -- Close again w:close_tab() -- Undo Close again with argument w:undo_close_tab(1) test.wait_for_view(w.view) assert.is_equal(w.tabs:current(), 2) assert.is_equal(w.view.uri, 'about:blank') -- Navigate back w:back(1) test.wait_for_view(w.view) assert.is_equal(w.view.uri, uri) -- Restore to initial state w:close_tab() assert(w.tabs:current() == 1) assert(w.view.uri == "about:blank") end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/test_widget.lua000066400000000000000000000017311475363222200203220ustar00rootroot00000000000000--- Test widget library. -- -- @copyright 2017 Aidan Holm local assert = require "luassert" local T = {} T.test_widget_of_invalid_type_fails = function () assert.has_error(function () widget{type="no_such_widget_type"} end) assert.has_error(function () widget{} end) end T.test_bin_widget_set_child = function () local bin = widget{type="window"} local child1 = widget{type="entry"} local child2 = widget{type="label"} assert.is_nil(bin.child) bin.child = child1 assert.is_equal(bin.child, child1) bin.child = child2 assert.is_equal(bin.child, child2) bin.child = nil assert.is_nil(bin.child) end T.test_webview_widget_privacy = function () local v = widget{type="webview"} assert.is_false(v.private) v = widget{type="webview", private=false} assert.is_false(v.private) v = widget{type="webview", private=true} assert.is_true(v.private) end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/async/wrangle_paths.lua000066400000000000000000000015521475363222200206370ustar00rootroot00000000000000--- Test runner path wrangler. -- -- @script async.wrangle_paths -- @copyright 2017 Aidan Holm local system_paths, luakit_paths = {}, {} for path in string.gmatch(package.path, "[^;]+") do if not path:match("^%./") and not path:find("luakit") then table.insert(system_paths, path) elseif not path:match("^%./") and path:find("luakit_test_") then table.insert(luakit_paths, path) end end local rel_paths = { "./lib/?.lua", "./lib/?/init.lua", "./config/?.lua", "./config/?/init.lua", } system_paths = table.concat(system_paths, ";") rel_paths = table.concat(rel_paths, ";") luakit_paths = table.concat(luakit_paths, ";") package.path = string.format("./?.lua;%s;%s;%s", system_paths, rel_paths, luakit_paths) luakit.resource_path = "./resources" -- Don't use installed luakit when testing -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/html/000077500000000000000000000000001475363222200151225ustar00rootroot00000000000000luakit-2.4.0/tests/html/hello_world.html000066400000000000000000000000151475363222200203160ustar00rootroot00000000000000Hello, world!luakit-2.4.0/tests/html/image_css/000077500000000000000000000000001475363222200170545ustar00rootroot00000000000000luakit-2.4.0/tests/html/image_css/default.html000066400000000000000000000001701475363222200213640ustar00rootroot00000000000000 Default Just an empty page. luakit-2.4.0/tests/html/image_css/image.png000066400000000000000000000166461475363222200206610ustar00rootroot00000000000000PNG  IHDRPLTEZvmzCxPTfI|MuedaBw\nNAw}{WSFzglpcRh_VwK}J}sOhr`@v:sU[DxuH{q{Yj=ty~tV;rdK~X>t?uRG{[yXG|EzokboDyx=tjG{H|l`?vxkY^GzrL~ufmqL~tUS^L@v~bAvMwvrc~^T|_oYbJ}naBw?uiyMXq\td ` g2ϑ%F%9g|pW9ˊDd.GPͭJQV_1, D,bZt*G31~pzzWk#-nKݜn ?VK-2-LˮS䛒Ʃ)S%,3jdv1~o}  ͘ t+=M~3(h.swȚrcMsuZJS\?]]@W5ۻ,|ќ_u8Sx8uW#5:\UÔ9:::nו8u[]'1:XH~9:linhI;۱yPiʶKQ5;ߝq)Qf:&o[%Vp2BVIORmXJ5]q\S碧$J;I5Z\Q83tܯ_ӗ}FSqIL$3z t  '2=}{4J3Hap#NQY V3;?e@8&aX'O,x1?ov#W~2ȕl@ύzHYn u|EʻBd,y2tQD<Pt%7E9*mnIsp|O˧7ioE1( " (,_`1h<<4`j`*!zP2eij<Cr㠬j C(@4 BCY&ʲ'6] < |% }1l6ý^0|3Vfp̥&0Ka.m2 ]_nf93Cgnܚ 쌦-U`A!S)I:CBi)Ѿ)FUfC^%` ;4'1v޸b6搒gç߬y ֏@`~{ [#[0%~!bmv ~oЇ7.<Ltn@K Kv8wQ2"mG^] dfcx<'+`n< ayItŁc..r}Y¬Tf:M$// t*u>*9!23u M(4PBW!Ad~&Yh|6_BjW011LE:Mc%,WWF_.26rD3~y hܧ,TW߇Z&D m^h+SW/!`ʠI#]@gz ``M  @A7 l@eH~l-rIw=,I7kk_08^_W@1Z^kղmjm[jZyWZ+ -eY+hj嵶xkmkE, T֖N4!`a ¶-q%*A²jiQKX9^&#LXbK0ň%|W̗b n>10d #)'1>1|3M|&غPkst:bii&<3GVeߤVekzN6;AlƁ 6MxMP&L >?PqOZ֨MD&%F4p5( P(/QZÈ*[@`,jxUKFGX hHwI(H HTb .<(Yl4TKS"x0aqCx0nYMMS(wZ QH&.zG6vmhdAsh6%4`SF^%"I*Za!x=gu 41~~&my59nKoۯ2GmrRy:\^VK9*q1ˊ (6LO4&dT8{6+F)ZAŎbzbKV [h"%Ks\FI ˧hう85a$P?Y4.ҝh:;cf3v);ʯx&R{H h:tz6㔔~(-I\ɤoT3SIBI:P*\u2yFU.v]W7uwG#D眤qwu-T 2ѤɻkGɟ-sBﮝ䝤gRRPpڝϻs|,]v²;/DU8=w@n[LD9zFdt9GIZ.xBT[HNuS]~tMMA,x4jŦRlS&|SpTusfޤ b¥Za1w\C\C`."F9I[`04o|]Cnp>ٺCLZ@ - E`ƅħRw(~GEF`xKKÊvpXqOEΟ. (T`Tш7jCJWZ`V#IlFVd4Pj}@I __:88H~5ju⾈UWls}L@XEԨ ^tvuoTFn/>/"1riΝsOsPl/ Kif_8*N sO^7z 0`e8f٠$$$6NǍL6 Q%Đ8 m\lBCh!+/V` FN7&ħ"T܍釵Xt5B+ew3g֪ĝdBL!3Cw&^C+x!ft>TQ#nGg_yΜep2gLw~>fϼ}]F=|>y (|B⹀G|B|g够E tGHI]AB,z,Puh C]áR(` T *.~QKm cb$Pi nJ]iW*cfԩ;m vs4<ĭ][lB)h:,=!uP`qľt:):c"::1M{5w:V*T\{bT_D7 ƽSt_(211%S'Nγ(J^ߞjiWГRE.2$+/ްozxx!'CR*E_z Je{q{W»XN;/Pr!܈աU{ {-ȅk1b5rTr)*$!ǒFfJu.r%׽lGX ^`UFwU{2Qv(.|h_]Wk* =Ā(j:  +RqǍ3HXh,Gc,[#NOt6 UtVsӧO8 6+KP'i?>?\FB06WYNB OҞN!Zp(ICC )$QNd,H$Cdf`6@,(:*N:;^NDB e1y+Ezd]C#6yUU]詧XzI1]]T/Uz*zɧd B8@J'Y*SӎODK1)]k;W^%i~W$p%bXѐAnڒL 4y$7̻"L6IENDB`luakit-2.4.0/tests/html/scroll.html000066400000000000000000000003211475363222200173020ustar00rootroot00000000000000 luakit-2.4.0/tests/html/undoclose_page.html000066400000000000000000000001521475363222200207750ustar00rootroot00000000000000 undoclose_page luakit-2.4.0/tests/lib.lua000066400000000000000000000163271475363222200154400ustar00rootroot00000000000000--- Testing interface. -- -- This module provides useful functions for use in luakit tests. -- -- @module tests.lib -- @copyright 2017 Aidan Holm local find_files = require "build-utils.find_files" local _M = {} local shared_lib = nil function _M.init(arg) _M.init = nil shared_lib = arg end --- Pause test execution until a webview widget finishes loading. -- -- @tparam widget view The webview widget to wait on. function _M.wait_for_view(view) assert(type(view) == "widget" and view.type == "webview") shared_lib.traceback = debug.traceback("",2) repeat local _, status, uri, err = _M.wait_for_signal(view, "load-status", 5000) if status == "failed" then local fmt = "tests.wait_for_view() failed loading '%s': %s" local msg = fmt:format(uri, err) assert(false, msg) end until status == "finished" end --- Pause test execution for a short time. -- -- @tparam[opt] number timeout The time to delay, in milliseconds. -- Defaults to 5 milliseconds. function _M.delay(timeout) assert(not timeout or type(timeout) == "number", "Expected number") timeout = timeout or 5 -- "Sensible default" of 5ms local t = timer{interval = timeout} t:start() -- timeout+1000 ensures we don't fail the test while waiting _M.wait_for_signal(t, "timeout", timeout+1000) end --- Pause test execution until a predicate returns `true`. -- -- Suspends test execution, polling the provided predicate function at an -- interval, until the predicate returns a truthy value. If the predicate does -- not return a truthy value within a certain time period, the running test fails. -- -- @tparam function func The predicate function. -- @tparam[opt] number poll_time The interval at which to poll the predicate, in -- milliseconds. Defaults to 5 milliseconds. -- @tparam[opt] number timeout Maximum time to wait before failing the running test, -- in milliseconds. Defaults to 200 milliseconds. function _M.wait_until(func, poll_time, timeout) assert(type(func) == "function", "Expected a function") assert(not poll_time or type(poll_time) == "number", "Expected number") assert(not timeout or type(timeout) == "number", "Expected number") shared_lib.traceback = debug.traceback("",2) poll_time = poll_time or 5 timeout = timeout or 200 local t = 0 repeat _M.delay(poll_time) t = t + poll_time assert(t < timeout, "Timed out") until func() end --- Pause test execution until a particular signal is emitted on an object. -- -- Suspends test execution until `signal` is emitted on `object`. If no such -- signal is emitted on `object` within `timeout` milliseconds, the running test -- fails. -- -- @param object The object to wait for `signal` on. -- @tparam string signal The signal to wait for. -- @tparam[opt] number timeout Maximum time to wait before failing the running test, -- in milliseconds. Defaults to 200 milliseconds. function _M.wait_for_signal(object, signal, timeout) assert(shared_lib.current_coroutine, "Not currently running a test!") assert(coroutine.running() == shared_lib.current_coroutine, "Not currently running in the test coroutine!") assert(type(signal) == "string", "Expected string") assert(not timeout or type(timeout) == "number", "Expected number") shared_lib.traceback = debug.traceback("",2) timeout = timeout or 200 return coroutine.yield({object, signal, timeout=timeout}) end local waiting = false --- Pause test execution indefinitely. -- -- The running test is suspended until `continue()` is called. If `continue()` -- is not called within `timeout` milliseconds, the running test fails. -- -- @tparam[opt] number timeout Maximum time to wait before failing the running test, -- in milliseconds. Defaults to 200 milliseconds. -- @return All parameters to `continue()`. function _M.wait(timeout) assert(shared_lib.current_coroutine, "Not currently running a test!") assert(coroutine.running() == shared_lib.current_coroutine, "Not currently running in the test coroutine!") assert(not timeout or type(timeout) == "number", "Expected number") assert(not waiting, "Already waiting") shared_lib.traceback = debug.traceback("",2) waiting = true timeout = timeout or 200 return coroutine.yield({timeout=timeout}) end --- Continue test execution. -- -- The running test, currently suspended after a call to `wait()`, is resumed. -- `wait()` must have been previously called. -- -- All parameters to `continue()` are returned by `wait()`. -- @param ... Values to return from `wait()`. function _M.continue(...) assert(shared_lib.current_coroutine, "Not currently running a test!") assert(waiting and (coroutine.running() ~= shared_lib.current_coroutine), "Not waiting, cannot continue") waiting = false shared_lib.resume_suspended_test(...) end --- Get the URI prefix for the test HTTP server. -- -- The port the test server listens on may not always be the same. This function -- returns the current URI prefix, which looks like `http://127.0.0.1:8888/`. -- -- Currently, however, there is no HTTP server; instead, the custom URI scheme -- `luakit-test://` is used. -- @treturn string The URI prefix for the test HTTP server. function _M.http_server() return "luakit-test://" end --- Retrieve a subset of files in the current directory. -- -- This function searches the directory and then filters the result -- according to the provided parameters and the `.gitignore` file. It -- is mostly intended for use in code style tests. The returned list of -- file paths includes all files that: -- -- * are within at least one of the directories in `dirs`, -- * match at least one of the Lua patterns in `patterns`, and -- * do _not_ match any of the Lua patterns in `excludes`. -- -- @function find_files -- @tparam string|table dirs The directory prefix (or list of prefixes) in which -- to look for files. -- @tparam string|table patterns A Lua pattern (or list of patterns) with which -- to filter file paths; non-matching files are removed. -- @tparam[opt] table excludes A list of Lua patterns with which to filter file -- paths; matching files are removed. -- @treturn table A list of matching file paths. _M.find_files = find_files.find_files --- Helper function to format a list of file errors. -- -- Aligns file names and file errors into two separate columns. -- -- @tparam {entry} entries A list of file error entries. -- -- # `entry` format -- -- - file: The path of the file. -- - err: The error string. -- @treturn string The formatted output string. function _M.format_file_errors(entries) assert(type(entries) == "table") local sep = " " -- Find file alignment length local align, luakit_files = 0, find_files.get_luakit_files() for _, file in ipairs(luakit_files) do align = math.max(align, file:len()) end -- Build output local lines = {} local prev_file = nil for _, entry in ipairs(entries) do local file = entry.file ~= prev_file and entry.file or "" prev_file = entry.file local line = string.format("%-" .. tostring(align) .. "s%s%s", file, sep, entry.err) table.insert(lines, line) end return table.concat(lines, "\n") end return _M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/priv.lua000066400000000000000000000010031475363222200156330ustar00rootroot00000000000000--- Utilities for use in tests. -- -- @module tests.util -- @copyright 2017 Aidan Holm local M = {} function M.load_test_file(test_file) local ok, ret = pcall(dofile, test_file) if not ok then return nil, ret end assert(type(ret) == "table") for test_name, func in pairs(ret) do assert(type(test_name) == "string") assert(type(func) == "function" or type(func) == "thread") end return ret end return M -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/run_test.lua000077500000000000000000000165611475363222200165400ustar00rootroot00000000000000#!/usr/bin/env luajit --- Main test runner. -- -- @script run_test -- @copyright 2017 Aidan Holm -- Add ./tests to package.path package.path = package.path .. ';./tests/?.lua' package.path = package.path .. ';./lib/?.lua;./lib/?/init.lua' local shared_lib = {} local test = require "tests.lib" local priv = require "tests.priv" local util = require "tests.util" test.init(shared_lib) local lfs = require "lfs" local lousy = { util = require "lousy.util" } local orig_print = print local xvfb_display local current_test_file local current_test_name local prev_test_name local have_test_failures = false -- Wrap print() local function log_test_output(...) local msg = table.concat({...}, "\t") prev_test_name = nil local indent = " " orig_print(indent .. msg:gsub("\n", "\n" .. indent)) end local function update_test_status(status, test_name, test_file) assert(type(status) == "string") local esc = string.char(27) local c_red = esc .. "[0;31m" local c_green = esc .. "[0;32m" local c_grey = esc .. "[0;37m" local c_reset = esc .. "[0;0m" local status_color = ({ pass = c_green, fail = c_red, wait = c_grey, cont = c_grey, run = c_grey, load = c_grey, })[status] or "" -- Beginning a new test file if status == "load" then current_test_file = test_file:match("tests/(.*)%.lua") current_test_name = "" end if status == "run" then status = "run " current_test_name = test_name end if status == "fail" then have_test_failures = true end -- Overwrite the previous status line if it's for the same test -- Or if the previous test was "" (a test file load) if prev_test_name == current_test_name or prev_test_name == "" then io.write(esc .. "[1A" .. esc .. "[K") end prev_test_name = current_test_name local line = current_test_file .. " / " .. current_test_name orig_print(status_color .. status:upper() .. c_reset .. " " .. line) end local function do_style_tests(test_files) print = log_test_output -- luacheck: ignore for _, test_file in ipairs(test_files) do -- Load test table update_test_status("load", "", test_file) local T, err = priv.load_test_file(test_file) if not T then update_test_status("fail") log_test_output(err) break end for test_name, func in pairs(T) do assert(type(test_name) == "string") assert(type(func) == "function") update_test_status("run", test_name, test_file) local ok, ret = pcall(func) update_test_status(ok and "pass" or "fail") if not ok and ret then log_test_output(ret) end end end print = orig_print -- luacheck: ignore end local luakit_tmp_dirs = {} local function spawn_luakit_instance(config, ...) -- Create a temporary directory, hopefully on a ramdisk local dir = util.make_tmp_dir("luakit_test_XXXXXX") table.insert(luakit_tmp_dirs, dir) -- Cheap version of a chroot that doesn't require special permissions local env = { HOME = dir, XDG_CACHE_HOME = dir .. "/cache", XDG_DATA_HOME = dir .. "/data", XDG_CONFIG_HOME = dir .. "/config", XDG_RUNTIME_DIR = dir .. "/runtime", XDG_CONFIG_DIRS = "", DISPLAY = xvfb_display } -- HACK: make GStreamer shut up about not finding random .so files -- when it rebuilds its registry, which it does with every single -- luakit instance spawned this way local cache_dir = util.getenv("XDG_CACHE_HOME") or (util.getenv("HOME") .. "/") local gst_dir = cache_dir .. "/gstreamer-1.0" if lfs.attributes(gst_dir, "mode") == "directory" then os.execute("mkdir -p " .. env.XDG_CACHE_HOME .. "/gstreamer-1.0/") os.execute("cp "..gst_dir.."/registry.x86_64.bin " .. env.XDG_CACHE_HOME .. "/gstreamer-1.0") end -- Build env prefix local cmd = "env -i - " for k, v in pairs(env) do cmd = cmd .. k .."=" .. v .. " " end cmd = cmd .. "./luakit -U --log=error -c " .. config .. " " .. table.concat({...}, " ") .. " 2>&1" return assert(io.popen(cmd)) end -- On exit stuff local exit_handlers = {} local cleanup = function () for _, f in ipairs(exit_handlers) do f() end exit_handlers = {} end -- Run automatically local onexit_prx = newproxy(true) getmetatable(onexit_prx).__gc = cleanup -- Automatically clean up test directories table.insert(exit_handlers, function () print("Removing temporary directories") for _, dir in ipairs(luakit_tmp_dirs) do os.execute("rm -r " .. dir) end end) local function do_async_tests(test_files) for _, test_file in ipairs(test_files) do local f = spawn_luakit_instance("tests/async/run_test.lua", test_file) local status, test_name for line in f:lines() do status, test_name = line:match("^__(%a+)__ (.*)$") if status and test_name then update_test_status(status, test_name, test_file) else log_test_output(line) end end f:close() end end -- Check for luassert if not pcall(require, "luassert") then print("Running tests requires installing luassert") os.exit(1) end -- Check for untracked files in Git local git=io.open(".git","r") if git~=nil then io.close(git) do local untracked = {} local f = io.popen("git ls-files --others --exclude-standard") for line in f:lines() do table.insert(untracked, line) end f:close() if #untracked > 0 then local c_yellow = string.char(27) .. "[0;33m" local c_reset = string.char(27) .. "[0;0m" print(c_yellow .. "WARN" .. c_reset .. " The following files are untracked:") for _, line in ipairs(untracked) do print(" " .. line) end end end end -- Find a free server number -- Does have a race condition... for i=0,math.huge do local flat_lock = lfs.attributes(("/tmp/.X%d-lock"):format(i)) local nest_lock = lfs.attributes(("/tmp/.X11-unix/X%d"):format(i)) if not (flat_lock or nest_lock) then xvfb_display = ":" .. tostring(i) break end end -- Launch Xvfb for lifetime of test runner print("Starting Xvfb") local pid_xvfb = assert(util.spawn_async({"Xvfb", xvfb_display, "-screen", "0", "800x600x8"})) table.insert(exit_handlers, function () print("Stopping Xvfb") util.kill(pid_xvfb) end) -- Find test files local test_file_pat = "/test_%S+%.lua$" local test_files = { style = test.find_files("tests/style/", test_file_pat), async = test.find_files("tests/async/", test_file_pat), } -- Filter test files to arguments local include_patterns = arg if #arg > 0 then for k, v in pairs(test_files) do test_files[k] = lousy.util.table.filter_array(v, function (_, file) for _, pat in ipairs(include_patterns) do if file:match(pat) then return true end end return false end) end end local ok, err = pcall(function () do_style_tests(test_files.style) do_async_tests(test_files.async) end) if not ok then print("\n" .. err) end cleanup() os.exit(have_test_failures and 1 or 0) -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/style/000077500000000000000000000000001475363222200153165ustar00rootroot00000000000000luakit-2.4.0/tests/style/test_common.lua000066400000000000000000000013721475363222200203530ustar00rootroot00000000000000local test = require "tests.lib" local T = {} function T.test_no_globalconf_in_common() local has_globalconf = {} local file_list = test.find_files("common", "%.[ch]$") for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() if contents:match("globalconf") then table.insert(has_globalconf, file) end end if #has_globalconf > 0 then local err = {} for _, file in ipairs(has_globalconf) do err[#err+1] = " " .. file end error("Some files in common/ access globalconf:\n" .. table.concat(err, "\n")) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/style/test_documentation.lua000066400000000000000000000020201475363222200217230ustar00rootroot00000000000000local test = require "tests.lib" local T = {} local function read_file(file) local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() return contents end function T.test_module_blurb_not_empty () local file_list = test.find_files({"lib", "doc/luadoc"}, "%.lua$", {"lib/lousy/widget/", "lib/lousy/init.lua", "lib/markdown.lua"}) local errors = {} for _, file in ipairs(file_list) do local header = read_file(file):gsub("\n\n.*", "") .. "\n" -- Strip heading line, empty lines, @-lines, DOCMACRO lines local desc = header:gsub("^%-%-%-.-\n", ""):gsub("%-%- *\n", "") desc = desc:gsub("-- *%@.-\n", ""):gsub("^%-%- *DOCMACRO.-\n", "") if not desc:match("^%-%-") then table.insert(errors, { file = file, err = "Missing documentation" }) end end if #errors > 0 then error("Some files do not have documentation:\n" .. test.format_file_errors(errors)) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/style/test_luacheck.lua000066400000000000000000000057421475363222200206470ustar00rootroot00000000000000local test = require "tests.lib" local luacheck = require "luacheck" local lousy = { util = require("lousy.util") } local T = {} function T.test_luacheck () local lua_dirs = {"lib", "config", "tests", "build-utils"} local exclude_files = { "lib/markdown.lua", } local options = { std = "luajit", } local shared_globals = { "luakit", "soup", "msg", "ipc_channel", "string.wlen", "regex", "utf8", } local ui_globals = { "sqlite3", "lfs", "xdg", "timer", "download", "stylesheet", "unique", "widget", "uris", "require_web_module", "os", } local wm_globals = { "extension", "dom_document", "dom_element", "page", } local file_options = { ["config/rc.lua"] = { ignore = { "211" } -- 211: Unused variable }, ["lib/adblock.lua"] = { ignore = { "542" }, -- 542: Empty if branch }, ["tests/run_test.lua"] = { ignore = { "311/.*_prx" }, -- 311: Value assigned to variable is unused }, } wm_globals = lousy.util.table.join(shared_globals, wm_globals) ui_globals = lousy.util.table.join(shared_globals, ui_globals) local file_list = test.find_files(lua_dirs, "%.lua$", exclude_files) local warnings, errors, fatals = 0, 0, 0 local issues = {} for _, file in ipairs(file_list) do -- Build options table for file local opts = lousy.util.table.clone(options) if string.match(file, ".*_wm%.lua$") then opts.globals = wm_globals else opts.globals = ui_globals end for pattern, subopts in pairs(file_options) do if string.match(file, pattern) then for k, v in pairs(subopts) do opts[k] = v end end end -- Check file, collate any warning messages local report = luacheck.check_files({file}, opts) local file_report = report[1] for _, issue in ipairs(file_report) do local src = ("%s:%d: (%d:%d):"):format(file, issue.line, issue.column, issue.end_column) local msg = luacheck.get_message(issue) issues[#issues+1] = { src = src, msg = msg } end warnings = warnings + report.warnings errors = errors + report.errors fatals = fatals + report.fatals end if warnings + errors + fatals > 0 then local align = 0 for _, issue in ipairs(issues) do align = math.max(align, issue.src:len()) end local output = {} for _, issue in ipairs(issues) do output[#output + 1] = string.format(" %-" .. tostring(align+10) .. "s %s", issue.src, issue.msg) end error("Luacheck messages:\n" .. table.concat(output, "\n")) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/style/test_source_format.lua000066400000000000000000000247131475363222200217370ustar00rootroot00000000000000local test = require "tests.lib" local T = {} local function add_file_error(errors, file, error) table.insert(errors, { file = file, err = error }) end function T.test_vim_modeline () local errors = {} -- Test all C and H files local file_list = test.find_files("", "%.[ch]$") for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() local modeline_pat = "[^\n]\n\n// vim: ft=c:et:sw=4:ts=8:sts=4:tw=80\n?$" if not contents:match(modeline_pat) then add_file_error(errors, file, "Missing/malformed modeline") end end -- Test all lua files file_list = test.find_files("", "%.lua$", {"lib/markdown.lua"}) for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() local modeline_pat = "[^\n]\n\n%-%- vim: et:sw=4:ts=8:sts=4:tw=80\n?$" if not contents:match(modeline_pat) then add_file_error(errors, file, "Missing/malformed modeline") end end if #errors > 0 then error("Some files do not have modelines:\n" .. test.format_file_errors(errors)) end end function T.test_include_guard () local include_guard_pat = "#ifndef LUAKIT_%s\n#define LUAKIT_%s\n\n" local errors = {} local file_list = test.find_files("", "%.h$") for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() local s = file:gsub("[%.%/]", "_"):upper() local pat = include_guard_pat:format(s, s) if not contents:match(pat) then add_file_error(errors, file, "Missing/malformed include guard") end end if #errors > 0 then error("Some files do not have include guards:\n" .. test.format_file_errors(errors)) end end local function get_first_paragraph_of_file(file) -- Get first paragraph of file local f = assert(io.open(file, "r")) local lines = {} for line in f:lines() do lines[#lines + 1] = line if line == "" then break end end local contents = table.concat(lines, "\n") .. "\n" f:close() return contents end function T.test_header_comment () local file_desc_pat = "^%/%*\n %* (%S+) %- [^\n]*\n %*\n" local copyright_pat = " %* Copyright © [^\n]*\n" local gpl_text = [[ * 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 . * */ ]] local errors = {} local file_list = test.find_files("", "%.[ch]$") for _, file in ipairs(file_list) do local contents = get_first_paragraph_of_file(file) local file_desc_name = contents:match(file_desc_pat) local bad_file_desc = file_desc_name and file_desc_name ~= file if bad_file_desc then local err = ("File description must match file name (expected %s, got %s)"):format(file, file_desc_name) add_file_error(errors, file, err) end if not contents:find(copyright_pat) then add_file_error(errors, file, "missing/malformed copyright line") end if not contents:find(gpl_text, 1, true) then add_file_error(errors, file, "missing/malformed GPL license text") end end if #errors > 0 then error("Some files have header comment errors:\n" .. test.format_file_errors(errors)) end end function T.test_lua_header () local exclude_files = { "lib/markdown%.lua" } local errors = {} local file_list = test.find_files("lib", "%.lua$", exclude_files) for _, file in ipairs(file_list) do local contents = get_first_paragraph_of_file(file) -- Check for module or submodule documentation tag local module_pat = "\n%-%- @module ([a-z_.]+)\n" local submodule_pat = "\n%-%- @submodule [a-z_.]+\n" local is_module = not not contents:find(module_pat) local is_submodule = not not contents:find(submodule_pat) if not is_module and not is_submodule then add_file_error(errors, file, "Must have a @module or @submodule line") elseif is_module and is_submodule then add_file_error(errors, file, "Cannot have both @module and @submodule lines") end -- Check for correct module name local module_name = contents:match(module_pat) if module_name then local expected_module_name = file:match("lib/(.*).lua"):gsub("/", "."):gsub(".init$","") if module_name ~= expected_module_name then local fmt = "Module name must match file name (expected %s, got %s)" local err = fmt:format(expected_module_name, module_name) add_file_error(errors, file, err) end end -- Check summary line local summary_pat = "^%-%-%-? [^\n]*%.\n" if not contents:find(summary_pat) then add_file_error(errors, file, "Missing/malformed summary line") end if is_module and not contents:match("^%-%-%- ") then add_file_error(errors, file, "Files with @module must start with ---") end if is_submodule and not contents:match("^%-%- ") then add_file_error(errors, file, "Files with @submodule must start with --") end end if #errors > 0 then error("Some Lua files have header comment errors:\n" .. test.format_file_errors(errors)) end end function T.test_lua_module_uses_M () local exclude_files = { "lib/markdown%.lua", -- External file "lib/.*/init%.lua$", -- Module groupings "lib/widget/%S*%.lua", -- Status bar widgets "lib/introspector_chrome.lua", -- Deprecated module } local errors = {} local file_list = test.find_files("lib", "%.lua$", exclude_files) for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() -- Check for 'local _M = {}' in modules local module_pat = "\n%-%- @module ([a-z_.]+)\n" local is_module = not not contents:find(module_pat) if is_module then local _M_text = "\n\nlocal _M = {}\n\n" if not contents:find(_M_text, 1, true) then add_file_error(errors, file, "Missing/malformed module table declaration") end end end if #errors > 0 then error("Some Lua modules have module table declaration errors:\n" .. test.format_file_errors(errors)) end end local function test_lua_module_function_documentation (errors, file, lines, A, B) local func = lines[B]:match("^function _M%.([^ %(]+)%(") or lines[B]:match("^_M%.(%S+) %= ") if lines[A] ~= "" then add_file_error(errors, file .. ":" .. tostring(A), "Blank line required before export") return end A = A + 1 if A == B then add_file_error(errors, file .. ":" .. tostring(A), ("Undocumented export '%s'"):format(func)) return end if not lines[A]:match("^%-%-%- ") then add_file_error(errors, file .. ":" .. tostring(A), "Documentation must start with '--- '") end end function T.test_lua_module_functions_are_documented () local exclude_files = { "lib/markdown%.lua", -- External file } local errors = {} local file_list = test.find_files("lib", "%.lua$", exclude_files) for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local lines = {} for line in f:lines() do lines[#lines + 1] = line end f:close() -- Find all lines with ^function _M.foo lines local func_lines = {} for i, line in ipairs(lines) do if line:match("^function _M%.") or line:match("^_M%.%S+ %= ")then func_lines[#func_lines+1] = i end end -- Find the bounds of the comment section for _, i in ipairs(func_lines) do local j = i repeat j = j - 1 until j == 1 or not lines[j]:match("^%-%-") test_lua_module_function_documentation(errors, file, lines, j, i) end end if #errors > 0 then error("Some Lua modules have documentation issues:\n" .. test.format_file_errors(errors)) end end function T.test_no_tabs_in_indentation () local exclude_files = { "lib/markdown%.lua" } local errors = {} local file_list = test.find_files("", {"%.lua$", "%.[ch]$"}, exclude_files) for _, file in ipairs(file_list) do local lines = {} local f = assert(io.open(file, "r")) for line in f:lines() do lines[#lines+1] = line end f:close() for i, line in ipairs(lines) do if line:match("^(%s*)"):find("\t") then add_file_error(errors, file .. ":" .. i, "Tabs in indentation") end end end if #errors > 0 then error("Some files have tabs in indentation:\n" .. test.format_file_errors(errors)) end end function T.test_no_trailing_whitespace () local exclude_files = { "lib/markdown%.lua" } local errors = {} local file_list = test.find_files("", {"%.lua$", "%.[ch]$"}, exclude_files) for _, file in ipairs(file_list) do local lines = {} local f = assert(io.open(file, "r")) for line in f:lines() do lines[#lines+1] = line end f:close() for i, line in ipairs(lines) do if line:match("%s$") then add_file_error(errors, file .. ":" .. i, "Trailing whitespace") end end end if #errors > 0 then error("Some files have trailing whitespace:\n" .. test.format_file_errors(errors)) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/style/test_widgets.lua000066400000000000000000000013311475363222200205240ustar00rootroot00000000000000local test = require "tests.lib" local T = {} function T.test_widgets_use_macros() local errors = {} local file_list = test.find_files("widgets", "%.c$", {"widgets/webview/", "widgets/common.c"}) for _, file in ipairs(file_list) do -- Get file contents local f = assert(io.open(file, "r")) local contents = f:read("*all") f:close() if not contents:find("LUAKIT_WIDGET_SIGNAL_COMMON") then table.insert(errors, { file = file, err = "Missing LUAKIT_WIDGET_SIGNAL_COMMON" }) end end if #errors > 0 then error("Some widget wrappers have errors:\n" .. test.format_file_errors(errors)) end end return T -- vim: et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/tests/util.c000066400000000000000000000056411475363222200153050ustar00rootroot00000000000000/* * tests/util.c - testing utility functions * * Copyright © 2017 Aidan Holm * * 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 . * */ #include #include #include #include "common/util.h" #include "common/lualib.h" static int l_make_tmp_dir(lua_State *L) { const char *fmt = lua_isnil(L, 1) ? NULL : luaL_checkstring(L, 1); GError *error = NULL; char *dir = g_dir_make_tmp(fmt, &error); lua_pushstring(L, dir); g_free(dir); if (error) { lua_pushstring(L, error->message); g_error_free(error); } return error ? 2 : 1; } static int l_spawn_async(lua_State *L) { /* Convert the first argv table argument to a char** */ luaH_checktable(L, 1); size_t n = lua_objlen(L, 1); if (n == 0) return luaL_error(L, "argv must be non-empty"); GPtrArray *argv = g_ptr_array_sized_new(n + 1); for (size_t i = 1; i <= n; i++) { lua_rawgeti(L, 1, i); if (!lua_isstring(L, -1)) { g_ptr_array_free(argv, TRUE); return luaL_error(L, "non-string argv element #%u", i); } g_ptr_array_add(argv, (gpointer)lua_tostring(L, -1)); lua_pop(L, 1); } g_ptr_array_add(argv, NULL); GSpawnFlags spawn_flags = G_SPAWN_SEARCH_PATH; GPid child_pid; GError *error = NULL; g_spawn_async(NULL, (gchar**)argv->pdata, NULL, spawn_flags, NULL, NULL, &child_pid, &error); g_ptr_array_free(argv, TRUE); if (!error) lua_pushnumber(L, child_pid); else { lua_pushnil(L); lua_pushstring(L, error->message); g_error_free(error); } return error ? 2 : 1; } static int l_getenv(lua_State *L) { lua_pushstring(L, g_getenv(luaL_checkstring(L, 1))); return 1; } static int l_kill(lua_State *L) { pid_t pid = luaL_checknumber(L, 1); int sig = luaL_optint(L, 2, SIGTERM); if (!kill(pid, sig)) return 0; lua_pushstring(L, strerror(errno)); return 1; } int luaopen_tests_util(lua_State *L) { static const struct luaL_Reg util [] = { {"make_tmp_dir", l_make_tmp_dir}, {"spawn_async", l_spawn_async}, {"getenv", l_getenv}, {"kill", l_kill}, {NULL, NULL}, }; luaL_openlib(L, "tests.util", util, 0); return 1; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/web_context.c000066400000000000000000000070651475363222200155110ustar00rootroot00000000000000/* * web_context.c - WebKit web context setup and handling * * Copyright © 2016 Aidan Holm * * 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 . * */ #include "globalconf.h" #include "common/log.h" #include "web_context.h" #include /** WebKit context common to all web views */ static WebKitWebContext *web_context; /** WebKit process count; default to unlimited */ static guint process_limit = 0; /** Whether the web context startup function has been run */ static gboolean web_context_started = FALSE; /** Defined in widgets/webview/downloads.c */ gboolean download_start_cb(WebKitWebContext *, WebKitDownload *, gpointer); WebKitWebContext * web_context_get(void) { g_assert(web_context); return web_context; } guint web_context_process_limit_get(void) { return process_limit; } gboolean web_context_process_limit_set(guint limit) { if (web_context_started) return FALSE; process_limit = limit; return TRUE; } static void website_data_manager_init(void) { WebKitWebsiteDataManager *data_mgr = webkit_website_data_manager_new( "base-cache-directory", globalconf.cache_dir, "base-data-directory", globalconf.data_dir, NULL); web_context = webkit_web_context_new_with_website_data_manager(data_mgr); verbose("base_data_directory: %s", webkit_website_data_manager_get_base_data_directory(data_mgr)); verbose("base_cache_directory: %s", webkit_website_data_manager_get_base_cache_directory(data_mgr)); } static void web_context_set_default_spelling_language(void) { /* This seems to autodetect spell checking languages */ const gchar *null = NULL; webkit_web_context_set_spell_checking_languages(web_context, &null); gchar **ret = (gchar**)webkit_web_context_get_spell_checking_languages(web_context); if (!ret) return; gchar *langs = g_strjoinv(", ", ret); verbose("setting spell check languages: %s", langs); g_free(langs); } void web_context_init(void) { website_data_manager_init(); webkit_web_context_set_favicon_database_directory(web_context, NULL); g_signal_connect(G_OBJECT(web_context), "download-started", G_CALLBACK(download_start_cb), NULL); /* Set default cookie policy: must match default in clib/soup.c */ WebKitCookieManager *cookie_mgr = webkit_web_context_get_cookie_manager(web_context); webkit_cookie_manager_set_accept_policy(cookie_mgr, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY); web_context_set_default_spelling_language(); } void web_context_init_finish(void) { if (web_context_started) return; #if !WEBKIT_CHECK_VERSION(2,26,0) webkit_web_context_set_process_model(web_context, WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES); info("Web process count: %d", process_limit); webkit_web_context_set_web_process_count_limit(web_context, process_limit); #endif web_context_started = TRUE; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/web_context.h000066400000000000000000000022111475363222200155020ustar00rootroot00000000000000/* * web_context.h - WebKit web context setup and handling * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_WEB_CONTEXT_H #define LUAKIT_WEB_CONTEXT_H #include void web_context_init(void); void web_context_init_finish(void); WebKitWebContext *web_context_get(void); WebKitWebContext *web_context_get_private(void); guint web_context_process_limit_get(void); gboolean web_context_process_limit_set(guint limit); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/000077500000000000000000000000001475363222200144625ustar00rootroot00000000000000luakit-2.4.0/widgets/box.c000066400000000000000000000110061475363222200154140ustar00rootroot00000000000000/* * widgets/box.c - gtk hbox & vbox container widgets * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_box_pack(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint top = lua_gettop(L); gboolean expand = FALSE, fill = FALSE, start = TRUE; guint padding = 0; /* check for options table */ if (top > 2 && !lua_isnil(L, 3)) { luaH_checktable(L, 3); /* pack child from start or end of container? */ if (luaH_rawfield(L, 3, "from")) start = L_TK_END == l_tokenize(lua_tostring(L, -1)) ? FALSE : TRUE; /* expand? */ if (luaH_rawfield(L, 3, "expand")) expand = lua_toboolean(L, -1) ? TRUE : FALSE; /* fill? */ if (luaH_rawfield(L, 3, "fill")) fill = lua_toboolean(L, -1) ? TRUE : FALSE; /* padding? */ if (luaH_rawfield(L, 3, "padding")) padding = (guint)lua_tonumber(L, -1); /* return stack to original state */ lua_settop(L, top); } if (start) gtk_box_pack_start(GTK_BOX(w->widget), GTK_WIDGET(child->widget), expand, fill, padding); else gtk_box_pack_end(GTK_BOX(w->widget), GTK_WIDGET(child->widget), expand, fill, padding); return 0; } /* direct wrapper around gtk_box_reorder_child */ static gint luaH_box_reorder_child(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint pos = luaL_checknumber(L, 3); gtk_box_reorder_child(GTK_BOX(w->widget), GTK_WIDGET(child->widget), pos); return 0; } static gint luaH_box_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push class methods */ PF_CASE(PACK, luaH_box_pack) PF_CASE(REORDER, luaH_box_reorder_child) /* push boolean properties */ PB_CASE(HOMOGENEOUS, gtk_box_get_homogeneous(GTK_BOX(w->widget))) /* push string properties */ PN_CASE(SPACING, gtk_box_get_spacing(GTK_BOX(w->widget))) PS_CASE(BG, g_object_get_data(G_OBJECT(w->widget), "bg")) default: break; } return 0; } static gint luaH_box_newindex(lua_State *L, widget_t *w, luakit_token_t token) { size_t len; const gchar *tmp; GdkRGBA c; switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_HOMOGENEOUS: gtk_box_set_homogeneous(GTK_BOX(w->widget), luaH_checkboolean(L, 3)); break; case L_TK_SPACING: gtk_box_set_spacing(GTK_BOX(w->widget), luaL_checknumber(L, 3)); break; case L_TK_BG: tmp = luaL_checklstring(L, 3, &len); if (!gdk_rgba_parse(&c, tmp)) luaL_argerror(L, 3, "unable to parse colour"); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "background-color", tmp, NULL); #else gtk_widget_override_background_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "bg", g_strdup(tmp), g_free); break; default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_box(lua_State *UNUSED(L), widget_t *w, luakit_token_t token) { w->index = luaH_box_index; w->newindex = luaH_box_newindex; w->widget = gtk_box_new((token == L_TK_VBOX) ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_set_homogeneous(GTK_BOX(w->widget), (token == L_TK_VBOX) ? FALSE : TRUE); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::add", G_CALLBACK(add_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/common.c000066400000000000000000000415321475363222200161230ustar00rootroot00000000000000/* * widgets/common.c - common widget functions or callbacks * * Copyright © 2010 Mason Larobina * * 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 . * */ #include #include "luah.h" #include "globalconf.h" #include "common/luaobject.h" #include "common/lualib.h" #include "widgets/common.h" gboolean key_press_cb(GtkWidget* UNUSED(win), GdkEventKey *ev, widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); luaH_keystr_push(L, ev->keyval); lua_pushboolean(L, ev->send_event); gint ret = luaH_object_emit_signal(L, -4, "key-press", 3, 1); gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE; lua_pop(L, ret + 1); return catch; } gboolean button_cb(GtkWidget* UNUSED(win), GdkEventButton *ev, widget_t *w) { gint ret; lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); lua_pushinteger(L, ev->button); switch (ev->type) { case GDK_2BUTTON_PRESS: ret = luaH_object_emit_signal(L, -3, "button-double-click", 2, 1); break; case GDK_BUTTON_RELEASE: ret = luaH_object_emit_signal(L, -3, "button-release", 2, 1); break; default: ret = luaH_object_emit_signal(L, -3, "button-press", 2, 1); break; } gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE; lua_pop(L, ret + 1); return catch; } gboolean scroll_cb(GtkWidget *UNUSED(wid), GdkEventScroll *ev, widget_t *w) { double dx, dy; switch (ev->direction) { case GDK_SCROLL_UP: dx = 0; dy = -1; break; case GDK_SCROLL_DOWN: dx = 0; dy = 1; break; case GDK_SCROLL_LEFT: dx = -1; dy = 0; break; case GDK_SCROLL_RIGHT: dx = 1; dy = 0; break; case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas((GdkEvent*)ev, &dx, &dy); break; default: g_assert_not_reached(); } lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); lua_pushnumber(L, dx); lua_pushnumber(L, dy); gboolean ret = luaH_object_emit_signal(L, -4, "scroll", 3, 1); lua_pop(L, ret + 1); return ret; } gboolean mouse_cb(GtkWidget* UNUSED(win), GdkEventCrossing *ev, widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); GdkEventType type = ev->type; g_assert(type == GDK_ENTER_NOTIFY || type == GDK_LEAVE_NOTIFY); gint ret = luaH_object_emit_signal(L, -2, type == GDK_ENTER_NOTIFY ? "mouse-enter" : "mouse-leave", 1, 1); gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE; lua_pop(L, ret + 1); return catch; } gboolean focus_cb(GtkWidget* UNUSED(win), GdkEventFocus *ev, widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); gint ret; if (ev->in) ret = luaH_object_emit_signal(L, -1, "focus", 0, 1); else ret = luaH_object_emit_signal(L, -1, "unfocus", 0, 1); /* catch focus event */ if (ret && lua_toboolean(L, -1)) { lua_pop(L, ret + 1); return TRUE; } lua_pop(L, ret + 1); /* propagate event further */ return FALSE; } /* gtk container add callback */ void add_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w) { widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); luaH_object_emit_signal(L, -2, "add", 1, 0); lua_pop(L, 1); } void resize_cb(GtkWidget* UNUSED(win), GdkRectangle *rect, widget_t *w) { int width = rect->width, height = rect->height; if (width == w->prev_width && height == w->prev_height) return; w->prev_width = width; w->prev_height = height; lua_State *L = common.L; luaH_object_push(L, w->ref); lua_pushinteger(L, width); lua_pushinteger(L, height); luaH_object_emit_signal(L, -3, "resize", 2, 0); lua_pop(L, 1); } /* gtk container remove callback */ void remove_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w) { widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); luaH_object_emit_signal(L, -2, "remove", 1, 0); lua_pop(L, 1); } void parent_set_cb(GtkWidget *widget, GtkWidget *UNUSED(p), widget_t *w) { lua_State *L = common.L; widget_t *parent = NULL; GtkContainer *new; g_object_get(G_OBJECT(widget), "parent", &new, NULL); luaH_object_push(L, w->ref); if (new && (parent = GOBJECT_TO_LUAKIT_WIDGET(new))) luaH_object_push(L, parent->ref); else lua_pushnil(L); luaH_object_emit_signal(L, -2, "parent-set", 1, 0); lua_pop(L, 1); } void destroy_cb(GtkWidget* UNUSED(win), widget_t *w) { /* 1. emit destroy signal */ lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "destroy", 0, 0); lua_pop(L, 1); /* 2. Call widget destructor */ debug("destroy %p (%s)", w, w->info->name); if (w->destructor) w->destructor(w); w->destructor = NULL; w->widget = NULL; /* 3. Allow this Lua instance to be freed */ luaH_object_unref(L, w->ref); } gboolean true_cb() { return TRUE; } /* set child method for gtk container widgets */ gint luaH_widget_set_child(lua_State *L, widget_t *w) { widget_t *child = luaH_checkwidgetornil(L, 3); /* remove old child */ GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget)); if (widget) { g_object_ref(G_OBJECT(widget)); gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(widget)); } /* add new child to container */ if (child) gtk_container_add(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget)); return 0; } /* get child method for gtk container widgets */ gint luaH_widget_get_child(lua_State *L, widget_t *w) { GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget)); if (!widget) return 0; widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); luaH_object_push(L, child->ref); return 1; } gint luaH_widget_remove(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); g_object_ref(G_OBJECT(child->widget)); gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget)); return 0; } gint luaH_widget_get_children(lua_State *L, widget_t *w) { if (!GTK_IS_CONTAINER(w->widget)) return 0; GList *children = gtk_container_get_children(GTK_CONTAINER(w->widget)); GList *iter = children; /* push table of the containers children onto the stack */ lua_newtable(L); for (gint i = 1; iter; iter = iter->next) { luaH_object_push(L, GOBJECT_TO_LUAKIT_WIDGET(iter->data)->ref); lua_rawseti(L, -2, i++); } g_list_free(children); return 1; } gint luaH_widget_replace(lua_State *L) { widget_t *och = luaH_checkwidget(L, 1); widget_t *nch = luaH_checkwidget(L, 2); GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(och->widget)); if (!parent) return 0; guint num_props; GParamSpec **props = gtk_container_class_list_child_properties( G_OBJECT_GET_CLASS(parent), &num_props); GValue *values = g_new0(GValue, num_props); for (guint i = 0; i < num_props; i++) { g_value_init(&values[i], G_PARAM_SPEC_VALUE_TYPE(props[i])); gtk_container_child_get_property(GTK_CONTAINER(parent), GTK_WIDGET(och->widget), props[i]->name, &values[i]); } g_object_ref(G_OBJECT(och->widget)); gtk_container_remove(GTK_CONTAINER(parent), GTK_WIDGET(och->widget)); gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(nch->widget)); for (guint i = 0; i < num_props; i++) { gtk_container_child_set_property(GTK_CONTAINER(parent), GTK_WIDGET(nch->widget), props[i]->name, &values[i]); g_value_unset(&values[i]); } g_free(props); g_free(values); return 0; } gint luaH_widget_show(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); gtk_widget_show(w->widget); return 0; } gint luaH_widget_hide(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); gtk_widget_hide(w->widget); return 0; } gint luaH_widget_send_key(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); const gchar *key_name = luaL_checkstring(L, 2); if (!lua_istable(L, 3)) { lua_newtable(L); lua_insert(L, 3); } const gboolean is_release = lua_toboolean(L, 4); if (!g_utf8_validate(key_name, -1, NULL)) return luaL_error(L, "key name isn't a utf-8 string"); guint keyval; if (g_utf8_strlen(key_name, -1) == 1) keyval = gdk_unicode_to_keyval(g_utf8_get_char(key_name)); else keyval = gdk_keyval_from_name(key_name); if (!keyval || keyval == GDK_KEY_VoidSymbol) return luaL_error(L, "failed to get a valid key value"); guint state = 0; GString *state_string = g_string_sized_new(32); lua_pushnil(L); while (lua_next(L, 3)) { const gchar *mod = luaL_checkstring(L, -1); g_string_append_printf(state_string, "%s-", mod); #define MODKEY(modstr, modconst) \ if (strcmp(modstr, mod) == 0) { \ state = state | GDK_##modconst##_MASK; \ } MODKEY("shift", SHIFT); MODKEY("control", CONTROL); MODKEY("lock", LOCK); MODKEY("mod1", MOD1); MODKEY("mod2", MOD2); MODKEY("mod3", MOD3); MODKEY("mod4", MOD4); MODKEY("mod5", MOD5); #undef MODKEY lua_pop(L, 1); } GdkKeymapKey *keys = NULL; gint n_keys; #if GTK_CHECK_VERSION(3,22,0) GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default()); #else GdkKeymap *keymap = gdk_keymap_get_default(); #endif if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) { g_string_free(state_string, TRUE); return luaL_error(L, "cannot type '%s' on current keyboard layout", key_name); } GdkEvent *event = gdk_event_new(is_release ? GDK_KEY_RELEASE : GDK_KEY_PRESS); GdkEventKey *event_key = (GdkEventKey *) event; event_key->window = gtk_widget_get_window(w->widget); event_key->send_event = TRUE; event_key->time = GDK_CURRENT_TIME; event_key->state = state; event_key->keyval = keyval; event_key->hardware_keycode = keys[0].keycode; event_key->group = keys[0].group; GdkDevice *kbd = NULL; #if GTK_CHECK_VERSION(3,20,0) GdkSeat *seat = gdk_display_get_default_seat(gdk_display_get_default()); kbd = gdk_seat_get_keyboard(seat); #else GdkDeviceManager *dev_mgr = gdk_display_get_device_manager(gdk_display_get_default()); GList *devices = gdk_device_manager_list_devices(dev_mgr, GDK_DEVICE_TYPE_MASTER); for (GList *dev = devices; dev && !kbd; dev = dev->next) if (gdk_device_get_source(dev->data) == GDK_SOURCE_KEYBOARD) kbd = dev->data; g_list_free(devices); #endif if (!kbd) return luaL_error(L, "failed to find a keyboard device"); gdk_event_set_device(event, kbd); gboolean ret; debug("sending key '%s%s' to widget %p", state_string->str, key_name, w->widget); g_signal_emit_by_name(w->widget, is_release ? "key-release-event" : "key-press-event", event, &ret); g_string_free(state_string, TRUE); g_free(keys); return 0; } gint luaH_widget_set_visible(lua_State *L, widget_t *w) { gboolean visible = luaH_checkboolean(L, 3); gtk_widget_set_visible(w->widget, visible); if (visible && w->info->tok == L_TK_WINDOW) gdk_window_set_events(gtk_widget_get_window(w->widget), GDK_ALL_EVENTS_MASK); return 0; } gint luaH_widget_get_min_size(lua_State *L, widget_t *w) { gint width, height; gtk_widget_get_size_request(w->widget, &width, &height); lua_newtable(L); lua_pushliteral(L, "width"); lua_pushinteger(L, width); lua_rawset(L, -3); lua_pushliteral(L, "height"); lua_pushinteger(L, height); lua_rawset(L, -3); return 1; } gint luaH_widget_set_min_size(lua_State *L, widget_t *w) { luaH_checktable(L, 3); gint width, height; gtk_widget_get_size_request(w->widget, &width, &height); gint top = lua_gettop(L); if (luaH_rawfield(L, 3, "w")) width = lua_tonumber(L, -1); if (luaH_rawfield(L, 3, "h")) height = lua_tonumber(L, -1); lua_settop(L, top); gtk_widget_set_size_request(w->widget, width, height); return 1; } gint luaH_widget_get_align(lua_State *L, widget_t *w) { GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)), valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget)); lua_createtable(L, 0, 2); /* set align.h */ lua_pushliteral(L, "h"); lua_pushnumber(L, halign); lua_rawset(L, -3); /* set align.v */ lua_pushliteral(L, "v"); lua_pushnumber(L, valign); lua_rawset(L, -3); return 1; } gint luaH_widget_set_align(lua_State *L, widget_t *w) { luaH_checktable(L, 3); GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)), valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget)); if (luaH_rawfield(L, 3, "h")) switch (l_tokenize(lua_tostring(L, -1))) { case L_TK_FILL: halign = GTK_ALIGN_FILL; break; case L_TK_START: halign = GTK_ALIGN_START; break; case L_TK_END: halign = GTK_ALIGN_END; break; case L_TK_CENTER: halign = GTK_ALIGN_CENTER; break; case L_TK_BASELINE: halign = GTK_ALIGN_BASELINE; break; default: return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)"); } if (luaH_rawfield(L, 3, "v")) switch (l_tokenize(lua_tostring(L, -1))) { case L_TK_FILL: valign = GTK_ALIGN_FILL; break; case L_TK_START: valign = GTK_ALIGN_START; break; case L_TK_END: valign = GTK_ALIGN_END; break; case L_TK_CENTER: valign = GTK_ALIGN_CENTER; break; case L_TK_BASELINE: valign = GTK_ALIGN_BASELINE; break; default: return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)"); } gtk_widget_set_halign(GTK_WIDGET(w->widget), halign); gtk_widget_set_valign(GTK_WIDGET(w->widget), valign); return 0; } gint luaH_widget_set_tooltip(lua_State *L, widget_t *w) { gtk_widget_set_tooltip_markup(w->widget, lua_tostring(L, 3) ?: ""); return 0; } gint luaH_widget_get_tooltip(lua_State *L, widget_t *w) { lua_pushstring(L, gtk_widget_get_tooltip_markup(w->widget)); return 1; } gint luaH_widget_get_parent(lua_State *L, widget_t *w) { GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(w->widget)); if (!widget) return 0; widget_t *parent = GOBJECT_TO_LUAKIT_WIDGET(widget); luaH_object_push(L, parent->ref); return 1; } gint luaH_widget_get_focused(lua_State *L, widget_t *w) { gboolean focused = w->info->tok == L_TK_WINDOW ? gtk_window_has_toplevel_focus(GTK_WINDOW(w->widget)) : gtk_widget_is_focus(w->widget); lua_pushboolean(L, focused); return 1; } gint luaH_widget_get_visible(lua_State *L, widget_t *w) { lua_pushboolean(L, gtk_widget_get_visible(w->widget)); return 1; } gint luaH_widget_get_width(lua_State *L, widget_t *w) { lua_pushnumber(L, gtk_widget_get_allocated_width(w->widget)); return 1; } gint luaH_widget_get_height(lua_State *L, widget_t *w) { lua_pushnumber(L, gtk_widget_get_allocated_height(w->widget)); return 1; } gint luaH_widget_focus(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); switch (w->info->tok) { case L_TK_WINDOW: /* win:focus() unfocuses anything within that window */ gtk_window_set_focus(GTK_WINDOW(w->widget), NULL); break; case L_TK_ENTRY: gtk_entry_grab_focus_without_selecting(GTK_ENTRY(w->widget)); break; default: gtk_widget_grab_focus(w->widget); break; } return 0; } gint luaH_widget_destroy(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); gtk_widget_destroy(GTK_WIDGET(w->widget)); return 0; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/common.h000066400000000000000000000144471475363222200161350ustar00rootroot00000000000000/* * widgets/common.h - common widget functions or callbacks * * Copyright © 2010 Mason Larobina * * 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 . * */ #ifndef LUAKIT_WIDGETS_COMMON_H #define LUAKIT_WIDGETS_COMMON_H #include "clib/widget.h" #define LUAKIT_WIDGET_INDEX_COMMON(widget) \ case L_TK_PARENT: \ return luaH_widget_get_parent(L, widget); \ case L_TK_FOCUSED: \ return luaH_widget_get_focused(L, widget); \ case L_TK_VISIBLE: \ return luaH_widget_get_visible(L, widget); \ case L_TK_TOOLTIP: \ return luaH_widget_get_tooltip(L, widget); \ case L_TK_WIDTH: \ return luaH_widget_get_width(L, widget); \ case L_TK_HEIGHT: \ return luaH_widget_get_height(L, widget); \ case L_TK_MIN_SIZE: \ return luaH_widget_get_min_size(L, widget); \ case L_TK_ALIGN: \ return luaH_widget_get_align(L, widget); \ case L_TK_CHILDREN: \ return luaH_widget_get_children(L, widget); \ case L_TK_SHOW: \ lua_pushcfunction(L, luaH_widget_show); \ return 1; \ case L_TK_HIDE: \ lua_pushcfunction(L, luaH_widget_hide); \ return 1; \ case L_TK_FOCUS: \ lua_pushcfunction(L, luaH_widget_focus); \ return 1; \ case L_TK_DESTROY: \ lua_pushcfunction(L, luaH_widget_destroy); \ return 1; \ case L_TK_REPLACE: \ lua_pushcfunction(L, luaH_widget_replace); \ return 1; \ case L_TK_SEND_KEY: \ lua_pushcfunction(L, luaH_widget_send_key); \ return 1; \ #define LUAKIT_WIDGET_NEWINDEX_COMMON(widget) \ case L_TK_VISIBLE: \ luaH_widget_set_visible(L, widget); \ break; \ case L_TK_TOOLTIP: \ luaH_widget_set_tooltip(L, widget); \ break; \ case L_TK_MIN_SIZE: \ luaH_widget_set_min_size(L, widget); \ break; \ case L_TK_ALIGN: \ luaH_widget_set_align(L, widget); \ break; \ #define LUAKIT_WIDGET_BIN_INDEX_COMMON(widget) \ case L_TK_CHILD: \ return luaH_widget_get_child(L, widget); #define LUAKIT_WIDGET_BIN_NEWINDEX_COMMON(widget) \ case L_TK_CHILD: \ luaH_widget_set_child(L, widget); \ break; #define LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(widget) \ case L_TK_REMOVE: \ lua_pushcfunction(L, luaH_widget_remove); \ return 1; \ #define LUAKIT_WIDGET_SIGNAL_COMMON(w) \ "signal::destroy", G_CALLBACK(destroy_cb), w, \ "signal::size-allocate", G_CALLBACK(resize_cb), w, \ "signal::focus-in-event", G_CALLBACK(focus_cb), w, \ "signal::focus-out-event", G_CALLBACK(focus_cb), w, \ "signal::parent-set", G_CALLBACK(parent_set_cb), w, gboolean button_cb(GtkWidget*, GdkEventButton*, widget_t*); gboolean scroll_cb(GtkWidget*, GdkEventScroll*, widget_t*); gboolean mouse_cb(GtkWidget*, GdkEventCrossing*, widget_t*); gboolean focus_cb(GtkWidget*, GdkEventFocus*, widget_t*); gboolean key_press_cb(GtkWidget*, GdkEventKey*, widget_t*); gboolean key_release_cb(GtkWidget*, GdkEventKey*, widget_t*); gboolean true_cb(); gint luaH_widget_destroy(lua_State*); gint luaH_widget_focus(lua_State*); gint luaH_widget_get_child(lua_State*, widget_t*); gint luaH_widget_get_children(lua_State*, widget_t*); gint luaH_widget_hide(lua_State*); gint luaH_widget_remove(lua_State*); gint luaH_widget_set_child(lua_State*, widget_t*); gint luaH_widget_show(lua_State*); gint luaH_widget_replace(lua_State*); gint luaH_widget_send_key(lua_State *); gint luaH_widget_get_parent(lua_State *L, widget_t *w); gint luaH_widget_get_focused(lua_State *L, widget_t*); gint luaH_widget_get_visible(lua_State *L, widget_t*); gint luaH_widget_get_width(lua_State *L, widget_t*); gint luaH_widget_get_height(lua_State *L, widget_t*); gint luaH_widget_set_visible(lua_State *L, widget_t*); gint luaH_widget_set_tooltip(lua_State *L, widget_t *w); gint luaH_widget_get_tooltip(lua_State *L, widget_t *w); gint luaH_widget_set_min_size(lua_State *L, widget_t *w); gint luaH_widget_get_min_size(lua_State *L, widget_t *w); gint luaH_widget_set_align(lua_State *L, widget_t *w); gint luaH_widget_get_align(lua_State *L, widget_t *w); void add_cb(GtkContainer*, GtkWidget*, widget_t*); void parent_set_cb(GtkWidget*, GtkWidget*, widget_t*); void resize_cb(GtkWidget*, GdkRectangle *, widget_t *); void remove_cb(GtkContainer*, GtkWidget*, widget_t*); void destroy_cb(GtkWidget* UNUSED(win), widget_t *w); void widget_destructor(widget_t*); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/drawing_area.c000066400000000000000000000060551475363222200172570ustar00rootroot00000000000000/* * Copyright © 2017 Aidan Holm * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gpointer ffi_new_ref; static gint luaH_drawing_area_invalidate(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); guint width = gtk_widget_get_allocated_width(w->widget); guint height = gtk_widget_get_allocated_height(w->widget); gtk_widget_queue_draw_area(w->widget, 0, 0, width, height); return 0; } static gint luaH_drawing_area_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) PF_CASE(INVALIDATE, luaH_drawing_area_invalidate) default: break; } return 0; } static gint luaH_drawing_area_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) default: break; } return luaH_object_property_signal(L, 1, token); } static gboolean drawing_area_draw_cb(GtkWidget *UNUSED(widget), cairo_t *cr, widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); /* Convert cr to a FFI wrapper */ luaH_object_push(L, ffi_new_ref); lua_pushliteral(L, "cairo_t *"); lua_pushlightuserdata(L, cr); gint error = lua_pcall(L, 2, 1, 0); g_assert(error == 0); luaH_object_emit_signal(L, -2, "draw", 1, 0); lua_pop(L, 1); return FALSE; } widget_t * widget_drawing_area(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_drawing_area_index; w->newindex = luaH_drawing_area_newindex; /* Store ref to ffi.new() */ /* FIXME: Should do this before Lua code runs at all, but there's no good * way for random C code to hook into the Lua initialization stuff */ if (!ffi_new_ref) { lua_State *L = common.L; lua_getglobal(L, "require"); lua_pushliteral(L, "ffi"); gint error = lua_pcall(L, 1, 1, 0); g_assert(error == 0); if (!lua_istable(L, -1)) luaL_error(L, "Cannot create/use drawing area without ffi"); lua_getfield(L, -1, "new"); ffi_new_ref = luaH_object_ref(L, -1); lua_pop(L, 1); } w->widget = gtk_drawing_area_new(); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "draw", G_CALLBACK(drawing_area_draw_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/entry.c000066400000000000000000000146201475363222200157720ustar00rootroot00000000000000/* * widgets/entry.c - gtk entry widget wrapper * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_entry_insert(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); /* get insert position (or append text) */ gint pos = -1, idx = 2; if (lua_gettop(L) > 2) { pos = luaL_checknumber(L, idx++); if (pos > 0) pos--; /* correct lua index */ } gtk_editable_insert_text(GTK_EDITABLE(w->widget), luaL_checkstring(L, idx), -1, &pos); return 0; } static gint luaH_entry_select_region(lua_State* L) { widget_t *w = luaH_checkwidget(L, 1); gint startpos = luaL_checknumber(L, 2); gint endpos = -1; if(lua_gettop(L) > 2) endpos = luaL_checknumber(L, 3); gtk_editable_select_region(GTK_EDITABLE(w->widget), startpos, endpos); return 0; } static gint luaH_entry_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) /* push class methods */ PF_CASE(INSERT, luaH_entry_insert) PF_CASE(SELECT_REGION, luaH_entry_select_region) /* push integer properties */ PI_CASE(POSITION, gtk_editable_get_position(GTK_EDITABLE(w->widget))) /* push string properties */ PS_CASE(TEXT, gtk_entry_get_text(GTK_ENTRY(w->widget))) PS_CASE(FG, g_object_get_data(G_OBJECT(w->widget), "fg")) PS_CASE(BG, g_object_get_data(G_OBJECT(w->widget), "bg")) PS_CASE(FONT, g_object_get_data(G_OBJECT(w->widget), "font")) /* push boolean properties */ PB_CASE(SHOW_FRAME, gtk_entry_get_has_frame(GTK_ENTRY(w->widget))) default: break; } return 0; } static gint luaH_entry_newindex(lua_State *L, widget_t *w, luakit_token_t token) { size_t len; const gchar *tmp; GdkRGBA c; #if !GTK_CHECK_VERSION(3,16,0) PangoFontDescription *font; #endif switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_TEXT: gtk_entry_set_text(GTK_ENTRY(w->widget), luaL_checklstring(L, 3, &len)); break; case L_TK_FG: case L_TK_BG: tmp = luaL_checklstring(L, 3, &len); if (!gdk_rgba_parse(&c, tmp)) luaL_argerror(L, 3, "unable to parse color"); if (token == L_TK_FG) { #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "color", tmp, NULL); widget_set_css_properties(w, "caret-color", tmp, NULL); #else gtk_widget_override_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "fg", g_strdup(tmp), g_free); } else { #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "background-color", tmp, NULL); #else gtk_widget_override_background_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "bg", g_strdup(tmp), g_free); } break; case L_TK_SHOW_FRAME: gtk_entry_set_has_frame(GTK_ENTRY(w->widget), luaH_checkboolean(L, 3)); break; case L_TK_POSITION: gtk_editable_set_position(GTK_EDITABLE(w->widget), luaL_checknumber(L, 3)); break; case L_TK_FONT: tmp = luaL_checklstring(L, 3, &len); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "font", tmp, NULL); #else font = pango_font_description_from_string(tmp); gtk_widget_override_font(GTK_WIDGET(w->widget), font); #endif g_object_set_data_full(G_OBJECT(w->widget), "font", g_strdup(tmp), g_free); break; default: luaH_warn(L, "unknown property: %s", luaL_checkstring(L, 2)); return 0; } return luaH_object_property_signal(L, 1, token); } static void activate_cb(GtkEntry* UNUSED(e), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "activate", 0, 0); lua_pop(L, 1); } static void changed_cb(widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "changed", 0, 0); lua_pop(L, 1); } static void position_cb(GtkEntry* UNUSED(e), GParamSpec* UNUSED(ps), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "property::position", 0, 0); lua_pop(L, 1); } widget_t * widget_entry(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_entry_index; w->newindex = luaH_entry_newindex; /* create gtk label widget as main widget */ w->widget = gtk_entry_new(); /* setup default settings */ #if GTK_CHECK_VERSION(3,4,0) GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(w->widget)); const gchar *inputbar_css = "GtkEntry {border: none; padding: 2px;}"; GtkCssProvider *provider = gtk_css_provider_new(); gtk_css_provider_load_from_data(provider, inputbar_css, strlen(inputbar_css), NULL); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); #else gtk_entry_set_inner_border(GTK_ENTRY(w->widget), NULL); #endif g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::activate", G_CALLBACK(activate_cb), w, "signal::key-press-event", G_CALLBACK(key_press_cb), w, "signal::notify::cursor-position", G_CALLBACK(position_cb), w, NULL); // Further signal to replace "signal::changed" g_object_connect(G_OBJECT(w->widget), "swapped-signal::changed", G_CALLBACK(changed_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/eventbox.c000066400000000000000000000053621475363222200164660ustar00rootroot00000000000000/* * widgets/eventbox.c - gtk eventbox widget * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_eventbox_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_BIN_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push string properties */ PS_CASE(BG, g_object_get_data(G_OBJECT(w->widget), "bg")) default: break; } return 0; } static gint luaH_eventbox_newindex(lua_State *L, widget_t *w, luakit_token_t token) { size_t len; const gchar *tmp; GdkRGBA c; switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) LUAKIT_WIDGET_BIN_NEWINDEX_COMMON(w) case L_TK_BG: tmp = luaL_checklstring(L, 3, &len); if (!gdk_rgba_parse(&c, tmp)) luaL_argerror(L, 3, "unable to parse colour"); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "background-color", tmp, NULL); #else gtk_widget_override_background_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "bg", g_strdup(tmp), g_free); break; default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_eventbox(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_eventbox_index; w->newindex = luaH_eventbox_newindex; w->widget = gtk_event_box_new(); gtk_widget_show(w->widget); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::add", G_CALLBACK(add_cb), w, "signal::button-press-event", G_CALLBACK(button_cb), w, "signal::button-release-event", G_CALLBACK(button_cb), w, "signal::scroll-event", G_CALLBACK(scroll_cb), w, "signal::enter-notify-event", G_CALLBACK(mouse_cb), w, "signal::leave-notify-event", G_CALLBACK(mouse_cb), w, NULL); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/image.c000066400000000000000000000166041475363222200157170ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include #include "luah.h" #include "widgets/common.h" #include "web_context.h" #include "common/resource.h" static widget_t* luaH_checkimage(lua_State *L, gint udx) { widget_t *w = luaH_checkwidget(L, udx); if (w->info->tok != L_TK_IMAGE) luaL_argerror(L, udx, "incorrect widget type (expected image)"); return w; } static gint luaH_image_set_from_file_name(lua_State *L) { widget_t *w = luaH_checkimage(L, 1); gchar *path = (gchar*)luaL_checkstring(L, 2), *x2_path = NULL; float scale = gtk_widget_get_scale_factor(w->widget); path = resource_find_file(path); if (!path) return luaL_error(L, "unable to find image file"); /* Detect @2x file if on HiDPI screen */ if (scale == 2) { const gchar *ext = strrchr(path, '.') ?: &path[strlen(path)]; x2_path = g_strdup_printf("%.*s@2x%s", (int)(ext - path), path, ext); if (!g_file_test(x2_path, G_FILE_TEST_IS_REGULAR)) { g_free(x2_path); x2_path = NULL; } } /* Load image into pixbuf */ GError *error; fallback: error = NULL; GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(x2_path ?: path, &error); if (error) verbose("unable to load image file: %s", error->message); if (error && x2_path) { g_error_free(error); g_free(x2_path); x2_path = NULL; goto fallback; } if (error) { lua_pushstring(L, error->message); g_error_free(error); g_free(path); return luaL_error(L, "unable to load image file: %s", lua_tostring(L, -1)); } if (w->data) { g_cancellable_cancel(w->data); g_clear_object(&w->data); } /* Convert to cairo surface, and scale */ cairo_surface_t *source = gdk_cairo_surface_create_from_pixbuf(pixbuf, 1, 0); g_object_unref(G_OBJECT(pixbuf)); float src_w = cairo_image_surface_get_width(source); float src_h = cairo_image_surface_get_height(source); cairo_surface_t *target = cairo_surface_create_similar(source, CAIRO_CONTENT_COLOR_ALPHA, src_w, src_h); cairo_surface_set_device_scale(target, scale, scale); cairo_t *cr = cairo_create(target); cairo_scale(cr, 1/scale, 1/scale); cairo_set_source_surface(cr, source, 0, 0); cairo_surface_set_device_offset(source, 0, 0); cairo_paint(cr); gtk_image_set_from_surface(GTK_IMAGE(w->widget), target); cairo_surface_destroy(source); cairo_surface_destroy(target); cairo_destroy(cr); g_free(path); g_free(x2_path); return 0; } static gint luaH_image_set_from_icon_name(lua_State *L) { widget_t *w = luaH_checkimage(L, 1); GtkIconSize size; switch (luaL_checkint(L, 3)) { case 16: size = GTK_ICON_SIZE_SMALL_TOOLBAR; break; case 24: size = GTK_ICON_SIZE_LARGE_TOOLBAR; break; case 32: size = GTK_ICON_SIZE_DND; break; case 48: size = GTK_ICON_SIZE_DIALOG; break; default: return luaL_error(L, "Bad icon size: must be 16, 24, 32, or 48."); } if (w->data) { g_cancellable_cancel(w->data); g_clear_object(&w->data); } gtk_image_set_from_icon_name(GTK_IMAGE(w->widget), luaL_checkstring(L, 2), size); return 0; } static gint luaH_image_scale(lua_State *L) { widget_t *w = luaH_checkimage(L, 1); int width = luaL_checkinteger(L, 2); int height = lua_isnil(L, 3) ? width : luaL_checkinteger(L, 3); if (width <= 0 || height <= 0) return luaL_error(L, "Image dimensions must be positive"); GdkPixbuf *pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(w->widget)); GdkPixbuf *scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, width, height, GDK_INTERP_BILINEAR); g_object_unref(pixbuf); gtk_image_set_from_pixbuf(GTK_IMAGE(w->widget), scaled_pixbuf); g_object_unref(scaled_pixbuf); return 0; } void luaH_image_set_favicon_for_uri_finished(WebKitFaviconDatabase *fdb, GAsyncResult *res, widget_t *w) { cairo_surface_t *source = webkit_favicon_database_get_favicon_finish(fdb, res, NULL); if (!source) return; /* Source width/height, scale factor, target logical size, target device size */ float src_w = cairo_image_surface_get_width(source); float src_h = cairo_image_surface_get_height(source); float scale = gtk_widget_get_scale_factor(w->widget); float log_sz = 16, dev_sz = log_sz*scale; cairo_surface_t *target = cairo_surface_create_similar(source, CAIRO_CONTENT_COLOR_ALPHA, dev_sz, dev_sz); cairo_surface_set_device_scale(target, scale, scale); cairo_t *cr = cairo_create(target); cairo_scale(cr, log_sz/src_w, log_sz/src_h); cairo_set_source_surface(cr, source, 0, 0); cairo_surface_set_device_offset(source, 0, 0); cairo_paint(cr); gtk_image_set_from_surface(GTK_IMAGE(w->widget), target); cairo_surface_destroy(source); cairo_surface_destroy(target); cairo_destroy(cr); } static gint luaH_image_set_favicon_for_uri(lua_State *L) { widget_t *w = luaH_checkimage(L, 1); const gchar *uri = luaL_checkstring(L, 2); WebKitWebContext *main_ctx = web_context_get(); WebKitFaviconDatabase *main_fdb = webkit_web_context_get_favicon_database(main_ctx); gchar *f_uri; gboolean ok = TRUE; if ((f_uri = webkit_favicon_database_get_favicon_uri(main_fdb, uri))) { g_free(f_uri); if (w->data) { g_cancellable_cancel(w->data); g_clear_object(&w->data); } w->data = g_cancellable_new(); webkit_favicon_database_get_favicon(main_fdb, uri, w->data, (GAsyncReadyCallback)luaH_image_set_favicon_for_uri_finished, w); } else ok = FALSE; lua_pushboolean(L, ok); return 1; } static gint luaH_image_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) PF_CASE(FILENAME, luaH_image_set_from_file_name) PF_CASE(ICON, luaH_image_set_from_icon_name) PF_CASE(SCALE, luaH_image_scale) PF_CASE(SET_FAVICON_FOR_URI, luaH_image_set_favicon_for_uri) default: break; } return 0; } static gint luaH_image_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_image(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_image_index; w->newindex = luaH_image_newindex; w->widget = gtk_image_new(); w->data = NULL; g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/label.c000066400000000000000000000173331475363222200157140ustar00rootroot00000000000000/* * widgets/label.c - gtk text area widget * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_label_get_align(lua_State *L, widget_t *w) { gfloat xalign, yalign; #if GTK_CHECK_VERSION(3,16,0) xalign = gtk_label_get_xalign(GTK_LABEL(w->widget)); yalign = gtk_label_get_yalign(GTK_LABEL(w->widget)); #else # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" gtk_misc_get_alignment(GTK_MISC(w->widget), &xalign, &yalign); # pragma GCC diagnostic pop #endif luaH_widget_get_align(L, w); /* set align.x */ lua_pushliteral(L, "x"); lua_pushnumber(L, xalign); lua_rawset(L, -3); /* set align.y */ lua_pushliteral(L, "y"); lua_pushnumber(L, yalign); lua_rawset(L, -3); return 1; } static gint luaH_label_set_align(lua_State *L, widget_t *w) { luaH_widget_set_align(L, w); gfloat xalign, yalign; luaH_checktable(L, 3); #if !GTK_CHECK_VERSION(3,16,0) /* get old alignment values */ gtk_misc_get_alignment(GTK_MISC(w->widget), &xalign, &yalign); #endif /* get align.x */ if (luaH_rawfield(L, 3, "x")) { xalign = (gfloat) lua_tonumber(L, -1); lua_pop(L, 1); #if GTK_CHECK_VERSION(3,16,0) gtk_label_set_xalign(GTK_LABEL(w->widget), xalign); #endif } /* get align.y */ if (luaH_rawfield(L, 3, "y")) { yalign = (gfloat) lua_tonumber(L, -1); lua_pop(L, 1); #if GTK_CHECK_VERSION(3,16,0) gtk_label_set_yalign(GTK_LABEL(w->widget), yalign); #endif } #if !GTK_CHECK_VERSION(3,16,0) gtk_misc_set_alignment(GTK_MISC(w->widget), xalign, yalign); #endif return 0; } #if !GTK_CHECK_VERSION(3,14,0) static gint luaH_label_get_padding(lua_State *L, widget_t *w) { gint xpad, ypad; gtk_misc_get_padding(GTK_MISC(w->widget), &xpad, &ypad); lua_createtable(L, 0, 2); /* set padding.x */ lua_pushliteral(L, "x"); lua_pushnumber(L, xpad); lua_rawset(L, -3); /* set padding.y */ lua_pushliteral(L, "y"); lua_pushnumber(L, ypad); lua_rawset(L, -3); return 1; } static gint luaH_label_set_padding(lua_State *L, widget_t *w) { luaH_checktable(L, 3); /* get old padding values */ gint xpad = 0, ypad = 0; gtk_misc_get_padding(GTK_MISC(w->widget), &xpad, &ypad); /* get padding.x */ if (luaH_rawfield(L, 3, "x")) { xpad = (gint) lua_tonumber(L, -1); lua_pop(L, 1); } /* get padding.y */ if (luaH_rawfield(L, 3, "y")) { ypad = (gint) lua_tonumber(L, -1); lua_pop(L, 1); } gtk_misc_set_padding(GTK_MISC(w->widget), xpad, ypad); return 0; } #endif static gint luaH_label_index(lua_State *L, widget_t *w, luakit_token_t token) { if (token == L_TK_ALIGN) return luaH_label_get_align(L, w); switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) #if !GTK_CHECK_VERSION(3,14,0) case L_TK_PADDING: return luaH_label_get_padding(L, w); #endif /* push string properties */ PS_CASE(FG, g_object_get_data(G_OBJECT(w->widget), "fg")) PS_CASE(BG, g_object_get_data(G_OBJECT(w->widget), "bg")) PS_CASE(FONT, g_object_get_data(G_OBJECT(w->widget), "font")) PS_CASE(TEXT, gtk_label_get_label(GTK_LABEL(w->widget))) /* push boolean properties */ PB_CASE(SELECTABLE, gtk_label_get_selectable(GTK_LABEL(w->widget))) /* push integer properties */ PI_CASE(TEXTWIDTH, gtk_label_get_width_chars(GTK_LABEL(w->widget))) default: break; } return 0; } static gint luaH_label_newindex(lua_State *L, widget_t *w, luakit_token_t token) { size_t len; const gchar *tmp; GdkRGBA c; PangoFontDescription *font; if (token == L_TK_ALIGN) { luaH_label_set_align(L, w); return luaH_object_property_signal(L, 1, token); } switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) #if !GTK_CHECK_VERSION(3,14,0) case L_TK_PADDING: luaH_label_set_padding(L, w); break; #endif case L_TK_TEXT: gtk_label_set_markup(GTK_LABEL(w->widget), luaL_checklstring(L, 3, &len)); break; case L_TK_FG: tmp = luaL_checklstring(L, 3, &len); if (!gdk_rgba_parse(&c, tmp)) luaL_argerror(L, 3, "unable to parse color"); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "color", tmp, NULL); #else gtk_widget_override_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "fg", g_strdup(tmp), g_free); break; case L_TK_BG: tmp = luaL_checklstring(L, 3, &len); if (!gdk_rgba_parse(&c, tmp)) luaL_argerror(L, 3, "unable to parse color"); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "background-color", tmp, NULL); #else gtk_widget_override_background_color(GTK_WIDGET(w->widget), GTK_STATE_FLAG_NORMAL, &c); #endif g_object_set_data_full(G_OBJECT(w->widget), "bg", g_strdup(tmp), g_free); break; case L_TK_FONT: tmp = luaL_checklstring(L, 3, &len); font = pango_font_description_from_string(tmp); #if GTK_CHECK_VERSION(3,16,0) widget_set_css_properties(w, "font", tmp, NULL); #else gtk_widget_override_font(GTK_WIDGET(w->widget), font); #endif pango_font_description_free(font); g_object_set_data_full(G_OBJECT(w->widget), "font", g_strdup(tmp), g_free); break; case L_TK_SELECTABLE: gtk_label_set_selectable(GTK_LABEL(w->widget), luaH_checkboolean(L, 3)); break; case L_TK_TEXTWIDTH: gtk_label_set_width_chars(GTK_LABEL(w->widget), (gint)luaL_checknumber(L, 3)); break; default: luaH_warn(L, "unknown property: %s", luaL_checkstring(L, 2)); return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_label(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_label_index; w->newindex = luaH_label_newindex; /* create gtk label widget as main widget */ w->widget = gtk_label_new(NULL); gtk_label_set_ellipsize(GTK_LABEL(w->widget), PANGO_ELLIPSIZE_END); /* setup default settings */ gtk_label_set_selectable(GTK_LABEL(w->widget), FALSE); gtk_label_set_use_markup(GTK_LABEL(w->widget), TRUE); #if GTK_CHECK_VERSION(3,14,0) gtk_widget_set_halign(GTK_WIDGET(w->widget), GTK_ALIGN_START); gtk_widget_set_valign(GTK_WIDGET(w->widget), GTK_ALIGN_START); GValue margin = G_VALUE_INIT; g_value_init(&margin, G_TYPE_INT); g_value_set_int(&margin, 2); g_object_set_property(G_OBJECT(w->widget), "margin", &margin); #else gtk_misc_set_alignment(GTK_MISC(w->widget), 0, 0); gtk_misc_set_padding(GTK_MISC(w->widget), 2, 2); #endif g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::key-press-event", G_CALLBACK(key_press_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/notebook.c000066400000000000000000000177301475363222200164560ustar00rootroot00000000000000/* * widgets/notebook.c - gtk notebook widget * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_notebook_current(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); gint n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(w->widget)); if (n == 1) lua_pushnumber(L, 1); else lua_pushnumber(L, gtk_notebook_get_current_page( GTK_NOTEBOOK(w->widget)) + 1); return 1; } static gint luaH_notebook_atindex(lua_State *L, widget_t *w, gint idx) { /* correct index */ if (idx != -1) idx--; GtkWidget *widget = gtk_notebook_get_nth_page(GTK_NOTEBOOK(w->widget), idx); if (!widget) return 0; widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); luaH_object_push(L, child->ref); return 1; } static gint luaH_notebook_indexof(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint i = gtk_notebook_page_num(GTK_NOTEBOOK(w->widget), child->widget); /* return index or nil */ if (!++i) return 0; lua_pushnumber(L, i); return 1; } /* Inserts a widget into the notebook widget at an index */ static gint luaH_notebook_insert(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); /* get insert position (or append page) */ gint pos = -1, idx = 2; if (lua_gettop(L) > 2) { pos = luaL_checknumber(L, idx++); if (pos > 0) pos--; /* correct lua index */ } pos = gtk_notebook_insert_page(GTK_NOTEBOOK(w->widget), GTK_WIDGET(luaH_checkwidget(L, idx)->widget), NULL, pos); /* failed to insert page */ if (pos == -1) return 0; /* return new (lua corrected) index */ lua_pushnumber(L, ++pos); return 1; } /* Return the number of widgets in the notebook */ static gint luaH_notebook_count(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); lua_pushnumber(L, gtk_notebook_get_n_pages(GTK_NOTEBOOK(w->widget))); return 1; } static gint luaH_notebook_set_title(lua_State *L) { size_t len; widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); const gchar *title = luaL_checklstring(L, 3, &len); GtkWidget *label = gtk_label_new(title); gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE); gtk_notebook_set_tab_label(GTK_NOTEBOOK(w->widget), child->widget, label); gtk_container_child_set(GTK_CONTAINER(w->widget), label, "tab-expand", TRUE, "tab-fill", TRUE, NULL); return 0; } static gint luaH_notebook_get_title(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); lua_pushstring(L, gtk_notebook_get_tab_label_text( GTK_NOTEBOOK(w->widget), child->widget)); return 1; } static gint luaH_notebook_switch(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); gint i = luaL_checknumber(L, 2); /* correct index */ if (i != -1) i--; gtk_notebook_set_current_page(GTK_NOTEBOOK(w->widget), i); lua_pushnumber(L, gtk_notebook_get_current_page(GTK_NOTEBOOK(w->widget))); return 1; } static gint luaH_notebook_reorder(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint i = luaL_checknumber(L, 3); /* correct lua index */ if (i != -1) i--; gtk_notebook_reorder_child(GTK_NOTEBOOK(w->widget), child->widget, i); lua_pushnumber(L, gtk_notebook_page_num(GTK_NOTEBOOK(w->widget), child->widget)); return 1; } static gint luaH_notebook_index(lua_State *L, widget_t *w, luakit_token_t token) { /* handle numerical index lookups */ if (token == L_TK_UNKNOWN && lua_isnumber(L, 2)) return luaH_notebook_atindex(L, w, (gint)luaL_checknumber(L, 2)); switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push class methods */ PF_CASE(COUNT, luaH_notebook_count) PF_CASE(CURRENT, luaH_notebook_current) PF_CASE(GET_TITLE, luaH_notebook_get_title) PF_CASE(INDEXOF, luaH_notebook_indexof) PF_CASE(INSERT, luaH_notebook_insert) PF_CASE(SET_TITLE, luaH_notebook_set_title) PF_CASE(SWITCH, luaH_notebook_switch) PF_CASE(REORDER, luaH_notebook_reorder) /* push boolean properties */ PB_CASE(SHOW_TABS, gtk_notebook_get_show_tabs(GTK_NOTEBOOK(w->widget))) PB_CASE(SHOW_BORDER, gtk_notebook_get_show_border(GTK_NOTEBOOK(w->widget))) default: break; } return 0; } static gint luaH_notebook_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_SHOW_TABS: gtk_notebook_set_show_tabs(GTK_NOTEBOOK(w->widget), luaH_checkboolean(L, 3)); break; case L_TK_SHOW_BORDER: gtk_notebook_set_show_border(GTK_NOTEBOOK(w->widget), luaH_checkboolean(L, 3)); break; default: return 0; } return luaH_object_property_signal(L, 1, token); } static void page_added_cb(GtkNotebook* UNUSED(n), GtkWidget *widget, guint i, widget_t *w) { widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); lua_pushnumber(L, i + 1); luaH_object_emit_signal(L, -3, "page-added", 2, 0); lua_pop(L, 1); } static void page_removed_cb(GtkNotebook* UNUSED(n), GtkWidget *widget, guint UNUSED(i), widget_t *w) { widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); luaH_object_emit_signal(L, -2, "page-removed", 1, 0); lua_pop(L, 1); } static void switch_cb(GtkNotebook *n, GtkWidget* UNUSED(p), guint i, widget_t *w) { GtkWidget *widget = gtk_notebook_get_nth_page(GTK_NOTEBOOK(n), i); widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); lua_pushnumber(L, i + 1); luaH_object_emit_signal(L, -3, "switch-page", 2, 0); lua_pop(L, 1); } static void reorder_cb(GtkNotebook* UNUSED(n), GtkWidget *widget, guint i, widget_t *w) { widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_push(L, child->ref); lua_pushnumber(L, i + 1); luaH_object_emit_signal(L, -3, "page-reordered", 2, 0); lua_pop(L, 1); } widget_t * widget_notebook(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_notebook_index; w->newindex = luaH_notebook_newindex; /* create and setup notebook widget */ w->widget = gtk_notebook_new(); gtk_notebook_set_show_border(GTK_NOTEBOOK(w->widget), FALSE); gtk_notebook_set_scrollable(GTK_NOTEBOOK(w->widget), TRUE); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::key-press-event", G_CALLBACK(key_press_cb), w, "signal::page-added", G_CALLBACK(page_added_cb), w, "signal::page-removed", G_CALLBACK(page_removed_cb), w, "signal::page-reordered", G_CALLBACK(reorder_cb), w, "signal::switch-page", G_CALLBACK(switch_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/overlay.c000066400000000000000000000076771475363222200163300ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_overlay_pack(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint top = lua_gettop(L); GtkAlign halign = GTK_ALIGN_FILL, valign = GTK_ALIGN_FILL; /* check for options table */ if (top > 2 && !lua_isnil(L, 3)) { luaH_checktable(L, 3); if (luaH_rawfield(L, 3, "halign")) switch (l_tokenize(lua_tostring(L, -1))) { case L_TK_FILL: halign = GTK_ALIGN_FILL; break; case L_TK_START: halign = GTK_ALIGN_START; break; case L_TK_END: halign = GTK_ALIGN_END; break; case L_TK_CENTER: halign = GTK_ALIGN_CENTER; break; case L_TK_BASELINE: halign = GTK_ALIGN_BASELINE; break; default: return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)"); } if (luaH_rawfield(L, 3, "valign")) switch (l_tokenize(lua_tostring(L, -1))) { case L_TK_FILL: valign = GTK_ALIGN_FILL; break; case L_TK_START: valign = GTK_ALIGN_START; break; case L_TK_END: valign = GTK_ALIGN_END; break; case L_TK_CENTER: valign = GTK_ALIGN_CENTER; break; case L_TK_BASELINE: valign = GTK_ALIGN_BASELINE; break; default: return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)"); } /* return stack to original state */ lua_settop(L, top); } gtk_widget_set_halign(GTK_WIDGET(child->widget), halign); gtk_widget_set_valign(GTK_WIDGET(child->widget), valign); gtk_overlay_add_overlay(GTK_OVERLAY(w->widget), GTK_WIDGET(child->widget)); return 0; } static gint luaH_overlay_reorder_child(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint pos = luaL_checknumber(L, 3); gtk_overlay_reorder_overlay(GTK_OVERLAY(w->widget), GTK_WIDGET(child->widget), pos); return 0; } static gint luaH_overlay_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_BIN_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push class methods */ PF_CASE(PACK, luaH_overlay_pack) PF_CASE(REORDER, luaH_overlay_reorder_child) default: break; } return 0; } static gint luaH_overlay_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) LUAKIT_WIDGET_BIN_NEWINDEX_COMMON(w) default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_overlay(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_overlay_index; w->newindex = luaH_overlay_newindex; #if GTK_CHECK_VERSION(3,2,0) w->widget = gtk_overlay_new(); #endif g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/paned.c000066400000000000000000000076701475363222200157270ustar00rootroot00000000000000/* * widgets/paned.c - gtk paned container widget * * Copyright © 2010 Mason Larobina * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_paned_pack(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gint top = lua_gettop(L); gboolean resize = TRUE, shrink = TRUE; if (top > 2 && !lua_isnil(L, 3)) { luaH_checktable(L, 3); if (luaH_rawfield(L, 3, "resize")) resize = lua_toboolean(L, -1) ? TRUE : FALSE; if (luaH_rawfield(L, 3, "shrink")) shrink = lua_toboolean(L, -1) ? TRUE : FALSE; lua_settop(L, top); } /* get packing position from C closure upvalue */ luakit_token_t t = (luakit_token_t)lua_tonumber(L, lua_upvalueindex(1)); if (t == L_TK_PACK1) gtk_paned_pack1(GTK_PANED(w->widget), GTK_WIDGET(child->widget), resize, shrink); else gtk_paned_pack2(GTK_PANED(w->widget), GTK_WIDGET(child->widget), resize, shrink); return 0; } static gint luaH_paned_get_child(lua_State *L, widget_t *w, gint n) { GtkWidget *widget = NULL; if (n == 1) widget = gtk_paned_get_child1(GTK_PANED(w->widget)); else widget = gtk_paned_get_child2(GTK_PANED(w->widget)); if (!widget) return 0; widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); luaH_object_push(L, child->ref); return 1; } static gint luaH_paned_get_position(lua_State *L, widget_t *w) { gint position = gtk_paned_get_position(GTK_PANED(w->widget)); lua_pushnumber(L, position); return 1; } static gint luaH_paned_set_position(lua_State *L, widget_t *w) { gint position = lua_tonumber(L, -1); gtk_paned_set_position(GTK_PANED(w->widget), position); return 0; } static gint luaH_paned_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push paned widget methods */ case L_TK_PACK1: case L_TK_PACK2: lua_pushnumber(L, (gint) token); lua_pushcclosure(L, luaH_paned_pack, 1); return 1; case L_TK_TOP: case L_TK_LEFT: return luaH_paned_get_child(L, w, 1); case L_TK_BOTTOM: case L_TK_RIGHT: return luaH_paned_get_child(L, w, 2); case L_TK_POSITION: return luaH_paned_get_position(L, w); default: break; } return 0; } static gint luaH_paned_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_POSITION: luaH_paned_set_position(L, w); break; default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_paned(lua_State *UNUSED(L), widget_t *w, luakit_token_t token) { w->index = luaH_paned_index; w->newindex = luaH_paned_newindex; w->widget = (token == L_TK_VPANED) ? gtk_paned_new(GTK_ORIENTATION_VERTICAL) : gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::add", G_CALLBACK(add_cb), w, NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/scrolled.c000066400000000000000000000122341475363222200164370ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint gtk_policy_from_string(const gchar *str, GtkPolicyType *out) { if (!strcmp(str, "always")) *out = GTK_POLICY_ALWAYS; else if (!strcmp(str, "auto")) *out = GTK_POLICY_AUTOMATIC; else if (!strcmp(str, "never")) *out = GTK_POLICY_NEVER; #if GTK_CHECK_VERSION(3,16,0) else if (!strcmp(str, "external")) *out = GTK_POLICY_EXTERNAL; #endif else return 1; return 0; } static const gchar * string_from_gtk_policy(GtkPolicyType policy) { switch (policy) { case GTK_POLICY_ALWAYS: return "always"; case GTK_POLICY_AUTOMATIC: return "auto"; case GTK_POLICY_NEVER: return "never"; #if GTK_CHECK_VERSION(3,16,0) case GTK_POLICY_EXTERNAL: return "external"; #endif default: return NULL; } } gint luaH_widget_get_scrollbars(lua_State *L, widget_t *w) { GtkPolicyType horz, vert; gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(w->widget), &horz, &vert); lua_newtable(L); lua_pushliteral(L, "h"); lua_pushstring(L, string_from_gtk_policy(horz)); lua_rawset(L, -3); lua_pushliteral(L, "v"); lua_pushstring(L, string_from_gtk_policy(vert)); lua_rawset(L, -3); return 1; } gint luaH_widget_set_scrollbars(lua_State *L, widget_t *w) { luaH_checktable(L, 3); GtkPolicyType horz, vert; gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(w->widget), &horz, &vert); gint top = lua_gettop(L); if (luaH_rawfield(L, 3, "h")) { if (gtk_policy_from_string(lua_tostring(L, -1), &horz)) luaL_error(L, "Bad horizontal scrollbar policy"); } if (luaH_rawfield(L, 3, "v")) { if (gtk_policy_from_string(lua_tostring(L, -1), &vert)) luaL_error(L, "Bad vertical scrollbar policy"); } lua_settop(L, top); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), horz, vert); return 1; } gint luaH_scrolled_get_scroll(lua_State *L, widget_t *w) { GtkAdjustment *horz = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget)); GtkAdjustment *vert = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget)); lua_newtable(L); lua_pushliteral(L, "x"); lua_pushnumber(L, gtk_adjustment_get_value(horz)); lua_rawset(L, -3); lua_pushliteral(L, "y"); lua_pushnumber(L, gtk_adjustment_get_value(vert)); lua_rawset(L, -3); lua_pushliteral(L, "xmax"); lua_pushnumber(L, gtk_adjustment_get_upper(horz)); lua_rawset(L, -3); lua_pushliteral(L, "ymax"); lua_pushnumber(L, gtk_adjustment_get_upper(vert)); lua_rawset(L, -3); return 1; } gint luaH_scrolled_set_scroll(lua_State *L, widget_t *w) { luaH_checktable(L, 3); GtkAdjustment *horz = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget)); GtkAdjustment *vert = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget)); gint top = lua_gettop(L); if (luaH_rawfield(L, 3, "x")) gtk_adjustment_set_value(horz, lua_tonumber(L, -1)); if (luaH_rawfield(L, 3, "y")) gtk_adjustment_set_value(vert, lua_tonumber(L, -1)); lua_settop(L, top); return 0; } static gint luaH_scrolled_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_BIN_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) case L_TK_SCROLLBARS: return luaH_widget_get_scrollbars(L, w); case L_TK_SCROLL: return luaH_scrolled_get_scroll(L, w); default: break; } return 0; } static gint luaH_scrolled_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) LUAKIT_WIDGET_BIN_NEWINDEX_COMMON(w) case L_TK_SCROLLBARS: luaH_widget_set_scrollbars(L, w); break; case L_TK_SCROLL: luaH_scrolled_set_scroll(L, w); break; default: break; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_scrolled(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_scrolled_index; w->newindex = luaH_scrolled_newindex; #if GTK_CHECK_VERSION(3,2,0) w->widget = gtk_scrolled_window_new(NULL, NULL); #endif g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/spinner.c000066400000000000000000000044151475363222200163100ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "luah.h" #include "widgets/common.h" static widget_t* luaH_checkspinner(lua_State *L, gint udx) { widget_t *w = luaH_checkwidget(L, udx); if (w->info->tok != L_TK_SPINNER) luaL_argerror(L, udx, "incorrect widget type (expected spinner)"); return w; } static gint luaH_spinner_start(lua_State *L) { widget_t *w = luaH_checkspinner(L, 1); gtk_spinner_start(GTK_SPINNER(w->widget)); return 0; } static gint luaH_spinner_stop(lua_State *L) { widget_t *w = luaH_checkspinner(L, 1); gtk_spinner_stop(GTK_SPINNER(w->widget)); return 0; } static gint luaH_spinner_index(lua_State *L, widget_t *w, luakit_token_t token) { gboolean active; switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) PB_CASE(STARTED, (g_object_get(G_OBJECT(w->widget), "active", &active, NULL), active)) PF_CASE(START, luaH_spinner_start) PF_CASE(STOP, luaH_spinner_stop) default: break; } return 0; } static gint luaH_spinner_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) default: break; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_spinner(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_spinner_index; w->newindex = luaH_spinner_newindex; w->widget = gtk_spinner_new(); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/stack.c000066400000000000000000000051711475363222200157370ustar00rootroot00000000000000/* * widgets/stack.c - gtk stack widget * * Copyright © 2017 Aidan Holm * * 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 . * */ #include "luah.h" #include "widgets/common.h" static gint luaH_stack_pack(lua_State *L) { widget_t *w = luaH_checkwidget(L, 1); widget_t *child = luaH_checkwidget(L, 2); gtk_container_add(GTK_CONTAINER (w->widget), GTK_WIDGET(child->widget)); return 0; } static gint luaH_stack_index(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) PF_CASE(PACK, luaH_stack_pack) PB_CASE(HOMOGENEOUS, gtk_stack_get_homogeneous(GTK_STACK(w->widget))) case L_TK_VISIBLE_CHILD: { GtkWidget *widget = gtk_stack_get_visible_child(GTK_STACK(w->widget)); if (!widget) return 0; widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget); luaH_object_push(L, child->ref); return 1; } default: break; } return 0; } static gint luaH_stack_newindex(lua_State *L, widget_t *w, luakit_token_t token) { switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_HOMOGENEOUS: gtk_stack_set_homogeneous(GTK_STACK(w->widget), luaH_checkboolean(L, 3)); break; case L_TK_VISIBLE_CHILD: { widget_t *child = luaH_checkwidget(L, 3); gtk_stack_set_visible_child(GTK_STACK(w->widget), child->widget); break; } default: return 0; } return luaH_object_property_signal(L, 1, token); } widget_t * widget_stack(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_stack_index; w->newindex = luaH_stack_newindex; w->widget = gtk_stack_new(); g_object_connect(G_OBJECT(w->widget), LUAKIT_WIDGET_SIGNAL_COMMON(w) NULL); gtk_widget_show(w->widget); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview.c000066400000000000000000001464251475363222200163120ustar00rootroot00000000000000/* * widgets/webview.c - webkit webview widget * * Copyright © 2010-2011 Mason Larobina * * 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 . * */ #include #include #include "globalconf.h" #include "widgets/common.h" #include "widgets/webview.h" #include "common/property.h" #include "luah.h" #include "clib/widget.h" #include "clib/request.h" #include "common/signal.h" #include "web_context.h" #include "common/ipc.h" #include "common/luayield.h" typedef struct { /** The parent widget_t struct */ widget_t *widget; /** The webview widget */ WebKitWebView *view; /** The user content manager for the webview */ WebKitUserContentManager *user_content; /** A list of stylesheets enabled for this user content */ GList *stylesheets; /** Helpers for user content manager updating */ gboolean stylesheet_added, stylesheet_removed, stylesheet_refreshed; /** Current webview uri */ gchar *uri; /** Currently hovered uri */ gchar *hover; /** Inspector properties */ WebKitWebInspector *inspector; /** Whether inspector is open */ gboolean inspector_open; guint htr_context; gboolean is_committed; gboolean is_failed; gboolean private; /** Document size */ gint doc_w, doc_h; /** Viewport size */ gint win_w, win_h; /** Current scroll position */ gint scroll_x, scroll_y; /** TLS Certificate, if using HTTPS */ GTlsCertificate *cert; ipc_endpoint_t *ipc; pid_t web_process_id; } webview_data_t; static WebKitWebView *related_view; #define luaH_checkwvdata(L, udx) ((webview_data_t*)(luaH_checkwebview(L, udx)->data)) static struct { GSList *refs; GSList *old_refs; } last_popup = { NULL, NULL }; static property_t webview_properties[] = { { L_TK_EDITABLE, "editable", BOOL, TRUE }, { L_TK_PROGRESS, "estimated-load-progress", DOUBLE, FALSE }, { L_TK_IS_LOADING, "is-loading", BOOL, FALSE }, { L_TK_IS_PLAYING_AUDIO, "is-playing-audio", BOOL, FALSE }, { L_TK_TITLE, "title", CHAR, FALSE }, { L_TK_ZOOM_LEVEL, "zoom-level", DOUBLE, TRUE }, { 0, NULL, 0, 0 }, }; static property_t webview_settings_properties[] = { { L_TK_ALLOW_FILE_ACCESS_FROM_FILE_URLS, "allow-file-access-from-file-urls", BOOL, TRUE }, { L_TK_ALLOW_MODAL_DIALOGS, "allow-modal-dialogs", BOOL, TRUE }, { L_TK_ALLOW_UNIVERSAL_ACCESS_FROM_FILE_URLS, "allow-universal-access-from-file-urls", BOOL, TRUE }, { L_TK_AUTO_LOAD_IMAGES, "auto-load-images", BOOL, TRUE }, { L_TK_CURSIVE_FONT_FAMILY, "cursive-font-family", CHAR, TRUE }, { L_TK_DEFAULT_CHARSET, "default-charset", CHAR, TRUE }, { L_TK_DEFAULT_FONT_FAMILY, "default-font-family", CHAR, TRUE }, { L_TK_DEFAULT_FONT_SIZE, "default-font-size", INT, TRUE }, { L_TK_DEFAULT_MONOSPACE_FONT_SIZE, "default-monospace-font-size", INT, TRUE }, { L_TK_DRAW_COMPOSITING_INDICATORS, "draw-compositing-indicators", BOOL, TRUE }, { L_TK_ENABLE_ACCELERATED_2D_CANVAS, "enable-accelerated-2d-canvas", BOOL, TRUE }, { L_TK_ENABLE_CARET_BROWSING, "enable-caret-browsing", BOOL, TRUE }, { L_TK_ENABLE_DEVELOPER_EXTRAS, "enable-developer-extras", BOOL, TRUE }, { L_TK_ENABLE_DNS_PREFETCHING, "enable-dns-prefetching", BOOL, TRUE }, { L_TK_ENABLE_FRAME_FLATTENING, "enable-frame-flattening", BOOL, TRUE }, { L_TK_ENABLE_FULLSCREEN, "enable-fullscreen", BOOL, TRUE }, { L_TK_ENABLE_HTML5_DATABASE, "enable-html5-database", BOOL, TRUE }, { L_TK_ENABLE_HTML5_LOCAL_STORAGE, "enable-html5-local-storage", BOOL, TRUE }, { L_TK_ENABLE_HYPERLINK_AUDITING, "enable-hyperlink-auditing", BOOL, TRUE }, { L_TK_ENABLE_JAVA, "enable-java", BOOL, TRUE }, { L_TK_ENABLE_JAVASCRIPT, "enable-javascript", BOOL, TRUE }, { L_TK_ENABLE_MEDIA_STREAM, "enable-media-stream", BOOL, TRUE }, { L_TK_ENABLE_MEDIASOURCE, "enable-mediasource", BOOL, TRUE }, { L_TK_ENABLE_PAGE_CACHE, "enable-page-cache", BOOL, TRUE }, { L_TK_ENABLE_PLUGINS, "enable-plugins", BOOL, TRUE }, /* replaces resizable-text-areas */ { L_TK_ENABLE_RESIZABLE_TEXT_AREAS, "enable-resizable-text-areas", BOOL, TRUE }, { L_TK_ENABLE_SITE_SPECIFIC_QUIRKS, "enable-site-specific-quirks", BOOL, TRUE }, { L_TK_ENABLE_SMOOTH_SCROLLING, "enable-smooth-scrolling", BOOL, TRUE }, { L_TK_ENABLE_SPATIAL_NAVIGATION, "enable-spatial-navigation", BOOL, TRUE }, { L_TK_ENABLE_WEBGL, "enable-webgl", BOOL, TRUE }, { L_TK_ENABLE_TABS_TO_LINKS, "enable-tabs-to-links", BOOL, TRUE }, { L_TK_ENABLE_WEBAUDIO, "enable-webaudio", BOOL, TRUE }, { L_TK_ENABLE_WRITE_CONSOLE_MESSAGES_TO_STDOUT, "enable-write-console-messages-to-stdout", BOOL, TRUE }, { L_TK_ENABLE_XSS_AUDITOR, "enable-xss-auditor", BOOL, TRUE }, { L_TK_FANTASY_FONT_FAMILY, "fantasy-font-family", CHAR, TRUE }, { L_TK_JAVASCRIPT_CAN_ACCESS_CLIPBOARD, "javascript-can-access-clipboard", BOOL, TRUE }, { L_TK_JAVASCRIPT_CAN_OPEN_WINDOWS_AUTOMATICALLY, "javascript-can-open-windows-automatically", BOOL, TRUE }, { L_TK_MEDIA_PLAYBACK_ALLOWS_INLINE, "media-playback-allows-inline", BOOL, TRUE }, { L_TK_MEDIA_PLAYBACK_REQUIRES_GESTURE, "media-playback-requires-user-gesture", BOOL, TRUE }, { L_TK_MINIMUM_FONT_SIZE, "minimum-font-size", INT, TRUE }, { L_TK_MONOSPACE_FONT_FAMILY, "monospace-font-family", CHAR, TRUE }, { L_TK_PICTOGRAPH_FONT_FAMILY, "pictograph-font-family", CHAR, TRUE }, { L_TK_PRINT_BACKGROUNDS, "print-backgrounds", BOOL, TRUE }, { L_TK_SANS_SERIF_FONT_FAMILY, "sans-serif-font-family", CHAR, TRUE }, { L_TK_SERIF_FONT_FAMILY, "serif-font-family", CHAR, TRUE }, { L_TK_USER_AGENT, "user-agent", CHAR, TRUE }, { L_TK_ZOOM_TEXT_ONLY, "zoom-text-only", BOOL, TRUE }, { 0, NULL, 0, 0 }, }; widget_t* luaH_checkwebview(lua_State *L, gint udx) { widget_t *w = luaH_checkwidget(L, udx); if (w->info->tok != L_TK_WEBVIEW) luaL_argerror(L, udx, "incorrect widget type (expected webview)"); return w; } widget_t* webview_get_by_id(guint64 view_id) { for (unsigned i = 0; i < globalconf.webviews->len; i++) { widget_t *w = g_ptr_array_index(globalconf.webviews, i); if (webkit_web_view_get_page_id(WEBKIT_WEB_VIEW(w->widget)) == view_id) return w; } return NULL; } static void update_uri(widget_t *w, const gchar *uri); #include "widgets/webview/javascript.c" #include "widgets/webview/downloads.c" #include "widgets/webview/history.c" #include "widgets/webview/scroll.c" #include "widgets/webview/inspector.c" #include "widgets/webview/find_controller.c" #include "widgets/webview/stylesheets.c" #include "widgets/webview/auth.c" static gint luaH_webview_load_string(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); const gchar *string = luaL_checkstring(L, 2); const gchar *base_uri = luaL_checkstring(L, 3); webkit_web_view_load_alternate_html(d->view, string, base_uri, NULL); return 0; } struct save_cb_s { const gchar *filename; widget_t *window; }; static void save_cb(GObject *o, GAsyncResult *res, gpointer user_data) { WebKitWebView *view = (WebKitWebView *) o; struct save_cb_s *scbs = (struct save_cb_s *) user_data; lua_State *L = common.L; GError *err = NULL; gboolean result; result = webkit_web_view_save_to_file_finish(view, res, &err); luaH_object_push(L, scbs->window->ref); lua_pushstring(L, scbs->filename); if (result) lua_pushnil(L); else lua_pushstring(L, err->message); luaH_object_emit_signal(L, -3, "save-finished", 2, 0); lua_pop(L, 1); g_free(scbs); } static gint luaH_webview_save(lua_State *L) { struct save_cb_s *scbs = g_new0(struct save_cb_s, 1); webview_data_t *d = luaH_checkwvdata(L, 1); scbs->filename = luaL_checkstring(L, 2); scbs->window = d->widget; GFile *fd = g_file_new_for_path(scbs->filename); webkit_web_view_save_to_file(d->view, fd, WEBKIT_SAVE_MODE_MHTML, NULL, save_cb, (gpointer) scbs); return 0; } static void notify_cb(WebKitWebView* UNUSED(v), GParamSpec *ps, widget_t *w) { static GHashTable *wvprops = NULL; property_t *p; if (!wvprops) { wvprops = g_hash_table_new(g_str_hash, g_str_equal); for (p = webview_properties; p->name; p++) g_hash_table_insert(wvprops, (gpointer)p->name, (gpointer)p); } if ((p = g_hash_table_lookup(wvprops, ps->name))) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_property_signal(L, -1, p->tok); lua_pop(L, 1); } } static void update_uri(widget_t *w, const gchar *uri) { webview_data_t *d = w->data; if (!w->destructor) return; if (!uri) { uri = webkit_web_view_get_uri(d->view); if (!uri || !uri[0]) uri = "about:blank"; } /* uris are the same, do nothing */ if (g_strcmp0(d->uri, uri)) { g_free(d->uri); d->uri = g_strdup(uri); lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "property::uri", 0, 0); lua_pop(L, 1); } } static gboolean load_failed_cb(WebKitWebView* UNUSED(v), WebKitLoadEvent UNUSED(e), gchar *failing_uri, GError *error, widget_t *w) { update_uri(w, failing_uri); lua_State *L = common.L; ((webview_data_t*) w->data)->is_failed = TRUE; luaH_object_push(L, w->ref); lua_pushstring(L, "failed"); lua_pushstring(L, failing_uri); luaH_push_gerror(L, error); gint ret = luaH_object_emit_signal(L, -4, "load-status", 3, 1); gboolean ignore = ret && lua_toboolean(L, -1); lua_pop(L, ret + 1); return ignore; } static int luaH_webview_push_certificate_flags(lua_State *L, GTlsCertificateFlags errors) { lua_newtable(L); int n = 1; #define CASE(err, str) \ if (errors & G_TLS_CERTIFICATE_##err) { \ lua_pushliteral(L, str); \ lua_rawseti(L, -2, n++); \ } CASE(UNKNOWN_CA, "unknown-ca") CASE(BAD_IDENTITY, "bad-identity") CASE(NOT_ACTIVATED, "not-activated") CASE(EXPIRED, "expired") CASE(REVOKED, "revoked") CASE(INSECURE, "insecure") CASE(GENERIC_ERROR, "generic-error") return 1; } static gboolean load_failed_tls_cb(WebKitWebView* UNUSED(v), gchar *failing_uri, GTlsCertificate *certificate, GTlsCertificateFlags errors, widget_t *w) { lua_State *L = common.L; webview_data_t *d = w->data; update_uri(w, failing_uri); ((webview_data_t*) w->data)->is_failed = TRUE; /* Store certificate information */ if (d->cert) { g_object_unref(G_OBJECT(d->cert)); d->cert = NULL; } d->cert = certificate; g_object_ref(G_OBJECT(d->cert)); luaH_object_push(L, w->ref); lua_pushliteral(L, "failed"); lua_pushstring(L, failing_uri); GError *error = g_error_new_literal(LUAKIT_ERROR, LUAKIT_ERROR_TLS, "Unacceptable TLS certificate"); luaH_push_gerror(L, error); g_error_free(error); luaH_webview_push_certificate_flags(L, errors); lua_setfield(L, -2, "certificate_flags"); luaH_object_emit_signal(L, -4, "load-status", 3, 0); lua_pop(L, 1); return TRUE; /* Prevent load-failed signal */ } static void webview_get_source_finished(WebKitWebResource *main_resource, GAsyncResult *res, lua_State *L) { gsize length; const gchar *source = (gchar*) webkit_web_resource_get_data_finish(main_resource, res, &length, NULL); g_object_unref(main_resource); lua_pushlstring(L, source, length); luaH_resume(L, 1); } static gint luaH_webview_push_source(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); WebKitWebResource *main_resource = webkit_web_view_get_main_resource(d->view); if (!main_resource) return 0; g_object_ref(main_resource); webkit_web_resource_get_data(main_resource, NULL, (GAsyncReadyCallback) webview_get_source_finished, L); return luaH_yield(L); } static void load_changed_cb(WebKitWebView* UNUSED(v), WebKitLoadEvent e, widget_t *w) { webview_data_t *d = w->data; lua_State *L = common.L; /* get load status literal */ gchar *name = NULL; switch (e) { #define LT_CASE(a, l) case WEBKIT_LOAD_##a: name = l; break; LT_CASE(STARTED, "provisional") LT_CASE(REDIRECTED, "redirected") LT_CASE(COMMITTED, "committed") LT_CASE(FINISHED, "finished") #undef LT_CASE default: warn("programmer error, unable to get load status literal"); break; } update_uri(w, NULL); if (e == WEBKIT_LOAD_STARTED) { ((webview_data_t*) w->data)->is_committed = FALSE; } else if (e == WEBKIT_LOAD_COMMITTED || e == WEBKIT_LOAD_FINISHED) { ((webview_data_t*) w->data)->is_committed = TRUE; } /* Store certificate information about current page */ if (e == WEBKIT_LOAD_STARTED) { if (d->cert) { g_object_unref(G_OBJECT(d->cert)); d->cert = NULL; } } else if (e == WEBKIT_LOAD_COMMITTED) { g_assert(!d->cert); webkit_web_view_get_tls_info(d->view, &d->cert, NULL); if (d->cert) g_object_ref(G_OBJECT(d->cert)); } if (e == WEBKIT_LOAD_COMMITTED) webview_update_stylesheets(L, w); /* Don't send "finished" signal after "failed" signal */ if (e == WEBKIT_LOAD_STARTED) ((webview_data_t*) w->data)->is_failed = FALSE; if (e == WEBKIT_LOAD_FINISHED && ((webview_data_t*) w->data)->is_failed) return; luaH_object_push(L, w->ref); lua_pushstring(L, name); luaH_object_emit_signal(L, -2, "load-status", 1, 0); lua_pop(L, 1); } static GtkWidget* create_cb(WebKitWebView* v, WebKitNavigationAction* UNUSED(a), widget_t *w) { WebKitWebView *view = NULL; widget_t *new; g_assert(!related_view); related_view = v; lua_State *L = common.L; gint top = lua_gettop(L); luaH_object_push(L, w->ref); gint ret = luaH_object_emit_signal(L, -1, "create-web-view", 0, 1); related_view = NULL; /* check for new webview widget */ if (ret) { if ((new = luaH_towidget(L, -1))) { if (new->info->tok == L_TK_WEBVIEW) view = WEBKIT_WEB_VIEW(((webview_data_t*)new->data)->view); else warn("invalid return widget type (expected webview, got %s)", new->info->name); } else warn("invalid signal return object type (expected webview widget, " "got %s)", lua_typename(L, lua_type(L, -1))); } lua_settop(L, top); return GTK_WIDGET(view); } static gboolean decide_policy_cb(WebKitWebView* UNUSED(v), WebKitPolicyDecision *p, WebKitPolicyDecisionType type, widget_t *w) { lua_State *L = common.L; switch (type) { /* Raises the "navigation-request" signal on a webkit navigation policy * decision request. The default action is to load the requested uri. * * The signal handler is able to: * - return true for the handler execution to stop and the request to continue * - return false for the handler execution to stop and the request to hault * - do nothing and give the navigation decision to the next signal handler * * This signal is also where you would attach custom scheme handlers to take * over the navigation request by launching an external application. */ case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: { gint top = lua_gettop(L); WebKitNavigationPolicyDecision *np = WEBKIT_NAVIGATION_POLICY_DECISION(p); WebKitNavigationAction *na = webkit_navigation_policy_decision_get_navigation_action(np); const gchar *signal_name = type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION ? "navigation-request" : "new-window-decision"; const gchar *uri = webkit_uri_request_get_uri(webkit_navigation_action_get_request(na)); gchar *reason = NULL; switch (webkit_navigation_action_get_navigation_type(na)) { # define NR_CASE(a, l) case WEBKIT_NAVIGATION_TYPE_##a: reason = l; break; NR_CASE(LINK_CLICKED, "link-clicked"); NR_CASE(FORM_SUBMITTED, "form-submitted"); NR_CASE(BACK_FORWARD, "back-forward"); NR_CASE(RELOAD, "reload"); NR_CASE(FORM_RESUBMITTED, "form-resubmitted"); NR_CASE(OTHER, "other"); #undef NR_CASE default: warn("programmer error, unable to get web navigation reason literal"); break; } luaH_object_push(L, w->ref); lua_pushstring(L, uri); lua_pushstring(L, reason); gint ret = luaH_object_emit_signal(L, -3, signal_name, 2, 1); gboolean ignore = ret && !lua_toboolean(L, -1); if (ignore) webkit_policy_decision_ignore(p); lua_settop(L, top); return ignore; } case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { // replaces mime_type_decision_cb() in widgets/webview/downloads.c WebKitResponsePolicyDecision *rp = WEBKIT_RESPONSE_POLICY_DECISION(p); WebKitURIResponse *r = webkit_response_policy_decision_get_response(rp); const gchar *uri = webkit_uri_response_get_uri(r); const gchar *mime = webkit_uri_response_get_mime_type(r); if(webkit_uri_response_get_status_code(r) && !SOUP_STATUS_IS_SUCCESSFUL(webkit_uri_response_get_status_code(r))) return FALSE; luaH_object_push(L, w->ref); lua_pushstring(L, uri); lua_pushstring(L, mime); gint ret = luaH_object_emit_signal(L, -3, "mime-type-decision", 2, 1); gboolean ignore = ret && !lua_toboolean(L, -1); if (ignore) /* User responded with false, ignore request */ webkit_policy_decision_ignore(p); else if (g_str_equal(mime, "application/x-extension-html")) webkit_policy_decision_use(p); else if (webkit_response_policy_decision_is_mime_type_supported(rp)) webkit_policy_decision_use(p); else webkit_policy_decision_download(p); lua_pop(L, ret + 1); return TRUE; } default: break; } return FALSE; } static gint luaH_webview_reload(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); webkit_web_view_reload(d->view); return 0; } static gint luaH_webview_reload_bypass_cache(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); webkit_web_view_reload_bypass_cache(d->view); return 0; } static gint luaH_webview_search(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); const gchar *text = luaL_checkstring(L, 2); gboolean case_sensitive = luaH_checkboolean(L, 3); gboolean forward = luaH_checkboolean(L, 4); gboolean wrap = luaH_checkboolean(L, 5); size_t textlen = strlen(text); guint max_match_count = textlen < 5 ? 100 : G_MAXUINT; WebKitFindController *webkit_fc = webkit_web_view_get_find_controller(d->view); webkit_find_controller_search_finish(webkit_fc); webkit_find_controller_search(webkit_fc, text, WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE * (!case_sensitive) | WEBKIT_FIND_OPTIONS_BACKWARDS * (!forward) | WEBKIT_FIND_OPTIONS_WRAP_AROUND * wrap, max_match_count); return 0; } static gint luaH_webview_search_next(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); WebKitFindController *webkit_fc = webkit_web_view_get_find_controller(d->view); webkit_find_controller_search_next(webkit_fc); return 0; } static gint luaH_webview_search_previous(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); WebKitFindController *webkit_fc = webkit_web_view_get_find_controller(d->view); webkit_find_controller_search_previous(webkit_fc); return 0; } static gint luaH_webview_clear_search(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); WebKitFindController *webkit_fc = webkit_web_view_get_find_controller(d->view); webkit_find_controller_search_finish(webkit_fc); return 0; } /* Proxy for the is_loading property; included for compatibility */ static gint luaH_webview_loading(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); luaH_gobject_index(L, webview_properties, L_TK_IS_LOADING, G_OBJECT(d->view)); return 1; } static gint luaH_webview_stop(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); webkit_web_view_stop_loading(d->view); return 0; } static gint luaH_webview_crash(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); ipc_header_t header = { .type = IPC_TYPE_crash, .length = 0 }; ipc_send(d->ipc, &header, NULL); return 0; } /* check for trusted ssl certificate * make sure this function is called after WEBKIT_LOAD_COMMITTED */ static gint luaH_webview_ssl_trusted(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); const gchar *uri = webkit_web_view_get_uri(d->view); GTlsCertificate *cert; GTlsCertificateFlags cert_errors; if (uri && d->is_committed && webkit_web_view_get_tls_info(d->view, &cert, &cert_errors)) { gboolean is_trusted = (cert_errors == 0); lua_pushboolean(L, is_trusted); return 1; } /* return nil if not viewing https uri */ return 0; } static gint luaH_webview_allow_certificate(lua_State *L) { warn("webview:allow_certificate() is deprecated: use luakit.allow_certificate() instead"); (void)luaH_checkwvdata(L, 1); lua_remove(L, 1); luaL_checkstring(L, 1); luaL_checkstring(L, 2); /* When removing this function, make luaH_luakit_allow_certificate static */ return luaH_luakit_allow_certificate(L); } static gint luaH_webview_push_certificate(lua_State *L, widget_t *w) { webview_data_t *d = w->data; if (!d->cert) return 0; gchar *cert_pem; g_object_get(G_OBJECT(d->cert), "certificate-pem", &cert_pem, NULL); lua_pushstring(L, cert_pem); g_free(cert_pem); return 1; } static luakit_token_t webview_translate_old_token(luakit_token_t token) { switch(token) { case L_TK_ENABLE_SCRIPTS: return L_TK_ENABLE_JAVASCRIPT; default: return token; } } static void favicon_cb(WebKitWebView* UNUSED(v), GParamSpec *UNUSED(param_spec), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "favicon", 0, 0); lua_pop(L, 1); } static void uri_cb(WebKitWebView* UNUSED(v), GParamSpec *UNUSED(param_spec), widget_t *w) { update_uri(w, NULL); } static gboolean permission_request_cb(WebKitWebView *UNUSED(v), WebKitPermissionRequest *request, widget_t *w) { lua_State *L = common.L; gint top = lua_gettop(L); if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) lua_pushliteral(L, "notification"); else if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) lua_pushliteral(L, "geolocation"); else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) { lua_pushliteral(L, "install-missing-media-plugins"); WebKitInstallMissingMediaPluginsPermissionRequest* ummpr = (WebKitInstallMissingMediaPluginsPermissionRequest*)request; lua_pushstring(L, webkit_install_missing_media_plugins_permission_request_get_description(ummpr)); } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { lua_pushliteral(L, "user-media"); lua_createtable(L, 0, 2); WebKitUserMediaPermissionRequest* umpr = (WebKitUserMediaPermissionRequest*)request; lua_pushboolean(L, webkit_user_media_permission_is_for_audio_device(umpr)); lua_setfield(L, -2, "audio"); lua_pushboolean(L, webkit_user_media_permission_is_for_video_device(umpr)); lua_setfield(L, -2, "video"); } else return FALSE; gint argc = lua_gettop(L) - top; luaH_object_push(L, w->ref); lua_insert(L, top+1); gint ret = luaH_object_emit_signal(L, top+1, "permission-request", argc, 1); if (ret) { if (lua_toboolean(L, -1)) webkit_permission_request_allow(request); else webkit_permission_request_deny(request); } lua_pop(L, 1); return ret > 0; } static gint luaH_webview_set_pdfjs(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); gboolean enabled = luaH_checkboolean(L, 2); g_autoptr(WebKitFeatureList) features = webkit_settings_get_all_features(); WebKitSettings *settings = webkit_web_view_get_settings(d->view); for (gsize i = 0; i < webkit_feature_list_get_length(features); i++) { WebKitFeature *feature = webkit_feature_list_get(features, i); if (!strcmp( webkit_feature_get_identifier(feature), "PdfJSViewer")) { webkit_settings_set_feature_enabled(settings, feature, enabled); break; } } return 0; } static gint luaH_webview_index(lua_State *L, widget_t *w, luakit_token_t token) { webview_data_t *d = w->data; gint ret; token = webview_translate_old_token(token); switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) PB_CASE(INSPECTOR, d->inspector_open); PB_CASE(PRIVATE, d->private); /* push property methods */ PF_CASE(CLEAR_SEARCH, luaH_webview_clear_search) /* push search methods */ PF_CASE(SEARCH, luaH_webview_search) PF_CASE(SEARCH_NEXT, luaH_webview_search_next) PF_CASE(SEARCH_PREVIOUS, luaH_webview_search_previous) /* push history navigation methods */ PF_CASE(GO_BACK, luaH_webview_go_back) PF_CASE(GO_FORWARD, luaH_webview_go_forward) PF_CASE(CAN_GO_BACK, luaH_webview_can_go_back) PF_CASE(CAN_GO_FORWARD, luaH_webview_can_go_forward) /* push misc webview methods */ PF_CASE(EVAL_JS, luaH_webview_eval_js) PF_CASE(LOAD_STRING, luaH_webview_load_string) PF_CASE(SAVE, luaH_webview_save) /* use is_loading property instead of this function */ PF_CASE(LOADING, luaH_webview_loading) PF_CASE(RELOAD, luaH_webview_reload) PF_CASE(RELOAD_BYPASS_CACHE, luaH_webview_reload_bypass_cache) PF_CASE(SSL_TRUSTED, luaH_webview_ssl_trusted) PF_CASE(STOP, luaH_webview_stop) PF_CASE(CRASH, luaH_webview_crash) /* push inspector webview methods */ PF_CASE(SHOW_INSPECTOR, luaH_webview_show_inspector) PF_CASE(CLOSE_INSPECTOR, luaH_webview_close_inspector) PF_CASE(ALLOW_CERTIFICATE, luaH_webview_allow_certificate) PF_CASE(SET_PDFJS, luaH_webview_set_pdfjs) /* push string properties */ PS_CASE(HOVERED_URI, d->hover) PS_CASE(URI, d->uri) PI_CASE(WEB_PROCESS_ID, d->web_process_id) case L_TK_SOURCE: return luaL_error(L, "view.source has been removed; use view:get_source() instead"); case L_TK_GET_SOURCE: lua_pushcfunction(L, luaH_webview_push_source); luaH_yield_wrap_function(L); return 1; case L_TK_SESSION_STATE: return luaH_webview_push_session_state(L, d); case L_TK_STYLESHEETS: return luaH_webview_push_stylesheets_table(L); PN_CASE(ID, webkit_web_view_get_page_id(d->view)) case L_TK_HISTORY: return luaH_webview_push_history(L, d->view); case L_TK_SCROLL: return luaH_webview_push_scroll_table(L); case L_TK_CERTIFICATE: return luaH_webview_push_certificate(L, w); default: break; } if ((ret = luaH_gobject_index(L, webview_properties, token, G_OBJECT(d->view)))) return ret; if (token == L_TK_HARDWARE_ACCELERATION_POLICY) { /* HACK: there's only one exposed property that has an enum type, so we * special-case it; this should be refactored if there's more than one */ switch (webkit_settings_get_hardware_acceleration_policy(webkit_web_view_get_settings(d->view))) { case WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND: lua_pushstring (L, "on-demand"); return 1; case WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS: lua_pushstring (L, "always"); return 1; case WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER: lua_pushstring (L, "never"); return 1; default: g_assert_not_reached(); } } if ((ret = luaH_gobject_index(L, webview_settings_properties, token, G_OBJECT(webkit_web_view_get_settings(d->view))))) return ret; return luaL_error(L, "cannot get unknown webview property '%s'", lua_tostring(L, 2)); } static gchar* parse_uri(const gchar *uri) { /* check for null uri */ if (!uri || !uri[0] || !g_strcmp0(uri, "about:blank")) return g_strdup("about:blank"); /* check for scheme or "about:blank" */ else if (g_strrstr(uri, "://")) return g_strdup(uri); /* check if uri points to a file */ else if (file_exists(uri)) { if (g_path_is_absolute(uri)) return g_strdup_printf("file://%s", uri); else { /* make path absolute */ gchar *cwd = g_get_current_dir(); gchar *path = g_build_filename(cwd, uri, NULL); gchar *new = g_strdup_printf("file://%s", path); g_free(cwd); g_free(path); return new; } } /* default to http:// scheme */ return g_strdup_printf("http://%s", uri); } /* The __newindex method for the webview object */ static gint luaH_webview_newindex(lua_State *L, widget_t *w, luakit_token_t token) { size_t len; webview_data_t *d = w->data; gchar *uri; token = webview_translate_old_token(token); switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) case L_TK_URI: uri = parse_uri(luaL_checklstring(L, 3, &len)); webkit_web_view_load_uri(d->view, uri); update_uri(w, uri); g_free(uri); return 0; case L_TK_SESSION_STATE: luaH_webview_set_session_state(L, d); return 0; default: break; } /* If setting view.zoom_level = x, x != 1.0, then first reset zoom_level * This prevents an issue where the zoom_level is ignored after a view crash * https://github.com/luakit/luakit/issues/357 */ if (token == L_TK_ZOOM_LEVEL && lua_isnumber(L, 3) && (lua_tonumber(L, 3) != 1.0)) { g_object_freeze_notify(G_OBJECT(d->view)); g_object_set(d->view, "zoom-level", 1.0, NULL); g_object_thaw_notify(G_OBJECT(d->view)); } /* check for webview widget gobject properties */ gboolean emit = luaH_gobject_newindex(L, webview_properties, token, 3, G_OBJECT(d->view)); if (token == L_TK_HARDWARE_ACCELERATION_POLICY) { /* HACK: there's only one exposed property that has an enum type, so we * special-case it; this should be refactored if there's more than one */ const char *str = luaL_checkstring(L, 3); WebKitHardwareAccelerationPolicy value; if (g_str_equal(str, "on-demand")) value = WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND; else if (g_str_equal(str, "always")) value = WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS; else if (g_str_equal(str, "never")) value = WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER; else return luaL_error(L, "invalid value (expected one of 'on-demand', 'always', 'never')"); webkit_settings_set_hardware_acceleration_policy(webkit_web_view_get_settings(d->view), value); emit = TRUE; } /* check for webkit widget's settings gobject properties */ if (!emit) emit = luaH_gobject_newindex(L, webview_settings_properties, token, 3, G_OBJECT(webkit_web_view_get_settings(d->view))); if (emit) return luaH_object_property_signal(L, 1, token); return luaL_error(L, "cannot set unknown webview property '%s'", lua_tostring(L, 2)); } static gboolean expose_cb(GtkWidget* UNUSED(widget), cairo_t *UNUSED(e), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "expose", 0, 0); lua_pop(L, 1); return FALSE; } static void mouse_target_changed_cb(WebKitWebView* UNUSED(v), WebKitHitTestResult *htr, guint UNUSED(modifiers), widget_t *w) { lua_State *L = common.L; webview_data_t *d = w->data; d->htr_context = webkit_hit_test_result_get_context(htr); const char *link = NULL; if (webkit_hit_test_result_context_is_link(htr)) link = webkit_hit_test_result_get_link_uri(htr); /* links are identical, do nothing */ if (d->hover && link && !strcmp(d->hover, link)) return; luaH_object_push(L, w->ref); if (d->hover) { lua_pushstring(L, d->hover); g_free(d->hover); luaH_object_emit_signal(L, -2, "link-unhover", 1, 0); } if (link) { d->hover = g_strdup(link); lua_pushstring(L, d->hover); luaH_object_emit_signal(L, -2, "link-hover", 1, 0); luaH_object_emit_signal(L, -1, "property::hovered_uri", 0, 0); } else d->hover = NULL; lua_pop(L, 1); } static gint luaH_push_hit_test(lua_State *L, WebKitWebView* UNUSED(v), widget_t *w) { /* get hit test */ guint c = ((webview_data_t*) w->data)->htr_context; /* create new table to store hit test context data */ lua_newtable(L); const gchar *name; #define HTR_CHECK(a, l) \ if ((c & WEBKIT_HIT_TEST_RESULT_CONTEXT_##a)) { \ name = l; \ lua_pushstring(L, name); \ lua_pushboolean(L, TRUE); \ lua_rawset(L, -3); \ } /* add context items to table */ HTR_CHECK(DOCUMENT, "document") HTR_CHECK(LINK, "link") HTR_CHECK(IMAGE, "image") HTR_CHECK(MEDIA, "media") HTR_CHECK(EDITABLE, "editable") HTR_CHECK(SCROLLBAR, "scrollbar") #undef HTR_CHECK return 1; } static gboolean webview_button_cb(GtkWidget *view, GdkEventButton *ev, widget_t *w) { gint ret; lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); lua_pushinteger(L, ev->button); /* push webview hit test context */ luaH_push_hit_test(L, WEBKIT_WEB_VIEW(view), w); switch (ev->type) { case GDK_2BUTTON_PRESS: ret = luaH_object_emit_signal(L, -4, "button-double-click", 3, 1); break; case GDK_BUTTON_RELEASE: ret = luaH_object_emit_signal(L, -4, "button-release", 3, 1); break; default: ret = luaH_object_emit_signal(L, -4, "button-press", 3, 1); break; } /* User responded with TRUE, so do not propagate event any further */ if (ret && lua_toboolean(L, -1)) { lua_pop(L, ret + 1); return TRUE; } lua_pop(L, ret + 1); /* propagate event further */ return FALSE; } static gboolean webview_scroll_cb(GtkWidget *view, GdkEventScroll *ev, widget_t *w) { double dx, dy; switch (ev->direction) { case GDK_SCROLL_UP: dx = 0; dy = -1; break; case GDK_SCROLL_DOWN: dx = 0; dy = 1; break; case GDK_SCROLL_LEFT: dx = -1; dy = 0; break; case GDK_SCROLL_RIGHT: dx = 1; dy = 0; break; case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas((GdkEvent*)ev, &dx, &dy); break; default: g_assert_not_reached(); } lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_modifier_table_push(L, ev->state); lua_pushnumber(L, dx); lua_pushnumber(L, dy); luaH_push_hit_test(L, WEBKIT_WEB_VIEW(view), w); gboolean ret = luaH_object_emit_signal(L, -5, "scroll", 4, 1); lua_pop(L, ret + 1); return ret; } static void menu_item_cb(GtkAction *action, widget_t *w) { lua_State *L = common.L; gpointer ref = g_object_get_data(G_OBJECT(action), "lua_callback"); luaH_object_push(L, w->ref); luaH_object_push(L, ref); luaH_dofunction(L, 1, 0); } static void hide_popup_cb(WebKitWebView* UNUSED(v), widget_t* UNUSED(w)) { GSList *iter; lua_State *L = common.L; /* dereference context menu items callback functions from the last context menu */ /* context-menu-dismissed callback gets run before menu_item_cb(), causing the lua_callback to not exist if the refs belonging to the current context menu are freed during hide_popup_cb(). */ if (last_popup.old_refs) { for (iter = last_popup.old_refs; iter; iter = iter->next) luaH_object_unref(L, iter->data); g_slist_free(last_popup.old_refs); last_popup.old_refs = NULL; } } static GSList *context_menu_actions; static int table_from_context_menu(lua_State *L, WebKitContextMenu *menu, widget_t *w) { guint len = webkit_context_menu_get_n_items(menu); lua_createtable(L, len, 0); for (guint i = 1; i <= len; i++) { WebKitContextMenuItem *item = webkit_context_menu_get_item_at_position(menu, i-1); if (webkit_context_menu_item_is_separator(item)) lua_pushboolean(L, TRUE); else { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" GtkAction *action = webkit_context_menu_item_get_action(item); WebKitContextMenuAction stock_action = webkit_context_menu_item_get_stock_action(item); WebKitContextMenu *submenu = webkit_context_menu_item_get_submenu(item); lua_createtable(L, 2, 0); lua_pushstring(L, gtk_action_get_label(action)); #pragma GCC diagnostic pop lua_rawseti(L, -2, 1); if (submenu) table_from_context_menu(L, submenu, w); else if (stock_action == WEBKIT_CONTEXT_MENU_ACTION_CUSTOM) { context_menu_actions = g_slist_prepend(context_menu_actions, action); g_object_ref(G_OBJECT(action)); lua_pushlightuserdata(L, action); } else lua_pushinteger(L, stock_action); lua_rawseti(L, -2, 2); } lua_rawseti(L, -2, i); } return 1; } static void context_menu_from_table(lua_State *L, WebKitContextMenu *menu, widget_t *w) { WebKitContextMenuItem *item; WebKitContextMenu *submenu; gpointer ref; const gchar *label; gint i, len = lua_objlen(L, -1); /* walk table and build context menu */ for(i = 1; i <= len; i++) { lua_rawgeti(L, -1, i); if((lua_type(L, -1) == LUA_TTABLE) && (lua_objlen(L, -1) >= 2)) { lua_rawgeti(L, -1, 1); label = lua_tostring(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); /* add new submenu */ if(lua_type(L, -1) == LUA_TTABLE) { submenu = webkit_context_menu_new(); item = webkit_context_menu_item_new_with_submenu(label, submenu); webkit_context_menu_append(menu, item); context_menu_from_table(L, submenu, w); lua_pop(L, 1); /* add context menu item */ } else if(lua_type(L, -1) == LUA_TFUNCTION) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" GtkAction *action = gtk_action_new(label, label, NULL, NULL); item = webkit_context_menu_item_new(action); #pragma GCC diagnostic pop ref = luaH_object_ref(L, -1); last_popup.refs = g_slist_prepend(last_popup.refs, ref); g_object_set_data(G_OBJECT(action), "lua_callback", ref); webkit_context_menu_append(menu, item); g_signal_connect(action, "activate", G_CALLBACK(menu_item_cb), (gpointer)w); } else if(lua_type(L, -1) == LUA_TNUMBER) { WebKitContextMenuAction stock_action = lua_tointeger(L, -1); g_assert_cmpint(stock_action, !=, WEBKIT_CONTEXT_MENU_ACTION_CUSTOM); item = webkit_context_menu_item_new_from_stock_action_with_label(stock_action, label); webkit_context_menu_append(menu, item); lua_pop(L, 1); } else if(lua_type(L, -1) == LUA_TLIGHTUSERDATA) { GtkAction *action = (void*)lua_topointer(L, -1); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" item = webkit_context_menu_item_new(action); #pragma GCC diagnostic pop webkit_context_menu_append(menu, item); lua_pop(L, 1); } /* add separator if encounters `true` */ } else if(lua_type(L, -1) == LUA_TBOOLEAN && lua_toboolean(L, -1)) { item = webkit_context_menu_item_new_separator(); webkit_context_menu_append(menu, item); } lua_pop(L, 1); } } static gboolean context_menu_cb(WebKitWebView* UNUSED(v), WebKitContextMenu *menu, GdkEvent* UNUSED(e), WebKitHitTestResult* UNUSED(htr), widget_t *w) { lua_State *L = common.L; g_assert(!context_menu_actions); table_from_context_menu(L, menu, w); luaH_object_push(L, w->ref); lua_pushvalue(L, -2); luaH_object_emit_signal(L, -2, "populate-popup", 1, 0); lua_pop(L, 1); last_popup.old_refs = last_popup.refs; last_popup.refs = NULL; webkit_context_menu_remove_all(menu); context_menu_from_table(L, menu, w); g_slist_free_full(context_menu_actions, g_object_unref); context_menu_actions = NULL; lua_pop(L, 1); return FALSE; } static void webview_destructor(widget_t *w) { webview_data_t *d = w->data; g_idle_remove_by_data(w); g_assert(d->ipc); ipc_endpoint_decref(d->ipc); d->ipc = NULL; g_ptr_array_remove(globalconf.webviews, w); g_free(d->uri); g_free(d->hover); g_object_unref(G_OBJECT(d->user_content)); if (d->cert) g_object_unref(G_OBJECT(d->cert)); g_slice_free(webview_data_t, d); } void luakit_uri_scheme_request_cb(WebKitURISchemeRequest *request, const gchar *scheme) { const gchar *uri = webkit_uri_scheme_request_get_uri(request); WebKitWebView *view = webkit_uri_scheme_request_get_web_view(request); if (!view) return; widget_t *w = GOBJECT_TO_LUAKIT_WIDGET(view); lua_State *L = common.L; g_assert(scheme); gchar *sig = g_strconcat("scheme-request::", scheme, NULL); luaH_object_push(L, w->ref); lua_pushstring(L, uri); luaH_request_push_uri_scheme_request(L, request); luaH_object_emit_signal(L, -3, sig, 2, 0); lua_pop(L, 1); g_free(sig); } gboolean webview_crashed_cb(WebKitWebView *UNUSED(view), widget_t *w) { /* Give webview a new disconnected IPC endpoint */ webview_data_t *d = w->data; d->ipc = ipc_endpoint_new("UI"); /* Emit 'crashed' signal on web view */ lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "crashed", 0, 0); lua_pop(L, 1); return FALSE; } void webview_connect_to_endpoint(widget_t *w, ipc_endpoint_t *ipc) { g_assert(w->info->tok == L_TK_WEBVIEW); g_assert(ipc); /* Replace old endpoint with new, sendinq queued data */ webview_data_t *d = w->data; d->ipc = ipc_endpoint_replace(d->ipc, ipc); lua_State *L = common.L; /* Emit 'web-extension-created' signal on luakit if necessary */ /* TODO: move signal emission to somewhere else */ if (!ipc->creation_notified) { ipc->creation_notified = TRUE; gint top = lua_gettop(L); luaH_object_push(L, w->ref); lua_class_t *luakit_class = luakit_lib_get_luakit_class(); luaH_class_emit_signal(L, luakit_class, "web-extension-created", 1, 0); lua_settop(L, top); } /* Emit 'web-extension-loaded' signal on webview */ luaH_object_push(L, w->ref); if (!lua_isnil(L, -1)) luaH_object_emit_signal(L, -1, "web-extension-loaded", 0, 0); lua_pop(L, 1); } ipc_endpoint_t * webview_get_endpoint(widget_t *w) { g_assert(w->info->tok == L_TK_WEBVIEW); webview_data_t *d = w->data; return d->ipc; } void webview_set_web_process_id(widget_t *w, pid_t pid) { webview_data_t *d = w->data; d->web_process_id = pid; } widget_t * widget_webview(lua_State *L, widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_webview_index; w->newindex = luaH_webview_newindex; w->destructor = webview_destructor; /* create private webview data struct */ webview_data_t *d = g_slice_new0(webview_data_t); d->widget = w; w->data = d; /* Determine whether webview should be ephemeral */ /* Lua stack: [{class meta}, {props}, new widget, "type", "webview"] */ gint prop_tbl_idx = luaH_absindex(L, -4); g_assert(lua_istable(L, prop_tbl_idx)); lua_pushstring(L, "private"); lua_rawget(L, prop_tbl_idx); gboolean private = lua_type(L, -1) == LUA_TNIL ? FALSE : lua_toboolean(L, -1); lua_pop(L, 1); d->private = private; /* keep a list of all webview widgets */ if (!globalconf.webviews) globalconf.webviews = g_ptr_array_new(); if (!globalconf.stylesheets) globalconf.stylesheets = g_ptr_array_new(); d->stylesheets = NULL; /* Set web process limits if not already set */ web_context_init_finish(); /* create widgets */ d->user_content = webkit_user_content_manager_new(); d->view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "web-context", web_context_get(), "is-ephemeral", d->private, "user-content-manager", d->user_content, related_view ? "related-view" : NULL, related_view, NULL); d->inspector = webkit_web_view_get_inspector(d->view); d->is_committed = FALSE; /* Create a new endpoint with one ref (this webview) */ d->ipc = ipc_endpoint_new("UI"); w->widget = GTK_WIDGET(d->view); /* insert data into global tables and arrays */ g_ptr_array_add(globalconf.webviews, w); g_object_connect(G_OBJECT(d->view), LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::button-press-event", G_CALLBACK(webview_button_cb), w, "signal::button-release-event", G_CALLBACK(webview_button_cb), w, "signal::scroll-event", G_CALLBACK(webview_scroll_cb), w, "signal::create", G_CALLBACK(create_cb), w, "signal::web-process-crashed", G_CALLBACK(webview_crashed_cb), w, "signal::draw", G_CALLBACK(expose_cb), w, "signal::mouse-target-changed", G_CALLBACK(mouse_target_changed_cb), w, "signal::key-press-event", G_CALLBACK(key_press_cb), w, "signal::decide-policy", G_CALLBACK(decide_policy_cb), w, "signal::notify", G_CALLBACK(notify_cb), w, "signal::load-changed", G_CALLBACK(load_changed_cb), w, "signal::load-failed", G_CALLBACK(load_failed_cb), w, "signal::load-failed-with-tls-errors", G_CALLBACK(load_failed_tls_cb), w, "signal::context-menu", G_CALLBACK(context_menu_cb), w, "signal::context-menu-dismissed", G_CALLBACK(hide_popup_cb), w, "signal::notify::favicon", G_CALLBACK(favicon_cb), w, "signal::notify::uri", G_CALLBACK(uri_cb), w, "signal::authenticate", G_CALLBACK(session_authenticate), w, "signal::permission-request", G_CALLBACK(permission_request_cb), w, NULL); g_object_connect(G_OBJECT(webkit_web_view_get_find_controller(d->view)), "signal::found-text", G_CALLBACK(found_text_cb), w, "signal::failed-to-find-text", G_CALLBACK(failed_to_find_text_cb), w, NULL); g_object_connect(G_OBJECT(d->view), "signal::parent-set", G_CALLBACK(parent_set_cb), w, NULL); g_object_connect(G_OBJECT(d->inspector), "signal::attach", G_CALLBACK(inspector_attach_window_cb), w, "signal::bring-to-front", G_CALLBACK(inspector_show_window_cb), w, "signal::closed", G_CALLBACK(inspector_close_window_cb), w, "signal::detach", G_CALLBACK(inspector_detach_window_cb), w, "signal::open-window", G_CALLBACK(inspector_open_window_cb), w, NULL); /* show widgets */ gtk_widget_show(GTK_WIDGET(d->view)); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview.h000066400000000000000000000023221475363222200163020ustar00rootroot00000000000000/* * widgets/webview.h - interfaces to webview functionality * * Copyright © 2016 Aidan Holm * * 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 . * */ #ifndef LUAKIT_WIDGETS_WEBVIEW_H #define LUAKIT_WIDGETS_WEBVIEW_H #include #include "clib/widget.h" #include "common/ipc.h" widget_t* luaH_checkwebview(lua_State *L, gint udx); widget_t* webview_get_by_id(guint64 view_id); void webview_connect_to_endpoint(widget_t *w, ipc_endpoint_t *ipc); void webview_set_web_process_id(widget_t *w, pid_t pid); ipc_endpoint_t * webview_get_endpoint(widget_t *w); #endif // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/000077500000000000000000000000001475363222200161325ustar00rootroot00000000000000luakit-2.4.0/widgets/webview/auth.c000066400000000000000000000220111475363222200172330ustar00rootroot00000000000000/* * widgets/webview/auth.c - authentication management * * Copyright © 2009 Igalia S.L. * Copyright © 2010 Fabian Streitel * * 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 . * */ #include "luah.h" #include typedef struct { WebKitAuthenticationRequest *request; widget_t *w; GtkWidget *login_entry; GtkWidget *password_entry; GtkWidget *checkbutton; } LuakitAuthData; static void free_auth_data(LuakitAuthData *auth_data) { g_object_unref(auth_data->request); g_slice_free(LuakitAuthData, auth_data); } static void luakit_store_password(LuakitAuthData *auth_data, const gchar *login, const gchar *password) { lua_State *L = common.L; const gchar *uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(auth_data->w->widget)); luaH_object_push(L, auth_data->w->ref); lua_pushstring(L, uri); lua_pushstring(L, login); lua_pushstring(L, password); luaH_object_emit_signal(L, -4, "store-password", 3, 0); lua_pop(L, 1); } static void luakit_find_password(LuakitAuthData *auth_data, const gchar **login, const gchar **password) { lua_State *L = common.L; const gchar *uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(auth_data->w->widget)); luaH_object_push(L, auth_data->w->ref); lua_pushstring(L, uri); gint ret = luaH_object_emit_signal(L, -2, "store-password", 1, LUA_MULTRET); if (ret >= 2) { *password = luaL_checkstring(L, -1); *login = luaL_checkstring(L, -2); } lua_pop(L, 1 + ret); } static void response_callback(GtkDialog *dialog, gint response_id, LuakitAuthData *auth_data) { const gchar *login; const gchar *password; gboolean store_password; WebKitCredential *credential; switch(response_id) { case GTK_RESPONSE_OK: login = gtk_entry_get_text(GTK_ENTRY(auth_data->login_entry)); password = gtk_entry_get_text(GTK_ENTRY(auth_data->password_entry)); credential = webkit_credential_new(login, password, WEBKIT_CREDENTIAL_PERSISTENCE_NONE); webkit_authentication_request_authenticate(auth_data->request, credential); webkit_credential_free(credential); store_password = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(auth_data->checkbutton)); if (store_password) luakit_store_password(auth_data, login, password); default: break; } free_auth_data(auth_data); gtk_widget_destroy(GTK_WIDGET(dialog)); } static GtkWidget * table_add_entry(GtkWidget *table, gint row, const gchar *label_text, const gchar *value, gpointer UNUSED(user_data)) { GtkWidget *label = gtk_label_new(label_text); #if GTK_CHECK_VERSION(3,14,0) GValue align = G_VALUE_INIT; g_value_init(&align, G_TYPE_ENUM); g_value_set_int(&align, GTK_ALIGN_CENTER); g_object_set_property(G_OBJECT(label), "halign", &align); #else gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); #endif gtk_widget_set_vexpand(GTK_WIDGET(label), TRUE); GtkWidget *entry = gtk_entry_new(); gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); if (value) gtk_entry_set_text(GTK_ENTRY(entry), value); // left,top,width,height gtk_grid_attach(GTK_GRID(table), label, 0, row, 1, 1); gtk_grid_attach(GTK_GRID(table), entry, 1, row, 1, 1); /* fill in all directions */ gtk_widget_set_halign(label, GTK_ALIGN_FILL); gtk_widget_set_valign(label, GTK_ALIGN_FILL); gtk_widget_set_halign(entry, GTK_ALIGN_FILL); gtk_widget_set_valign(entry, GTK_ALIGN_FILL); /* expand vertically */ gtk_widget_set_vexpand(label, TRUE); gtk_widget_set_vexpand(entry, TRUE); return entry; } static void show_auth_dialog(LuakitAuthData *auth_data, const char *login, const char *password) { GtkWidget *widget = gtk_dialog_new(); GtkWindow *window = GTK_WINDOW(widget); GtkDialog *dialog = GTK_DIALOG(widget); #if GTK_CHECK_VERSION(3,10,0) gtk_dialog_add_buttons(dialog, "_Cancel", GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_OK, NULL); #else gtk_dialog_add_buttons(dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); #endif /* set dialog properties */ gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); #if GTK_CHECK_VERSION(3,12,0) GValue button_spacing = G_VALUE_INIT; g_value_init(&button_spacing, G_TYPE_INT); g_value_set_int(&button_spacing, 6); g_object_set_property(G_OBJECT(dialog), "button-spacing", &button_spacing); #else gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(dialog)), 2); gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_action_area(dialog)), 5); gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_action_area(dialog)), 6); #endif gtk_window_set_resizable(window, FALSE); gtk_window_set_title(window, ""); gtk_window_set_icon_name(window, "dialog-password"); gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK); /* build contents */ GtkWidget *hbox = gtk_grid_new(); GValue margin = G_VALUE_INIT; g_value_init(&margin, G_TYPE_INT); g_value_set_int(&margin, 5); g_object_set_property(G_OBJECT(hbox), "margin", &margin); gtk_grid_set_column_spacing(GTK_GRID(hbox), 12); gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(dialog)), hbox, TRUE, TRUE, 0); #if GTK_CHECK_VERSION(3,10,0) GtkWidget *icon = gtk_image_new_from_icon_name("dialog-password", GTK_ICON_SIZE_DIALOG); #else GtkWidget *icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); #endif #if GTK_CHECK_VERSION(3,14,0) GValue align = G_VALUE_INIT; g_value_init(&align, G_TYPE_ENUM); g_value_set_int(&align, GTK_ALIGN_CENTER); g_object_set_property(G_OBJECT(hbox), "halign", &align); #else gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0); #endif gtk_grid_attach(GTK_GRID(hbox), icon, 0,0,1,2); gtk_grid_set_row_spacing(GTK_GRID(hbox), 6); gchar *msg = g_strdup_printf("A username and password are being requested by the site %s", webkit_authentication_request_get_host(auth_data->request)); GtkWidget *msg_label = gtk_label_new(msg); g_free(msg); #if GTK_CHECK_VERSION(3,14,0) g_object_set_property(G_OBJECT(msg_label), "halign", &align); #else gtk_misc_set_alignment(GTK_MISC(msg_label), 0.0, 0.5); #endif gtk_label_set_line_wrap(GTK_LABEL(msg_label), TRUE); GValue max_width_chars = G_VALUE_INIT; g_value_init(&max_width_chars, G_TYPE_INT); g_value_set_int(&max_width_chars, 32); /* TODO this is a kludge */ g_object_set_property(G_OBJECT(msg_label), "max-width-chars", &max_width_chars); gtk_grid_attach_next_to(GTK_GRID(hbox), GTK_WIDGET(msg_label), icon, GTK_POS_RIGHT, 1, 1); gtk_widget_set_hexpand(GTK_WIDGET(msg_label), FALSE); gtk_widget_set_vexpand(GTK_WIDGET(msg_label), TRUE); GtkWidget *table = gtk_grid_new(); gtk_grid_attach_next_to(GTK_GRID(hbox), table, GTK_WIDGET(msg_label), GTK_POS_BOTTOM, 1, 1); gtk_grid_set_column_homogeneous(GTK_GRID(table), FALSE); gtk_grid_set_row_homogeneous(GTK_GRID(table), FALSE); gtk_grid_set_column_spacing(GTK_GRID(table), 12); gtk_grid_set_row_spacing(GTK_GRID(table), 6); /* default margin of GtkWidgets is 0; no need to set explicitly */ /* default hexpand/vexpand value for table is FALSE */ auth_data->login_entry = table_add_entry(table, 0, "Username:", login, NULL); auth_data->password_entry = table_add_entry(table, 1, "Password:", password, NULL); gtk_entry_set_visibility(GTK_ENTRY(auth_data->password_entry), FALSE); GtkWidget *checkbutton = gtk_check_button_new_with_label("Store password"); gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkbutton))), TRUE); gtk_grid_attach_next_to(GTK_GRID(hbox), checkbutton, table, GTK_POS_BOTTOM, 1, 1); auth_data->checkbutton = checkbutton; g_signal_connect(dialog, "response", G_CALLBACK(response_callback), auth_data); gtk_widget_show_all(widget); } static gboolean session_authenticate(WebKitWebView *UNUSED(web_view), WebKitAuthenticationRequest *request, widget_t *w) { g_object_ref(request); LuakitAuthData *auth_data = g_slice_new(LuakitAuthData); auth_data->request = request; auth_data->w = w; const gchar *login = NULL; const gchar *password = NULL; luakit_find_password(auth_data, &login, &password); show_auth_dialog(auth_data, login, password); /* TODO: g_free login and password? */ return TRUE; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/downloads.c000066400000000000000000000030101475363222200202620ustar00rootroot00000000000000/* * widgets/webview/downloads.c - webkit webview download functions * * Copyright © 2010-2011 Mason Larobina * * 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 . * */ #include "clib/download.h" #include "clib/luakit.h" gboolean download_start_cb(WebKitWebContext* UNUSED(c), WebKitDownload *dl, gpointer UNUSED(user_data)) { WebKitWebView *dl_view = webkit_download_get_web_view(dl); widget_t *w = dl_view ? GOBJECT_TO_LUAKIT_WIDGET(dl_view) : NULL; lua_State *L = common.L; gint top = lua_gettop(L); luaH_download_push(L, dl); if (w) luaH_object_push(L, w->ref); else lua_pushnil(L); lua_class_t *luakit_class = luakit_lib_get_luakit_class(); gint ret = luaH_class_emit_signal(L, luakit_class, "download-start", 2, 1); gboolean handled = (ret && lua_toboolean(L, 2)); lua_settop(L, top); return handled; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/find_controller.c000066400000000000000000000026771475363222200214750ustar00rootroot00000000000000/* * widgets/webview/find_controller.c - WebKitFindController wrappers * * Copyright © 2012 Mason Larobina * Copyright © 2011-2012 Fabian Streitel * * 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 . * */ static void found_text_cb(WebKitFindController* UNUSED(find_controller), guint match_count, widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); lua_pushinteger(L, match_count); luaH_object_emit_signal(L, -2, "found-text", 1, 0); lua_pop(L, 1); return; } static void failed_to_find_text_cb(WebKitFindController* UNUSED(find_controller), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "failed-to-find-text", 0, 0); lua_pop(L, 1); return; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/history.c000066400000000000000000000112061475363222200177770ustar00rootroot00000000000000/* * widgets/webview/history.c - webkit webview history functions * * Copyright © 2010-2011 Mason Larobina * * 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 . * */ static gint luaH_webview_push_history(lua_State *L, WebKitWebView *view) { /* obtain the history list of the tab and get information about it */ WebKitBackForwardList *bflist = webkit_web_view_get_back_forward_list(view); WebKitBackForwardListItem *item; // TODO do these new GLists need to be freed? gint backlen = g_list_length( webkit_back_forward_list_get_back_list(bflist)); gint forwardlen = g_list_length( webkit_back_forward_list_get_forward_list(bflist)); /* compose an overall table with the history list and the position thereof */ lua_createtable(L, 0, 2); /* Set hist[index] = pos */ lua_pushliteral(L, "index"); lua_pushnumber(L, backlen + 1); lua_rawset(L, -3); /* create a table with the history items */ lua_createtable(L, backlen + forwardlen + 1, 0); for(gint i = -backlen; i <= forwardlen; i++) { /* each individual history item is composed of a URL and a page title */ item = webkit_back_forward_list_get_nth_item(bflist, i); lua_createtable(L, 0, 2); /* Set hist_item[uri] = uri */ lua_pushliteral(L, "uri"); lua_pushstring(L, item ? webkit_back_forward_list_item_get_uri(item) : "about:blank"); lua_rawset(L, -3); /* Set hist_item[title] = title */ lua_pushliteral(L, "title"); lua_pushstring(L, item ? webkit_back_forward_list_item_get_title(item) : ""); lua_rawset(L, -3); lua_rawseti(L, -2, backlen + i + 1); } /* Set hist[items] = hist_items_table */ lua_pushliteral(L, "items"); lua_insert(L, lua_gettop(L) - 1); lua_rawset(L, -3); return 1; } static gint luaH_webview_can_go_back(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); lua_pushboolean(L, webkit_web_view_can_go_back(d->view)); return 1; } static gint luaH_webview_can_go_forward(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); lua_pushboolean(L, webkit_web_view_can_go_forward(d->view)); return 1; } static gint webview_history_go(lua_State *L, gint direction) { webview_data_t *d = luaH_checkwvdata(L, 1); gint steps = (gint) luaL_checknumber(L, 2) * direction; WebKitBackForwardListItem *item = webkit_back_forward_list_get_nth_item( webkit_web_view_get_back_forward_list(d->view), steps); if (item) webkit_web_view_go_to_back_forward_list_item(d->view, item); lua_pushboolean(L, item != NULL); return 1; } static gint luaH_webview_go_back(lua_State *L) { return webview_history_go(L, -1); } static gint luaH_webview_go_forward(lua_State *L) { return webview_history_go(L, 1); } static void luaH_webview_set_session_state(lua_State *L, webview_data_t *d) { size_t len; const gchar *str = lua_tolstring(L, 3, &len); GBytes *bytes = g_bytes_new(str, len); WebKitWebViewSessionState *state = webkit_web_view_session_state_new(bytes); g_bytes_unref(bytes); if (!state) luaL_error(L, "Invalid session state"); webkit_web_view_restore_session_state(d->view, state); webkit_web_view_session_state_unref(state); WebKitBackForwardList *bfl = webkit_web_view_get_back_forward_list(d->view); WebKitBackForwardListItem *item = webkit_back_forward_list_get_current_item(bfl); if (item) { webkit_web_view_go_to_back_forward_list_item(d->view, item); update_uri(d->widget, webkit_back_forward_list_item_get_uri(item)); } } static int luaH_webview_push_session_state(lua_State *L, webview_data_t *d) { WebKitWebViewSessionState *state = webkit_web_view_get_session_state(d->view); GBytes *bytes = webkit_web_view_session_state_serialize(state); gsize len; const gchar *str = g_bytes_get_data(bytes, &len); lua_pushlstring(L, str, len); g_bytes_unref(bytes); webkit_web_view_session_state_unref(state); return 1; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/inspector.c000066400000000000000000000056711475363222200203150ustar00rootroot00000000000000/* * widgets/webview/inspector.c - WebKitWebInspector wrappers * * Copyright © 2012 Mason Larobina * Copyright © 2011-2012 Fabian Streitel * * 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 . * */ gboolean inspector_open_window_cb(WebKitWebInspector *UNUSED(inspector), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); gint nret = luaH_object_emit_signal(L, -1, "create-inspector-window", 0, 1); gboolean ret = nret && lua_toboolean(L, -1); lua_pop(L, 1 + nret); return ret; } static gboolean inspector_show_window_cb(WebKitWebInspector* UNUSED(inspector), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); gint nret = luaH_object_emit_signal(L, -1, "show-inspector", 0, 1); gboolean ret = nret && lua_toboolean(L, -1); lua_pop(L, 1 + nret); return ret; } static gboolean inspector_close_window_cb(WebKitWebInspector* UNUSED(inspector), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); webview_data_t *d = w->data; lua_pushnil(L); d->inspector_open = FALSE; gint nret = luaH_object_emit_signal(L, -2, "close-inspector", 1, 0); gboolean ret = nret && lua_toboolean(L, -1); lua_pop(L, 1 + nret); return ret; } static gboolean inspector_attach_window_cb(WebKitWebInspector* UNUSED(inspector), widget_t *w) { lua_State *L = common.L; webview_data_t *d = w->data; d->inspector_open = TRUE; luaH_object_push(L, w->ref); gint nret = luaH_object_emit_signal(L, -1, "attach-inspector", 0, 0); gboolean ret = nret && lua_toboolean(L, -1); lua_pop(L, 1 + nret); return ret; } static gboolean inspector_detach_window_cb(WebKitWebInspector* UNUSED(inspector), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); gint nret = luaH_object_emit_signal(L, -1, "detach-inspector", 0, 0); gboolean ret = nret && lua_toboolean(L, -1); lua_pop(L, 1 + nret); return ret; } static gint luaH_webview_show_inspector(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); webkit_web_inspector_show(d->inspector); return 0; } static gint luaH_webview_close_inspector(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, 1); webkit_web_inspector_close(d->inspector); return 0; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/javascript.c000066400000000000000000000062541475363222200204530ustar00rootroot00000000000000/* * widgets/webview/javascript.c - webkit webview javascript functions * * Copyright © 2010-2012 Mason Larobina * * 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 . * */ #include #include "common/luajs.h" #include "common/ipc.h" #include "common/luaserialize.h" void run_javascript_finished(const guint8 *msg, guint length) { lua_State *L = common.L; gint top = lua_gettop(L); gint n = lua_deserialize_range(L, msg, length); g_assert_cmpint(n, >=, 2); g_assert_cmpint(n, <=, 4); /* Lua stack: [page_id, cb], [page_id, cb, nil, err] or [page_id, cb, ret] */ widget_t *w = webview_get_by_id(lua_tointeger(L, -n)); lua_remove(L, -n); n--; gpointer cb = lua_touserdata(L, -n); if(!cb) { warn("javascript finshed called on non object"); return; } lua_remove(L, -n); n--; if (n == 2) { /* Nil return value and Error */ g_assert(lua_isnil(L, -2)); g_assert(lua_isstring(L, -1)); } if (n >= 1 && cb && w) { luaH_object_push(L, cb); luaH_dofunction(L, n, 0); } if (w && cb) { g_signal_handlers_disconnect_by_data(w->widget, cb); luaH_object_unref(L, cb); } lua_settop(L, top); } static void run_javascript_webview_closed(WebKitWebView *UNUSED(view), gpointer cb) { luaH_object_unref(common.L, cb); } static gint luaH_webview_eval_js(lua_State *L) { gpointer cb = NULL; webview_data_t *d = luaH_checkwvdata(L, 1); const gchar *script = luaL_checkstring(L, 2); const gchar *usr_source = NULL; gchar *source = NULL; bool no_return = false; luaH_checktable(L, 3); gint top = lua_gettop(L); /* source filename to use in error messages and webinspector */ if (luaH_rawfield(L, 3, "source") && lua_isstring(L, -1)) usr_source = lua_tostring(L, -1); if (luaH_rawfield(L, 3, "no_return")) no_return = lua_toboolean(L, -1); if (luaH_rawfield(L, 3, "callback")) { luaH_checkfunction(L, -1); cb = luaH_object_ref(L, -1); } lua_settop(L, top); if (!usr_source) source = luaH_callerinfo(L); lua_pushboolean(L, no_return); lua_pushstring(L, script); lua_pushstring(L, usr_source ? g_strdup(usr_source) : source); lua_pushinteger(L, webkit_web_view_get_page_id(d->view)); lua_pushlightuserdata(L, cb); ipc_send_lua(d->ipc, IPC_TYPE_eval_js, L, -5, -1); lua_pop(L, 5); if (cb) g_signal_connect(d->view, "destroy", G_CALLBACK(run_javascript_webview_closed), cb); return FALSE; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/scroll.c000066400000000000000000000062111475363222200175740ustar00rootroot00000000000000/* * widgets/webview/scroll.c - webkit webview scroll functions * * Copyright © 2010-2011 Mason Larobina * * 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 . * */ #include "ipc.h" void webview_scroll_recv(widget_t *w, const ipc_scroll_t *msg) { webview_data_t *d = w->data; if (webkit_web_view_get_page_id(d->view) != msg->page_id) return; switch (msg->subtype) { case IPC_SCROLL_TYPE_docresize: d->doc_w = msg->h; d->doc_h = msg->v; break; case IPC_SCROLL_TYPE_winresize: d->win_w = msg->h; d->win_h = msg->v; break; case IPC_SCROLL_TYPE_scroll: d->scroll_x = msg->h; d->scroll_y = msg->v; default: break; } } static gint luaH_webview_scroll_newindex(lua_State *L) { /* get webview widget upvalue */ webview_data_t *d = luaH_checkwvdata(L, lua_upvalueindex(1)); const gchar *prop = luaL_checkstring(L, 2); luakit_token_t t = l_tokenize(prop); if (t == L_TK_X) d->scroll_x = luaL_checknumber(L, 3); else if (t == L_TK_Y) d->scroll_y = luaL_checknumber(L, 3); else { return 0; } lua_pushinteger(L, webkit_web_view_get_page_id(d->view)); lua_pushinteger(L, d->scroll_x); lua_pushinteger(L, d->scroll_y); ipc_send_lua(d->ipc, IPC_TYPE_scroll, L, 4, 6); return 0; } static gint luaH_webview_scroll_index(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, lua_upvalueindex(1)); const gchar *prop = luaL_checkstring(L, 2); luakit_token_t t = l_tokenize(prop); switch (t) { PN_CASE(X, d->scroll_x); PN_CASE(Y, d->scroll_y); PN_CASE(XMAX, d->doc_w - d->win_w); PN_CASE(YMAX, d->doc_h - d->win_h); PN_CASE(XPAGE_SIZE, d->win_w); PN_CASE(YPAGE_SIZE, d->win_h); default: return 0; } } static gint luaH_webview_push_scroll_table(lua_State *L) { /* create scroll table */ lua_newtable(L); /* setup metatable */ lua_createtable(L, 0, 2); /* push __index metafunction */ lua_pushliteral(L, "__index"); lua_pushvalue(L, 1); /* copy webview userdata */ lua_pushcclosure(L, luaH_webview_scroll_index, 1); lua_rawset(L, -3); /* push __newindex metafunction */ lua_pushliteral(L, "__newindex"); lua_pushvalue(L, 1); /* copy webview userdata */ lua_pushcclosure(L, luaH_webview_scroll_newindex, 1); lua_rawset(L, -3); lua_setmetatable(L, -2); return 1; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/webview/stylesheets.c000066400000000000000000000103711475363222200206540ustar00rootroot00000000000000/* * Copyright © 2016 Aidan Holm * * 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 . * */ #include "clib/stylesheet.h" static gboolean inside_stylesheet_cb = FALSE; void webview_stylesheets_regenerate_stylesheet(widget_t *w, lstylesheet_t *stylesheet) { webview_data_t *d = w->data; /* If this styleheet was enabled, it needs to be re-added to the user * content manager, since its internal WebKitUserStyleSheet pointer has * changed: mark for refresh */ if (g_list_find(d->stylesheets, stylesheet)) d->stylesheet_refreshed = TRUE; } void webview_stylesheets_regenerate(widget_t *w) { webview_data_t *d = w->data; /* Re-add the user content manager stylesheets, if necessary * Always fully rebuild, because there's no remove_style_sheet(), * it's not currently easy to tell if a stylesheet has already been * added, and a full rebuild is required anyway fairly often */ if (d->stylesheet_added || d->stylesheet_removed || d->stylesheet_refreshed) { webkit_user_content_manager_remove_all_style_sheets(d->user_content); GList *l; for (l = d->stylesheets; l; l = l->next) { lstylesheet_t *stylesheet = l->data; webkit_user_content_manager_add_style_sheet(d->user_content, stylesheet->stylesheet); } d->stylesheet_added = FALSE; d->stylesheet_removed = FALSE; } } int webview_stylesheet_set_enabled(widget_t *w, lstylesheet_t *stylesheet, gboolean enable) { webview_data_t *d = w->data; GList *item = g_list_find(d->stylesheets, stylesheet); /* Return early if nothing to do */ if (enable == (item != NULL)) return 0; if (enable) { d->stylesheets = g_list_prepend(d->stylesheets, stylesheet); d->stylesheet_added = TRUE; } else { d->stylesheets = g_list_remove_link(d->stylesheets, item); d->stylesheet_removed = TRUE; } if (!inside_stylesheet_cb) webview_stylesheets_regenerate(w); return 0; } static gint luaH_webview_stylesheets_index(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, lua_upvalueindex(1)); lstylesheet_t *stylesheet = luaH_checkstylesheet(L, 2); gboolean enabled = g_list_find(d->stylesheets, stylesheet) != NULL; lua_pushboolean(L, enabled); return 1; } static gint luaH_webview_stylesheets_newindex(lua_State *L) { webview_data_t *d = luaH_checkwvdata(L, lua_upvalueindex(1)); lstylesheet_t *stylesheet = luaH_checkstylesheet(L, 2); gboolean enable = lua_toboolean(L, 3); webview_stylesheet_set_enabled(d->widget, stylesheet, enable); return 0; } static gint luaH_webview_push_stylesheets_table(lua_State *L) { /* create scroll table */ lua_newtable(L); /* setup metatable */ lua_createtable(L, 0, 2); /* push __index metafunction */ lua_pushliteral(L, "__index"); lua_pushvalue(L, 1); /* copy webview userdata */ lua_pushcclosure(L, luaH_webview_stylesheets_index, 1); lua_rawset(L, -3); /* push __newindex metafunction */ lua_pushliteral(L, "__newindex"); lua_pushvalue(L, 1); /* copy webview userdata */ lua_pushcclosure(L, luaH_webview_stylesheets_newindex, 1); lua_rawset(L, -3); lua_setmetatable(L, -2); return 1; } static void webview_update_stylesheets(lua_State *L, widget_t *w) { webview_data_t *d = w->data; d->stylesheet_added = FALSE; d->stylesheet_removed = FALSE; inside_stylesheet_cb = TRUE; luaH_object_push(L, w->ref); luaH_object_emit_signal(L, -1, "stylesheet", 0, 0); lua_pop(L, 1); inside_stylesheet_cb = FALSE; webview_stylesheets_regenerate(w); } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80 luakit-2.4.0/widgets/window.c000066400000000000000000000162531475363222200161440ustar00rootroot00000000000000/* * widgets/window.c - gtk window widget wrapper * * Copyright © 2010 Mason Larobina * * 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 . * */ #ifdef GDK_WINDOWING_X11 #include #endif #include #include "luah.h" #include "widgets/common.h" typedef struct { widget_t *widget; GtkWindow *win; GdkWindowState state; guint id; } window_data_t; static int window_id_next = 0; static widget_t * luaH_checkwindow(lua_State *L, gint udx) { widget_t *w = luaH_checkwidget(L, udx); if (w->info->tok != L_TK_WINDOW) luaL_argerror(L, udx, "expected window widget"); return w; } #define luaH_checkwindata(L, udx) ((window_data_t*)(luaH_checkwindow(L, udx)->data)) static void destroy_win_cb(GtkWidget* UNUSED(win), widget_t *w) { /* remove window from global windows list */ g_ptr_array_remove(globalconf.windows, w); } static gint can_close_cb(GtkWidget* UNUSED(win), GdkEvent *UNUSED(event), widget_t *w) { lua_State *L = common.L; luaH_object_push(L, w->ref); gint ret = luaH_object_emit_signal(L, -1, "can-close", 0, 1); gboolean keep_open = ret && !lua_toboolean(L, -1); lua_pop(L, ret + 1); return keep_open; } static gint luaH_window_set_dark_mode(lua_State *L) { window_data_t *d = luaH_checkwindata(L, 1); gboolean dark_mode = lua_toboolean(L, 2); g_object_set(gtk_widget_get_settings(GTK_WIDGET(d->win)), "gtk-application-prefer-dark-theme", dark_mode, NULL); return 0; } static gint luaH_window_set_default_size(lua_State *L) { window_data_t *d = luaH_checkwindata(L, 1); gint width = (gint) luaL_checknumber(L, 2); gint height = (gint) luaL_checknumber(L, 3); gtk_window_set_default_size(d->win, width, height); return 0; } static gint luaH_window_index(lua_State *L, widget_t *w, luakit_token_t token) { window_data_t *d = w->data; switch(token) { LUAKIT_WIDGET_INDEX_COMMON(w) LUAKIT_WIDGET_BIN_INDEX_COMMON(w) LUAKIT_WIDGET_CONTAINER_INDEX_COMMON(w) /* push window class methods */ PF_CASE(SET_DEFAULT_SIZE, luaH_window_set_default_size) /* push string properties */ PS_CASE(TITLE, gtk_window_get_title(d->win)) /* push boolean properties */ PB_CASE(DECORATED, gtk_window_get_decorated(d->win)) PB_CASE(URGENCY_HINT, gtk_window_get_urgency_hint(d->win)) PB_CASE(FULLSCREEN, d->state & GDK_WINDOW_STATE_FULLSCREEN) PB_CASE(MAXIMIZED, d->state & GDK_WINDOW_STATE_MAXIMIZED) PF_CASE(SET_DARK_MODE, luaH_window_set_dark_mode) /* push integer properties */ PN_CASE(ID, d->id) # ifdef GDK_WINDOWING_X11 case L_TK_ROOT_WIN_XID: lua_pushlightuserdata(L, GDK_WINDOW( # if GTK_CHECK_VERSION(3,12,0) gdk_screen_get_root_window(gtk_widget_get_screen(GTK_WIDGET(d->win))) # else gtk_widget_get_root_window(GTK_WIDGET(d->win)) # endif )); return 1; PD_CASE(WIN_XID, GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(d->win)))); # endif case L_TK_SCREEN: lua_pushlightuserdata(L, gtk_window_get_screen(d->win)); return 1; default: break; } return 0; } static gint luaH_window_newindex(lua_State *L, widget_t *w, luakit_token_t token) { window_data_t *d = w->data; switch(token) { LUAKIT_WIDGET_NEWINDEX_COMMON(w) LUAKIT_WIDGET_BIN_NEWINDEX_COMMON(w) case L_TK_DECORATED: gtk_window_set_decorated(d->win, luaH_checkboolean(L, 3)); break; case L_TK_URGENCY_HINT: gtk_window_set_urgency_hint(d->win, luaH_checkboolean(L, 3)); break; case L_TK_TITLE: gtk_window_set_title(d->win, luaL_checkstring(L, 3)); break; case L_TK_ICON: gtk_window_set_icon_from_file(d->win, luaL_checkstring(L, 3), NULL); break; case L_TK_SCREEN: if (!lua_islightuserdata(L, 3)) luaL_argerror(L, 3, "expected GdkScreen lightuserdata"); gtk_window_set_screen(d->win, (GdkScreen*)lua_touserdata(L, 3)); gtk_window_present(d->win); break; case L_TK_FULLSCREEN: if (luaH_checkboolean(L, 3)) gtk_window_fullscreen(d->win); else gtk_window_unfullscreen(d->win); return 0; case L_TK_MAXIMIZED: if (luaH_checkboolean(L, 3)) gtk_window_maximize(d->win); else gtk_window_unmaximize(d->win); return 0; default: return 0; } return luaH_object_property_signal(L, 1, token); } static gboolean window_state_cb(GtkWidget* UNUSED(widget), GdkEventWindowState *ev, widget_t *w) { window_data_t *d = (window_data_t*)w->data; d->state = ev->new_window_state; lua_State *L = common.L; luaH_object_push(L, w->ref); if (ev->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) luaH_object_property_signal(L, -1, L_TK_MAXIMIZED); if (ev->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) luaH_object_property_signal(L, -1, L_TK_FULLSCREEN); lua_pop(L, 1); return FALSE; } static void window_destructor(widget_t *w) { g_slice_free(window_data_t, w->data); } widget_t * widget_window(lua_State *UNUSED(L), widget_t *w, luakit_token_t UNUSED(token)) { w->index = luaH_window_index; w->newindex = luaH_window_newindex; w->destructor = window_destructor; /* create private window data struct */ window_data_t *d = g_slice_new0(window_data_t); d->widget = w; w->data = d; /* create and setup window widget */ w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); d->win = GTK_WINDOW(w->widget); gtk_window_set_default_size(d->win, 800, 600); gtk_window_set_title(d->win, "luakit"); if (globalconf.application) gtk_window_set_application(d->win, globalconf.application); GdkGeometry hints; hints.min_width = 1; hints.min_height = 1; gtk_window_set_geometry_hints(d->win, NULL, &hints, GDK_HINT_MIN_SIZE); g_object_connect(G_OBJECT(w->widget), "signal::destroy", G_CALLBACK(destroy_win_cb), w, LUAKIT_WIDGET_SIGNAL_COMMON(w) "signal::add", G_CALLBACK(add_cb), w, "signal::delete-event", G_CALLBACK(can_close_cb), w, "signal::key-press-event", G_CALLBACK(key_press_cb), w, "signal::remove", G_CALLBACK(remove_cb), w, "signal::window-state-event", G_CALLBACK(window_state_cb), w, NULL); d->id = ++window_id_next; /* add to global windows list */ g_ptr_array_add(globalconf.windows, w); return w; } // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80