aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSilvio Rhatto <rhatto@riseup.net>2017-09-21 21:39:54 -0300
committerSilvio Rhatto <rhatto@riseup.net>2017-09-21 21:39:54 -0300
commitfc24ecca542a10deefc789342f373e8d6a4a43cb (patch)
treef3e11c3a14e156a145778c200219c86058e9805c
parent562adce657add8aaf2c80597fc54fa0a906c2287 (diff)
downloadluakit-fc24ecca542a10deefc789342f373e8d6a4a43cb.tar.gz
luakit-fc24ecca542a10deefc789342f373e8d6a4a43cb.tar.bz2
Stable config
-rw-r--r--config.dot/luakit.link/globals.lua68
-rw-r--r--config.dot/luakit.link/rc.lua68
-rw-r--r--config.dot/luakit.link/userprefs.lua19
-rw-r--r--config.dot/luakit.link/webview.lua360
-rw-r--r--config.dot/luakit.link/webview_wm.lua21
-rw-r--r--config.dot/luakit.link/window.lua789
6 files changed, 1287 insertions, 38 deletions
diff --git a/config.dot/luakit.link/globals.lua b/config.dot/luakit.link/globals.lua
new file mode 100644
index 0000000..ff2cbf3
--- /dev/null
+++ b/config.dot/luakit.link/globals.lua
@@ -0,0 +1,68 @@
+-- Global variables for luakit
+
+local globals = {
+ homepage = "luakit://newtab",
+ scroll_step = 40,
+ zoom_step = 0.1,
+ max_cmd_history = 100,
+ max_srch_history = 100,
+ default_window_size = "800x600",
+ vertical_tab_width = 200,
+
+ -- Disables loading of hostnames from /etc/hosts (for large host files)
+ -- load_etc_hosts = false,
+ -- Disables checking if a filepath exists in search_open function
+ -- check_filepath = false,
+ -- Specify your preferred terminal emulator
+ -- term = "urxvt",
+}
+
+-- List of search engines. Each item must contain a single %s which is
+-- replaced by URI encoded search terms. All other occurances of the percent
+-- character (%) may need to be escaped by placing another % before or after
+-- it to avoid collisions with lua's string.format characters.
+-- See: http://www.lua.org/manual/5.1/manual.html#pdf-string.format
+globals.search_engines = {
+ github = "https://github.com/search?q=%s",
+ imdb = "http://imdb.com/find?s=all&q=%s",
+ google = "https://google.com/search?q=%s",
+ duck = "https://duckduckgo.com/?q=%s&t=debian",
+ wikipedia = "https://en.wikipedia.org/wiki/Special:Search?search=%s",
+ debbugs = "https://bugs.debian.org/%s",
+ sourceforge = "https://sf.net/search/?words=%s",
+ debian = "https://packages.debian.org/search?keywords=%s",
+ searx = "https://searx.org/?q=%s",
+}
+
+-- Set google as fallback search engine
+globals.search_engines.default = globals.search_engines.duck
+-- Use this instead to disable auto-searching
+--search_engines.default = "%s"
+
+-- Per-domain webview properties
+-- https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html
+-- https://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html
+globals.domain_props = {
+ ["all"] = {
+ enable_webgl = true,
+ -- enable_scripts = false,
+ -- enable_plugins = false,
+ -- zoom_text_only = true,
+ }, --[[
+ ["youtube.com"] = {
+ enable_scripts = true,
+ enable_plugins = true,
+ }, ]]
+}
+
+-- Cookie acceptance policy
+-- Acceptable values: "always", "never", "no_third_party"
+soup.accept_policy = "no_third_party"
+
+-- Cookie storage location
+-- Remove this line or set to nil to disable cookie persistence
+soup.cookies_storage = luakit.data_dir .. "/cookies.db"
+
+return globals
+
+-- vim: et:sw=4:ts=8:sts=4:tw=80
diff --git a/config.dot/luakit.link/rc.lua b/config.dot/luakit.link/rc.lua
index b04d775..01240a1 100644
--- a/config.dot/luakit.link/rc.lua
+++ b/config.dot/luakit.link/rc.lua
@@ -1,21 +1,35 @@
-------------------------------------------------------------------------------
--- luakit configuration file, more information at https://luakit.github.io/ --
-------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+-- luakit configuration file, more information at http://luakit.org/ --
+----------------------------------------------------------------------------------------
require "lfs"
--- This might cause confusing behaviors with used along with session management
--- and one want to be able to run simultaneous instances with different sessions
---require "unique_instance"
+--if unique then
+-- unique.new("org.luakit")
+-- -- Check for a running luakit instance
+-- if unique.is_running() then
+-- if uris[1] then
+-- for _, uri in ipairs(uris) do
+-- if lfs.attributes(uri) then uri = os.abspath(uri) end
+-- unique.send_message("tabopen " .. uri)
+-- end
+-- else
+-- unique.send_message("winopen")
+-- end
+-- luakit.quit()
+-- end
+--end
-- Set the number of web processes to use. A value of 0 means 'no limit'.
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 global config
+-- ("$XDG_CONFIG_HOME/luakit/globals.lua" or "/etc/xdg/luakit/globals.lua")
+local globals = require "globals"
+
-- Load users theme
-- ("$XDG_CONFIG_HOME/luakit/theme.lua" or "/etc/xdg/luakit/theme.lua")
lousy.theme.init(lousy.util.find_config("theme.lua"))
@@ -52,9 +66,6 @@ end)
local modes = require "modes"
local binds = require "binds"
-local settings = require "settings"
-require "settings_chrome"
-
----------------------------------
-- Optional user script loading --
----------------------------------
@@ -120,7 +131,7 @@ local history = require "history"
local history_chrome = require "history_chrome"
local help_chrome = require "help_chrome"
-local binds_chrome = require "binds_chrome"
+local introspector_chrome = require "introspector_chrome"
-- Add command completion
local completion = require "completion"
@@ -131,8 +142,8 @@ local open_editor = require "open_editor"
-- NoScript plugin, toggle scripts and or plugins on a per-domain basis.
-- `,ts` to toggle scripts, `,tp` to toggle plugins, `,tr` to reset.
--- If you use this module, don't use any site-specific `enable_scripts` or
--- `enable_plugins` settings, as these will conflict.
+-- Remove all "enable_scripts" & "enable_plugins" lines from your
+-- domain_props table (in config/globals.lua) as this module will conflict.
--require "noscript"
local follow_selected = require "follow_selected"
@@ -151,6 +162,9 @@ local styles = require "styles"
-- Hide scrollbars on all pages
local hide_scrollbars = require "hide_scrollbars"
+-- Automatically apply per-domain webview properties
+local domain_props = require "domain_props"
+
-- Add a stylesheet when showing images
local image_css = require "image_css"
@@ -163,15 +177,13 @@ local tab_favicons = require "tab_favicons"
-- Add :view-source command
local view_source = require "view_source"
--- Load sessions
+-- Sessions
+local session = require "session"
local sessions = require "sessions"
--- Custom keys
+-- Keys
local keys = require "keys"
--- Globals
-local userprefs = require "userprefs"
-
-----------------------------
-- End user script loading --
-----------------------------
@@ -188,4 +200,22 @@ local userprefs = require "userprefs"
--end
window.new(uris)
+-------------------------------------------
+-- Open URIs from other luakit instances --
+-------------------------------------------
+
+--if unique then
+-- unique.add_signal("message", function (msg, screen)
+-- local cmd, arg = string.match(msg, "^(%S+)%s*(.*)")
+-- local ww = lousy.util.table.values(window.bywidget)[1]
+-- if cmd == "tabopen" then
+-- ww:new_tab(arg)
+-- elseif cmd == "winopen" then
+-- ww = window.new((arg ~= "") and { arg } or {})
+-- end
+-- ww.win.screen = screen
+-- ww.win.urgency_hint = true
+-- end)
+--end
+
-- vim: et:sw=4:ts=8:sts=4:tw=80
diff --git a/config.dot/luakit.link/userprefs.lua b/config.dot/luakit.link/userprefs.lua
deleted file mode 100644
index 35c865c..0000000
--- a/config.dot/luakit.link/userprefs.lua
+++ /dev/null
@@ -1,19 +0,0 @@
--- User preferences
--- See https://github.com/luakit/luakit/issues/468
-
--- Requirements
-local settings = require "settings"
-
--- Homepage
-settings.window.home_page = "luakit://newtab"
-
--- Search engines
-settings.window.search_engines.imdb = "http://imdb.com/find?s=all&q=%s"
-settings.window.search_engines.google = "https://google.com/search?q=%s"
-settings.window.search_engines.duck = "https://duckduckgo.com/?q=%s&t=debian"
-settings.window.search_engines.wikipedia = "https://en.wikipedia.org/wiki/Special:Search?search=%s"
-settings.window.search_engines.debbugs = "https://bugs.debian.org/%s"
-settings.window.search_engines.sourceforge = "https://sf.net/search/?words=%s"
-settings.window.search_engines.debian = "https://packages.debian.org/search?keywords=%s"
-settings.window.search_engines.searx = "https://searx.org/?q=%s"
-settings.window.search_engines.default = settings.window.search_engines.duck
diff --git a/config.dot/luakit.link/webview.lua b/config.dot/luakit.link/webview.lua
new file mode 100644
index 0000000..e3fbfe6
--- /dev/null
+++ b/config.dot/luakit.link/webview.lua
@@ -0,0 +1,360 @@
+--------------------------
+-- WebKit WebView class --
+--------------------------
+
+local window = require("window")
+local lousy = require("lousy")
+local globals = require("globals")
+
+-- Webview class table
+local webview = {}
+
+lousy.signal.setup(webview, 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 = {
+ -- Set useragent
+ set_useragent = function (view)
+ view.user_agent = globals.useragent
+ end,
+
+ -- Update window and tab titles
+ title_update = function (view)
+ view:add_signal("property::title", function (v)
+ local w = webview.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 = webview.window(v)
+ if not w.mode.passthrough then
+ w:set_mode("insert")
+ end
+ end)
+ view:add_signal("root-active", function (v)
+ local w = webview.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 = webview.window(v)
+ if w:hit(mods, button, { context = context }) 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 = webview.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)
+ return webview.window(v):new_tab(nil, { private = v.private })
+ 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,
+}
+
+-- 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.
+webview.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 globals.zoom_step or 0.1
+ view.zoom_level = view.zoom_level + step
+ end,
+
+ zoom_out = function (view, _, step)
+ step = step or globals.zoom_step or 0.1
+ 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,
+}
+
+function webview.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
+
+function webview.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
+ webview.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)
+
+function webview.window(view)
+ assert(type(view) == "widget" and view.type == "webview")
+ return window.ancestor(view)
+end
+
+function webview.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
+ webview.set_location(view, queued)
+ end
+end
+
+function webview.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))
+ 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 = webview.methods[k]
+ if not func then return end
+ local view = w.view
+ if view then
+ return function (_, ...) return func(view, w, ...) end
+ end
+end)
+
+return webview
+
+-- vim: et:sw=4:ts=8:sts=4:tw=80
diff --git a/config.dot/luakit.link/webview_wm.lua b/config.dot/luakit.link/webview_wm.lua
new file mode 100644
index 0000000..0d7b5dd
--- /dev/null
+++ b/config.dot/luakit.link/webview_wm.lua
@@ -0,0 +1,21 @@
+local ui = ipc_channel("webview_wm")
+
+ui:add_signal("load-finished", function(_, page)
+ 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
diff --git a/config.dot/luakit.link/window.lua b/config.dot/luakit.link/window.lua
new file mode 100644
index 0000000..ec50c12
--- /dev/null
+++ b/config.dot/luakit.link/window.lua
@@ -0,0 +1,789 @@
+------------------
+-- Window class --
+------------------
+
+require "lfs"
+local lousy = require("lousy")
+local globals = require("globals")
+local search_engines = globals.search_engines
+local theme = lousy.theme.get()
+
+-- Window class table
+local window = {}
+
+lousy.signal.setup(window, true)
+
+-- List of active windows by window widget
+window.bywidget = 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
+
+-- Build and pack window widgets
+function window.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(),
+
+ -- Input bar widgets
+ ibar = {
+ layout = hbox(),
+ ebox = eventbox(),
+ prompt = label(),
+ input = entry(),
+ prompt_text = "",
+ input_text = "",
+ },
+ bar_layout = vbox(),
+ }
+
+ -- 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 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;"
+
+ 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
+ window.bywidget[w.win] = w
+end
+
+-- Table of functions to call on window creation. Normally used to add signal
+-- handlers to the new windows widgets.
+local init_funcs = {
+ -- Attach notebook widget signals
+ notebook_signals = function (w)
+ w.tabs:add_signal("switch-page", function ()
+ 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)
+ end,
+
+ 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)
+ -- 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, i = w.sbar, 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({
+ [i.prompt] = theme.prompt_ibar_font,
+ [i.input] = theme.input_ibar_font,
+ }) do wi.font = v end
+ end,
+
+ set_default_size = function (w)
+ local size = globals.default_window_size or "800x600"
+ 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("/usr/share/pixmaps/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.sbar.layout.visible = not win.fullscreen
+ 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.
+window.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,
+
+ -- insert a string into the command line at the current cursor position
+ insert_cmd = function (w, str)
+ 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,
+
+ -- Emulates pressing the Return key in input field
+ activate = function (w)
+ w.ibar.input:emit_signal("activate")
+ end,
+
+ del_word = function (w)
+ local i = w.ibar.input
+ local text = i.text
+ local pos = i.position
+ if text and #text > 1 and pos > 1 then
+ local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
+ if not string.find(left, "%s") then
+ left = ""
+ elseif string.find(left, "%w+%s*$") then
+ left = string.sub(left, 0, string.find(left, "%w+%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 = #left + 1
+ end
+ end,
+
+ del_line = function (w)
+ local i = w.ibar.input
+ if not string.match(i.text, "^[:/?]$") then
+ i.text = string.sub(i.text, 1, 1)
+ i.position = -1
+ end
+ end,
+
+ del_backward_char = 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,
+
+ del_forward_char = 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,
+
+ beg_line = function (w)
+ local i = w.ibar.input
+ i.position = 1
+ end,
+
+ end_line = function (w)
+ local i = w.ibar.input
+ i.position = -1
+ end,
+
+ forward_char = function (w)
+ local i = w.ibar.input
+ i.position = i.position + 1
+ end,
+
+ backward_char = function (w)
+ local i = w.ibar.input
+ local pos = i.position
+ if pos > 1 then
+ i.position = pos - 1
+ end
+ end,
+
+ forward_word = function (w)
+ local i = w.ibar.input
+ local text = i.text
+ local pos = i.position
+ if text and #text > 1 then
+ local right = string.sub(text, pos+1)
+ if string.find(right, "%w+") then
+ local _, move = string.find(right, "%w+")
+ i.position = pos + move
+ end
+ end
+ end,
+
+ backward_word = function (w)
+ local i = w.ibar.input
+ local text = i.text
+ local pos = i.position
+ if text and #text > 1 and pos > 1 then
+ local left = string.reverse(string.sub(text, 2, pos))
+ if string.find(left, "%w+") then
+ local _, move = string.find(left, "%w+")
+ i.position = pos - move
+ end
+ end
+ 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 w.ibar.prompt_text or w.ibar.input_text then
+ w.ibar.ebox:show()
+ w.sbar.ebox:hide()
+ else
+ w.ibar.ebox:hide()
+ if not w.win.fullscreen then
+ w.sbar.ebox:show()
+ end
+ end
+ end,
+
+ -- Set and display the prompt
+ set_prompt = function (w, text, opts)
+ local input, prompt, layout = w.ibar.input, w.ibar.prompt, w.ibar.layout
+ opts = opts or {}
+ prompt:hide()
+ -- 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 prompt.fg ~= fg then prompt.fg = fg end
+ if layout.bg ~= bg then layout.bg = bg end
+ -- Set text or remain hidden
+ if text then
+ prompt.text = lousy.util.escape(text)
+ prompt:show()
+ end
+ w.ibar.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 {}
+ input:hide()
+ -- 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:show()
+ input:focus()
+ input.position = opts.pos or -1
+ end
+ w.ibar.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 = globals.max_title_len or 80
+ if #title > max then title = string.sub(title, 1, 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)
+ 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)
+ else
+ -- Make new webview widget
+ view = webview.new({ private = opts.private })
+ end
+
+ w:attach_tab(view, switch, order)
+ if arg and not (type(arg) == "widget" and arg.type == "webview") then
+ webview.set_location(view, arg)
+ end
+
+ return view
+ end,
+
+ -- close the current tab
+ close_tab = function (w, view, blank_last)
+ 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)
+ 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
+ local pos = w.tabs:insert((order and order(w, view)) or -1, view)
+ if switch ~= false then w.tabs:switch(pos) end
+ end,
+
+ detach_tab = function (w, view, blank_last)
+ view = view or w.view
+ view.parent:remove(view)
+ -- 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("luakit://newtab/", 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 not force and (#luakit.windows == 1) and not w:can_quit() then
+ return false
+ end
+
+ 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
+ window.bywidget[w.win] = 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)
+ view = view or w.view
+ if not view then return w:new_tab(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 match, find = string.match, string.find
+
+ -- Detect blank uris
+ if not arg or match(arg, "^%s*$") then return "luakit://newtab/" end
+
+ -- Strip whitespace and split by whitespace into args table
+ local args = lstring.split(lstring.strip(arg))
+
+ -- Guess if single argument is an address, file, etc
+ if #args == 1 and not search_engines[args[1]] then
+ local uri = args[1]
+ if uri == "about:blank" then return uri end
+
+ -- Navigate if . or / in uri (I.e. domains, IP's, scheme://)
+ if find(uri, "%.") or find(uri, "/") then return uri end
+
+ -- Navigate if this is a javascript-uri
+ if find(uri, "^javascript:") then return uri end
+
+ -- Valid hostnames to check
+ local hosts = { "localhost" }
+ if globals.load_etc_hosts ~= false then
+ hosts = lousy.util.get_etc_hosts()
+ end
+
+ -- Check hostnames
+ for _, h in pairs(hosts) do
+ if h == uri or match(uri, "^"..h..":%d+$") then return uri end
+ end
+
+ -- Check for file in filesystem
+ if globals.check_filepath ~= false then
+ if lfs.attributes(uri) then return "file://" .. uri end
+ end
+ end
+
+ -- Find search engine (or use search_engines.default)
+ local engine = "default"
+ if args[1] and search_engines[args[1]] then
+ engine = args[1]
+ table.remove(args, 1)
+ end
+ local e = search_engines[engine] or "%s"
+
+ -- URI encode search terms
+ local terms = table.concat(args, " ")
+ return type(e) == "string" and string.format(e, luakit.uri_encode(terms)) or e(terms)
+ 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.
+window.indexes = {
+ -- Find function in window.methods first
+ function (_, k) return window.methods[k] end
+}
+
+window.add_signal("build", window.build)
+
+-- Create new window
+function window.new(args)
+ local w = {}
+ window.emit_signal("build", w)
+
+ -- Set window metatable
+ setmetatable(w, {
+ __index = function (_, k)
+ -- Call each window index function
+ for _, index in ipairs(window.indexes) do
+ local v = index(w, k)
+ if v then return v end
+ end
+ end,
+ })
+
+ -- Setup window widget for signals
+ lousy.signal.setup(w)
+
+ -- Call window init functions
+ for _, func in pairs(init_funcs) do
+ func(w)
+ end
+ window.emit_signal("init", w)
+
+ -- Populate notebook with tabs
+ for _, arg in ipairs(args or {}) do
+ if type(arg) == "string" then
+ arg = w:search_open(arg)
+ end
+ w:new_tab(arg, false)
+ 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(w:search_open(globals.homepage), false)
+ end
+
+ return w
+end
+
+function window.ancestor(w)
+ repeat
+ w = w.parent
+ until w == nil or w.type == "window"
+ return w and window.bywidget[w] or nil
+end
+
+return window
+
+-- vim: et:sw=4:ts=8:sts=4:tw=80