app_list: Offer gpu selection
This commit is contained in:
parent
d7a8db6a07
commit
dfd863c54f
7 changed files with 182 additions and 45 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -806,12 +806,14 @@ dependencies = [
|
|||
"rust-embed 6.8.1",
|
||||
"rust-embed-utils 7.8.1",
|
||||
"serde",
|
||||
"switcheroo-control",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"xdg",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1062,7 +1064,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-dbus-networkmanager"
|
||||
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 = [
|
||||
"bitflags 2.4.2",
|
||||
"derive_builder",
|
||||
|
|
@ -3506,7 +3508,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mpris2-zbus"
|
||||
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 = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
|
@ -4962,6 +4964,14 @@ dependencies = [
|
|||
"zeno",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "switcheroo-control"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/dbus-settings-bindings#5dea929b730460f883935357a1a8fb9736f36f95"
|
||||
dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
|
@ -5982,7 +5992,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"naga",
|
||||
"parking_lot 0.12.1",
|
||||
"parking_lot 0.11.2",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"smallvec",
|
||||
|
|
@ -6007,7 +6017,7 @@ dependencies = [
|
|||
"codespan-reporting",
|
||||
"log",
|
||||
"naga",
|
||||
"parking_lot 0.12.1",
|
||||
"parking_lot 0.11.2",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"rustc-hash",
|
||||
|
|
@ -6047,7 +6057,7 @@ dependencies = [
|
|||
"naga",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"parking_lot 0.11.2",
|
||||
"profiling",
|
||||
"range-alloc",
|
||||
"raw-window-handle",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
|||
cctk.workspace = true
|
||||
cosmic-protocols.workspace = true
|
||||
libcosmic.workspace = true
|
||||
zbus.workspace = true
|
||||
# libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "tokio", "applet"] }
|
||||
ron = "0.8"
|
||||
futures = "0.3"
|
||||
|
|
@ -29,3 +30,4 @@ rust-embed = "6.3"
|
|||
url = "2.3.1"
|
||||
rust-embed-utils = "7.5.0"
|
||||
rand = "0.8.5"
|
||||
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
|
|
@ -3,4 +3,7 @@ favorite = Favorisieren
|
|||
unfavorite = Entfavorisieren
|
||||
quit = 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)
|
||||
|
|
@ -3,4 +3,7 @@ favorite = Favorite
|
|||
unfavorite = Un-Favorite
|
||||
quit = Quit
|
||||
quit-all = Quit All
|
||||
new-window = New Window
|
||||
new-window = New Window
|
||||
run = Run
|
||||
run-on = Run on {$gpu}
|
||||
run-on-default = (Default)
|
||||
|
|
@ -11,7 +11,7 @@ use cctk::sctk::reexports::calloop::channel::Sender;
|
|||
use cctk::toplevel_info::ToplevelInfo;
|
||||
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||
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::iced;
|
||||
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::Color;
|
||||
use cosmic::iced::{window, Subscription};
|
||||
use cosmic::iced_core::Padding;
|
||||
use cosmic::iced_runtime::core::alignment::Horizontal;
|
||||
use cosmic::iced_runtime::core::event;
|
||||
use cosmic::iced_sctk::commands::data_device::accept_mime_type;
|
||||
|
|
@ -55,6 +56,7 @@ use std::collections::HashMap;
|
|||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use switcheroo_control::Gpu;
|
||||
use tokio::time::sleep;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -105,6 +107,7 @@ impl DockItem {
|
|||
applet: &Context,
|
||||
rectangle_tracker: Option<&RectangleTracker<u32>>,
|
||||
interaction_enabled: bool,
|
||||
gpus: Option<&[Gpu]>,
|
||||
) -> Element<'_, Message> {
|
||||
let Self {
|
||||
toplevels,
|
||||
|
|
@ -168,7 +171,20 @@ impl DockItem {
|
|||
toplevels
|
||||
.first()
|
||||
.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)
|
||||
.height(Length::Shrink),
|
||||
|
|
@ -212,6 +228,7 @@ struct CosmicAppList {
|
|||
rectangles: HashMap<u32, iced::Rectangle>,
|
||||
dnd_offer: Option<DndOffer>,
|
||||
is_listening_for_dnd: bool,
|
||||
gpus: Option<Vec<Gpu>>,
|
||||
}
|
||||
|
||||
// TODO DnD after sctk merges DnD
|
||||
|
|
@ -221,10 +238,11 @@ enum Message {
|
|||
Favorite(String),
|
||||
UnFavorite(String),
|
||||
Popup(String),
|
||||
GpuRequest(Option<Vec<Gpu>>),
|
||||
CloseRequested(window::Id),
|
||||
ClosePopup,
|
||||
Activate(ZcosmicToplevelHandleV1),
|
||||
Exec(String),
|
||||
Exec(String, Option<usize>),
|
||||
Quit(String),
|
||||
Ignore,
|
||||
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 {
|
||||
type Message = Message;
|
||||
type Executor = cosmic::SingleThreadExecutor;
|
||||
|
|
@ -316,7 +367,12 @@ impl cosmic::Application for CosmicAppList {
|
|||
};
|
||||
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 {
|
||||
|
|
@ -348,7 +404,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
};
|
||||
|
||||
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(
|
||||
window::Id::MAIN,
|
||||
|
|
@ -369,7 +425,11 @@ impl cosmic::Application for CosmicAppList {
|
|||
width: width 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) => {
|
||||
|
|
@ -547,7 +607,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
}
|
||||
Message::DndData(file_path) => {
|
||||
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;
|
||||
*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();
|
||||
if let Some(token) = token {
|
||||
envs.push(("XDG_ACTIVATION_TOKEN", token.clone()));
|
||||
envs.push(("DESKTOP_STARTUP_ID", token));
|
||||
envs.push(("XDG_ACTIVATION_TOKEN".to_string(), token.clone()));
|
||||
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(|| {
|
||||
cosmic::desktop::spawn_desktop_exec(exec, envs);
|
||||
|
|
@ -707,11 +779,12 @@ impl cosmic::Application for CosmicAppList {
|
|||
Message::RemovedSeat(_) => {
|
||||
self.seat.take();
|
||||
}
|
||||
Message::Exec(exec) => {
|
||||
Message::Exec(exec, gpu_idx) => {
|
||||
if let Some(tx) = self.wayland_sender.as_ref() {
|
||||
let _ = tx.send(WaylandRequest::TokenRequest {
|
||||
app_id: Self::APP_ID.to_string(),
|
||||
exec,
|
||||
gpu_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -775,6 +848,9 @@ impl cosmic::Application for CosmicAppList {
|
|||
self.popup = None;
|
||||
}
|
||||
}
|
||||
Message::GpuRequest(gpus) => {
|
||||
self.gpus = gpus;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
|
|
@ -793,6 +869,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
&self.core.applet,
|
||||
self.rectangle_tracker.as_ref(),
|
||||
self.popup.is_none(),
|
||||
self.gpus.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -802,7 +879,10 @@ impl cosmic::Application for CosmicAppList {
|
|||
.as_ref()
|
||||
.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() {
|
||||
// show star indicating favorite_list is drag target
|
||||
favorites.push(
|
||||
|
|
@ -823,6 +903,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
&self.core.applet,
|
||||
self.rectangle_tracker.as_ref(),
|
||||
self.popup.is_none(),
|
||||
self.gpus.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -949,19 +1030,45 @@ impl cosmic::Application for CosmicAppList {
|
|||
.as_ref()
|
||||
.is_some_and(|wm_class| self.config.favorites.contains(wm_class));
|
||||
|
||||
let mut content = column![
|
||||
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center),
|
||||
]
|
||||
.padding(8)
|
||||
.spacing(4)
|
||||
let mut content = column![container(
|
||||
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center)
|
||||
)
|
||||
.padding(menu_control_padding()),]
|
||||
.padding([8, 0])
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
if let Some(exec) = desktop_info.exec.clone() {
|
||||
content = content.push(
|
||||
cosmic::widget::button(iced::widget::text(fl!("new-window")))
|
||||
.style(Button::Text)
|
||||
.on_press(Message::Exec(exec)),
|
||||
);
|
||||
if !toplevels.is_empty() {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(fl!("new-window")))
|
||||
.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());
|
||||
}
|
||||
|
||||
|
|
@ -974,8 +1081,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
info.title.clone()
|
||||
};
|
||||
list_col = list_col.push(
|
||||
cosmic::widget::button(iced::widget::text(title))
|
||||
.style(Button::Text)
|
||||
menu_button(iced::widget::text(title))
|
||||
.on_press(Message::Activate(handle.clone())),
|
||||
);
|
||||
}
|
||||
|
|
@ -983,25 +1089,21 @@ impl cosmic::Application for CosmicAppList {
|
|||
content = content.push(divider::horizontal::default());
|
||||
}
|
||||
content = content.push(if is_favorite {
|
||||
cosmic::widget::button(iced::widget::text(fl!("unfavorite")))
|
||||
.style(Button::Text)
|
||||
menu_button(iced::widget::text(fl!("unfavorite")))
|
||||
.on_press(Message::UnFavorite(desktop_info.id.clone()))
|
||||
} else {
|
||||
cosmic::widget::button(iced::widget::text(fl!("favorite")))
|
||||
.style(Button::Text)
|
||||
menu_button(iced::widget::text(fl!("favorite")))
|
||||
.on_press(Message::Favorite(desktop_info.id.clone()))
|
||||
});
|
||||
|
||||
content = match toplevels.len() {
|
||||
0 => content,
|
||||
1 => content.push(
|
||||
cosmic::widget::button(iced::widget::text(fl!("quit")))
|
||||
.style(Button::Text)
|
||||
menu_button(iced::widget::text(fl!("quit")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
_ => content.push(
|
||||
cosmic::widget::button(iced::widget::text(&fl!("quit-all")))
|
||||
.style(Button::Text)
|
||||
menu_button(iced::widget::text(&fl!("quit-all")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ impl ProvidesRegistryState for AppData {
|
|||
struct ExecRequestData {
|
||||
data: RequestData,
|
||||
exec: String,
|
||||
gpu_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl RequestDataExt for ExecRequestData {
|
||||
|
|
@ -74,6 +75,7 @@ impl ActivationHandler for AppData {
|
|||
let _ = self.tx.unbounded_send(WaylandUpdate::ActivationToken {
|
||||
token: Some(token),
|
||||
exec: data.exec.clone(),
|
||||
gpu_idx: data.gpu_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +219,11 @@ pub(crate) fn wayland_handler(
|
|||
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() {
|
||||
activation_state.request_token_with_data(
|
||||
&state.queue_handle,
|
||||
|
|
@ -232,12 +238,15 @@ pub(crate) fn wayland_handler(
|
|||
surface: None,
|
||||
},
|
||||
exec,
|
||||
gpu_idx,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let _ = state
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::ActivationToken { token: None, exec });
|
||||
let _ = state.tx.unbounded_send(WaylandUpdate::ActivationToken {
|
||||
token: None,
|
||||
exec,
|
||||
gpu_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -79,7 +79,11 @@ pub enum WaylandUpdate {
|
|||
Init(calloop::channel::Sender<WaylandRequest>),
|
||||
Finished,
|
||||
Toplevel(ToplevelUpdate),
|
||||
ActivationToken { token: Option<String>, exec: String },
|
||||
ActivationToken {
|
||||
token: Option<String>,
|
||||
exec: String,
|
||||
gpu_idx: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -92,7 +96,11 @@ pub enum ToplevelUpdate {
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum WaylandRequest {
|
||||
Toplevel(ToplevelRequest),
|
||||
TokenRequest { app_id: String, exec: String },
|
||||
TokenRequest {
|
||||
app_id: String,
|
||||
exec: String,
|
||||
gpu_idx: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue