use std::env;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use interprocess::local_socket::{GenericFilePath, GenericNamespaced, Name, prelude::*};
use tao::window::{Window, WindowBuilder};
use wry::{WebView, WebViewBuilder};
pub const CONTEXT_PER_WINDOW: bool = cfg!(all(unix, not(target_os = "macos")));
pub const FRAMELESS: bool = cfg!(windows);
pub fn data_dir() -> PathBuf {
#[cfg(windows)]
let base = env::var_os("LOCALAPPDATA").map(PathBuf::from);
#[cfg(target_os = "macos")]
let base = env::var_os("HOME").map(|home| PathBuf::from(home).join("Library/Application Support"));
#[cfg(all(unix, not(target_os = "macos")))]
let base = env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".local/share")));
base.unwrap_or_else(env::temp_dir).join("vjuu")
}
pub fn config_dir() -> PathBuf {
#[cfg(windows)]
let base = env::var_os("APPDATA").map(PathBuf::from);
#[cfg(target_os = "macos")]
let base = env::var_os("HOME").map(|home| PathBuf::from(home).join("Library/Application Support"));
#[cfg(all(unix, not(target_os = "macos")))]
let base = env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")));
base.unwrap_or_else(env::temp_dir).join("vjuu")
}
pub fn pipe_name() -> Name<'static> {
let user = env::var("USERNAME").or_else(|_| env::var("USER")).unwrap_or_default();
let name = format!("vjuu-{user}.sock");
let name = if GenericNamespaced::is_supported() {
name.to_ns_name::<GenericNamespaced>()
} else {
env::temp_dir().join(name).to_fs_name::<GenericFilePath>()
};
name.expect("valid socket name")
}
pub fn socket_may_be_stale() -> bool {
!GenericNamespaced::is_supported()
}
pub fn keep_stdio_private() {
#[cfg(windows)]
{
use windows_sys::Win32::Foundation::{HANDLE_FLAG_INHERIT, SetHandleInformation};
use windows_sys::Win32::System::Console::{
GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
for std_handle in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
unsafe { SetHandleInformation(GetStdHandle(std_handle), HANDLE_FLAG_INHERIT, 0) };
}
}
}
pub fn own_console() -> bool {
#[cfg(windows)]
{
use windows_sys::Win32::System::Console::GetConsoleProcessList;
let mut ids = [0u32; 2];
unsafe { GetConsoleProcessList(ids.as_mut_ptr(), ids.len() as u32) <= 1 }
}
#[cfg(not(windows))]
false
}
pub fn spawn_detached(command: &mut Command) {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const DETACHED_PROCESS: u32 = 0x8;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x200;
command.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
}
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
command.process_group(0);
}
let _ = command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn();
}
pub fn open_url(url: &str) {
let opener = if cfg!(windows) {
"explorer"
} else if cfg!(target_os = "macos") {
"open"
} else {
"xdg-open"
};
let _ = Command::new(opener).arg(url).spawn();
}
pub fn frame(builder: WindowBuilder) -> WindowBuilder {
#[cfg(windows)]
{
use tao::platform::windows::{IconExtWindows, WindowBuilderExtWindows};
use tao::window::Icon;
let icon = || Icon::from_resource(1, None).ok();
builder
.with_decorations(false)
.with_undecorated_shadow(true)
.with_window_icon(icon())
.with_taskbar_icon(icon())
}
#[cfg(not(windows))]
builder
}
pub fn build_webview(builder: WebViewBuilder, window: &Window) -> wry::Result<WebView> {
#[cfg(all(unix, not(target_os = "macos")))]
{
use tao::platform::unix::WindowExtUnix;
use wry::WebViewBuilderExtUnix;
builder.build_gtk(window.default_vbox().expect("tao windows have a default GTK box"))
}
#[cfg(not(all(unix, not(target_os = "macos"))))]
builder.build(window)
}
pub fn fit_height(window: &Window) {
use tao::dpi::{PhysicalPosition, PhysicalSize};
let width = window.outer_size().width;
#[cfg(windows)]
let area = {
use tao::platform::windows::WindowExtWindows;
use windows_sys::Win32::Graphics::Gdi::{
GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromWindow,
};
let mut info = MONITORINFO { cbSize: size_of::<MONITORINFO>() as u32, ..unsafe { std::mem::zeroed() } };
let ok =
unsafe { GetMonitorInfoW(MonitorFromWindow(window.hwnd() as _, MONITOR_DEFAULTTONEAREST), &mut info) }
!= 0;
let work = info.rcWork;
ok.then(|| (work.left, work.top, (work.right - work.left) as u32, (work.bottom - work.top) as u32))
};
#[cfg(not(windows))]
let area = window.current_monitor().map(|monitor| {
let (pos, size) = (monitor.position(), monitor.size());
let height = size.height * 92 / 100;
(pos.x, pos.y + ((size.height - height) / 2) as i32, size.width, height)
});
let Some((left, top, area_width, height)) = area else {
return;
};
let width = width.min(area_width);
window.set_outer_position(PhysicalPosition::new(left + ((area_width - width) / 2) as i32, top));
window.set_inner_size(PhysicalSize::new(width, height));
#[cfg(windows)]
{
use tao::platform::windows::WindowExtWindows;
use windows_sys::Win32::Foundation::RECT;
use windows_sys::Win32::Graphics::Dwm::{DWMWA_EXTENDED_FRAME_BOUNDS, DwmGetWindowAttribute};
let mut visible: RECT = unsafe { std::mem::zeroed() };
let ok = unsafe {
DwmGetWindowAttribute(
window.hwnd() as _,
DWMWA_EXTENDED_FRAME_BOUNDS as u32,
(&raw mut visible).cast(),
size_of::<RECT>() as u32,
)
} == 0;
let overlap = visible.bottom - (top + height as i32);
if ok && overlap > 0 {
window.set_inner_size(PhysicalSize::new(width, height - overlap as u32));
}
}
}
#[cfg(windows)]
const BRAND_COLORREF: u32 = 0x002E_E2A6;
pub fn style_frame(window: &Window) {
#[cfg(windows)]
{
use tao::platform::windows::WindowExtWindows;
use windows_sys::Win32::Graphics::Dwm::{
DWMWA_BORDER_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND, DwmSetWindowAttribute,
};
let set = |attribute: i32, value: u32| unsafe {
DwmSetWindowAttribute(
window.hwnd() as _,
attribute as u32,
(&raw const value).cast(),
size_of_val(&value) as u32,
);
};
set(DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND as u32);
set(DWMWA_BORDER_COLOR, BRAND_COLORREF);
}
#[cfg(not(windows))]
let _ = window;
}
pub fn bring_to_front(window: &Window, webview: &WebView) {
#[cfg(windows)]
{
use tao::platform::windows::WindowExtWindows;
use windows_sys::Win32::System::Threading::{AttachThreadInput, GetCurrentThreadId};
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{KEYEVENTF_KEYUP, VK_MENU, keybd_event};
use windows_sys::Win32::UI::WindowsAndMessaging::{
BringWindowToTop, GetForegroundWindow, GetWindowThreadProcessId, SetForegroundWindow,
};
let hwnd = window.hwnd() as _;
unsafe {
let ours = GetCurrentThreadId();
let theirs = GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut());
let attached = theirs != 0 && theirs != ours && AttachThreadInput(ours, theirs, 1) != 0;
BringWindowToTop(hwnd);
if SetForegroundWindow(hwnd) == 0 {
keybd_event(VK_MENU as u8, 0, 0, 0);
keybd_event(VK_MENU as u8, 0, KEYEVENTF_KEYUP, 0);
SetForegroundWindow(hwnd);
}
if attached {
AttachThreadInput(ours, theirs, 0);
}
}
}
window.set_focus();
let _ = webview.focus();
}