app_list: Offer gpu selection

This commit is contained in:
Victoria Brekenfeld 2024-01-31 12:44:56 +00:00
parent d7a8db6a07
commit dfd863c54f
7 changed files with 182 additions and 45 deletions

20
Cargo.lock generated
View file

@ -806,12 +806,14 @@ dependencies = [
"rust-embed 6.8.1", "rust-embed 6.8.1",
"rust-embed-utils 7.8.1", "rust-embed-utils 7.8.1",
"serde", "serde",
"switcheroo-control",
"tokio", "tokio",
"tracing", "tracing",
"tracing-log", "tracing-log",
"tracing-subscriber", "tracing-subscriber",
"url", "url",
"xdg", "xdg",
"zbus",
] ]
[[package]] [[package]]
@ -1062,7 +1064,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-dbus-networkmanager" name = "cosmic-dbus-networkmanager"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/dbus-settings-bindings#3644bc909984842f35adb1057ef6e0a277c1aa6a" source = "git+https://github.com/pop-os/dbus-settings-bindings#5dea929b730460f883935357a1a8fb9736f36f95"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"derive_builder", "derive_builder",
@ -3506,7 +3508,7 @@ dependencies = [
[[package]] [[package]]
name = "mpris2-zbus" name = "mpris2-zbus"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/dbus-settings-bindings#3644bc909984842f35adb1057ef6e0a277c1aa6a" source = "git+https://github.com/pop-os/dbus-settings-bindings#5dea929b730460f883935357a1a8fb9736f36f95"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror", "thiserror",
@ -4962,6 +4964,14 @@ dependencies = [
"zeno", "zeno",
] ]
[[package]]
name = "switcheroo-control"
version = "0.1.0"
source = "git+https://github.com/pop-os/dbus-settings-bindings#5dea929b730460f883935357a1a8fb9736f36f95"
dependencies = [
"zbus",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -5982,7 +5992,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"naga", "naga",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"profiling", "profiling",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
@ -6007,7 +6017,7 @@ dependencies = [
"codespan-reporting", "codespan-reporting",
"log", "log",
"naga", "naga",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"profiling", "profiling",
"raw-window-handle", "raw-window-handle",
"rustc-hash", "rustc-hash",
@ -6047,7 +6057,7 @@ dependencies = [
"naga", "naga",
"objc", "objc",
"once_cell", "once_cell",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"profiling", "profiling",
"range-alloc", "range-alloc",
"raw-window-handle", "raw-window-handle",

View file

@ -8,6 +8,7 @@ edition = "2021"
cctk.workspace = true cctk.workspace = true
cosmic-protocols.workspace = true cosmic-protocols.workspace = true
libcosmic.workspace = true libcosmic.workspace = true
zbus.workspace = true
# libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "tokio", "applet"] } # libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "tokio", "applet"] }
ron = "0.8" ron = "0.8"
futures = "0.3" futures = "0.3"
@ -29,3 +30,4 @@ rust-embed = "6.3"
url = "2.3.1" url = "2.3.1"
rust-embed-utils = "7.5.0" rust-embed-utils = "7.5.0"
rand = "0.8.5" rand = "0.8.5"
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }

View file

@ -4,3 +4,6 @@ unfavorite = Entfavorisieren
quit = Beenden quit = Beenden
quit-all = Alle beenden quit-all = Alle beenden
new-window = Neues Fenster new-window = Neues Fenster
run = Ausführen
run-on = Ausführen auf {$gpu}
run-on-default = (Standard)

View file

@ -4,3 +4,6 @@ unfavorite = Un-Favorite
quit = Quit quit = Quit
quit-all = Quit All quit-all = Quit All
new-window = New Window new-window = New Window
run = Run
run-on = Run on {$gpu}
run-on-default = (Default)

View file

@ -11,7 +11,7 @@ use cctk::sctk::reexports::calloop::channel::Sender;
use cctk::toplevel_info::ToplevelInfo; use cctk::toplevel_info::ToplevelInfo;
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction; use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
use cctk::wayland_client::protocol::wl_seat::WlSeat; use cctk::wayland_client::protocol::wl_seat::WlSeat;
use cosmic::cosmic_config::{self, Config, CosmicConfigEntry}; use cosmic::cosmic_config::{Config, CosmicConfigEntry};
use cosmic::desktop::{load_applications_for_app_ids, DesktopEntryData}; use cosmic::desktop::{load_applications_for_app_ids, DesktopEntryData};
use cosmic::iced; use cosmic::iced;
use cosmic::iced::event::listen_with; use cosmic::iced::event::listen_with;
@ -25,6 +25,7 @@ use cosmic::iced::widget::vertical_space;
use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row}; use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row};
use cosmic::iced::Color; use cosmic::iced::Color;
use cosmic::iced::{window, Subscription}; use cosmic::iced::{window, Subscription};
use cosmic::iced_core::Padding;
use cosmic::iced_runtime::core::alignment::Horizontal; use cosmic::iced_runtime::core::alignment::Horizontal;
use cosmic::iced_runtime::core::event; use cosmic::iced_runtime::core::event;
use cosmic::iced_sctk::commands::data_device::accept_mime_type; use cosmic::iced_sctk::commands::data_device::accept_mime_type;
@ -55,6 +56,7 @@ use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use switcheroo_control::Gpu;
use tokio::time::sleep; use tokio::time::sleep;
use url::Url; use url::Url;
@ -105,6 +107,7 @@ impl DockItem {
applet: &Context, applet: &Context,
rectangle_tracker: Option<&RectangleTracker<u32>>, rectangle_tracker: Option<&RectangleTracker<u32>>,
interaction_enabled: bool, interaction_enabled: bool,
gpus: Option<&[Gpu]>,
) -> Element<'_, Message> { ) -> Element<'_, Message> {
let Self { let Self {
toplevels, toplevels,
@ -168,7 +171,20 @@ impl DockItem {
toplevels toplevels
.first() .first()
.map(|t| Message::Activate(t.0.clone())) .map(|t| Message::Activate(t.0.clone()))
.or_else(|| desktop_info.exec.clone().map(Message::Exec)), .or_else(|| {
let gpu_idx = gpus.map(|gpus| {
if desktop_info.prefers_dgpu {
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
} else {
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
}
});
desktop_info
.exec
.clone()
.map(|exec| Message::Exec(exec, gpu_idx))
}),
) )
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink), .height(Length::Shrink),
@ -212,6 +228,7 @@ struct CosmicAppList {
rectangles: HashMap<u32, iced::Rectangle>, rectangles: HashMap<u32, iced::Rectangle>,
dnd_offer: Option<DndOffer>, dnd_offer: Option<DndOffer>,
is_listening_for_dnd: bool, is_listening_for_dnd: bool,
gpus: Option<Vec<Gpu>>,
} }
// TODO DnD after sctk merges DnD // TODO DnD after sctk merges DnD
@ -221,10 +238,11 @@ enum Message {
Favorite(String), Favorite(String),
UnFavorite(String), UnFavorite(String),
Popup(String), Popup(String),
GpuRequest(Option<Vec<Gpu>>),
CloseRequested(window::Id), CloseRequested(window::Id),
ClosePopup, ClosePopup,
Activate(ZcosmicToplevelHandleV1), Activate(ZcosmicToplevelHandleV1),
Exec(String), Exec(String, Option<usize>),
Quit(String), Quit(String),
Ignore, Ignore,
NewSeat(WlSeat), NewSeat(WlSeat),
@ -282,6 +300,39 @@ fn index_in_list(
} }
} }
async fn try_get_gpus() -> Option<Vec<Gpu>> {
let connection = zbus::Connection::system().await.ok()?;
let proxy = switcheroo_control::SwitcherooControlProxy::new(&connection)
.await
.ok()?;
if !proxy.has_dual_gpu().await.ok()? {
return None;
}
let gpus = proxy.get_gpus().await.ok()?;
if gpus.is_empty() {
return None;
}
Some(gpus)
}
pub fn menu_button<'a, Message>(
content: impl Into<Element<'a, Message>>,
) -> cosmic::widget::Button<'a, Message, cosmic::Renderer> {
cosmic::widget::Button::new(content)
.style(Button::AppletMenu)
.padding(menu_control_padding())
.width(Length::Fill)
}
pub fn menu_control_padding() -> Padding {
let theme = cosmic::theme::active();
let cosmic = theme.cosmic();
[cosmic.space_xxs(), cosmic.space_m()].into()
}
impl cosmic::Application for CosmicAppList { impl cosmic::Application for CosmicAppList {
type Message = Message; type Message = Message;
type Executor = cosmic::SingleThreadExecutor; type Executor = cosmic::SingleThreadExecutor;
@ -316,7 +367,12 @@ impl cosmic::Application for CosmicAppList {
}; };
self_.item_ctr = self_.favorite_list.len() as u32; self_.item_ctr = self_.favorite_list.len() as u32;
(self_, Command::none()) (
self_,
Command::perform(try_get_gpus(), |gpus| {
cosmic::app::Message::App(Message::GpuRequest(gpus))
}),
)
} }
fn core(&self) -> &cosmic::app::Core { fn core(&self) -> &cosmic::app::Core {
@ -348,7 +404,7 @@ impl cosmic::Application for CosmicAppList {
}; };
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup = Some((new_id, toplevel_group.id.clone())); self.popup = Some((new_id, toplevel_group.id));
let mut popup_settings = self.core.applet.get_popup_settings( let mut popup_settings = self.core.applet.get_popup_settings(
window::Id::MAIN, window::Id::MAIN,
@ -369,7 +425,11 @@ impl cosmic::Application for CosmicAppList {
width: width as i32, width: width as i32,
height: height as i32, height: height as i32,
}; };
return get_popup(popup_settings);
let gpu_update = Command::perform(try_get_gpus(), |gpus| {
cosmic::app::Message::App(Message::GpuRequest(gpus))
});
return Command::batch([gpu_update, get_popup(popup_settings)]);
} }
} }
Message::Favorite(id) => { Message::Favorite(id) => {
@ -547,7 +607,7 @@ impl cosmic::Application for CosmicAppList {
} }
Message::DndData(file_path) => { Message::DndData(file_path) => {
if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() { if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() {
if let Some(di) = cosmic::desktop::load_desktop_file(None, &file_path) { if let Some(di) = cosmic::desktop::load_desktop_file(None, file_path) {
self.item_ctr += 1; self.item_ctr += 1;
*dock_item = Some(DockItem::new(self.item_ctr, Vec::new(), di)); *dock_item = Some(DockItem::new(self.item_ctr, Vec::new(), di));
} }
@ -689,11 +749,23 @@ impl cosmic::Application for CosmicAppList {
} }
} }
}, },
WaylandUpdate::ActivationToken { token, exec } => { WaylandUpdate::ActivationToken {
token,
exec,
gpu_idx,
} => {
let mut envs = Vec::new(); let mut envs = Vec::new();
if let Some(token) = token { if let Some(token) = token {
envs.push(("XDG_ACTIVATION_TOKEN", token.clone())); envs.push(("XDG_ACTIVATION_TOKEN".to_string(), token.clone()));
envs.push(("DESKTOP_STARTUP_ID", token)); envs.push(("DESKTOP_STARTUP_ID".to_string(), token));
}
if let (Some(gpus), Some(idx)) = (self.gpus.as_ref(), gpu_idx) {
envs.extend(
gpus[idx]
.environment
.iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
} }
tokio::task::spawn_blocking(|| { tokio::task::spawn_blocking(|| {
cosmic::desktop::spawn_desktop_exec(exec, envs); cosmic::desktop::spawn_desktop_exec(exec, envs);
@ -707,11 +779,12 @@ impl cosmic::Application for CosmicAppList {
Message::RemovedSeat(_) => { Message::RemovedSeat(_) => {
self.seat.take(); self.seat.take();
} }
Message::Exec(exec) => { Message::Exec(exec, gpu_idx) => {
if let Some(tx) = self.wayland_sender.as_ref() { if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::TokenRequest { let _ = tx.send(WaylandRequest::TokenRequest {
app_id: Self::APP_ID.to_string(), app_id: Self::APP_ID.to_string(),
exec, exec,
gpu_idx,
}); });
} }
} }
@ -775,6 +848,9 @@ impl cosmic::Application for CosmicAppList {
self.popup = None; self.popup = None;
} }
} }
Message::GpuRequest(gpus) => {
self.gpus = gpus;
}
} }
Command::none() Command::none()
@ -793,6 +869,7 @@ impl cosmic::Application for CosmicAppList {
&self.core.applet, &self.core.applet,
self.rectangle_tracker.as_ref(), self.rectangle_tracker.as_ref(),
self.popup.is_none(), self.popup.is_none(),
self.gpus.as_deref(),
) )
}) })
.collect(); .collect();
@ -802,7 +879,10 @@ impl cosmic::Application for CosmicAppList {
.as_ref() .as_ref()
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index))) .and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
{ {
favorites.insert(index, item.as_icon(&self.core.applet, None, false)); favorites.insert(
index,
item.as_icon(&self.core.applet, None, false, self.gpus.as_deref()),
);
} else if self.is_listening_for_dnd && self.favorite_list.is_empty() { } else if self.is_listening_for_dnd && self.favorite_list.is_empty() {
// show star indicating favorite_list is drag target // show star indicating favorite_list is drag target
favorites.push( favorites.push(
@ -823,6 +903,7 @@ impl cosmic::Application for CosmicAppList {
&self.core.applet, &self.core.applet,
self.rectangle_tracker.as_ref(), self.rectangle_tracker.as_ref(),
self.popup.is_none(), self.popup.is_none(),
self.gpus.as_deref(),
) )
}) })
.collect(); .collect();
@ -949,19 +1030,45 @@ impl cosmic::Application for CosmicAppList {
.as_ref() .as_ref()
.is_some_and(|wm_class| self.config.favorites.contains(wm_class)); .is_some_and(|wm_class| self.config.favorites.contains(wm_class));
let mut content = column![ let mut content = column![container(
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center), iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center)
] )
.padding(8) .padding(menu_control_padding()),]
.spacing(4) .padding([8, 0])
.align_items(Alignment::Center); .align_items(Alignment::Center);
if let Some(exec) = desktop_info.exec.clone() { if let Some(exec) = desktop_info.exec.clone() {
content = content.push( if !toplevels.is_empty() {
cosmic::widget::button(iced::widget::text(fl!("new-window"))) content = content.push(
.style(Button::Text) menu_button(iced::widget::text(fl!("new-window")))
.on_press(Message::Exec(exec)), .on_press(Message::Exec(exec, None)),
); );
} else if let Some(gpus) = self.gpus.as_ref() {
let default_idx = if desktop_info.prefers_dgpu {
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
} else {
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
};
for (i, gpu) in gpus.iter().enumerate() {
content = content.push(
menu_button(iced::widget::text(format!(
"{} {}",
fl!("run-on", gpu = gpu.name.clone()),
if i == default_idx {
fl!("run-on-default")
} else {
String::new()
}
)))
.on_press(Message::Exec(exec.clone(), Some(i))),
);
}
} else {
content = content.push(
menu_button(iced::widget::text(fl!("run")))
.on_press(Message::Exec(exec, None)),
);
}
content = content.push(divider::horizontal::default()); content = content.push(divider::horizontal::default());
} }
@ -974,8 +1081,7 @@ impl cosmic::Application for CosmicAppList {
info.title.clone() info.title.clone()
}; };
list_col = list_col.push( list_col = list_col.push(
cosmic::widget::button(iced::widget::text(title)) menu_button(iced::widget::text(title))
.style(Button::Text)
.on_press(Message::Activate(handle.clone())), .on_press(Message::Activate(handle.clone())),
); );
} }
@ -983,25 +1089,21 @@ impl cosmic::Application for CosmicAppList {
content = content.push(divider::horizontal::default()); content = content.push(divider::horizontal::default());
} }
content = content.push(if is_favorite { content = content.push(if is_favorite {
cosmic::widget::button(iced::widget::text(fl!("unfavorite"))) menu_button(iced::widget::text(fl!("unfavorite")))
.style(Button::Text)
.on_press(Message::UnFavorite(desktop_info.id.clone())) .on_press(Message::UnFavorite(desktop_info.id.clone()))
} else { } else {
cosmic::widget::button(iced::widget::text(fl!("favorite"))) menu_button(iced::widget::text(fl!("favorite")))
.style(Button::Text)
.on_press(Message::Favorite(desktop_info.id.clone())) .on_press(Message::Favorite(desktop_info.id.clone()))
}); });
content = match toplevels.len() { content = match toplevels.len() {
0 => content, 0 => content,
1 => content.push( 1 => content.push(
cosmic::widget::button(iced::widget::text(fl!("quit"))) menu_button(iced::widget::text(fl!("quit")))
.style(Button::Text)
.on_press(Message::Quit(desktop_info.id.clone())), .on_press(Message::Quit(desktop_info.id.clone())),
), ),
_ => content.push( _ => content.push(
cosmic::widget::button(iced::widget::text(&fl!("quit-all"))) menu_button(iced::widget::text(&fl!("quit-all")))
.style(Button::Text)
.on_press(Message::Quit(desktop_info.id.clone())), .on_press(Message::Quit(desktop_info.id.clone())),
), ),
}; };

