aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs55
-rw-r--r--src/niri.rs335
2 files changed, 207 insertions, 183 deletions
diff --git a/src/main.rs b/src/main.rs
index 66616c7b..42656f80 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,6 +20,7 @@ mod dummy_pw_utils;
mod pw_utils;
use std::ffi::OsString;
use std::path::PathBuf;
+use std::process::Command;
use std::{env, mem};
use clap::Parser;
@@ -29,6 +30,7 @@ use dummy_pw_utils as pw_utils;
use miette::{Context, NarratableReportHandler};
use niri::{Niri, State};
use portable_atomic::Ordering;
+use sd_notify::NotifyState;
use smithay::reexports::calloop::{self, EventLoop};
use smithay::reexports::wayland_server::Display;
use tracing_subscriber::EnvFilter;
@@ -52,6 +54,8 @@ fn main() {
env::set_var("RUST_LIB_BACKTRACE", "0");
}
+ let is_systemd_service = env::var_os("NOTIFY_SOCKET").is_some();
+
let directives = env::var("RUST_LOG").unwrap_or_else(|_| "niri=debug,info".to_owned());
let env_filter = EnvFilter::builder().parse_lossy(directives);
tracing_subscriber::fmt()
@@ -63,6 +67,7 @@ fn main() {
let _client = tracy_client::Client::start();
+ // Load the config.
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))).unwrap();
let (mut config, path) = match Config::load(cli.config).context("error loading config") {
Ok((config, path)) => (config, Some(path)),
@@ -74,6 +79,7 @@ fn main() {
animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed);
let spawn_at_startup = mem::take(&mut config.spawn_at_startup);
+ // Create the compositor.
let mut event_loop = EventLoop::try_new().unwrap();
let display = Display::new().unwrap();
let mut state = State::new(
@@ -83,6 +89,26 @@ fn main() {
display,
);
+ // Set WAYLAND_DISPLAY for children.
+ let socket_name = &state.niri.socket_name;
+ env::set_var("WAYLAND_DISPLAY", socket_name);
+ info!(
+ "listening on Wayland socket: {}",
+ socket_name.to_string_lossy()
+ );
+
+ if is_systemd_service {
+ // We're starting as a systemd service. Export our variables.
+ import_env_to_systemd();
+ }
+
+ state.niri.start_dbus(&state.backend, is_systemd_service);
+
+ // Notify systemd we're ready.
+ if let Err(err) = sd_notify::notify(true, &[NotifyState::Ready]) {
+ warn!("error notifying systemd: {err:?}");
+ };
+
// Set up config file watcher.
let _watcher = if let Some(path) = path {
let (tx, rx) = calloop::channel::sync_channel(1);
@@ -110,7 +136,36 @@ fn main() {
}
}
+ // Run the compositor.
event_loop
.run(None, &mut state, |state| state.refresh_and_flush_clients())
.unwrap();
}
+
+fn import_env_to_systemd() {
+ let rv = Command::new("/bin/sh")
+ .args([
+ "-c",
+ "systemctl --user import-environment WAYLAND_DISPLAY && \
+ hash dbus-update-activation-environment 2>/dev/null && \
+ dbus-update-activation-environment WAYLAND_DISPLAY",
+ ])
+ .spawn();
+ // Wait for the import process to complete, otherwise services will start too fast without
+ // environment variables available.
+ match rv {
+ Ok(mut child) => match child.wait() {
+ Ok(status) => {
+ if !status.success() {
+ warn!("import environment shell exited with {status}");
+ }
+ }
+ Err(err) => {
+ warn!("error waiting for import environment shell: {err:?}");
+ }
+ },
+ Err(err) => {
+ warn!("error spawning shell to import environment into systemd: {err:?}");
+ }
+ }
+}
diff --git a/src/niri.rs b/src/niri.rs
index 1c32fdd4..6c8a1d50 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -1,8 +1,8 @@
use std::cell::RefCell;
use std::cmp::max;
use std::collections::HashMap;
+use std::ffi::OsString;
use std::path::PathBuf;
-use std::process::Command;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
@@ -10,7 +10,6 @@ use std::{env, mem, thread};
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
use anyhow::Context;
-use sd_notify::NotifyState;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
@@ -92,6 +91,7 @@ pub struct Niri {
pub event_loop: LoopHandle<'static, State>,
pub stop_signal: LoopSignal,
pub display_handle: DisplayHandle,
+ pub socket_name: OsString,
// Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
// however it may have none (when there are no outputs connected) or mutiple (when mirroring).
@@ -138,8 +138,6 @@ pub struct Niri {
pub zbus_conn: Option<zbus::blocking::Connection>,
pub inhibit_power_key_fd: Option<zbus::zvariant::OwnedFd>,
- #[cfg(feature = "xdp-gnome-screencast")]
- pub screen_cast: ScreenCast,
// Casts are dropped before PipeWire to prevent a double-free (yay).
pub casts: Vec<Cast>,
@@ -308,6 +306,53 @@ impl State {
// FIXME: apply xkb settings.
// FIXME: apply xdg decoration settings.
}
+
+ #[cfg(feature = "xdp-gnome-screencast")]
+ fn on_screen_cast_msg(
+ &mut self,
+ to_niri: &calloop::channel::Sender<ToNiriMsg>,
+ msg: ToNiriMsg,
+ ) {
+ match msg {
+ ToNiriMsg::StartCast {
+ session_id,
+ output,
+ cursor_mode,
+ signal_ctx,
+ } => {
+ let _span = tracy_client::span!("StartCast");
+
+ debug!(session_id, "StartCast");
+
+ let gbm = match self.backend.gbm_device() {
+ Some(gbm) => gbm,
+ None => {
+ debug!("no GBM device available");
+ return;
+ }
+ };
+
+ let pw = self.niri.pipewire.as_ref().unwrap();
+ match pw.start_cast(
+ to_niri.clone(),
+ gbm,
+ session_id,
+ output,
+ cursor_mode,
+ signal_ctx,
+ ) {
+ Ok(cast) => {
+ self.niri.casts.push(cast);
+ }
+ Err(err) => {
+ warn!("error starting screencast: {err:?}");
+ self.niri.stop_cast(session_id);
+ }
+ }
+ }
+ ToNiriMsg::StopCast { session_id } => self.niri.stop_cast(session_id),
+ }
+ }
}
impl Niri {
@@ -390,11 +435,6 @@ impl Niri {
}
})
.unwrap();
- std::env::set_var("WAYLAND_DISPLAY", &socket_name);
- info!(
- "listening on Wayland socket: {}",
- socket_name.to_string_lossy()
- );
let pipewire = match PipeWire::new(&event_loop) {
Ok(pipewire) => Some(pipewire),
@@ -404,89 +444,75 @@ impl Niri {
}
};
+ let display_source = Generic::new(display, Interest::READ, Mode::Level);
+ event_loop
+ .insert_source(display_source, |_, display, state| {
+ // SAFETY: we don't drop the display.
+ unsafe {
+ display.get_mut().dispatch_clients(state).unwrap();
+ }
+ Ok(PostAction::Continue)
+ })
+ .unwrap();
+
+ drop(config_);
+ Self {
+ config,
+
+ event_loop,
+ stop_signal,
+ display_handle,
+ socket_name,
+
+ layout,
+ global_space: Space::default(),
+ output_state: HashMap::new(),
+ output_by_name: HashMap::new(),
+ unmapped_windows: HashMap::new(),
+ monitors_active: true,
+
+ compositor_state,
+ xdg_shell_state,
+ xdg_decoration_state,
+ kde_decoration_state,
+ layer_shell_state,
+ text_input_state,
+ input_method_state,
+ virtual_keyboard_state,
+ shm_state,
+ output_manager_state,
+ seat_state,
+ tablet_state,
+ pointer_gestures_state,
+ data_device_state,
+ primary_selection_state,
+ data_control_state,
+ popups: PopupManager::default(),
+ presentation_state,
+
+ seat,
+ default_cursor,
+ cursor_image: CursorImageStatus::default_named(),
+ dnd_icon: None,
+
+ zbus_conn: None,
+ inhibit_power_key_fd: None,
+ pipewire,
+ casts: vec![],
+ }
+ }
+
+ pub fn start_dbus(&mut self, backend: &Backend, is_session_instance: bool) {
+ let config = self.config.borrow();
+
#[cfg(feature = "xdp-gnome-screencast")]
let (to_niri, from_screen_cast) = calloop::channel::channel();
#[cfg(feature = "xdp-gnome-screencast")]
- event_loop
+ self.event_loop
.insert_source(from_screen_cast, {
let to_niri = to_niri.clone();
move |event, _, state| match event {
- calloop::channel::Event::Msg(msg) => match msg {
- ToNiriMsg::StartCast {
- session_id,
- output,
- cursor_mode,
- signal_ctx,
- } => {
- let _span = tracy_client::span!("StartCast");
-
- debug!(session_id, "StartCast");
-
- let gbm = match state.backend.gbm_device() {
- Some(gbm) => gbm,
- None => {
- debug!("no GBM device available");
- return;
- }
- };
-
- let pw = state.niri.pipewire.as_ref().unwrap();
- match pw.start_cast(
- to_niri.clone(),
- gbm,
- session_id,
- output,
- cursor_mode,
- signal_ctx,
- ) {
- Ok(cast) => {
- state.niri.casts.push(cast);
- }
- Err(err) => {
- warn!("error starting screencast: {err:?}");
-
- if let Err(err) =
- to_niri.send(ToNiriMsg::StopCast { session_id })
- {
- warn!("error sending StopCast to niri: {err:?}");
- }
- }
- }
- }
- ToNiriMsg::StopCast { session_id } => {
- let _span = tracy_client::span!("StopCast");
-
- debug!(session_id, "StopCast");
-
- for i in (0..state.niri.casts.len()).rev() {
- let cast = &state.niri.casts[i];
- if cast.session_id != session_id {
- continue;
- }
-
- let cast = state.niri.casts.swap_remove(i);
- if let Err(err) = cast.stream.disconnect() {
- warn!("error disconnecting stream: {err:?}");
- }
- }
-
- let server = state.niri.zbus_conn.as_ref().unwrap().object_server();
- let path =
- format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
- if let Ok(iface) =
- server.interface::<_, mutter_screen_cast::Session>(path)
- {
- let _span = tracy_client::span!("invoking Session::stop");
-
- async_io::block_on(async move {
- iface
- .get()
- .stop(&server, iface.signal_context().clone())
- .await
- });
- }
- }
- },
+ calloop::channel::Event::Msg(msg) => state.on_screen_cast_msg(&to_niri, msg),
calloop::channel::Event::Closed => (),
}
})
@@ -496,7 +522,7 @@ impl Niri {
let (to_niri, from_screenshot) = calloop::channel::channel();
let (to_screenshot, from_niri) = async_channel::unbounded();
- event_loop
+ self.event_loop
.insert_source(from_screenshot, move |event, _, state| match event {
calloop::channel::Event::Msg(ScreenshotToNiri::TakeScreenshot {
include_cursor,
@@ -539,43 +565,14 @@ impl Niri {
let mut zbus_conn = None;
let mut inhibit_power_key_fd = None;
- if std::env::var_os("NOTIFY_SOCKET").is_some() {
- // We're starting as a systemd service. Export our variables and tell systemd we're
- // ready.
- let rv = Command::new("/bin/sh")
- .args([
- "-c",
- "systemctl --user import-environment WAYLAND_DISPLAY && \
- hash dbus-update-activation-environment 2>/dev/null && \
- dbus-update-activation-environment WAYLAND_DISPLAY",
- ])
- .spawn();
- // Wait for the import process to complete, otherwise services will start too fast
- // without environment variables available.
- match rv {
- Ok(mut child) => match child.wait() {
- Ok(status) => {
- if !status.success() {
- warn!("import environment shell exited with {status}");
- }
- }
- Err(err) => {
- warn!("error waiting for import environment shell: {err:?}");
- }
- },
- Err(err) => {
- warn!("error spawning shell to import environment into systemd: {err:?}");
- }
- }
-
- // Set up zbus, make sure it happens before anything might want it.
+ if is_session_instance {
let conn = zbus::blocking::ConnectionBuilder::session()
.unwrap()
.name("org.gnome.Mutter.ServiceChannel")
.unwrap()
.serve_at(
"/org/gnome/Mutter/ServiceChannel",
- ServiceChannel::new(display_handle.clone()),
+ ServiceChannel::new(self.display_handle.clone()),
)
.unwrap()
.build()
@@ -603,7 +600,7 @@ impl Niri {
.unwrap();
#[cfg(feature = "xdp-gnome-screencast")]
- if pipewire.is_some() {
+ if self.pipewire.is_some() {
server
.at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
.unwrap();
@@ -614,11 +611,6 @@ impl Niri {
zbus_conn = Some(conn);
- // Notify systemd we're ready.
- if let Err(err) = sd_notify::notify(true, &[NotifyState::Ready]) {
- warn!("error notifying systemd: {err:?}");
- };
-
// Inhibit power key handling so we can suspend on it.
let zbus_system_conn = zbus::blocking::ConnectionBuilder::system()
.unwrap()
@@ -644,7 +636,7 @@ impl Niri {
warn!("error inhibiting power key: {err:?}");
}
}
- } else if config_.debug.dbus_interfaces_in_non_session_instances {
+ } else if config.debug.dbus_interfaces_in_non_session_instances {
let conn = zbus::blocking::Connection::session().unwrap();
let flags = RequestNameFlags::AllowReplacement
| RequestNameFlags::ReplaceExisting
@@ -669,7 +661,7 @@ impl Niri {
.unwrap();
#[cfg(feature = "xdp-gnome-screencast")]
- if pipewire.is_some() {
+ if self.pipewire.is_some() {
server
.at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
.unwrap();
@@ -681,63 +673,8 @@ impl Niri {
zbus_conn = Some(conn);
}
- let display_source = Generic::new(display, Interest::READ, Mode::Level);
- event_loop
- .insert_source(display_source, |_, display, state| {
- // SAFETY: we don't drop the display.
- unsafe {
- display.get_mut().dispatch_clients(state).unwrap();
- }
- Ok(PostAction::Continue)
- })
- .unwrap();
-
- drop(config_);
- Self {
- config,
-
- event_loop,
- stop_signal,
- display_handle,
-
- layout,
- global_space: Space::default(),
- output_state: HashMap::new(),
- output_by_name: HashMap::new(),
- unmapped_windows: HashMap::new(),
- monitors_active: true,
-
- compositor_state,
- xdg_shell_state,
- xdg_decoration_state,
- kde_decoration_state,
- layer_shell_state,
- text_input_state,
- input_method_state,
- virtual_keyboard_state,
- shm_state,
- output_manager_state,
- seat_state,
- tablet_state,
- pointer_gestures_state,
- data_device_state,
- primary_selection_state,
- data_control_state,
- popups: PopupManager::default(),
- presentation_state,
-
- seat,
- default_cursor,
- cursor_image: CursorImageStatus::default_named(),
- dnd_icon: None,
-
- zbus_conn,
- inhibit_power_key_fd,
- #[cfg(feature = "xdp-gnome-screencast")]
- screen_cast,
- pipewire,
- casts: vec![],
- }
+ self.zbus_conn = zbus_conn;
+ self.inhibit_power_key_fd = inhibit_power_key_fd;
}
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
@@ -1627,6 +1564,38 @@ impl Niri {
}
}
+ #[cfg(feature = "xdp-gnome-screencast")]
+ fn stop_cast(&mut self, session_id: usize) {
+ let _span = tracy_client::span!("Niri::stop_cast");
+
+ debug!(session_id, "StopCast");
+
+ for i in (0..self.casts.len()).rev() {
+ let cast = &self.casts[i];
+ if cast.session_id != session_id {
+ continue;
+ }
+
+ let cast = self.casts.swap_remove(i);
+ if let Err(err) = cast.stream.disconnect() {
+ warn!("error disconnecting stream: {err:?}");
+ }
+ }
+
+ let server = self.zbus_conn.as_ref().unwrap().object_server();
+ let path = format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
+ if let Ok(iface) = server.interface::<_, mutter_screen_cast::Session>(path) {
+ let _span = tracy_client::span!("invoking Session::stop");
+
+ async_io::block_on(async move {
+ iface
+ .get()
+ .stop(&server, iface.signal_context().clone())
+ .await
+ });
+ }
+ }
+
pub fn screenshot(
&mut self,
renderer: &mut GlesRenderer,