aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhongheng Liu <z.liu@outlook.com.gr>2025-03-06 18:16:40 +0200
committerZhongheng Liu <z.liu@outlook.com.gr>2025-03-06 18:16:40 +0200
commit6735fb705331c9b84cc30858c2ec0810446d2233 (patch)
tree1107c95725e4b6a8420cbe055b7ad29001e973d7
parentb46301bfdba68748d536bedcf7faac3f367b230f (diff)
downloadfirefox-dmenu-integration-6735fb705331c9b84cc30858c2ec0810446d2233.tar.gz
firefox-dmenu-integration-6735fb705331c9b84cc30858c2ec0810446d2233.tar.bz2
firefox-dmenu-integration-6735fb705331c9b84cc30858c2ec0810446d2233.zip
feat: integration with dmenu and firefox
doc: init README
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md35
-rw-r--r--src/cmdline.rs3
-rw-r--r--src/main.rs67
5 files changed, 95 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5a3385f..ebe3990 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 9f32144..d4e59e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(())
}