View file

@ -52,6 +52,7 @@ impl ProvidesRegistryState for AppData {
struct ExecRequestData { struct ExecRequestData {
data: RequestData, data: RequestData,
exec: String, exec: String,
gpu_idx: Option<usize>,
} }
impl RequestDataExt for ExecRequestData { impl RequestDataExt for ExecRequestData {
@ -74,6 +75,7 @@ impl ActivationHandler for AppData {
let _ = self.tx.unbounded_send(WaylandUpdate::ActivationToken { let _ = self.tx.unbounded_send(WaylandUpdate::ActivationToken {
token: Some(token), token: Some(token),
exec: data.exec.clone(), exec: data.exec.clone(),
gpu_idx: data.gpu_idx,
}); });
} }
} }
@ -217,7 +219,11 @@ pub(crate) fn wayland_handler(
state.exit = true; state.exit = true;
} }
}, },
WaylandRequest::TokenRequest { app_id, exec } => { WaylandRequest::TokenRequest {
app_id,
exec,
gpu_idx,
} => {
if let Some(activation_state) = state.activation_state.as_ref() { if let Some(activation_state) = state.activation_state.as_ref() {
activation_state.request_token_with_data( activation_state.request_token_with_data(
&state.queue_handle, &state.queue_handle,
@ -232,12 +238,15 @@ pub(crate) fn wayland_handler(
surface: None, surface: None,
}, },
exec, exec,
gpu_idx,
}, },
); );
} else { } else {
let _ = state let _ = state.tx.unbounded_send(WaylandUpdate::ActivationToken {
.tx token: None,
.unbounded_send(WaylandUpdate::ActivationToken { token: None, exec }); exec,
gpu_idx,
});
} }
} }
}, },

View file

@ -79,7 +79,11 @@ pub enum WaylandUpdate {
Init(calloop::channel::Sender<WaylandRequest>), Init(calloop::channel::Sender<WaylandRequest>),
Finished, Finished,
Toplevel(ToplevelUpdate), Toplevel(ToplevelUpdate),
ActivationToken { token: Option<String>, exec: String }, ActivationToken {
token: Option<String>,
exec: String,
gpu_idx: Option<usize>,
},
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -92,7 +96,11 @@ pub enum ToplevelUpdate {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum WaylandRequest { pub enum WaylandRequest {
Toplevel(ToplevelRequest), Toplevel(ToplevelRequest),
TokenRequest { app_id: String, exec: String }, TokenRequest {
app_id: String,
exec: String,
gpu_idx: Option<usize>,
},
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]