mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-18 22:26:12 +01:00
commit
8f218c6be2
4 changed files with 161 additions and 143 deletions
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
289
src/state.rs
289
src/state.rs
|
@ -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> {
|
||||
|
|
Loading…
Reference in a new issue