Compare commits

...

12 commits

Author SHA1 Message Date
Michael Aaron Murphy
495e591dc6 fix(debian): seatd conflict causes Ctrl+C to kill the session
Add it as a conflict since it conflicts with systemd-logind
2026-05-07 11:50:27 -06:00
Vukašin Vojinović
17cf4485a9 chore(rustfmt): change imports_granularity to "Module"
Also includes a Zed config.
2026-05-02 08:35:47 -06:00
Vukašin Vojinović
673e4c949b chore: update dependencies
Removes the `cosmic-notifications-utils` dependency, and instead defines the notification FDs here (since they're unlikely to change).
2026-05-02 08:35:47 -06:00
Vukašin Vojinović
a77aaa1904 chore: clippy 2026-05-02 08:35:47 -06:00
Vukašin Vojinović
56f0115952 improv(main): use tokio signals for SIGINT and SIGTERM
This allows removing async-signals and libc.
2026-05-02 08:35:47 -06:00
Tony Wasserka
9a55864e81 fix: set XDG_SESSION_DESKTOP in start-cosmic
On NixOS, autologin bypasses cosmic-greeter and instead launches start-cosmic
directly. The XDG_SESSION_DESKTOP variable previously wasn't set hence, which
would break functionality like session commands ("Power Off") in the launcher.
2026-04-13 11:49:23 -06:00
Adil Hanney
02a4c58c3d fix: use QT_QPA_PLATFORMTHEME=qt5ct not qt6ct
The version of qt5ct shipped with Pop!_OS requires this. qt6ct still accepts either value.
2026-03-18 13:53:58 -04:00
Adil Hanney
af9aaf9c1a fix: don't overwrite QT_QPA_PLATFORMTHEME
This lets you set QT_QPA_PLATFORMTHEME in ~/.profile if you need to.
2026-03-18 13:53:58 -04:00
Levi Portenier
59b48b5b3b
Merge pull request #183 from thefossguy/fix-whitespace-vars-checking
start-cosmic: handle ANSI-C quoted strings returned by systemd
2026-03-10 13:16:45 -06:00
Adil Hanney
618624bcc0
feat: enable cutecosmic or qt6ct with QT_QPA_PLATFORMTHEME 2026-02-20 21:36:45 +01:00
Pratham Patel
a823516ebd
start-cosmic: handle ANSI-C quoted strings returned by systemd 2026-01-30 16:39:46 +05:30
Thomas Wouters
42752142e3 Use mapfile to read environment variables
Looping `tr '\n' ' '` would break if variables contain whitespace
2026-01-28 11:30:46 +01:00
10 changed files with 687 additions and 993 deletions

15
.zed/settings.json Normal file
View file

@ -0,0 +1,15 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

1461
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,25 +4,21 @@ description = "The session manager for the COSMIC desktop environment"
version = "1.0.0" version = "1.0.0"
license = "GPL-3.0-only" license = "GPL-3.0-only"
edition = "2024" edition = "2024"
rust-version = "1.85" rust-version = "1.93"
authors = ["Lucy <lucy@system76.com>"] authors = ["Lucy <lucy@system76.com>"]
publish = false publish = false
[dependencies] [dependencies]
async-signals = "0.5"
color-eyre = "0.6" color-eyre = "0.6"
futures-util = "0.3" futures-util = "0.3"
cosmic-dbus-a11y = { git = "https://github.com/pop-os/dbus-settings-bindings" } cosmic-dbus-a11y = { git = "https://github.com/pop-os/dbus-settings-bindings" }
freedesktop-desktop-entry = { version = "0.7.14", optional = true } freedesktop-desktop-entry = { version = "0.8", optional = true }
shell-words = { version = "1.1.0", optional = true } shell-words = { version = "1.1.1", optional = true }
dirs = { version = "6.0.0", optional = true } dirs = { version = "6.0.0", optional = true }
itertools = "0.14"
launch-pad = { git = "https://github.com/pop-os/launch-pad" } launch-pad = { git = "https://github.com/pop-os/launch-pad" }
libc = "0.2"
log-panics = { version = "2", features = ["with-backtrace"] } log-panics = { version = "2", features = ["with-backtrace"] }
rustix = "1.0" rustix = "1.1"
scopeguard = "1" scopeguard = "1"
sendfd = { version = "0.4", features = ["tokio"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tokio = { version = "1", features = [ tokio = { version = "1", features = [
@ -38,15 +34,14 @@ tokio = { version = "1", features = [
"sync", "sync",
"time", "time",
] } ] }
zbus_systemd = { version = "0.25701.0", optional = true, features = [ zbus_systemd = { version = "0.26000.0", optional = true, features = [
"systemd1", "systemd1",
] } ] }
tokio-util = "0.7" tokio-util = "0.7"
tracing = "0.1" tracing = "0.1"
tracing-journald = { version = "0.3", optional = true } tracing-journald = { version = "0.3", optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
zbus = { version = "5.10.0", default-features = false, features = ["tokio"] } zbus = { version = "5.14.0", default-features = false, features = ["tokio"] }
cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" }
logind-zbus = { version = "5.3.2", optional = true } logind-zbus = { version = "5.3.2", optional = true }
[features] [features]

View file

@ -29,6 +29,7 @@ if [ -n "${SHELL}" ]; then
fi fi
export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:=COSMIC}" export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:=COSMIC}"
export XDG_SESSION_DESKTOP="${XDG_SESSION_DESKTOP:=COSMIC}"
export XDG_SESSION_TYPE="${XDG_SESSION_TYPE:=wayland}" export XDG_SESSION_TYPE="${XDG_SESSION_TYPE:=wayland}"
export _JAVA_AWT_WM_NONREPARENTING=1 export _JAVA_AWT_WM_NONREPARENTING=1
export GDK_BACKEND=wayland,x11 export GDK_BACKEND=wayland,x11
@ -38,6 +39,22 @@ export QT_AUTO_SCREEN_SCALE_FACTOR=1
export QT_ENABLE_HIGHDPI_SCALING=1 export QT_ENABLE_HIGHDPI_SCALING=1
export DCONF_PROFILE=cosmic export DCONF_PROFILE=cosmic
# Set the QT platform theme to CuteCosmic. Fallback to qt6ct if CuteCosmic is not installed.
if [ -z "$QT_QPA_PLATFORMTHEME" ]; then
export QT_QPA_PLATFORMTHEME=cosmic
for QT_PLUGIN_PATH in /usr/lib{*,/*}/qt6/plugins; do
if [ -f "${QT_PLUGIN_PATH}/platformthemes/libcutecosmictheme.so" ]; then
# CuteCosmic found, no need for a fallback.
export QT_QPA_PLATFORMTHEME=cosmic
break
elif [ -f "${QT_PLUGIN_PATH}/platformthemes/libqt6ct.so" ] || [ -f "${QT_PLUGIN_PATH}/platformthemes/libqt5ct.so" ]; then
# Fallback to qt6ct, but keep looking for CuteCosmic.
# Note that "qt5ct" is compatible with both qt5ct and qt6ct.
export QT_QPA_PLATFORMTHEME=qt5ct
fi
done
fi
# Start gnome keyring components if the daemon is active # Start gnome keyring components if the daemon is active
# -> check if /run/user/$UID/keyring exists # -> check if /run/user/$UID/keyring exists
if [ -d "/run/user/$(id -u)/keyring" ]; then if [ -d "/run/user/$(id -u)/keyring" ]; then
@ -69,16 +86,23 @@ if command -v systemctl >/dev/null; then
# environment, update it. # environment, update it.
mapfile -t existing_env_vars < <(systemctl --user show-environment) mapfile -t existing_env_vars < <(systemctl --user show-environment)
for env_var in "${existing_env_vars[@]}"; do for env_var in "${existing_env_vars[@]}"; do
env_var_name="$(echo "${env_var}" | awk -F '=' '{print $1}')" env_var_name="${env_var%%=*}"
env_var_val_str_to_compare="${env_var_name}=${!env_var_name:-}" env_var_value=${!env_var_name:-}
if [[ "${env_var}" != "${env_var_val_str_to_compare}" ]]; then # Skip current iteration if the environment variable's value
# Update only if the value in current environment is non-empty # in the current envionment is unset.
env_var_unassigned_str="${env_var_name}=" if [[ -z "${env_var_value}" ]]; then
if [[ "${env_var_val_str_to_compare}" != "${env_var_unassigned_str}" ]]; then continue
systemctl --user import-environment "${env_var_name}" ||:
fi
fi fi
env_var_val_str_to_compare="${env_var_name}=${env_var_value}"
env_var_val_str_to_compare_ansi_c_quoted="${env_var_name}=\$'$(printf '%q' "${env_var_value}")'"
if [[ "${env_var}" == "${env_var_val_str_to_compare}" ]]; then
continue
elif [[ "${env_var}" == "${env_var_val_str_to_compare_ansi_c_quoted}" ]]; then
continue
fi
systemctl --user import-environment "${env_var_name}" ||:
done done
fi fi

1
debian/control vendored
View file

@ -12,6 +12,7 @@ Homepage: https://github.com/pop-os/cosmic-session
Package: cosmic-session Package: cosmic-session
Architecture: amd64 arm64 Architecture: amd64 arm64
Conflicts: seatd
Depends: Depends:
${misc:Depends}, ${misc:Depends},
${shlibs:Depends}, ${shlibs:Depends},

View file

@ -4,7 +4,7 @@ use_field_init_shorthand = true
# Unstable formatting options below; remove if you REALLY don't wanna use `cargo +nightly fmt` # Unstable formatting options below; remove if you REALLY don't wanna use `cargo +nightly fmt`
format_code_in_doc_comments = true format_code_in_doc_comments = true
format_strings = true format_strings = true
imports_granularity = "Crate" imports_granularity = "Module"
normalize_comments = true normalize_comments = true
reorder_impl_items = true reorder_impl_items = true
wrap_comments = true wrap_comments = true

View file

@ -1,17 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use color_eyre::eyre::{Result, WrapErr}; use color_eyre::eyre::{Result, WrapErr};
use launch_pad::{ProcessManager, process::Process}; use launch_pad::ProcessManager;
use launch_pad::process::Process;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, os::unix::prelude::*}; use std::collections::HashMap;
use tokio::{ use std::os::unix::prelude::*;
io::AsyncReadExt, use tokio::io::AsyncReadExt;
net::{UnixStream, unix::OwnedReadHalf}, use tokio::net::UnixStream;
sync::{mpsc, oneshot}, use tokio::net::unix::OwnedReadHalf;
task::JoinHandle, use tokio::sync::{mpsc, oneshot};
}; use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use crate::{process::mark_as_not_cloexec, service::SessionRequest}; use crate::process::mark_as_not_cloexec;
use crate::service::SessionRequest;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "message")] #[serde(rename_all = "snake_case", tag = "message")]

View file

@ -9,36 +9,36 @@ mod process;
mod service; mod service;
mod systemd; mod systemd;
use async_signals::Signals; use color_eyre::Result;
use color_eyre::{Result, eyre::WrapErr}; use color_eyre::eyre::WrapErr;
use cosmic_notifications_util::{DAEMON_NOTIFICATIONS_FD, PANEL_NOTIFICATIONS_FD}; use launch_pad::ProcessManager;
use futures_util::StreamExt; use launch_pad::process::Process;
#[cfg(feature = "autostart")]
use itertools::Itertools;
use launch_pad::{ProcessManager, process::Process};
use service::SessionRequest; use service::SessionRequest;
use std::borrow::Cow;
#[cfg(feature = "autostart")] #[cfg(feature = "autostart")]
use std::collections::HashSet; use std::collections::HashSet;
use std::env;
use std::os::fd::AsRawFd;
#[cfg(feature = "autostart")] #[cfg(feature = "autostart")]
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(feature = "autostart")] #[cfg(feature = "autostart")]
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::{borrow::Cow, env, os::fd::AsRawFd, sync::Arc}; use std::sync::Arc;
#[cfg(feature = "systemd")] #[cfg(feature = "systemd")]
use systemd::{get_systemd_env, is_systemd_used, spawn_scope}; use systemd::{get_systemd_env, is_systemd_used, spawn_scope};
use tokio::{ use tokio::signal::unix::{SignalKind, signal};
sync::{ use tokio::sync::mpsc::{Receiver, Sender};
Mutex, use tokio::sync::{Mutex, oneshot};
mpsc::{Receiver, Sender}, use tokio::time::Duration;
oneshot,
},
time::Duration,
};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tracing::{Instrument, metadata::LevelFilter}; use tracing::Instrument;
use tracing_subscriber::{EnvFilter, fmt, prelude::*}; use tracing::metadata::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{EnvFilter, fmt};
use crate::notifications::notifications_process; use crate::notifications::{
DAEMON_NOTIFICATIONS_FD, PANEL_NOTIFICATIONS_FD, notifications_process,
};
#[cfg(feature = "autostart")] #[cfg(feature = "autostart")]
const AUTOSTART_DIR: &'static str = "autostart"; const AUTOSTART_DIR: &'static str = "autostart";
#[cfg(feature = "autostart")] #[cfg(feature = "autostart")]
@ -165,7 +165,7 @@ async fn start(
Ok(env) => { Ok(env) => {
for systemd_env in env { for systemd_env in env {
// Only update the envvar if unset // Only update the envvar if unset
if std::env::var_os(&systemd_env.key) == None { if std::env::var_os(&systemd_env.key).is_none() {
// Blacklist of envvars that we shouldn't touch (taken from KDE) // Blacklist of envvars that we shouldn't touch (taken from KDE)
if (!systemd_env.key.starts_with("XDG_") if (!systemd_env.key.starts_with("XDG_")
|| systemd_env.key == "XDG_DATA_DIRS" || systemd_env.key == "XDG_DATA_DIRS"
@ -244,10 +244,8 @@ async fn start(
.instrument(stderr_span) .instrument(stderr_span)
}) })
.with_on_exit(move |_, _, _, will_restart| { .with_on_exit(move |_, _, _, will_restart| {
if !will_restart { if !will_restart && let Some(tx) = settings_exit_tx.lock().unwrap().take() {
if let Some(tx) = settings_exit_tx.lock().unwrap().take() { _ = tx.send(());
_ = tx.send(());
}
} }
async {} async {}
}), }),
@ -415,7 +413,9 @@ async fn start(
if let Some(program_name) = exec_words.next() { if let Some(program_name) = exec_words.next() {
// filter out any placeholder args, since we might not be able to deal with them // filter out any placeholder args, since we might not be able to deal with them
let filtered_args = exec_words.filter(|s| !s.starts_with("%")).collect_vec(); let filtered_args = exec_words
.filter(|s| !s.starts_with("%"))
.collect::<Vec<_>>();
// escape them // escape them
let escaped_args = shell_words::split(&*filtered_args.join(" ")); let escaped_args = shell_words::split(&*filtered_args.join(" "));
@ -457,7 +457,8 @@ async fn start(
info!("started {} programs", dedupe.len()); info!("started {} programs", dedupe.len());
} }
let mut signals = Signals::new(vec![libc::SIGTERM, libc::SIGINT]).unwrap(); let mut sigterm = signal(SignalKind::terminate()).expect("Failed to bind SIGTERM handler");
let mut sigint = signal(SignalKind::interrupt()).expect("Failed to bind SIGINT handler");
let mut status = Status::Exited; let mut status = Status::Exited;
let session_dbus_rx_next = session_rx.recv(); let session_dbus_rx_next = session_rx.recv();
tokio::select! { tokio::select! {
@ -475,12 +476,11 @@ async fn start(
} }
} }
}, },
signal = signals.next() => match signal { _ = sigterm.recv() => {
Some(libc::SIGTERM | libc::SIGINT) => { info!("EXITING: received SIGTERM request to terminate");
info!("EXITING: received request to terminate"); },
} _ = sigint.recv() => {
Some(signal) => unreachable!("EXITING: received unhandled signal {}", signal), info!("EXITING: received SIGINT request to terminate");
None => {},
} }
} }
@ -534,17 +534,15 @@ async fn start_component(
}) })
.with_on_start(move |pman, pkey, _will_restart| async move { .with_on_start(move |pman, pkey, _will_restart| async move {
#[cfg(feature = "systemd")] #[cfg(feature = "systemd")]
if *is_systemd_used() { if *is_systemd_used()
if let Ok((innr_cmd, Some(pid))) = pman.get_exe_and_pid(pkey).await { && let Ok((innr_cmd, Some(pid))) = pman.get_exe_and_pid(pkey).await
if let Err(err) = spawn_scope(innr_cmd.clone(), vec![pid]).await { && let Err(err) = spawn_scope(innr_cmd.clone(), vec![pid]).await
warn!( {
"Failed to spawn scope for {}. Creating transient unit failed \ warn!(
with {}", "Failed to spawn scope for {}. Creating transient unit failed with {}",
innr_cmd, err innr_cmd, err
); );
}; };
}
}
}) })
.with_on_exit(move |mut _pman, _key, err_code, _will_restart| { .with_on_exit(move |mut _pman, _key, err_code, _will_restart| {
if let Some(err) = err_code { if let Some(err) = err_code {

View file

@ -1,14 +1,17 @@
use color_eyre::{Result, eyre::Context}; use color_eyre::Result;
use cosmic_notifications_util::{DAEMON_NOTIFICATIONS_FD, PANEL_NOTIFICATIONS_FD}; use color_eyre::eyre::Context;
use launch_pad::{ProcessKey, process::Process}; use launch_pad::ProcessKey;
use launch_pad::process::Process;
use rustix::fd::AsRawFd; use rustix::fd::AsRawFd;
use std::{ use std::os::fd::OwnedFd;
os::{fd::OwnedFd, unix::net::UnixStream}, use std::os::unix::net::UnixStream;
sync::Arc, use std::sync::Arc;
};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::Instrument; use tracing::Instrument;
pub const PANEL_NOTIFICATIONS_FD: &str = "PANEL_NOTIFICATIONS_FD";
pub const DAEMON_NOTIFICATIONS_FD: &str = "DAEMON_NOTIFICATIONS_FD";
pub fn create_socket() -> Result<(OwnedFd, OwnedFd)> { pub fn create_socket() -> Result<(OwnedFd, OwnedFd)> {
// Create a new pair of unnamed Unix sockets // Create a new pair of unnamed Unix sockets
let (sock_1, sock_2) = UnixStream::pair().wrap_err("failed to create socket pair")?; let (sock_1, sock_2) = UnixStream::pair().wrap_err("failed to create socket pair")?;
@ -26,6 +29,7 @@ pub fn create_socket() -> Result<(OwnedFd, OwnedFd)> {
Ok((OwnedFd::from(sock_1), OwnedFd::from(sock_2))) Ok((OwnedFd::from(sock_1), OwnedFd::from(sock_2)))
} }
#[allow(clippy::too_many_arguments)]
pub fn notifications_process( pub fn notifications_process(
span: tracing::Span, span: tracing::Span,
cmd: &'static str, cmd: &'static str,

View file

@ -1,15 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use std::{ use std::path::Path;
path::Path, use std::process::{Command, Stdio};
process::{Command, Stdio}, use std::sync::OnceLock;
sync::OnceLock,
};
use zbus::{ use zbus::Connection;
Connection, use zbus::zvariant::{Array, OwnedValue};
zvariant::{Array, OwnedValue},
};
#[derive(Debug)] #[derive(Debug)]
pub struct EnvVar { pub struct EnvVar {
@ -17,11 +13,11 @@ pub struct EnvVar {
pub value: String, pub value: String,
} }
impl Into<EnvVar> for (&str, &str) { impl From<(&str, &str)> for EnvVar {
fn into(self) -> EnvVar { fn from(val: (&str, &str)) -> Self {
EnvVar { EnvVar {
key: self.0.to_owned(), key: val.0.to_owned(),
value: self.1.to_owned(), value: val.1.to_owned(),
} }
} }
} }