diff --git a/.build-aux/fetchcontent2flatpak.py b/.build-aux/fetchcontent2flatpak.py new file mode 100644 index 0000000..9f8f62e --- /dev/null +++ b/.build-aux/fetchcontent2flatpak.py @@ -0,0 +1,241 @@ +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Final + +FETCHCONTENT_REGEX: Final[str] = ( + r"^--\ Fetching\ (?P.+)\ (?P.+)\ (?P.+)\n?$" +) +SHA1_REGEX: Final[str] = r"^[0-9a-f]{40}$" + + +class FetchContent: + name: str + url: str + branch: str | None = None + commit: str | None = None + tag: str | None = None + custom_flag: str | None = None + + def __init__( + self, name: str, url: str, rev: str, custom_flag: str | None = None + ) -> None: + self.name = name + self.url = url + + if custom_flag: + self.custom_flag = custom_flag + + if not re.match(SHA1_REGEX, rev, re.IGNORECASE): + self.branch = rev + + # Maybe not a good idea running processes on __init__? + result = subprocess.run( + ["git", "ls-remote", url, rev], stdout=subprocess.PIPE, text=True + ) + if result.returncode == 0: + output = result.stdout.rstrip().split() + + if re.match(SHA1_REGEX, output[0], re.IGNORECASE): + self.commit = output[0] + if output[1].startswith("refs/tags/"): + self.tag = output[1].split("/")[2] + else: + self.commit = rev + + +def parse_stdout(command: list[str]) -> list[FetchContent]: + matches: list[FetchContent] = [] + + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=sys.stderr, + text=True, + bufsize=1, + ) + + if process.stdout: + for line in process.stdout: + line = line.rstrip() + print(line) + + try_match = re.match(FETCHCONTENT_REGEX, line) + if try_match: + name, url, rev = try_match.groups() + matches.append(FetchContent(name, url, rev)) + + process.stdout.close() + + returncode = process.wait() + if returncode != 0: + raise subprocess.CalledProcessError(returncode, command) + + return matches + + +def flatpak_configure( + build_dir: str, runtime: str, additional_args: list[str] = [] +) -> list[FetchContent]: + flatpak = shutil.which("flatpak") + cwd = os.getcwd() + + if not flatpak: + raise FileNotFoundError("flatpak") + + command = [ + flatpak, + "run", + "--devel", + "--share=network", + f"--filesystem={cwd}", + f"--filesystem={build_dir}", + "--command=cmake", + runtime, + "-B", + build_dir, + *additional_args, + ] + return parse_stdout(command) + + +def local_configure( + build_dir: str, additional_args: list[str] = [] +) -> list[FetchContent]: + cmake = shutil.which("cmake") + + if not cmake: + raise FileNotFoundError("cmake") + + command = [cmake, "-B", build_dir, *additional_args] + return parse_stdout(command) + + +def to_flatpak(sources: list[FetchContent]): + sources_array: list[dict[str, str]] = [] + flags: list[str] = [] + + for source in sources: + obj = { + "type": "git", + "url": source.url, + "dest": f"_deps/{source.name.lower()}", + } + + if source.commit: + obj.update({"commit": source.commit}) + if source.tag: + obj.update({"tag": source.tag}) + # elif source.branch: + # obj.update({"branch": source.branch}) + + sources_array.append(obj) + + env = ( + source.custom_flag + if source.custom_flag + else f"FETCHCONTENT_SOURCE_DIR_{source.name.upper()}" + ) + # Assumes `builddir: true` on manifest + flags.append(f"-D{env}=../_deps/{source.name.lower()}") + + sources_json = json.dumps(sources_array, indent=4) + + return (sources_json, "\n".join(flags)) + + +def to_nix(sources: list[FetchContent]): + lines: list[str] = [] + flags: list[str] = [] + + flags.append("cmakeFlags = with finalAttrs; [") + + for source in sources: + lines.append(f"{source.name.lower()}-src = fetchgit {{") + lines.append(f' url = "{source.url}";') + if source.tag: + lines.append(f' tag = "{source.tag}";') + elif source.commit: + lines.append(f' rev = "{source.commit}";') + lines.append(' hash = "TODO";') # I don't use nix, btw + lines.append("};\n") + + env = ( + source.custom_flag + if source.custom_flag + else f"FETCHCONTENT_SOURCE_DIR_{source.name.upper()}" + ) + flags.append(f' (lib.cmakeFeature "{env}" "${{{source.name.lower()}-src}}")') + + flags.append("];") + + return ("\n".join(lines), "\n".join(flags)) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--runtime", "-r") + parser.add_argument("--output", "-o") + parser.add_argument("--to-flatpak", "-f", action="store_true") + parser.add_argument("--to-nix", "-n", action="store_true") + parser.add_argument("--args", action="store_true") + + opts, args = parser.parse_known_args() + args = args if opts.args else [] + + matches: list[FetchContent] = [] + + if opts.to_flatpak or opts.to_nix: + with tempfile.TemporaryDirectory() as build_dir: + try: + if opts.runtime: + matches.extend(flatpak_configure(build_dir, opts.runtime, args)) + else: + matches.extend(local_configure(build_dir, args)) + except FileNotFoundError as err: + print(f'File "{err}" was not found!') + return 1 + except subprocess.CalledProcessError as err: + print(f'Subprocess "{" ".join(err.cmd)}" failed!') + return 1 + else: + print("You need to specify either --to-flatpak or --to-nix") + return 1 + + matches.append( + FetchContent( + "cryptopp", + "https://github.com/weidai11/cryptopp", + "master", + "CRYPTOPP_SOURCES", + ) + ) + + if opts.to_flatpak: + sources, flags = to_flatpak(matches) + + print() + if opts.output: + path = Path(opts.output) + path.write_text(sources) + else: + print(sources) + + print(flags) + elif opts.to_nix: + sources, flags = to_nix(matches) + print() + print(sources) + print(flags) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.build-aux/flatpak/fetchcontent-sources.json b/.build-aux/flatpak/fetchcontent-sources.json new file mode 100644 index 0000000..c8aea4d --- /dev/null +++ b/.build-aux/flatpak/fetchcontent-sources.json @@ -0,0 +1,87 @@ +[ + { + "type": "git", + "url": "https://github.com/craftablescience/BufferStream", + "dest": "_deps/bufferstream", + "commit": "2a7c9e8b786fa50a3ae1961f1ee5bca6c4a5a6c5" + }, + { + "type": "git", + "url": "https://github.com/craftablescience/compressonator", + "dest": "_deps/cmp_compressonator", + "commit": "f9c8c58fe753108c260b33ec32301805c33c08b7" + }, + { + "type": "git", + "url": "https://github.com/abdes/cryptopp-cmake", + "dest": "_deps/cryptopp-cmake", + "commit": "866aceb8b13b6427a3c4541288ff412ad54f11ea" + }, + { + "type": "git", + "url": "https://github.com/richgel999/miniz", + "dest": "_deps/miniz", + "commit": "4b9fcf1df525114484be49f3216169b061c07ac6" + }, + { + "type": "git", + "url": "https://github.com/craftablescience/minizip-ng", + "dest": "_deps/minizip-ng", + "commit": "2f0041b6f7c2193a06d18ca47ccd81fc7070ee8f" + }, + { + "type": "git", + "url": "https://github.com/zlib-ng/zlib-ng", + "dest": "_deps/zlib", + "commit": "12731092979c6d07f42da27da673a9f6c7b13586" + }, + { + "type": "git", + "url": "https://sourceware.org/git/bzip2.git", + "dest": "_deps/bzip2", + "commit": "af79253677ad98d6dfe11ea315ee9947d86586d3" + }, + { + "type": "git", + "url": "https://github.com/tukaani-project/xz", + "dest": "_deps/liblzma", + "commit": "ebb0e6789cefe3be71756881aa8f2009fda9938c" + }, + { + "type": "git", + "url": "https://github.com/ip7z/7zip", + "dest": "_deps/ppmd", + "commit": "5e96a8279489832924056b1fa82f29d5837c9469", + "tag": "25.01" + }, + { + "type": "git", + "url": "https://github.com/facebook/zstd", + "dest": "_deps/zstd", + "commit": "f8745da6ff1ad1e7bab384bd1f9d742439278e99" + }, + { + "type": "git", + "url": "https://github.com/phoboslab/qoi", + "dest": "_deps/qoi", + "commit": "6fff9b70dd79b12f808b0acc5cb44fde9998725e" + }, + { + "type": "git", + "url": "https://github.com/syoyo/tinyexr", + "dest": "_deps/tinyexr", + "commit": "3ffe5f9d5e673e6e8c378d59b306b2824993e705" + }, + { + "type": "git", + "url": "https://github.com/webmproject/libwebp", + "dest": "_deps/webp", + "commit": "f342dfc1756785df8803d25478bf664c0de629de" + }, + { + "type": "git", + "url": "https://github.com/weidai11/cryptopp", + "dest": "_deps/cryptopp", + "commit": "b5242667a24e3db8e4600e77b2e502ef204e5280" + } +] \ No newline at end of file diff --git a/.build-aux/flatpak/science.craftable.MareTF.yml b/.build-aux/flatpak/science.craftable.MareTF.yml new file mode 100644 index 0000000..06a6560 --- /dev/null +++ b/.build-aux/flatpak/science.craftable.MareTF.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/flatpak/flatpak-builder/refs/heads/main/data/flatpak-manifest.schema.json +id: science.craftable.MareTF +runtime: org.kde.Platform +runtime-version: "6.10" +sdk: org.kde.Sdk +command: maretf-wrapper +finish-args: + - --share=ipc + - --socket=wayland + - --socket=fallback-x11 + - --device=dri + # Discord RPC support + - --filesystem=xdg-run/app/com.discordapp.Discord:create + +modules: + - name: maretf + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DMARETF_BUILD_THUMBNAILER=OFF + - -DMARETF_BUILD_INSTALLER=ON + - -DMARETF_USE_LTO=ON + - -DFLATPAK=ON + + - -DFETCHCONTENT_SOURCE_DIR_BUFFERSTREAM=../_deps/bufferstream + - -DFETCHCONTENT_SOURCE_DIR_CMP_COMPRESSONATOR=../_deps/cmp_compressonator + - -DFETCHCONTENT_SOURCE_DIR_CRYPTOPP-CMAKE=../_deps/cryptopp-cmake + - -DFETCHCONTENT_SOURCE_DIR_MINIZ=../_deps/miniz + - -DFETCHCONTENT_SOURCE_DIR_MINIZIP-NG=../_deps/minizip-ng + - -DFETCHCONTENT_SOURCE_DIR_ZLIB=../_deps/zlib + - -DFETCHCONTENT_SOURCE_DIR_BZIP2=../_deps/bzip2 + - -DFETCHCONTENT_SOURCE_DIR_LIBLZMA=../_deps/liblzma + - -DFETCHCONTENT_SOURCE_DIR_PPMD=../_deps/ppmd + - -DFETCHCONTENT_SOURCE_DIR_ZSTD=../_deps/zstd + - -DFETCHCONTENT_SOURCE_DIR_QOI=../_deps/qoi + - -DFETCHCONTENT_SOURCE_DIR_TINYEXR=../_deps/tinyexr + - -DFETCHCONTENT_SOURCE_DIR_WEBP=../_deps/webp + - -DCRYPTOPP_SOURCES=../_deps/cryptopp + sources: + - fetchcontent-sources.json + - type: dir + path: ../../ + + - name: wrapper + buildsystem: simple + build-commands: + - install -Dm0755 maretf-wrapper "${FLATPAK_DEST}/bin" + sources: + - type: inline + dest-filename: maretf-wrapper + contents: | + #!/usr/bin/env bash + for i in {0..9}; do + test -S $XDG_RUNTIME_DIR/discord-ipc-$i || ln -sf {app/com.discordapp.Discord,$XDG_RUNTIME_DIR}/discord-ipc-$i; + done + + exec maretf_gui "$@" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1abb528..81c2b7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -333,12 +333,38 @@ jobs: ${{env.BUILD_DIR}}/maretf_gui retention-days: 7 + build-flatpak: + runs-on: ubuntu-latest + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 + options: --privileged + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6.6 + with: + bundle: science.craftable.MareTF.flatpak + manifest-path: .build-aux/flatpak/science.craftable.MareTF.yml + upload-artifact: false + + - name: Upload Flatpak + uses: actions/upload-artifact@v4 + with: + name: 'MareTF-Linux-Flatpak-Installer-gcc-Release' + path: science.craftable.MareTF.flatpak + retention-days: 7 + deploy: needs: - build-windows-maretf - build-windows-stratasource - build-linux-maretf - build-linux-stratasource + - build-flatpak runs-on: ubuntu-latest steps: - name: Download Artifacts diff --git a/.gitignore b/.gitignore index ddd6643..0844d21 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,12 @@ out/ *.so* +# Flatpak +.flatpak-builder/ +builddir/ +repo/ + + # Generated **/generated/ src/common/Config.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d4bf80f..13e2bf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ include(CS_All) set(PROJECT_NAME_PRETTY "MareTF" CACHE STRING "" FORCE) cs_version_pretty() # PROJECT_VERSION_PRETTY set(PROJECT_ORGANIZATION_NAME "craftablescience" CACHE STRING "" FORCE) +set(PROJECT_APPLICATION_ID "science.craftable.${PROJECT_NAME_PRETTY}" CACHE STRING "" FORCE) # options option(MARETF_BUILD_FOR_STRATA_SOURCE "Build ${PROJECT_NAME_PRETTY} for Strata Source games" OFF) diff --git a/install/_install.cmake b/install/_install.cmake index 5f742e4..03ee8ee 100644 --- a/install/_install.cmake +++ b/install/_install.cmake @@ -63,12 +63,30 @@ elseif(UNIX) # Desktop file configure_file( "${CMAKE_CURRENT_LIST_DIR}/linux/desktop.in" - "${CMAKE_CURRENT_LIST_DIR}/linux/generated/${PROJECT_NAME}.desktop") - install(FILES "${CMAKE_CURRENT_LIST_DIR}/linux/generated/${PROJECT_NAME}.desktop" + "${CMAKE_CURRENT_LIST_DIR}/linux/generated/${PROJECT_APPLICATION_ID}.desktop") + install(PROGRAMS "${CMAKE_CURRENT_LIST_DIR}/linux/generated/${PROJECT_APPLICATION_ID}.desktop" DESTINATION "share/applications") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/logo.png" DESTINATION "share/icons/hicolor/512x512/apps" - RENAME "${PROJECT_NAME}.png") + RENAME "${PROJECT_APPLICATION_ID}.png") + + # MIME type info + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/linux/mime-type.xml.in" + "${CMAKE_CURRENT_LIST_DIR}/linux/generated/mime-type.xml") + install(FILES "${CMAKE_CURRENT_LIST_DIR}/linux/generated/mime-type.xml" + DESTINATION "share/mime/packages" + RENAME "${PROJECT_APPLICATION_ID}.xml") + endif() + + if(MARETF_BUILD_CLI OR MARETF_BUILD_GUI) + # MetaInfo file + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/linux/metainfo.xml.in" + "${CMAKE_CURRENT_LIST_DIR}/linux/generated/metainfo.xml") + install(FILES "${CMAKE_CURRENT_LIST_DIR}/linux/generated/metainfo.xml" + DESTINATION "share/metainfo" + RENAME "${PROJECT_APPLICATION_ID}.metainfo.xml") endif() if(MARETF_BUILD_THUMBNAILER) @@ -81,18 +99,6 @@ elseif(UNIX) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/install/linux/generated/${PROJECT_NAME}.thumbnailer" DESTINATION "share/thumbnailers") endif() - - if(MARETF_BUILD_GUI OR MARETF_BUILD_THUMBNAILER) - # Use system Qt - no install rules - - # MIME type info - configure_file( - "${CMAKE_CURRENT_LIST_DIR}/linux/mime-type.xml.in" - "${CMAKE_CURRENT_LIST_DIR}/linux/generated/mime-type.xml") - install(FILES "${CMAKE_CURRENT_LIST_DIR}/linux/generated/mime-type.xml" - DESTINATION "share/mime/packages" - RENAME "${PROJECT_NAME}.xml") - endif() else() message(FATAL_ERROR "No install rules for selected platform.") endif() @@ -140,7 +146,7 @@ if(WIN32) set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait 'regsvr32 /s \\\"$INSTDIR\\\\${PROJECT_NAME}_thumbnailer.dll\\\"'") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'regsvr32 /u /s \\\"$INSTDIR\\\\${PROJECT_NAME}_thumbnailer.dll\\\"'") endif() -else() +elseif(NOT FLATPAK) if(CPACK_GENERATOR STREQUAL "DEB") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR} <${CPACK_PACKAGE_CONTACT}>") if(MARETF_BUILD_GUI) @@ -158,7 +164,7 @@ else() set(CPACK_RPM_COMPRESSION_TYPE "zstd") endif() else() - message(FATAL_ERROR "CPACK_GENERATOR is unset! Only DEB and RPM generators are supported.") + message(AUTHOR_WARNING "CPACK_GENERATOR is unset! Only DEB and RPM generators are supported.") endif() endif() include(CPack) diff --git a/install/linux/desktop.in b/install/linux/desktop.in index 8605429..4d01dd2 100644 --- a/install/linux/desktop.in +++ b/install/linux/desktop.in @@ -1,9 +1,9 @@ [Desktop Entry] Name=${PROJECT_NAME_PRETTY} Comment=${PROJECT_DESCRIPTION} -Exec=sh -c 'if [ -n "$__NV_PRIME_RENDER_OFFLOAD" ]; then export QT_QPA_PLATFORM=xcb; fi; exec ${PROJECT_NAME}_gui "$@"' _ %f -Icon=${PROJECT_NAME} +Exec=${PROJECT_NAME}_gui %F +Icon=${PROJECT_APPLICATION_ID} Terminal=false Type=Application -Categories=Qt;Development;Utility; +Categories=Graphics;Utility;2DGraphics;RasterGraphics; MimeType=image/x-vtf;image/x-xtf; diff --git a/install/linux/metainfo.xml.in b/install/linux/metainfo.xml.in new file mode 100644 index 0000000..26cd924 --- /dev/null +++ b/install/linux/metainfo.xml.in @@ -0,0 +1,43 @@ + + + ${PROJECT_APPLICATION_ID} + ${PROJECT_NAME_PRETTY} + A utility to create, edit, and display every type of VTF file ever made + + Laura Lewis + https://craftable.science + + CC0-1.0 + MIT + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magnam aliquam quaerat voluptatem.

