From c6034220855a6194bbb6d95aaa170d6e89dee222 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Wed, 17 Feb 2021 00:47:30 -0500 Subject: [PATCH] Replace bin/bookmarkmenu with bin/browsermenu Handles both history and bookmarks. Also, rewritten in ruby. --- bin/rofi/bookmarkmenu | 60 --------------- bin/rofi/browsermenu | 137 ++++++++++++++++++++++++++++++++++ modules/desktop/apps/rofi.nix | 10 ++- 3 files changed, 145 insertions(+), 62 deletions(-) delete mode 100755 bin/rofi/bookmarkmenu create mode 100755 bin/rofi/browsermenu diff --git a/bin/rofi/bookmarkmenu b/bin/rofi/bookmarkmenu deleted file mode 100755 index 623d3980..00000000 --- a/bin/rofi/bookmarkmenu +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env sh - -# Interactively select (in skim) an url from Firefox/Chrome/Chromium history to -# open in your browser (depends on $BROWSER). Should work on Linux and MacOS. -# -# requires: sqlite3 - -ROFI_CMD='rofi -dmenu -theme theme/bookmarkmenu.rasi -columns 1 -markup-rows -multi-select -p browser -i' -SEP=${1:-'{{::}}'} - -_do_rofi() { - urls=$(awk -F "$SEP" '{printf "%s: %s\n", $1, $2}' | $ROFI_CMD | sed -e 's#&#&#g' -e 's#<#<#g' -e 's#>#>#g') - IFS=$'\n' - for url in $urls; do - url=${url%*} - url=${url##*} - echo $url - done | xargs -r $BROWSER -} - -case ${BROWSER:-firefox} in - firefox) - for dir in ~/.mozilla/firefox/*.default \ - $XDG_CONFIG_HOME/firefox/.mozilla/firefox/*.default \ - $XDG_DATA_HOME/mozilla/firefox/*.default \ - ~/Library/Application\ Support/Firefox/Profiles/*.default; do - if [[ -d "$dir" ]]; then - # The db can be locked from time to time. This is inefficient, but - # places.sqlite is usually under 30mb so this should be instant, and - # /tmp is cleared at boot so... - # if ! find /tmp/rofi-bookmarkmenu -mmin +60 2>&1 >/dev/null; then - cp "$dir/places.sqlite" "/tmp/places.sqlite" - # sqlite3 -separator $SEP "/tmp/places.sqlite" \ - # "SELECT DISTINCT title, url FROM moz_places \ - # WHERE title IS NOT NULL \ - # ORDER BY last_visit_date DESC" >/tmp/rofi-bookmarkmenu - sqlite3 -separator $SEP "/tmp/places.sqlite" \ - "SELECT DISTINCT mb.title, mp.url FROM moz_bookmarks mb \ - INNER JOIN moz_places mp ON mp.id = mb.fk \ - WHERE mb.type = 1 AND mb.title IS NOT NULL \ - ORDER BY mp.frecency DESC \ - LIMIT 200" >>/tmp/rofi-bookmarkmenu - # fi - cat /tmp/rofi-bookmarkmenu | sed -e 's#&#&#g' -e 's#<#<#g' -e 's#>#>#g' | _do_rofi - break - fi - done - ;; - qutebrowser) - { - sed -e 's/ /{{::}}/' urls | awk -F'{{::}}' '{printf "%s{{::}}%s\n", $2, $1}' - # sqlite3 -separator $SEP "$XDG_DATA_HOME/qutebrowser/history.sqlite" \ - # "SELECT title, url FROM History \ - # WHERE title != '' \ - # GROUP BY url \ - # ORDER BY atime DESC" - } | _do_rofi - ;; - *) rofi -e "No known browser" ;; -esac diff --git a/bin/rofi/browsermenu b/bin/rofi/browsermenu new file mode 100755 index 00000000..c1f79950 --- /dev/null +++ b/bin/rofi/browsermenu @@ -0,0 +1,137 @@ +#!/usr/bin/env cached-nix-shell +#! nix-shell -p ruby rubyPackages.sqlite3 -i "ruby -S" --quiet + +# Interactively select (in skim) an url from Firefox/Chrome/Chromium history to +# open in your browser (depends on $BROWSER). Should work on Linux and MacOS. + +require 'shellwords' +require 'sqlite3' +require 'json' +require 'fileutils' +require 'cgi' + +def rofi items, *args + cmd = [*%w{rofi -dmenu -theme theme/bookmarkmenu.rasi -columns 1 -markup-rows -multi-select -p browser -i}, *args] + IO.popen(cmd.compact.shelljoin, "r+") do |pipe| + pipe.puts items.keys.join "\n" + pipe.close_write + (pipe.gets || "").split "\n" + end +end + +def db file, command + copy = "/tmp/browsermenu-db-#{File.basename(file)}" + begin + # Copy the sqlite database because history/bookmark info is usually small + # and may be locked by an open browser. + unless File.exists? copy + FileUtils.cp file, copy + FileUtils.chmod 0400, copy, verbose: true + end + results = [] + db = SQLite3::Database.open copy + db.execute(command).each { |r| results.push r } + results + ensure + File.delete copy + end +end + +def walk_json children + result = [] + for c in children + if c['children'] + result += walk_json c['children'] + else + result.push [ c['name'], c['url'] ] + end + end + result +end + +def get_bookmarks browser + bookmarks = [] + case browser + when :firefox + # In my nixos dotfiles I hardcode the default profile to $USER.profile, + # which makes this lookup easier, but means this script won't work on any + # other system. + user = ENV['USER'] + ["#{ENV['HOME']}/.mozilla/firefox/#{user}.default", + "#{ENV['XDG_CONFIG_HOME']}/firefox/.mozilla/firefox/#{user}.default", + "#{ENV['XDG_DATA_HOME']}/mozilla/firefox/#{user}.default"].each do |dir| + if Dir.exists? dir + db "#{dir}/places.sqlite", + ["SELECT DISTINCT mb.title, mp.url FROM moz_bookmarks mb", + "INNER JOIN moz_places mp ON mp.id = mb.fk", + "WHERE mb.type = 1 AND mb.title IS NOT NULL", + "ORDER BY mp.frecency DESC", + "LIMIT 200"].join(' ') + end + end + when :brave + f = File.read "#{ENV['XDG_CONFIG_HOME']}/BraveSoftware/Brave-Browser/Default/Bookmarks" + data = JSON.parse f + walk_json data["roots"]["bookmark_bar"]["children"] + when :qutebrowser + bookmarks = File.readlines "#{ENV['XDG_CONFIG_HOME']}/qutebrowser/bookmarks/urls" + bookmarks.map { |s| s.strip.split(' ', 2).reverse } + end +end + +def get_history browser + case browser + when :firefox + # In my nixos dotfiles I hardcode the default profile to $USER.profile, + # which makes this lookup easier, but means this script won't work on any + # other system. + user = ENV['USER'] + ["#{ENV['HOME']}/.mozilla/firefox/#{user}.default", + "#{ENV['XDG_CONFIG_HOME']}/firefox/.mozilla/firefox/#{user}.default", + "#{ENV['XDG_DATA_HOME']}/mozilla/firefox/#{user}.default"].each do |dir| + if Dir.exists? dir + return db "#{dir}/places.sqlite", + ["SELECT title, url", + "FROM moz_places", + "WHERE title<>''", + "ORDER BY last_visit_date DESC"].join(' ') + end + end + return [] + when :brave + db "#{ENV['XDG_CONFIG_HOME']}/BraveSoftware/Brave-Browser/Default/History", + "SELECT title, url FROM urls GROUP BY url ORDER BY last_visit_time DESC LIMIT 200" + when :qutebrowser + db "#{ENV['XDG_DATA_HOME']}/qutebrowser/history.sqlite", + ["SELECT title, url FROM History", + "WHERE title != ''", + "GROUP BY url", + "ORDER BY atime DESC"].join(' ') + end +end + +def list urls + items = urls.map do |x| + x.map! { |x| CGI.escapeHTML x } + [x[0].empty? ? + "#{x[1]}" : + "#{x[0]} #{x[1]}", + x[1]] + end.to_h + selected = rofi items + selected.map { |key| items[key] } +end + +type, browser = *ARGV +browser = (browser || ENV['BROWSER']).to_sym +urls = if type == 'history' + get_history browser +else + get_bookmarks browser +end +urls = list urls +if urls.empty? + puts "No URL selected. Aborting..." + exit 1 +end +Process.detach fork { exec ENV['BROWSER'], *urls } diff --git a/modules/desktop/apps/rofi.nix b/modules/desktop/apps/rofi.nix index 434383f7..8f26efe0 100644 --- a/modules/desktop/apps/rofi.nix +++ b/modules/desktop/apps/rofi.nix @@ -34,10 +34,16 @@ in { # Fake rofi dmenu entries (makeDesktopItem { - name = "rofi-bookmarkmenu"; + name = "rofi-browsermenu"; desktopName = "Open Bookmark in Browser"; icon = "bookmark-new-symbolic"; - exec = "${binDir}/rofi/bookmarkmenu"; + exec = "${binDir}/rofi/browsermenu"; + }) + (makeDesktopItem { + name = "rofi-browsermenu-history"; + desktopName = "Open Browser History"; + icon = "accessories-clock"; + exec = "${binDir}/rofi/browsermenu history"; }) (makeDesktopItem { name = "rofi-filemenu";