feat(desktop_entries): Allow selecting gpu/secondary actions on COSMIC

This commit is contained in:
Victoria Brekenfeld 2024-02-01 16:03:03 +01:00 committed by GitHub
parent 0205007463
commit 091581c362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 692 additions and 229 deletions

735
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ slab = "0.4.7"
strsim = "0.10.0"
tracing = "0.1.37"
urlencoding = "2.1.2"
zbus = "3.4.0"
zbus = "3.14.0"
zvariant = "3.7.1"
ward = "2.1.0"
url = "2.3.1"
@ -36,6 +36,8 @@ recently-used-xbel = "1.0.0"
# dependencies cosmic toplevel
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" }
# dependencies desktop entries
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
[dependencies.reqwest]
version = "0.11.12"

View file

@ -1,43 +0,0 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::Context;
use sysfs_class::{PciDevice, SysClass};
/// Checks if the system has switchable graphics.
///
/// A system is considered switchable if multiple graphics card devices are found.
pub fn is_switchable() -> bool {
let main = || -> anyhow::Result<bool> {
let devices = PciDevice::all().context("cannot get PCI devices")?;
let mut amd_graphics = 0;
let mut intel_graphics = 0;
let mut nvidia_graphics = 0;
for dev in devices {
let c = dev.class().context("cannot get class of device")?;
if let 0x03 = (c >> 16) & 0xFF {
match dev.vendor().context("cannot get vendor of device")? {
0x1002 => amd_graphics += 1,
0x10DE => nvidia_graphics += 1,
0x8086 => intel_graphics += 1,
_ => (),
}
}
}
let switchable = (nvidia_graphics > 0 && (intel_graphics > 0 || amd_graphics > 0))
|| (intel_graphics > 0 && amd_graphics > 0);
Ok(switchable)
};
match main() {
Ok(value) => value,
Err(why) => {
tracing::error!("{}", why);
false
}
}
}

View file

