Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ members = [
"tests/examples/eui-neo-app-main",
"tests/examples/eui-neo-markdown",
"tests/examples/eui-neo-sdl2",
"tests/examples/eui-neo-tray",
"tests/examples/eui-neo-vulkan",
"tests/examples/eui-neo-window",
"tests/examples/ffmpeg",
Expand Down
59 changes: 35 additions & 24 deletions pkgs/e/compat.eui-neo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -606,17 +606,18 @@ end
-- work by an accident of the archive's shape rather than by anything this
-- descriptor controls. Adding a hook means owning that.
--
-- ⚠️ DONE IN THE SHELL, and not for brevity: this xim's Lua sandbox has
-- neither `os.files` nor `path.basename`, and both absences surface as
-- `attempt to call a nil value` AFTER the entire dependency set has
-- downloaded. `os.exec` / `os.isfile` / `os.isdir` / `os.mkdir` are what the
-- rest of this index uses, so they are what this uses.
-- ⚠️ WHAT THIS RUNTIME ACTUALLY OFFERS, measured here rather than read out of
-- xmake's sources -- descriptors run on libxpkg, which is a different and much
-- narrower environment. Three things a first draft assumed, and none hold:
-- `os.files` and `path.basename` are absent, and `os.cp` takes a LITERAL path,
-- so a glob argument copies nothing without raising. Every one of those
-- surfaces only AFTER the whole dependency set has downloaded, which is why
-- the assertions further down are worth their lines.
local function normalise_layout(layer)
-- ⚠️ NO SHELL. `sh -c` does not exist on a Windows runner, and this hook
-- runs on every platform -- the flag that says otherwise is further down,
-- and it only guards the glib staging. Listing a directory is also out
-- (`os.files` is not in this sandbox), so the archive's shape is ASKED
-- ABOUT by name rather than discovered.
-- ⚠️ NO SHELL HERE. `sh -c` does not exist on a Windows runner and this
-- hook runs on every platform; the host guard is further down and it only
-- covers the glib staging. With no directory listing available, the
-- archive's shape is ASKED ABOUT by name rather than discovered.
local v = pkginfo.version()
for _, name in ipairs({ "EUI-NEO-" .. v, "eui-neo-" .. v,
"EUI-NEO-v" .. v, "eui-neo-v" .. v }) do
Expand All @@ -625,21 +626,17 @@ local function normalise_layout(layer)
return os.isfile(path.join(layer, "CMakeLists.txt"))
end
end
-- A mirror that flattened its tarball: everything at this level IS the
-- tree, so give it the one layer the globs expect.
-- ⚠️ NO FALLBACK FOR A FLATTENED ARCHIVE, and that is a measured decision
-- rather than an omission. A draft of this file carried one built on
-- `os.cp("*", layer)`; libxpkg's `os.cp` copies a LITERAL path and silently
-- copies nothing when handed a glob, so the branch could only ever have
-- returned false one line later -- a fallback in shape only. Reaching the
-- error below is strictly more honest than appearing to recover.
--
-- ⚠️ DEFENSIVE, NOT OBSERVED. An earlier draft of this change said the CN
-- mirror does not preserve the wrap layer. Checked rather than repeated:
-- `eui-neo-0.5.6.tar.gz` from gitcode unpacks to `EUI-NEO-0.5.6/`, byte
-- for byte the same archive as GitHub's (same sha256, which is what one
-- `sha256` field for two URLs already required). Every mirrored version
-- was re-downloaded and checked the same way. This branch exists so the
-- globs cannot depend on that continuing to hold.
if os.isfile("CMakeLists.txt") then
os.mkdir(layer)
os.cp("*", layer)
return os.isfile(path.join(layer, "CMakeLists.txt"))
end
-- Nothing published needs it either: `eui-neo-0.5.6.tar.gz` from gitcode
-- unpacks to `EUI-NEO-0.5.6/`, byte for byte the same archive as GitHub's
-- (one `sha256` field for two URLs already required that), and every
-- mirrored version was re-downloaded and checked the same way.
return false
end

