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";