diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d3bcea..4ea8d4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,6 +117,142 @@ jobs: files: ${{ env.ARCHIVE }} fail_on_unmatched_files: true + release-linux-appimage: + name: Release / Linux X64 AppImage + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + path: 'Theseus' + - uses: actions/checkout@v4 + with: + repository: 'mpv-player/mpv' + ref: 'release/0.41' + path: 'mpv' + - name: Install deps + # libgl-dev: needed by projectM's cmake FindOpenGL probe even + # though the running binary uses bgfx/Vulkan. + # cmake: GitHub runners preinstall it, but make it explicit. + run: | + sudo apt update + sudo apt install -y wayland-protocols \ + libwayland-client0 libavfilter-dev \ + libplacebo-dev libass-dev \ + libcdio-dev libcdio-paranoia-dev \ + libdvdnav-dev libarchive-dev \ + libopenal-dev libpipewire-0.3-dev \ + libpulse-dev libdrm-dev \ + libdisplay-info-dev libgbm-dev \ + libwayland-dev libx11-dev \ + xscreensaver libxext-dev \ + libxpresent-dev libxrandr-dev \ + libxkbcommon-dev libxss-dev \ + libegl-dev libva-dev \ + libavformat-dev libswscale-dev \ + build-essential pkg-config \ + cmake libsdl2-dev \ + libsdl2-mixer-dev libvulkan-dev \ + libgl-dev libcurl4-openssl-dev \ + meson + + - name: Build minimal mpv + run: | + meson setup build \ + --auto-features disabled \ + -Dgpl='true' \ + -Dlibmpv='true' \ + -Dcdda=enabled \ + -Ddvbin=enabled \ + -Ddvdnav=enabled \ + -Dlibarchive=enabled \ + -Dopenal=enabled \ + -Degl='enabled' \ + -Ddrm='enabled' \ + -Dgbm='enabled' \ + -Degl-x11='enabled' \ + -Degl-wayland='enabled' \ + -Degl-drm='enabled' \ + -Dalsa='enabled' \ + -Dpulse='enabled' \ + -Dx11='enabled' \ + -Dvaapi='enabled' \ + -Dvaapi-drm='enabled' \ + -Dvaapi-wayland='enabled' \ + -Dvaapi-x11='enabled' \ + -Dpipewire='enabled' \ + -Dlibavdevice='disabled' \ + -Ddmabuf-wayland='enabled' \ + -Dwayland='enabled' + + meson compile -C build + + meson install -C build + + - name: Stub Configs/Data/Library + run: | + cd ~/Theseus + mkdir -p Configs Data Library + + - name: Build bgfx libs + run: | + cd theseus/third-party/bgfx + ../bx/tools/bin/linux/genie --gcc=linux-gcc gmake + make -C .build/projects/gmake-linux-gcc -j$(nproc) \ + bx bimg bgfx config=release64 + + - name: Build + run: | + make -C build desktop BGFX=1 + + echo "Manually installing libprojectm" + cp -r ~/Theseus/theseus/third-party/projectm/install /usr + + - name: Package AppImage + run: | + cd ~/ + mkdir -p AppImage/AppDir/usr/bin + mkdir -p AppImage/AppDir/usr/lib + mkdir -p AppImage/AppDir/usr/share + cd ./AppImage + cp -r ~/builds/theseus/desktop/* ./AppDir/usr/bin + echo "Install Appimage builder" + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20251107-1/linuxdeploy-x86_64.AppImage + wget https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-appimage-x86_64.AppImage + chmod +x ./linuxdeploy-x86_64.AppImage + chmod +x ./linuxdeploy-plugin-appimage-x86_64.AppImage + echo "Get Icon and write UIX-Desktop.desktop" + + wget 'https://avatars.githubusercontent.com/u/39630670' + + cat > UIX-Desktop.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=UIX Desktop + GenericName=Theseus + Comment=UIX Desktop - A Reverse Engineered Xbox Dashboard, now on PC! + Exec=theseus + Icon=theseus + Categories=Utility;Game; + Terminal=false + EOF + + apt install -y ffmpeg + mv ./39630670 ./ico.jpeg + ffmpeg -i ico.jpeg -vf scale=256:256 theseus.png + TAG="${{ github.event.inputs.tag || github.ref_name }}" + DROP="Theseus-${TAG}-linux-x64" + echo "Building Appimage" + ./linuxdeploy-x86_64.AppImage --appdir AppDir -d ./UIX-Desktop.desktop -i ./theseus.png --output appimage + mv UIX_Desktop.AppImage ${DROP}.AppImage + echo "APPIMG=${DROP}.AppImage" >> "$GITHUB_ENV" + - name: Upload to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.inputs.tag || github.ref_name }} + files: ${{ env.APPIMG }} + fail_on_unmatched_files: true + release-linux-arm64: name: Release / Linux ARM64 runs-on: ubuntu-24.04-arm diff --git a/theseus/desktop/launch.cpp b/theseus/desktop/launch.cpp index cff8933..ead36ca 100644 --- a/theseus/desktop/launch.cpp +++ b/theseus/desktop/launch.cpp @@ -57,8 +57,13 @@ static void ExecLaunch(const char* spec) // macOS: system handler works fine for steam:// execlp("open", "open", spec, (char*)NULL); #else + if (std::getenv("FLATPAK") != NULL) { + execlp("flatpak-spawn", "flatpak-spawn", "--host", "/usr/bin/steam", spec, (char*)NULL); + } + else { // Linux: bypass xdg-open/KIO; steam binary takes the URL directly. execlp("steam", "steam", spec, (char*)NULL); + } #endif } else if (IsUrl(spec)) @@ -66,7 +71,12 @@ static void ExecLaunch(const char* spec) #ifdef __APPLE__ execlp("open", "open", spec, (char*)NULL); #else - execlp("xdg-open", "xdg-open", spec, (char*)NULL); + if (std::getenv("FLATPAK") != NULL) { + execlp("flatpak-spawn", "flatpak-spawn", "--host", "/bin/sh", spec, (char*)NULL); + } + else { + execlp("xdg-open", "xdg-open", spec, (char*)NULL); + } #endif } else diff --git a/theseus/desktop/sdl_main.cpp b/theseus/desktop/sdl_main.cpp index 01651bf..6105633 100644 --- a/theseus/desktop/sdl_main.cpp +++ b/theseus/desktop/sdl_main.cpp @@ -24,6 +24,8 @@ #include "panel_shared.h" #include "launch.h" #include +#include +#include #ifdef THESEUS_USE_BGFX #include @@ -31,7 +33,6 @@ #include #include #include -#include // Loads Data/shaders//.bin. Picks subdir from the active renderer. bgfx::ShaderHandle theseus_bgfx_load_shader(const char* name) @@ -1129,8 +1130,19 @@ int main(int argc, char* argv[]) { // Change working directory to the executable's directory so that // relative paths (xboxfs/, etc.) work when launched from Finder/Explorer. + // Additionally check for xdg paths to use instead if running on Linux. + // And failover to (in order) ~/.local/share (manual) or default running dir { + + const char* xdgdir = std::getenv("XDG_DATA_HOME"); char exeDir[1024]; + + //prepare fallback ~/.local/share path + const char* homedir = std::getenv("HOME"); + char* localdir = "/.local/share"; + char* homelocaldir = (char *) malloc(1+strlen(homedir) + strlen(localdir) ); + strcpy(homelocaldir, homedir); + strcat(homelocaldir, localdir); #ifdef _WIN32 // GetModuleFileName + strip filename DWORD len = GetModuleFileNameA(NULL, exeDir, sizeof(exeDir)); @@ -1147,13 +1159,189 @@ int main(int argc, char* argv[]) { chdir(exeDir); } #else + // Try to use XDG path on linux. Should be $HOME/.local/share Fails over to default working dir behaviour if not set. + if (xdgdir != nullptr) { ssize_t len = readlink("/proc/self/exe", exeDir, sizeof(exeDir) - 1); - if (len > 0) { - exeDir[len] = '\0'; - char* last = strrchr(exeDir, '/'); - if (last) *last = '\0'; - chdir(exeDir); + if (len > 0) { + exeDir[len] = '\0'; + char* last = strrchr(exeDir, '/'); + if (last) *last = '\0'; + } + fprintf(stdout, "Using XDG directory: %s\n", xdgdir); + char* theseusdir = "/Theseus"; + char* configdir = "/Configs"; + char* configini = "/Configs/games.ini"; + char* datadir = "/Data"; + char* librarydir = "/Library"; + char* theseusworkdir = (char *) malloc(1+strlen(xdgdir) + strlen(theseusdir) ); + strcpy(theseusworkdir, xdgdir); + strcat(theseusworkdir, theseusdir); + // Make sure we know the main paths. This is used to ensure these paths exist in this location before trying to copy. + // We skip the config dir as it, and any files within, get generated at runtime. + char* execonfig = (char *) malloc(1+strlen(exeDir) + strlen(configdir) ); + char* exedata = (char *) malloc(1+strlen(exeDir) + strlen(datadir) ); + char* exelib = (char *) malloc(1+strlen(exeDir) + strlen(librarydir) ); + char* theseusconfigdir = (char *) malloc(1+strlen(theseusworkdir) + strlen(configdir) ); + char* theseusdatadir = (char *) malloc(1+strlen(theseusworkdir) + strlen(datadir) ); + char* theseuslibrarydir = (char *) malloc(1+strlen(theseusworkdir) + strlen(librarydir) ); + + // Copy contents of theseus work directory to each of the three main data paths + strcpy(execonfig, exeDir); + strcpy(exedata, exeDir); + strcpy(exelib, exeDir); + strcpy(theseusconfigdir, theseusworkdir); + strcpy(theseusdatadir, theseusworkdir); + strcpy(theseuslibrarydir, theseusworkdir); + // And concatonate the specific folders we are looking for + strcat(execonfig, configdir); + strcat(exedata, datadir); + strcat(exelib, librarydir); + strcat(theseusconfigdir, configdir); + strcat(theseusdatadir, datadir); + strcat(theseuslibrarydir, librarydir); + if (std::filesystem::is_directory(theseusworkdir) && std::filesystem::exists(theseusconfigdir) && std::filesystem::is_directory(theseusdatadir) && std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "All required directories exist in: %s\n", theseusworkdir); + chdir(theseusworkdir); + } + else { + fprintf(stdout, "XDG path missing files.. running self test\n"); + if (std::filesystem::is_directory(theseusworkdir)) { + fprintf(stdout, "Working dir exists... %s\n", theseusworkdir); + } + else { + fprintf(stdout, "Working dir missing in XDG Path.. Trying to create"); + std::filesystem::create_directory(theseusworkdir); + if(std::filesystem::is_directory(theseusworkdir)) { + fprintf(stdout, "XDG Working directory created... Copying data files."); + std::filesystem::copy(execonfig, theseusconfigdir, std::filesystem::copy_options::recursive); + std::filesystem::copy(exedata, theseusdatadir, std::filesystem::copy_options::recursive); + std::filesystem::copy(exelib, theseuslibrarydir, std::filesystem::copy_options::recursive); + } + } + if (std::filesystem::exists(theseusconfigdir)) { + fprintf(stdout, "Config files exist: %s\n", theseusconfigdir); + } + else { + fprintf(stdout, "Config files missing... Trying to copy to xdg path\n"); + std::filesystem::copy(execonfig, theseusconfigdir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseusdatadir)) { + fprintf(stdout, "Data dir exists: %s\n", theseusdatadir); + } + else { + fprintf(stdout, "Data dir missing... Trying to copy to xdg path\n"); + std::filesystem::copy(exedata, theseusdatadir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "Library dir exists: %s\n", theseuslibrarydir); + } + else { + fprintf(stdout, "Library dir missing... Trying to copy to xdg path\n"); + std::filesystem::copy(exelib, theseuslibrarydir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseusworkdir) && std::filesystem::exists(theseusconfigdir) && std::filesystem::is_directory(theseusdatadir) && std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "XDG setup complete... All required directories exist in: %s\n", theseusworkdir); + chdir(theseusworkdir); + } + else { + fprintf(stdout, "Attempted XDG setup failed... Failing over to default behaviour\n"); + ssize_t len = readlink("/proc/self/exe", exeDir, sizeof(exeDir) - 1); + if (len > 0) { + exeDir[len] = '\0'; + char* last = strrchr(exeDir, '/'); + if (last) *last = '\0'; + chdir(exeDir); + } + } + } + } + else if (std::filesystem::is_directory(homelocaldir)) { + char* theseusdir = "/Theseus"; + char* configdir = "/Configs"; + char* configini = "/Configs/games.ini"; + char* datadir = "/Data"; + char* librarydir = "/Library"; + char* theseusworkdir = (char *) malloc(1+strlen(homelocaldir) + strlen(theseusdir) ); + strcpy(theseusworkdir, homelocaldir); + strcat(theseusworkdir, theseusdir); + // Make sure we know the main paths. This is used to ensure these paths exist in this location before trying to copy. + // We skip the config dir as it, and any files within, get generated at runtime. + char* execonfig = (char *) malloc(1+strlen(exeDir) + strlen(configdir) ); + char* exedata = (char *) malloc(1+strlen(exeDir) + strlen(datadir) ); + char* exelib = (char *) malloc(1+strlen(exeDir) + strlen(librarydir) ); + char* theseusconfigdir = (char *) malloc(1+strlen(theseusworkdir) + strlen(configdir) ); + char* theseusdatadir = (char *) malloc(1+strlen(theseusworkdir) + strlen(datadir) ); + char* theseuslibrarydir = (char *) malloc(1+strlen(theseusworkdir) + strlen(librarydir) ); + + // Copy contents of theseus work directory to each of the three main data paths + strcpy(execonfig, exeDir); + strcpy(exedata, exeDir); + strcpy(exelib, exeDir); + strcpy(theseusconfigdir, theseusworkdir); + strcpy(theseusdatadir, theseusworkdir); + strcpy(theseuslibrarydir, theseusworkdir); + // And concatonate the specific folders we are looking for + strcat(execonfig, configdir); + strcat(exedata, datadir); + strcat(exelib, librarydir); + strcat(theseusconfigdir, configdir); + strcat(theseusdatadir, datadir); + strcat(theseuslibrarydir, librarydir); + if (std::filesystem::is_directory(theseusworkdir) && std::filesystem::exists(theseusconfigdir) && std::filesystem::is_directory(theseusdatadir) && std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "All required directories exist in: %s\n", theseusworkdir); + chdir(theseusworkdir); + } + else { + fprintf(stdout, "Share path missing files.. running self test\n"); + if (std::filesystem::is_directory(theseusworkdir)) { + fprintf(stdout, "Working dir exists... %s\n", theseusworkdir); + } + else { + fprintf(stdout, "Working dir missing in Share Path.. Trying to create"); + std::filesystem::create_directory(theseusworkdir); + if(std::filesystem::is_directory(theseusworkdir)) { + fprintf(stdout, "Share Working directory created... Copying data files."); + std::filesystem::copy(execonfig, theseusconfigdir, std::filesystem::copy_options::recursive); + std::filesystem::copy(exedata, theseusdatadir, std::filesystem::copy_options::recursive); + std::filesystem::copy(exelib, theseuslibrarydir, std::filesystem::copy_options::recursive); + } + } + if (std::filesystem::exists(theseusconfigdir)) { + fprintf(stdout, "Config files exist: %s\n", theseusconfigdir); + } + else { + fprintf(stdout, "Config files missing... Trying to copy to Share path\n"); + std::filesystem::copy(execonfig, theseusconfigdir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseusdatadir)) { + fprintf(stdout, "Data dir exists: %s\n", theseusdatadir); + } + else { + fprintf(stdout, "Data dir missing... Trying to copy to Share path\n"); + std::filesystem::copy(exedata, theseusdatadir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "Library dir exists: %s\n", theseuslibrarydir); + } + else { + fprintf(stdout, "Library dir missing... Trying to copy to xdg path\n"); + std::filesystem::copy(exelib, theseuslibrarydir, std::filesystem::copy_options::recursive); + } + if (std::filesystem::is_directory(theseusworkdir) && std::filesystem::exists(theseusconfigdir) && std::filesystem::is_directory(theseusdatadir) && std::filesystem::is_directory(theseuslibrarydir)) { + fprintf(stdout, "Share folder setup complete... All required directories exist in: %s\n", theseusworkdir); + chdir(theseusworkdir); + } } + } + else { + ssize_t len = readlink("/proc/self/exe", exeDir, sizeof(exeDir) - 1); + if (len > 0) { + exeDir[len] = '\0'; + char* last = strrchr(exeDir, '/'); + if (last) *last = '\0'; + } + } + #endif }