Expand Down Expand Up @@ -674,6 +671,20 @@ local function stage_glib(outdir)
-- host guard in install()), and preserving the SYMLINKS matters. glib ships
-- `libgio-2.0.so -> .so.0 -> .so.0.8000.0`; dereferencing them would stage
-- three full copies of each library and lose the name the linker resolves.
-- ⚠️ THE SHELL IS DOING TWO THINGS `os.cp` CANNOT, both measured against
-- this runtime rather than assumed:
--
-- * GLOBBING. libxpkg's `os.cp` copies a literal path; handed
-- `libgio-2.0.so*` it silently copies NOTHING, and the assertion below
-- is what catches it. The versioned real file (`libgio-2.0.so.0.8000.0`)
-- cannot be named literally without discovering it, and this sandbox
-- has no directory listing (`os.files` is absent).
-- * SYMLINKS. glib ships `libgio-2.0.so -> .so.0 -> .so.0.8000.0`;
-- dereferencing that chain would stage three full copies of each
-- library and lose the name `-lgio-2.0` actually resolves to.
--
-- Safe despite running inside a hook that fires on every platform, because
-- install() returns before this on any non-Linux host.
for _, pattern in ipairs(glib_libs) do
os.exec("for lib in " .. sh_quote(path.join(glib.path, "lib")) .. "/" .. pattern ..
"; do [ -e \"$lib\" ] || continue; " ..
Expand Down
28 changes: 28 additions & 0 deletions tests/examples/eui-neo-tray/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# compat.eui-neo's system tray, over the public `core::platform` surface.
#
# The other six eui-neo members cover the headless surface, the two entry-point
# shapes, and the SDL2 / Vulkan / markdown legs. None of them touches the tray,
# and none of them CAN detect it: tray_bridge.c's `#else` arm defines all six
# tray symbols and returns 0 from each, so a descriptor that lost its backend
# define keeps every existing member green while the tray quietly stops
# existing. This member exists to make that difference observable.
#
# ALL THREE PLATFORMS, deliberately not cfg-gated. The descriptor gives windows
# EUI_TRAY_WINAPI and macOS EUI_TRAY_APPKIT unconditionally, and linux
# EUI_TRAY_SNI since 0.5.7; `core/platform/platform.h` is platform-clean, so the
# consumer code below is identical everywhere. Only the ASSERTION is
# linux-specific, for the reason given in tests/tray.cpp.
#
# compat.eui-neo is the only declared dependency. On linux the tray pulls glib
# through the package's own staged copy and its `runtime` deps, all of which
# mcpp propagates to the consumer -- the same convention as eui-neo-sdl2 and
# eui-neo-vulkan, which include <SDL.h> / <vulkan/vulkan.h> without re-declaring
# either.
#
# The `compat` index redirect is inherited from the workspace root.
[package]
name = "eui-neo-tray-tests"
version = "0.1.0"

[dependencies.compat]
eui-neo = "0.5.7"
152 changes: 152 additions & 0 deletions tests/examples/eui-neo-tray/tests/tray.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// compat.eui-neo's SYSTEM TRAY, through the public C++ surface.
//
// This is the shape a consumer should copy. `core::platform` exposes the whole
// tray as six free functions and a two-field options struct, and none of it
// needs a window, a GL context, or the DSL app loop -- a tray-only utility is
// the ten lines in runTray() below. The raw C bridge in
// core/platform/tray_bridge.h is an implementation detail and is not used here.
//
// WHY THIS MEMBER EXISTS, and why the assertion is the shape it is.
//
// tray_bridge.c selects one of four backends at preprocessor level --
// EUI_TRAY_WINAPI, EUI_TRAY_APPKIT, EUI_TRAY_SNI, EUI_TRAY_APPINDICATOR -- and
// falls through to an `#else` stub when none is defined. THE STUB DEFINES ALL
// SIX SYMBOLS and returns 0 from every one of them. A descriptor that lost its
// backend define would still compile, still link, and still pass all six other
// eui-neo members, while the tray silently stopped existing: "no tray" and "not
// tested" would read identically. That is the failure this member is built
// against.
//
// Two criteria were tried and only the second one works:
//
// * `dlsym(RTLD_DEFAULT, "g_bus_get_sync") != nullptr` -- BLUNT, verified by
// rebuilding with the define removed and watching it pass anyway. The
// descriptor's `ldflags` carry `-lgio-2.0` unconditionally, so gio is in
// DT_NEEDED and its symbols resolve whether or not the SNI arm was
// compiled. It answered "is gio linked", not "is the backend real".
//
// * initializeTray() under a session bus -- SHARP. Measured both ways on a
// bare `dbus-run-session` with no panel and no display: the real backend
// returns true (it exports its StatusNotifierItem object and waits for a
// watcher), the stub returns false unconditionally.
//
// So on linux this test supplies its own private bus and asserts the tray comes
// up. It needs no display, no panel, and no desktop session, which is what lets
// it run on an ordinary CI runner.
//
// Windows and macOS get compile-and-link coverage only. Their backends are
// unconditional in the descriptor, and neither has an equivalent cheap probe
// that a headless runner can perform.

#include "core/platform/platform.h"

#if defined(__linux__)
#include <limits.h>
#include <unistd.h>
#endif

import std;

namespace {

// What a consumer actually writes. Everything else in this file is about
// proving the backend is real; this is the feature.
int runTray() {
core::platform::TrayOptions options;
options.tooltip = "EUI NEO tray example";
options.iconPath = ""; // empty: the panel substitutes a placeholder icon

if (!core::platform::initializeTray(options)) {
std::println("tray unavailable (no session bus)");
return 1;
}

std::println("tray is live -- right click it for Show / Exit");
while (!core::platform::consumeTrayExitRequested()) {
core::platform::pollTray(false);
if (core::platform::consumeTrayShowRequested()) std::println(" -> Show");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}

core::platform::shutdownTray();
std::println("tray shut down");
return 0;
}

#if defined(__linux__)
// Re-run this same executable with a private session bus around it. Returns
// only on failure; on success `dbus-run-session` replaces this process and the
// second entry takes the branch below it.
void reexec_under_private_bus(const char* marker) {
::setenv(marker, "1", 1);

char self[PATH_MAX];
const ssize_t n = ::readlink("/proc/self/exe", self, sizeof(self) - 1);
if (n <= 0) return;
self[n] = '\0';

char* const argv[] = { const_cast<char*>("dbus-run-session"),
const_cast<char*>("--"), self, nullptr };
::execvp("dbus-run-session", argv);
}
#endif

} // namespace

int main() {
// Safe on every platform and before any backend has been touched: the state
// is a plain bool and the stub's eui_tray_is_initialized() also returns 0.
// This pins the query, not the backend.
if (core::platform::isTrayInitialized()) {
std::println(stderr, "isTrayInitialized() is true before initializeTray()");
return 1;
}

if (std::getenv("MCPP_RUN_TRAY") != nullptr) return runTray();

#if defined(__linux__)
constexpr const char* kMarker = "EUI_TRAY_TEST_HAS_BUS";

if (std::getenv("DBUS_SESSION_BUS_ADDRESS") == nullptr) {
if (std::getenv(kMarker) != nullptr) {
// dbus-run-session ran but produced no bus address. Do not report
// this as a pass: an unverifiable backend is exactly what this
// member exists to prevent.
std::println(stderr,
"dbus-run-session left no DBUS_SESSION_BUS_ADDRESS, so "
"the linux tray backend could not be verified");
return 1;
}
reexec_under_private_bus(kMarker);
std::println(stderr,
"could not exec dbus-run-session, so the linux tray backend "
"could not be verified. It ships with dbus; install that "
"package on this runner.");
return 1;
}

// THE ASSERTION. True only when tray_bridge.c compiled a real backend --
// the EUI_TRAY_HAS_BACKEND=0 stub returns 0 here no matter what the bus is.
core::platform::TrayOptions options;
options.tooltip = "compat.eui-neo tray smoke test";
if (!core::platform::initializeTray(options)) {
std::println(stderr,
"compat.eui-neo was built WITHOUT its linux tray backend: a "
"session bus is present, yet initializeTray() failed, which "
"is what tray_bridge.c's EUI_TRAY_HAS_BACKEND=0 stub does "
"unconditionally. Expected -DEUI_TRAY_SNI=1 and glib on the "
"link line (see pkgs/e/compat.eui-neo.lua).");
return 1;
}

if (!core::platform::isTrayInitialized()) {
std::println(stderr, "initializeTray() succeeded but isTrayInitialized() is false");
return 1;
}
core::platform::shutdownTray();
std::println("linux tray backend verified over a private session bus");
#else
std::println("tray path compiled and linked; run with MCPP_RUN_TRAY=1 to show it");
#endif
return 0;
}
Loading