Initial work on Flatpak support

This commit is contained in:
Charlie Le 2024-11-19 14:20:22 -05:00
parent af5c57f0f8
commit 2cd30019d1
10 changed files with 249 additions and 71 deletions

96
dist/flatpak/org.gabmus.envision.json vendored Normal file
View file

@ -0,0 +1,96 @@
{
"id": "org.gabmus.envision",
"branch": "47",
"runtime": "org.gnome.Sdk",
"runtime-version": "47",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm19"
],
"command": "envision",
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm19/bin",
"env": {
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang",
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold",
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER": "clang",
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold"
},
"build-args": [
"--share=network"
]
},
"cleanup": [
"/share/doc",
"/share/man"
],
"finish-args": [
"--share=ipc",
"--share=network",
"--socket=wayland",
"--socket=fallback-x11",
"--socket=pulseaudio",
"--device=all",
"--filesystem=xdg-run/pipewire-0",
"--filesystem=xdg-run/monado_comp_ipc",
"--filesystem=~/.steam",
"--filesystem=~/.var/app/com.valvesoftware.Steam",
"--talk-name=org.freedesktop.Flatpak"
],
"modules": [
{
"name": "OpenXR-SDK",
"buildsystem": "cmake",
"sources": [
{
"type": "git",
"url": "https://github.com/KhronosGroup/OpenXR-SDK-Source.git",
"tag": "release-1.1.42"
}
]
},
{
"name": "vte",
"buildsystem": "meson",
"config-opts": [
"-Dgtk4=true",
"-Dgtk3=false"
],
"sources": [
{
"type": "archive",
"url": "https://gitlab.gnome.org/GNOME/vte/-/archive/0.78.0/vte-0.78.0.tar.gz",
"sha256": "82e19d11780fed4b66400f000829ce5ca113efbbfb7975815f26ed93e4c05f2d"
}
]
},
{
"name": "eigen",
"buildsystem": "cmake",
"builddir": true,
"build-options": {
"config-opts": [
"-DEIGEN_BUILD_CMAKE_PACKAGE=NO"
]
},
"sources": [
{
"type": "git",
"url": "https://gitlab.com/libeigen/eigen.git",
"tag": "3.4.0"
}
]
},
{
"name": "Envision",
"buildsystem": "meson",
"sources": [
{
"type": "dir",
"path": "../../"
}
]
}
]
}

View file

