diff options
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 35 | ||||
-rw-r--r-- | src/cmdline.rs | 3 | ||||
-rw-r--r-- | src/main.rs | 67 |
5 files changed, 95 insertions, 14 deletions
@@ -128,7 +128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "firefox-menu-integration" +name = "firefox-dmenu-integration" version = "0.1.0" dependencies = [ "clap", @@ -1,5 +1,5 @@ [package] -name = "firefox-menu-integration" +name = "firefox-dmenu-integration" version = "0.1.0" edition = "2021" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d3cc42 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# firefox-dmenu-integration + +An integration util between dmenu and firefox. It gets recently accessed +host names from history database, and calls dmenu for selection. +**100% Written in Rust.** + +## Usage + +``` +Usage: firefox-dmenu-integration [OPTIONS] --browser <BROWSER> --dmenu <DMENU> + +Options: + -b, --browser <BROWSER> Path to browser executable + -m, --dmenu <DMENU> Path to dmenu executable + -p, --profile <PROFILE> Location history sqlite database [default: ~/.mozilla/firefox/000000.default] + -l, --limit <LIMIT> Limit of location history entries to collect [default: 100] + -h, --help Print help + -V, --version Print version +``` + +### Finding your Firefox profile location +> +> In the future, fuzzily attempting to discover firefox profile directories can +be implemented. See To-dos. +Your Firefox profile is most like located in +`~/.mozilla/firefox/<string of numbers>.default`. +To verify that this is the correct folder, confirm that `places.sqlite` exists +in this folder. + +## How it works + +It uses `rusqlite` to connect to the locations database, then collects +non-duplicate items into a set, then formatted as standard input for dmenu (or +dmenu-like alternatives). The choice output by dmenu is then put as URL +argument for launching the browser specified in `--browser` argument. diff --git a/src/cmdline.rs b/src/cmdline.rs index 43adbec..9d3bc29 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -7,10 +7,11 @@ pub struct Args { /// Path to browser executable #[arg(short, long)] pub browser: PathBuf, + /// Path to dmenu executable #[arg(short = 'm', long)] pub dmenu: PathBuf, /// Location history sqlite database - #[arg(short = 'p', long, default_value = PathBuf::from("~/.mozilla/firefox/11ybm96o.default").into_os_string())] + #[arg(short = 'p', long, default_value = PathBuf::from("~/.mozilla/firefox/000000.default").into_os_string())] pub profile: PathBuf, /// Limit of location history entries to seek #[arg(short = 'l', long, default_value_t = 100)] diff --git a/src/main.rs b/src/main.rs index d3c927d..3a34912 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ mod cmdline; use clap::Parser; use cmdline::Args; -use rusqlite::{params, Connection, Result}; +use rusqlite::{Connection, Result}; +use std::io::Write; use std::{ collections::HashSet, fmt::Display, fs, - path::{Path, PathBuf}, + path::PathBuf, + process::{Command, Stdio}, }; use url; #[derive(Debug)] @@ -30,7 +32,7 @@ fn copy_db(root_path: Option<&PathBuf>) -> Option<PathBuf> { if let Ok(found_profile_path) = match_firefox_profile() { found_profile_path } else { - panic!("thing"); + panic!("[Profile] Cannot find any profiles!"); } }; root.push("places.sqlite"); @@ -38,14 +40,14 @@ fn copy_db(root_path: Option<&PathBuf>) -> Option<PathBuf> { match fs::exists(&root) { Ok(existence) => { if !existence { - panic!("[FS] DB does not exist") + panic!("[Filesystem] Database does not exist!") } else { - fs::copy(root, &tmp_path); + let _ = fs::copy(root, &tmp_path); return Some(tmp_path); } } Err(_) => { - panic!("[FS] something wrong...") + panic!("[Filesystem] Nondescript error.") } }; } @@ -54,10 +56,13 @@ fn main() -> Result<()> { let path = if let Some(p) = copy_db(Some(&args.profile)) { p } else { - panic!("baddddd") + panic!("[main] Database copying failed.") }; let conn = Connection::open(&path).unwrap(); - let query = format!("SELECT id, url FROM moz_places ORDER BY last_visit_date DESC LIMIT {}", args.limit); + let query = format!( + "SELECT id, url FROM moz_places ORDER BY last_visit_date DESC LIMIT {}", + args.limit + ); let mut stmt = conn.prepare(&query).unwrap(); let urls_iter = stmt.query_map([], |row| { Ok(FirefoxPlace { @@ -65,7 +70,7 @@ fn main() -> Result<()> { url: row.get(1)?, }) })?; - let mut hosts = HashSet::new(); + let mut hosts: HashSet<String> = HashSet::new(); for place in urls_iter { if let Ok(p) = place { let url = url::Url::parse(&p.url); @@ -76,9 +81,49 @@ fn main() -> Result<()> { } } } - for h in hosts { + for h in &hosts { println!("{}", h); } - fs::remove_file(&path); + let tmp_urls_path = PathBuf::from("/tmp/firefox-dmenu-urls.tmp"); + let _ = fs::remove_file(&path); + let dmenu_opts = hosts.into_iter().collect::<Vec<String>>().join("\n"); + println!( + "cat {} | {}", + tmp_urls_path.to_str().unwrap(), + args.dmenu.to_str().unwrap() + ); + let _ = fs::write(&tmp_urls_path, &dmenu_opts); + let source_command = Command::new("cat") + .arg(&tmp_urls_path) + .stdout(Stdio::piped()) + .output() + .expect("[cat] Failed to cat source file. Is it readable?"); + let mut dmenu_command = Command::new(args.dmenu) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("[dmenu] Failed to launch dmenu. Is it executable?"); + let mut dmenu_stdin = dmenu_command + .stdin + .take() + .expect("[dmenu] Failed to take standard input from dmenu."); + std::thread::spawn(move || { + dmenu_stdin + .write_all(&source_command.stdout) + .expect("[dmenu] Failed to write to stdin of dmenu."); + }); + let dmenu_out = dmenu_command + .wait_with_output() + .expect("[dmenu] Expected dmenu to successfully execute."); + let dmenu_sel = String::from_utf8_lossy(&dmenu_out.stdout); + println!("Captured selection: {}", dmenu_sel); + println!("{}", dmenu_sel); + if dmenu_sel.to_string() == String::from("") { + println!("dmenu did not produce any output."); + return Ok(()); + } + let _ = Command::new(args.browser) + .arg(dmenu_sel.to_string()) + .spawn(); Ok(()) } |