Skip to content

Commit 14bac29

Browse files
Prevent suspension during file operations
Closes: #1408 Partially a port of: pop-os/cosmic-player#185
1 parent 6fa6d30 commit 14bac29

File tree

5 files changed

+101
-11
lines changed

5 files changed

+101
-11
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rust-version = "1.85"
88

99
[dependencies]
1010
anyhow = "1"
11+
ashpd = { version = "0.12", optional = true }
1112
chrono = { version = "0.4", features = ["unstable-locales"] }
1213
icu = { version = "2.1.1", features = ["compiled_data"] }
1314
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
@@ -103,6 +104,7 @@ default = [
103104
"notify",
104105
"wgpu",
105106
"wayland",
107+
"xdg-portal",
106108
]
107109
dbus-config = ["libcosmic/dbus-config"]
108110
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
@@ -114,6 +116,7 @@ jemalloc = ["dep:tikv-jemallocator"]
114116
notify = ["dep:notify-rust"]
115117
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
116118
wgpu = ["libcosmic/wgpu"]
119+
xdg-portal = ["dep:ashpd"]
117120

118121
[profile.dev]
119122
opt-level = 1

src/app.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,8 @@ pub struct App {
731731
tab_drag_id: DragId,
732732
auto_scroll_speed: Option<i16>,
733733
file_dialog_opt: Option<Dialog<Message>>,
734+
#[cfg(feature = "xdg-portal")]
735+
pub inhibit: tokio::sync::watch::Sender<bool>,
734736
}
735737

736738
impl App {
@@ -2072,6 +2074,20 @@ impl App {
20722074

20732075
false
20742076
}
2077+
2078+
/// Allow user to suspend or log out.
2079+
///
2080+
/// Basically, undo [`Self::inhibit_suspension`].
2081+
fn allow_suspend(&self) {
2082+
#[cfg(feature = "xdg-portal")]
2083+
let _ = self.inhibit.send(false);
2084+
}
2085+
2086+
/// Prevent suspending during active file operations.
2087+
fn inhibit_suspend(&self) {
2088+
#[cfg(feature = "xdg-portal")]
2089+
let _ = self.inhibit.send(true);
2090+
}
20752091
}
20762092

20772093
/// Implement [`Application`] to integrate with COSMIC.
@@ -2153,6 +2169,15 @@ impl Application for App {
21532169
),
21542170
]);
21552171

2172+
// The inhibit task must be created here since we need a runtime and libcosmic spawns
2173+
// the runtime - not main!
2174+
#[cfg(feature = "xdg-portal")]
2175+
let inhibit = {
2176+
let (tx, rx) = tokio::sync::watch::channel(false);
2177+
std::mem::drop(tokio::spawn(crate::xdg_portals::inhibit(rx)));
2178+
tx
2179+
};
2180+
21562181
let mut app = Self {
21572182
core,
21582183
about,
@@ -2204,6 +2229,8 @@ impl Application for App {
22042229
file_dialog_opt: None,
22052230
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
22062231
layer_sizes: FxHashMap::default(),
2232+
#[cfg(feature = "xdg-portal")]
2233+
inhibit,
22072234
};
22082235

22092236
let mut commands = vec![app.update_config()];
@@ -6126,7 +6153,7 @@ impl Application for App {
61266153
}));
61276154

61286155
if !self.pending_operations.is_empty() {
6129-
//TODO: inhibit suspend/shutdown?
6156+
self.inhibit_suspend();
61306157

61316158
if self.core.main_window_id().is_some() {
61326159
// Force refresh the UI every 100ms while an operation is active.
@@ -6179,6 +6206,8 @@ impl Application for App {
61796206
));
61806207
}
61816208
}
6209+
} else {
6210+
self.allow_suspend();
61826211
}
61836212

61846213
let mut selected_preview = None;

src/lib.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
// Copyright 2023 System76 <info@system76.com>
22
// SPDX-License-Identifier: GPL-3.0-only
33

4-
use cosmic::{app::Settings, iced::Limits};
5-
use std::{env, fs, path::PathBuf, process};
6-
7-
use app::{App, Flags};
84
pub mod app;
95
mod archive;
106
pub mod clipboard;
11-
use config::Config;
127
pub mod config;
138
pub mod dialog;
149
mod key_bind;
@@ -21,13 +16,22 @@ mod mounter;
2116
mod mouse_area;
2217
pub mod operation;
2318
mod spawn_detached;
24-
use tab::Location;
25-
mod zoom;
26-
27-
use crate::config::State;
2819
pub mod tab;
2920
mod thumbnail_cacher;
3021
mod thumbnailer;
22+
mod zoom;
23+
24+
#[cfg(feature = "xdg-portal")]
25+
pub mod xdg_portals;
26+
27+
use cosmic::{app::Settings, iced::Limits};
28+
use std::{env, fs, path::PathBuf, process};
29+
30+
use crate::{
31+
app::{App, Flags},
32+
config::{Config, State},
33+
tab::Location,
34+
};
3135

3236
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
3337

@@ -96,7 +100,7 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
96100
state,
97101
mode: app::Mode::Desktop,
98102
locations,
99-
uris: Vec::new()
103+
uris: Vec::new(),
100104
};
101105
cosmic::app::run::<App>(settings, flags)?;
102106

src/xdg_portals.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 System76 <info@system76.com>
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
//! Integrations with XDG portals.
5+
6+
use ashpd::{
7+
desktop::inhibit::{InhibitFlags, InhibitProxy, SessionState},
8+
enumflags2::{BitFlags, make_bitflags},
9+
};
10+
use futures::StreamExt;
11+
use log::warn;
12+
use tokio::sync::watch::Receiver;
13+
14+
const INHIBIT_FLAGS: BitFlags<InhibitFlags> =
15+
make_bitflags!(InhibitFlags::{Logout | UserSwitch | Suspend});
16+
17+
/// Inhibit suspension and shutdown while file operations are in progress.
18+
///
19+
/// # Usage
20+
/// Enable the inhibitor by setting the watcher to `true`. Disable the inhibitor by sending a
21+
/// `false`. Sending multiple consecutive trues/falses is safe and guarded internally.
22+
///
23+
/// Portal:
24+
/// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html
25+
pub async fn inhibit(mut signal: Receiver<bool>) -> ashpd::Result<()> {
26+
let proxy = InhibitProxy::new().await?;
27+
let session = proxy.create_monitor(None).await?;
28+
// Mark the watcher's value as unseen so we don't need a temporary bool and branch for the
29+
// initial state.
30+
signal.mark_changed();
31+
32+
let mut states = proxy.receive_state_changed().await?;
33+
while let Some(SessionState::QueryEnd) = states.next().await.map(|state| state.session_state())
34+
{
35+
// Copying the bool is important or else we would needlessly hold the lock below.
36+
let should_inhibit = *signal.borrow_and_update();
37+
38+
if should_inhibit {
39+
// XXX: Better message (if the message even matters).
40+
let _ = proxy
41+
.inhibit(None, INHIBIT_FLAGS, "File operations in progress")
42+
.await
43+
.inspect_err(|e| warn!("Failed to call inhibit endpoint: {e}"));
44+
}
45+
46+
let _ = proxy
47+
.query_end_response(&session)
48+
.await
49+
.inspect_err(|e| warn!("Error sending QueryEnd to the XDG portal: {e}"));
50+
}
51+
52+
Ok(())
53+
}

0 commit comments

Comments
 (0)