aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-06-11 13:01:24 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-06-11 13:01:43 +0200
commit1e5856e56196549104841c14578fd39364360900 (patch)
tree79405b35388205e14156d8e306e25ca59d6663f6 /pkgs
parentmodules/common/projects.json: Remove unneeded ones (diff)
downloadnixos-config-1e5856e56196549104841c14578fd39364360900.zip
pkgs/tskm: Port to qutebrowser
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.toml1
-rw-r--r--pkgs/by-name/ts/tskm/package.nix2
-rw-r--r--pkgs/by-name/ts/tskm/src/browser/mod.rs172
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs12
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/input/handle.rs44
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/handle.rs262
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/mod.rs204
-rw-r--r--pkgs/by-name/ts/tskm/src/main.rs3
8 files changed, 407 insertions, 293 deletions
diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml
index f635ba1e..42b91ae5 100644
--- a/pkgs/by-name/ts/tskm/Cargo.toml
+++ b/pkgs/by-name/ts/tskm/Cargo.toml
@@ -21,7 +21,6 @@ clap = { version = "4.5.39", features = [ "derive", "std", "color", "help", "usa
clap_complete = { version = "4.5.52", features = ["unstable-dynamic"] }
dirs = { version = "6.0.0", default-features = false }
log = { version = "0.4.27", default-features = false }
-lz4_flex = { version = "0.11.3", features = ["std"], default-features = false }
serde = { version = "1.0.219", features = ["derive"], default-features = false }
serde_json = { version = "1.0.140", default-features = false }
stderrlog = { version = "0.6.0", default-features = false }
diff --git a/pkgs/by-name/ts/tskm/package.nix b/pkgs/by-name/ts/tskm/package.nix
index d75afe6e..ad10865f 100644
--- a/pkgs/by-name/ts/tskm/package.nix
+++ b/pkgs/by-name/ts/tskm/package.nix
@@ -16,7 +16,6 @@
taskwarrior3,
git,
rofi,
- firefox,
sqlite,
}:
rustPlatform.buildRustPackage (finalAttrs: {
@@ -32,7 +31,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
taskwarrior3
git
rofi
- firefox
sqlite
];
diff --git a/pkgs/by-name/ts/tskm/src/browser/mod.rs b/pkgs/by-name/ts/tskm/src/browser/mod.rs
new file mode 100644
index 00000000..8dd52663
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/browser/mod.rs
@@ -0,0 +1,172 @@
+use std::{
+ env,
+ io::Write,
+ os::unix::net::UnixStream,
+ path::PathBuf,
+ process::{self, ExitStatus},
+};
+
+use anyhow::{Context, Result};
+use log::{error, info};
+use serde_json::json;
+use url::Url;
+
+use crate::{state::State, task};
+
+#[allow(clippy::too_many_lines)]
+pub fn open_in_browser(
+ selected_project: &task::Project,
+ state: &mut State,
+ url: Option<Url>,
+) -> Result<()> {
+ let old_project: Option<task::Project> =
+ task::Project::get_current().context("Failed to get currently active project")?;
+ let old_task: Option<task::Task> =
+ task::Task::get_current(state).context("Failed to get currently active task")?;
+
+ selected_project.activate().with_context(|| {
+ format!(
+ "Failed to active project: '{}'",
+ selected_project.to_project_display()
+ )
+ })?;
+
+ let tracking_task = {
+ let all_tasks = selected_project.get_tasks(state).with_context(|| {
+ format!(
+ "Failed to get assoctiated tasks for project: '{}'",
+ selected_project.to_project_display()
+ )
+ })?;
+
+ let tracking_task = all_tasks.into_iter().find(|t| {
+ let maybe_desc = t.description(state);
+ if let Ok(desc) = maybe_desc {
+ desc == "tracking"
+ } else {
+ error!(
+ "Getting task description returned error: {}",
+ maybe_desc.expect_err("We already check for Ok")
+ );
+ false
+ }
+ });
+
+ if let Some(task) = tracking_task {
+ info!(
+ "Starting task {} -> tracking",
+ selected_project.to_project_display()
+ );
+ task.start(state)
+ .with_context(|| format!("Failed to start task {task}"))?;
+ }
+ tracking_task
+ };
+
+ let status = {
+ // #!/bin/sh
+ // # initial idea: Florian Bruhin (The-Compiler)
+ // # author: Thore Bödecker (foxxx0)
+ //
+ // _url="$1"
+ // _qb_version='1.0.4'
+ // _proto_version=1
+ // _ipc_socket="${XDG_RUNTIME_DIR}/qutebrowser/ipc-$(printf '%s' "$USER" | md5sum | cut -d' ' -f1)"
+ // _qute_bin="/usr/bin/qutebrowser"
+ //
+ // printf '{"args": ["%s"], "target_arg": null, "version": "%s", "protocol_version": %d, "cwd": "%s"}\n' \
+ // "${_url}" \
+ // "${_qb_version}" \
+ // "${_proto_version}" \
+ // "${PWD}" | socat -lf /dev/null - UNIX-CONNECT:"${_ipc_socket}" || "$_qute_bin" "$@" &
+
+ let ipc_socket_path = PathBuf::from(
+ env::var("XDG_RUNTIME_DIR").context("Failed to access XDG_RUNTIME_DIR var")?,
+ )
+ .join("qutebrowser")
+ .join(selected_project.to_project_display())
+ .join(format!("ipc-{:x}", {
+ let user_name = env::var("USER").context("Failed to get USER var")?;
+ let base_dir = env::var("XDG_DATA_HOME").context("Failed to get XDG_DATA_HOME")?;
+
+ md5::compute(
+ format!(
+ "{user_name}-{}",
+ PathBuf::from(base_dir)
+ .join("qutebrowser")
+ .join(selected_project.to_project_display())
+ .display()
+ )
+ .as_bytes(),
+ )
+ }));
+
+ if ipc_socket_path.exists() {
+ let mut stream = UnixStream::connect(ipc_socket_path)?;
+
+ let real_url = if let Some(url) = url {
+ url.to_string()
+ } else {
+ // Always add a new tab, so that qutebrowser is marked as “urgent”.
+ "qute://start".to_owned()
+ };
+
+ stream.write_all(
+ json! {
+ {
+ "args": [real_url],
+ "target_arg": null,
+ "version": "1.0.4",
+ "protocol_version": 1,
+ "cwd": "/"
+ }
+ }
+ .to_string()
+ .as_bytes(),
+ )?;
+ stream.write_all(b"\n")?;
+
+ ExitStatus::default()
+ } else {
+ let args = if let Some(url) = url {
+ &[url.to_string()][..]
+ } else {
+ &[][..]
+ };
+
+ process::Command::new(format!(
+ "qutebrowser-{}",
+ selected_project.to_project_display()
+ ))
+ .args(args)
+ .status()
+ .context("Failed to start qutebrowser")?
+ }
+ };
+
+ if !status.success() {
+ error!("Qutebrowser run exited with error.");
+ }
+
+ if let Some(task) = tracking_task {
+ task.stop(state)
+ .with_context(|| format!("Failed to stop task {task}"))?;
+ }
+ if let Some(task) = old_task {
+ task.start(state)
+ .with_context(|| format!("Failed to start task {task}"))?;
+ }
+
+ if let Some(project) = old_project {
+ project.activate().with_context(|| {
+ format!(
+ "Failed to activate project {}",
+ project.to_project_display()
+ )
+ })?;
+ } else {
+ task::Project::clear().context("Failed to clear currently focused project")?;
+ }
+
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs
index 0e32fa62..262fdd60 100644
--- a/pkgs/by-name/ts/tskm/src/cli.rs
+++ b/pkgs/by-name/ts/tskm/src/cli.rs
@@ -8,7 +8,7 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{ffi::OsStr, fmt::Display, path::PathBuf};
+use std::{ffi::OsStr, path::PathBuf};
use anyhow::{bail, Result};
use clap::{builder::StyledStr, ArgAction, Parser, Subcommand, ValueEnum};
@@ -28,7 +28,7 @@ use crate::{
/// This is the core interface to the system-integrated task management
///
/// `tskm` effectively combines multiple applications together:
-/// - `taskwarrior` projects are raised connected to `firefox` profiles, making it possible to “open”
+/// - `taskwarrior` projects are connected to `qutebrowser` profiles, making it possible to “open”
/// a project.
///
/// - Every `taskwarrior` project has a determined `neorg` path, so that extra information for a
@@ -69,7 +69,7 @@ pub enum Command {
command: NeorgCommand,
},
- /// Interface with the Firefox profile of each project.
+ /// Interface with the Qutebrowser profile of each project.
Open {
#[command(subcommand)]
command: OpenCommand,
@@ -111,12 +111,12 @@ fn task_from_working_set_id(id: &str) -> Result<task::Task> {
#[derive(Subcommand, Debug)]
pub enum OpenCommand {
- /// Open each project's Firefox profile consecutively, that was opened since the last review.
+ /// Open each project's Qutebrowser profile consecutively, that was opened since the last review.
///
/// This allows you to remove stale opened tabs and to commit open tabs to the `inputs`.
Review,
- /// Opens Firefox with either the supplied project or the currently active project profile.
+ /// Opens Qutebrowser with either the supplied project or the currently active project profile.
Project {
/// The project to open.
#[arg(value_parser = task::Project::from_project_string, add = ArgValueCompleter::new(complete_project))]
@@ -126,7 +126,7 @@ pub enum OpenCommand {
url: Option<Url>,
},
- /// Open a selected project in it's Firefox profile.
+ /// Open a selected project in it's Qutebrowser profile.
///
/// This will use rofi's dmenu mode to select one project from the list of all registered
/// projects.
diff --git a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
index 0469870c..839e122f 100644
--- a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
@@ -9,26 +9,25 @@
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
use std::{
- collections::HashSet,
- fs, process,
+ collections::{HashMap, HashSet},
+ fs,
str::FromStr,
- thread::{self, sleep},
- time::Duration,
};
use anyhow::{Context, Result};
-use log::{error, info};
+use log::info;
-use crate::cli::InputCommand;
+use crate::{browser::open_in_browser, cli::InputCommand, state::State};
-use super::Input;
+use super::{Input, Tag};
/// # Errors
/// When command handling fails.
///
/// # Panics
/// When internal assertions fail.
-pub fn handle(command: InputCommand) -> Result<()> {
+#[allow(clippy::too_many_lines)]
+pub fn handle(command: InputCommand, state: &mut State) -> Result<()> {
match command {
InputCommand::Add { inputs } => {
for input in inputs {
@@ -67,36 +66,12 @@ pub fn handle(command: InputCommand) -> Result<()> {
}
}
InputCommand::Review { project } => {
- let project = project.to_project_display();
-
- let local_project = project.clone();
- let handle = thread::spawn(move || {
- // We assume that the project is not yet open.
- let mut firefox = process::Command::new("firefox")
- .args(["-P", local_project.as_str(), "about:newtab"])
- .spawn()?;
-
- Ok::<_, anyhow::Error>(firefox.wait()?)
- });
- // Give Firefox some time to start.
- info!("Waiting on firefox to start");
- sleep(Duration::from_secs(4));
-
- let project_str = project.as_str();
'outer: for all in Input::all()?.chunks(100) {
info!("Starting review for the first hundred URLs.");
for input in all {
info!("-> '{input}'");
- let status = process::Command::new("firefox")
- .args(["-P", project_str, input.url().to_string().as_str()])
- .status()?;
-
- if status.success() {
- input.remove()?;
- } else {
- error!("Adding `{input}` to Firefox failed!");
- }
+ open_in_browser(&project, state, Some(input.url.clone()))?;
}
{
@@ -122,9 +97,6 @@ pub fn handle(command: InputCommand) -> Result<()> {
}
}
}
-
- info!("Waiting for firefox to stop");
- handle.join().expect("Should be joinable")?;
}
InputCommand::List { tags } => {
let mut tag_set = HashSet::with_capacity(tags.len());
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
index 82f468b3..231545b6 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
@@ -8,18 +8,11 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{
- fs,
- net::{IpAddr, Ipv4Addr},
- path::PathBuf,
- process,
-};
-
-use anyhow::{Context, Result, bail};
-use log::{error, info, warn};
+use anyhow::{bail, Context, Result};
+use log::{error, info};
use url::Url;
-use crate::{cli::OpenCommand, rofi, state::State, task};
+use crate::{browser::open_in_browser, cli::OpenCommand, rofi, state::State, task};
pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
match command {
@@ -29,13 +22,7 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
info!("Reviewing project: '{}'", project.to_project_display());
open_in_browser(project, state, None).with_context(|| {
format!(
- "Failed to open project ('{}') in Firefox",
- project.to_project_display()
- )
- })?;
- project.untouch().with_context(|| {
- format!(
- "Failed to untouch project ('{}')",
+ "Failed to open project ('{}') in qutebrowser",
project.to_project_display()
)
})?;
@@ -68,175 +55,104 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
open_in_browser(&selected_project, state, url).context("Failed to open project")?;
}
- OpenCommand::ListTabs { project } => {
- let project = if let Some(p) = project {
- p
- } else if let Some(p) =
- task::Project::get_current().context("Failed to get currently focused project")?
- {
- p
- } else {
- bail!("You need to either supply a project or have a project active!");
+ OpenCommand::ListTabs { projects, mode } => {
+ let projects = {
+ if let Some(p) = projects {
+ p
+ } else if mode.is_some() {
+ task::Project::all()
+ .context("Failed to get all projects")?
+ .to_owned()
+ } else if let Some(p) = task::Project::get_current()
+ .context("Failed to get currently focused project")?
+ {
+ vec![p]
+ } else {
+ bail!("You need to either select projects or pass --mode");
+ }
};
- let session_store = project.get_sessionstore().with_context(|| {
- format!(
- "Failed to get session store for project: '{}'",
- project.to_project_display()
- )
- })?;
-
- let selected = session_store
- .windows
- .iter()
- .map(|w| w.selected)
- .collect::<Vec<_>>();
-
- let tabs = session_store
- .windows
- .iter()
- .flat_map(|window| window.tabs.iter())
- .map(|tab| tab.entries.get(tab.index - 1).expect("This should be Some"))
- .collect::<Vec<_>>();
+ for project in &projects {
+ if let Some(mode) = mode {
+ match mode {
+ crate::cli::ListMode::Empty => {
+ if !is_empty(project)? {
+ continue;
+ }
- for (index, entry) in tabs.iter().enumerate() {
- let index = index + 1;
- let is_selected = {
- if selected.contains(&index) {
- "🔻 "
- } else {
- " "
+ // We do not need to print, tabs they are always empty.
+ if projects.len() > 1 {
+ println!("/* {} */", project.to_project_display());
+ }
+ continue;
+ }
+ crate::cli::ListMode::NonEmpty => {
+ if is_empty(project)? {
+ continue;
+ }
+ }
}
- };
- println!("{}{}", is_selected, entry.url);
- }
- }
- }
- Ok(())
-}
-
-fn open_in_browser(
- selected_project: &task::Project,
- state: &mut State,
- url: Option<Url>,
-) -> Result<()> {
- let old_project: Option<task::Project> =
- task::Project::get_current().context("Failed to get currently active project")?;
- let old_task: Option<task::Task> =
- task::Task::get_current(state).context("Failed to get currently active task")?;
-
- selected_project.activate().with_context(|| {
- format!(
- "Failed to active project: '{}'",
- selected_project.to_project_display()
- )
- })?;
-
- let tracking_task = {
- let all_tasks = selected_project.get_tasks(state).with_context(|| {
- format!(
- "Failed to get assoctiated tasks for project: '{}'",
- selected_project.to_project_display()
- )
- })?;
-
- let tracking_task = all_tasks.into_iter().find(|t| {
- let maybe_desc = t.description(state);
- if let Ok(desc) = maybe_desc {
- desc == "tracking"
- } else {
- error!(
- "Getting task description returned error: {}",
- maybe_desc.expect_err("We already check for Ok")
- );
- false
- }
- });
-
- if let Some(task) = tracking_task {
- info!(
- "Starting task {} -> tracking",
- selected_project.to_project_display()
- );
- task.start(state)
- .with_context(|| format!("Failed to start task {task}"))?;
- }
- tracking_task
- };
-
- let status = {
- let mut args = vec!["-P".to_owned(), selected_project.to_project_display()];
- if let Some(url) = url {
- args.push(url.to_string());
- } else {
- let lock_file = dirs::home_dir()
- .expect("Exists")
- .join(".mozilla/firefox")
- .join(selected_project.to_project_display())
- .join("lock");
+ }
- if lock_file.exists() {
- let (ip, pid): (IpAddr, u32) = {
- let link = fs::read_link(&lock_file).with_context(|| {
- format!("Failed to readlink lock at '{}'", lock_file.display())
- })?;
+ if projects.len() > 1 {
+ println!("/* {} */", project.to_project_display());
+ }
- let (ip, pid) = link
- .to_str()
- .expect("Should work")
- .split_once(':')
- .expect("The split works");
+ let tabs = match get_tabs(project) {
+ Ok(ok) => ok,
+ Err(err) => {
+ if projects.len() > 1 {
+ error!(
+ "While trying to get the sessionstore for {}: {:?}",
+ project.to_project_display(),
+ err
+ );
+ continue;
+ }
- (
- ip.parse().expect("Should be a valid ip address"),
- pid.parse().expect("Should be a valid pid"),
- )
+ return Err(err).with_context(|| {
+ format!(
+ "While trying to get the sessionstore for {}",
+ project.to_project_display()
+ )
+ });
+ }
};
- if ip != Ipv4Addr::new(127, 0, 0, 2) {
- warn!("Your ip is weird..");
- }
-
- if PathBuf::from("/proc").join(pid.to_string()).exists() {
- // Another Firefox instance has already been started for this project
- // Add a buffer URL to force Firefox to open it in the already open instance
- args.push("about:newtab".to_owned());
- } else {
- // This project does not yet have another Firefox instance
- // We do not need to add anything to the arguments, Firefox will open a new
- // instance.
+ for (active, url) in tabs {
+ let is_selected = {
+ if active {
+ "🔻 "
+ } else {
+ " "
+ }
+ };
+ println!("{is_selected}{url}");
}
- } else {
- // There is no lock file and thus no instance already open.
}
- };
-
- process::Command::new("firefox")
- .args(args)
- .status()
- .context("Failed to start firefox")?
- };
-
- if !status.success() {
- error!("Firefox run exited with error.");
+ }
}
- if let Some(task) = tracking_task {
- task.stop(state)
- .with_context(|| format!("Failed to stop task {task}"))?;
- }
- if let Some(task) = old_task {
- task.start(state)
- .with_context(|| format!("Failed to start task {task}"))?;
- }
+ Ok(())
+}
- if let Some(project) = old_project {
- project.activate().with_context(|| {
- format!("Failed to active project {}", project.to_project_display())
- })?;
- } else {
- task::Project::clear().context("Failed to clear currently focused project")?;
- }
+fn get_tabs(project: &task::Project) -> Result<Vec<(bool, Url)>> {
+ let session_store = project.get_sessionstore()?;
- Ok(())
+ let tabs = session_store
+ .windows
+ .iter()
+ .flat_map(|window| window.tabs.iter())
+ .filter_map(|tab| {
+ tab.history
+ .iter()
+ .find(|hist| hist.active)
+ .map(|hist| (tab.active, hist))
+ })
+ .collect::<Vec<_>>();
+
+ Ok(tabs
+ .into_iter()
+ .map(|(active, hist)| (active, hist.url.clone()))
+ .collect())
}
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
index a4060fa3..40e057c1 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
@@ -8,13 +8,12 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{collections::HashMap, fs::File, io};
+use std::{fs::File, io::Read, str::FromStr};
-use anyhow::{Context, Result};
-use lz4_flex::decompress_size_prepended;
-use serde::Deserialize;
-use serde_json::Value;
+use anyhow::{anyhow, Context, Result};
+use taskchampion::chrono::NaiveDateTime;
use url::Url;
+use yaml_rust2::Yaml;
use crate::task::Project;
@@ -23,94 +22,151 @@ pub use handle::handle;
impl Project {
pub(super) fn get_sessionstore(&self) -> Result<SessionStore> {
- let path = dirs::home_dir()
- .expect("Will exist")
- .join(".mozilla/firefox")
+ let path = dirs::data_local_dir()
+ .context("Failed to get data dir")?
+ .join("qutebrowser")
.join(self.to_project_display())
- .join("sessionstore-backups/recovery.jsonlz4");
- let file = decompress_mozlz4(
- File::open(&path)
- .with_context(|| format!("Failed to open path '{}'", path.display()))?,
- )
- .with_context(|| format!("Failed to decompress file as mozlzh '{}'", path.display()))?;
+ // NOTE(@bpeetz): We could use another real session name, but this file should
+ // always exist. <2025-06-03>
+ .join("data/sessions/_autosave.yml");
- let contents: SessionStore = serde_json::from_str(&file).with_context(|| {
- format!(
- "Failed to deserialize file ('{}') as session store.",
- path.display()
- )
- })?;
- Ok(contents)
+ let mut file = File::open(&path)
+ .with_context(|| format!("Failed to open path '{}'", path.display()))?;
+
+ let mut yaml_str = String::new();
+ file.read_to_string(&mut yaml_str)
+ .context("Failed to read _autosave.yml path")?;
+ let yaml = yaml_rust2::YamlLoader::load_from_str(&yaml_str)?;
+
+ let store = qute_store_from_yaml(&yaml).context("Failed to read yaml store")?;
+
+ Ok(store)
}
}
-fn decompress_mozlz4<P: io::Read>(mut file: P) -> Result<String> {
- const MOZLZ4_MAGIC_NUMBER: &[u8] = b"mozLz40\0";
+fn qute_store_from_yaml(yaml: &[Yaml]) -> Result<SessionStore> {
+ assert_eq!(yaml.len(), 1);
+ let doc = &yaml[0];
- let mut buf = [0u8; 8];
- file.read_exact(&mut buf)
- .context("Failed to read the mozlz40 header.")?;
+ let hash = doc.as_hash().context("Invalid yaml")?;
+ let windows = hash
+ .get(&Yaml::String("windows".to_owned()))
+ .ok_or(anyhow!("Missing windows"))?
+ .as_vec()
+ .ok_or(anyhow!("Windows not vector"))?;
- assert_eq!(buf, MOZLZ4_MAGIC_NUMBER);
+ Ok(SessionStore {
+ windows: windows
+ .iter()
+ .map(|window| {
+ let hash = window.as_hash().ok_or(anyhow!("Windows not hashmap"))?;
- let mut buf = vec![];
- file.read_to_end(&mut buf).context("Failed to read file")?;
+ Ok::<_, anyhow::Error>(Window {
+ geometry: hash
+ .get(&Yaml::String("geometry".to_owned()))
+ .ok_or(anyhow!("Missing window geometry"))?
+ .as_str()
+ .ok_or(anyhow!("geometry not string"))?
+ .to_owned(),
+ tabs: hash
+ .get(&Yaml::String("tabs".to_owned()))
+ .ok_or(anyhow!("Missing window tabs"))?
+ .as_vec()
+ .ok_or(anyhow!("Tabs not vec"))?
+ .iter()
+ .map(|tab| {
+ let hash = tab.as_hash().ok_or(anyhow!("Tab not hashmap"))?;
- let uncompressed = decompress_size_prepended(&buf).context("Failed to decompress file")?;
+ Ok::<_, anyhow::Error>(Tab {
+ history: hash
+ .get(&Yaml::String("history".to_owned()))
+ .ok_or(anyhow!("Missing tab history"))?
+ .as_vec()
+ .ok_or(anyhow!("tab history not vec"))?
+ .iter()
+ .map(|history| {
+ let hash = history
+ .as_hash()
+ .ok_or(anyhow!("Tab history not hashmap"))?;
- Ok(String::from_utf8(uncompressed).expect("This should be valid json and thus utf8"))
+ Ok::<_, anyhow::Error>(TabHistory {
+ active: hash
+ .get(&Yaml::String("active".to_owned()))
+ .ok_or(anyhow!("Missing tab history active"))?
+ .as_bool()
+ .ok_or(anyhow!("tab history active not bool"))?,
+ last_visited: NaiveDateTime::from_str(
+ hash.get(&Yaml::String("last_visited".to_owned()))
+ .ok_or(anyhow!(
+ "Missing tab history last_visited"
+ ))?
+ .as_str()
+ .ok_or(anyhow!(
+ "tab history last_visited not string"
+ ))?,
+ )
+ .context("Failed to parse last_visited")?,
+ pinned: hash
+ .get(&Yaml::String("pinned".to_owned()))
+ .ok_or(anyhow!("Missing tab history pinned"))?
+ .as_bool()
+ .ok_or(anyhow!("tab history pinned not bool"))?,
+ title: hash
+ .get(&Yaml::String("title".to_owned()))
+ .ok_or(anyhow!("Missing tab history title"))?
+ .as_str()
+ .ok_or(anyhow!("tab history title not string"))?
+ .to_owned(),
+ url: Url::parse(
+ hash.get(&Yaml::String("url".to_owned()))
+ .ok_or(anyhow!("Missing tab history url"))?
+ .as_str()
+ .ok_or(anyhow!("tab history url not string"))?,
+ )
+ .context("Failed to parse url")?,
+ zoom: hash
+ .get(&Yaml::String("zoom".to_owned()))
+ .ok_or(anyhow!("Missing tab history zoom"))?
+ .as_f64()
+ .ok_or(anyhow!("tab history zoom not 64"))?,
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()?,
+ active: hash
+ .get(&Yaml::String("active".to_owned()))
+ .unwrap_or(&Yaml::Boolean(false))
+ .as_bool()
+ .ok_or(anyhow!("active not bool"))?,
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()?,
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()?,
+ })
}
-#[derive(Deserialize, Debug)]
+#[derive(Debug)]
pub struct SessionStore {
pub windows: Vec<Window>,
}
-
-#[derive(Deserialize, Debug)]
+#[derive(Debug)]
pub struct Window {
+ pub geometry: String,
pub tabs: Vec<Tab>,
- pub selected: usize,
}
-
-#[derive(Deserialize, Debug)]
+#[derive(Debug)]
pub struct Tab {
- pub entries: Vec<TabEntry>,
- #[serde(rename = "lastAccessed")]
- pub last_accessed: u64,
- pub hidden: bool,
- #[serde(rename = "searchMode")]
- pub search_mode: Option<Value>,
- #[serde(rename = "userContextId")]
- pub user_context_id: u32,
- pub attributes: TabAttributes,
- #[serde(rename = "extData")]
- pub ext_data: Option<HashMap<String, Value>>,
- pub index: usize,
- #[serde(rename = "requestedIndex")]
- pub requested_index: Option<u32>,
- pub image: Option<Url>,
+ pub history: Vec<TabHistory>,
+ pub active: bool,
}
-
-#[derive(Deserialize, Debug)]
-pub struct TabEntry {
- pub url: Url,
+#[derive(Debug)]
+pub struct TabHistory {
+ pub active: bool,
+ pub last_visited: NaiveDateTime,
+ pub pinned: bool,
+ // pub scroll-pos:
pub title: String,
- #[serde(rename = "cacheKey")]
- pub cache_key: u32,
- #[serde(rename = "ID")]
- pub id: u32,
- #[serde(rename = "docshellUUID")]
- pub docshell_uuid: Value,
- #[serde(rename = "resultPrincipalURI")]
- pub result_principal_uri: Option<Url>,
- #[serde(rename = "hasUserInteraction")]
- pub has_user_interaction: bool,
- #[serde(rename = "triggeringPrincipal_base64")]
- pub triggering_principal_base64: Value,
- #[serde(rename = "docIdentifier")]
- pub doc_identifier: u32,
- pub persist: bool,
+ pub url: Url,
+ pub zoom: f64,
}
-
-#[derive(Deserialize, Debug, Clone, Copy)]
-pub struct TabAttributes {}
diff --git a/pkgs/by-name/ts/tskm/src/main.rs b/pkgs/by-name/ts/tskm/src/main.rs
index dc425dcc..e6113111 100644
--- a/pkgs/by-name/ts/tskm/src/main.rs
+++ b/pkgs/by-name/ts/tskm/src/main.rs
@@ -17,6 +17,7 @@ use crate::{
state::State,
};
+pub mod browser;
pub mod cli;
pub mod interface;
pub mod rofi;
@@ -40,7 +41,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut state = State::new_rw()?;
match args.command {
- Command::Inputs { command } => input::handle(command)?,
+ Command::Inputs { command } => input::handle(command, &mut state)?,
Command::Neorg { command } => neorg::handle(command, &mut state)?,
Command::Open { command } => open::handle(command, &mut state)?,
Command::Projects { command } => project::handle(command)?,