@ -125,6 +125,8 @@ fn include_paths() -> Vec<String> {
vec![
"/usr/include".into(),
"/usr/local/include".into(),
// flatpak applications use /app
"/app/include".into(),
// fedora puts avcodec here
"/usr/include/ffmpeg".into(),
"/usr/include/x86_64-linux-gnu".into(),

View file

@ -1,8 +1,5 @@
use crate::{
paths::{get_backup_dir, SYSTEM_PREFIX},
profile::Profile,
util::file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly},
xdg::XDG,
is_flatpak::IS_FLATPAK, paths::{get_backup_dir, get_home_dir, SYSTEM_PREFIX}, profile::Profile, util::file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, xdg::XDG
};
use serde::{Deserialize, Serialize};
use std::{
@ -138,17 +135,28 @@ fn relativize_active_runtime_lib_path(ar: &ActiveRuntime, path: &Path) -> Active
}
pub fn set_current_active_runtime_to_profile(profile: &Profile) -> anyhow::Result<()> {
let dest = get_active_runtime_json_path();
set_file_readonly(&dest, false)?;
backup_steam_active_runtime();
let pfx = profile.clone().prefix;
let mut ar = build_profile_active_runtime(profile)?;
// hack: relativize libopenxr_monado.so path for system installs
if pfx == PathBuf::from(SYSTEM_PREFIX) {
ar = relativize_active_runtime_lib_path(&ar, &dest);
let mut dests = vec![
get_active_runtime_json_path(),
get_home_dir().join(".var/app/com.valvesoftware.Steam/config/openxr/1/active_runtime.json"),
];
if *IS_FLATPAK {
dests.push(get_home_dir().join(".config/openxr/1/active_runtime.json"));
}
backup_steam_active_runtime();
let pfx: PathBuf = profile.clone().prefix;
for dest in dests {
if !dest.is_file() {
continue;
}
let mut ar = build_profile_active_runtime(profile)?;
// hack: relativize libopenxr_monado.so path for system installs
if pfx == PathBuf::from(SYSTEM_PREFIX) {
ar = relativize_active_runtime_lib_path(&ar, &dest);
}
set_file_readonly(&dest, false)?;
dump_active_runtime_to_path(&ar, &dest)?;
set_file_readonly(&dest, true)?;
}
dump_current_active_runtime(&ar)?;
set_file_readonly(&dest, true)?;
Ok(())
}

View file

@ -1,10 +1,7 @@
use std::path::{Path, PathBuf};
use crate::{
paths::get_backup_dir,
profile::Profile,
util::file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly},
xdg::XDG,
is_flatpak::IS_FLATPAK, paths::{get_home_dir, get_backup_dir}, profile::Profile, util::file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, xdg::XDG
};
use serde::{ser::Error, Deserialize, Serialize};
@ -23,9 +20,19 @@ pub fn get_openvr_conf_dir() -> PathBuf {
}
fn get_openvrpaths_vrpath_path() -> PathBuf {
if *IS_FLATPAK {
return get_home_dir().join(".config/openvr/openvrpaths.vrpath")
}
get_openvr_conf_dir().join("openvrpaths.vrpath")
}
fn get_openvrpaths_vrpaths() -> Vec<PathBuf> {
vec![
get_openvrpaths_vrpath_path(),
get_home_dir().join(".var/app/com.valvesoftware.Steam/config/openvr/openvrpaths.vrpath"),
]
}
pub fn is_steam(ovr_paths: &OpenVrPaths) -> bool {
ovr_paths.runtime.iter().any(|rt| {
rt.to_string_lossy()
@ -104,11 +111,15 @@ pub fn build_profile_openvrpaths(profile: &Profile) -> OpenVrPaths {
}
pub fn set_current_openvrpaths_to_profile(profile: &Profile) -> anyhow::Result<()> {
let dest = get_openvrpaths_vrpath_path();
set_file_readonly(&dest, false)?;
backup_steam_openvrpaths();
dump_current_openvrpaths(&build_profile_openvrpaths(profile))?;
set_file_readonly(&dest, true)?;
for dest in get_openvrpaths_vrpaths() {
if !dest.is_file() {
continue;
}
set_file_readonly(&dest, false)?;
dump_openvrpaths_to_path(&build_profile_openvrpaths(profile), &dest)?;
set_file_readonly(&dest, true)?;
}
Ok(())
}

6
src/is_flatpak.rs Normal file
View file

@ -0,0 +1,6 @@
use lazy_static::lazy_static;
use std::path::Path;
lazy_static! {
pub static ref IS_FLATPAK: bool = Path::new("/.flatpak-info").is_file();
}

View file

@ -30,6 +30,7 @@ pub mod env_var_descriptions;
pub mod file_builders;
pub mod gpu_profile;
pub mod is_appimage;
pub mod is_flatpak;
pub mod linux_distro;
pub mod log_parser;
pub mod openxr_prober;

View file

@ -20,34 +20,21 @@ use crate::{
build_mercury::get_build_mercury_jobs, build_monado::get_build_monado_jobs,
build_opencomposite::get_build_opencomposite_jobs, build_openhmd::get_build_openhmd_jobs,
build_wivrn::get_build_wivrn_jobs,
},
config::Config,
constants::APP_NAME,
depcheck::{
}, config::Config, constants::APP_NAME, depcheck::{
basalt_deps::get_missing_basalt_deps, common::dep_pkexec,
libsurvive_deps::get_missing_libsurvive_deps, mercury_deps::get_missing_mercury_deps,
monado_deps::get_missing_monado_deps, openhmd_deps::get_missing_openhmd_deps,
wivrn_deps::get_missing_wivrn_deps,
},
file_builders::{
}, file_builders::{
active_runtime_json::{
set_current_active_runtime_to_profile, set_current_active_runtime_to_steam,
},
openvrpaths_vrpath::{
set_current_openvrpaths_to_profile, set_current_openvrpaths_to_steam,
},
},
linux_distro::LinuxDistro,
openxr_prober::is_openxr_ready,
paths::get_data_dir,
profile::{Profile, XRServiceType},
stateless_action,
steam_linux_runtime_injector::{
}, is_flatpak::IS_FLATPAK, linux_distro::LinuxDistro, openxr_prober::is_openxr_ready, paths::get_data_dir, profile::{Profile, XRServiceType}, stateless_action, steam_linux_runtime_injector::{
restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile,
},
util::file_utils::{setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd},
vulkaninfo::VulkanInfo,
xr_devices::XRDevice,
}, util::file_utils::{setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd}, vulkaninfo::VulkanInfo, xr_devices::XRDevice
};
use adw::{prelude::*, ResponseAppearance};
use gtk::glib::{self, clone};
@ -56,7 +43,7 @@ use relm4::{
new_action_group, new_stateful_action, new_stateless_action,
prelude::*,
};
use std::{collections::VecDeque, fs::remove_file, time::Duration};
use std::{collections::{HashMap, VecDeque}, fs::remove_file, process::Command, time::Duration};
pub struct App {
application: adw::Application,
@ -175,9 +162,20 @@ impl App {
};
self.debug_view.sender().emit(DebugViewMsg::ClearLog);
self.xr_devices = vec![];
remove_file(prof.xrservice_type.ipc_file_path())
.is_err()
.then(|| println!("Failed to remove xrservice IPC file"));
if *IS_FLATPAK {
Command::new("flatpak-spawn")
.args(vec![
"--host",
"rm",
prof.xrservice_type.ipc_file_path().to_str().unwrap()
])
.output()
.expect("Failed to remove xrservice IPC file");
} else {
remove_file(prof.xrservice_type.ipc_file_path())
.is_err()
.then(|| println!("Failed to remove xrservice IPC file"));
}
let worker = JobWorker::xrservice_worker_wrap_from_profile(
&prof,
sender.input_sender(),
@ -218,11 +216,28 @@ impl App {
let prof = self.get_selected_profile();
if let Some(autostart_cmd) = &prof.autostart_command {
let mut jobs = VecDeque::new();
jobs.push_back(WorkerJob::new_cmd(
Some(prof.environment.clone()),
"sh".into(),
Some(vec!["-c".into(), autostart_cmd.clone()]),
));
if *IS_FLATPAK {
let mut args = vec![String::from("--host")];
for (key, value) in &prof.environment {
args.push(format!("--env={}={}", key, value));
}
args.push(String::from("sh"));
args.push(String::from("-c"));
args.push(autostart_cmd.clone());
jobs.push_back(WorkerJob::new_cmd(
Some(HashMap::new()),
"flatpak-spawn".into(),
Some(args),
));
} else {
jobs.push_back(WorkerJob::new_cmd(
Some(prof.environment.clone()),
"sh".into(),
Some(vec!["-c".into(), autostart_cmd.clone()]),
));
}
let autostart_worker = JobWorker::new(jobs, sender.input_sender(), |msg| match msg {
JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows),
JobWorkerOut::Exit(code) => Msg::OnAutostartExit(code),
@ -533,7 +548,7 @@ impl AsyncComponent for App {
.sender()
.emit(BuildWindowMsg::UpdateBuildStatus(BuildStatus::Done));
let profile = self.get_selected_profile();
if dep_pkexec().check() {
if dep_pkexec().check() || *IS_FLATPAK {
self.setcap_confirm_dialog.present(Some(&self.app_win));
} else {
alert_w_widget(
@ -606,11 +621,11 @@ impl AsyncComponent for App {
.emit(DebugViewMsg::UpdateSelectedProfile(prof.clone()));
}
Msg::RunSetCap => {
if !dep_pkexec().check() {
println!("pkexec not found, skipping setcap");
} else {
if dep_pkexec().check() || *IS_FLATPAK {
let profile = self.get_selected_profile();
setcap_cap_sys_nice_eip(&profile).await;
} else {
println!("pkexec not found, skipping setcap");
}
}
Msg::ProfileSelected(prof) => {
@ -698,7 +713,7 @@ impl AsyncComponent for App {
let worker = JobWorker::new_with_timer(
Duration::from_millis(500),
WorkerJob::new_func(Box::new(move || {
let ready = is_openxr_ready();
let ready = !*IS_FLATPAK && is_openxr_ready();
FuncWorkerOut {
success: ready,
..Default::default()

View file

@ -3,19 +3,12 @@ use super::{
state::JobWorkerState,
};
use crate::{
profile::{LighthouseDriver, Profile},
ui::SENDER_IO_ERR_MSG,
is_flatpak::IS_FLATPAK, profile::{LighthouseDriver, Profile}, ui::SENDER_IO_ERR_MSG
};
use nix::unistd::Pid;
use relm4::{prelude::*, Worker};
use std::{
collections::VecDeque,
io::{BufRead, BufReader},
mem,
os::unix::process::ExitStatusExt,
process::{Command, Stdio},
sync::{Arc, Mutex},
thread,
collections::{HashMap, VecDeque}, io::{BufRead, BufReader}, mem, os::unix::process::ExitStatusExt, process::{Command, Stdio}, sync::{Arc, Mutex}, thread
};
macro_rules! logger_thread {
@ -217,13 +210,23 @@ impl InternalJobWorker {
vec![],
),
};
let data = CmdWorkerData {
environment: env,
command,
args,
let mut data = CmdWorkerData {
environment: env.clone(),
command: command.clone(),
args: args.clone(),
};
if *IS_FLATPAK {
data.environment = HashMap::new();
data.command = String::from("flatpak-spawn");
data.args = vec![String::from("--host")];
for (key, value) in env {
data.args.push(format!("--env={}={}", key, value));
}
data.args.push(command);
data.args.extend(args);
}
let mut jobs = VecDeque::new();
jobs.push_back(WorkerJob::Cmd(data));
jobs.push_back(WorkerJob::Cmd(data));
Self::builder().detach_worker(JobWorkerInit { jobs, state })
}
}

View file

@ -17,6 +17,7 @@ use crate::{
config::Config,
depcheck::common::dep_pkexec,
gpu_profile::{get_amd_gpu_power_profile, GpuPowerProfile},
is_flatpak::IS_FLATPAK,
paths::{get_data_dir, get_home_dir},
profile::{LighthouseDriver, Profile, XRServiceType},
stateless_action,
@ -270,7 +271,7 @@ impl SimpleComponent for MainView {
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
set_visible: match mount_has_nosuid(&model.selected_profile.prefix) {
set_visible: !*IS_FLATPAK && match mount_has_nosuid(&model.selected_profile.prefix) {
Ok(b) => b,
Err(_) => {
eprintln!(
@ -302,7 +303,34 @@ impl SimpleComponent for MainView {
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
set_visible: !dep_pkexec().check(),
set_visible: *IS_FLATPAK,
warning_heading(),
gtk::Label {
set_label: concat!(
"Envision is currently running as a Flatpak.\n",
"If Steam is running as a Flatpak, it will need to be granted certain ",
"permissions to run VR applications. Run the following command on your host ",
"terminal to grant the Steam Flatpak access to Envision's Monado socket:\n\n",
"<tt>flatpak --user override --filesystem=xdg-run/monado_comp_ipc com.valvesoftware.Steam</tt>\n\n",
"Run the following to also grant the Steam Flatpak access to Envision's data:\n\n",
"<tt>flatpak --user override --filesystem=~/.var/app/org.gabmus.envision com.valvesoftware.Steam</tt>\n\n",
),
set_use_markup: true,
add_css_class: "warning",
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
}
},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: false,
set_spacing: 12,
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
set_visible: !*IS_FLATPAK && !dep_pkexec().check(),
warning_heading(),
gtk::Label {
set_label: &format!(

View file

@ -1,4 +1,4 @@
use crate::{async_process::async_process, profile::Profile};
use crate::{async_process::async_process, is_flatpak::IS_FLATPAK, profile::Profile};
use anyhow::bail;
use nix::{
errno::Errno,
@ -81,7 +81,15 @@ pub fn setcap_cap_sys_nice_eip_cmd(profile: &Profile) -> Vec<String> {
}
pub async fn setcap_cap_sys_nice_eip(profile: &Profile) {
if let Err(e) = async_process("pkexec", Some(&setcap_cap_sys_nice_eip_cmd(profile)), None).await
let setcap_cmd = setcap_cap_sys_nice_eip_cmd(profile);
if *IS_FLATPAK {
let mut flatpak_cmd = vec!["--host".into(), "pkexec".into()];
flatpak_cmd.extend(setcap_cmd);
if let Err(e) = async_process("flatpak-spawn", Some(&flatpak_cmd), None).await
{
eprintln!("Error: failed running setcap: {e}");
}
} else if let Err(e) = async_process("pkexec", Some(&setcap_cmd), None).await
{
eprintln!("Error: failed running setcap: {e}");
}