@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
// Copyright © 2021 System76
mod graphics;
use crate::*;
use freedesktop_desktop_entry::{default_paths, DesktopEntry, Iter as DesktopIter, PathSource};
use futures::StreamExt;
@ -23,6 +21,7 @@ struct Item {
path: PathBuf,
prefers_non_default_gpu: bool,
src: PathSource,
actions: Vec<String>,
}
impl Hash for Item {
@ -68,6 +67,7 @@ struct App<W> {
entries: Vec<Item>,
locale: Option<String>,
tx: W,
gpus: Option<Vec<switcheroo_control::Gpu>>,
}
impl<W: AsyncWrite + Unpin> App<W> {
@ -81,6 +81,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
.and_then(|l| l.split('.').next())
.map(String::from),
tx,
gpus: None,
}
}
@ -171,6 +172,18 @@ impl<W: AsyncWrite + Unpin> App<W> {
path: path.clone(),
prefers_non_default_gpu: entry.prefers_non_default_gpu(),
src,
actions: entry
.actions()
.map(|actions| {
actions
.split(';')
.filter_map(|action| {
entry.action_entry_localized(action, "Name", None)
})
.map(Cow::into_owned)
.collect::<Vec<_>>()
})
.unwrap_or_default(),
};
deduplicator.insert(item);
@ -180,7 +193,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
}
}
self.entries.extend(deduplicator)
self.entries.extend(deduplicator);
self.gpus = try_get_gpus().await;
}
async fn activate(&mut self, id: u32) {
@ -192,6 +207,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
} else {
GpuPreference::Default
},
action_name: None,
};
send(&mut self.tx, response).await;
@ -200,14 +216,33 @@ impl<W: AsyncWrite + Unpin> App<W> {
async fn activate_context(&mut self, id: u32, context: u32) {
if let Some(entry) = self.entries.get(id as usize) {
let response = match context {
0 => PluginResponse::DesktopEntry {
path: entry.path.clone(),
gpu_preference: if !entry.prefers_non_default_gpu {
let is_cosmic = matches!(current_desktop().as_deref(), Some("cosmic"));
let gpu_len = self.gpus.as_ref().map(Vec::len).unwrap_or(0) as u32;
let gpu_preference = if is_cosmic {
if context < gpu_len {
GpuPreference::SpecificIdx(context)
} else {
if entry.prefers_non_default_gpu {
GpuPreference::NonDefault
} else {
GpuPreference::Default
},
}
}
} else {
if !entry.prefers_non_default_gpu {
GpuPreference::NonDefault
} else {
GpuPreference::Default
}
};
let response = match context {
0 => PluginResponse::DesktopEntry {
path: entry.path.clone(),
gpu_preference,
action_name: (is_cosmic && context >= gpu_len)
.then(|| entry.actions[(context - gpu_len) as usize].clone()),
},
_ => return,
};
@ -218,19 +253,10 @@ impl<W: AsyncWrite + Unpin> App<W> {
async fn context(&mut self, id: u32) {
if let Some(entry) = self.entries.get(id as usize) {
let mut options = Vec::new();
if graphics::is_switchable() {
options.push(ContextOption {
id: 0,
name: (if entry.prefers_non_default_gpu {
"Launch Using Integrated Graphics Card"
} else {
"Launch Using Discrete Graphics Card"
})
.to_owned(),
});
}
let options = match current_desktop().as_deref() {
Some("cosmic") => self.cosmic_context(entry).await,
_ => self.gnome_context(entry).await,
};
if !options.is_empty() {
let response = PluginResponse::Context { id, options };
@ -294,6 +320,54 @@ impl<W: AsyncWrite + Unpin> App<W> {
send(tx, PluginResponse::Finished).await;
}
async fn gnome_context(&self, entry: &Item) -> Vec<ContextOption> {
if self.gpus.is_some() {
vec![ContextOption {
id: 0,
name: (if entry.prefers_non_default_gpu {
"Launch Using Integrated Graphics Card"
} else {
"Launch Using Discrete Graphics Card"
})
.to_owned(),
}]
} else {
Vec::new()
}
}
async fn cosmic_context(&self, entry: &Item) -> Vec<ContextOption> {
let mut options = Vec::new();
if let Some(gpus) = self.gpus.as_ref() {
let default_idx = if entry.prefers_non_default_gpu {
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() {
options.push(ContextOption {
id: i as u32,
name: format!(
"Launch using {}{}",
gpu.name,
(i == default_idx).then_some(" (default)").unwrap_or("")
),
});
}
}
let options_offset = self.gpus.as_ref().map(|gpus| gpus.len()).unwrap_or(0);
for (i, action) in entry.actions.iter().enumerate() {
options.push(ContextOption {
id: (i + options_offset) as u32,
name: action.clone(),
});
}
options
}
}
fn current_desktop() -> Option<String> {
@ -320,3 +394,20 @@ fn path_string(source: &PathSource) -> Cow<'static, str> {
PathSource::Other(other) => Cow::Owned(other.clone()),
}
}
async fn try_get_gpus() -> Option<Vec<switcheroo_control::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)
}

View file

@ -216,10 +216,12 @@ impl<O: futures::Sink<Response> + Unpin> Service<O> {
PluginResponse::DesktopEntry {
path,
gpu_preference,
action_name,
} => {
self.respond(Response::DesktopEntry {
path,
gpu_preference,
action_name,
})
.await;
}

View file

@ -54,6 +54,7 @@ pub struct ContextOption {
pub enum GpuPreference {
Default,
NonDefault,
SpecificIdx(u32),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -84,6 +85,8 @@ pub enum PluginResponse {
DesktopEntry {
path: PathBuf,
gpu_preference: GpuPreference,
#[serde(skip_serializing_if = "Option::is_none", default)]
action_name: Option<String>,
},
/// Update the text in the launcher.
Fill(String),
@ -156,6 +159,7 @@ pub enum Response {
DesktopEntry {
path: PathBuf,
gpu_preference: GpuPreference,
action_name: Option<String>,
},
// The frontend should clear its search results and display a new list.
Update(Vec<SearchResult>),