mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat!: switch to resvg as the SVG renderer (#2581)
This commit is contained in:
parent
be00881403
commit
0082d1181a
9 changed files with 243 additions and 69 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -3386,6 +3386,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"futures",
|
||||
"globset",
|
||||
"libc",
|
||||
"md-5",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
|
|
@ -3400,6 +3401,7 @@ dependencies = [
|
|||
"twox-hash",
|
||||
"unicode-width 0.2.0",
|
||||
"uzers",
|
||||
"windows-sys 0.59.0",
|
||||
"yazi-adapter",
|
||||
"yazi-binding",
|
||||
"yazi-boot",
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ unicode-width = { workspace = true }
|
|||
yazi-prebuilt = "0.1.0"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
uzers = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
clipboard-win = "5.4.0"
|
||||
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_System_JobObjects", "Win32_System_Threading" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -25,18 +25,44 @@ function M:preload(job)
|
|||
end
|
||||
|
||||
-- stylua: ignore
|
||||
local cmd = require("magick").with_env():args {
|
||||
"-size", string.format("%dx%d^", rt.preview.max_width, rt.preview.max_height),
|
||||
string.format("rsvg:%s", job.file.url), "-strip",
|
||||
"-quality", rt.preview.image_quality,
|
||||
string.format("JPG:%s", cache),
|
||||
local cmd = Command("resvg"):args {
|
||||
"-w", rt.preview.max_width, "-h", rt.preview.max_height,
|
||||
"--image-rendering", "optimizeSpeed",
|
||||
tostring(job.file.url), tostring(cache)
|
||||
}
|
||||
if rt.tasks.image_alloc > 0 then
|
||||
cmd = cmd:memory(rt.tasks.image_alloc)
|
||||
end
|
||||
|
||||
local child, err = cmd:spawn()
|
||||
if not child then
|
||||
return true, Err("Failed to start `resvg`, error: %s", err)
|
||||
end
|
||||
|
||||
local status, err
|
||||
while true do
|
||||
ya.sleep(0.2)
|
||||
|
||||
status, err = child:try_wait()
|
||||
if status or err then
|
||||
break
|
||||
end
|
||||
|
||||
local id, mem = child:id(), nil
|
||||
if id then
|
||||
mem = ya.proc_info(id).mem_resident
|
||||
end
|
||||
if mem and mem > rt.tasks.image_alloc then
|
||||
child:start_kill()
|
||||
err = Err("memory limit exceeded, pid: %s, memory: %s", id, mem)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local status, err = cmd:status()
|
||||
if status then
|
||||
return status.success
|
||||
else
|
||||
return true, Err("Failed to start `magick`, error: %s", err)
|
||||
return true, Err("Error while running `resvg`: %s", err)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function M:preload(job)
|
|||
"-skip_frame", "nokey", "-ss", ss,
|
||||
"-an", "-sn", "-dn",
|
||||
"-i", tostring(job.file.url),
|
||||
"-vframes", 1,
|
||||
"-map", "0:v", "-vframes", 1,
|
||||
"-q:v", qv,
|
||||
"-vf", string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height),
|
||||
"-f", "image2",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{ops::DerefMut, time::Duration};
|
||||
use std::{ops::DerefMut, process::ExitStatus, time::Duration};
|
||||
|
||||
use futures::future::try_join3;
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, Value};
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||
use yazi_binding::Error;
|
||||
|
||||
|
|
@ -9,39 +9,94 @@ use super::Status;
|
|||
use crate::process::Output;
|
||||
|
||||
pub struct Child {
|
||||
inner: tokio::process::Child,
|
||||
stdin: Option<BufWriter<ChildStdin>>,
|
||||
stdout: Option<BufReader<ChildStdout>>,
|
||||
stderr: Option<BufReader<ChildStderr>>,
|
||||
inner: tokio::process::Child,
|
||||
stdin: Option<BufWriter<ChildStdin>>,
|
||||
stdout: Option<BufReader<ChildStdout>>,
|
||||
stderr: Option<BufReader<ChildStderr>>,
|
||||
#[cfg(windows)]
|
||||
job_handle: Option<std::os::windows::io::RawHandle>,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for Child {
|
||||
fn drop(&mut self) {
|
||||
if let Some(h) = self.job_handle.take() {
|
||||
unsafe { windows_sys::Win32::Foundation::CloseHandle(h) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Child {
|
||||
pub fn new(mut inner: tokio::process::Child) -> Self {
|
||||
pub fn new(
|
||||
mut inner: tokio::process::Child,
|
||||
#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,
|
||||
) -> Self {
|
||||
let stdin = inner.stdin.take().map(BufWriter::new);
|
||||
let stdout = inner.stdout.take().map(BufReader::new);
|
||||
let stderr = inner.stderr.take().map(BufReader::new);
|
||||
Self { inner, stdin, stdout, stderr }
|
||||
Self {
|
||||
inner,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
#[cfg(windows)]
|
||||
job_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
drop(self.stdin.take());
|
||||
self.inner.wait().await
|
||||
}
|
||||
|
||||
pub(super) async fn status(&mut self) -> io::Result<ExitStatus> {
|
||||
drop(self.stdin.take());
|
||||
drop(self.stdout.take());
|
||||
drop(self.stderr.take());
|
||||
self.inner.wait().await
|
||||
}
|
||||
|
||||
async fn read_line(&mut self) -> (Option<Vec<u8>>, u8) {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
match r?.read_until(b'\n', &mut buf).await {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf),
|
||||
}
|
||||
}
|
||||
|
||||
select! {
|
||||
r @ Some(_) = read(self.stdout.as_mut()) => (r, 0u8),
|
||||
r @ Some(_) = read(self.stderr.as_mut()) => (r, 1u8),
|
||||
else => (None, 2u8),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn wait_with_output(mut self) -> io::Result<std::process::Output> {
|
||||
async fn read(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
|
||||
let mut vec = Vec::new();
|
||||
if let Some(r) = r.as_mut() {
|
||||
r.read_to_end(&mut vec).await?;
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
// Ensure stdin is closed so the child isn't stuck waiting on input while the
|
||||
// parent is waiting for it to exit.
|
||||
drop(self.stdin.take());
|
||||
|
||||
// Drop happens after `try_join` due to <https://github.com/tokio-rs/tokio/issues/4309>
|
||||
let mut stdout = self.stdout.take();
|
||||
let mut stderr = self.stderr.take();
|
||||
|
||||
let result = try_join3(self.inner.wait(), read(&mut stdout), read(&mut stderr)).await?;
|
||||
Ok(std::process::Output { status: result.0, stdout: result.1, stderr: result.2 })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Child {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
#[inline]
|
||||
async fn read_line(me: &mut Child) -> (Option<Vec<u8>>, u8) {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
match r?.read_until(b'\n', &mut buf).await {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf),
|
||||
}
|
||||
}
|
||||
|
||||
select! {
|
||||
r @ Some(_) = read(me.stdout.as_mut()) => (r, 0u8),
|
||||
r @ Some(_) = read(me.stderr.as_mut()) => (r, 1u8),
|
||||
else => (None, 2u8),
|
||||
}
|
||||
}
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("id", |_, me, ()| Ok(me.inner.id()));
|
||||
|
||||
methods.add_async_method_mut("read", |_, mut me, len: usize| async move {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
||||
|
|
@ -62,14 +117,15 @@ impl UserData for Child {
|
|||
})
|
||||
});
|
||||
methods.add_async_method_mut("read_line", |lua, mut me, ()| async move {
|
||||
match read_line(&mut me).await {
|
||||
match me.read_line().await {
|
||||
(Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua),
|
||||
(None, event) => (Value::Nil, event).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
// TODO: deprecate this method
|
||||
methods.add_async_method_mut("read_line_with", |lua, mut me, options: Table| async move {
|
||||
let timeout = Duration::from_millis(options.raw_get("timeout")?);
|
||||
let Ok(result) = tokio::time::timeout(timeout, read_line(&mut me)).await else {
|
||||
let Ok(result) = tokio::time::timeout(timeout, me.read_line()).await else {
|
||||
return (Value::Nil, 3u8).into_lua_multi(&lua);
|
||||
};
|
||||
match result {
|
||||
|
|
@ -98,38 +154,21 @@ impl UserData for Child {
|
|||
});
|
||||
|
||||
methods.add_async_method_mut("wait", |lua, mut me, ()| async move {
|
||||
drop(me.stdin.take());
|
||||
match me.inner.wait().await {
|
||||
match me.wait().await {
|
||||
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
|
||||
async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
|
||||
let mut vec = Vec::new();
|
||||
if let Some(r) = r.as_mut() {
|
||||
r.read_to_end(&mut vec).await?;
|
||||
}
|
||||
Ok(vec)
|
||||
match ud.take::<Self>()?.wait_with_output().await {
|
||||
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
||||
let mut me = ud.take::<Self>()?;
|
||||
let mut stdout_pipe = me.stdout.take();
|
||||
let mut stderr_pipe = me.stderr.take();
|
||||
|
||||
let stdout_fut = read_to_end(&mut stdout_pipe);
|
||||
let stderr_fut = read_to_end(&mut stderr_pipe);
|
||||
|
||||
drop(me.stdin.take());
|
||||
let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await;
|
||||
drop(stdout_pipe);
|
||||
drop(stderr_pipe);
|
||||
|
||||
match result {
|
||||
Ok((status, stdout, stderr)) => {
|
||||
(Output::new(std::process::Output { status, stdout, stderr }), Value::Nil)
|
||||
.into_lua_multi(&lua)
|
||||
}
|
||||
});
|
||||
methods.add_async_method_mut("try_wait", |lua, mut me, ()| async move {
|
||||
match me.inner.try_wait() {
|
||||
Ok(Some(status)) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Ok(None) => (Value::Nil, Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::process::Stdio;
|
||||
use std::{io, process::Stdio};
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value};
|
||||
use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
|
||||
|
|
@ -8,7 +8,8 @@ use super::{Child, output::Output};
|
|||
use crate::process::Status;
|
||||
|
||||
pub struct Command {
|
||||
inner: tokio::process::Command,
|
||||
inner: tokio::process::Command,
|
||||
memory: Option<usize>,
|
||||
}
|
||||
|
||||
const NULL: u8 = 0;
|
||||
|
|
@ -21,7 +22,7 @@ impl Command {
|
|||
let mut inner = tokio::process::Command::new(program);
|
||||
inner.kill_on_drop(true).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
Ok(Self { inner })
|
||||
Ok(Self { inner, memory: None })
|
||||
})?;
|
||||
|
||||
let command = lua.create_table_from([
|
||||
|
|
@ -35,6 +36,74 @@ impl Command {
|
|||
|
||||
lua.globals().raw_set("Command", command)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn spawn(&mut self) -> io::Result<Child> {
|
||||
if let Some(max) = self.memory {
|
||||
unsafe {
|
||||
self.inner.pre_exec(move || {
|
||||
let rlp = libc::rlimit { rlim_cur: max as _, rlim_max: max as _ };
|
||||
libc::setrlimit(libc::RLIMIT_AS, &rlp);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
self.inner.spawn().map(Child::new)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn spawn(&mut self) -> io::Result<Child> {
|
||||
use std::os::windows::io::RawHandle;
|
||||
|
||||
use windows_sys::Win32::System::JobObjects::{AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_PROCESS_MEMORY, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, SetInformationJobObject};
|
||||
|
||||
fn assign_job(max: usize, handle: RawHandle) -> io::Result<RawHandle> {
|
||||
unsafe {
|
||||
let job = CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
|
||||
if job.is_null() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
|
||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
|
||||
info.ProcessMemoryLimit = max;
|
||||
|
||||
let result = SetInformationJobObject(
|
||||
job,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut info as *mut _ as *mut _,
|
||||
std::mem::size_of_val(&info) as u32,
|
||||
);
|
||||
|
||||
if result == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else if AssignProcessToJobObject(job, handle) == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let child = self.inner.spawn()?;
|
||||
if let (Some(max), Some(handle)) = (self.memory, child.raw_handle()) {
|
||||
if let Ok(job) = assign_job(max, handle) {
|
||||
return Ok(Child::new(child, Some(job)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Child::new(child, None))
|
||||
}
|
||||
|
||||
async fn output(&mut self) -> io::Result<std::process::Output> {
|
||||
self.inner.stdin(Stdio::piped());
|
||||
self.inner.stdout(Stdio::piped());
|
||||
self.spawn()?.wait_with_output().await
|
||||
}
|
||||
|
||||
async fn status(&mut self) -> io::Result<std::process::ExitStatus> {
|
||||
self.spawn()?.status().await
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Command {
|
||||
|
|
@ -102,18 +171,22 @@ impl UserData for Command {
|
|||
ud.borrow_mut::<Self>()?.inner.stderr(make_stdio(stdio)?);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_method_mut("spawn", |lua, me, ()| match me.inner.spawn() {
|
||||
Ok(child) => (Child::new(child), Value::Nil).into_lua_multi(lua),
|
||||
methods.add_function_mut("memory", |_, (ud, max): (AnyUserData, usize)| {
|
||||
ud.borrow_mut::<Self>()?.memory = Some(max);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_method_mut("spawn", |lua, me, ()| match me.spawn() {
|
||||
Ok(child) => (child, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(lua),
|
||||
});
|
||||
methods.add_async_method_mut("output", |lua, mut me, ()| async move {
|
||||
match me.inner.output().await {
|
||||
match me.output().await {
|
||||
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
methods.add_async_method_mut("status", |lua, mut me, ()| async move {
|
||||
match me.inner.status().await {
|
||||
match me.status().await {
|
||||
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(
|
||||
app cache call image json layer log preview spot sync target text time user utils
|
||||
app cache call image json layer log preview process spot sync target text time user utils
|
||||
);
|
||||
|
|
|
|||
29
yazi-plugin/src/utils/process.rs
Normal file
29
yazi-plugin/src/utils/process.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use mlua::{Function, Lua};
|
||||
|
||||
use super::Utils;
|
||||
|
||||
impl Utils {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, pid: usize| {
|
||||
let info = unsafe {
|
||||
let mut info: libc::proc_taskinfo = std::mem::zeroed();
|
||||
libc::proc_pidinfo(
|
||||
pid as _,
|
||||
libc::PROC_PIDTASKINFO,
|
||||
0,
|
||||
&mut info as *mut _ as *mut _,
|
||||
std::mem::size_of_val(&info) as _,
|
||||
);
|
||||
info
|
||||
};
|
||||
|
||||
lua.create_table_from([("mem_resident", info.pti_resident_size)])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, ()| lua.create_table())
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,9 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result<Value> {
|
|||
b"preview_code" => Utils::preview_code(lua)?,
|
||||
b"preview_widgets" => Utils::preview_widgets(lua)?,
|
||||
|
||||
// Process
|
||||
b"proc_info" => Utils::proc_info(lua)?,
|
||||
|
||||
// Spot
|
||||
b"spot_table" => Utils::spot_table(lua)?,
|
||||
b"spot_widgets" => Utils::spot_widgets(lua)?,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue