Skip to content

Commit ea32b0d

Browse files
committed
fix(launcher): resolve executable paths for app origin detection
#3975
1 parent 3a54127 commit ea32b0d

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

src/system/desktop_entry.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,36 @@ namespace {
2626

2727
constexpr Logger kLog("desktop_entry");
2828

29+
fs::path resolveExecutable(std::string_view executable) {
30+
const fs::path path(executable);
31+
if (path.has_parent_path()) {
32+
return path;
33+
}
34+
35+
const char* pathEnv = std::getenv("PATH");
36+
if (pathEnv == nullptr || pathEnv[0] == '\0') {
37+
return {};
38+
}
39+
40+
const std::string_view searchPath(pathEnv);
41+
std::size_t start = 0;
42+
while (start <= searchPath.size()) {
43+
const std::size_t end = searchPath.find(':', start);
44+
const std::string_view directory =
45+
end == std::string_view::npos ? searchPath.substr(start) : searchPath.substr(start, end - start);
46+
const fs::path candidate = directory.empty() ? path : fs::path(directory) / path;
47+
std::error_code ec;
48+
if (::access(candidate.c_str(), X_OK) == 0 && fs::is_regular_file(candidate, ec)) {
49+
return candidate;
50+
}
51+
if (end == std::string_view::npos) {
52+
break;
53+
}
54+
start = end + 1;
55+
}
56+
return {};
57+
}
58+
2959
bool executableIsAppImage(std::string_view exec) {
3060
const auto start = exec.find_first_not_of(' ');
3161
if (start == std::string_view::npos) {
@@ -44,7 +74,7 @@ namespace {
4474
return true;
4575
}
4676

47-
std::ifstream file(executable, std::ios::binary);
77+
std::ifstream file(resolveExecutable(executable.string()), std::ios::binary);
4878
std::array<char, 10> header{};
4979
if (!file.read(header.data(), static_cast<std::streamsize>(header.size()))) {
5080
return false;

tests/app_identity_test.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <optional>
1010
#include <string>
1111
#include <string_view>
12+
#include <sys/stat.h>
1213
#include <unistd.h>
1314
#include <vector>
1415

@@ -96,7 +97,11 @@ namespace {
9697
const fs::path root = fs::temp_directory_path() / ("noctalia-appimage-origin-" + std::to_string(getpid()));
9798
const fs::path applications = root / "data/applications";
9899
const fs::path executable = root / "PortableApp";
100+
const fs::path bin = root / "bin";
101+
const fs::path workingDirectory = root / "cwd";
99102
fs::create_directories(applications);
103+
fs::create_directories(bin);
104+
fs::create_directories(workingDirectory);
100105
{
101106
std::ofstream binary(executable, std::ios::binary);
102107
binary.write(
@@ -120,15 +125,36 @@ namespace {
120125
std::ofstream entry(applications / "suffix.desktop");
121126
entry << "[Desktop Entry]\nType=Application\nName=Suffixed AppImage\nExec=/opt/Suffixed.AppImage\n";
122127
}
128+
{
129+
std::ofstream binary(bin / "native-app");
130+
binary << "#!/bin/sh\nexit 0\n";
131+
chmod((bin / "native-app").c_str(), 0700);
132+
fs::create_symlink(executable, bin / "path-appimage");
133+
fs::create_symlink(executable, workingDirectory / "native-app");
134+
}
135+
{
136+
std::ofstream entry(applications / "native.desktop");
137+
entry << "[Desktop Entry]\nType=Application\nName=Native App\nExec=native-app\n";
138+
}
139+
{
140+
std::ofstream entry(applications / "path.desktop");
141+
entry << "[Desktop Entry]\nType=Application\nName=PATH AppImage\nExec=path-appimage\n";
142+
}
123143

124144
const char* oldDataHome = std::getenv("XDG_DATA_HOME");
125145
const char* oldDataDirs = std::getenv("XDG_DATA_DIRS");
146+
const char* oldPath = std::getenv("PATH");
126147
const std::optional<std::string> savedDataHome =
127148
oldDataHome == nullptr ? std::nullopt : std::optional<std::string>(oldDataHome);
128149
const std::optional<std::string> savedDataDirs =
129150
oldDataDirs == nullptr ? std::nullopt : std::optional<std::string>(oldDataDirs);
151+
const std::optional<std::string> savedPath =
152+
oldPath == nullptr ? std::nullopt : std::optional<std::string>(oldPath);
153+
const fs::path savedWorkingDirectory = fs::current_path();
130154
setenv("XDG_DATA_HOME", (root / "data").c_str(), 1);
131155
setenv("XDG_DATA_DIRS", (root / "empty").c_str(), 1);
156+
setenv("PATH", bin.c_str(), 1);
157+
fs::current_path(workingDirectory);
132158

133159
const auto entries = scanDesktopEntries();
134160
const auto findOrigin = [&](std::string_view id) {
@@ -138,6 +164,10 @@ namespace {
138164
TEST_CHECK(findOrigin("metadata") == DesktopEntryOrigin::AppImage);
139165
TEST_CHECK(findOrigin("portable") == DesktopEntryOrigin::AppImage);
140166
TEST_CHECK(findOrigin("suffix") == DesktopEntryOrigin::AppImage);
167+
TEST_CHECK(findOrigin("native") == DesktopEntryOrigin::System);
168+
TEST_CHECK(findOrigin("path") == DesktopEntryOrigin::AppImage);
169+
170+
fs::current_path(savedWorkingDirectory);
141171

142172
if (savedDataHome.has_value()) {
143173
setenv("XDG_DATA_HOME", savedDataHome->c_str(), 1);
@@ -149,6 +179,11 @@ namespace {
149179
} else {
150180
unsetenv("XDG_DATA_DIRS");
151181
}
182+
if (savedPath.has_value()) {
183+
setenv("PATH", savedPath->c_str(), 1);
184+
} else {
185+
unsetenv("PATH");
186+
}
152187
fs::remove_all(root);
153188
}
154189

0 commit comments

Comments
 (0)