Merge pull request #12 from Ottatop/dev

Raise fd limit, add minor docs
This commit is contained in:
Ottatop 2023-06-28 16:44:04 -05:00 committed by GitHub
commit 8f218c6be2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 161 additions and 143 deletions

View file

@ -99,6 +99,7 @@ function client.get_window(identifier)
local response = ReadMsg()
local props = response.RequestResponse.response.Window.window
---@type Window
local win = {
id = props.id,

View file

@ -11,8 +11,8 @@ pcall(require, "luarocks.loader")
-- Neovim users be like:
require("pinnacle").setup(function(pinnacle)
local input = pinnacle.input --Key and mouse binds
local client = pinnacle.client --Window management
local input = pinnacle.input -- Key and mouse binds
local client = pinnacle.client -- Window management
local process = pinnacle.process -- Process spawning
-- Every key supported by xkbcommon.

View file

@ -4,11 +4,19 @@
--
-- SPDX-License-Identifier: MPL-2.0
---@diagnostic disable: redefined-local
local process = {}
---Spawn a process with an optional callback for its stdout and stderr.
---
---`callback` has the following parameters:
--- - `stdout`: The process's stdout printed this line.
--- - `stderr`: The process's stderr printed this line.
--- - `exit_code`: The process exited with this code.
--- - `exit_msg`: The process exited with this message.
---@param command string|string[] The command as one whole string or a table of each of its arguments
---@param callback fun(stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string?)? A callback to do something whenever the process's stdout or stderr print a line. Only one will be non-nil at a time.
---@param callback fun(stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string?)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
function process.spawn(command, callback)
---@type integer|nil
local callback_id = nil

View file

@ -6,19 +6,16 @@
use std::{
error::Error,
ffi::OsString,
ffi::{CString, OsString},
io::{BufRead, BufReader},
os::{fd::AsRawFd, unix::net::UnixStream},
process::Stdio,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
},
sync::{Arc, Mutex},
};
use crate::{
api::{
msg::{Args, Msg, OutgoingMsg, Request, RequestResponse},
msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse},
PinnacleSocketSource,
},
focus::FocusState,
@ -94,7 +91,7 @@ pub struct State<B: Backend> {
}
impl<B: Backend> State<B> {
/// Create the main [State].
/// Create the main [`State`].
///
/// This will set the WAYLAND_DISPLAY environment variable, insert Wayland necessary sources
/// into the event loop, and run an implementation of the config API (currently Lua).
@ -109,6 +106,19 @@ impl<B: Backend> State<B> {
std::env::set_var("WAYLAND_DISPLAY", socket_name.clone());
// Opening a new process will use up a few file descriptors, around 10 for Alacritty, for
// example. Because of this, opening up only around 100 processes would exhaust the file
// descriptor limit on my system (Arch btw) and cause a "Too many open files" crash.
//
// To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical
// environment, I *think* this is ok.
smithay::reexports::nix::sys::resource::setrlimit(
smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE,
65536,
65536 * 2,
)
.unwrap();
loop_handle.insert_source(socket, |stream, _metadata, data| {
data.display
.handle()
@ -133,6 +143,7 @@ impl<B: Backend> State<B> {
Event::Msg(msg) => {
// TODO: move this into its own function
// TODO: no like seriously this is getting a bit unwieldy
// TODO: no like rustfmt literally refuses to format the code below
match msg {
Msg::SetKeybind {
key,
@ -164,139 +175,7 @@ impl<B: Backend> State<B> {
command,
callback_id,
} => {
let mut command = command.into_iter().peekable();
if command.peek().is_none() {
// TODO: notify that command was nothing
return;
}
// TODO: may need to set env for WAYLAND_DISPLAY
let mut child =
std::process::Command::new(OsString::from(command.next().unwrap()))
.env("WAYLAND_DISPLAY", data.state.socket_name.clone())
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(if callback_id.is_some() {
Stdio::piped()
} else {
// piping to null because foot won't open without a callback_id
// otherwise
Stdio::null()
})
.stdout(if callback_id.is_some() {
Stdio::piped()
} else {
Stdio::null()
})
.stderr(if callback_id.is_some() {
Stdio::piped()
} else {
Stdio::null()
})
.args(command)
.spawn()
.unwrap(); // TODO: handle unwrap
// TODO: find a way to make this hellish code look better, deal with unwraps
if let Some(callback_id) = callback_id {
let stdout = child.stdout.take();
let stderr = child.stderr.take();
let stream_out = data.state.api_state.stream.as_ref().unwrap().clone();
let stream_err = stream_out.clone();
let stream_exit = stream_out.clone();
// TODO: make this not use 3 whole threads per process
if let Some(stdout) = stdout {
std::thread::spawn(move || {
let mut reader = BufReader::new(stdout);
loop {
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => break, // stream closed
Ok(_) => {
let mut stream = stream_out.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: Some(
buf.trim_end_matches('\n')
.to_string(),
),
stderr: None,
exit_code: None,
exit_msg: None,
}),
},
)
.unwrap();
}
Err(err) => {
tracing::error!("child read err: {err}");
break;
}
}
}
});
}
if let Some(stderr) = stderr {
std::thread::spawn(move || {
let mut reader = BufReader::new(stderr);
loop {
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => break, // stream closed
Ok(_) => {
let mut stream = stream_err.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: None,
stderr: Some(
buf.trim_end_matches('\n')
.to_string(),
),
exit_code: None,
exit_msg: None,
}),
},
)
.unwrap();
}
Err(err) => {
tracing::error!("child read err: {err}");
break;
}
}
}
});
}
std::thread::spawn(move || match child.wait() {
Ok(exit_status) => {
let mut stream = stream_exit.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: None,
stderr: None,
exit_code: exit_status.code(),
exit_msg: Some(exit_status.to_string()),
}),
},
)
.unwrap()
}
Err(err) => {
tracing::warn!("child wait() err: {err}");
}
});
}
data.state.handle_spawn(command, callback_id);
}
Msg::SpawnShell {
shell,
@ -490,6 +369,136 @@ impl<B: Backend> State<B> {
popup_manager: PopupManager::default(),
})
}
pub fn handle_spawn(&self, command: Vec<String>, callback_id: Option<CallbackId>) {
let mut command = command.into_iter().peekable();
if command.peek().is_none() {
// TODO: notify that command was nothing
return;
}
let mut child = std::process::Command::new(OsString::from(command.next().unwrap()))
.env("WAYLAND_DISPLAY", self.socket_name.clone())
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(if callback_id.is_some() {
Stdio::piped()
} else {
// piping to null because foot won't open without a callback_id
// otherwise
Stdio::null()
})
.stdout(if callback_id.is_some() {
Stdio::piped()
} else {
Stdio::null()
})
.stderr(if callback_id.is_some() {
Stdio::piped()
} else {
Stdio::null()
})
.args(command)
.spawn()
.unwrap(); // TODO: handle unwrap
// TODO: find a way to make this hellish code look better, deal with unwraps
if let Some(callback_id) = callback_id {
let stdout = child.stdout.take();
let stderr = child.stderr.take();
let stream_out = self.api_state.stream.as_ref().unwrap().clone();
let stream_err = stream_out.clone();
let stream_exit = stream_out.clone();
// TODO: make this not use 3 whole threads per process
if let Some(stdout) = stdout {
std::thread::spawn(move || {
let mut reader = BufReader::new(stdout);
loop {
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => break, // stream closed
Ok(_) => {
let mut stream = stream_out.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: Some(buf.trim_end_matches('\n').to_string()),
stderr: None,
exit_code: None,
exit_msg: None,
}),
},
)
.unwrap();
}
Err(err) => {
tracing::error!("child read err: {err}");
break;
}
}
}
});
}
if let Some(stderr) = stderr {
std::thread::spawn(move || {
let mut reader = BufReader::new(stderr);
loop {
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => break, // stream closed
Ok(_) => {
let mut stream = stream_err.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: None,
stderr: Some(buf.trim_end_matches('\n').to_string()),
exit_code: None,
exit_msg: None,
}),
},
)
.unwrap();
}
Err(err) => {
tracing::error!("child read err: {err}");
break;
}
}
}
});
}
std::thread::spawn(move || match child.wait() {
Ok(exit_status) => {
let mut stream = stream_exit.lock().unwrap();
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::Spawn {
stdout: None,
stderr: None,
exit_code: exit_status.code(),
exit_msg: Some(exit_status.to_string()),
}),
},
)
.unwrap()
}
Err(err) => {
tracing::warn!("child wait() err: {err}");
}
});
} else {
std::thread::spawn(move || child.wait());
}
}
}
pub struct CalloopData<B: Backend> {