Skip to content

Commit 0e5eeb2

Browse files
feat: Background portal
I based my implementation on the official specs as well as code from GNOME, KDE, Xapp (Xfce), and Pantheon (elementary). KDE's imminently readable codebase served as this implementation's primary inspiration. Autostart is deprecated but seemingly still used, so this implementation will still support it for compatibility. The Background portal depends on a working Access portal as `xdg-desktop-portal` calls it to show the initial warning. References: * https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Background.html * https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome/-/blob/main/src/background.c * https://invent.kde.org/plasma/xdg-desktop-portal-kde/-/blob/master/src/background.cpp * https://github.com/linuxmint/xdg-desktop-portal-xapp/blob/f1c24244f90571209c56b7f45802b70e80da4922/src/background.c * https://github.com/elementary/portals/blob/d868cfa854c731e0f37615e225d5db07cc3f4604/src/Background/Portal.vala * flatpak/xdg-desktop-portal#1188
1 parent 41c1e7c commit 0e5eeb2

File tree

10 files changed

+488
-15
lines changed

10 files changed

+488
-15
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize, Serialize)]
6+
#[serde(deny_unknown_fields)]
7+
pub struct Background {
8+
/// Default preference for NotifyBackground's dialog
9+
pub default_perm: PermissionDialog,
10+
}
11+
12+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
13+
pub enum PermissionDialog {
14+
/// Grant apps permission to run in the background
15+
Allow,
16+
/// Deny apps permission to run in the background
17+
Deny,
18+
/// Always ask if new apps should be granted background permissions
19+
#[default]
20+
Ask,
21+
}

cosmic-portal-config/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22

3+
pub mod background;
34
pub mod screenshot;
45

56
use cosmic_config::{cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry};
67
use serde::{Deserialize, Serialize};
78

9+
use background::Background;
810
use screenshot::Screenshot;
911

1012
pub const APP_ID: &str = "com.system76.CosmicPortal";
@@ -17,6 +19,8 @@ pub const CONFIG_VERSION: u64 = 1;
1719
pub struct Config {
1820
/// Interactive screenshot settings
1921
pub screenshot: Screenshot,
22+
/// Background portal settings
23+
pub background: Background,
2024
}
2125

