From 2f1e73741da625c173156102457d5442b27cd953 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 20 Apr 2025 19:20:33 +0530 Subject: Add configuration directory loading + --config-dir cli arg --- TODO.org | 2 +- config.lua | 159 ------------------------------------ init.lua | 159 ++++++++++++++++++++++++++++++++++++ lua/null-browser/api.lua | 85 +++++++++++++++---- lua/null-browser/extras/history.lua | 2 +- src/Configuration.hpp | 12 +++ src/LuaRuntime.cpp | 26 +++--- src/LuaRuntime.hpp | 3 +- src/main.cpp | 8 +- src/widgets/BrowserApp.cpp | 17 +++- src/widgets/BrowserApp.hpp | 5 +- 11 files changed, 282 insertions(+), 196 deletions(-) delete mode 100644 config.lua create mode 100644 init.lua diff --git a/TODO.org b/TODO.org index 8bfcd63..3f76291 100644 --- a/TODO.org +++ b/TODO.org @@ -4,7 +4,7 @@ - [X] Update window title on current webview title change - [X] Search text in page - [X] Dev tools -- [ ] Configuration +- [X] Configuration - [ ] Scroll api (for j/k/h/l/gg/G) - [ ] Tests for api - [ ] Fullscreen diff --git a/config.lua b/config.lua deleted file mode 100644 index e1b4308..0000000 --- a/config.lua +++ /dev/null @@ -1,159 +0,0 @@ -print('hello starting up...') - -local dmenu = require 'null-browser.extras.dmenu' -local history = require 'null-browser.extras.history' - ---- @type table -web = web ---- @type table -uv = uv - -local function get_current_view_index() - local currentView = web.view.current(); - for index, view in ipairs(web.view.list()) do - if view.id == currentView then - return index - end - end -end - -web.set('new_view_url', 'https://lite.duckduckgo.com') -web.set('close_window_when_no_views', true) -web.set('user_agent', 'MacOS | Safari - $500 edition') -web.set('downloads_dir', os.getenv('HOME') .. '/Downloads/firefox') - --- Switch modes -web.keymap.set_mode('n') -- Start in normal mode -web.keymap.set('n', 'i', function() web.keymap.set_mode('i') end) -web.keymap.set('i', '', function() web.keymap.set_mode('n') end) - -web.event.add_listener('UrlChanged', { - callback = function(opts) - print('url change', web.inspect(opts)); - history.add(opts.url) - end -}) - -local function trim(s) - local res, _ = string.gsub(s, '^%s*(.-)%s*$', '%1') - return res -end - -local function to_url(url) - url = trim(url) - if string.match(url, "^https?://") then - return url - end - return "https://" .. url -end - --- Search -web.keymap.set('n', '', function() web.search.reset() end) -web.keymap.set('n', 'n', function() web.search.next() end) -web.keymap.set('n', '', function() web.search.previous() end) -web.keymap.set('n', '', function() - dmenu.input({ prompt = 'Search:', input = web.search.get_text() }, function(err, input) - if err then return end - web.search.set_text(input) - end) -end) - --- Open in new view -web.keymap.set('n', 'o', function() - print(web.get('new_view_url'), web.get('user_agent')) - dmenu.select(history.list(), { prompt = 'Open view:' }, function(err, result) - if err or not result then return end - web.view.create(to_url(result)) - end) -end) --- Open in current view -web.keymap.set('n', '', function() - dmenu.select(history.list(), { prompt = 'Open:' }, function(err, result) - if err or not result then return end - web.view.set_url(to_url(result)) - end) -end) --- Delete from history -web.keymap.set('n', 'd', function() - dmenu.select(history.list(), { prompt = 'Delete history:' }, function(err, result) - if err or not result then return end - history.delete(trim(result)) - end) -end) --- Update current url -web.keymap.set('n', '', function() - local views = web.view.list() - local view = views[get_current_view_index()]; - if view == nil then return end - dmenu.select(history.list(), { prompt = 'Set url:', input = view.url }, function(err, result) - if err or not result then return end - web.view.set_url(trim(result)) - end) -end) - --- Run lua code -web.keymap.set('n', 'q', function() - dmenu.input({ prompt = 'Lua:' }, function(err, result) - if err or not result then return end - local run, run_err = load(result) - if run_err then print(run_err) end - if run then run() end - end) -end) - --- History back/forward -web.keymap.set('n', '', function() web.history.back(); end) -web.keymap.set('n', '', function() web.history.forward(); end) - --- Close view -web.keymap.set('n', '', function() web.view.close(); end) - --- view select -web.keymap.set('n', 'b', function() - local views_list = {} - local views = web.view.list() - for index, view in ipairs(web.view.list()) do - table.insert(views_list, index .. ': ' .. view.title .. ' (' .. view.url .. ')') - end - dmenu.select(views_list, { prompt = 'Views:' }, function(err, result) - if err or not result then return end - local index_str, _ = trim(result):gsub('%s*:.*$', '') - local index = tonumber(index_str) - if views[index] then - web.view.select(views[index].id) - end - end) -end) - --- 1-0 for tab indexes 1-10 -for index = 1, 10 do - local key = index - if index >= 10 then key = 0 end - web.keymap.set('n', '' .. key, function() - local views = web.view.list() - if index > #views then return end - web.view.select(views[index].id) - end) -end - --- Next view -web.keymap.set('n', 'tn', function() - local views = web.view.list() - if #views <= 1 then return end - local index = get_current_view_index() + 1; - if index > #views then index = 1 end - web.view.select(views[index].id) -end) - --- Prev view -web.keymap.set('n', 'tp', function() - local views = web.views.list() - if #views <= 1 then return end - local index = get_current_view_index() - 1; - if index <= 0 then index = #views end - web.views.select(views[index].id) -end) - -web.keymap.set('n', '', function() web.view.open_devtools() end) - -print('ending...') diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..e1b4308 --- /dev/null +++ b/init.lua @@ -0,0 +1,159 @@ +print('hello starting up...') + +local dmenu = require 'null-browser.extras.dmenu' +local history = require 'null-browser.extras.history' + +--- @type table +web = web +--- @type table +uv = uv + +local function get_current_view_index() + local currentView = web.view.current(); + for index, view in ipairs(web.view.list()) do + if view.id == currentView then + return index + end + end +end + +web.set('new_view_url', 'https://lite.duckduckgo.com') +web.set('close_window_when_no_views', true) +web.set('user_agent', 'MacOS | Safari - $500 edition') +web.set('downloads_dir', os.getenv('HOME') .. '/Downloads/firefox') + +-- Switch modes +web.keymap.set_mode('n') -- Start in normal mode +web.keymap.set('n', 'i', function() web.keymap.set_mode('i') end) +web.keymap.set('i', '', function() web.keymap.set_mode('n') end) + +web.event.add_listener('UrlChanged', { + callback = function(opts) + print('url change', web.inspect(opts)); + history.add(opts.url) + end +}) + +local function trim(s) + local res, _ = string.gsub(s, '^%s*(.-)%s*$', '%1') + return res +end + +local function to_url(url) + url = trim(url) + if string.match(url, "^https?://") then + return url + end + return "https://" .. url +end + +-- Search +web.keymap.set('n', '', function() web.search.reset() end) +web.keymap.set('n', 'n', function() web.search.next() end) +web.keymap.set('n', '', function() web.search.previous() end) +web.keymap.set('n', '', function() + dmenu.input({ prompt = 'Search:', input = web.search.get_text() }, function(err, input) + if err then return end + web.search.set_text(input) + end) +end) + +-- Open in new view +web.keymap.set('n', 'o', function() + print(web.get('new_view_url'), web.get('user_agent')) + dmenu.select(history.list(), { prompt = 'Open view:' }, function(err, result) + if err or not result then return end + web.view.create(to_url(result)) + end) +end) +-- Open in current view +web.keymap.set('n', '', function() + dmenu.select(history.list(), { prompt = 'Open:' }, function(err, result) + if err or not result then return end + web.view.set_url(to_url(result)) + end) +end) +-- Delete from history +web.keymap.set('n', 'd', function() + dmenu.select(history.list(), { prompt = 'Delete history:' }, function(err, result) + if err or not result then return end + history.delete(trim(result)) + end) +end) +-- Update current url +web.keymap.set('n', '', function() + local views = web.view.list() + local view = views[get_current_view_index()]; + if view == nil then return end + dmenu.select(history.list(), { prompt = 'Set url:', input = view.url }, function(err, result) + if err or not result then return end + web.view.set_url(trim(result)) + end) +end) + +-- Run lua code +web.keymap.set('n', 'q', function() + dmenu.input({ prompt = 'Lua:' }, function(err, result) + if err or not result then return end + local run, run_err = load(result) + if run_err then print(run_err) end + if run then run() end + end) +end) + +-- History back/forward +web.keymap.set('n', '', function() web.history.back(); end) +web.keymap.set('n', '', function() web.history.forward(); end) + +-- Close view +web.keymap.set('n', '', function() web.view.close(); end) + +-- view select +web.keymap.set('n', 'b', function() + local views_list = {} + local views = web.view.list() + for index, view in ipairs(web.view.list()) do + table.insert(views_list, index .. ': ' .. view.title .. ' (' .. view.url .. ')') + end + dmenu.select(views_list, { prompt = 'Views:' }, function(err, result) + if err or not result then return end + local index_str, _ = trim(result):gsub('%s*:.*$', '') + local index = tonumber(index_str) + if views[index] then + web.view.select(views[index].id) + end + end) +end) + +-- 1-0 for tab indexes 1-10 +for index = 1, 10 do + local key = index + if index >= 10 then key = 0 end + web.keymap.set('n', '' .. key, function() + local views = web.view.list() + if index > #views then return end + web.view.select(views[index].id) + end) +end + +-- Next view +web.keymap.set('n', 'tn', function() + local views = web.view.list() + if #views <= 1 then return end + local index = get_current_view_index() + 1; + if index > #views then index = 1 end + web.view.select(views[index].id) +end) + +-- Prev view +web.keymap.set('n', 'tp', function() + local views = web.views.list() + if #views <= 1 then return end + local index = get_current_view_index() - 1; + if index <= 0 then index = #views end + web.views.select(views[index].id) +end) + +web.keymap.set('n', '', function() web.view.open_devtools() end) + +print('ending...') diff --git a/lua/null-browser/api.lua b/lua/null-browser/api.lua index 7a3efb1..293afc5 100644 --- a/lua/null-browser/api.lua +++ b/lua/null-browser/api.lua @@ -29,18 +29,37 @@ end --- Add a keymap --- ---- @param mode string|table Mode or a list of modes ("i", "n") +--- @param mode string Keymap mode ("i", "n", ...) --- @param key string Key sequence (Eg: `j`) --- @param action function Function to run when keymap is triggered --- --- @example --- ```lua --- web.keymap.set('n', 'o', function() ---- web.view.create('https://google.com') -- Open google in new view +--- web.view.create('https://google.com') --- end) --- ``` function web.keymap.set(mode, key, action) return __internals.keymap_set(mode, key, action) end +--- Set the current keymap mode +--- +--- @param mode string The keymap mode to set +--- +--- @example +--- ```lua +--- web.keymap.set_mode('n') -- Set to normal mode +--- web.keymap.set_mode('i') -- Set to insert mode +--- ``` +function web.keymap.set_mode(mode) return __internals.keymap_set_mode(mode) end + +--- Get the current keymap mode +--- +--- @example +--- ```lua +--- local current_mode = web.keymap.get_mode() +--- ``` +function web.keymap.get_mode() return __internals.keymap_get_mode() end + --- Close view --- --- @param view_id? number View id to close @@ -106,6 +125,17 @@ function web.view.select(view_id) return __internals.view_select(view_id) end --- ``` function web.view.set_url(url, view_id) return __internals.view_set_url(url, view_id) end +--- Open devtools window for the view +--- +--- @param view_id? number Id of the view +--- +--- @example +--- ```lua +--- web.view.open_devtools() -- Open devtools window for current view +--- web.view.open_devtools(5) -- Open devtools window for view id 5 +--- ``` +function web.view.open_devtools(view_id) return __internals.view_open_devtools(view_id) end + --- Listen to events from the browser --- --- @class nullb.EventListenerOptions @@ -173,36 +203,57 @@ function web.history.back(count, view_id) return __internals.history_back(count, --- ``` function web.history.forward(count, view_id) return __internals.history_forward(count, view_id) end ---- Search +--- Search text inside a view --- --- @param text string Text to search --- @param view_id? number Id of the view --- --- @example --- ```lua ---- web.search.set_text('whatever') +--- web.search.set_text('whatever') -- Search in current view +--- web.search.set_text('whatever', 5) -- Search in view id 5 --- ``` function web.search.set_text(text, view_id) return __internals.search_set_text(text, view_id) end --- TODO: Documentation please +--- Reset searched text in a view (Same as web.search.set_text('')) +--- +--- @param view_id? number Id of the view +--- +--- @example +--- ```lua +--- web.search.reset() -- Reset search state for current view +--- web.search.reset(5) -- Reset search state for view id 5 +--- ``` function web.search.reset(view_id) return __internals.search_set_text('', view_id) end --- TODO: Documentation please +--- Get the last searched text +--- +--- @example +--- ```lua +--- local text = web.search.get_text() +--- ``` function web.search.get_text() return __internals.search_get_text() end --- TODO: Documentation please +--- Highlight next search term for the last searched text +--- +--- @param view_id? number Id of the view +--- +--- @example +--- ```lua +--- web.search.next() -- Next search result in current view +--- web.search.next(5) -- Next search result in view id 5 +--- ``` function web.search.next(view_id) return __internals.search_next(view_id) end --- TODO: Documentation please +--- Highlight previous search term for the last searched text +--- +--- @param view_id? number Id of the view +--- +--- @example +--- ```lua +--- web.search.previous() -- Previous search result in current view +--- web.search.previous(5) -- Previous search result in view id 5 +--- ``` function web.search.previous(view_id) return __internals.search_previous(view_id) end --- TODO: Documentation please -function web.view.open_devtools(view_id) return __internals.view_open_devtools(view_id) end - --- TODO: Documentation please -function web.keymap.set_mode(mode) return __internals.keymap_set_mode(mode) end - --- TODO: Documentation please -function web.keymap.get_mode() return __internals.keymap_get_mode() end - print("api loaded") diff --git a/lua/null-browser/extras/history.lua b/lua/null-browser/extras/history.lua index 4304dc0..f38bfa2 100644 --- a/lua/null-browser/extras/history.lua +++ b/lua/null-browser/extras/history.lua @@ -1,5 +1,5 @@ local history = { - path = '/tmp/.null-browser-history', -- TODO: Use proper path + path = web.get('app_data_dir') .. '/.null-browser-history', max_entires = 200, } diff --git a/src/Configuration.hpp b/src/Configuration.hpp index 3b491b2..1f861fa 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -1,5 +1,6 @@ #pragma once +#include "utils.hpp" #include #include #include @@ -13,7 +14,9 @@ private: {"close_window_when_no_views", true}, {"user_agent", QWebEngineProfile::defaultProfile()->httpUserAgent()}, {"downloads_dir", QWebEngineProfile::defaultProfile()->downloadPath()}, + {"app_data_dir", QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)}, }; + QDir config_dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); public: using QObject::QObject; @@ -24,6 +27,9 @@ public: } QVariant get_config(const QString &name, QVariant default_value = "") const { + if (name == "config_dir") // readonly + return config_dir.path(); + if (!config_map.contains(name)) return default_value; @@ -37,6 +43,12 @@ public: QString user_agent() const { return get_config("user_agent").toString(); } QString downloads_dir() const { return get_config("downloads_dir").toString(); } + DEFINE_GETTER(get_config_dir, config_dir) + DEFINE_SETTER(set_config_dir, config_dir) + + QString get_config_lua_init_file() const { return config_dir.filePath("init.lua"); } + QString get_config_lua_dir() const { return config_dir.filePath("lua"); } + private: void on_update(const QString &name, const QVariant &value) { if (name == "user_agent") diff --git a/src/LuaRuntime.cpp b/src/LuaRuntime.cpp index cb5a69a..b4c39a2 100644 --- a/src/LuaRuntime.cpp +++ b/src/LuaRuntime.cpp @@ -15,7 +15,7 @@ LuaRuntime::LuaRuntime() { state = luaL_newstate(); luaL_openlibs(state); - init_lua_package_path(); + init_builtins_package_path(); init_web_api(); } @@ -57,16 +57,9 @@ void LuaRuntime::init_web_api() { }); } -void LuaRuntime::init_lua_package_path() { - auto lua_path = QString(PROJECT_LUA_PATH); - preserve_top(state, { - lua_getglobal(state, "package"); - lua_getfield(state, -1, "path"); - auto pkg_path = QString(lua_tostring(state, -1)) + ";" + lua_path; - lua_pop(state, 1); - lua_pushstring(state, pkg_path.toStdString().c_str()); - lua_setfield(state, -2, "path"); - }); +void LuaRuntime::init_builtins_package_path() { + auto builtins_lua_path = QString(PROJECT_LUA_PATH); + append_package_path(builtins_lua_path); } void LuaRuntime::evaluate(const QString &code) { @@ -84,6 +77,17 @@ void LuaRuntime::evaluate(const QString &code) { }); } +void LuaRuntime::append_package_path(const QString &path) { + preserve_top(state, { + lua_getglobal(state, "package"); + lua_getfield(state, -1, "path"); + auto pkg_path = QString(lua_tostring(state, -1)) + ";" + path; + lua_pop(state, 1); + lua_pushstring(state, pkg_path.toStdString().c_str()); + lua_setfield(state, -2, "path"); + }); +} + void LuaRuntime::load_file_sync(const QString &path) { preserve_top(state, { if (luaL_dofile(state, path.toStdString().c_str()) != LUA_OK) { diff --git a/src/LuaRuntime.hpp b/src/LuaRuntime.hpp index d7ec1ae..abefd86 100644 --- a/src/LuaRuntime.hpp +++ b/src/LuaRuntime.hpp @@ -34,6 +34,7 @@ public: void evaluate(const QString &code); void load_file_sync(const QString &path); + void append_package_path(const QString &path); void stop_event_loop(); void start_event_loop(); @@ -61,7 +62,7 @@ signals: protected: LuaRuntime(); ~LuaRuntime() override; - void init_lua_package_path(); + void init_builtins_package_path(); void init_web_api(); private: diff --git a/src/main.cpp b/src/main.cpp index dc51342..dc67c8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ QCommandLineParser *create_cli_parser() { parser->addPositionalArgument("url", "URL(s) to open", "[url]"); parser->addOptions({ {{"e", "expr"}, "Lua expression to execute", "lua"}, + {{"c", "config-dir"}, "Config directory for null browser", "dir"}, }); return parser; @@ -27,10 +28,15 @@ int main(int argc, char *argv[]) { auto urls = parser->positionalArguments(); auto lua_expr = parser->value("expr"); + auto config_dir = parser->value("config-dir"); + + Configuration configuration; + if (!config_dir.isEmpty()) + configuration.set_config_dir(config_dir); InstanceManager instance_manager; if (instance_manager.is_server()) { - auto *browser = new BrowserApp; + auto *browser = new BrowserApp(configuration); browser->create_window(urls); auto &lua = LuaRuntime::instance(); diff --git a/src/widgets/BrowserApp.cpp b/src/widgets/BrowserApp.cpp index 502d930..1362941 100644 --- a/src/widgets/BrowserApp.cpp +++ b/src/widgets/BrowserApp.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -6,7 +7,7 @@ #include "widgets/BrowserApp.hpp" #include "widgets/BrowserWindow.hpp" -BrowserApp::BrowserApp() { +BrowserApp::BrowserApp(Configuration &configuration) : configuration(configuration) { auto &lua = LuaRuntime::instance(); lua.start_event_loop(); @@ -17,8 +18,18 @@ BrowserApp::BrowserApp() { // Global event filter qApp->installEventFilter(this); - // NOTE: TMP - lua.load_file_sync("./config.lua"); + qDebug() << "Config dir:" << configuration.get_config_dir().path(); + + // Load lua directory into package path + lua.append_package_path(configuration.get_config_lua_dir()); + + // Load init.lua + auto lua_init_file = configuration.get_config_lua_init_file(); + if (QFile::exists(lua_init_file)) { + lua.load_file_sync(lua_init_file); + } else { + qWarning() << "Unable to find init.lua:" << lua_init_file; + } // Initializes profile for (auto *profile : profiles) { diff --git a/src/widgets/BrowserApp.hpp b/src/widgets/BrowserApp.hpp index 90d1ae7..c659763 100644 --- a/src/widgets/BrowserApp.hpp +++ b/src/widgets/BrowserApp.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.hpp" #include "widgets/BrowserWindow.hpp" #include @@ -7,7 +8,7 @@ class BrowserApp : public QObject { Q_OBJECT public: - BrowserApp(); + BrowserApp(Configuration &configuration); BrowserWindow *create_window(const QStringList &urls = {}); @@ -15,7 +16,7 @@ protected: bool eventFilter(QObject *target, QEvent *event) override; private: - Configuration configuration; + Configuration &configuration; QWebEngineProfile default_profile{"default"}; QList profiles{&default_profile}; }; -- cgit v1.3.1