Merge pull request #122 from pinnacle-comp/spawn_once

Move spawn_once logic into compositor
This commit is contained in:
Ottatop 2023-12-25 21:07:27 -06:00 committed by GitHub
commit ddb4b21720
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 233 additions and 121 deletions

79
Cargo.lock generated
View file

@ -183,7 +183,7 @@ dependencies = [
"polling",
"rustix 0.38.28",
"slab",
"tracing 0.1.37",
"tracing",
"windows-sys 0.52.0",
]
@ -1458,7 +1458,7 @@ dependencies = [
"sysinfo",
"thiserror",
"toml",
"tracing 0.2.0",
"tracing",
"tracing-appender",
"tracing-subscriber",
"x11rb 0.13.0",
@ -1483,7 +1483,7 @@ dependencies = [
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.28",
"tracing 0.1.37",
"tracing",
"windows-sys 0.48.0",
]
@ -1855,7 +1855,7 @@ dependencies = [
"smallvec",
"tempfile",
"thiserror",
"tracing 0.1.37",
"tracing",
"udev 0.8.0",
"wayland-backend",
"wayland-egl",
@ -2079,30 +2079,20 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.37"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes 0.1.24",
"tracing-core 0.1.31",
]
[[package]]
name = "tracing"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes 0.2.0",
"tracing-core 0.2.0",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror",
@ -2112,19 +2102,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.24"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]]
name = "tracing-attributes"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
@ -2133,35 +2113,30 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.31"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-core"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core 0.2.0",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.0"
source = "git+https://github.com/tokio-rs/tracing?rev=84f0a60#84f0a608123a651f268ff76347f81182f487d3b1"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
@ -2170,8 +2145,8 @@ dependencies = [
"sharded-slab",
"smallvec",
"thread_local",
"tracing 0.2.0",
"tracing-core 0.2.0",
"tracing",
"tracing-core",
"tracing-log",
]
@ -2216,6 +2191,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"

View file

@ -10,9 +10,9 @@ repository = "https://github.com/pinnacle-comp/pinnacle/"
keywords = ["wayland", "compositor", "smithay", "lua"]
[dependencies]
tracing = { git = "https://github.com/tokio-rs/tracing", rev = "84f0a60" }
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "84f0a60", features = ["env-filter"] }
tracing-appender = { git = "https://github.com/tokio-rs/tracing", rev = "84f0a60" }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-appender = "0.2.3"
smithay = { git = "https://github.com/Smithay/smithay", rev = "56b6441a14600593d13229b9584058ec19e3e18b", default-features = false, features = ["desktop", "wayland_frontend"] }
smithay-drm-extras = { git = "https://github.com/Smithay/smithay", rev = "56b6441a14600593d13229b9584058ec19e3e18b", optional = true }
thiserror = "1"

View file