2226
impl Config {

data/cosmic.portal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[portal]
22
DBusName=org.freedesktop.impl.portal.desktop.cosmic
3-
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast
3+
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast
44
UseIn=cosmic

examples/background.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use ashpd::desktop::background::Background;
2+
3+
#[tokio::main(flavor = "current_thread")]
4+
async fn main() -> ashpd::Result<()> {
5+
let command = std::env::args()
6+
.nth(1)
7+
.unwrap_or_else(|| "FakeTestApp".into());
8+
9+
// Based off of the ashpd docs
10+
// https://docs.rs/ashpd/latest/ashpd/desktop/background/index.html
11+
let response = Background::request()
12+
.reason("Testing the background portal")
13+
.auto_start(false)
14+
.dbus_activatable(false)
15+
.command(&[command])
16+
.send()
17+
.await?
18+
.response()?;
19+
20+
assert!(!response.auto_start(), "Auto start should be disabled");
21+
assert!(
22+
response.run_in_background(),
23+
"App should have background permissions"
24+
);
25+
26+
Ok(())
27+
}

i18n/en/xdg_desktop_portal_cosmic.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ share-screen = Share your screen
1313
unknown-application = Unknown Application
1414
output = Output
1515
window = Window
16+
17+
# Background portal
18+
allow-once = Allow once
19+
deny = Deny
20+
bg-dialog-title = Background
21+
bg-dialog-body = {$appname} requests to run in the background. This will allow it to run without any open windows.

src/app.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{access, config, file_chooser, fl, screencast_dialog, screenshot, subscription};
1+
use crate::{
2+
access, background, config, file_chooser, fl, screencast_dialog, screenshot, subscription,
3+
};
24
use cosmic::iced_core::event::wayland::OutputEvent;
35
use cosmic::widget::{self, dropdown};
46
use cosmic::Command;
@@ -29,6 +31,7 @@ pub struct CosmicPortal {
2931

3032
pub config_handler: Option<cosmic_config::Config>,
3133
pub config: config::Config,
34+
pub tx_conf: Option<tokio::sync::watch::Sender<config::Config>>,
3235

3336
pub access_args: Option<access::AccessDialogArgs>,
3437
pub access_choices: Vec<(Option<usize>, Vec<String>)>,
@@ -43,6 +46,8 @@ pub struct CosmicPortal {
4346
pub prev_rectangle: Option<screenshot::Rect>,
4447
pub wayland_helper: crate::wayland::WaylandHelper,
4548

49+
pub background_prompts: HashMap<window::Id, background::Args>,
50+
4651
pub outputs: Vec<OutputState>,
4752
pub active_output: Option<WlOutput>,
4853
}
@@ -64,8 +69,10 @@ pub enum Msg {
6469
FileChooser(window::Id, file_chooser::Msg),
6570
Screenshot(screenshot::Msg),
6671
Screencast(screencast_dialog::Msg),
72+
Background(background::Msg),
6773
Portal(subscription::Event),
6874
Output(OutputEvent, WlOutput),
75+
ConfigNotifyWatcher,
6976
ConfigSetScreenshot(config::screenshot::Screenshot),
7077
/// Update config from external changes
7178
ConfigSubUpdate(config::Config),
@@ -127,6 +134,7 @@ impl cosmic::Application for CosmicPortal {
127134
core,
128135
config_handler,
129136
config,
137+
tx_conf: None,
130138
access_args: Default::default(),
131139
access_choices: Default::default(),
132140
file_choosers: Default::default(),
@@ -135,6 +143,7 @@ impl cosmic::Application for CosmicPortal {
135143
screencast_tab_model: Default::default(),
136144
location_options: Vec::new(),
137145
prev_rectangle: Default::default(),
146+
background_prompts: Default::default(),
138147
outputs: Default::default(),
139148
active_output: Default::default(),
140149
wayland_helper,
@@ -155,6 +164,8 @@ impl cosmic::Application for CosmicPortal {
155164
screencast_dialog::view(self).map(Msg::Screencast)
156165
} else if self.outputs.iter().any(|o| o.id == id) {
157166
screenshot::view(self, id).map(Msg::Screenshot)
167+
} else if self.background_prompts.contains_key(&id) {
168+
background::view(self, id).map(Msg::Background)
158169
} else {
159170
file_chooser::view(self, id)
160171
}
@@ -181,19 +192,25 @@ impl cosmic::Application for CosmicPortal {
181192
subscription::Event::CancelScreencast(handle) => {
182193
screencast_dialog::cancel(self, handle).map(cosmic::app::Message::App)
183194
}
195+
subscription::Event::Background(args) => {
196+
background::update_args(self, args).map(cosmic::app::Message::App)
197+
}
184198
subscription::Event::Config(config) => self.update(Msg::ConfigSubUpdate(config)),
185199
subscription::Event::Accent(_)
186200
| subscription::Event::IsDark(_)
187-
| subscription::Event::HighContrast(_) => cosmic::iced::Command::none(),
188-
subscription::Event::Init(tx) => {
201+
| subscription::Event::HighContrast(_)
202+
| subscription::Event::BackgroundToplevels => cosmic::iced::Command::none(),
203+
subscription::Event::Init { tx, tx_conf } => {
189204
self.tx = Some(tx);
190-
Command::none()
205+
self.tx_conf = Some(tx_conf);
206+
self.update(Msg::ConfigNotifyWatcher)
191207
}
192208
},
193209
Msg::Screenshot(m) => screenshot::update_msg(self, m).map(cosmic::app::Message::App),
194210
Msg::Screencast(m) => {
195211
screencast_dialog::update_msg(self, m).map(cosmic::app::Message::App)
196212
}
213+
Msg::Background(m) => background::update_msg(self, m).map(cosmic::app::Message::App),
197214
Msg::Output(o_event, wl_output) => {
198215
match o_event {
199216
OutputEvent::Created(Some(info))
@@ -266,6 +283,12 @@ impl cosmic::Application for CosmicPortal {
266283

267284
cosmic::iced::Command::none()
268285
}
286+
Msg::ConfigNotifyWatcher => {
287+
if let Some(tx) = self.tx_conf.as_mut() {
288+
tx.send_replace(self.config.clone());
289+
}
290+
cosmic::iced::Command::none()
291+
}
269292
Msg::ConfigSetScreenshot(screenshot) => {
270293
match &mut self.config_handler {
271294
Some(handler) => {
@@ -275,12 +298,11 @@ impl cosmic::Application for CosmicPortal {
275298
}
276299
None => log::error!("Failed to save config: No config handler"),
277300
}
278-
279-
cosmic::iced::Command::none()
301+
self.update(Msg::ConfigNotifyWatcher)
280302
}
281303
Msg::ConfigSubUpdate(config) => {
282304
self.config = config;
283-
cosmic::iced::Command::none()
305+
self.update(Msg::ConfigNotifyWatcher)
284306
}
285307
}
286308
}

0 commit comments

Comments
 (0)