// yabridge: a Wine VST bridge // Copyright (C) 2020 Robbert van der Helm // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Small helper utilities. use anyhow::{Context, Result}; use colored::Colorize; use std::env; use std::fs; use std::os::unix::fs as unix_fs; use std::os::unix::process::CommandExt; use std::path::Path; use std::process::{Command, Stdio}; use textwrap::Wrapper; /// Wrapper around `std::fs::copy` with a human readable error message. pub fn copy, Q: AsRef>(from: P, to: Q) -> Result { fs::copy(&from, &to).with_context(|| { format!( "Error copying '{}' to '{}'", from.as_ref().display(), to.as_ref().display() ) }) } /// Wrapper around `std::fs::remove_file` with a human readable error message. pub fn remove_file>(path: P) -> Result<()> { fs::remove_file(&path) .with_context(|| format!("Could not remove '{}'", path.as_ref().display())) } /// Wrapper around `std::os::unix::fs::symlink` with a human readable error message. pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { unix_fs::symlink(&src, &dst).with_context(|| { format!( "Error symlinking '{}' to '{}'", src.as_ref().display(), dst.as_ref().display() ) }) } /// Verify that `yabridge-host.exe` is accessible in a login shell. Returns unit if it is, or if we /// the login shell is set to an unknown shell. In the last case we'll just print a warning since we /// don't know how to invoke the shell as a login shell. This is needed when using copies to ensure /// that yabridge can find the host binaries when the VST host is launched from the desktop /// enviornment. /// /// When we could not find `yabridge-host.exe`, we'll return `Err(shell_name)` so we can print a /// descriptive warning message. pub fn verify_path_setup() -> Result<(), String> { match env::var("SHELL") { Ok(shell_path) => { // `$SHELL` will often contain a full path, but it doesn't have to let shell = Path::new(&shell_path) .file_name() .and_then(|os_str| os_str.to_str()) .unwrap_or_else(|| shell_path.as_str()); // We're using the `-l` flag present in most shells to start a login shell, but some // shells don't have this option. According the Bash's man page, another method some // shells use to determine that they're being run as a login shell is by checking that // `argv[0]` starts with a hyphen, so we'll also do that. let mut command = Command::new(&shell_path); command.arg0(format!("-{}", &shell_path)); let command = match shell { // All of these shells support the `-l` flag to start a login shell and have a // POSIX-compatible `command` builtin "ash" | "bash" | "csh" | "ksh" | "dash" | "fish" | "ion" | "sh" | "tcsh" | "zsh" => command .arg("-l") .arg("-c") .arg("command -v yabridge-host.exe"), // These shells either have their own implementation of `which` and don't support // `command`, or they don't have a seperate login shell flag "elvish" | "oil" => command.arg("-c").arg("command -v yabridge-host.exe"), // xonsh's which implementation is broken as of writing this, so I left it out "pwsh" => command.arg("-l").arg("-c").arg("which yabridge-host.exe"), "nu" => command.arg("-c").arg("which yabridge-host.exe"), shell => { eprintln!( "\n{}", wrap(&format!( "WARNING: Yabridgectl does not know how to handle your login shell \ '{}', skipping PATH environment variable check. Feel free to open a \ feature request in order to get yabridgectl to support your shell.\n\ \n\ https://github.com/robbert-vdh/yabridge/issues", shell.bright_white(), )) ); return Ok(()); } }; // For the login shell we want to a clean environment, but we still have to set `$HOME` // or else most shells won't know which profile to load command .env_clear() .env("HOME", env::var("HOME").unwrap_or_default()); match command.stdout(Stdio::null()).stderr(Stdio::null()).status() { Ok(status) if status.success() => Ok(()), Ok(_) => Err(shell.to_string()), Err(err) => { eprintln!( "\n{}", wrap(&format!( "Warning: could not run {} as a login shell, skipping PATH setup check: \ {}", shell.bright_white(), err )) ); Ok(()) } } } Err(_) => { eprintln!("\nWarning: Could not determine login shell, skipping PATH setup check"); Ok(()) } } } /// Wrap a long paragraph of text to terminal width, or 80 characters if the width of the terminal /// can't be determined. Everything after the first line gets indented with four spaces. pub fn wrap(text: &str) -> String { let wrapper = Wrapper::with_termwidth().subsequent_indent(" "); wrapper.fill(text) }