Skip to content

Commit 2153422

Browse files
committed
feat: add launch function for desktop entries
1 parent 973c5ad commit 2153422

File tree

10 files changed

+568
-4
lines changed

10 files changed

+568
-4
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ gettext-rs = { version = "0.7", features = ["gettext-system"]}
1717
memchr = "2"
1818
thiserror = "1"
1919
xdg = "2.4.0"
20+
udev = "0.6.3"
21+
22+
[dev-dependencies]
23+
speculoos = "0.9.0"

src/exec/error.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::env::VarError;
2+
use std::io;
3+
use std::path::Path;
4+
use thiserror::Error;
5+
6+
#[derive(Debug, Error)]
7+
pub enum ExecError<'a> {
8+
#[error("Unmatched quote delimiter: '{exec}'")]
9+
UnmatchedQuote { exec: String },
10+
11+
#[error("Exec string is empty")]
12+
EmptyExecString,
13+
14+
#[error("$SHELL environment variable is not set")]
15+
ShellNotFound(#[from] VarError),
16+
17+
#[error("Failed to run Exec command")]
18+
IoError(#[from] io::Error),
19+
20+
#[error("Exec command '{exec}' exited with status code '{status:?}'")]
21+
NonZeroStatusCode { status: Option<i32>, exec: String },
22+
23+
#[error("Unknown field code: '{0}'")]
24+
UnknownFieldCode(String),
25+
26+
#[error("Deprecated field code: '{0}'")]
27+
DeprecatedFieldCode(String),
28+
29+
#[error("Exec key not found in desktop entry '{0:?}'")]
30+
MissingExecKey(&'a Path),
31+
}

src/exec/graphics.rs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright 2022 System76 <[email protected]>
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
use std::collections::HashSet;
5+
use std::hash::{Hash, Hasher};
6+
use std::io;
7+
use std::ops::Deref;
8+
use std::path::PathBuf;
9+
10+
#[derive(Debug, Default)]
11+
pub struct Gpus {
12+
devices: Vec<Dev>,
13+
default: Option<Dev>,
14+
}
15+
16+
impl Gpus {
17+
// Get gpus via udev
18+
pub fn load() -> Self {
19+
let drivers = get_gpus();
20+
21+
let mut gpus = Gpus::default();
22+
for dev in drivers.unwrap() {
23+
if dev.is_default {
24+
gpus.default = Some(dev)
25+
} else {
26+
gpus.devices.push(dev)
27+
}
28+
}
29+
30+
gpus
31+
}
32+
33+
/// `true` if there is at least one non default gpu
34+
pub fn is_switchable(&self) -> bool {
35+
self.default.is_some() && !self.devices.is_empty()
36+
}
37+
38+
/// Return the default gpu
39+
pub fn get_default(&self) -> Option<&Dev> {
40+
self.default.as_ref()
41+
}
42+
43+
/// Get the first non-default gpu, the current `PreferNonDefaultGpu` specification
44+
/// Does not tell us which one should be used. Anyway most machine out there should have
45+
/// only one discrete graphic card.
46+
/// see: https://gitlab.freedesktop.org/xdg/xdg-specs/-/issues/59
47+
pub fn non_default(&self) -> Option<&Dev> {
48+
self.devices.first()
49+
}
50+
}
51+
52+
#[derive(Debug)]
53+
pub struct Dev {
54+
id: usize,
55+
driver: Driver,
56+
is_default: bool,
57+
parent_path: PathBuf,
58+
}
59+
60+
impl Dev {
61+
/// Get the environment variable to launch a program with the correct gpu settings
62+
pub fn launch_options(&self) -> Vec<(String, String)> {
63+
let dev_num = self.id.to_string();
64+
let mut options = vec![];
65+
66+
match self.driver {
67+
Driver::Unknown | Driver::Amd(_) | Driver::Intel => {
68+
options.push(("DRI_PRIME".into(), dev_num))
69+
}
70+
Driver::Nvidia => {
71+
options.push(("__GLX_VENDOR_LIBRARY_NAME".into(), "nvidia".into()));
72+
options.push(("__NV_PRIME_RENDER_OFFLOAD".into(), "1".into()));
73+
options.push((" __VK_LAYER_NV_optimus".into(), "NVIDIA_only".into()));
74+
}
75+
}
76+
77+
match self.get_vulkan_icd_paths() {
78+
Ok(vulkan_icd_paths) if !vulkan_icd_paths.is_empty() => {
79+
options.push(("VK_ICD_FILENAMES".into(), vulkan_icd_paths.join(":")))
80+
}
81+
Err(err) => eprintln!("Failed to open vulkan icd paths: {err}"),
82+
_ => {}
83+
}
84+
85+
options
86+
}
87+
88+
// Lookup vulkan icd files and return the ones matching the driver in use
89+
fn get_vulkan_icd_paths(&self) -> io::Result<Vec<String>> {
90+
let vulkan_icd_paths = dirs::data_dir()
91+
.expect("local data dir does not exists")
92+
.join("vulkan/icd.d");
93+
let vulkan_icd_paths = &[PathBuf::from("/usr/share/vulkan/icd.d"), vulkan_icd_paths];
94+
95+
let mut icd_paths = vec![];
96+
if let Some(driver) = self.driver.as_str() {
97+
for path in vulkan_icd_paths {
98+
if path.exists() {
99+
for entry in path.read_dir()? {
100+
let entry = entry?;
101+
let path = entry.path();
102+
if path.is_file() {
103+
let path_str = path.to_string_lossy();
104+
if path_str.contains(driver) {
105+
icd_paths.push(path_str.to_string())
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
Ok(icd_paths)
114+
}
115+
}
116+
117+
// Ensure we filter out "render" devices having the same parent as the card
118+
impl Hash for Dev {
119+
fn hash<H: Hasher>(&self, state: &mut H) {
120+
state.write(self.parent_path.to_string_lossy().as_bytes());
121+
state.finish();
122+
}
123+
}
124+
125+
impl PartialEq<Dev> for Dev {
126+
fn eq(&self, other: &Self) -> bool {
127+
self.parent_path == other.parent_path
128+
}
129+
}
130+
131+
impl Eq for Dev {}
132+
133+
#[derive(Debug)]
134+
enum Driver {
135+
Intel,
136+
Amd(String),
137+
Nvidia,
138+
Unknown,
139+
}
140+
141+
impl Driver {
142+
fn from_udev<S: Deref<Target = str>>(driver: Option<S>) -> Driver {
143+
match driver.as_deref() {
144+
// For amd devices we need the name of the driver to get vulkan icd files
145+
Some("radeon") => Driver::Amd("radeon".to_string()),
146+
Some("amdgpu") => Driver::Amd("amdgpu".to_string()),
147+
Some("nvidia") => Driver::Nvidia,
148+
Some("iris") | Some("i915") | Some("i965") => Driver::Intel,
149+
_ => Driver::Unknown,
150+
}
151+
}
152+
153+
fn as_str(&self) -> Option<&str> {
154+
match self {
155+
Driver::Intel => Some("intel"),
156+
Driver::Amd(driver) => Some(driver.as_str()),
157+
Driver::Nvidia => Some("nvidia"),
158+
Driver::Unknown => None,
159+
}
160+
}
161+
}
162+
163+
fn get_gpus() -> io::Result<HashSet<Dev>> {
164+
let mut enumerator = udev::Enumerator::new()?;
165+
let mut dev_map = HashSet::new();
166+
let mut drivers: Vec<Dev> = enumerator
167+
.scan_devices()?
168+
.into_iter()
169+
.filter(|dev| {
170+
dev.devnode()
171+
.map(|path| path.starts_with("/dev/dri"))
172+
.unwrap_or(false)
173+
})
174+
.filter_map(|dev| {
175+
dev.parent().and_then(|parent| {
176+
let id = dev.sysnum();
177+
let parent_path = parent.syspath().to_path_buf();
178+
let driver = parent.driver().map(|d| d.to_string_lossy().to_string());
179+
let driver = Driver::from_udev(driver);
180+
181+
let is_default = parent
182+
.attribute_value("boot_vga")
183+
.map(|v| v == "1")
184+
.unwrap_or(false);
185+
186+
id.map(|id| Dev {
187+
id,
188+
driver,
189+
is_default,
190+
parent_path,
191+
})
192+
})
193+
})
194+
.collect();
195+
196+
// Sort the devices by sysnum so we get card0, card1 first and ignore the other 3D devices
197+
drivers.sort_by(|a, b| a.id.cmp(&b.id));
198+
199+
for dev in drivers {
200+
dev_map.insert(dev);
201+
}
202+
203+
Ok(dev_map)
204+
}

0 commit comments

Comments
 (0)