# HG changeset patch # User Matthew Wild # Date 1283309610 -3600 # Node ID 98e4b0c9fcac0a8c4fceebca48a5f18449a47249 Initial commit of default luakit config from 2010.08.30 diff -r 000000000000 -r 98e4b0c9fcac binds.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/binds.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,221 @@ +----------------- +-- Keybindings -- +----------------- + +binds = {} + +-- Binding aliases +local key, buf, but, cmd = lousy.bind.key, lousy.bind.buf, lousy.bind.but, lousy.bind.cmd + +-- Globals or defaults that are used in binds +local scroll_step = globals.scroll_step or 20 +local zoom_step = globals.zoom_step or 0.1 +local homepage = globals.homepage or "http://luakit.org" + +-- Add key bindings to be used across all windows in the given modes. +binds.mode_binds = { + -- buf(Pattern, function (w, buffer, opts) .. end, opts), + -- key({Modifiers}, Key name, function (w, opts) .. end, opts), + -- but({Modifiers}, Button num, function (w, opts) .. end, opts), + all = { + key({}, "Escape", function (w) w:set_mode() end), + key({"Control"}, "[", function (w) w:set_mode() end), + + but({}, 8, function (w) w:back() end), + but({}, 9, function (w) w:forward() end), + }, + normal = { + key({}, "i", function (w) w:set_mode("insert") end), + key({}, ":", function (w) w:set_mode("command") end), + + -- Scrolling + key({}, "j", function (w) w:scroll_vert("+"..scroll_step.."px") end), + key({}, "k", function (w) w:scroll_vert("-"..scroll_step.."px") end), + key({}, "h", function (w) w:scroll_horiz("-"..scroll_step.."px") end), + key({}, "l", function (w) w:scroll_horiz("+"..scroll_step.."px") end), + key({"Control"}, "d", function (w) w:scroll_page(0.5) end), + key({"Control"}, "u", function (w) w:scroll_page(-0.5) end), + key({"Control"}, "f", function (w) w:scroll_page(1.0) end), + key({"Control"}, "b", function (w) w:scroll_page(-1.0) end), + buf("^gg$", function (w) w:scroll_vert("0%") end), + buf("^G$", function (w) w:scroll_vert("100%") end), + buf("^[\-\+]?[0-9]+[%%G]$", function (w, b) w:scroll_vert(string.match(b, "^([\-\+]?%d+)[%%G]$") .. "%") end), + + -- Traditional scrolling commands + key({}, "Down", function (w) w:scroll_vert("+"..scroll_step.."px") end), + key({}, "Up", function (w) w:scroll_vert("-"..scroll_step.."px") end), + key({}, "Left", function (w) w:scroll_horiz("-"..scroll_step.."px") end), + key({}, "Right", function (w) w:scroll_horiz("+"..scroll_step.."px") end), + key({}, "Page_Down", function (w) w:scroll_page(1.0) end), + key({}, "Page_Up", function (w) w:scroll_page(-1.0) end), + key({}, "Home", function (w) w:scroll_vert("0%") end), + key({}, "End", function (w) w:scroll_vert("100%") end), + + -- Zooming + buf("^z0$", function (w) w:zoom_reset() end), + buf("^zI$", function (w) w:zoom_in(zoom_step) end), + buf("^zO$", function (w) w:zoom_out(zoom_step) end), + key({"Control"}, "+", function (w) w:zoom_in(zoom_step) end), + key({"Control"}, "-", function (w) w:zoom_out(zoom_step) end), + + -- Clipboard + key({}, "p", function (w) w:navigate(luakit.get_selection()) end), + key({}, "P", function (w) w:new_tab(luakit.get_selection()) end), + buf("^yy$", function (w) luakit.set_selection((w:get_current() or {}).uri or "") end), + buf("^yt$", function (w) luakit.set_selection(w.win.title) end), + + -- Commands + buf("^o$", function (w, c) w:enter_cmd(":open ") end), + buf("^t$", function (w, c) w:enter_cmd(":tabopen ") end), + buf("^w$", function (w, c) w:enter_cmd(":winopen ") end), + buf("^O$", function (w, c) w:enter_cmd(":open " .. ((w:get_current() or {}).uri or "")) end), + buf("^T$", function (w, c) w:enter_cmd(":tabopen " .. ((w:get_current() or {}).uri or "")) end), + buf("^W$", function (w, c) w:enter_cmd(":winopen " .. ((w:get_current() or {}).uri or "")) end), + buf("^,g$", function (w, c) w:enter_cmd(":websearch google ") end), + + -- Debian search shorcut access + buf("^\\dbug$", function (w, c) w:enter_cmd(":websearch debbugs ") end), + buf("^\\dpts$", function (w, c) w:enter_cmd(":websearch dpts ") end), + buf("^\\dpkg$", function (w, c) w:enter_cmd(":websearch dpkg ") end), + + -- Searching + key({}, "/", function (w) w:start_search("/") end), + key({}, "?", function (w) w:start_search("?") end), + key({}, "n", function (w) w:search(nil, true) end), + key({}, "N", function (w) w:search(nil, false) end), + + -- History + buf("^[0-9]*H$", function (w, b) w:back (tonumber(string.match(b, "^(%d*)H$") or 1)) end), + buf("^[0-9]*L$", function (w, b) w:forward(tonumber(string.match(b, "^(%d*)L$") or 1)) end), + key({}, "b", function (w) w:back() end), + key({}, "XF86Back", function (w) w:back() end), + key({}, "XF86Forward", function (w) w:forward() end), + + -- Tab + key({"Control"}, "Page_Up", function (w) w:prev_tab() end), + key({"Control"}, "Page_Down", function (w) w:next_tab() end), + buf("^[0-9]*gT$", function (w, b) w:prev_tab(tonumber(string.match(b, "^(%d*)gT$") or 1)) end), + buf("^[0-9]*gt$", function (w, b) w:next_tab(tonumber(string.match(b, "^(%d*)gt$") or 1)) end), + buf("^gH$", function (w) w:new_tab(homepage) end), + buf("^d$", function (w) w:close_tab() end), + + key({}, "r", function (w) w:reload() end), + buf("^gh$", function (w) w:navigate(homepage) end), + + -- Window + buf("^ZZ$", function (w) w:close_win() end), + buf("^D$", function (w) w:close_win() end), + + -- Link following + key({}, "f", function (w) w:set_mode("follow") end), + + -- Bookmarking + key({}, "B", function (w) w:enter_cmd(":bookmark " .. ((w:get_current() or {}).uri or "http://") .. " ") end), + buf("^gb$", function (w) w:navigate(bookmarks.dump_html()) end), + buf("^gB$", function (w) w:new_tab (bookmarks.dump_html()) end), + + -- Mouse bindings + but({}, 2, function (w) + -- Open hovered uri in new tab + local uri = w:get_current().hovered_uri + if uri then w:new_tab(uri) + else -- Open selection in current tab + uri = luakit.get_selection() + if uri then w:get_current().uri = uri end + end + end), + }, + command = { + key({"Shift"}, "Insert", function (w) w:insert_cmd(luakit.get_selection()) end), + key({}, "Up", function (w) w:cmd_hist_prev() end), + key({}, "Down", function (w) w:cmd_hist_next() end), + key({}, "Tab", function (w) w:cmd_completion() end), + key({"Control"}, "w", function (w) w:del_word() end), + key({"Control"}, "u", function (w) w:del_line() end), + }, + search = { + key({}, "Up", function (w) w:srch_hist_prev() end), + key({}, "Down", function (w) w:srch_hist_next() end), + }, + insert = { }, +} + +-- Command bindings which are matched in the "command" mode from text +-- entered into the input bar. +binds.commands = { + -- cmd({Command, Alias1, ...}, function (w, arg, opts) .. end, opts), + cmd({"open", "o" }, function (w, a) w:navigate(a) end), + cmd({"tabopen", "t" }, function (w, a) w:new_tab(a) end), + cmd({"winopen", "w" }, function (w, a) window.new{a} end), + cmd({"back" }, function (w, a) w:back(tonumber(a) or 1) end), + cmd({"forward", "f" }, function (w, a) w:forward(tonumber(a) or 1) end), + cmd({"scroll" }, function (w, a) w:scroll_vert(a) end), + cmd({"quit", "q" }, function (w) luakit.quit() end), + cmd({"close", "c" }, function (w) w:close_tab() end), + cmd({"websearch", "ws" }, function (w, e, s) w:websearch(e, s) end), + cmd({"reload", }, function (w) w:reload() end), + cmd({"viewsource", "vs" }, function (w) w:toggle_source(true) end), + cmd({"viewsource!", "vs!"}, function (w) w:toggle_source() end), + cmd({"bookmark", "bm" }, function (w, a) + local args = lousy.util.string.split(a) + local uri = table.remove(args, 1) + bookmarks.add(uri, args) + end), +} + +-- Helper functions which are added to the window struct +binds.helper_methods = { + -- Navigate current view or open new tab + navigate = function (w, uri, view) + if not view then view = w:get_current() end + if view then + view.uri = uri + else + return w:new_tab(uri) + end + end, + + -- search engine wrapper + websearch = function (w, args) + local sep = string.find(args, " ") + local engine = string.sub(args, 1, sep-1) + local search = string.sub(args, sep+1) + search = string.gsub(search, "^%s*(.-)%s*$", "%1") + if not search_engines[engine] then + return error("No matching search engine found: " .. engine) + end + local uri = string.gsub(search_engines[engine], "{%d}", search) + return w:navigate(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) + w.tabs:switch(n) + 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:get_current():emit_signal("form-active") + elseif s == "root-active" then + w:get_current():emit_signal("root-active") + end + end, +} + +-- Insert webview method lookup on window structure +table.insert(window.indexes, 1, function (w, k) + -- Lookup bind helper method + return binds.helper_methods[k] +end) + +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff -r 000000000000 -r 98e4b0c9fcac globals.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/globals.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,68 @@ +-- Global variables for luakit +globals = { + homepage = "http://luakit.org/", + -- homepage = "http://github.com/mason-larobina/luakit", + scroll_step = 20, + zoom_step = 0.1, + max_cmd_history = 100, + max_srch_history = 100, + -- http_proxy = "http://example.com:3128", + download_dir = luakit.get_special_dir("DOWNLOAD") or (os.getenv("HOME") .. "/downloads"), +} + +-- Make useragent +local rv, out, err = luakit.spawn_sync("uname -sm") +local webkit_version = string.format("WebKitGTK+/%d.%d.%d", luakit.webkit_major_version, + luakit.webkit_minor_version, luakit.webkit_micro_version) +local luakit_version = string.format("luakit/%s", luakit.version) +globals.useragent = string.format("Mozilla/5.0 (%s) %s %s", string.match(out, "([^\n]*)"), webkit_version, luakit_version) + +-- Search common locations for a ca file which is used for ssl connection validation. +local ca_files = {luakit.data_dir .. "/ca-certificates.crt", + "/etc/certs/ca-certificates.crt", "/etc/ssl/certs/ca-certificates.crt",} +for _, ca_file in ipairs(ca_files) do + if os.exists(ca_file) then + globals.ca_file = ca_file + break + end +end + +-- Change to stop navigation sites with invalid or expired ssl certificates +globals.ssl_strict = false + +-- Search engines +search_engines = { + luakit = "http://luakit.org/search/index/luakit?q={0}", + google = "http://google.com/search?q={0}", + wikipedia = "http://en.wikipedia.org/wiki/Special:Search?search={0}", + debbugs = "http://bugs.debian.org/{0}", + imdb = "http://imdb.com/find?s=all&q={0}", + sourceforge = "http://sf.net/search/?words={0}", +} + +-- Fake the cookie policy enum here +cookie_policy = { always = 0, never = 1, no_third_party = 2 } + +-- Per-domain webview properties +domain_props = { --[[ + ["all"] = { + ["enable-scripts"] = false, + ["enable-plugins"] = false, + ["enable-private-browsing"] = false, + ["user-stylesheet-uri"] = "", + ["accept-policy"] = cookie_policy.never, + }, + ["youtube.com"] = { + ["enable-scripts"] = true, + ["enable-plugins"] = true, + }, + ["lwn.net"] = { + ["accept-policy"] = cookie_policy.no_third_party, + }, + ["forums.archlinux.org"] = { + ["user-stylesheet-uri"] = luakit.data_dir .. "/styles/dark.css", + ["enable-private-browsing"] = true, + }, ]] +} + +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff -r 000000000000 -r 98e4b0c9fcac modes.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modes.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,162 @@ +------------------------------- +-- luakit mode configuration -- +------------------------------- + +-- Table of modes and their callback hooks +modes = {} + +-- Currently active mode hooks +local current + +-- Update a modes hook table with new hooks +function new_mode(mode, hooks) + modes[mode] = lousy.util.table.join(modes[mode] or {}, hooks) +end + +-- Attach window & input bar signals for mode hooks +window.init_funcs.modes_setup = function (w) + -- Calls the `enter` and `leave` mode hooks. + w.win:add_signal("mode-changed", function (_, mode) + -- Call the last modes `leave` hook. + if current and current.leave then + current.leave(w) + end + + -- Update window binds + w:update_binds(mode) + + -- Get new modes functions + current = modes[mode] + if not current then + error("changed to un-handled mode: " .. mode) + end + + -- Call new modes `enter` hook. + if current.enter then current.enter(w) end + end) + + -- Calls the `changed` hook on input widget changed. + w.ibar.input:add_signal("changed", function() + local text = w.ibar.input.text + if current and current.changed then + current.changed(w, text) + end + end) + + -- Calls the `activate` hook on input widget activate. + w.ibar.input:add_signal("activate", function() + local text = w.ibar.input.text + if current and current.activate then + current.activate(w, text) + end + end) +end + +-- Add mode related window methods +for name, func in pairs({ + set_mode = function (w, name) lousy.mode.set(w.win, name) end, + get_mode = function (w) return lousy.mode.get(w.win) end, + is_mode = function (w, name) return name == w:get_mode() end, +}) do window.methods[name] = func end + +-- Setup normal mode +new_mode("normal", { + enter = function (w) + local i, p = w.ibar.input, w.ibar.prompt + i:hide() + p:hide() + end, +}) + +-- Setup insert mode +new_mode("insert", { + enter = function (w) + local i, p = w.ibar.input, w.ibar.prompt + i:hide() + i.text = "" + p.text = "-- INSERT --" + p:show() + end, +}) + +-- Setup command mode +new_mode("command", { + enter = function (w) + local i, p = w.ibar.input, w.ibar.prompt + p:hide() + i.text = ":" + i:show() + i:focus() + i:set_position(-1) + 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:cmd_hist_add(text) + w:match_cmd(string.sub(text, 2)) + w:set_mode() + end, +}) + +-- Setup search mode +new_mode("search", { + enter = function (w) + -- Clear old search state + w.search_state = {} + local i, p = w.ibar.input, w.ibar.prompt + p:hide() + p.text = "" + i.text = "/" + i:show() + end, + leave = function (w) + -- Check if search was aborted and return to original position + local s = w.search_state + if s.marker then + w:get_current():set_scroll_vert(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 + w:search(string.sub(text, 2), (string.sub(text, 1, 1) == "/")) + else + w:clear_search() + w:set_mode() + end + end, + activate = function (w, text) + w.search_state.marker = nil + w:srch_hist_add(text) + w:set_mode() + -- Ghost the search term in the prompt + local p = w.ibar.prompt + p.text = lousy.util.escape(text) + p:show() + end, +}) + +-- Setup follow mode +new_mode("follow", { + enter = function (w) + local i, p = w.ibar.input, w.ibar.prompt + w:eval_js_from_file(lousy.util.find_data("scripts/follow.js")) + w:eval_js("clear(); show_hints();") + p.text = "Follow:" + p:show() + i.text = "" + i:show() + i:focus() + i:set_position(-1) + end, + leave = function (w) + if w.eval_js then w:eval_js("clear();") end + end, + changed = function (w, text) + local ret = w:eval_js(string.format("update(%q);", text)) + w:emit_form_root_active_signal(ret) + end, +}) diff -r 000000000000 -r 98e4b0c9fcac rc.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rc.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,43 @@ +-- Luakit configuration file, more information at http://luakit.org/ + +-- Load library of useful functions for luakit +require "lousy" + +-- Small util function to print output only when luakit.verbose is true +function info(...) if luakit.verbose then print(string.format(...)) end end + +-- Load users global config +-- ("$XDG_CONFIG_HOME/luakit/globals.lua" or "/etc/xdg/luakit/globals.lua") +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")) + +-- Load users window class +-- ("$XDG_CONFIG_HOME/luakit/window.lua" or "/etc/xdg/luakit/window.lua") +require "window" + +-- Load users mode configuration +-- ("$XDG_CONFIG_HOME/luakit/modes.lua" or "/etc/xdg/luakit/modes.lua") +require "modes" + +-- Load users webview class +-- ("$XDG_CONFIG_HOME/luakit/webview.lua" or "/etc/xdg/luakit/webview.lua") +require "webview" + +-- Load users keybindings +-- ("$XDG_CONFIG_HOME/luakit/binds.lua" or "/etc/xdg/luakit/binds.lua") +require "binds" + +-- Init formfiller lib +require "formfiller" + +-- Init bookmarks lib +require "bookmarks" +bookmarks.load() +bookmarks.dump_html() + +window.new(uris) + +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff -r 000000000000 -r 98e4b0c9fcac theme.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/theme.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,32 @@ +-------------------------- +-- Default luakit theme -- +-------------------------- + +theme = {} + +-- Default settings +theme.font = "monospace normal 9" +theme.fg = "#fff" +theme.bg = "#000" + +-- Statusbar specific +theme.sbar_fg = "#fff" +theme.sbar_bg = "#000" +theme.loaded_sbar_fg = "#33AADD" + +-- Input bar specific +theme.ibar_fg = "#000" +theme.ibar_bg = "#fff" + +-- Tab label +theme.tab_fg = "#888" +theme.tab_bg = "#222" +theme.selected_fg = "#fff" +theme.selected_bg = "#000" + +-- Trusted/untrusted ssl colours +theme.trust_fg = "#0F0" +theme.notrust_fg = "#F00" + +return theme +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff -r 000000000000 -r 98e4b0c9fcac webview.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webview.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,373 @@ +-------------------------- +-- WebKit WebView class -- +-------------------------- + +-- Webview class table +webview = {} + +-- Table of functions which are called on new webview widgets. +webview.init_funcs = { + -- Set global properties + set_global_props = function (view, w) + -- Set proxy options + local proxy = globals.http_proxy or os.getenv("http_proxy") + if proxy then view:set_prop('proxy-uri', proxy) end + view:set_prop('user-agent', globals.useragent) + + -- Set ssl options + if globals.ssl_strict ~= nil then + view:set_prop('ssl-strict', globals.ssl_strict) + end + if globals.ca_file and os.exists(globals.ca_file) then + view:set_prop('ssl-ca-file', globals.ca_file) + -- Warning: update the following variable if 'ssl-ca-file' is + -- changed anywhere else. + w.checking_ssl = true + end + end, + + -- Update window and tab titles + title_update = function (view, w) + view:add_signal("property::title", function (v) + w:update_tab_labels() + if w:is_current(v) then + w:update_win_title() + end + end) + end, + + -- Update uri label in statusbar + uri_update = function (view, w) + view:add_signal("property::uri", function (v) + w:update_tab_labels() + if w:is_current(v) then + w:update_uri(v) + end + end) + end, + + -- Update scroll widget + scroll_update = function (view, w) + view:add_signal("expose", function (v) + if w:is_current(v) then + w:update_scroll(v) + end + end) + end, + + -- Update progress widget + progress_update = function (view, w) + for _, sig in ipairs({"load-status", "property::progress"}) do + view:add_signal(sig, function (v) + if w:is_current(v) then + w:update_progress(v) + w:update_ssl(v) + end + end) + end + end, + + -- Display hovered link in statusbar + link_hover_display = function (view, w) + view:add_signal("link-hover", function (v, link) + if w:is_current(v) and link then + w.sbar.l.uri.text = "Link: " .. lousy.util.escape(link) + end + end) + view:add_signal("link-unhover", function (v) + if w:is_current(v) then + w:update_uri(v) + end + end) + end, + + -- Clicking a form field automatically enters insert mode + form_insert_mode = function (view, w) + view:add_signal("form-active", function () + (w.search_state or {}).marker = nil + w:set_mode("insert") + end) + view:add_signal("root-active", function () + (w.search_state or {}).marker = nil + w:set_mode() + end) + end, + + -- Stop key events hitting the webview if the user isn't in insert mode + mode_key_filter = function (view, w) + view:add_signal("key-press", function () + if not w:is_mode("insert") then return true 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, w) + -- Match button press + view:add_signal("button-release", function (v, mods, button) + (w.search_state or {}).marker = nil + if w:hit(mods, button) then return true end + end) + end, + + -- Reset the mode on navigation + mode_reset_on_nav = function (view, w) + view:add_signal("load-status", function (v, status) + if w:is_current(v) and status == "provisional" then w:set_mode() end + end) + end, + + -- Domain properties + domain_properties = function (view, w) + view:add_signal("load-status", function (v, status) + if status ~= "provisional" then return end + local domain = (v.uri and string.match(v.uri, "^%a+://([^/]*)/?")) or "about:blank" + if string.match(domain, "^www.") then domain = string.sub(domain, 5) end + local props = lousy.util.table.join(domain_props.all or {}, domain_props[domain] or {}) + for k, v in pairs(props) do + info("Domain prop: %s = %s (%s)", k, tostring(v), domain) + view:set_prop(k, v) + end + end) + end, + + -- Action to take on mime type decision request. + mime_decision = function (view, w) + -- Return true to accept or false to reject from this signal. + view:add_signal("mime-type-decision", function (v, link, mime) + info("Requested link: %s (%s)", link, 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) + -- 'link' contains the download link + -- 'reason' contains the reason of the request (i.e. "link-clicked") + -- return TRUE to handle the request by yourself or FALSE to proceed + -- with default behaviour + view:add_signal("new-window-decision", function (v, link, reason) + info("New window decision: %s (%s)", link, reason) + if reason == "link-clicked" then + window.new({ link }) + return true + end + w:new_tab(link) + end) + end, + + create_webview = function (view, w) + -- Return a newly created webview in a new tab + view:add_signal("create-web-view", function (v) + return w:new_tab() + end) + end, + + -- Action to take on download request. + download_request = function (view, w) + -- 'link' contains the download link + -- 'filename' contains the suggested filename (from server or webkit) + view:add_signal("download-request", function (v, link, filename) + if not filename then return end + -- Make download dir + os.execute(string.format("mkdir -p %q", globals.download_dir)) + local dl = globals.download_dir .. "/" .. filename + local wget = string.format("wget -q %q -O %q", link, dl) + info("Launching: %s", wget) + luakit.spawn(wget) + end) + end, + + -- Creates context menu popup from table (and nested tables). + -- Use `true` for menu separators. + populate_popup = function (view, w) + view:add_signal("populate-popup", function (v) + return { + true, + { "_Toggle Source", function () w:toggle_source() end }, + { "_Zoom", { + { "Zoom _In", function () w:zoom_in(globals.zoom_step) end }, + { "Zoom _Out", function () w:zoom_out(globals.zoom_step) end }, + true, + { "Zoom _Reset", function () w:zoom_reset() end }, }, }, + } + end) + end, + + -- Action to take on resource request. + resource_request_decision = function (view, w) + view:add_signal("resource-request-starting", function(v, uri) + if luakit.verbose then print("Requesting: "..uri) end + -- Return false to cancel the request. + 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 = function (view, w) + view:reload() + end, + + -- Property functions + get = function (view, w, k) + return view:get_prop(k) + end, + + set = function (view, w, k, v) + view:set_prop(k, v) + end, + + -- evaluate javascript code and return string result + eval_js = function (view, w, script, file) + return view:eval_js(script, file or "(inline)") + end, + + -- evaluate javascript code from file and return string result + eval_js_from_file = function (view, w, file) + local fh, err = io.open(file) + if not fh then return error(err) end + local script = fh:read("*a") + fh:close() + return view:eval_js(script, file) + end, + + -- close the current tab + close_tab = function (view, w) + w.tabs:remove(view) + view.uri = "about:blank" + view:destroy() + w:update_tab_count() + w:update_tab_labels() + end, + + -- Toggle source view + toggle_source = function (view, w, show) + if show == nil then show = not view:get_view_source() end + view:set_view_source(show) + end, + + -- Zoom functions + zoom_in = function (view, w, step) + view:set_prop("zoom-level", view:get_prop("zoom-level") + step) + end, + + zoom_out = function (view, w, step) + local value = view:get_prop("zoom-level") - step + view:set_prop("zoom-level", ((value > 0.01) and value) or 0.01) + end, + + zoom_reset = function (view, w) + view:set_prop("zoom-level", 1.0) + end, + + -- Searching functions + start_search = function (view, w, text) + if string.match(text, "^[\?\/]") then + w:set_mode("search") + local i = w.ibar.input + i.text = text + i:focus() + i:set_position(-1) + else + return error("invalid search term, must start with '?' or '/'") + end + end, + + search = function (view, w, text, forward) + if forward == nil then forward = true end + + -- Get search state (or new state) + if not w.search_state then w.search_state = {} end + local s = w.search_state + + -- Get search term + text = text or s.last_search + if not text or #text == 0 then + return w:clear_search() + end + s.last_search = text + + if s.forward == nil then + -- Haven't searched before, save some state. + s.forward = forward + s.marker = view:get_scroll_vert() + else + -- Invert direction if originally searching in reverse + forward = (s.forward == forward) + end + + view:search(text, false, forward, true); + end, + + clear_search = function (view, w) + view:clear_search() + w.search_state = {} + end, + + -- Webview scroll functions + scroll_vert = function (view, w, value) + local cur, max = view:get_scroll_vert() + if type(value) == "string" then + value = lousy.util.parse_scroll(cur, max, value) + end + view:set_scroll_vert(value) + end, + + scroll_horiz = function (view, w, value) + local cur, max = view:get_scroll_horiz() + if type(value) == "string" then + value = lousy.util.parse_scroll(cur, max, value) + end + view:set_scroll_horiz(value) + end, + + -- vertical scroll of a multiple of the view_size + scroll_page = function (view, w, value) + local cur, max, size = view:get_scroll_vert() + view:set_scroll_vert(cur + (size * value)) + end, + + -- History traversing functions + back = function (view, w, n) + view:go_back(n or 1) + end, + + forward = function (view, w, n) + view:go_forward(n or 1) + end, +} + +function webview.new(w, uri) + local view = widget{type = "webview"} + + -- Call webview init functions + for k, func in pairs(webview.init_funcs) do + func(view, w) + end + + if uri then view.uri = uri end + view.show_scrollbars = false + return view +end + +-- Insert webview method lookup on window structure +table.insert(window.indexes, 1, function (w, k) + -- Get current webview + local view = w.tabs:atindex(w.tabs:current()) + if not view then return end + -- Lookup webview method + local func = webview.methods[k] + if not func then return end + -- Return webview method wrapper function + return function (_, ...) return func(view, w, ...) end +end) + +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff -r 000000000000 -r 98e4b0c9fcac window.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/window.lua Wed Sep 01 03:53:30 2010 +0100 @@ -0,0 +1,638 @@ +------------------ +-- Window class -- +------------------ + +-- Window class table +window = {} + +-- 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 + +-- Build and pack window widgets +function window.build() + -- Create a table for widgets and state variables for a window + local w = { + win = widget{type="window"}, + ebox = eventbox(), + layout = vbox(), + tabs = notebook(), + -- Tab bar widgets + tbar = { + layout = hbox(), + ebox = eventbox(), + titles = { }, + }, + -- Status bar widgets + sbar = { + layout = hbox(), + ebox = eventbox(), + -- Left aligned widgets + l = { + layout = hbox(), + ebox = eventbox(), + uri = label(), + loaded = label(), + }, + -- Fills space between the left and right aligned widgets + sep = eventbox(), + -- Right aligned widgets + r = { + layout = hbox(), + ebox = eventbox(), + buf = label(), + ssl = label(), + tabi = label(), + scroll = label(), + }, + }, + -- Input bar widgets + ibar = { + layout = hbox(), + ebox = eventbox(), + prompt = label(), + input = entry(), + }, + } + + -- Assemble window + w.ebox:set_child(w.layout) + w.win:set_child(w.ebox) + + -- Pack tab bar + local t = w.tbar + t.ebox:set_child(t.layout, false, false, 0) + w.layout:pack_start(t.ebox, false, false, 0) + + -- Pack notebook + w.layout:pack_start(w.tabs, true, true, 0) + + -- Pack left-aligned statusbar elements + local l = w.sbar.l + l.layout:pack_start(l.uri, false, false, 0) + l.layout:pack_start(l.loaded, false, false, 0) + l.ebox:set_child(l.layout) + + -- Pack right-aligned statusbar elements + local r = w.sbar.r + r.layout:pack_start(r.buf, false, false, 0) + r.layout:pack_start(r.ssl, false, false, 0) + r.layout:pack_start(r.tabi, false, false, 0) + r.layout:pack_start(r.scroll, false, false, 0) + r.ebox:set_child(r.layout) + + -- Pack status bar elements + local s = w.sbar + s.layout:pack_start(l.ebox, false, false, 0) + s.layout:pack_start(s.sep, true, true, 0) + s.layout:pack_start(r.ebox, false, false, 0) + s.ebox:set_child(s.layout) + w.layout:pack_start(s.ebox, false, false, 0) + + -- Pack input bar + local i = w.ibar + i.layout:pack_start(i.prompt, false, false, 0) + i.layout:pack_start(i.input, true, true, 0) + i.ebox:set_child(i.layout) + w.layout:pack_start(i.ebox, false, false, 0) + + -- Other settings + i.input.show_frame = false + w.tabs.show_tabs = false + l.loaded:hide() + l.uri.selectable = true + r.ssl:hide() + + return w +end + +-- Table of functions to call on window creation. Normally used to add signal +-- handlers to the new windows widgets. +window.init_funcs = { + -- Attach notebook widget signals + notebook_signals = function (w) + w.tabs:add_signal("page-added", function (nbook, view, idx) + w:update_tab_count(idx) + w:update_tab_labels() + end) + w.tabs:add_signal("switch-page", function (nbook, view, idx) + w:set_mode() + w:update_tab_count(idx) + w:update_win_title(view) + w:update_uri(view) + w:update_progress(view) + w:update_tab_labels(idx) + w:update_buf() + w:update_ssl(view) + 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 + end) + end, + + key_press_match = function (w) + w.win:add_signal("key-press", function (_, mods, key) + -- Reset command line completion + if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then + w:update_uri() + w.compl_index = 0 + end + + if w:hit(mods, key) then + return true + end + end) + end, + + apply_window_theme = function (w) + local theme = lousy.theme.get() + local s, i = w.sbar, w.ibar + + -- Set foregrounds + for wi, v in pairs({ + [s.l.uri] = theme.uri_sbar_fg, + [s.l.loaded] = theme.loaded_sbar_fg, + [s.r.buf] = theme.buf_sbar_fg, + [s.r.tabi] = theme.tabi_sbar_fg, + [s.r.scroll] = theme.scroll_sbar_fg, + [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({ + [s.l.uri] = theme.uri_sbar_font, + [s.l.loaded] = theme.loaded_sbar_font, + [s.r.buf] = theme.buf_sbar_font, + [s.r.ssl] = theme.ssl_sbar_font, + [s.r.tabi] = theme.tabi_sbar_font, + [s.r.scroll] = theme.scroll_sbar_font, + [i.prompt] = theme.prompt_ibar_font, + [i.input] = theme.input_ibar_font, + }) do wi.font = v end + end, +} + +-- Helper functions which operate on the window widgets or structure. +window.methods = { + -- Return the widget in the currently active tab + get_current = function (w) return w.tabs:atindex(w.tabs:current()) end, + -- Check if given widget is the widget in the currently active tab + is_current = function (w, wi) return w.tabs:indexof(wi) == w.tabs:current() end, + + get_tab_title = function (w, view) + if not view then view = w:get_current() end + return view:get_prop("title") or view.uri or "(Untitled)" + end, + + new_tab = function (w, uri) + local view = webview.new(w, uri) + w.tabs:append(view) + w:update_tab_count() + return view + end, + + -- Wrapper around the bind plugin's hit method + hit = function (w, mods, key) + local caught, newbuf = lousy.bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w) + if w.win then + w.buffer = newbuf + w:update_buf() + end + return caught + end, + + -- Wrapper around the bind plugin's match_cmd method + match_cmd = function (w, buffer) + return lousy.bind.match_cmd(binds.commands, buffer, w) + end, + + -- enter command or characters into command line + enter_cmd = function (w, cmd) + local i = w.ibar.input + w:set_mode("command") + i.text = cmd + i:set_position(-1) + 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:get_position() + local left, right = string.sub(text, 1, pos), string.sub(text, pos+1) + i.text = left .. str .. right + i:set_position(pos + #str + 1) + end, + + -- Command line completion of available commands + cmd_completion = function (w) + local i = w.ibar.input + local s = w.sbar.l.uri + local cmpl = {} + + -- Get last completion (is reset on key press other than ) + if not w.compl_start or w.compl_index == 0 then + w.compl_start = "^" .. string.sub(i.text, 2) + w.compl_index = 1 + end + + -- Get suitable commands + for _, b in ipairs(binds.commands) do + for _, c in pairs(b.commands) do + if c and string.match(c, w.compl_start) then + table.insert(cmpl, c) + end + end + end + + table.sort(cmpl) + + if #cmpl > 0 then + local text = "" + for index, comp in pairs(cmpl) do + if index == w.compl_index then + i.text = ":" .. comp .. " " + i:set_position(-1) + end + if text ~= "" then + text = text .. " | " + end + text = text .. comp + end + + -- cycle through all possible completions + if w.compl_index == #cmpl then + w.compl_index = 1 + else + w.compl_index = w.compl_index + 1 + end + s.text = lousy.util.escape(text) + end + end, + + del_word = function (w) + local i = w.ibar.input + local text = i.text + local pos = i:get_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:set_position(#left + 2) + end + end, + + del_line = function (w) + local i = w.ibar.input + if i.text ~= ":" then + i.text = ":" + i:set_position(-1) + end + end, + + -- Search history adding + srch_hist_add = function (w, srch) + if not w.srch_hist then w.srch_hist = {} end + -- Check overflow + local max_hist = globals.max_srch_history or 100 + if #w.srch_hist > (max_hist + 5) then + while #w.srch_hist > max_hist do + table.remove(w.srch_hist, 1) + end + end + table.insert(w.srch_hist, srch) + end, + + -- Search history traversing + srch_hist_prev = function (w) + if not w.srch_hist then w.srch_hist = {} end + if not w.srch_hist_cursor then + w.srch_hist_cursor = #w.srch_hist + 1 + w.srch_hist_current = w.ibar.input.text + end + local c = w.srch_hist_cursor - 1 + if w.srch_hist[c] then + w.srch_hist_cursor = c + w.ibar.input.text = w.srch_hist[c] + w.ibar.input:set_position(-1) + end + end, + + srch_hist_next = function (w) + if not w.srch_hist then w.srch_hist = {} end + local c = (w.srch_hist_cursor or #w.srch_hist) + 1 + if w.srch_hist[c] then + w.srch_hist_cursor = c + w.ibar.input.text = w.srch_hist[c] + w.ibar.input:set_position(-1) + elseif w.srch_hist_current then + w.srch_hist_cursor = nil + w.ibar.input.text = w.srch_hist_current + w.ibar.input:set_position(-1) + end + end, + + -- Command history adding + cmd_hist_add = function (w, cmd) + if not w.cmd_hist then w.cmd_hist = {} end + -- Make sure history doesn't overflow + local max_hist = globals.max_cmd_hist or 100 + if #w.cmd_hist > (max_hist + 5) then + while #w.cmd_hist > max_hist do + table.remove(w.cmd_hist, 1) + end + end + table.insert(w.cmd_hist, cmd) + end, + + -- Command history traversing + cmd_hist_prev = function (w) + if not w.cmd_hist then w.cmd_hist = {} end + if not w.cmd_hist_cursor then + w.cmd_hist_cursor = #w.cmd_hist + 1 + w.cmd_hist_current = w.ibar.input.text + end + local c = w.cmd_hist_cursor - 1 + if w.cmd_hist[c] then + w.cmd_hist_cursor = c + w.ibar.input.text = w.cmd_hist[c] + w.ibar.input:set_position(-1) + end + end, + + cmd_hist_next = function (w) + if not w.cmd_hist then w.cmd_hist = {} end + local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1 + if w.cmd_hist[c] then + w.cmd_hist_cursor = c + w.ibar.input.text = w.cmd_hist[c] + w.ibar.input:set_position(-1) + elseif w.cmd_hist_current then + w.cmd_hist_cursor = nil + w.ibar.input.text = w.cmd_hist_current + w.ibar.input:set_position(-1) + end + end, + + -- GUI content update functions + update_tab_count = function (w, i, t) + w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count()) + end, + + update_win_title = function (w, view) + if not view then view = w:get_current() end + local title = view:get_prop("title") + local uri = view.uri + if not title and not uri then + w.win.title = "luakit" + else + w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank") + end + end, + + update_uri = function (w, view, uri) + if not view then view = w:get_current() end + w.sbar.l.uri.text = lousy.util.escape((uri or (view and view.uri) or "about:blank")) + end, + + update_progress = function (w, view, p) + if not view then view = w:get_current() end + if not p then p = view:get_prop("progress") end + local loaded = w.sbar.l.loaded + if not view:loading() or p == 1 then + loaded:hide() + else + loaded:show() + loaded.text = string.format("(%d%%)", p * 100) + end + end, + + update_scroll = function (w, view) + if not view then view = w:get_current() end + local scroll = w.sbar.r.scroll + if view then + local val, max = view:get_scroll_vert() + if max == 0 then val = "All" + elseif val == 0 then val = "Top" + elseif val == max then val = "Bot" + else val = string.format("%2d%%", (val/max) * 100) + end + if scroll.text ~= val then scroll.text = val end + scroll:show() + else + scroll:hide() + end + end, + + update_ssl = function (w, view) + if not view then view = w:get_current() end + local trusted = view:ssl_trusted() + local theme = lousy.theme.get() + local ssl = w.sbar.r.ssl + if trusted ~= nil and not w.checking_ssl then + ssl.fg = theme.notrust_fg + ssl.text = "(nocheck)" + ssl:show() + elseif trusted == true then + ssl.fg = theme.trust_fg + ssl.text = "(trust)" + ssl:show() + elseif trusted == false then + ssl.fg = theme.notrust_fg + ssl.text = "(notrust)" + ssl:show() + else + ssl:hide() + end + end, + + update_buf = function (w) + local buf = w.sbar.r.buf + if w.buffer then + buf.text = lousy.util.escape(string.format(" %-3s", w.buffer)) + buf:show() + else + buf:hide() + end + end, + + update_binds = function (w, mode) + -- Generate the list of active key & buffer binds for this mode + w.binds = lousy.util.table.join(binds.mode_binds[mode], binds.mode_binds.all) + -- Clear & hide buffer + w.buffer = nil + w:update_buf() + end, + + -- Tab label functions + -- TODO: Move these functions into a module (I.e. lousy.widget.tablist) + make_tab_label = function (w, pos) + local theme = lousy.theme.get() + local t = { + label = label(), + sep = eventbox(), + ebox = eventbox(), + layout = hbox(), + } + t.label.font = theme.tab_font + t.label:set_width(1) + t.layout:pack_start(t.label, true, true, 0) + t.layout:pack_start(t.sep, false, false, 0) + t.ebox:set_child(t.layout) + t.ebox:add_signal("button-release", function (e, m, b) + if b == 1 then + w.tabs:switch(pos) + return true + elseif b == 2 then + w:close_tab(w.tabs:atindex(pos)) + return true + end + end) + return t + end, + + destroy_tab_label = function (w, t) + if not t then t = table.remove(w.tbar.titles) end + -- Destroy widgets without their own windows first (I.e. labels) + for _, wi in ipairs{ t.label, t.sep, t.ebox, t.layout } do wi:destroy() end + end, + + update_tab_labels = function (w, current) + local tb = w.tbar + local count, current = w.tabs:count(), current or w.tabs:current() + tb.ebox:hide() + + -- Leave the tablist hidden if there is only one tab open + if count <= 1 then + return nil + end + + if count ~= #tb.titles then + -- Grow the number of labels + while count > #tb.titles do + local t = w:make_tab_label(#tb.titles + 1) + tb.layout:pack_start(t.ebox, true, true, 0) + table.insert(tb.titles, t) + end + -- Prune number of labels + while count < #tb.titles do + w:destroy_tab_label() + end + end + + if count ~= 0 then + for i = 1, count do + local view = w.tabs:atindex(i) + local t = tb.titles[i] + local title = " " ..i.. " "..w:get_tab_title(view) + t.label.text = lousy.util.escape(string.format("%-40s", title)) + w:apply_tablabel_theme(t, i == current) + end + end + tb.ebox:show() + end, + + -- Theme functions + apply_tablabel_theme = function (w, t, selected) + local theme = lousy.theme.get() + selected = (selected and "_selected") or "" + t.label.fg = theme[string.format("tab%s_fg", selected)] + t.ebox.bg = theme[string.format("tab%s_bg", selected)] + end, + + close_win = function (w) + -- Close all tabs + while w.tabs:count() ~= 0 do + w:close_tab() + end + + -- Recursively remove widgets from window + local children = lousy.util.recursive_remove(w.win) + -- Destroy all widgets + for i, c in ipairs(lousy.util.table.join(children, {w.win})) do + if c.hide then c:hide() end + c:destroy() + end + + -- Clear window struct + w = setmetatable(w, {}) + for k, _ in pairs(w) do w[k] = nil end + + -- Quit if closed last window + if #luakit.windows == 0 then luakit.quit() 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 (w, k) return window.methods[k] end +} + +-- Create new window +function window.new(uris) + local w = window.build() + + -- Set window metatable + setmetatable(w, { + __index = function (_, k) + -- Check widget structure first + local v = rawget(w, k) + if v then return v end + -- Call each window index function + for _, index in ipairs(window.indexes) do + v = index(w, k) + if v then return v end + end + end, + }) + + -- Call window init functions + for _, func in pairs(window.init_funcs) do + func(w) + end + + -- Populate notebook with tabs + for _, uri in ipairs(uris or {}) do + w:new_tab(uri) + end + + -- Make sure something is loaded + if w.tabs:count() == 0 then + w:new_tab(globals.homepage) + end + + -- Set initial mode + w:set_mode() + + return w +end + +-- vim: et:sw=4:ts=8:sts=4:tw=80