@ -29,8 +29,8 @@ require("pinnacle").setup(function(pinnacle)
-- Outputs -----------------------------------------------------------------------
-- You can set your own monitor layout as I have done below for my monitors.
--
-- local lg = output.get_by_name("DP-2") --[[@as Output]]
-- local dell = output.get_by_name("DP-3") --[[@as Output]]
-- local lg = output.get_by_name("DP-2") --[[@as OutputHandle]]
-- local dell = output.get_by_name("DP-3") --[[@as OutputHandle]]
--
-- dell:set_loc_left_of(lg, "bottom")
@ -42,49 +42,55 @@ require("pinnacle").setup(function(pinnacle)
-- Mousebinds --------------------------------------------------------------------
input.mousebind({"Ctrl"}, buttons.left, "Press",
function() window.begin_move(buttons.left) end)
input.mousebind({"Ctrl"}, buttons.right, "Press",
function() window.begin_resize(buttons.right) end)
input.mousebind({ "Ctrl" }, buttons.left, "Press", function()
window.begin_move(buttons.left)
end)
input.mousebind({ "Ctrl" }, buttons.right, "Press", function()
window.begin_resize(buttons.right)
end)
-- Keybinds ----------------------------------------------------------------------
-- mod_key + Alt + q quits the compositor
input.keybind({mod_key, "Alt"}, keys.q, pinnacle.quit)
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
-- mod_key + Alt + c closes the focused window
input.keybind({mod_key, "Alt"}, keys.c,
function() window.get_focused():close() end)
input.keybind({ mod_key, "Alt" }, keys.c, function()
window.get_focused():close()
end)
-- mod_key + return spawns a terminal
input.keybind({mod_key}, keys.Return, function()
input.keybind({ mod_key }, keys.Return, function()
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
-- do something with the output here
end)
end)
-- mod_key + Alt + Space toggle floating on the focused window
input.keybind({mod_key, "Alt"}, keys.space,
function() window.get_focused():toggle_floating() end)
input.keybind({ mod_key, "Alt" }, keys.space, function()
window.get_focused():toggle_floating()
end)
-- mod_key + f toggles fullscreen on the focused window
input.keybind({mod_key}, keys.f,
function() window.get_focused():toggle_fullscreen() end)
input.keybind({ mod_key }, keys.f, function()
window.get_focused():toggle_fullscreen()
end)
-- mod_key + m toggles maximized on the focused window
input.keybind({mod_key}, keys.m,
function() window.get_focused():toggle_maximized() end)
input.keybind({ mod_key }, keys.m, function()
window.get_focused():toggle_maximized()
end)
-- Tags ---------------------------------------------------------------------------
local tags = {"1", "2", "3", "4", "5"}
local tags = { "1", "2", "3", "4", "5" }
output.connect_for_all(function(op)
-- Add tags 1, 2, 3, 4 and 5 on all monitors, and toggle tag 1 active by default
op:add_tags(tags)
-- Same as tag.add(op, "1", "2", "3", "4", "5")
tag.toggle({name = "1", output = op})
tag.toggle({ name = "1", output = op })
-- Window rules
-- Add your own window rules here. Below is an example.
@ -111,30 +117,35 @@ require("pinnacle").setup(function(pinnacle)
-- Create a layout cycler to cycle your tag layouts. This will store which layout each tag has
-- and change to the next or previous one in the array when the respective function is called.
local layout_cycler = tag.layout_cycler({
"MasterStack", "Dwindle", "Spiral", "CornerTopLeft", "CornerTopRight",
"CornerBottomLeft", "CornerBottomRight"
"MasterStack",
"Dwindle",
"Spiral",
"CornerTopLeft",
"CornerTopRight",
"CornerBottomLeft",
"CornerBottomRight",
})
input.keybind({mod_key}, keys.space, layout_cycler.next)
input.keybind({mod_key, "Shift"}, keys.space, layout_cycler.prev)
input.keybind({ mod_key }, keys.space, layout_cycler.next)
input.keybind({ mod_key, "Shift" }, keys.space, layout_cycler.prev)
-- Tag manipulation
for _, tag_name in pairs(tags) do
-- mod_key + 1-5 switches tags
input.keybind({mod_key}, tag_name,
function() tag.switch_to(tag_name) end)
input.keybind({ mod_key }, tag_name, function()
tag.switch_to(tag_name)
end)
-- mod_key + Shift + 1-5 toggles tags
input.keybind({mod_key, "Shift"}, tag_name,
function() tag.toggle(tag_name) end)
input.keybind({ mod_key, "Shift" }, tag_name, function()
tag.toggle(tag_name)
end)
-- mod_key + Alt + 1-5 moves windows to tags
input.keybind({mod_key, "Alt"}, tag_name,
function()
input.keybind({ mod_key, "Alt" }, tag_name, function()
window:get_focused():move_to_tag(tag_name)
end)
-- mod_key + Shift + Alt + 1-5 toggles tags on windows
input.keybind({mod_key, "Shift", "Alt"}, tag_name,
function()
input.keybind({ mod_key, "Shift", "Alt" }, tag_name, function()
window.get_focused():toggle_tag(tag_name)
end)
end

View file

@ -18,6 +18,7 @@
---@field WindowResizeGrab { button: integer }?
--
---@field Spawn { command: string[], callback_id: integer? }?
---@field SpawnOnce { command: string[], callback_id: integer? }?
---@field SetEnv { key: string, value: string }?
--Tags
---@field ToggleTag { tag_id: TagId }?

View file

@ -8,17 +8,10 @@
---@class ProcessModule
local process_module = {}
---Spawn a process with an optional callback for its stdout, stderr, and exit information.
---
---`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 command string|string[]
---@param callback fun(stdout: string|nil, stderr: string|nil, exit_code: integer|nil, exit_msg: string|nil)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
function process_module.spawn(command, callback)
---@param spawn_once boolean
local function spawn_inner(command, callback, spawn_once)
---@type integer|nil
local callback_id = nil
@ -40,14 +33,38 @@ function process_module.spawn(command, callback)
command_arr = command
end
SendMsg({
Spawn = {
command = command_arr,
callback_id = callback_id,
},
})
if spawn_once then
SendMsg({
SpawnOnce = {
command = command_arr,
callback_id = callback_id,
},
})
else
SendMsg({
Spawn = {
command = command_arr,
callback_id = callback_id,
},
})
end
end
---Spawn a process with an optional callback for its stdout, stderr, and exit information.
---
---`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|nil, stderr: string|nil, exit_code: integer|nil, exit_msg: string|nil)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
function process_module.spawn(command, callback)
spawn_inner(command, callback, false)
end
-- PERF: callback is stored regardless if cmd was actually spawned
---Spawn a process only if it isn't already running, with an optional callback for its stdout, stderr, and exit information.
---
---`callback` has the following parameters:
@ -61,19 +78,7 @@ end
---@param command string|string[] The command as one whole string or a table of each of its arguments
---@param callback fun(stdout: string|nil, stderr: string|nil, exit_code: integer|nil, exit_msg: string|nil)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
function process_module.spawn_once(command, callback)
local proc = ""
if type(command) == "string" then
proc = command:match("%S+")
else
proc = command[1]
end
---@type string
local procs = io.popen("pgrep -f " .. proc):read("*a")
if procs:len() ~= 0 then -- if process exists, return
return
end
process_module.spawn(command, callback)
spawn_inner(command, callback, true)
end
---Set an environment variable for Pinnacle. All future processes spawned will have this env set.

View file

@ -150,6 +150,12 @@ pub(crate) enum Msg {
#[serde(default)]
callback_id: Option<CallbackId>,
},
/// Spawn a program with an optional callback only if it isn't running.
SpawnOnce {
command: Vec<String>,
#[serde(default)]
callback_id: Option<CallbackId>,
},
SetEnv {
key: String,
value: String,

View file

@ -19,6 +19,20 @@ pub fn spawn(command: Vec<&str>) -> anyhow::Result<()> {
send_msg(msg)
}
/// Spawn a process only if it isn't already running.
///
/// This will use Rust's (more specifically `async_process`'s) `Command` to spawn the provided
/// arguments. If you are using any shell syntax like `~`, you may need to spawn a shell
/// instead. If so, you may *also* need to correctly escape the input.
pub fn spawn_once(command: Vec<&str>) -> anyhow::Result<()> {
let msg = Msg::SpawnOnce {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: None,
};
send_msg(msg)
}
/// Spawn a process with an optional callback for its stdout, stderr, and exit information.
///
/// `callback` has the following parameters:
@ -60,6 +74,49 @@ where
send_msg(msg)
}
// TODO: literally copy pasted from above, but will be rewritten so meh
/// Spawn a process with an optional callback for its stdout, stderr, and exit information,
/// only if it isn't already running.
///
/// `callback` has the following parameters:
/// - `0`: The process's stdout printed this line.
/// - `1`: The process's stderr printed this line.
/// - `2`: The process exited with this code.
/// - `3`: The process exited with this message.
/// - `4`: A `&mut `[`CallbackVec`] for use inside the closure.
///
/// You must also pass in a mutable reference to a [`CallbackVec`] in order to store your callback.
pub fn spawn_once_with_callback<'a, F>(
command: Vec<&str>,
mut callback: F,
callback_vec: &mut CallbackVec<'a>,
) -> anyhow::Result<()>
where
F: FnMut(Option<String>, Option<String>, Option<i32>, Option<String>, &mut CallbackVec) + 'a,
{
let args_callback = move |args: Option<Args>, callback_vec: &mut CallbackVec<'_>| {
if let Some(Args::Spawn {
stdout,
stderr,
exit_code,
exit_msg,
}) = args
{
callback(stdout, stderr, exit_code, exit_msg, callback_vec);
}
};
let len = callback_vec.callbacks.len();
callback_vec.callbacks.push(Box::new(args_callback));
let msg = Msg::SpawnOnce {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: Some(CallbackId(len as u32)),
};
send_msg(msg)
}
/// Set an environment variable for Pinnacle. All future processes spawned will have this env set.
///
/// Note that this will only set the variable for the compositor, not the running config process.

View file

@ -44,14 +44,12 @@ use std::{
sync::{Arc, Mutex},
};
use self::msg::{Msg, OutgoingMsg};
use anyhow::Context;
use calloop::RegistrationToken;
use smithay::reexports::calloop::{
self, channel::Sender, generic::Generic, EventSource, Interest, Mode, PostAction,
};
use sysinfo::{ProcessRefreshKind, RefreshKind, SystemExt};
use self::msg::{Msg, OutgoingMsg};
pub const SOCKET_NAME: &str = "pinnacle_socket";
@ -100,15 +98,15 @@ pub struct PinnacleSocketSource {
impl PinnacleSocketSource {
/// Create a loop source that listens for connections to the provided `socket_dir`.
/// This will also set PINNACLE_SOCKET for use in API implementations.
pub fn new(sender: Sender<Msg>, socket_dir: &Path) -> anyhow::Result<Self> {
pub fn new(
sender: Sender<Msg>,
socket_dir: &Path,
multiple_instances: bool,
) -> anyhow::Result<Self> {
tracing::debug!("Creating socket source for dir {socket_dir:?}");
let system = sysinfo::System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
);
// Test if you are running multiple instances of Pinnacle
let multiple_instances = system.processes_by_exact_name("pinnacle").count() > 1;
// let multiple_instances = system.processes_by_exact_name("pinnacle").count() > 1;
// If you are, append a suffix to the socket name
let socket_name = if multiple_instances {

View file

@ -9,6 +9,7 @@ use smithay::{
utils::{Point, Rectangle, SERIAL_COUNTER},
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
};
use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, SystemExt};
use crate::{
api::msg::{
@ -90,6 +91,31 @@ impl State {
} => {
self.handle_spawn(command, callback_id);
}
Msg::SpawnOnce {
command,
callback_id,
} => {
self.system_processes
.refresh_processes_specifics(ProcessRefreshKind::new());
let Some(arg0) = command.first() else {
tracing::warn!("No command specified for `SpawnOnce`");
return;
};
let compositor_pid = std::process::id();
let already_running =
self.system_processes
.processes_by_exact_name(arg0)
.any(|proc| {
proc.parent()
.is_some_and(|parent_pid| parent_pid.as_u32() == compositor_pid)
});
if !already_running {
self.handle_spawn(command, callback_id);
}
}
Msg::SetEnv { key, value } => std::env::set_var(key, value),
Msg::SetWindowSize {

View file

@ -125,6 +125,11 @@ pub enum Msg {
#[serde(default)]
callback_id: Option<CallbackId>,
},
SpawnOnce {
command: Vec<String>,
#[serde(default)]
callback_id: Option<CallbackId>,
},
SetEnv {
key: String,
value: String,

View file

@ -15,6 +15,7 @@ use smithay::{
input::keyboard::keysyms,
utils::{Logical, Point},
};
use sysinfo::{ProcessRefreshKind, SystemExt};
use toml::Table;
use crate::api::msg::{CallbackId, Modifier};
@ -236,7 +237,16 @@ impl State {
.unwrap_or(PathBuf::from(DEFAULT_SOCKET_DIR))
};
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir)
self.system_processes
.refresh_processes_specifics(ProcessRefreshKind::new());
let multiple_instances = self
.system_processes
.processes_by_exact_name("pinnacle")
.count()
> 1;
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir, multiple_instances)
.context("Failed to create socket source")?;
let reload_keybind = metaconfig.reload_keybind;

View file

@ -13,6 +13,7 @@
use clap::Parser;
use nix::unistd::Uid;
use sysinfo::{ProcessRefreshKind, RefreshKind, SystemExt};
use tracing_appender::rolling::Rotation;
use tracing_subscriber::{fmt::writer::MakeWriterExt, EnvFilter};
use xdg::BaseDirectories;
@ -35,6 +36,10 @@ mod window;
lazy_static::lazy_static! {
pub static ref XDG_BASE_DIRS: BaseDirectories =
BaseDirectories::with_prefix("pinnacle").expect("couldn't create xdg BaseDirectories");
pub static ref SYSTEM_PROCESSES: sysinfo::System =
sysinfo::System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
);
}
#[derive(clap::Args, Debug)]

View file

@ -41,6 +41,7 @@ use smithay::{
},
xwayland::{X11Wm, XWayland, XWaylandEvent},
};
use sysinfo::{ProcessRefreshKind, RefreshKind, SystemExt};
use crate::input::InputState;
@ -98,6 +99,8 @@ pub struct State {
pub xwayland: XWayland,
pub xwm: Option<X11Wm>,
pub xdisplay: Option<u32>,
pub system_processes: sysinfo::System,
}
impl State {
@ -281,6 +284,10 @@ impl State {
xwayland,
xwm: None,
xdisplay: None,
system_processes: sysinfo::System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
),
})
}