+
+ + + Lorem ipsum dolor sit amet + https://raw.githubusercontent.com/craftablescience/MareTF/refs/heads/mane/res/brand/screenshot1.png + + + ${PROJECT_HOMEPAGE_URL} + ${PROJECT_HOMEPAGE_URL} + ${PROJECT_HOMEPAGE_URL}/issues + https://ko-fi.com/craftablescience + + #ffffff + #000000 + + + + https://github.com/craftablescience/MareTF/releases/tag/v0.10.2 + + + https://github.com/craftablescience/MareTF/releases/tag/v0.10.1 + + + + ${PROJECT_APPLICATION_ID}.desktop + + ${PROJECT_NAME}_gui + ${PROJECT_NAME} + +
\ No newline at end of file diff --git a/install/linux/mime-type.xml.in b/install/linux/mime-type.xml.in index 5ac39e9..84b8247 100644 --- a/install/linux/mime-type.xml.in +++ b/install/linux/mime-type.xml.in @@ -3,12 +3,11 @@ Valve Texture Format File - + VTF Valve Texture Format - @@ -43,12 +42,11 @@ Valve Xbox Texture Format File - + XTF Valve Xbox Texture Format - diff --git a/src/common/Config.h.in b/src/common/Config.h.in index 746c61c..e3c6b88 100644 --- a/src/common/Config.h.in +++ b/src/common/Config.h.in @@ -6,5 +6,6 @@ #define PROJECT_VERSION "${PROJECT_VERSION}" #define PROJECT_VERSION_PRETTY "${PROJECT_VERSION_PRETTY}" #define PROJECT_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}" +#define PROJECT_APPLICATION_ID "${PROJECT_APPLICATION_ID}" #define PROJECT_TITLE PROJECT_NAME_PRETTY " v" PROJECT_VERSION_PRETTY diff --git a/src/gui/MareTF.cpp b/src/gui/MareTF.cpp index a0a5eb2..f419306 100644 --- a/src/gui/MareTF.cpp +++ b/src/gui/MareTF.cpp @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) { QCoreApplication::setApplicationVersion(PROJECT_VERSION); #if !defined(__APPLE__) && !defined(_WIN32) - QGuiApplication::setDesktopFileName(PROJECT_NAME); + QGuiApplication::setDesktopFileName(PROJECT_APPLICATION_ID); #endif const auto options = std::make_unique(); diff --git a/src/gui/utility/QMareDiscordPresence.cpp b/src/gui/utility/QMareDiscordPresence.cpp index 9b6e444..7f2543a 100644 --- a/src/gui/utility/QMareDiscordPresence.cpp +++ b/src/gui/utility/QMareDiscordPresence.cpp @@ -28,7 +28,7 @@ void QMareDiscordPresence::init(std::string_view appID) { __try { #endif DiscordEventHandlers handlers{}; - Discord_Initialize(appID.data(), &handlers, 1, nullptr); + Discord_Initialize(appID.data(), &handlers, 0, nullptr); std::atexit(&QMareDiscordPresence::shutdown); g_DiscordInitialized = true; #ifdef _WIN32