Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background portal #70

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ time = { version = "0.3.31", features = [
"macros",
] }
url = "2.5"
shlex = "1"
# i18n
i18n-embed = { version = "0.14.1", features = [
"fluent-system",
Expand Down
21 changes: 21 additions & 0 deletions cosmic-portal-config/src/background.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-only

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Background {
/// Default preference for NotifyBackground's dialog
pub default_perm: PermissionDialog,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
pub enum PermissionDialog {
/// Grant apps permission to run in the background
Allow,
/// Deny apps permission to run in the background
Deny,
/// Always ask if new apps should be granted background permissions
#[default]
Ask,
}
4 changes: 4 additions & 0 deletions cosmic-portal-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only

pub mod background;
pub mod screenshot;

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

use background::Background;
use screenshot::Screenshot;

pub const APP_ID: &str = "com.system76.CosmicPortal";
Expand All @@ -17,6 +19,8 @@ pub const CONFIG_VERSION: u64 = 1;
pub struct Config {
/// Interactive screenshot settings
pub screenshot: Screenshot,
/// Background portal settings
pub background: Background,
}

impl Config {
Expand Down
2 changes: 1 addition & 1 deletion data/cosmic.portal
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.cosmic
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
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
UseIn=cosmic
124 changes: 124 additions & 0 deletions examples/background.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-3.0-only

use ashpd::desktop::background::Background;
use cosmic::{
app::{self, message, Core},
executor,
iced::{Length, Size},
widget, Command,
};

#[derive(Clone, Debug)]
pub enum Message {
BackgroundResponse(bool),
RequestBackground,
}

pub struct App {
core: Core,
executable: String,
background_allowed: bool,
}

impl App {
async fn request_background(executable: String) -> ashpd::Result<Background> {
log::info!("Requesting permission to run in the background for: {executable}");
// Based off of the ashpd docs
// https://docs.rs/ashpd/latest/ashpd/desktop/background/index.html
Background::request()
.reason("Testing the background portal")
.auto_start(false)
.dbus_activatable(false)
.command(&[executable])
.send()
.await?
.response()
}
}

impl cosmic::Application for App {
type Executor = executor::single::Executor;
type Flags = ();
type Message = Message;
const APP_ID: &'static str = "org.cosmic.BackgroundPortalExample";

fn core(&self) -> &Core {
&self.core
}

fn core_mut(&mut self) -> &mut Core {
&mut self.core
}

fn init(core: Core, _: Self::Flags) -> (Self, app::Command<Self::Message>) {
(
Self {
core,
executable: std::env::args().next().unwrap(),
background_allowed: false,
},
Command::none(),
)
}

fn view(&self) -> cosmic::Element<Self::Message> {
widget::row::with_children(vec![
widget::text::title3(if self.background_allowed {
"Running in background"
} else {
"Not running in background"
})
.width(Length::Fill)
.into(),
widget::button::standard("Run in background")
.on_press(Message::RequestBackground)
.padding(8.0)
.into(),
])
.width(Length::Fill)
.height(Length::Fixed(64.0))
.padding(16.0)
.into()
}

fn update(&mut self, message: Self::Message) -> app::Command<Self::Message> {
match message {
Message::BackgroundResponse(background_allowed) => {
log::info!("Permission to run in the background: {background_allowed}");
self.background_allowed = background_allowed;
Command::none()
}
Message::RequestBackground => {
let executable = self.executable.clone();
Command::perform(Self::request_background(executable), |result| {
let background_allowed = match result {
Ok(response) => {
assert!(
!response.auto_start(),
"Auto start shouldn't have been enabled"
);
response.run_in_background()
}
Err(e) => {
log::error!("Background portal request failed: {e:?}");
false
}
};

message::app(Message::BackgroundResponse(background_allowed))
})
}
}
}
}

// TODO: Write a small flatpak manifest in order to test this better
#[tokio::main]
async fn main() -> cosmic::iced::Result {
env_logger::Builder::from_default_env().init();
let settings = app::Settings::default()
.resizable(None)
.size(Size::new(512.0, 128.0))
.exit_on_close(false);
app::run::<App>(settings, ())
}
6 changes: 6 additions & 0 deletions i18n/en/xdg_desktop_portal_cosmic.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ share-screen = Share your screen
unknown-application = Unknown Application
output = Output
window = Window

# Background portal
allow-once = Allow once
deny = Deny
bg-dialog-title = Background
bg-dialog-body = {$appname} requests to run in the background. This will allow it to run without any open windows.
88 changes: 55 additions & 33 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{access, config, file_chooser, fl, screencast_dialog, screenshot, subscription};
use crate::{
access, background, config, file_chooser, fl, screencast_dialog, screenshot, subscription,
};
use cosmic::iced_core::event::wayland::OutputEvent;
use cosmic::widget::{self, dropdown};
use cosmic::Command;
Expand All @@ -14,12 +16,7 @@ pub(crate) fn run() -> cosmic::iced::Result {
let settings = cosmic::app::Settings::default()
.no_main_window(true)
.exit_on_close(false);
let (config, config_handler) = config::Config::load();
let flags = Flags {
config,
config_handler,
};
cosmic::app::run::<CosmicPortal>(settings, flags)
cosmic::app::run::<CosmicPortal>(settings, ())
}

// run iced app with no main surface
Expand All @@ -28,7 +25,7 @@ pub struct CosmicPortal {
pub tx: Option<tokio::sync::mpsc::Sender<subscription::Event>>,

pub config_handler: Option<cosmic_config::Config>,
pub config: config::Config,
pub tx_conf: Option<tokio::sync::watch::Sender<config::Config>>,

pub access_args: Option<access::AccessDialogArgs>,
pub access_choices: Vec<(Option<usize>, Vec<String>)>,
Expand All @@ -43,6 +40,8 @@ pub struct CosmicPortal {
pub prev_rectangle: Option<screenshot::Rect>,
pub wayland_helper: crate::wayland::WaylandHelper,

pub background_prompts: HashMap<window::Id, background::Args>,

pub outputs: Vec<OutputState>,
pub active_output: Option<WlOutput>,
}
Expand All @@ -64,23 +63,18 @@ pub enum Msg {
FileChooser(window::Id, file_chooser::Msg),
Screenshot(screenshot::Msg),
Screencast(screencast_dialog::Msg),
Background(background::Msg),
Portal(subscription::Event),
Output(OutputEvent, WlOutput),
ConfigSetScreenshot(config::screenshot::Screenshot),
/// Update config from external changes
ConfigSubUpdate(config::Config),
}

#[derive(Clone, Debug)]
pub struct Flags {
pub config_handler: Option<cosmic_config::Config>,
pub config: config::Config,
}

impl cosmic::Application for CosmicPortal {
type Executor = cosmic::executor::Default;

type Flags = Flags;
type Flags = ();

type Message = Msg;

Expand All @@ -96,10 +90,7 @@ impl cosmic::Application for CosmicPortal {

fn init(
core: app::Core,
Flags {
config_handler,
config,
}: Self::Flags,
_: Self::Flags,
) -> (Self, cosmic::iced::Command<app::Message<Self::Message>>) {
let mut model = cosmic::widget::dropdown::multi::model();
model.insert(dropdown::multi::list(
Expand All @@ -119,14 +110,14 @@ impl cosmic::Application for CosmicPortal {
),
],
));
model.selected = Some(config.screenshot.save_location);
model.selected = None;
let wayland_conn = crate::wayland::connect_to_wayland();
let wayland_helper = crate::wayland::WaylandHelper::new(wayland_conn);
(
Self {
core,
config_handler,
config,
config_handler: None,
tx_conf: None,
access_args: Default::default(),
access_choices: Default::default(),
file_choosers: Default::default(),
Expand All @@ -135,6 +126,7 @@ impl cosmic::Application for CosmicPortal {
screencast_tab_model: Default::default(),
location_options: Vec::new(),
prev_rectangle: Default::default(),
background_prompts: Default::default(),
outputs: Default::default(),
active_output: Default::default(),
wayland_helper,
Expand All @@ -155,6 +147,8 @@ impl cosmic::Application for CosmicPortal {
screencast_dialog::view(self).map(Msg::Screencast)
} else if self.outputs.iter().any(|o| o.id == id) {
screenshot::view(self, id).map(Msg::Screenshot)
} else if self.background_prompts.contains_key(&id) {
background::view(self, id).map(Msg::Background)
} else {
file_chooser::view(self, id)
}
Expand All @@ -181,19 +175,31 @@ impl cosmic::Application for CosmicPortal {
subscription::Event::CancelScreencast(handle) => {
screencast_dialog::cancel(self, handle).map(cosmic::app::Message::App)
}
subscription::Event::Background(args) => {
background::update_args(self, args).map(cosmic::app::Message::App)
}
subscription::Event::Config(config) => self.update(Msg::ConfigSubUpdate(config)),
subscription::Event::Accent(_)
| subscription::Event::IsDark(_)
| subscription::Event::HighContrast(_) => cosmic::iced::Command::none(),
subscription::Event::Init(tx) => {
| subscription::Event::HighContrast(_)
| subscription::Event::BackgroundToplevels => cosmic::iced::Command::none(),
subscription::Event::Init {
tx,
tx_conf,
handler,
} => {
let config = tx_conf.borrow().clone();
self.tx = Some(tx);
Command::none()
self.tx_conf = Some(tx_conf);
self.config_handler = handler;
self.update(Msg::ConfigSubUpdate(config))
}
},
Msg::Screenshot(m) => screenshot::update_msg(self, m).map(cosmic::app::Message::App),
Msg::Screencast(m) => {
screencast_dialog::update_msg(self, m).map(cosmic::app::Message::App)
}
Msg::Background(m) => background::update_msg(self, m).map(cosmic::app::Message::App),
Msg::Output(o_event, wl_output) => {
match o_event {
OutputEvent::Created(Some(info))
Expand Down Expand Up @@ -267,19 +273,35 @@ impl cosmic::Application for CosmicPortal {
cosmic::iced::Command::none()
}
Msg::ConfigSetScreenshot(screenshot) => {
match &mut self.config_handler {
Some(handler) => {
if let Err(e) = self.config.set_screenshot(handler, screenshot) {
log::error!("Failed to save screenshot config: {e}")
}
match (self.tx_conf.as_mut(), &mut self.config_handler) {
(Some(tx), Some(handler)) => {
tx.send_if_modified(|config| {
if screenshot != config.screenshot {
if let Err(e) = config.set_screenshot(handler, screenshot) {
log::error!("Failed to save screenshot config: {e}");
}
true
} else {
false
}
});
}
None => log::error!("Failed to save config: No config handler"),
_ => log::error!("Failed to save config: No config handler"),
}

cosmic::iced::Command::none()
}
Msg::ConfigSubUpdate(config) => {
self.config = config;
if let Some(tx) = self.tx_conf.as_ref() {
tx.send_if_modified(|current| {
if config != *current {
*current = config;
true
} else {
false
}
});
}

cosmic::iced::Command::none()
}
}
Expand Down
Loading