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, ) -> Result<()> { let old_project: Option = task::Project::get_current().context("Failed to get currently active project")?; let old_task: Option = 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(()) }