├── .dockerignore
├── .github
└── workflows
│ └── on-release.yml
├── LICENSE
├── README.md
├── deploy
├── Dockerfile.backend.build
├── Dockerfile.backend.dev
├── Dockerfile.launcher.build
├── Dockerfile.launcher.dev
├── Dockerfile.wizard.build
├── Dockerfile.wizard.dev
├── flatimage-alpine.sh
└── libraries-wizard.txt
├── doc
├── img
│ ├── PCSX2.svg
│ ├── RPCS3.svg
│ ├── alpine.svg
│ ├── arch.svg
│ ├── archlinux.svg
│ ├── banner.svg
│ ├── fedora.svg
│ ├── gameimage.png
│ ├── gameimage.svg
│ ├── getfedora-ar21.svg
│ ├── heart.svg
│ ├── linux-mint.svg
│ ├── linux.svg
│ ├── opensuse.svg
│ ├── pcsx2.webp
│ ├── retroarch.png
│ ├── retroarch.svg
│ ├── rpcs3.jpg
│ ├── steam.svg
│ ├── ubuntu-ar21.svg
│ ├── ubuntu.svg
│ └── wine.svg
├── legacy
│ ├── example-gui-1.png
│ ├── example-gui-2.png
│ ├── example-gui-launcher.png
│ ├── example-gui.png
│ ├── example-gui.xcf
│ ├── gui-installer-1.png
│ ├── gui-installer-2.png
│ ├── gui-installer-3.png
│ ├── gui-launcher-1.png
│ ├── gui-launcher-2.png
│ ├── not-aidan-tutorial.mp4
│ ├── wall.png
│ └── wine.mp4
├── readme
│ ├── showcase-00.png
│ ├── showcase-01.png
│ ├── showcase-02.png
│ └── showcase-03.png
├── toc.sh
└── video
│ ├── tutorial-demo.mp4
│ ├── tutorial-download.mp4
│ ├── tutorial-linux.low.mp4
│ ├── tutorial-linux.mp4
│ ├── tutorial-wine-multi.low.mp4
│ ├── tutorial-wine-multi.mp4
│ ├── tutorial-wine.low.mp4
│ └── tutorial-wine.mp4
├── gui
├── Cargo.toml
├── launcher
│ ├── Cargo.toml
│ └── src
│ │ ├── common.rs
│ │ ├── db
│ │ ├── mod.rs
│ │ └── project.rs
│ │ ├── frame
│ │ ├── cover.rs
│ │ ├── fail.rs
│ │ ├── menu.rs
│ │ ├── menu
│ │ │ ├── enabler_executable.rs
│ │ │ └── environment.rs
│ │ ├── mod.rs
│ │ ├── selector_executable.rs
│ │ └── selector_game.rs
│ │ ├── games.rs
│ │ └── main.rs
├── shared
│ ├── Cargo.toml
│ └── src
│ │ ├── db
│ │ ├── kv.rs
│ │ └── mod.rs
│ │ ├── dimm.rs
│ │ ├── fltk.rs
│ │ ├── fltk
│ │ ├── button.rs
│ │ ├── dialog.rs
│ │ ├── frame.rs
│ │ ├── macros.rs
│ │ ├── paginator.rs
│ │ ├── progress.rs
│ │ └── separator.rs
│ │ ├── image.rs
│ │ ├── lib.rs
│ │ ├── std.rs
│ │ └── svg.rs
└── wizard
│ ├── Cargo.toml
│ └── src
│ ├── common.rs
│ ├── db
│ ├── fetch.rs
│ ├── global.rs
│ ├── mod.rs
│ └── project.rs
│ ├── frame
│ ├── common.rs
│ ├── creator.rs
│ ├── desktop.rs
│ ├── finish.rs
│ ├── icon.rs
│ ├── mod.rs
│ ├── platform.rs
│ ├── term.rs
│ └── welcome.rs
│ ├── gameimage
│ ├── desktop.rs
│ ├── fetch.rs
│ ├── gameimage.rs
│ ├── init.rs
│ ├── install.rs
│ ├── mod.rs
│ ├── package.rs
│ ├── project.rs
│ ├── search.rs
│ ├── select.rs
│ └── test.rs
│ ├── lib
│ ├── ipc.rs
│ └── mod.rs
│ ├── main.rs
│ └── wizard
│ ├── compress.rs
│ ├── install.rs
│ ├── linux.rs
│ ├── mod.rs
│ ├── name.rs
│ ├── pcsx2.rs
│ ├── retroarch.rs
│ ├── rpcs3.rs
│ ├── test.rs
│ └── wine.rs
├── src
├── CMakeLists.txt
├── Dockerfile
├── boot
│ └── boot.cpp
├── build.sh
├── cmd
│ ├── compress.hpp
│ ├── desktop.hpp
│ ├── fetch.hpp
│ ├── fetch
│ │ └── check.hpp
│ ├── init.hpp
│ ├── install.hpp
│ ├── package.hpp
│ ├── project.hpp
│ ├── search.hpp
│ ├── select.hpp
│ └── test.hpp
├── common.hpp
├── conanfile.txt
├── enum.hpp
├── lib
│ ├── db.hpp
│ ├── db
│ │ ├── build.hpp
│ │ ├── fetch.hpp
│ │ └── project.hpp
│ ├── hope.hpp
│ ├── image.hpp
│ ├── ipc.hpp
│ ├── log.hpp
│ ├── parser.hpp
│ ├── sha.hpp
│ ├── subprocess.hpp
│ ├── tar.hpp
│ └── zip.hpp
├── macro.hpp
├── main.cpp
└── std
│ ├── concepts.hpp
│ ├── copy.hpp
│ ├── env.hpp
│ ├── exception.hpp
│ ├── fifo.hpp
│ ├── filesystem.hpp
│ ├── string.hpp
│ └── vector.hpp
└── thumbnailer
├── appimage.thumbnailer
├── install.sh
└── thumbnailer-appimage
/.dockerignore:
--------------------------------------------------------------------------------
1 | **
2 | !.dockerignore
3 | !.github/workflows/on-release.yml
4 | !.gitlab-ci.yml
5 | !Dockerfile
6 | !LICENSE
7 | !README.md
8 | !deploy
9 | !gui/Cargo.toml
10 | !gui/launcher
11 | !gui/shared
12 | !gui/wizard
13 | !src/std
14 | !src/cmd
15 | !src/boot
16 | !src/lib
17 | !src/CMakeLists.txt
18 | !src/Dockerfile
19 | !src/build.sh
20 | !src/common.hpp
21 | !src/conanfile.txt
22 | !src/enum.hpp
23 | !src/macro.hpp
24 | !src/main.cpp
25 |
--------------------------------------------------------------------------------
/.github/workflows/on-release.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: linux-x86_64
4 |
5 | on:
6 | workflow_dispatch:
7 | release:
8 | types: [ created, edited ]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-22.04
13 |
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v3
17 |
18 | - name: Build Package
19 | run: |
20 | # Install tools
21 | sudo apt install -y jq wget tar xz-utils pv git pcregrep
22 | # Custom packages
23 | mkdir -p bin
24 | export PATH="$(pwd)/bin:$PATH"
25 | wget -q --show-progress --progress=dot:binary -O bin/jq \
26 | https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux-amd64
27 | chmod +x ./bin/*
28 | # Build packages
29 | ( HOME="$(pwd)" FIM_DEBUG=1 ./deploy/flatimage-alpine.sh )
30 | # Create dist folder
31 | mkdir -p dist
32 | mv ./build/gameimage dist/gameimage.flatimage
33 | # Create SHA for gameimage.flatimage
34 | ( cd dist && sha256sum gameimage.flatimage > gameimage.flatimage.sha256sum )
35 |
36 | - name: Set permissions for dist directory
37 | run: |
38 | sudo chown -R "$(id -u)":"$(id -g)" dist/
39 | sudo chmod -R 766 dist/
40 |
41 | - name: Upload artifact to release
42 | uses: actions/upload-artifact@v3
43 | with:
44 | name: gameimage
45 | path: dist
46 |
47 | - name: Compute Short SHA
48 | id: ssha
49 | run: |
50 | echo "ssha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
51 |
52 | outputs:
53 | ssha: ${{ steps.ssha.outputs.ssha }}
54 |
55 | release:
56 | runs-on: ubuntu-latest
57 | needs: build
58 | steps:
59 | - uses: actions/download-artifact@v3
60 | with:
61 | name: gameimage
62 | path: dist
63 | - name: Upload to release
64 | uses: fnkr/github-action-ghr@v1
65 | env:
66 | GHR_PATH: dist/
67 | GHR_REPLACE: true
68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.backend.build:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/ruanformigoni/gameimage-backend-dev
2 |
3 | COPY ./src /backend
4 |
5 | WORKDIR /backend
6 |
7 | RUN conan install . --build=missing -g CMakeDeps -g CMakeToolchain
8 | RUN cmake --preset conan-release -DCMAKE_BUILD_TYPE=Release
9 | RUN cmake --build --preset conan-release
10 |
11 | # Move to dist
12 | RUN mkdir /dist
13 | RUN cp ./build/Release/main /dist
14 | RUN cp ./build/Release/boot /dist
15 | WORKDIR /dist
16 |
17 | # Strip
18 | RUN strip -s *
19 |
20 | # Compress
21 | RUN upx -6 --no-lzma *
22 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.backend.dev:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Enable community repo
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install deps
9 | RUN apk update && apk upgrade
10 | RUN apk add --no-cache build-base git libbsd-dev py3-pip pipx git patchelf cmake gcc \
11 | bash e2fsprogs xz curl zstd gawk debootstrap m4 gcompat nasm wget upx \
12 | boost-dev boost-static
13 |
14 | # Fonts to copy from inside the container
15 | RUN apk add --no-cache font-noto
16 |
17 | # Install conan
18 | RUN pipx install conan
19 |
20 | # Update PATH
21 | ENV PATH="/root/.local/bin:$PATH"
22 |
23 | # Setup
24 | RUN conan profile detect --force
25 |
26 | # Copy files
27 | COPY ./src /backend
28 |
29 | # Set workdir
30 | WORKDIR /backend
31 |
32 | # Compile
33 | # # This will fail ;(, needs to run, patch, run again
34 | RUN conan install . --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build || true
35 | # # nasm bug when on musl system "undefined symbol"
36 | # # m4 bug when on musl system "undefined symbol"
37 | RUN cp "$(command -v nasm)" /root/.conan2/p/nasmc*/p/bin/nasm
38 | RUN cp "$(command -v m4)" /root/.conan2/p/m4*/p/bin/m4
39 | # # Should work now
40 | RUN conan install . --build=missing -g CMakeDeps -g CMakeToolchain
41 | # The conan cache should now be built in /root/.conan2, remove project files
42 | WORKDIR /
43 | RUN rm -rf /backend
44 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.launcher.build:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/ruanformigoni/gameimage-launcher-dev
2 |
3 | # Copy files
4 | RUN mkdir /gameimage
5 | COPY ./gui /gameimage/
6 |
7 | # Retrieve target folder
8 | RUN mv /target /gameimage/target
9 |
10 | # Enter src dir
11 | WORKDIR gameimage
12 |
13 | # Compile
14 | RUN cd launcher && cargo build --release
15 |
16 | # Move to dist
17 | RUN mkdir /dist
18 | RUN cp ./target/release/launcher /dist/launcher
19 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.launcher.dev:
--------------------------------------------------------------------------------
1 | FROM archlinux:latest
2 |
3 | # Update
4 | RUN pacman -Syy --noconfirm
5 | RUN pacman-key --init
6 | RUN pacman -S --noconfirm archlinux-keyring
7 | RUN pacman -Syu --overwrite "*" --noconfirm
8 |
9 | # Install dependencies
10 | RUN pacman -S --noconfirm --overwrite "*" autoconf automake binutils bison debugedit fakeroot \
11 | file findutils flex gawk gcc gettext grep groff gzip libtool m4 make patch \
12 | pkgconf sed sudo texinfo which git cmake gtk3 wayland-protocols wayland dbus
13 |
14 | # Setup rust
15 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
16 | ENV PATH "/root/.cargo/bin:$PATH"
17 |
18 | # Copy files
19 | RUN mkdir /gameimage
20 | COPY ./gui /gameimage/
21 |
22 | # Enter src dir
23 | WORKDIR gameimage
24 |
25 | # Compile
26 | RUN cd launcher && cargo build --release
27 |
28 | # Save target folder
29 | WORKDIR /
30 | RUN mv /gameimage/target /target
31 | RUN rm -rf /gameimage
32 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.wizard.build:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/ruanformigoni/gameimage-wizard-dev
2 |
3 | # Copy sources
4 | COPY ./gui /gameimage
5 |
6 | # Enter src dir
7 | WORKDIR /gameimage
8 |
9 | # Retrieve pre-compiled dependencies
10 | RUN mv /target /gameimage/target
11 |
12 | # Compile wizard
13 | RUN cd wizard && cargo build --release --target=x86_64-unknown-linux-musl
14 |
15 | # Make dist
16 | RUN mkdir /dist
17 | RUN cp /gameimage/target/x86_64-unknown-linux-musl/release/wizard /dist
18 |
--------------------------------------------------------------------------------
/deploy/Dockerfile.wizard.dev:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Switch to edge
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install packages
9 | RUN apk update
10 | RUN apk add git wget curl patchelf py3-pip pipx file build-base cmake \
11 | fuse3-dev libxinerama-dev libxcursor-dev libxfixes-dev libxft-dev pango \
12 | pango-dev libayatana-appindicator libayatana-appindicator-dev gtk+3.0-dev \
13 | bash fuse fuse3 zlib-static glib-static cairo-static lddtree font-noto \
14 | openssl-dev wayland wayland-protocols wayland-dev dbus dbus-dev libssl3
15 |
16 | # Symlink compilers
17 | RUN ln -sfT /usr/bin/gcc /usr/bin/musl-gcc
18 | RUN ln -sfT /usr/bin/g++ /usr/bin/musl-g++
19 |
20 | # Compile as shared libraries
21 | ENV RUSTFLAGS='-C target-feature=-crt-static'
22 |
23 | # Install rust
24 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
25 |
26 | # Update env
27 | ENV PATH="/root/.cargo/bin:$PATH"
28 |
29 | # Copy sources
30 | COPY ./gui /gameimage
31 |
32 | # Enter src dir
33 | WORKDIR gameimage
34 |
35 | # Compile
36 | RUN cargo build --release --target=x86_64-unknown-linux-musl
37 |
38 | # Move target folder
39 | RUN mv ./target /target
40 |
41 | # Remove gameimage folder
42 | RUN rm -rf /gameimage
43 |
--------------------------------------------------------------------------------
/deploy/libraries-wizard.txt:
--------------------------------------------------------------------------------
1 | /usr/lib/libayatana-appindicator3.so.1
2 | /usr/lib/libayatana-appindicator3.so
3 | /usr/lib/libayatana-appindicator3.so.1.0.0
4 | /usr/lib/libayatana-indicator3.so.7.0.0
5 | /usr/lib/libayatana-indicator3.so.7
6 | /usr/lib/libayatana-appindicator3.so
7 | /usr/lib/libayatana-ido3-0.4.so.0
8 | /usr/lib/libayatana-ido3-0.4.so.0.0.0
9 |
--------------------------------------------------------------------------------
/doc/img/PCSX2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/img/RPCS3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/doc/img/arch.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/img/archlinux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/img/fedora.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/img/gameimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/img/gameimage.png
--------------------------------------------------------------------------------
/doc/img/heart.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/img/pcsx2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/img/pcsx2.webp
--------------------------------------------------------------------------------
/doc/img/retroarch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/img/retroarch.png
--------------------------------------------------------------------------------
/doc/img/retroarch.svg:
--------------------------------------------------------------------------------
1 |
2 |
47 |
--------------------------------------------------------------------------------
/doc/img/rpcs3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/img/rpcs3.jpg
--------------------------------------------------------------------------------
/doc/img/steam.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/img/ubuntu-ar21.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/img/ubuntu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/doc/img/wine.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
56 |
--------------------------------------------------------------------------------
/doc/legacy/example-gui-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/example-gui-1.png
--------------------------------------------------------------------------------
/doc/legacy/example-gui-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/example-gui-2.png
--------------------------------------------------------------------------------
/doc/legacy/example-gui-launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/example-gui-launcher.png
--------------------------------------------------------------------------------
/doc/legacy/example-gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/example-gui.png
--------------------------------------------------------------------------------
/doc/legacy/example-gui.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/example-gui.xcf
--------------------------------------------------------------------------------
/doc/legacy/gui-installer-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/gui-installer-1.png
--------------------------------------------------------------------------------
/doc/legacy/gui-installer-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/gui-installer-2.png
--------------------------------------------------------------------------------
/doc/legacy/gui-installer-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/gui-installer-3.png
--------------------------------------------------------------------------------
/doc/legacy/gui-launcher-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/gui-launcher-1.png
--------------------------------------------------------------------------------
/doc/legacy/gui-launcher-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/gui-launcher-2.png
--------------------------------------------------------------------------------
/doc/legacy/not-aidan-tutorial.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/not-aidan-tutorial.mp4
--------------------------------------------------------------------------------
/doc/legacy/wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/wall.png
--------------------------------------------------------------------------------
/doc/legacy/wine.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/legacy/wine.mp4
--------------------------------------------------------------------------------
/doc/readme/showcase-00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/readme/showcase-00.png
--------------------------------------------------------------------------------
/doc/readme/showcase-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/readme/showcase-01.png
--------------------------------------------------------------------------------
/doc/readme/showcase-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/readme/showcase-02.png
--------------------------------------------------------------------------------
/doc/readme/showcase-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/readme/showcase-03.png
--------------------------------------------------------------------------------
/doc/toc.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ######################################################################
4 | # @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
5 | # @file : toc
6 | ######################################################################
7 |
8 | set -e
9 |
10 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
11 |
12 | cd "${SCRIPT_DIR}"
13 |
14 | readarray -t arr <<<"$(grep -Ei "^#+" ../README.md)"
15 |
16 | for i in "${arr[@]}"; do
17 | lead="${i//${i//\#/}/}"
18 | lead="${lead//#/ }"
19 | text="${i#\#* }"
20 | link="${text// /-}"
21 | link="${link//\(/}"
22 | link="${link//\)/}"
23 | link="$(echo "$link" | tr "[:upper:]" "[:lower:]")"
24 | echo "${lead}- [$text](#${link})"
25 | done >> ../README.md
26 |
27 | # // cmd: !./%
28 |
--------------------------------------------------------------------------------
/doc/video/tutorial-demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-demo.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-download.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-download.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-linux.low.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-linux.low.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-linux.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-linux.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-wine-multi.low.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-wine-multi.low.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-wine-multi.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-wine-multi.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-wine.low.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-wine.low.mp4
--------------------------------------------------------------------------------
/doc/video/tutorial-wine.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/gameimage/92c19fdf0bf13b4936c37a2c97097c7c2011b219/doc/video/tutorial-wine.mp4
--------------------------------------------------------------------------------
/gui/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [ "shared", "wizard", "launcher", ]
3 | resolver = "2"
4 |
5 |
--------------------------------------------------------------------------------
/gui/launcher/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "launcher"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | shared = { path = "../shared" }
10 | fltk = { version = "1.4", features = ["use-wayland"] }
11 | fltk-theme = "0.6.0"
12 | fltk-evented = "0.5"
13 | display-info = "0.4.4"
14 | anyhow = "1.0"
15 | clown = "1.1.0"
16 | serde = { version = "1.0", features = ["derive"] }
17 | serde_yaml = "0.9"
18 | serde_json = "1.0"
19 | regex = "1.10.3"
20 | sysinfo = "0.30.7"
21 | walkdir = "2.5.0"
22 | once_cell = "1.19.0"
23 | clap = { version = "4.5.20", features = ["derive"] }
24 |
--------------------------------------------------------------------------------
/gui/launcher/src/common.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | #[derive(Debug, Clone, Copy)]
4 | #[allow(dead_code)]
5 | pub enum Msg
6 | {
7 | DrawCover,
8 | DrawSelectorGame,
9 | DrawSelectorExecutable,
10 | DrawEnv,
11 | DrawEnablerExecutable,
12 | DrawMenu,
13 | WindActivate,
14 | WindDeactivate,
15 | Quit,
16 | } // enum
17 |
18 | #[allow(dead_code)]
19 | #[derive(PartialEq)]
20 | pub enum Platform
21 | {
22 | LINUX,
23 | WINE,
24 | RETROARCH,
25 | PCSX2,
26 | RPCS3,
27 | } // Platform
28 |
29 | // impl Platform {{{
30 | impl Platform
31 | {
32 | pub fn as_str(&self) -> &'static str
33 | {
34 | match self
35 | {
36 | Platform::LINUX => "linux",
37 | Platform::WINE => "wine",
38 | Platform::RETROARCH => "retroarch",
39 | Platform::PCSX2 => "pcsx2",
40 | Platform::RPCS3 => "rpcs3",
41 | } // match
42 | } // as_str
43 |
44 | pub fn from_str(src : &str) -> anyhow::Result
45 | {
46 | match src.to_lowercase().as_str()
47 | {
48 | "linux" => Ok(Platform::LINUX),
49 | "wine" => Ok(Platform::WINE),
50 | "retroarch" => Ok(Platform::RETROARCH),
51 | "pcsx2" => Ok(Platform::PCSX2),
52 | "rpcs3" => Ok(Platform::RPCS3),
53 | &_ => Err(ah!("Could not determine platform with string")),
54 | } // match
55 | } // as_str
56 | } // impl Platform }}}
57 |
58 | #[macro_export]
59 | macro_rules! assign_to_arc_mutex
60 | {
61 | ($arc_mutex:expr, $value:expr) =>
62 | {
63 | {
64 | let mut data = $arc_mutex.lock().unwrap();
65 | *data = $value;
66 | }
67 | };
68 | }
69 |
70 | #[macro_export]
71 | macro_rules! call_with_args
72 | {
73 | ($func:ident, $( $obj:expr ),* ) =>
74 | {
75 | $(
76 | $obj.$func();
77 | )*
78 | };
79 | }
80 |
81 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
82 |
--------------------------------------------------------------------------------
/gui/launcher/src/db/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod project;
2 |
--------------------------------------------------------------------------------
/gui/launcher/src/db/project.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use std::io::prelude::*;
3 | use serde::{Deserialize, Serialize};
4 |
5 | // struct Project {{{
6 | #[derive(Clone, Serialize, Deserialize)]
7 | pub struct Project
8 | {
9 | pub project : String,
10 | pub platform : String,
11 | pub path_file_icon : Option,
12 | pub path_file_rom : Option,
13 | pub path_file_core : Option,
14 | pub path_file_bios : Option,
15 | #[serde(skip)]
16 | path_file_db : PathBuf,
17 | } // struct Project }}}
18 |
19 | // pub fn read() {{{
20 | pub fn read(path_file_db : &PathBuf) -> anyhow::Result
21 | {
22 | let mut project : Project = serde_json::from_reader(std::fs::File::open(path_file_db)?)?;
23 | project.path_file_db = path_file_db.into();
24 | Ok(project)
25 | } // fn: read }}}
26 |
27 | // pub fn write() {{{
28 | #[allow(dead_code)]
29 | pub fn write(project : Project) -> anyhow::Result<()>
30 | {
31 | write!(std::fs::File::create(&project.path_file_db)?, "{}", serde_json::to_string(&project)?)?;
32 | Ok(())
33 | } // fn: write }}}
34 |
35 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
36 |
--------------------------------------------------------------------------------
/gui/launcher/src/frame/cover.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use fltk::prelude::*;
4 | use fltk::{
5 | app::Sender,
6 | enums,
7 | frame::Frame,
8 | image::SharedImage,
9 | };
10 |
11 | use shared::dimm;
12 | use shared::fltk::WidgetExtExtra;
13 | use shared::fltk::SenderExt;
14 | use shared::{hover_blink,column,row,fixed};
15 |
16 | use crate::common;
17 | use crate::games;
18 | use common::Msg;
19 |
20 | // fn: new {{{
21 | pub fn new(tx : Sender)
22 | {
23 | let show_btn_game: bool = games::games().map(|e| e.len() > 1).unwrap_or(false);
24 | let show_btn_executable: bool = crate::frame::selector_executable::get_menu_entries().map(|e| e.0.len() > 1).unwrap_or(false);
25 | // Layout
26 | let mut frame_background = Frame::default_fill();
27 | if let Ok(env_image_launcher) = env::var("GIMG_LAUNCHER_IMG")
28 | && let Ok(shared_image) = SharedImage::load(env_image_launcher)
29 | {
30 | frame_background.set_image_scaled(Some(shared_image.clone()));
31 | frame_background.resize_callback(move |s,_,_,_,_| { s.set_image_scaled(Some(shared_image.clone())); });
32 | } // if
33 | else
34 | {
35 | println!("Failed to set launcher image");
36 | } // else
37 | // Buttons
38 | column!(col,
39 | col.add(&Frame::default());
40 | row!(row,
41 | row.set_margin(dimm::border_half());
42 | fixed!(row, btn_menu, shared::fltk::button::rect::list(), dimm::width_button_rec());
43 | if show_btn_game
44 | {
45 | row.add(&Frame::default());
46 | fixed!(row, btn_game, shared::fltk::button::rect::joystick(), dimm::width_button_rec());
47 | btn_game.clone().emit(tx, Msg::DrawSelectorGame);
48 | hover_blink!(btn_game);
49 | } // if
50 | if show_btn_executable
51 | {
52 | row.add(&Frame::default());
53 | fixed!(row, btn_executable, shared::fltk::button::rect::switch(), dimm::width_button_rec());
54 | btn_executable.clone().emit(tx, Msg::DrawSelectorExecutable);
55 | hover_blink!(btn_executable);
56 | } // if
57 | row.add(&Frame::default());
58 | fixed!(row, btn_play, shared::fltk::button::rect::play().with_color(enums::Color::Blue), dimm::width_button_rec());
59 | );
60 | col.fixed(&row, dimm::height_button_rec() + dimm::border());
61 | );
62 |
63 | let mut fb: Vec = vec![0u8; (dimm::width_launcher() * dimm::height_launcher() * 4) as usize];
64 | // Fill with required color
65 | for (_, pixel) in fb.chunks_exact_mut(4).enumerate() {
66 | pixel.copy_from_slice(&[0, 0, 0, 96]);
67 | }
68 | let image = fltk::image::RgbImage::new(&fb, dimm::width_launcher(), dimm::height_button_rec() + dimm::border(), enums::ColorDepth::Rgba8).unwrap();
69 | // Bottom background
70 | let mut row = row.clone();
71 | row.set_align(enums::Align::Inside | enums::Align::Center);
72 | row.set_frame(enums::FrameType::NoBox);
73 | row.set_image(Some(image));
74 | row.resize_callback(move |s,_,_,w,_|
75 | {
76 | let image = fltk::image::RgbImage::new(&fb, w, dimm::height_button_rec() + dimm::border(), enums::ColorDepth::Rgba8).unwrap();
77 | s.set_image(Some(image));
78 | });
79 |
80 | // Button left aligned
81 | btn_menu.clone().emit(tx, Msg::DrawMenu);
82 | hover_blink!(btn_menu);
83 | hover_blink!(btn_play);
84 |
85 | // Button right aligned
86 | let clone_tx = tx.clone();
87 | let mut clone_frame_background = frame_background.clone();
88 | btn_play.clone().set_callback(move |_|
89 | {
90 | // Cover image black and white
91 | if let Ok(env_image_launcher) = env::var("GIMG_LAUNCHER_IMG_GRAYSCALE")
92 | && let Ok(shared_image) = SharedImage::load(env_image_launcher)
93 | {
94 | clone_frame_background.set_image_scaled(Some(shared_image));
95 | } // if
96 | else
97 | {
98 | println!("Failed to set launcher image");
99 | } // else
100 | fltk::app::redraw();
101 | // Deactivate window
102 | clone_tx.send_awake(common::Msg::WindDeactivate);
103 | // Reference to spawned process
104 | std::thread::spawn(move ||
105 | {
106 | // Launch game
107 | games::launch();
108 | // Redraw
109 | clone_tx.send_activate(Msg::DrawCover);
110 | });
111 | });
112 | } // fn: new }}}
113 |
114 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
115 |
--------------------------------------------------------------------------------
/gui/launcher/src/frame/fail.rs:
--------------------------------------------------------------------------------
1 | use fltk::{
2 | prelude::*,
3 | frame::Frame,
4 | };
5 |
6 | use shared::column;
7 |
8 | // fn: new {{{
9 | pub fn new()
10 | {
11 | column!(col, Frame::default().with_label("No game found inside this image"););
12 | } // fn: new }}}
13 |
14 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
15 |
--------------------------------------------------------------------------------
/gui/launcher/src/frame/menu.rs:
--------------------------------------------------------------------------------
1 | use fltk::prelude::*;
2 | use fltk::{
3 | app::Sender,
4 | enums::{Align,FrameType,Color},
5 | frame::Frame,
6 | };
7 |
8 | use clown::clown;
9 | use common::Msg;
10 | use shared::dimm;
11 | use shared::fltk::WidgetExtExtra;
12 | use shared::{fixed,hover_blink,hseparator_fixed,column,row,rescope};
13 | use fltk::prelude::ButtonExt;
14 |
15 | use crate::common;
16 |
17 | pub mod enabler_executable;
18 | pub mod environment;
19 |
20 | // fn: new {{{
21 | pub fn new(tx : Sender)
22 | {
23 | // Layout
24 | column!(col,
25 | col.set_margin(dimm::border_half());
26 | fixed!(col, frame_title, Frame::default(), dimm::height_text());
27 | hseparator_fixed!(col, col.w() - dimm::border()*2, dimm::border_half());
28 | column!(col_content, );
29 | col_content.set_spacing(0);
30 | hseparator_fixed!(col, col.w() - dimm::border()*2, dimm::border_half());
31 | column!(col_bottom,
32 | row!(row_bottom,
33 | fixed!(row_bottom, btn_back, shared::fltk::button::rect::back(), dimm::width_button_rec());
34 | );
35 | col_bottom.fixed(&row_bottom, dimm::height_button_rec());
36 | );
37 | col.fixed(&col_bottom, dimm::height_button_rec());
38 | );
39 | // Title
40 | let mut frame_title = frame_title.clone();
41 | frame_title.set_label("Menu");
42 | // Footer button
43 | let mut btn_back = btn_back.clone();
44 | btn_back.emit(tx, Msg::DrawCover);
45 | hover_blink!(btn_back);
46 | // Entries
47 | rescope!(col_content,
48 | let f_make_entry = #[clown] move |label : &str|
49 | {
50 | let entry = fltk::button::ToggleButton::default()
51 | .with_size(0, dimm::height_button_wide() + dimm::border_half())
52 | .with_frame(FrameType::FlatBox)
53 | .with_color(Color::BackGround)
54 | .with_color_selected(Color::BackGround.lighter())
55 | .with_align(Align::Left | Align::Inside)
56 | .with_label(&format!(" {}", label));
57 | hover_blink!(entry);
58 | honk!(col_content).clone().fixed(&mut entry.as_base_widget(), entry.h());
59 | entry
60 | };
61 | // Environment
62 | f_make_entry("Environment").emit(tx, Msg::DrawEnv);
63 | // Executables
64 | if let Ok(str_platform) = std::env::var("GIMG_PLATFORM")
65 | && let Ok(platform) = common::Platform::from_str(&str_platform)
66 | && platform == common::Platform::WINE
67 | {
68 | f_make_entry("Executable Configuration").emit(tx, Msg::DrawEnablerExecutable);
69 | }
70 | );
71 | } // fn: new }}}
72 |
73 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
74 |
--------------------------------------------------------------------------------
/gui/launcher/src/frame/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cover;
2 | pub mod selector_game;
3 | pub mod fail;
4 | pub mod menu;
5 | pub mod selector_executable;
6 |
--------------------------------------------------------------------------------
/gui/launcher/src/frame/selector_game.rs:
--------------------------------------------------------------------------------
1 | use fltk::prelude::*;
2 | use fltk::{
3 | group,
4 | enums,
5 | app::Sender,
6 | button::Button,
7 | frame::Frame,
8 | };
9 |
10 | use shared::dimm;
11 | use shared::fltk::WidgetExtExtra;
12 | use shared::fltk::SenderExt;
13 |
14 | use crate::games;
15 | use crate::common;
16 | use common::Msg;
17 | use shared::{fixed,row,column,hpack,scroll,hover_blink,hseparator_fixed,rescope};
18 |
19 | // fn: new {{{
20 | pub fn new(tx : Sender)
21 | {
22 | // Layout
23 | column!(col,
24 | col.set_margin(dimm::border_half());
25 | fixed!(col, frame_title, Frame::default(), dimm::height_text());
26 | hseparator_fixed!(col, col.w() - dimm::border()*2, dimm::border_half());
27 | // Content
28 | scroll!(scroll,
29 | hpack!(col_scroll,);
30 | col_scroll.set_spacing(0);
31 | col_scroll.set_size(0,0);
32 | );
33 | hseparator_fixed!(col, col.w() - dimm::border()*2, dimm::border_half());
34 | column!(col_bottom,
35 | row!(row_bottom,
36 | row_bottom.add(&Frame::default());
37 | fixed!(row_bottom, btn_home, &shared::fltk::button::rect::home(), dimm::width_button_rec());
38 | row_bottom.add(&Frame::default());
39 | );
40 | col_bottom.fixed(&row_bottom, dimm::height_button_rec());
41 | );
42 | col.fixed(&col_bottom, dimm::height_button_rec());
43 | );
44 |
45 | // Title
46 | let mut frame_title = frame_title.clone();
47 | frame_title.set_label("Switch Game");
48 | // Scroll resize callback
49 | scroll.resize_callback({let mut c = col_scroll.clone(); move |_,_,_,w,_|
50 | {
51 | c.resize(c.x(),c.y(),w-dimm::border_half()*3,c.h());
52 | }});
53 | scroll.set_type(group::ScrollType::VerticalAlways);
54 | // Configure buttons
55 | let mut btn_home = btn_home.clone();
56 | btn_home.set_color(enums::Color::Blue);
57 | btn_home.emit(tx, Msg::DrawCover);
58 | hover_blink!(btn_home);
59 |
60 | // Create entries
61 | rescope!(col_scroll,
62 | for game in games::games().unwrap_or_default()
63 | {
64 | // Set entry name
65 | let osstr_name_file = game.path_root.file_name().unwrap_or_default();
66 | let str_name_file = osstr_name_file.to_str().unwrap_or_default();
67 | let entry = Button::default()
68 | .with_size(0, dimm::height_button_wide() + dimm::border_half())
69 | .with_label(&str_name_file)
70 | .with_frame(enums::FrameType::FlatBox)
71 | .with_color(enums::Color::BackGround)
72 | .with_color_selected(enums::Color::BackGround.lighter())
73 | .with_align(enums::Align::Left | enums::Align::Inside)
74 | .with_callback(move |_| { games::select(&game); tx.send_awake(Msg::DrawCover); });
75 | hover_blink!(entry);
76 | col_scroll.add(&entry);
77 | } // for
78 | );
79 |
80 | } // fn: new }}}
81 |
82 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
83 |
--------------------------------------------------------------------------------
/gui/launcher/src/games.rs:
--------------------------------------------------------------------------------
1 | use std::fs;
2 | use std::fs::DirEntry;
3 | use std::path::PathBuf;
4 |
5 | use shared::std::PathBufExt;
6 |
7 | use anyhow::anyhow as ah;
8 |
9 | use crate::common;
10 | use crate::db;
11 |
12 | pub struct Game
13 | {
14 | pub platform : common::Platform,
15 | pub path_root : PathBuf,
16 | pub path_icon : PathBuf,
17 | pub path_icon_grayscale : PathBuf,
18 | pub path_boot : PathBuf,
19 | } // Game
20 |
21 | // pub fn launch() {{{
22 | pub fn launch()
23 | {
24 | let _ = std::process::Command::new("sh")
25 | .args(["-c", "$GIMG_LAUNCHER_BOOT"])
26 | .stdout(std::process::Stdio::inherit())
27 | .stderr(std::process::Stdio::inherit())
28 | .output();
29 | } // fn: launch }}}
30 |
31 | // pub fn select() {{{
32 | pub fn select(game: &Game)
33 | {
34 | std::env::set_var("GIMG_PLATFORM", game.platform.as_str());
35 | std::env::set_var("GIMG_LAUNCHER_BOOT", game.path_boot.to_str().unwrap_or(""));
36 | std::env::set_var("GIMG_LAUNCHER_ROOT", game.path_root.to_str().unwrap_or(""));
37 | std::env::set_var("GIMG_LAUNCHER_IMG", game.path_icon.to_str().unwrap_or(""));
38 | std::env::set_var("GIMG_LAUNCHER_IMG_GRAYSCALE", game.path_icon_grayscale.to_str().unwrap_or(""));
39 | } // fn: select }}}
40 |
41 | // pub fn select_by_index() {{{
42 | pub fn select_by_index(index: usize) -> anyhow::Result<()>
43 | {
44 | select(games()?.get(index).ok_or(ah!("Index out of bounds"))?);
45 | Ok(())
46 | } // fn: select_by_index }}}
47 |
48 | // fn game() {{{
49 | fn game(path_root : PathBuf) -> anyhow::Result
50 | {
51 | let db_project = db::project::read(&path_root.join("gameimage.json"))?;
52 | let path_icon = path_root.join("icon/icon.png");
53 | let path_icon_grayscale = path_root.join("icon").join("icon.grayscale.png");
54 | let path_boot = path_root.join("boot");
55 | let platform = common::Platform::from_str(&db_project.platform)?;
56 | if path_icon.exists() && path_boot.exists()
57 | {
58 | return Ok(Game{ platform, path_boot, path_root, path_icon, path_icon_grayscale })
59 | } // if
60 | Err(ah!("Could not include project from '{}'", path_root.string()))
61 | } // fn game() }}}
62 |
63 | // pub fn games() {{{
64 | pub fn games() -> anyhow::Result>
65 | {
66 | let vec_entries : Vec = fs::read_dir("/opt/gameimage-games")?
67 | .filter_map(|e| { e.ok() })
68 | .filter(|e|{ e.path().is_dir() })
69 | .collect();
70 |
71 | let mut vec_data : Vec = vec![];
72 |
73 | for entry in vec_entries
74 | {
75 | match game(entry.path())
76 | {
77 | Ok(game) => vec_data.push(game),
78 | Err(e) => eprintln!("{}", e),
79 | }
80 | } // for
81 |
82 | // Sort
83 | vec_data.sort_by(|a, b| return a.path_root.string().partial_cmp(&b.path_root.string()).unwrap());
84 |
85 | Ok(vec_data)
86 | } // games() }}}
87 |
88 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
89 |
--------------------------------------------------------------------------------
/gui/launcher/src/main.rs:
--------------------------------------------------------------------------------
1 | #![feature(let_chains, proc_macro_hygiene, stmt_expr_attributes)]
2 |
3 | // Gui
4 | use fltk::{
5 | app,
6 | prelude::*,
7 | window::Window,
8 | enums::FrameType,
9 | };
10 |
11 | use shared::svg;
12 |
13 | mod games;
14 | mod frame;
15 | mod common;
16 | mod db;
17 |
18 | use common::Msg;
19 |
20 | use shared::dimm;
21 | use shared::std::PathBufExt;
22 | use clap::Parser;
23 |
24 | // struct: Gui {{{
25 | #[derive(Debug)]
26 | struct Gui
27 | {
28 | app: fltk::app::App,
29 | wind: Window,
30 | rx : fltk::app::Receiver,
31 | tx : fltk::app::Sender,
32 | } // struct: Gui }}}
33 |
34 | // impl: Gui {{{
35 | impl Gui
36 | {
37 |
38 | // fn: new {{{
39 | pub fn new() -> Self
40 | {
41 | let app = app::App::default().with_scheme(app::Scheme::Gtk);
42 | app::set_frame_type(FrameType::BorderBox);
43 | app::set_font_size(dimm::height_text());
44 | let mut wind = Window::default()
45 | .with_label("GameImage")
46 | .with_size(dimm::width_launcher(), dimm::height_launcher())
47 | .center_screen();
48 | wind.make_resizable(false);
49 |
50 | shared::fltk::theme();
51 |
52 | // Window icon
53 | if let Some(image) = fltk::image::SvgImage::from_data(svg::ICON_GAMEIMAGE).ok()
54 | {
55 | wind.set_icon(Some(image));
56 | } // if
57 | else
58 | {
59 | println!("Failed to load icon image");
60 | } // else
61 |
62 | let (tx, rx) = fltk::app::channel();
63 |
64 | Gui
65 | {
66 | app,
67 | wind,
68 | rx,
69 | tx,
70 | }
71 | } // fn: new }}}
72 |
73 | // fn redraw() {{{
74 | fn redraw(&mut self, msg: Msg)
75 | {
76 | self.wind.clear();
77 | self.wind.begin();
78 |
79 | match Some(msg)
80 | {
81 | Some(Msg::DrawCover) => frame::cover::new(self.tx),
82 | Some(Msg::DrawSelectorGame) => frame::selector_game::new(self.tx),
83 | Some(Msg::DrawSelectorExecutable) => frame::selector_executable::new(self.tx),
84 | Some(Msg::DrawEnablerExecutable) => frame::menu::enabler_executable::new(self.tx),
85 | Some(Msg::DrawEnv) => frame::menu::environment::new(self.tx),
86 | Some(Msg::DrawMenu) => frame::menu::new(self.tx),
87 | _ => (),
88 | }
89 | self.wind.end();
90 | } // fn: redraw }}}
91 |
92 | // init() {{{
93 | fn init(&mut self)
94 | {
95 | let vec_games = match games::games()
96 | {
97 | Ok(vec_games) => vec_games,
98 | Err(_) => { frame::fail::new(); vec![] }
99 | }; // match
100 |
101 | // Fetch game entries
102 | if vec_games.is_empty()
103 | {
104 | frame::fail::new();
105 | } // if
106 | else
107 | {
108 | // Create initial cover frame
109 | self.tx.send(common::Msg::DrawCover);
110 | // Select the first game as the current
111 | games::select(vec_games.first().unwrap());
112 | } // else
113 |
114 | // Show window
115 | self.wind.end();
116 | self.wind.show();
117 |
118 | while self.app.wait()
119 | {
120 | match self.rx.recv()
121 | {
122 | Some(common::Msg::WindActivate) =>
123 | {
124 | shared::fltk::set_active(self.wind.clone(), true);
125 | app::awake();
126 | }
127 | Some(common::Msg::WindDeactivate) =>
128 | {
129 | shared::fltk::set_active(self.wind.clone(), false);
130 | app::awake();
131 | }
132 | Some(Msg::Quit) =>
133 | {
134 | app::quit();
135 | app::flush();
136 | }
137 | Some(value) => self.redraw(value),
138 | None => (),
139 | } // match
140 | } // while
141 | } // init() }}}
142 |
143 | } // impl: Gui }}}
144 |
145 | // struct Cli {{{
146 | #[derive(Parser)]
147 | #[command(version, about, long_about = None)]
148 | struct Cli
149 | {
150 | #[arg(long, value_name = "INDEX")]
151 | select_index: Option,
152 | #[arg(long)]
153 | select_list: bool,
154 | } // struct Cli }}}
155 |
156 | // fn: main {{{
157 | fn main() -> anyhow::Result<()>
158 | {
159 | let args = Cli::parse();
160 |
161 | // Launch game directly
162 | if let Some(index) = args.select_index
163 | {
164 | match games::select_by_index(index as usize)
165 | {
166 | Ok(()) => games::launch(),
167 | Err(e) => { eprintln!("Could not select index '{}': '{}'", index, e); }
168 | } // match
169 | return Ok(());
170 | } // if
171 | else if args.select_list
172 | {
173 | for (index, game) in games::games()?.into_iter().enumerate()
174 | {
175 | println!("{}: {}", index, game.path_root.file_name_string());
176 | } // for
177 | return Ok(());
178 | } // else if
179 |
180 | // Start GUI
181 | Gui::new().init();
182 |
183 | Ok(())
184 | } // }}}
185 |
186 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
187 |
--------------------------------------------------------------------------------
/gui/shared/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "shared"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | anyhow = "1.0"
8 | fltk = "^1.4"
9 | fltk-theme = "0.6.0"
10 | fltk-evented = "0.5"
11 | display-info = "0.4.4"
12 | image = "0.24.8"
13 | serde = { version = "1.0", features = ["derive"] }
14 | serde_json = "1.0"
15 |
--------------------------------------------------------------------------------
/gui/shared/src/db/kv.rs:
--------------------------------------------------------------------------------
1 | use std::io::prelude::*;
2 | use std::fs::File;
3 | use std::path::PathBuf;
4 | use std::collections::HashMap;
5 |
6 | pub type Kv = HashMap;
7 |
8 | // fn open() {{{
9 | fn open(db: &PathBuf) -> anyhow::Result
10 | {
11 | Ok(serde_json::from_reader(File::open(db)?)?)
12 | } // fn open() }}}
13 |
14 | // pub fn read() {{{
15 | pub fn read(db : &PathBuf) -> anyhow::Result
16 | {
17 | open(db)
18 | } // fn: read }}}
19 |
20 | // pub fn write() {{{
21 | pub fn write(db : &PathBuf, key: &String, val: &String) -> anyhow::Result<()>
22 | {
23 | // Open existing or create an empty database
24 | let mut kv : Kv = open(db).unwrap_or(Kv::default());
25 |
26 | // Append
27 | kv.insert(key.clone(), val.clone());
28 |
29 | // Write to file
30 | write!(File::create(&db)?, "{}", serde_json::to_string(&kv)?)?;
31 |
32 | Ok(())
33 | } // fn: write }}}
34 |
35 | // pub fn erase() {{{
36 | pub fn erase(db : &PathBuf, key: String) -> anyhow::Result<()>
37 | {
38 | // Read current
39 | let mut kv = read(db)?;
40 |
41 | // Erase key
42 | kv.remove(&key);
43 |
44 | // Write to file
45 | write!(File::create(db)?, "{}", serde_json::to_string(&kv)?)?;
46 |
47 | Ok(())
48 | } // erase() }}}
49 |
50 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
51 |
--------------------------------------------------------------------------------
/gui/shared/src/db/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod kv;
2 |
--------------------------------------------------------------------------------
/gui/shared/src/dimm.rs:
--------------------------------------------------------------------------------
1 | const HEIGHT_WIZARD : i32 = 500;
2 | const WIDTH_WIZARD : i32 = 500;
3 | const HEIGHT_LAUNCHER : i32 = 450;
4 | const WIDTH_LAUNCHER : i32 = 300;
5 | const BORDER : i32 = 10;
6 | const BORDER_HALF : i32 = BORDER / 2;
7 |
8 | const HEIGHT_BUTTON_WIDE : i32 = 30;
9 | const WIDTH_BUTTON_WIDE : i32 = HEIGHT_BUTTON_WIDE*2;
10 |
11 | const HEIGHT_BUTTON_REC : i32 = HEIGHT_BUTTON_WIDE;
12 | const WIDTH_BUTTON_REC : i32 = WIDTH_BUTTON_WIDE/2;
13 |
14 | const HEIGHT_TEXT : i32 = 14;
15 |
16 | const HEIGHT_STATUS : i32 = (HEIGHT_WIZARD as f64 * 0.05) as i32;
17 |
18 | const HEIGHT_SEP : i32 = 1;
19 |
20 | const HEIGHT_HEADER : i32 = HEIGHT_BUTTON_WIDE + BORDER*2 + HEIGHT_SEP;
21 | const HEIGHT_FOOTER : i32 = HEIGHT_BUTTON_WIDE + BORDER*2 + HEIGHT_STATUS;
22 |
23 | const HEIGHT_BAR : i32 = HEIGHT_BUTTON_WIDE + BORDER;
24 |
25 | const POSY_FOOTER : i32 = HEIGHT_WIZARD - HEIGHT_FOOTER;
26 |
27 | macro_rules! function_scale
28 | {
29 | ($func_name:ident, $baseline:expr) =>
30 | {
31 | pub fn $func_name() -> i32
32 | {
33 | return $baseline;
34 | }
35 | }
36 | }
37 |
38 | function_scale!(height_wizard, HEIGHT_WIZARD);
39 | function_scale!(width_wizard, WIDTH_WIZARD);
40 | function_scale!(height_launcher, HEIGHT_LAUNCHER);
41 | function_scale!(width_launcher, WIDTH_LAUNCHER);
42 | function_scale!(border, BORDER);
43 | function_scale!(border_half, BORDER_HALF);
44 |
45 | function_scale!(height_button_wide, HEIGHT_BUTTON_WIDE);
46 | function_scale!(width_button_wide, WIDTH_BUTTON_WIDE);
47 |
48 | function_scale!(height_button_rec, HEIGHT_BUTTON_REC);
49 | function_scale!(width_button_rec, WIDTH_BUTTON_REC);
50 |
51 | function_scale!(height_text_header, HEIGHT_TEXT*2);
52 | function_scale!(height_text, HEIGHT_TEXT);
53 |
54 | function_scale!(height_status, HEIGHT_STATUS);
55 |
56 | function_scale!(height_sep, HEIGHT_SEP);
57 |
58 | function_scale!(height_header, HEIGHT_HEADER);
59 | function_scale!(height_footer, HEIGHT_FOOTER);
60 |
61 | function_scale!(posy_footer, POSY_FOOTER);
62 |
63 | function_scale!(bar, HEIGHT_BAR);
64 |
65 | pub fn width_checkbutton() -> i32 { 18 }
66 |
67 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
68 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/dialog.rs:
--------------------------------------------------------------------------------
1 | use fltk::
2 | {
3 | frame::Frame,
4 | window::Window,
5 | prelude::*,
6 | enums::{Align,Color},
7 | };
8 |
9 | use crate::dimm;
10 | use crate::svg;
11 | use crate::hover_blink;
12 |
13 | #[derive(Clone)]
14 | pub struct KeyValue
15 | {
16 | pub wind : Window,
17 | pub input_key : fltk::input::Input,
18 | pub input_value : fltk::input::Input,
19 | pub btn_ok : fltk::button::Button,
20 | }
21 |
22 | pub fn key_value() -> KeyValue
23 | {
24 | let mut wind = Window::default().with_size(
25 | dimm::width_button_wide() * 4 + dimm::border() * 3
26 | , dimm::height_button_wide() * 3 + dimm::border() * 4
27 | );
28 | // Window should be de-attached from other windows
29 | if let Some(mut parent) = wind.parent()
30 | {
31 | parent.remove(&wind);
32 | } // if
33 | // Window icon
34 | if let Some(image) = fltk::image::SvgImage::from_data(svg::ICON_GAMEIMAGE).ok()
35 | {
36 | wind.set_icon(Some(image.clone()));
37 | } // if
38 | wind.begin();
39 | let input_key = fltk::input::Input::default()
40 | .with_pos(wind.w() - dimm::width_button_wide()*3 - dimm::border(), dimm::border())
41 | .with_size(dimm::width_button_wide()*3, dimm::height_button_wide())
42 | .with_align(Align::Left);
43 | let _label_key = Frame::default()
44 | .with_size(dimm::width_button_wide(), dimm::height_button_wide())
45 | .left_of(&input_key, dimm::border())
46 | .with_align(Align::Inside | Align::Left)
47 | .with_label("Key");
48 | let input_value = fltk::input::Input::default()
49 | .below_of(&input_key, dimm::border())
50 | .with_size(input_key.w(), input_key.h())
51 | .with_align(input_key.align());
52 | let label_value = Frame::default()
53 | .with_size(dimm::width_button_wide(), dimm::height_button_wide())
54 | .left_of(&input_value, dimm::border())
55 | .with_align(Align::Inside | Align::Left)
56 | .with_label("Value");
57 | let mut btn_ok = crate::fltk::button::wide::default()
58 | .with_size(dimm::width_button_wide(), dimm::height_button_wide())
59 | .below_of(&label_value, dimm::border())
60 | .with_label("OK");
61 | hover_blink!(btn_ok);
62 | btn_ok.set_pos(wind.w() / 2 - btn_ok.w() / 2, btn_ok.y());
63 | btn_ok.set_color(Color::Green);
64 | wind.end();
65 |
66 | KeyValue{ wind, input_key, input_value, btn_ok }
67 | }
68 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
69 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/frame.rs:
--------------------------------------------------------------------------------
1 | use fltk::
2 | {
3 | draw,
4 | enums::Color,
5 | prelude::*,
6 | frame::Frame,
7 | enums::FrameType
8 | };
9 |
10 | use crate::dimm;
11 | use crate::fltk::WidgetExtExtra;
12 |
13 | pub fn bordered() -> Frame
14 | {
15 | let mut frame = Frame::default()
16 | .with_frame(FrameType::FlatBox)
17 | .with_color(Color::White);
18 |
19 | frame.draw(move |f|
20 | {
21 | let (x,y,w,h) = (f.x(),f.y(),f.w(),f.h());
22 | draw::draw_rect_fill(x, y, w, h, f.color());
23 | draw::draw_rect_fill(x + dimm::height_sep()
24 | , y + dimm::height_sep()
25 | , w - dimm::height_sep()*2
26 | , h - dimm::height_sep()*2
27 | , Color::BackGround
28 | );
29 | // Draw Image
30 | if let Some(mut image) = f.image()
31 | {
32 | let img_w = image.width();
33 | let img_h = image.height();
34 | let img_x = x + (w - img_w) / 2; // Center horizontally
35 | let img_y = y + (h - img_h) / 2; // Center vertically
36 | image.draw(img_x, img_y, img_w, img_h);
37 | }
38 | // Draw label
39 | draw::set_draw_color(Color::Foreground);
40 | draw::draw_text2(&f.label(), f.x(), f.y(), f.w(), f.h(), f.align());
41 | });
42 |
43 | frame
44 | }
45 |
46 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
47 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/macros.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! fit_to_children_height
3 | {
4 | ($widget:ident) =>
5 | {
6 | {
7 | let parent = $widget.parent().unwrap();
8 | let spacing = $widget.spacing();
9 | let height = $widget.bounds()
10 | .iter()
11 | .map(|e| e.3).sum::() + (($widget.children()-1) * spacing);
12 | $widget.resize($widget.x()
13 | , $widget.y()
14 | , $widget.w()
15 | , height.min(parent.h()).min($widget.height())
16 | );
17 | height
18 | }
19 | };
20 | }
21 |
22 | #[macro_export]
23 | macro_rules! rescope
24 | {
25 | ($row_name:ident, $($body:tt)*) =>
26 | {
27 | $row_name.begin();
28 | $($body)*
29 | $row_name.end();
30 | };
31 | }
32 |
33 | #[macro_export]
34 | macro_rules! group
35 | {
36 | ($col_name:ident, $($body:tt)*) =>
37 | {
38 | let mut $col_name = fltk::group::Group::default_fill();
39 | $($body)*
40 | $col_name.end();
41 | };
42 | }
43 |
44 | #[macro_export]
45 | macro_rules! tabs
46 | {
47 | ($col_name:ident, $($body:tt)*) =>
48 | {
49 | let mut $col_name = fltk::group::Tabs::default_fill();
50 | $($body)*
51 | $col_name.end();
52 | };
53 | }
54 |
55 | #[macro_export]
56 | macro_rules! hpack
57 | {
58 | ($col_name:ident, $($body:tt)*) =>
59 | {
60 | let mut $col_name = fltk::group::Pack::default_fill();
61 | $col_name.set_type(fltk::group::PackType::Vertical);
62 | $($body)*
63 | $col_name.end();
64 | };
65 | }
66 |
67 | #[macro_export]
68 | macro_rules! column
69 | {
70 | ($col_name:ident, $($body:tt)*) =>
71 | {
72 | let mut $col_name = fltk::group::Flex::default_fill().column();
73 | $($body)*
74 | $col_name.end();
75 | };
76 | }
77 |
78 | #[macro_export]
79 | macro_rules! row
80 | {
81 | ($row_name:ident, $($body:tt)*) =>
82 | {
83 | let mut $row_name = fltk::group::Flex::default_fill().row();
84 | $($body)*
85 | $row_name.end();
86 | };
87 | }
88 |
89 | #[macro_export]
90 | macro_rules! scroll
91 | {
92 | ($scroll_name:ident, $($body:tt)*) =>
93 | {
94 | let mut $scroll_name = fltk::group::Scroll::default_fill();
95 | $scroll_name.set_scrollbar_size(dimm::border());
96 | $($body)*
97 | $scroll_name.end();
98 | };
99 | }
100 |
101 | #[macro_export]
102 | macro_rules! hseparator_fixed
103 | {
104 | ($col_name:ident, $width:expr, $height: expr) =>
105 | {{
106 | column!(col_inner,
107 | col_inner.add(&Frame::default());
108 | let sep = shared::fltk::separator::horizontal($width);
109 | col_inner.fixed(&sep, sep.h());
110 | col_inner.add(&Frame::default());
111 | );
112 | $col_name.fixed(&col_inner, $height);
113 | }}
114 | }
115 |
116 | #[macro_export]
117 | macro_rules! hseparator
118 | {
119 | ($col_name:ident, $width:expr) =>
120 | {{
121 | column!(col_inner,
122 | col_inner.add(&Frame::default());
123 | let sep = shared::fltk::separator::horizontal($width);
124 | col_inner.fixed(&sep, sep.h());
125 | col_inner.add(&Frame::default());
126 | );
127 | $col_name.add(&col_inner);
128 | }}
129 | }
130 |
131 | #[macro_export]
132 | macro_rules! fixed
133 | {
134 | ($col_name:ident, $name:ident, $widget:expr, $size:expr) =>
135 | {
136 | let $name = $widget.clone();
137 | $col_name.fixed(&$name, $size);
138 | }
139 | }
140 |
141 | #[macro_export]
142 | macro_rules! add
143 | {
144 | ($col_name:ident, $name:ident, $widget:expr) =>
145 | {
146 | let $name = $widget.clone();
147 | $col_name.add(&$name);
148 | }
149 | }
150 |
151 | #[macro_export]
152 | macro_rules! col_center
153 | {
154 | ($widget:expr) =>
155 | {{
156 | let mut col = fltk::group::Flex::default_fill().column();
157 | col.add(&fltk::frame::Frame::default());
158 | let widget = $widget.clone();
159 | col.fixed(&widget, widget.w());
160 | col.add(&fltk::frame::Frame::default());
161 | col.end();
162 | (col,widget)
163 | }}
164 | }
165 |
166 | #[macro_export]
167 | macro_rules! row_center
168 | {
169 | ($widget:expr) =>
170 | {{
171 | let mut row = fltk::group::Flex::default_fill().row();
172 | row.add(&fltk::frame::Frame::default());
173 | let widget = $widget.clone();
174 | row.fixed(&widget, widget.w());
175 | row.add(&fltk::frame::Frame::default());
176 | row.end();
177 | (row,widget)
178 | }}
179 | }
180 |
181 | #[macro_export]
182 | macro_rules! hover_blink
183 | {
184 | ($button:ident) =>
185 | {
186 | $button.clone().handle(|b,ev| match ev {
187 | fltk::enums::Event::Enter => { b.set_value(true); true},
188 | fltk::enums::Event::Leave => { b.set_value(false); true},
189 | _ => false,
190 | });
191 | };
192 | }
193 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
194 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/paginator.rs:
--------------------------------------------------------------------------------
1 | use fltk::{
2 | prelude::*,
3 | frame,
4 | output,
5 | };
6 |
7 | use crate::{dimm,column,row,hover_blink};
8 |
9 | // pub fn paginator() {{{
10 | pub fn paginator(mut get_page: F, mut set_page: G, mut count_pages: H) -> fltk::group::Flex
11 | where F: Clone + FnMut() -> usize,
12 | G: 'static + Clone + FnMut(usize),
13 | H: 'static + Clone + FnMut() -> usize
14 | {
15 | column!(col,
16 | row!(row,
17 | let mut btn_prev = crate::fltk::button::rect::arrow_backward();
18 | row.fixed(&btn_prev, dimm::width_button_rec());
19 | row.add(&frame::Frame::default());
20 | row!(row_pages,
21 | let mut input_page : fltk_evented::Listener<_> = fltk::input::Input::default().into();
22 | row_pages.fixed(&input_page.as_base_widget(), dimm::width_button_rec());
23 | row_pages.fixed(&frame::Frame::default().with_label("/"), dimm::border());
24 | let mut output_pages = output::Output::default();
25 | row_pages.fixed(&output_pages, dimm::width_button_rec());
26 | let _ = output_pages.insert(&count_pages().to_string());
27 | );
28 | row.fixed(&row_pages, dimm::width_button_rec()*2 + dimm::border());
29 | row.add(&frame::Frame::default());
30 | let mut btn_next = crate::fltk::button::rect::arrow_forward();
31 | row.fixed(&btn_next, dimm::width_button_rec());
32 | );
33 | col.fixed(&row, dimm::height_button_wide());
34 | );
35 | // Adjust size
36 | col.resize(col.x(), col.y(), col.w(), dimm::height_button_wide());
37 | // Check for bounds
38 | if get_page() > count_pages() { set_page(0); } // if
39 | // Configure hover
40 | hover_blink!(btn_prev);
41 | hover_blink!(btn_next);
42 | // Configure callback
43 | input_page.set_value(&get_page().to_string());
44 | input_page.on_keydown({
45 | let mut set_page = set_page.clone();
46 | move |e|
47 | {
48 | // Filter non-digit values
49 | e.set_value(&e.value().chars().filter(|c| "1234567890".contains(*c)).collect::());
50 | // Make sure it does not stay empty
51 | if e.value().is_empty() { e.set_value("0"); } // if
52 | // Remove leading zeroes
53 | while e.value().starts_with("0") && e.value().len() > 1 { e.set_value(&e.value()[1..]); }
54 | // Set page
55 | if fltk::app::event_key() == fltk::enums::Key::Enter
56 | {
57 | set_page(e.value().parse::().unwrap_or_default());
58 | } // if
59 | }
60 | });
61 | btn_prev.set_callback({
62 | let input_page = input_page.clone();
63 | let mut set_page = set_page.clone();
64 | move |_|
65 | {
66 | let value = input_page.value().parse::().unwrap_or_default();
67 | set_page(if value == 0 { 0 } else { value-1 });
68 | }
69 | });
70 | btn_next.set_callback({
71 | let input_page = input_page.clone();
72 | let mut set_page = set_page.clone();
73 | move |_|
74 | {
75 | let value = input_page.value().parse::().unwrap_or_default();
76 | set_page((value + 1).min(count_pages()));
77 | }
78 | });
79 | col
80 | } // pub fn paginator() }}}
81 |
82 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
83 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/progress.rs:
--------------------------------------------------------------------------------
1 | use fltk::
2 | {
3 | draw,
4 | enums::Color,
5 | misc::Progress,
6 | prelude::*,
7 | };
8 |
9 | pub fn progress() -> Progress
10 | {
11 | let mut frame = Progress::default();
12 |
13 | frame.draw(move |f|
14 | {
15 | let (x,y,w,h) = (f.x(),f.y(),f.w(),f.h());
16 | let cs = f.selection_color();
17 | let cf = f.color();
18 | let value = f.value().clamp(f.minimum(), f.maximum()) as f32 / 100.0;
19 | let filled_width = (w as f32 * value).round() as i32;
20 |
21 | draw::draw_rect_fill(x, y, w, h, cf);
22 | draw::draw_rect_fill(x, y, filled_width, h, cs);
23 | draw::draw_rect_with_color(x, y, w, h, Color::BackGround.lighter());
24 | draw::set_draw_color(Color::ForeGround);
25 | draw::draw_text2(&f.label() , x , y , w , h , f.align());
26 | });
27 |
28 | frame
29 | }
30 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
31 |
--------------------------------------------------------------------------------
/gui/shared/src/fltk/separator.rs:
--------------------------------------------------------------------------------
1 | use fltk::
2 | {
3 | draw,
4 | enums::Color,
5 | prelude::*,
6 | frame::Frame,
7 | enums::FrameType
8 | };
9 |
10 | use crate::dimm;
11 | use crate::fltk::WidgetExtExtra;
12 |
13 | pub fn vertical(height: i32) -> Frame
14 | {
15 | let mut frame = Frame::default()
16 | .with_size(dimm::height_sep(), height)
17 | .with_frame(FrameType::FlatBox)
18 | .with_color(Color::White);
19 |
20 | frame.draw(move |f|
21 | {
22 | let (x,y,w,h) = (f.x(),f.y(),f.w(),f.h());
23 | draw::draw_rect_fill(x, y, w, h, f.color());
24 | });
25 |
26 | frame
27 | }
28 |
29 | pub fn horizontal(width: i32) -> Frame
30 | {
31 | let mut frame = Frame::default()
32 | .with_size(width, dimm::height_sep())
33 | .with_frame(FrameType::FlatBox)
34 | .with_color(Color::White);
35 |
36 | frame.draw(move |f|
37 | {
38 | let (x,y,w,h) = (f.x(),f.y(),f.w(),f.h());
39 | draw::draw_rect_fill(x, y, w, h, f.color());
40 | });
41 |
42 | frame
43 | }
44 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
45 |
46 |
--------------------------------------------------------------------------------
/gui/shared/src/image.rs:
--------------------------------------------------------------------------------
1 | // pub fn resize() {{{
2 | pub fn resize(path_out : std::path::PathBuf, path_in : std::path::PathBuf, width : u32, height : u32) -> anyhow::Result<()>
3 | {
4 | let mut img = image::io::Reader::open(path_in)?.decode()?;
5 | img = img.resize(width, height, image::imageops::FilterType::CatmullRom);
6 | Ok(img.save(path_out)?)
7 | } // }}}
8 |
9 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
10 |
--------------------------------------------------------------------------------
/gui/shared/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(let_chains,proc_macro_hygiene, stmt_expr_attributes, once_cell_get_mut, concat_idents)]
2 | pub mod fltk;
3 | pub mod dimm;
4 | pub mod svg;
5 | pub mod std;
6 | pub mod image;
7 | pub mod db;
8 |
9 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
10 |
--------------------------------------------------------------------------------
/gui/shared/src/std.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::{OsStr, OsString};
2 |
3 |
4 | // pub trait OsStrExt {{{
5 | pub trait OsStrExt
6 | {
7 | fn string(&self) -> String;
8 | }
9 |
10 | impl OsStrExt for OsStr
11 | {
12 | fn string(&self) -> String
13 | {
14 | self.to_string_lossy().into_owned()
15 | } // fn: string
16 | }
17 | // }}}
18 |
19 | // pub trait OsStringExt {{{
20 | pub trait OsStringExt
21 | {
22 | fn append(&mut self, val: &str) -> &mut Self;
23 | fn string(&self) -> String;
24 | }
25 |
26 | impl OsStringExt for OsString
27 | {
28 | fn append(&mut self, val: &str) -> &mut Self
29 | {
30 | self.push(val);
31 | self
32 | }
33 | fn string(&self) -> String
34 | {
35 | self.to_string_lossy().into_owned()
36 | } // fn: string
37 | }
38 | // }}}
39 |
40 | // pub trait PathBufExt {{{
41 | #[allow(warnings)]
42 | pub trait PathBufExt
43 | {
44 | fn string(&self) -> String;
45 | fn append_extension(&self, val: &str) -> Self;
46 | fn prepend(&self, upper: &std::path::PathBuf) -> Self;
47 | fn file_name_string(&self) -> String;
48 | }
49 |
50 | impl PathBufExt for std::path::PathBuf
51 | {
52 | fn string(&self) -> String
53 | {
54 | self.clone().to_string_lossy().into_owned()
55 | } // fn: string
56 |
57 | fn append_extension(&self, val: &str) -> Self
58 | {
59 | std::path::PathBuf::from(self.clone().into_os_string().append(val).string())
60 | } // fn: extend_extension
61 |
62 | fn prepend(&self, upper: &std::path::PathBuf) -> Self
63 | {
64 | upper.join(self)
65 | } // fn: prepend
66 |
67 | fn file_name_string(&self) -> String
68 | {
69 | self.file_name().unwrap_or_default().to_string_lossy().to_string()
70 | } // fn: file_name_string
71 | }
72 | // }}}
73 |
74 | // pub trait VecExt {{{
75 | pub struct VecString
76 | {
77 | str_owned: Vec,
78 | } // VecString
79 |
80 | impl VecString
81 | {
82 | // Method to access the &str references of the owned strings
83 | pub fn as_str_slice(&self) -> Vec<&str>
84 | {
85 | self.str_owned.iter().map(|s| s.as_str()).collect()
86 | } // as_str_slice()
87 | } // impl VecString
88 |
89 | pub trait VecExt
90 | {
91 | fn append_strings(&self, other: Vec) -> VecString;
92 | } // trait VecExt
93 |
94 | impl VecExt for Vec<&str>
95 | {
96 | fn append_strings(&self, other: Vec) -> VecString
97 | {
98 | // Map self to String
99 | let mut str_owned: Vec = self.iter().map(|s| s.to_string()).collect();
100 |
101 | // Extend self with other
102 | str_owned.extend(other);
103 |
104 | // Create VecString
105 | VecString { str_owned }
106 | }
107 | } // impl VecExt
108 | // }}}
109 |
110 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
111 |
--------------------------------------------------------------------------------
/gui/wizard/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wizard"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | shared = { path = "../shared" }
10 | fltk = { version = "1.4", features = ["use-wayland"] }
11 | fltk-theme = "0.6.0"
12 | fltk-grid = "0.4"
13 | fltk-evented = "0.5"
14 | serde_yaml = "0.9"
15 | which = "7.0.0"
16 | closure = "0.3.0"
17 | clown = "1.1.0"
18 | walkdir = "2"
19 | url = "2.5.0"
20 | anyhow = "1.0"
21 | sha256 = "1.5.0"
22 | rust_search = "2.1.0"
23 | serde = { version = "1.0", features = ["derive"] }
24 | serde_json = "1.0"
25 | image_search = { version = "0.4.3", features = ["blocking"] }
26 | tokio = { version = "1", features = ["full"] }
27 | once_cell = "1.19.0"
28 | sysinfo = "0.30.7"
29 | regex = "1.10.3"
30 | libc = "0.2.153"
31 | errno = "0.3.8"
32 | lazy_static = "1.5.0"
33 |
--------------------------------------------------------------------------------
/gui/wizard/src/db/fetch.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use std::collections::HashMap;
3 | use std::fs::File;
4 | use std::path::PathBuf;
5 | use serde::{Deserialize, Serialize};
6 |
7 | #[derive(Clone, Serialize, Deserialize)]
8 | pub struct EntryEmulator
9 | {
10 | pub layer: String,
11 | } // Entry }}}
12 |
13 | #[derive(Clone, Serialize, Deserialize)]
14 | pub struct EntryWine
15 | {
16 | pub layer: HashMap,
17 | } // Entry }}}
18 |
19 | // struct Entry {{{
20 | #[derive(Clone, Serialize, Deserialize)]
21 | pub struct Entry
22 | {
23 | pub version: String,
24 | pub linux: EntryEmulator,
25 | pub rpcs3: EntryEmulator,
26 | pub pcsx2: EntryEmulator,
27 | pub retroarch: EntryEmulator,
28 | pub wine: EntryWine,
29 | } // Entry }}}
30 |
31 | // read() {{{
32 | pub fn read() -> anyhow::Result
33 | {
34 | // GIMG_DIR should contain the path to the build dir
35 | let path_file_db : PathBuf = env::var("GIMG_DIR")?.into();
36 |
37 | // Try to open the gameimage.json file in it
38 | let file = File::open(path_file_db.join("fetch.json"))?;
39 |
40 | // Parse
41 | let entry : Entry = serde_json::from_reader(file)?;
42 |
43 | // Return with absolute paths
44 | Ok(entry)
45 | } // fn: read }}}
46 |
47 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
48 |
--------------------------------------------------------------------------------
/gui/wizard/src/db/global.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 | use std::collections::HashMap;
3 | use std::env;
4 | use std::fs::File;
5 | use std::path::PathBuf;
6 | use serde::{Deserialize, Serialize};
7 |
8 | // struct Entry {{{
9 | #[derive(Clone, Serialize, Deserialize)]
10 | pub struct Entry
11 | {
12 | pub project: String, // path to default project
13 | pub path_dir_build: PathBuf, // path to build dir
14 | pub path_dir_cache: PathBuf, // path to build dir
15 | pub path_file_image: PathBuf, // path to main flatimage
16 | pub path_file_output: PathBuf, // path to output file
17 | pub dist_wine: String, // Current wine distribution
18 | pub projects: HashMap,
19 | } // Entry }}}
20 |
21 | // struct EntryDetails {{{
22 | #[derive(Clone, Serialize, Deserialize)]
23 | pub struct EntryDetails {
24 | pub path_dir_project: PathBuf,
25 | pub path_dir_project_root: PathBuf,
26 | pub platform: String,
27 | } // EntryDetails }}}
28 |
29 | impl Entry
30 | {
31 |
32 | // get_project_dir() {{{
33 | pub fn get_project_dir(&self, name_project : &str) -> anyhow::Result
34 | {
35 | Ok(self.projects.get(name_project).ok_or(ah!("Key '{}' not found in projects list", name_project))?.path_dir_project.clone())
36 | } // get_project_dir() }}}
37 |
38 | }
39 |
40 | // get_current_project() {{{
41 | pub fn get_current_project() -> anyhow::Result
42 | {
43 | let db_global = read()?;
44 | let name_project = db_global.project;
45 | Ok(db_global.projects.get(&name_project).ok_or(ah!("Project not found in projects list"))?.clone())
46 | } // get_current_project() }}}
47 |
48 | // read() {{{
49 | pub fn read() -> anyhow::Result
50 | {
51 | // GIMG_DIR should contain the path to the build dir
52 | let path_file_db : PathBuf = env::var("GIMG_DIR")?.into();
53 | // Try to open the gameimage.json file in it
54 | let file = File::open(path_file_db.join("gameimage.json"))?;
55 | // Parse
56 | let entry : Entry = serde_json::from_reader(file)?;
57 | // Return with absolute paths
58 | Ok(entry)
59 | } // fn: read }}}
60 |
61 | // write() {{{
62 | pub fn write(entry: &Entry) -> anyhow::Result<()>
63 | {
64 | // GIMG_DIR should contain the path to the build dir
65 | let path_file_db : PathBuf = env::var("GIMG_DIR")?.into();
66 | // Try to open the gameimage.json file in it
67 | let file = File::create(path_file_db.join("gameimage.json"))?;
68 | // Parse
69 | serde_json::to_writer_pretty(file, entry)?;
70 | Ok(())
71 | } // fn: write }}}
72 |
73 | // update() {{{
74 | pub fn update(f: F) -> anyhow::Result<()>
75 | where F: FnOnce(Entry) -> Entry
76 | {
77 | // Read
78 | let entry = read()?;
79 | // Update
80 | let entry = f(entry);
81 | // Write
82 | write(&entry)?;
83 | Ok(())
84 | } // fn: update }}}
85 |
86 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
87 |
--------------------------------------------------------------------------------
/gui/wizard/src/db/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod project;
2 | pub mod global;
3 | pub mod fetch;
4 |
--------------------------------------------------------------------------------
/gui/wizard/src/db/project.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 | use std::fs::File;
3 | use std::path::PathBuf;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use crate::db::global;
7 | use crate::common;
8 | use shared::std::PathBufExt;
9 | use crate::log;
10 |
11 | #[allow(dead_code)]
12 | pub enum EntryName
13 | {
14 | PathFileIcon,
15 | PathFileRom,
16 | PathFileCore,
17 | PathFileBios,
18 | }
19 |
20 | // struct Entry {{{
21 | #[derive(Clone, Serialize, Deserialize)]
22 | pub struct Entry
23 | {
24 | project : String,
25 | platform : String,
26 | path_file_icon : Option,
27 | path_file_rom : Option,
28 | path_file_core : Option,
29 | path_file_bios : Option,
30 | } // Entry
31 |
32 | impl Entry
33 | {
34 |
35 | pub fn get_project(&self) -> String
36 | {
37 | self.project.clone()
38 | } // project
39 |
40 | pub fn get_platform(&self) -> String
41 | {
42 | self.platform.clone()
43 | } // project
44 |
45 | pub fn get_dir_self(&self) -> anyhow::Result
46 | {
47 | // Get the build dir
48 | let db = global::read()?;
49 |
50 | // Get project name
51 | let name_project = self.get_project();
52 |
53 | // Return project dir
54 | Ok(db.get_project_dir(&name_project)?)
55 | }
56 |
57 | pub fn get_path_absolute(&self, entry: EntryName) -> anyhow::Result
58 | {
59 | // Get project dir == build_dir / project_name
60 | let project_dir_self = self.get_dir_self()?;
61 |
62 | let f_to_absolute = |entry : &Option| -> Option
63 | {
64 | match entry
65 | {
66 | Some(pathbuf) => Some(project_dir_self.join(pathbuf)),
67 | None => None,
68 | }
69 | };
70 |
71 | let ok_path_file_absolute = match entry
72 | {
73 | EntryName::PathFileIcon => f_to_absolute(&self.path_file_icon),
74 | EntryName::PathFileRom => f_to_absolute(&self.path_file_rom),
75 | EntryName::PathFileCore => f_to_absolute(&self.path_file_core),
76 | EntryName::PathFileBios => f_to_absolute(&self.path_file_bios),
77 | }; // match
78 |
79 | Ok(ok_path_file_absolute.ok_or(ah!("Could not read absolute path"))?)
80 | } // dir_absolute
81 |
82 | pub fn get_path_relative(&self, entry: EntryName) -> anyhow::Result
83 | {
84 | let some_path_project_relative = match entry
85 | {
86 | EntryName::PathFileIcon => self.path_file_icon.clone(),
87 | EntryName::PathFileRom => self.path_file_rom.clone(),
88 | EntryName::PathFileCore => self.path_file_core.clone(),
89 | EntryName::PathFileBios => self.path_file_bios.clone(),
90 | }; // match
91 |
92 | Ok(some_path_project_relative.ok_or(ah!("Could not read relative path"))?)
93 | } // get_dir_relative
94 |
95 | }
96 | // struct Entry }}}
97 |
98 | pub type Entries = Vec;
99 |
100 | // list() {{{
101 |
102 | // List all projects
103 | pub fn list() -> anyhow::Result
104 | {
105 | let mut entries : Entries = Vec::new();
106 | let db_global = global::read()?;
107 |
108 | for project in db_global.projects.clone()
109 | {
110 | let (_, data) = project.clone();
111 |
112 | // Expected json file
113 | let path_file_json = data.path_dir_project.join("gameimage.json");
114 |
115 | // Open project file
116 | let file = match File::open(&path_file_json)
117 | {
118 | Ok(file) => file,
119 | Err(e) => { log!("Could not open file '{}' with error '{}'", path_file_json.string(), e); continue; }
120 | };
121 |
122 | // Get project entry
123 | let entry : Entry = match serde_json::from_reader(file)
124 | {
125 | Ok(entry) => entry,
126 | Err(_) => continue,
127 | };
128 |
129 | // Include in vec
130 | entries.push(entry);
131 | } // for
132 |
133 | Ok(entries)
134 | } // fn: list }}}
135 |
136 | // pub fn current() {{{
137 |
138 | // Reads the current project entry
139 | pub fn current() -> anyhow::Result
140 | {
141 | // Get global info
142 | let global = global::read()?;
143 |
144 | // Get the current project
145 | let path_file_project = global.get_project_dir(&global.project)?.join("gameimage.json");
146 |
147 | // Read file
148 | let file = File::open(&path_file_project)?;
149 |
150 | // Read entry
151 | Ok(serde_json::from_reader(file)?)
152 | } // current() }}}
153 |
154 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
155 |
--------------------------------------------------------------------------------
/gui/wizard/src/frame/common.rs:
--------------------------------------------------------------------------------
1 | // Gui
2 | use fltk::prelude::*;
3 | use fltk::{
4 | output::Output,
5 | frame::Frame,
6 | enums::{Align,Color},
7 | };
8 |
9 | use shared::fltk::WidgetExtExtra;
10 | use shared::{tabs,hover_blink,hseparator_fixed,column,row,add,fixed};
11 |
12 | use crate::dimm;
13 | use crate::frame;
14 |
15 | // pub fn layout() {{{
16 | pub fn layout()
17 | {
18 | column!(col,
19 | col.set_margin(dimm::border_half());
20 | col.set_spacing(dimm::border_half());
21 | row!(row_header,
22 | fixed!(row_header, btn_term, shared::fltk::button::rect::terminal().with_color(Color::Blue), dimm::width_button_rec());
23 | add!(row_header, frame_title, Frame::default().with_id("header_title").with_align(Align::Inside | Align::Center));
24 | fixed!(row_header, btn_resize, shared::fltk::button::rect::resize_down()
25 | .with_id("btn_resize")
26 | .with_color(Color::Blue), dimm::width_button_rec()
27 | );
28 | );
29 | col.fixed(&row_header, dimm::height_button_rec());
30 | hseparator_fixed!(col, dimm::width_wizard() - dimm::border()*2, dimm::border_half());
31 | tabs!(tab_content,
32 | column!(col_content_footer,
33 | col_content_footer.set_frame(fltk::enums::FrameType::FlatBox);
34 | col_content_footer.set_color(Color::BackGround);
35 | column!(group_content, group_content.set_id("content"););
36 | col_content_footer.add(&group_content);
37 | hseparator_fixed!(col_content_footer, dimm::width_wizard() - dimm::border()*2, dimm::border_half());
38 | row!(footer,
39 | footer.set_id("footer");
40 | fixed!(footer, btn_prev, shared::fltk::button::wide::default()
41 | .with_id("footer_prev")
42 | .with_label("Prev"), dimm::width_button_wide());
43 | add!(footer, expand, Frame::default());
44 | fixed!(footer, btn_next, shared::fltk::button::wide::default()
45 | .with_id("footer_next")
46 | .with_label("Next")
47 | .with_color(Color::Blue), dimm::width_button_wide());
48 | );
49 | col_content_footer.fixed(&footer, dimm::height_button_wide());
50 | col_content_footer.fixed(&Output::default().with_id("footer_status"), 20);
51 | );
52 | column!(col_content_term,
53 | col_content_term.set_frame(fltk::enums::FrameType::FlatBox);
54 | col_content_term.set_color(Color::BackGround);
55 | let term = frame::term::Term::default();
56 | term.term.clone().set_id("term_log");
57 | );
58 | );
59 | col.add(&tab_content);
60 | );
61 |
62 | // Configure buttons
63 | hover_blink!(btn_term);
64 | hover_blink!(btn_resize);
65 | hover_blink!(btn_prev);
66 | hover_blink!(btn_next);
67 |
68 | // Title font size
69 | frame_title.clone().set_label_size((dimm::height_text() as f32 * 1.5) as i32);
70 |
71 | // Switch between tabs
72 | btn_term.clone().set_callback({
73 | let col_content_term = col_content_term.clone();
74 | let col_content_footer = col_content_footer.clone();
75 | move |_|
76 | {
77 | if tab_content.value().unwrap().is_same(&col_content_footer.as_group().unwrap())
78 | {
79 | let _ = tab_content.set_value(&col_content_term.as_group().unwrap());
80 | }
81 | else
82 | {
83 | let _ = tab_content.set_value(&col_content_footer.as_group().unwrap());
84 | } // else
85 | }
86 | });
87 |
88 | } // }}}
89 |
90 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
91 |
--------------------------------------------------------------------------------
/gui/wizard/src/frame/finish.rs:
--------------------------------------------------------------------------------
1 | // Gui
2 | use fltk::prelude::*;
3 | use fltk::{
4 | app::Sender,
5 | output,
6 | text,
7 | };
8 |
9 | use anyhow::anyhow as ah;
10 |
11 | use shared::fltk::WidgetExtExtra;
12 |
13 | use crate::db;
14 | use crate::dimm;
15 | use crate::log_status;
16 | use crate::common;
17 | use shared::std::OsStrExt;
18 | use shared::std::PathBufExt;
19 |
20 | // fn: finish_file_location() {{{
21 | fn finish_file_location(output: &mut fltk::output::Output) -> anyhow::Result
22 | {
23 | let db_global = db::global::read()?;
24 | let _ = output.insert(&db_global.path_file_output.string());
25 | Ok(format!(".{}.config", &db_global.path_file_output.file_name().ok_or(ah!("Could not get file stem"))?.string()))
26 | } // fn: finish_file_location() }}}
27 |
28 | // pub fn finish() {{{
29 | pub fn finish(tx: Sender, title: &str)
30 | {
31 | // Enter the build directory
32 | if let Err(e) = common::dir_build()
33 | {
34 | log_status!("Err: {}", e.to_string());
35 | } // if
36 |
37 | let mut ui = crate::GUI.lock().unwrap().ui.clone()(title);
38 |
39 | // Deactivate prev button
40 | ui.btn_prev.deactivate();
41 |
42 | // Set next button to start over
43 | ui.btn_next.emit(tx, common::Msg::DrawWelcome);
44 | ui.btn_next.set_label("Finish");
45 |
46 | // Show where the package was saved into
47 | let mut col = fltk::group::Flex::default()
48 | .column()
49 | .with_size_of(&ui.group)
50 | .with_pos_of(&ui.group);
51 | // Label
52 | col.fixed(&fltk::frame::Frame::default()
53 | .with_align(fltk::enums::Align::Inside | fltk::enums::Align::Left)
54 | .with_label("Your package was saved in this location"), dimm::height_text());
55 | // Saved file path
56 | let mut output_saved_location = output::Output::default().with_focus(false);
57 | col.fixed(&output_saved_location, dimm::height_button_wide());
58 | // Retrieve output image file location
59 | let str_package_basename = finish_file_location(&mut output_saved_location).unwrap_or_default();
60 | let mut output_info = text::TextDisplay::default()
61 | .with_color(fltk::enums::Color::BackGround)
62 | .with_frame(fltk::enums::FrameType::NoBox);
63 | output_info.wrap_mode(text::WrapMode::AtColumn, 0);
64 | output_info.set_buffer(text::TextBuffer::default());
65 | output_info.insert("You can now move the package to your games folder,");
66 | output_info.insert(" other Linux computer or an external hard drive.");
67 | output_info.insert(" To start using your application, simply click to launch.");
68 | output_info.insert("\n\n");
69 | output_info.insert("Regardless of where you store your package,");
70 | output_info.insert(" launching it for the first time will generate");
71 | output_info.insert(&format!(" a directory called '{}'", str_package_basename));
72 | output_info.insert(", this directory contains application data such as save games.");
73 | output_info.insert("\n\n");
74 | output_info.insert("If you encounter any issues or have suggestions for new features,");
75 | output_info.insert(" I encourage you to create an issue on GitHub or GitLab.");
76 | output_info.insert(" Your feedback is invaluable to help project improve.");
77 | col.add(&output_info);
78 | col.end();
79 | } // }}}
80 |
81 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
82 |
--------------------------------------------------------------------------------
/gui/wizard/src/frame/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod term;
2 | pub mod common;
3 | pub mod welcome;
4 | pub mod platform;
5 | pub mod creator;
6 | pub mod desktop;
7 | pub mod finish;
8 | pub mod icon;
9 |
--------------------------------------------------------------------------------
/gui/wizard/src/frame/welcome.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use std::path::PathBuf;
3 |
4 | // Gui
5 | use fltk::prelude::*;
6 | use fltk::{
7 | app::Sender,
8 | input::FileInput,
9 | frame::Frame,
10 | dialog::dir_chooser,
11 | enums::Align,
12 | };
13 |
14 | use shared::fltk::SenderExt;
15 | use anyhow::anyhow as ah;
16 |
17 | use crate::db;
18 | use crate::gameimage;
19 | use crate::dimm;
20 | use crate::common;
21 | use crate::log_status;
22 | use shared::svg;
23 | use shared::std::PathBufExt;
24 | use shared::{column,row,add,fixed};
25 |
26 | // check_version() {{{
27 | fn check_version() -> anyhow::Result<()>
28 | {
29 | let db_fetch = match db::fetch::read()
30 | {
31 | Ok(db) => db,
32 | Err(e) => return Err(ah!("error: could not read fetch.json, backend failed? No internet? '{}", e)),
33 | }; // match
34 |
35 | let version = db_fetch.version;
36 | if ! version.starts_with("1.6")
37 | {
38 | return Err(ah!("error: you should update to version {}", version));
39 | } // if
40 |
41 | Ok(())
42 | } // check_version() }}}
43 |
44 | // pub fn welcome() {{{
45 | pub fn welcome(tx: Sender, title: &str)
46 | {
47 | let ui = crate::GUI.lock().unwrap().ui.clone()(title);
48 |
49 | column!(col,
50 | add!(col, spacer, Frame::default());
51 | row!(row,
52 | add!(row, spacer, Frame::default());
53 | fixed!(row, frame_image, Frame::default(), dimm::height_button_wide()*4);
54 | add!(row, spacer, Frame::default());
55 | );
56 | col.fixed(&row, dimm::height_button_wide()*4);
57 | add!(col, spacer, Frame::default());
58 | fixed!(col, _label, Frame::default()
59 | .with_align(Align::Left | Align::Inside)
60 | .with_label("Select The Directory for GameImage's Temporary Files")
61 | , dimm::height_text());
62 | fixed!(col, input_dir, FileInput::default(), dimm::height_button_wide() + dimm::border_half());
63 | );
64 |
65 | // Image
66 | let mut frame_image = frame_image.clone();
67 | frame_image.set_align(Align::Inside | Align::Bottom);
68 | frame_image.set_image_scaled(fltk::image::SvgImage::from_data(svg::ICON_GAMEIMAGE).ok());
69 |
70 | // Input
71 | let mut input_dir = input_dir.clone();
72 | input_dir.set_pos(dimm::border(), input_dir.y());
73 | input_dir.set_readonly(true);
74 | input_dir.set_value(&env::var("GIMG_DIR").unwrap_or_default());
75 | input_dir.set_callback(move |e|
76 | {
77 | let mut path_selected = match dir_chooser("Select the build directory", "", false)
78 | {
79 | Some(value) => PathBuf::from(value),
80 | None => { log_status!("No file selected"); return; },
81 | };
82 | // Set build dir as chosen dir + /build
83 | path_selected = path_selected.join("build");
84 | // Update chosen dir in selection bar
85 | e.set_value(&path_selected.string());
86 | // Set env var to build dir
87 | env::set_var("GIMG_DIR", &path_selected.string());
88 | });
89 |
90 | // Set callback for next
91 | let clone_tx = tx.clone();
92 | ui.btn_next.clone().set_callback(move |_|
93 | {
94 | let path_dir_build = match env::var("GIMG_DIR")
95 | {
96 | Ok(value) => PathBuf::from(value),
97 | Err(e) => { log_status!("Invalid temporary files directory: {}", e); return; }
98 | }; // if
99 | // Create build directory
100 | match std::fs::create_dir_all(&path_dir_build)
101 | {
102 | Ok(()) => (),
103 | Err(e) => log_status!("Could not create build directory: {}", e),
104 | }
105 | // Init project build directory
106 | match gameimage::init::build(path_dir_build)
107 | {
108 | Ok(()) => (),
109 | Err(e) => log_status!("Error to initialize build directory: {}", e)
110 | }; // match
111 | // Fetch fetch list
112 | match gameimage::fetch::sources()
113 | {
114 | Ok(code) => log_status!("Fetch exited with code {}", code),
115 | Err(e) => log_status!("Error to initialize build directory: {}", e)
116 | }; // match
117 | // Check if version matches
118 | if let Err(e) = check_version()
119 | {
120 | log_status!("{}", e);
121 | fltk::dialog::message_default(&format!("{}", e));
122 | clone_tx.send_awake(common::Msg::WindActivate);
123 | return;
124 | } // if
125 | // Draw creator frame
126 | clone_tx.send_awake(common::Msg::DrawCreator);
127 | });
128 | } // fn: welcome }}}
129 |
130 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
131 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/desktop.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | use serde_json::json;
4 |
5 | use shared::std::PathBufExt;
6 |
7 | use crate::gameimage::gameimage;
8 |
9 | // pub fn desktop() {{{
10 | pub fn desktop(name: &str, items: &str) -> anyhow::Result<()>
11 | {
12 | let mut json_args = json!({});
13 | json_args["op"] = "desktop".into();
14 | json_args["desktop"]["op"] = "setup".into();
15 | json_args["desktop"]["name"] = name.into();
16 | json_args["desktop"]["items"] = items.into();
17 | // Wait for message & check return value
18 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
19 | {
20 | 0 => Ok(()),
21 | ret => Err(ah!("Could not include {} into the image: {}", name, ret)),
22 | } // match
23 | } // fn: desktop }}}
24 |
25 | // pub fn icon() {{{
26 | pub fn icon(path : &std::path::PathBuf) -> anyhow::Result<()>
27 | {
28 | let mut json_args = json!({});
29 | json_args["op"] = "desktop".into();
30 | json_args["desktop"]["op"] = "icon".into();
31 | json_args["desktop"]["path_file_icon"] = path.string().into();
32 | // Wait for message & check return value
33 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
34 | {
35 | 0 => Ok(()),
36 | ret => Err(ah!("Could not setup desktop icon {}: {}", path.string(), ret)),
37 | } // match
38 | } // fn: icon }}}
39 |
40 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
41 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/fetch.rs:
--------------------------------------------------------------------------------
1 | use std::sync::{mpsc,Arc,Mutex};
2 |
3 | use serde_json::json;
4 |
5 | use anyhow::anyhow as ah;
6 |
7 | use crate::log;
8 | use crate::common;
9 | use crate::gameimage;
10 |
11 | // fetch() {{{
12 | pub fn fetch(platform: common::Platform, f: F) -> anyhow::Result
13 | where F: FnMut(mpsc::Receiver) + Send + 'static
14 | {
15 | let mut json_args = json!({});
16 | json_args["op"] = "fetch".into();
17 | json_args["fetch"]["op"] = "fetch".into();
18 | json_args["fetch"]["platform"] = platform.as_str().into();
19 | match gameimage::gameimage::gameimage_sync_ipc(vec![&json_args.to_string()], f)
20 | {
21 | 0 => { log!("Fetch on backend finished successfully"); Ok(0) },
22 | rc => { return Err(ah!("Failed to execute fetch on backend with {}", rc)); },
23 | } // match
24 | } // fetch() }}}
25 |
26 | // installed() {{{
27 | pub fn installed() -> anyhow::Result>
28 | {
29 | let mut json_args = json!({});
30 | json_args["op"] = "fetch".into();
31 | json_args["fetch"]["op"] = "installed".into();
32 | let arc_platforms : Arc>> = Arc::new(Mutex::new(vec![]));
33 | let clone_arc_platforms = arc_platforms.clone();
34 | gameimage::gameimage::gameimage_sync_ipc(vec![&json_args.to_string()], move |rx|
35 | {
36 | while let Ok(msg) = rx.recv()
37 | {
38 | match clone_arc_platforms.lock()
39 | {
40 | Ok(mut guard) => match common::Platform::from_str(&msg)
41 | {
42 | Some(platform) => guard.push(platform),
43 | None => log!("Invalid platform: {}", msg),
44 | }
45 | Err(e) => log!("Could not lock installed vec: {}", e),
46 | };
47 | } // while
48 | });
49 |
50 | match arc_platforms.clone().lock()
51 | {
52 | Ok(platforms) => Ok(platforms.clone()),
53 | Err(e) => return Err(ah!("Could not lock platforms: {}", e)),
54 | }
55 | } // installed() }}}
56 |
57 | // sources() {{{
58 | pub fn sources() -> anyhow::Result
59 | {
60 | let mut json_args = json!({});
61 | json_args["op"] = "fetch".into();
62 | json_args["fetch"]["op"] = "sources".into();
63 | match gameimage::gameimage::gameimage_sync(vec![&json_args.to_string()])
64 | {
65 | 0 => { log!("Fetch on backend finished successfully"); Ok(0)},
66 | rc => { log!("Failed to execute fetch on backend with {}", rc); Ok(rc)},
67 | } // match
68 | } // sources() }}}
69 |
70 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
71 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/gameimage.rs:
--------------------------------------------------------------------------------
1 | use std::
2 | {
3 | env,
4 | sync::{Arc,Mutex,mpsc},
5 | };
6 |
7 | use crate::lib;
8 | use crate::common;
9 | use crate::log_err;
10 | use crate::log;
11 |
12 |
13 |
14 | // fn binary() {{{
15 | pub fn binary() -> anyhow::Result
16 | {
17 | Ok(which::which("gameimage-cli")?)
18 | } // }}}
19 |
20 | // pub fn dir_build() {{{
21 | pub fn dir_build() -> anyhow::Result<()>
22 | {
23 | Ok(env::set_current_dir(std::path::PathBuf::from(env::var("GIMG_DIR")?))?)
24 | } // fn: dir_build }}}
25 |
26 | // pub fn gameimage_async() {{{
27 | pub fn gameimage_async(args : Vec<&str>) -> anyhow::Result<(mpsc::Receiver, mpsc::Receiver)>
28 | {
29 | dir_build()?;
30 |
31 | let path_binary_gameimage = binary()?;
32 |
33 | let mut handle = std::process::Command::new(&path_binary_gameimage)
34 | .stdout(std::process::Stdio::piped())
35 | .stderr(std::process::Stdio::piped())
36 | .args(&args)
37 | .spawn()?;
38 |
39 | log!("Dispatch command: {:?} : {:?}", path_binary_gameimage, args);
40 |
41 | // Create arc reader for stdout
42 | let arc_stdout = Arc::new(Mutex::new(handle.stdout.take()));
43 | let arc_stderr = Arc::new(Mutex::new(handle.stderr.take()));
44 | let arc_handle = Arc::new(Mutex::new(handle));
45 |
46 | // Create t/r
47 | let stdout = arc_stdout.lock().unwrap().take();
48 | let stderr = arc_stderr.lock().unwrap().take();
49 | let (tx_code, rx_code) = mpsc::channel();
50 | std::thread::spawn(move ||
51 | {
52 | let (tx_log, rx_log) = mpsc::channel();
53 | let f_callback = |tx : mpsc::Sender, msg| { log_err!(tx.send(msg)); };
54 | let handle_stdout = std::thread::spawn(common::log_fd(stdout.unwrap(), tx_log.clone(), f_callback));
55 | let handle_stderr = std::thread::spawn(common::log_fd(stderr.unwrap(), tx_log, f_callback));
56 | while let Ok(msg) = rx_log.recv()
57 | {
58 | for line in msg.split('\n')
59 | {
60 | log!("{}", line);
61 | }
62 | } // while
63 |
64 | log_err!(handle_stdout.join());
65 | log_err!(handle_stderr.join());
66 |
67 | // Close IPC
68 | lib::ipc::Ipc::close();
69 |
70 | // Send exit code
71 | if let Ok(mut guard) = arc_handle.lock()
72 | && let Ok(status) = guard.wait()
73 | && let Some(code) = status.code()
74 | {
75 | log_err!(tx_code.send(code));
76 | }
77 | else
78 | {
79 | log_err!(tx_code.send(1));
80 | } // else
81 | });
82 |
83 | let (tx_ipc, rx_ipc) = mpsc::channel();
84 | // Write from ipc to channel
85 | std::thread::spawn(move ||
86 | {
87 | // Open ipc
88 | let ipc = match lib::ipc::Ipc::new()
89 | {
90 | Ok(ipc) => ipc,
91 | Err(e) => { log!("Could not create ipc instance: {}", e); return; },
92 | }; // match
93 | // Write received message to transmitter
94 | while let Ok(msg) = ipc.recv()
95 | {
96 | if let Err(e) = tx_ipc.send(msg)
97 | {
98 | log!("Could not send ipc retrieved message: {}", e);
99 | } // if
100 | } // while
101 | });
102 |
103 | Ok((rx_ipc, rx_code))
104 | } // fn: gameimage_async }}}
105 |
106 | // pub fn gameimage_sync() {{{
107 | pub fn gameimage_sync(args : Vec<&str>) -> i32
108 | {
109 | let (_, rx_code) = match gameimage_async(args)
110 | {
111 | Ok((rx_ipc, rx_code)) => (rx_ipc, rx_code),
112 | Err(e) => { log!("Could not start backend: {}", e); return 1; },
113 | }; // if
114 |
115 | rx_code.recv().unwrap_or(1)
116 | } // fn: gameimage_sync }}}
117 |
118 | // pub fn gameimage_sync_ipc() {{{
119 | pub fn gameimage_sync_ipc(args : Vec<&str>, mut f: F) -> i32
120 | where F: FnMut(mpsc::Receiver) + Send + 'static
121 | {
122 | let (rx_ipc, rx_code) = match gameimage_async(args)
123 | {
124 | Ok((rx_ipc, rx_code)) => (rx_ipc, rx_code),
125 | Err(e) => { log!("Could not start backend: {}", e); return 1; },
126 | }; // if
127 |
128 | // Receive messages
129 | f(rx_ipc);
130 |
131 | // Recover exit code
132 | rx_code.recv().unwrap_or(1)
133 | } // fn: gameimage_sync_ipc }}}
134 |
135 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
136 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/init.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | use anyhow::anyhow as ah;
4 |
5 | use serde_json::json;
6 |
7 | use shared::std::PathBufExt;
8 |
9 | use crate::gameimage::gameimage;
10 |
11 | // pub fn build() {{{
12 | pub fn build(path_dir_build : PathBuf) -> anyhow::Result<()>
13 | {
14 | let mut json_args = json!({});
15 | json_args["op"] = "init".into();
16 | json_args["init"]["op"] = "build".into();
17 | json_args["init"]["path_dir_build"] = path_dir_build.string().into();
18 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
19 | {
20 | 0 => Ok(()),
21 | ret => Err(ah!("Could not init gameimage build root: {}", ret)),
22 | } // match
23 | } // fn: build }}}
24 |
25 | // pub fn project() {{{
26 | pub fn project(name : String, platform : String) -> anyhow::Result<()>
27 | {
28 | let mut json_args = json!({});
29 | json_args["op"] = "init".into();
30 | json_args["init"]["op"] = "project".into();
31 | json_args["init"]["name"] = name.into();
32 | json_args["init"]["platform"] = platform.into();
33 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
34 | {
35 | 0 => Ok(()),
36 | ret => Err(ah!("Could not init gameimage project: {}", ret)),
37 | } // match
38 | } // fn: project }}}
39 |
40 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
41 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/install.rs:
--------------------------------------------------------------------------------
1 | use shared::std::PathBufExt;
2 |
3 | use serde_json::json;
4 |
5 | use anyhow::anyhow as ah;
6 |
7 | use crate::gameimage::gameimage;
8 |
9 | // pub fn icon() {{{
10 | pub fn icon(path : &std::path::PathBuf) -> anyhow::Result<()>
11 | {
12 | let mut json_args = json!({});
13 | json_args["op"] = "install".into();
14 | json_args["install"]["op"] = "install".into();
15 | json_args["install"]["sub_op"] = "icon".into();
16 | json_args["install"]["args"] = vec![path.string()].into();
17 | // Wait for message & check return value
18 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
19 | {
20 | 0 => Ok(()),
21 | ret => Err(ah!("Could not install icon '{}' into the image: {}", path.string(), ret)),
22 | } // match
23 | } // fn: icon }}}
24 |
25 | // pub fn install() {{{
26 | pub fn install(str_type : &str, vec_path_files : Vec) -> anyhow::Result<()>
27 | {
28 | let mut json_args = json!({});
29 | json_args["op"] = "install".into();
30 | json_args["install"]["op"] = "install".into();
31 | json_args["install"]["sub_op"] = str_type.into();
32 | json_args["install"]["args"] = vec_path_files.into();
33 | // Wait for message & check return value
34 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
35 | {
36 | 0 => Ok(()),
37 | ret => Err(ah!("Could not install files: {}", ret)),
38 | } // match
39 | } // fn: install }}}
40 |
41 | // pub fn remote() {{{
42 | pub fn remote(str_type : &str, vec_path_files : Vec) -> anyhow::Result<()>
43 | {
44 | let mut json_args = json!({});
45 | json_args["op"] = "install".into();
46 | json_args["install"]["op"] = "remote".into();
47 | json_args["install"]["sub_op"] = str_type.into();
48 | json_args["install"]["args"] = vec_path_files.into();
49 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
50 | {
51 | 0 => Ok(()),
52 | ret => Err(ah!("Could not install remote files: {}", ret)),
53 | } // match
54 | } // fn: remote }}}
55 |
56 | // pub fn remove() {{{
57 | pub fn remove(str_type : &str, vec_path_files : Vec) -> anyhow::Result<()>
58 | {
59 | let mut json_args = json!({});
60 | json_args["op"] = "install".into();
61 | json_args["install"]["op"] = "remove".into();
62 | json_args["install"]["sub_op"] = str_type.into();
63 | json_args["install"]["args"] = vec_path_files.into();
64 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
65 | {
66 | 0 => Ok(()),
67 | ret => Err(ah!("Could not remove files: {}", ret)),
68 | } // match
69 | } // fn: remove }}}
70 |
71 | // pub fn gui() {{{
72 | pub fn gui() -> anyhow::Result<()>
73 | {
74 | let mut json_args = json!({});
75 | json_args["op"] = "install".into();
76 | json_args["install"]["op"] = "install".into();
77 | json_args["install"]["sub_op"] = "gui".into();
78 | json_args["install"]["args"] = Vec::::new().into();
79 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
80 | {
81 | 0 => Ok(()),
82 | ret => Err(ah!("Could not install files: {}", ret)),
83 | } // match
84 | } // fn: install }}}
85 |
86 | // pub fn winetricks() {{{
87 | pub fn winetricks(vec_path_files : Vec) -> anyhow::Result<()>
88 | {
89 | let mut json_args = json!({});
90 | json_args["op"] = "install".into();
91 | json_args["install"]["op"] = "install".into();
92 | json_args["install"]["sub_op"] = "winetricks".into();
93 | json_args["install"]["args"] = vec_path_files.into();
94 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
95 | {
96 | 0 => Ok(()),
97 | ret => Err(ah!("Could not install files: {}", ret)),
98 | } // match
99 | } // fn: winetricks }}}
100 |
101 | // pub fn wine() {{{
102 | pub fn wine(vec_path_files : Vec) -> anyhow::Result<()>
103 | {
104 | let mut json_args = json!({});
105 | json_args["op"] = "install".into();
106 | json_args["install"]["op"] = "install".into();
107 | json_args["install"]["sub_op"] = "wine".into();
108 | json_args["install"]["args"] = vec_path_files.into();
109 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
110 | {
111 | 0 => Ok(()),
112 | ret => Err(ah!("Could not install files: {}", ret)),
113 | } // match
114 | } // fn: wine }}}
115 |
116 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
117 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod gameimage;
2 | pub mod fetch;
3 | pub mod search;
4 | pub mod package;
5 | pub mod desktop;
6 | pub mod install;
7 | pub mod select;
8 | pub mod init;
9 | pub mod test;
10 | pub mod project;
11 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/package.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | use serde_json::json;
4 |
5 | use crate::gameimage::gameimage;
6 |
7 | // pub fn package() {{{
8 | pub fn package(name: &str, projects : Vec) -> anyhow::Result<()>
9 | {
10 | let mut json_args = json!({});
11 | json_args["op"] = "package".into();
12 | json_args["package"]["name"] = name.into();
13 | json_args["package"]["projects"] = projects.clone().into();
14 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
15 | {
16 | 0 => Ok(()),
17 | ret => Err(ah!("Could not include projects '{}' into the image: {}", projects.join(":"), ret)),
18 | } // match
19 | } // fn: package }}}
20 |
21 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
22 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/project.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | use serde_json::json;
4 |
5 | use crate::gameimage::gameimage;
6 |
7 | // pub fn set() {{{
8 | #[allow(dead_code)] pub fn set(str_name: &str) -> anyhow::Result<()>
9 | {
10 | let mut json_args = json!({});
11 | json_args["op"] = "project".into();
12 | json_args["project"]["op"] = "set".into();
13 | json_args["project"]["name"] = str_name.into();
14 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
15 | {
16 | 0 => Ok(()),
17 | ret => Err(ah!("Project command failed with return code: {}", ret)),
18 | } // match
19 | } // fn: select }}}
20 |
21 | // pub fn del() {{{
22 | pub fn del(str_name: &str) -> anyhow::Result<()>
23 | {
24 | let mut json_args = json!({});
25 | json_args["op"] = "project".into();
26 | json_args["project"]["op"] = "del".into();
27 | json_args["project"]["name"] = str_name.into();
28 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
29 | {
30 | 0 => Ok(()),
31 | ret => Err(ah!("Project command failed with return code: {}", ret)),
32 | } // match
33 | } // fn: select }}}
34 |
35 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
36 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/search.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | use serde_json::json;
4 |
5 | use crate::log;
6 | use crate::common;
7 | use crate::gameimage;
8 |
9 | // search() {{{
10 | fn search(str_type : &str, use_remote : bool) -> anyhow::Result>
11 | {
12 | let mut json_args = json!({});
13 | json_args["op"] = "search".into();
14 | json_args["search"]["op"] = if use_remote { "remote".into() } else { "local".into() };
15 | json_args["search"]["query"] = str_type.into();
16 | // Start backend
17 | let (rx_msg, rx_code) = match gameimage::gameimage::gameimage_async(vec![&json_args.to_string()])
18 | {
19 | Ok((rx_msg, rx_code)) => (rx_msg, rx_code),
20 | Err(e) => return Err(ah!("Could not start gameimage backend: {}", e)),
21 | };
22 | log!("Started backend");
23 | // Retrieve messages
24 | let mut vec : Vec = vec![];
25 | while let Ok(msg) = rx_msg.recv()
26 | {
27 | vec.push(msg.into());
28 | } // while
29 | log!("Finished reading messages");
30 | match rx_code.recv()
31 | {
32 | Ok(code) => match code
33 | {
34 | 0 => log!("Backend exited successfully"),
35 | val => log!("Backend exited with code '{}'", val),
36 | },
37 | Err(e) => return Err(ah!("Failed to retrieve code from backend: {}", e)),
38 | } // match
39 | Ok(vec)
40 | } // search() }}}
41 |
42 | // search_local() {{{
43 | pub fn search_local(str_type : &str) -> anyhow::Result>
44 | {
45 | Ok(search(str_type, false)?)
46 | } // search_local() }}}
47 |
48 | // search_remote() {{{
49 | pub fn search_remote(str_type : &str) -> anyhow::Result>
50 | {
51 | Ok(search(str_type, true)?)
52 | } // search_remote() }}}
53 |
54 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
55 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/select.rs:
--------------------------------------------------------------------------------
1 | use shared::std::PathBufExt;
2 |
3 | use serde_json::json;
4 |
5 | use anyhow::anyhow as ah;
6 |
7 | use crate::gameimage::gameimage;
8 |
9 | // pub fn select() {{{
10 | pub fn select(str_label : &str, path : &std::path::PathBuf) -> anyhow::Result<()>
11 | {
12 | let mut json_args = json!({});
13 | json_args["op"] = "select".into();
14 | json_args["select"]["op"] = str_label.into();
15 | json_args["select"]["path_file_target"] = path.string().into();
16 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
17 | {
18 | 0 => Ok(()),
19 | ret => Err(ah!("Could not select '{}' '{}' into the image: {}", &str_label, path.string(), ret)),
20 | } // match
21 | } // fn: select }}}
22 |
23 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
24 |
--------------------------------------------------------------------------------
/gui/wizard/src/gameimage/test.rs:
--------------------------------------------------------------------------------
1 | use anyhow::anyhow as ah;
2 |
3 | use serde_json::json;
4 |
5 | use crate::log;
6 | use crate::common;
7 | use crate::gameimage::gameimage;
8 |
9 | // pub fn test() {{{
10 | pub fn test() -> anyhow::Result<()>
11 | {
12 | let mut json_args = json!({});
13 | json_args["op"] = "test".into();
14 | match gameimage::gameimage_sync(vec![&json_args.to_string()])
15 | {
16 | 0 => { log!("test returned successfully"); return Ok(()) },
17 | ret => return Err(ah!("test returned with error: {}", ret)),
18 | } // match
19 | } // fn: test }}}
20 |
--------------------------------------------------------------------------------
/gui/wizard/src/lib/ipc.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::CString;
2 |
3 | use anyhow::anyhow as ah;
4 |
5 | use shared::std::PathBufExt;
6 |
7 | use crate::log;
8 | use crate::common;
9 | use crate::gameimage;
10 |
11 | // struct MsgBuf {{{
12 | #[repr(C)]
13 | struct MsgBuf
14 | {
15 | mtype: libc::c_long,
16 | mtext: [u8; 1024],
17 | } // struct MsgBuf }}}
18 |
19 | // pub struct Ipc {{{
20 | pub struct Ipc
21 | {
22 | msgid : i32,
23 | } // struct Ipc }}}
24 |
25 | impl Ipc
26 | {
27 |
28 | // pub fn new() {{{
29 | pub fn new() -> anyhow::Result
30 | {
31 | let msgid = match Ipc::get_msgid(libc::IPC_CREAT)
32 | {
33 | Ok(msgid) => msgid,
34 | Err(e) => return Err(ah!("Failed to create message queue: {}", e)),
35 | };
36 |
37 | Ok(Ipc { msgid })
38 | } // }}}
39 |
40 | // pub fn recv() {{{
41 | pub fn recv(&self) -> anyhow::Result
42 | {
43 | let mut buf: MsgBuf = unsafe { std::mem::zeroed() };
44 |
45 | let ret = unsafe
46 | {
47 | libc::msgrcv(self.msgid
48 | , &mut buf as *mut MsgBuf as *mut libc::c_void
49 | , buf.mtext.len() as libc::size_t
50 | , 0
51 | , libc::MSG_NOERROR)
52 | };
53 |
54 | if ret == -1
55 | {
56 | return Err(ah!("Could not recover message: {}", std::io::Error::last_os_error()));
57 | } // if
58 |
59 | let ret = ret as usize;
60 | let bytes = &buf.mtext[..ret];
61 |
62 | let str_slice = match std::str::from_utf8(bytes) {
63 | Ok(s) => s,
64 | Err(e) => return Err(ah!("Received message is not valid UTF-8: {}", e)),
65 | };
66 |
67 | Ok(str_slice.to_owned())
68 | } // }}}
69 |
70 | // fn get_msgid() {{{
71 | fn get_msgid(flags: i32) -> anyhow::Result
72 | {
73 | let path_file_backend = match gameimage::gameimage::binary()
74 | {
75 | Ok(path_file_backend) => path_file_backend,
76 | Err(e) => return Err(ah!("Could not get path to backend binary for ipc: {}", e)),
77 | };
78 |
79 | // Wait for backend to create fifo
80 | let cstr_path = match CString::new(path_file_backend.string().clone())
81 | {
82 | Ok(cstr) => cstr,
83 | Err(e) => return Err(ah!("Could not create C string: {}", e)),
84 | }; // match
85 |
86 | let key = match unsafe { libc::ftok(cstr_path.as_ptr(), 65) }
87 | {
88 | -1 => return Err(ah!("Failed to get key to check message queue: {}", errno::errno())),
89 | key => key,
90 | };
91 | log!("Frontend key is: {}", key);
92 |
93 | match unsafe { libc::msgget(key, 0o666 | flags) }
94 | {
95 | -1 => return Err(ah!("Message queue does not yet exist, no need to close: {}", errno::errno())),
96 | msgid => Ok(msgid),
97 | }
98 | } // fn get_msgid() }}}
99 |
100 | // pub fn close() {{{
101 | pub fn close()
102 | {
103 | let msgid = match Ipc::get_msgid(0)
104 | {
105 | Ok(msgid) => msgid,
106 | Err(e) => { log!("Could not retrieve msgid: {}", e); return; }
107 | };
108 |
109 | match unsafe { libc::msgctl(msgid, libc::IPC_RMID, std::ptr::null_mut()) }
110 | {
111 | -1 => log!("Could not close existing message queue"),
112 | _ => log!("Closed existing message queue"),
113 | } // match
114 | } // close }}}
115 |
116 | } // impl Ipc
117 |
118 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
119 |
--------------------------------------------------------------------------------
/gui/wizard/src/lib/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod ipc;
2 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/compress.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | // Gui
4 | use fltk::
5 | {
6 | prelude::*,
7 | app::Sender,
8 | menu,
9 | };
10 |
11 | use shared::fltk::SenderExt;
12 | use shared::std::PathBufExt;
13 |
14 | use crate::dimm;
15 | use crate::gameimage;
16 | use crate::frame;
17 | use crate::common;
18 | use crate::log;
19 | use crate::log_alert;
20 | use crate::log_err;
21 | use shared::{column,row,fixed};
22 |
23 | // fn compress_next() {{{
24 | pub fn compress_next(tx: Sender, term: frame::term::Term)
25 | {
26 | tx.send_awake(common::Msg::WindDeactivate);
27 | let backend = match gameimage::gameimage::binary()
28 | {
29 | Ok(backend) => backend,
30 | Err(e) => { log_alert!("Error to execute backend: {}", e); return; }
31 | };
32 | let mut term = term.clone();
33 | std::thread::spawn(move ||
34 | {
35 | let handle = term.dispatch(vec![&backend.string(), r#"{ "op": "compress" }"#], |_| {});
36 | match handle
37 | {
38 | Ok(handle) => log_err!(handle.lock().unwrap().wait().map(|_|{})),
39 | Err(e) => log!("{}", e),
40 | };
41 | tx.send_activate(common::Msg::DrawCreator);
42 | });
43 |
44 | } // fn compress_next() }}}
45 |
46 | // pub fn compress() {{{
47 | pub fn compress(tx: Sender
48 | , title: &str
49 | , msg_prev: common::Msg
50 | , _msg_curr: common::Msg
51 | , _msg_next: common::Msg)
52 | {
53 | let ui = crate::GUI.lock().unwrap().ui.clone()(title);
54 | // Layout
55 | column!(col,
56 | let term = frame::term::Term::default();
57 | col.add(&term.group);
58 | row!(row,
59 | fixed!(row, btn_level, menu::MenuButton::default(), dimm::width_button_wide());
60 | row.add(&fltk::frame::Frame::default()
61 | .with_align(fltk::enums::Align::Inside | fltk::enums::Align::Center)
62 | .with_label("Select the compression level before clicking on start")
63 | );
64 | );
65 | col.fixed(&row, dimm::height_button_wide());
66 | );
67 |
68 | // Configure buttons
69 | ui.btn_prev.clone().emit(tx.clone(), msg_prev);
70 | let mut btn_next = ui.btn_next.clone();
71 | btn_next.set_label("Start");
72 | btn_next.set_callback(move |_| { compress_next(tx, term.clone()); });
73 |
74 | // Open space for compress level button
75 | let mut btn_level = btn_level.clone();
76 | btn_level.set_callback(|e|
77 | {
78 | let str_level = e.choice().unwrap_or(String::from("7"));
79 | log!("Set compression level to {}", str_level);
80 | env::set_var("FIM_COMPRESSION_LEVEL", &str_level);
81 | e.set_value(e.value());
82 | e.set_label(&str_level);
83 | });
84 | for i in 0..11
85 | {
86 | btn_level.add_choice(&i.to_string());
87 | } // for
88 | env::set_var("FIM_COMPRESSION_LEVEL", "7");
89 | btn_level.set_value(8);
90 | btn_level.set_label("7");
91 | } // fn compress() }}}
92 |
93 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
94 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/install.rs:
--------------------------------------------------------------------------------
1 | // Gui
2 | use fltk::prelude::*;
3 | use fltk::{
4 | app::Sender,
5 | browser::MultiBrowser,
6 | dialog,
7 | enums::{FrameType,Color},
8 | };
9 |
10 | use shared::fltk::WidgetExtExtra;
11 | use shared::fltk::SenderExt;
12 | use shared::{hover_blink,column,row,add,fixed};
13 |
14 | use crate::dimm;
15 | use crate::common;
16 | use shared::std::PathBufExt;
17 | use crate::log_status;
18 | use crate::gameimage;
19 |
20 | // pub fn install() {{{
21 | pub fn install(tx: Sender
22 | , title: &str
23 | , label: &str
24 | , msg_prev: common::Msg
25 | , msg_curr: common::Msg
26 | , msg_next: common::Msg) -> crate::Ui
27 | {
28 | let ui = crate::GUI.lock().unwrap().ui.clone()(title);
29 |
30 | // Set previous frame
31 | ui.btn_prev.clone().emit(tx.clone(), msg_prev);
32 | ui.btn_next.clone().emit(tx.clone(), msg_next);
33 | // Layout
34 | column!(col,
35 | row!(row,
36 | add!(row, list, MultiBrowser::default());
37 | column!(col_buttons,
38 | fixed!(col_buttons, btn_add, shared::fltk::button::rect::add(), dimm::height_button_rec());
39 | fixed!(col_buttons, btn_del, shared::fltk::button::rect::del(), dimm::height_button_rec());
40 | col_buttons.add(&fltk::frame::Frame::default());
41 | );
42 | row.fixed(&col_buttons, dimm::width_button_rec());
43 | );
44 | );
45 | // Buttons
46 | hover_blink!(btn_add);
47 | hover_blink!(btn_del);
48 | // List of the currently installed items
49 | let mut list = list.clone();
50 | list.set_frame(FrameType::BorderBox);
51 | list.set_text_size(dimm::height_text());
52 | // Insert items in list of currently installed items
53 | match gameimage::search::search_local(label)
54 | {
55 | Ok(vec_items) => for item in vec_items { list.add(&item.string()); },
56 | Err(e) => log_status!("Could not get items to insert: {}", e),
57 | }; // match
58 | // Add new item
59 | let clone_tx = tx.clone();
60 | let clone_label : String = label.to_string();
61 | let _ = btn_add.clone()
62 | .with_color(Color::Green)
63 | .with_callback(move |_|
64 | {
65 | // Pick files to install
66 | let mut chooser = dialog::FileChooser::new("."
67 | , "*"
68 | , dialog::FileChooserType::Multi
69 | , "Pick one or multiple files");
70 | // Start dialog
71 | chooser.show();
72 | // Wait for choice(s)
73 | while chooser.shown() { fltk::app::wait(); } // while
74 | // Check if choice is valid
75 | if chooser.value(1).is_none()
76 | {
77 | log_status!("No file selected");
78 | return;
79 | } // if
80 | // Install files
81 | clone_tx.send_awake(common::Msg::WindDeactivate);
82 | let count = chooser.count()+1;
83 | let clone_label = clone_label.clone();
84 | let mut vec_entries : Vec = vec![];
85 | (1..count).into_iter().for_each(|idx| { vec_entries.push(chooser.value(idx).unwrap()); });
86 | std::thread::spawn(move ||
87 | {
88 | match gameimage::install::install(&clone_label, vec_entries.clone())
89 | {
90 | Ok(_) => log_status!("Installed selected files"),
91 | Err(e) => log_status!("Failed to install files: {}", e),
92 | }; // match
93 | clone_tx.send_activate(msg_curr);
94 | });
95 | });
96 | // Erase package
97 | let mut btn_del = btn_del.clone()
98 | .with_color(Color::Red);
99 | let mut clone_output_status = ui.status.clone();
100 | let clone_label = label.to_string();
101 | let clone_frame_list = list.clone();
102 | let clone_tx = tx.clone();
103 | btn_del.set_callback(move |_|
104 | {
105 | clone_tx.send_awake(common::Msg::WindDeactivate);
106 | let vec_indices = clone_frame_list.selected_items();
107 | if vec_indices.len() == 0
108 | {
109 | clone_output_status.set_value("No item selected for deletion");
110 | clone_tx.send_awake(common::Msg::WindActivate);
111 | return;
112 | } // if
113 | // Remove
114 | let clone_tx = clone_tx.clone();
115 | let clone_label = clone_label.clone();
116 | let clone_frame_list = clone_frame_list.clone();
117 | std::thread::spawn(move ||
118 | {
119 | // Get items
120 | let vec_items : Vec = vec_indices.into_iter().map(|e|{ clone_frame_list.text(e).unwrap() }).collect();
121 | // Run backend
122 | match gameimage::install::remove(&clone_label, vec_items.clone())
123 | {
124 | Ok(_) => log_status!("Successfully removed files"),
125 | Err(e) => log_status!("Failed to remove files: {}", e),
126 | }; // match
127 | // Redraw GUI
128 | clone_tx.send_activate(msg_curr);
129 | }); // std::thread
130 | }); // set_callback
131 | ui
132 | }
133 | // }}}
134 |
135 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
136 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/mod.rs:
--------------------------------------------------------------------------------
1 | mod name;
2 | mod install;
3 | mod compress;
4 | mod test;
5 | pub mod linux;
6 | pub mod wine;
7 | pub mod retroarch;
8 | pub mod pcsx2;
9 | pub mod rpcs3;
10 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/name.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | // Gui
4 | use fltk::prelude::*;
5 | use fltk::{
6 | app::Sender,
7 | input::Input,
8 | frame::Frame,
9 | enums::{Align,FrameType},
10 | };
11 |
12 | use anyhow::anyhow as ah;
13 |
14 | use shared::fltk::SenderExt;
15 | use shared::svg;
16 | use shared::{column,row,fixed};
17 |
18 | use crate::dimm;
19 | use crate::frame;
20 | use crate::common;
21 | use crate::log_status;
22 | use crate::gameimage;
23 |
24 | // fn name_next() {{{
25 | fn name_next() -> anyhow::Result<()>
26 | {
27 | // Check for name
28 | let name = match env::var("GIMG_NAME")
29 | {
30 | Ok(name) => name,
31 | Err(e) => return Err(ah!("Could not fetch GIMG_NAME: {}", e)),
32 | };
33 | // Check for platform
34 | let platform = match frame::platform::PLATFORM.lock()
35 | {
36 | Ok(guard) => match guard.clone()
37 | {
38 | Some(platform) => platform,
39 | None => return Err(ah!("No platform selected")),
40 | },
41 | Err(e) => return Err(ah!("Could not lock platform: {}", e)),
42 | };
43 | // Init project
44 | match gameimage::init::project(name, platform.as_str().to_string())
45 | {
46 | Ok(_) => (),
47 | Err(e) => return Err(ah!("Could not init project: {}", e)),
48 | } // match
49 |
50 | Ok(())
51 | } // fn name_next() }}}
52 |
53 | // pub fn name() {{{
54 | pub fn name(tx: Sender
55 | , title: &str
56 | , msg_prev: common::Msg
57 | , msg_next: common::Msg)
58 | {
59 | let ui = crate::GUI.lock().unwrap().ui.clone()(title);
60 | // Layout
61 | column!(col,
62 | col.add(&Frame::default());
63 | row!(row_icon,
64 | row_icon.add(&Frame::default());
65 | fixed!(row_icon, frame_icon, Frame::default(), 225);
66 | row_icon.add(&Frame::default());
67 | );
68 | col.fixed(&row_icon, 150);
69 | col.add(&Frame::default());
70 | fixed!(col, input_name, Input::default(), dimm::height_button_wide());
71 | );
72 | // Configure icon box
73 | let mut frame_icon = frame_icon.clone();
74 | frame_icon.set_frame(FrameType::NoBox);
75 | frame_icon.set_image(Some(fltk::image::SvgImage::from_data(svg::icon_joystick(10.0).as_str()).unwrap()));
76 | // Game name
77 | let mut input_name = input_name.clone()
78 | .with_align(Align::Top | Align::Left);
79 | input_name.set_pos(ui.group.x(), input_name.y() - input_name.h());
80 | let _ = input_name.take_focus();
81 | // Sanitize game name
82 | let f_sanitize = |input : String| -> String
83 | {
84 | input
85 | .chars()
86 | .filter_map(|c|
87 | {
88 | if c.is_alphanumeric() { Some(c) }
89 | else if c == '-' { Some(c) }
90 | else if c == '_' { Some(c) }
91 | else if c == ':' { Some('-') }
92 | else if c == ' ' { Some('-') }
93 | else { None }
94 | })
95 | .collect()
96 | };
97 | // Check if GIMG_NAME exists
98 | let env_name = f_sanitize(env::var("GIMG_NAME").unwrap_or_default());
99 | env::set_var("GIMG_NAME", &env_name);
100 | input_name.set_value(&env_name);
101 | // Set input_name callback
102 | input_name.handle(move |input,ev|
103 | {
104 | if ev == fltk::enums::Event::KeyUp
105 | {
106 | env::set_var("GIMG_NAME", f_sanitize(input.value()));
107 | return true;
108 | } // if
109 | return false;
110 | });
111 | // Callback to previous
112 | ui.btn_prev.clone().emit(tx, msg_prev);
113 | // Callback to Next
114 | let clone_tx = tx.clone();
115 | let clone_msg_next = msg_next.clone();
116 | ui.btn_next.clone().set_callback(move |_|
117 | {
118 | clone_tx.send_awake(common::Msg::WindDeactivate);
119 | std::thread::spawn(move ||
120 | {
121 | match name_next()
122 | {
123 | Ok(()) => tx.send_activate(clone_msg_next),
124 | Err(e) => { clone_tx.send_awake(common::Msg::WindActivate); log_status!("{}", e); }
125 | }
126 | });
127 | });
128 | } // }}}
129 |
130 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
131 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/pcsx2.rs:
--------------------------------------------------------------------------------
1 | use fltk::app::Sender;
2 |
3 | use crate::common;
4 | use crate::frame;
5 | use crate::wizard;
6 |
7 | // pub fn name() {{{
8 | pub fn name(tx: Sender, title: &str)
9 | {
10 | wizard::name::name(tx.clone()
11 | , title
12 | , common::Msg::DrawPlatform
13 | , common::Msg::DrawPcsx2Icon);
14 | } // }}}
15 |
16 | // pub fn icon() {{{
17 | pub fn icon(tx: Sender, title: &str)
18 | {
19 | frame::icon::project(tx.clone()
20 | , title
21 | , common::Msg::DrawPcsx2Name
22 | , common::Msg::DrawPcsx2Icon
23 | , common::Msg::DrawPcsx2Rom
24 | );
25 | } // }}}
26 |
27 | // pub fn rom() {{{
28 | pub fn rom(tx: Sender, title: &str)
29 | {
30 | wizard::install::install(tx.clone()
31 | , title
32 | , "rom"
33 | , common::Msg::DrawPcsx2Icon
34 | , common::Msg::DrawPcsx2Rom
35 | , common::Msg::DrawPcsx2Bios);
36 | } // }}}
37 |
38 | // pub fn bios() {{{
39 | pub fn bios(tx: Sender, title: &str)
40 | {
41 | wizard::install::install(tx.clone()
42 | , title
43 | , "bios"
44 | , common::Msg::DrawPcsx2Rom
45 | , common::Msg::DrawPcsx2Bios
46 | , common::Msg::DrawPcsx2Test);
47 | } // }}}
48 |
49 | // pub fn test() {{{
50 | pub fn test(tx: Sender, title: &str)
51 | {
52 | wizard::test::test(tx.clone()
53 | , title
54 | , common::Msg::DrawPcsx2Bios
55 | , common::Msg::DrawPcsx2Test
56 | , common::Msg::DrawPcsx2Compress);
57 | } // }}}
58 |
59 | // pub fn compress() {{{
60 | pub fn compress(tx: Sender, title: &str)
61 | {
62 | wizard::compress::compress(tx.clone()
63 | , title
64 | , common::Msg::DrawPcsx2Test
65 | , common::Msg::DrawPcsx2Compress
66 | , common::Msg::DrawCreator);
67 | } // }}}
68 |
69 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
70 |
--------------------------------------------------------------------------------
/gui/wizard/src/wizard/test.rs:
--------------------------------------------------------------------------------
1 | // Gui
2 | use fltk::prelude::*;
3 | use fltk::{
4 | app::Sender,
5 | enums::Color,
6 | };
7 |
8 | use serde_json::json;
9 |
10 | use shared::fltk::SenderExt;
11 | use shared::fltk::WidgetExtExtra;
12 | use shared::std::PathBufExt;
13 | use shared::{column,row,fixed,hover_blink};
14 |
15 | use crate::gameimage;
16 | use crate::dimm;
17 | use crate::frame;
18 | use crate::common;
19 | use crate::log_alert;
20 |
21 | // pub fn test() {{{
22 | pub fn test(tx: Sender
23 | , title: &str
24 | , msg_prev: common::Msg
25 | , _msg_curr: common::Msg
26 | , msg_next: common::Msg)
27 | {
28 | let ui = crate::GUI.lock().unwrap().ui.clone()(title);
29 | // Layout
30 | column!(col,
31 | let term = frame::term::Term::default();
32 | row!(row,
33 | row.add(&fltk::frame::Frame::default());
34 | fixed!(row, btn_test, shared::fltk::button::wide::default(), dimm::width_button_wide());
35 | row.add(&fltk::frame::Frame::default());
36 | );
37 | col.fixed(&row, dimm::height_button_wide());
38 | );
39 | // Cofigure buttons
40 | hover_blink!(btn_test);
41 | ui.btn_prev.clone().emit(tx.clone(), msg_prev);
42 | ui.btn_next.clone().emit(tx.clone(), msg_next);
43 |
44 | // Add a 'test' button
45 | let clone_tx = tx.clone();
46 | let mut term = term.clone();
47 | let mut btn_test = btn_test.clone()
48 | .with_label("Test")
49 | .with_color(Color::Green);
50 | btn_test.set_callback(move |_|
51 | {
52 | clone_tx.send_awake(common::Msg::WindDeactivate);
53 | let backend = match gameimage::gameimage::binary()
54 | {
55 | Ok(backend) => backend,
56 | Err(e) => { log_alert!("Error to execute backend: {}", e); return; }
57 | };
58 | let mut json_args = json!({});
59 | json_args["op"] = "test".into();
60 | let _ = term.dispatch(vec![&backend.string(), &json_args.to_string()], move |_|
61 | {
62 | clone_tx.send_awake(common::Msg::WindActivate);
63 | });
64 | });
65 | } // fn test() }}}
66 |
67 | // vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :
68 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | ######################################################################
2 | # @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | # @file : CMakeLists
4 | ######################################################################
5 |
6 | cmake_minimum_required(VERSION 3.5)
7 |
8 | project(GameImage DESCRIPTION "GameImage - FlatImage Game Packer" LANGUAGES CXX)
9 |
10 | # Compiler
11 | message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
12 | add_definitions("--std=c++23 -Wall -Wextra")
13 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
14 | message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")
15 | message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
16 | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
17 |
18 | # Tools
19 | ## Git
20 | find_program(GIT_COMMAND git)
21 | if(NOT GIT_COMMAND)
22 | message(FATAL_ERROR "git command not found")
23 | endif()
24 |
25 | # External libraries
26 | find_package(fmt REQUIRED)
27 | find_package(cpr REQUIRED)
28 | find_package(argparse REQUIRED)
29 | find_package(nlohmann_json REQUIRED)
30 | find_package(matchit REQUIRED)
31 | find_package(magic_enum REQUIRED)
32 | find_package(Boost REQUIRED)
33 | find_package(easyloggingpp REQUIRED)
34 | find_package(libjpeg-turbo REQUIRED)
35 | find_package(PNG REQUIRED)
36 | find_package(ZLIB REQUIRED)
37 | find_package(cppcoro REQUIRED)
38 | find_package(libzippp REQUIRED)
39 | find_package(cryptopp REQUIRED)
40 | find_package(LibArchive REQUIRED)
41 | find_package(Boost COMPONENTS filesystem REQUIRED)
42 | INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
43 |
44 | # Main executable
45 | add_executable(main main.cpp)
46 | target_link_libraries(main PRIVATE
47 | fmt::fmt
48 | argparse::argparse
49 | cpr::cpr
50 | nlohmann_json::nlohmann_json
51 | matchit::matchit
52 | magic_enum::magic_enum
53 | easyloggingpp::easyloggingpp
54 | libjpeg-turbo::libjpeg-turbo
55 | PNG::PNG
56 | /usr/lib/libboost_filesystem.a
57 | ZLIB::ZLIB
58 | cppcoro::cppcoro
59 | libzippp::libzippp
60 | cryptopp::cryptopp
61 | LibArchive::LibArchive
62 | )
63 | target_include_directories(main PRIVATE ${fmt_INCLUDE_DIRS})
64 |
65 | # Boot executables for each platform
66 | add_executable(boot boot/boot.cpp)
67 | target_link_libraries(boot PRIVATE
68 | fmt::fmt
69 | argparse::argparse
70 | cpr::cpr
71 | nlohmann_json::nlohmann_json
72 | matchit::matchit
73 | magic_enum::magic_enum
74 | easyloggingpp::easyloggingpp
75 | libjpeg-turbo::libjpeg-turbo
76 | PNG::PNG
77 | /usr/lib/libboost_filesystem.a
78 | ZLIB::ZLIB
79 | cppcoro::cppcoro
80 | )
81 | target_include_directories(boot PRIVATE ${fmt_INCLUDE_DIRS})
82 |
--------------------------------------------------------------------------------
/src/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Enable community repo
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install deps
9 | RUN apk update && apk upgrade
10 | RUN apk add --no-cache build-base git libbsd-dev py3-pip pipx git patchelf cmake gcc \
11 | bash e2fsprogs xz curl zstd gawk debootstrap m4 gcompat nasm
12 |
13 | # Install conan
14 | RUN pipx install conan
15 |
16 | # Update PATH
17 | ENV PATH="/root/.local/bin:$PATH"
18 |
19 | # Setup
20 | RUN conan profile detect --force
21 |
22 | # Copy files
23 | RUN mkdir /gameimage
24 | COPY . /gameimage/
25 |
26 | # Set workdir
27 | WORKDIR /gameimage
28 |
29 | # Compile
30 | RUN conan install . --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build || true
31 | # nasm bug when on musl system "undefined symbol"
32 | RUN cp "$(command -v nasm)" /root/.conan2/p/nasmc*/p/bin/nasm
33 | RUN conan install . --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build || true
34 | RUN cmake --preset conan-release -DCMAKE_BUILD_TYPE=Release
35 | RUN cmake --build --preset conan-release
36 |
37 | # Build image
38 | # RUN cp dist/main bin/elf
39 | # RUN ./src/scripts/_build.sh debootstrap focal
40 | # RUN ./src/scripts/_build.sh archbootstrap
41 | # RUN ./src/scripts/_build.sh alpinebootstrap
42 |
--------------------------------------------------------------------------------
/src/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
4 |
5 | # export HOME="$SCRIPT_DIR"
6 |
7 | conan install . --build=missing -g CMakeDeps -g CMakeToolchain -s build_type=Debug
8 | cmake --preset conan-debug -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1
9 | cmake --build --preset conan-debug
10 |
--------------------------------------------------------------------------------
/src/cmd/compress.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : compress
4 | ///
5 |
6 | #pragma once
7 |
8 | #include "../enum.hpp"
9 |
10 | #include "../std/filesystem.hpp"
11 |
12 | #include "../lib/db/build.hpp"
13 | #include "../lib/db/project.hpp"
14 | #include "../lib/subprocess.hpp"
15 |
16 | namespace ns_compress
17 | {
18 |
19 | namespace fs = std::filesystem;
20 |
21 | // validate() {{{
22 | inline void validate(ns_db::ns_build::Metadata& db_metadata, ns_db::ns_project::Project& db_project)
23 | {
24 | // Icon
25 | "Icon is not installed"_try([&]
26 | {
27 | fs::path path_file_icon = db_metadata.path_dir_project / db_project.path_file_icon;
28 | ns_fs::ns_path::file_exists(path_file_icon);
29 | ns_log::write('i', "Found icon '", path_file_icon, "'");
30 | });
31 |
32 |
33 | auto f_validate_file_or_directory = [&](ns_enum::Op const& op)
34 | {
35 | fs::path path_file = db_metadata.path_dir_project / db_project.find_file(op);
36 | try
37 | {
38 | ns_fs::ns_path::file_exists(path_file);
39 | } // try
40 | catch(std::exception const& e)
41 | {
42 | ns_fs::ns_path::dir_exists(path_file);
43 | } // catch
44 | };
45 |
46 | auto f_validate_files = [&](ns_enum::Op const& op)
47 | {
48 | std::vector paths_file = db_project.find_files(op);
49 |
50 | for(auto&& path_file : paths_file)
51 | {
52 | "Missing file {} in json for '{}'"_try([&]
53 | {
54 | try
55 | {
56 | ns_fs::ns_path::file_exists(db_metadata.path_dir_project / path_file);
57 | } // try
58 | catch(std::exception const& e)
59 | {
60 | ns_fs::ns_path::dir_exists(db_metadata.path_dir_project / path_file);
61 | } // catch
62 | }, ns_enum::to_string(op), path_file);
63 | } // for
64 | };
65 |
66 | switch(db_metadata.platform)
67 | {
68 | case ns_enum::Platform::LINUX:
69 | case ns_enum::Platform::WINE:
70 | {
71 | f_validate_file_or_directory(ns_enum::Op::ROM);
72 | }
73 | break;
74 | case ns_enum::Platform::RETROARCH:
75 | {
76 | // default rom
77 | f_validate_file_or_directory(ns_enum::Op::ROM);
78 | // default core
79 | f_validate_file_or_directory(ns_enum::Op::CORE);
80 | // all roms
81 | f_validate_files(ns_enum::Op::ROM);
82 | // all cores
83 | f_validate_files(ns_enum::Op::CORE);
84 | } // case
85 | break;
86 | case ns_enum::Platform::PCSX2:
87 | {
88 | // default rom
89 | f_validate_file_or_directory(ns_enum::Op::ROM);
90 | // default bios
91 | f_validate_file_or_directory(ns_enum::Op::BIOS);
92 | // all roms
93 | f_validate_files(ns_enum::Op::ROM);
94 | // all bios
95 | f_validate_files(ns_enum::Op::BIOS);
96 | } // case
97 | break;
98 | case ns_enum::Platform::RPCS3:
99 | {
100 | // default rom
101 | f_validate_file_or_directory(ns_enum::Op::ROM);
102 | } // case
103 | break;
104 | } // switch
105 |
106 | } // validate() }}}
107 |
108 | // compress() {{{
109 | inline decltype(auto) compress()
110 | {
111 | // Open databases
112 | auto db_build = ns_db::ns_build::read();
113 | ethrow_if(not db_build, "Could not open build database");
114 | auto db_metadata = db_build->find(db_build->project);
115 | auto db_project = ns_db::ns_project::read();
116 | ethrow_if(not db_project, "Could not open project database");
117 |
118 | // Validate package by platform
119 | validate(db_metadata, *db_project);
120 |
121 | // Output file
122 | fs::path path_file_layer{db_metadata.path_dir_project_root.string() + ".layer"};
123 |
124 | // Erase if exists
125 | lec(fs::remove, path_file_layer);
126 |
127 | // Log
128 | ns_log::write('i', "project: ", db_metadata.name);
129 | ns_log::write('i', "image: ", db_build->path_file_image);
130 | ns_log::write('i', "layer: ", path_file_layer);
131 |
132 | // Execute portal
133 | auto f_portal = [](Args&&... args)
134 | {
135 | std::ignore = ns_subprocess::Subprocess("/fim/static/fim_portal")
136 | .with_piped_outputs()
137 | .with_args(std::forward(args)...)
138 | .spawn()
139 | .wait();
140 | };
141 |
142 | // Compress
143 | f_portal(db_build->path_file_image , "fim-layer" , "create" , db_metadata.path_dir_project_root , path_file_layer);
144 |
145 | ns_log::write('i', "Wrote file to '", path_file_layer, "'");
146 | } // compress() }}}
147 |
148 | } // namespace ns_compress
149 |
150 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
151 |
--------------------------------------------------------------------------------
/src/cmd/desktop.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : desktop
4 | ///
5 |
6 | #pragma once
7 |
8 | #include "../lib/image.hpp"
9 | #include "../lib/db/build.hpp"
10 | #include "../lib/subprocess.hpp"
11 | #include "../std/vector.hpp"
12 |
13 | namespace ns_desktop
14 | {
15 |
16 | enum class Op
17 | {
18 | ICON,
19 | SETUP,
20 | };
21 |
22 | namespace
23 | {
24 |
25 | namespace fs = std::filesystem;
26 |
27 | }
28 |
29 | enum class IntegrationItems
30 | {
31 | MIMETYPE,
32 | ENTRY,
33 | ICON
34 | };
35 |
36 |
37 | // icon() {{{
38 | inline void icon(fs::path const& path_file_icon)
39 | {
40 | // Open databases
41 | auto db_build = ns_db::ns_build::read();
42 | ethrow_if(not db_build, "Could not open build database");
43 | auto db_metadata = db_build->find(db_build->project);
44 |
45 | // Create icon path
46 | fs::path path_file_icon_dst = db_build->path_dir_build / "desktop.png";
47 |
48 | // Resize icon to specified icon path
49 | ns_image::resize(path_file_icon, path_file_icon_dst, 300, 450);
50 | } // icon() }}}
51 |
52 | // desktop() {{{
53 | inline void desktop(std::string str_name, std::vector vec_items)
54 | {
55 | // Open databases
56 | auto db_build = ns_db::ns_build::read();
57 | ethrow_if(not db_build, "Could not open build database");
58 | auto db_metadata = db_build->find(db_build->project);
59 |
60 | // Check if output file exists
61 | db_build->path_file_output = ns_fs::ns_path::file_exists(db_build->path_file_output)._ret;
62 |
63 | // Path to project
64 | fs::path path_dir_build = db_build->path_dir_build;
65 | fs::path path_file_desktop = path_dir_build / "desktop.json";
66 | fs::path path_file_icon = ns_fs::ns_path::file_exists(path_dir_build / "desktop.png")._ret;
67 |
68 | // Create application data
69 | std::ignore = ns_db::from_file(path_file_desktop
70 | , [&](auto&& db)
71 | {
72 | db("name") = str_name;
73 | db("icon") = path_file_icon;
74 | db("categories") = std::vector{"Game"};
75 | }, ns_db::Mode::CREATE);
76 |
77 | // Apply application data
78 | std::ignore = ns_subprocess::Subprocess("/fim/static/fim_portal")
79 | .with_piped_outputs()
80 | .with_args(db_build->path_file_output, "fim-desktop", "setup", path_file_desktop)
81 | .spawn()
82 | .wait();
83 |
84 | // Enable desktop integration
85 | std::ignore = ns_subprocess::Subprocess("/fim/static/fim_portal")
86 | .with_piped_outputs()
87 | .with_args(db_build->path_file_output
88 | , "fim-desktop"
89 | , "enable"
90 | , ns_string::from_container(vec_items , ',', [](auto&& e){ return ns_enum::to_string(e); }))
91 | .spawn()
92 | .wait();
93 | } // desktop() }}}
94 |
95 | } // namespace ns_test
96 |
97 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
98 |
--------------------------------------------------------------------------------
/src/cmd/fetch/check.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : check
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 |
10 | #include "../../lib/log.hpp"
11 | #include "../../lib/sha.hpp"
12 |
13 | namespace ns_fetch::ns_check
14 | {
15 |
16 | namespace
17 | {
18 |
19 | namespace fs = std::filesystem;
20 |
21 | // check_file_from_sha() {{{
22 | inline bool check_file_from_sha(fs::path const& path_file_src
23 | , fs::path const& path_file_sha
24 | , ns_sha::SHA_TYPE const& sha_type)
25 | {
26 | return ns_sha::check_sha(path_file_src, path_file_sha, sha_type);
27 | } // }}}
28 |
29 | // check_file_from_url_impl() {{{
30 | inline bool check_file_from_url_impl(fs::path const& path_file_src, cpr::Url const& url)
31 | {
32 | uintmax_t size_reference = fs::file_size(path_file_src);
33 | uintmax_t size_calculated = 0;
34 |
35 | ns_log::write('i', "SIZE: Reference is ", size_reference);
36 |
37 | // Get size of file to download
38 | cpr::Response response_head = cpr::Head(url);
39 | if ( response_head.status_code != 200 )
40 | {
41 | ns_log::write('e', "Could not fetch remote size to compare local size with");
42 | return false;
43 | } // if
44 |
45 | auto it = response_head.header.find("Content-Length");
46 | if (it == response_head.header.end())
47 | {
48 | ns_log::write('e', "Could not find field 'Content-Length' in response");
49 | return false;
50 | } // if
51 |
52 | size_calculated = std::stoi(it->second);
53 | ns_log::write('i', "SIZE: Calculated is ", size_calculated);
54 |
55 | if ( size_reference != size_calculated )
56 | {
57 | ns_log::write('e', "Size reference differs from size_calculated");
58 | return false;
59 | } // if
60 |
61 | return true;
62 | } // check_file_from_url_impl() }}}
63 |
64 | // check_file_from_url() {{{
65 | inline bool check_file_from_url(fs::path const& path_file_src, cpr::Url const& url)
66 | {
67 | auto expected_check = ns_exception::to_expected([&]{ return check_file_from_url_impl(path_file_src, url); });
68 | ereturn_if(not expected_check, expected_check.error(), false);
69 | return *expected_check;
70 | } // check_file_from_url() }}}
71 |
72 | } // namespace
73 |
74 | // check_file() {{{
75 | inline bool check_file(fs::path const& path_file_src
76 | , fs::path const& path_file_sha
77 | , ns_sha::SHA_TYPE const& sha_type
78 | , cpr::Url const& url)
79 | {
80 | return check_file_from_sha(path_file_src, path_file_sha, sha_type) or check_file_from_url(path_file_src, url);
81 | } // check_file() }}}
82 |
83 | } // namespace ns_fetch::ns_check
84 |
85 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
86 |
--------------------------------------------------------------------------------
/src/cmd/init.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : init
4 | // @created : Friday Jan 19, 2024 19:13:00 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 |
11 | #include "project.hpp"
12 | #include "../macro.hpp"
13 | #include "../enum.hpp"
14 |
15 | #include "../std/filesystem.hpp"
16 | #include "../std/env.hpp"
17 |
18 | #include "../lib/log.hpp"
19 | #include "../lib/subprocess.hpp"
20 | #include "../lib/db/build.hpp"
21 | #include "../lib/db/project.hpp"
22 |
23 | //
24 | // Initializes a new directory configuration for gameimage
25 | //
26 | namespace ns_init
27 | {
28 |
29 | namespace fs = std::filesystem;
30 |
31 | // build() {{{
32 | inline void build(fs::path path_dir_build)
33 | {
34 | // Log
35 | ns_log::write('i', "path_dir_build: ", path_dir_build);
36 | // Initialize projects data
37 | elogerror(ns_db::ns_build::init(path_dir_build));
38 | } // function: build }}}
39 |
40 | // project() {{{
41 | inline void project(std::string const& str_name, ns_enum::Platform const& platform)
42 | {
43 | // Read build database
44 | auto db_build = ns_db::ns_build::read();
45 | ethrow_if(not db_build, "Could not read build database");
46 | // Create project dir and return an absolute path to it
47 | fs::path path_dir_project_root = ns_fs::ns_path::dir_create(db_build->path_dir_build / str_name)._ret;
48 | // The actual project files are nested in /opt/gameimage-games, because that's the final path
49 | // inside the container
50 | fs::path path_dir_project = ns_fs::ns_path::dir_create(db_build->path_dir_build / str_name / "opt" / "gameimage-games" / str_name)._ret;
51 | // Log
52 | ns_log::write('i', "platform :", ns_enum::to_string_lower(platform));
53 | ns_log::write('i', "image :", db_build->path_file_image);
54 | ns_log::write('i', "path_dir_project_root :", path_dir_project_root);
55 | ns_log::write('i', "path_dir_project :", path_dir_project);
56 | // Create novel metadata for project
57 | db_build->projects.push_back(ns_db::ns_build::Metadata
58 | {
59 | .name = str_name
60 | , .path_dir_project = path_dir_project
61 | , .path_dir_project_root = path_dir_project_root
62 | , .platform = platform
63 | });
64 | // Write changes to database
65 | ns_db::ns_build::write(*db_build);
66 | // Set project as default
67 | ns_project::set(str_name);
68 | // Copy boot file for platform
69 | auto path_file_boot = ns_subprocess::search_path("gameimage-boot");
70 | ethrow_if(not path_file_boot, "Could not find gameimage-boot in PATH");
71 | lec(fs::copy_file
72 | , *path_file_boot
73 | , path_dir_project / "boot"
74 | , fs::copy_options::overwrite_existing
75 | );
76 | ns_log::write('i', "Copy ", *path_file_boot, " -> ", path_dir_project / "boot");
77 | // Create project database
78 | elogerror(ns_db::ns_project::init(path_dir_project, platform));
79 | } // function: project }}}
80 |
81 | } // namespace ns_init
82 |
83 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
84 |
--------------------------------------------------------------------------------
/src/cmd/project.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | ///@author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | ///@file : project
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 |
10 | #include "../lib/db/build.hpp"
11 |
12 | namespace ns_project
13 | {
14 |
15 | namespace
16 | {
17 |
18 | namespace fs = std::filesystem;
19 |
20 | } // namespace
21 |
22 | // set() {{{
23 | [[nodiscard]] inline std::expected set(std::string const& name) noexcept
24 | {
25 | // Open db
26 | auto db_build = ns_db::ns_build::read();
27 | qreturn_if(not db_build, std::unexpected("Could not open build database"));
28 | // Check if 'name' is present in db
29 | auto const& opt_metadata = ns_exception::to_expected([&]{ return db_build->find(name); });
30 | qreturn_if(not opt_metadata, std::unexpected("Could not find project '{}'"_fmt(name)));
31 | // Set as current
32 | db_build->project = opt_metadata->name;
33 | // Update database
34 | ns_db::ns_build::write(*db_build);
35 | ns_log::write('i', "Set default project to: ", name);
36 | return {};
37 | } // set() }}}
38 |
39 | // del() {{{
40 | [[nodiscard]] inline std::expected del(std::string_view str_name) noexcept
41 | {
42 | // Open build db
43 | auto db_build = ns_db::ns_build::read();
44 | qreturn_if(not db_build, std::unexpected("Could not open build database"));
45 | // Find project
46 | auto it = std::ranges::find_if(db_build->projects
47 | , [&](auto&& e){ return e.name == str_name; }
48 | );
49 | qreturn_if(it == std::ranges::end(db_build->projects)
50 | , std::unexpected("Project '{}' not found to delete"_fmt(str_name))
51 | );
52 | // Erase project files from disk
53 | lec(fs::remove_all, it->path_dir_project_root);
54 | lec(fs::remove, it->path_dir_project_root.string() + ".layer");
55 | // Erase project from database
56 | db_build->projects.erase(it);
57 | // If is current project, blank it out
58 | if ( db_build->project == str_name ) { db_build->project = ""; } // if
59 | ns_db::ns_build::write(*db_build);
60 | return {};
61 | } // del() }}}
62 |
63 | } // namespace ns_project
64 |
65 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
66 |
--------------------------------------------------------------------------------
/src/cmd/select.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : select
4 | // @created : Monday Feb 05, 2024 15:02:17 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 |
11 | #include
12 |
13 | #include "../common.hpp"
14 | #include "../enum.hpp"
15 |
16 | #include "../std/filesystem.hpp"
17 |
18 | #include "../lib/db/build.hpp"
19 | #include "../lib/db/project.hpp"
20 |
21 | namespace ns_select
22 | {
23 |
24 | namespace fs = std::filesystem;
25 |
26 | using Op = ns_enum::Op;
27 |
28 | // by_op() {{{
29 | inline void by_op(ns_enum::Platform enum_platform
30 | , Op op
31 | , fs::path path_dir_project
32 | , fs::path path_file_target)
33 | {
34 | switch(enum_platform)
35 | {
36 | case ns_enum::Platform::LINUX:
37 | {
38 | "Only the rom option is available for linux"_throw_if([&]{ return op != Op::ROM; });
39 | } // case
40 | break;
41 | case ns_enum::Platform::WINE:
42 | {
43 | "Only the rom option is available for wine"_throw_if([&]{ return op != Op::ROM; });
44 | } // case
45 | break;
46 | case ns_enum::Platform::RETROARCH:
47 | {
48 | "Only rom, core and bios options are available for retroarch"_throw_if([&]
49 | {
50 | return op != Op::ROM && op != Op::CORE && op != Op::BIOS;
51 | });
52 | } // case
53 | break;
54 | case ns_enum::Platform::PCSX2:
55 | {
56 | "Only rom and bios options are available for pcsx2"_throw_if([&]
57 | {
58 | return op != Op::ROM && op != Op::BIOS;
59 | });
60 | } // case
61 | break;
62 | case ns_enum::Platform::RPCS3:
63 | {
64 | "Only rom and bios options are available for rpcs3"_throw_if([&]
65 | {
66 | return op != Op::ROM && op != Op::BIOS;
67 | });
68 | } // case
69 | break;
70 | } // switch
71 |
72 | // Check if is regular file or directory
73 | try
74 | {
75 | ns_fs::ns_path::file_exists(path_dir_project / path_file_target);
76 | } // try
77 | catch(std::exception const& e)
78 | {
79 | ns_fs::ns_path::dir_exists(path_dir_project / path_file_target);
80 | } // catch
81 |
82 | auto db_project = ns_db::ns_project::read();
83 | ethrow_if(not db_project, "Could not open project database '{}'"_fmt(db_project.error()));
84 | switch (op)
85 | {
86 | case ns_enum::Op::ROM: db_project->path_file_rom = path_file_target; break;
87 | case ns_enum::Op::BIOS: db_project->path_file_bios = path_file_target; break;
88 | case ns_enum::Op::CORE: db_project->path_file_core = path_file_target; break;
89 | default: throw std::runtime_error("Cannot set default for '{}'"_fmt(ns_enum::to_string(op)));
90 | } // switch
91 | ns_db::ns_project::write(*db_project);
92 | } // select() }}}
93 |
94 | // select() {{{
95 | inline void select(Op const& op, fs::path const& path_file_target)
96 | {
97 | // Open db
98 | auto db_build = ns_db::ns_build::read();
99 | ethrow_if(not db_build, "Could not open build database");
100 | auto db_metadata = db_build->find(db_build->project);
101 | // Select the default file by platform
102 | by_op(db_metadata.platform, op, db_metadata.path_dir_project, path_file_target);
103 | } // }}}
104 |
105 | } // namespace ns_select
106 |
107 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
108 |
--------------------------------------------------------------------------------
/src/cmd/test.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : test
4 | ///
5 |
6 | #pragma once
7 |
8 | #include "../lib/db/build.hpp"
9 | #include "../lib/subprocess.hpp"
10 |
11 | namespace ns_test
12 | {
13 |
14 | // test() {{{
15 | inline decltype(auto) test()
16 | {
17 | // Open db
18 | auto db_build = ns_db::ns_build::read();
19 | ethrow_if(not db_build, "Could not open build database");
20 | auto db_metadata = db_build->find(db_build->project);
21 |
22 | // Start application
23 | std::ignore = ns_subprocess::Subprocess("/fim/static/fim_portal")
24 | .with_piped_outputs()
25 | .with_args(db_build->path_file_image, "fim-exec", db_metadata.path_dir_project / "boot")
26 | .spawn()
27 | .wait();
28 | } // test() }}}
29 |
30 | } // namespace ns_test
31 |
32 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
33 |
--------------------------------------------------------------------------------
/src/common.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : common
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "std/concepts.hpp"
15 | #include "std/string.hpp"
16 |
17 | #include "lib/log.hpp"
18 |
19 | inline const char * GIMG_PATH_JSON_FETCH = "/tmp/gameimage/json";
20 |
21 | namespace std
22 | {
23 |
24 | template
25 | using error = optional;
26 |
27 | namespace ranges
28 | {
29 |
30 | template
31 | void sort_unique(R& r)
32 | {
33 | sort(r);
34 | auto [it_beg,it_end] = std::ranges::unique(r);
35 | r.erase(it_beg, it_end);
36 | }
37 |
38 | }
39 |
40 | } // namespace std
41 |
42 | // macros {{{
43 | #define throw_if(cond, msg) \
44 | if (cond) { throw std::runtime_error(msg); }
45 |
46 | #define return_if(cond, ...) \
47 | if (cond) { return __VA_ARGS__; }
48 |
49 | #define return_if_else(cond, val1, val2) \
50 | if (cond) { return val1; } else { return val2; }
51 |
52 | #define break_if(cond) \
53 | if ( (cond) ) { break; }
54 |
55 | #define continue_if(cond) \
56 | if ( (cond) ) { continue; }
57 |
58 | #define assign_if(cond, var, val) \
59 | if ( cond ) { var = val; }
60 |
61 | #define assign_or_return(val, cond, ret) \
62 | val; if ( not cond ) { return ret; }
63 | // }}}
64 |
65 | // class Exception {{{
66 | class Exception : public std::exception
67 | {
68 | private:
69 | std::string m_msg;
70 |
71 | public:
72 | Exception(std::string const& msg)
73 | : m_msg(msg)
74 | {
75 | } // Exception
76 | const char * what() const noexcept override
77 | {
78 | return m_msg.c_str();
79 | } // what()
80 | }; // class: Exception }}}
81 |
82 | // User defined literals {{{
83 |
84 | // Format strings with user-defined literals
85 | inline decltype(auto) operator ""_fmt(const char* str, size_t)
86 | {
87 | return [str](Args&&... args)
88 | {
89 | return fmt::format(fmt::runtime(str), ns_string::to_string(std::forward(args))... ) ;
90 | };
91 | } //
92 |
93 | // Format strings with user-defined literals
94 | inline decltype(auto) operator ""_throw(const char* str, size_t)
95 | {
96 | return [str](Args&&... args)
97 | {
98 | throw Exception(fmt::format(fmt::runtime(str), ns_string::to_string(std::forward(args))...));
99 | };
100 | }
101 |
102 | // Format strings with user-defined literals, throws if condition is false
103 | inline decltype(auto) operator ""_catch(const char* str, size_t)
104 | {
105 | return [str](F&& f, Args&&... args)
106 | {
107 | try
108 | {
109 | f();
110 | } // try
111 | catch(std::exception const& e)
112 | {
113 | ns_log::write('e', e.what());
114 | ns_log::write('e', fmt::format(fmt::runtime(str), ns_string::to_string(std::forward(args))...));
115 | } // catch
116 | };
117 | }
118 |
119 | // Format strings with user-defined literals, throws if condition is false
120 | inline decltype(auto) operator ""_throw_if(const char* str, size_t)
121 | {
122 | return [str](F&& f, Args&&... args)
123 | {
124 | if ( f() )
125 | {
126 | throw Exception(fmt::format(fmt::runtime(str), ns_string::to_string(std::forward(args))...));
127 | }
128 | };
129 | }
130 |
131 | // Format strings with user-defined literals, throws if condition is false
132 | inline decltype(auto) operator ""_try(const char* str, size_t)
133 | {
134 | return [str](F&& f, Args&&... args)
135 | {
136 | try
137 | {
138 | f();
139 | } // try
140 | catch(std::exception const& e)
141 | {
142 | ns_log::write('e', e.what());
143 | throw Exception(fmt::format(fmt::runtime(str), ns_string::to_string(std::forward(args))...));
144 | } // catch
145 | };
146 | }
147 |
148 | // }}}
149 |
150 | // ns_common {{{
151 |
152 | namespace ns_common
153 | {
154 |
155 | // check_and() {{{
156 | template
157 | constexpr bool check_and(T&& t, Args&&... flags)
158 | {
159 | return static_cast(t) & ( static_cast(flags) & ... );
160 | } // check_and() }}}
161 |
162 | // catch_to_optional() {{{
163 | template
164 | auto catch_to_optional(F&& f, Args&&... args) -> std::optional
165 | {
166 | try
167 | {
168 | auto val = make_optional(f(std::forward(args)...));
169 | ns_log::write('i', "Optional: ", *val);
170 | return val;
171 | } // try
172 | catch(std::exception const& e)
173 | {
174 | ns_log::write('i', "Optional (caught): ", e.what());
175 | return std::nullopt;
176 | } // catch
177 | } // catch_to_optional() }}}
178 |
179 | } // namespace ns_common}}}
180 |
181 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
182 |
--------------------------------------------------------------------------------
/src/conanfile.txt:
--------------------------------------------------------------------------------
1 | [requires]
2 | fmt/10.1.1
3 | argparse/3.0
4 | cpr/1.10.5
5 | nlohmann_json/3.11.3
6 | matchit/1.0.1
7 | magic_enum/0.9.5
8 | easyloggingpp/9.97.1
9 | libjpeg-turbo/3.0.1
10 | libpng/1.6.40
11 | zlib/1.3
12 | andreasbuhr-cppcoro/cci.20230629
13 | libzippp/7.1-1.10.1
14 | cryptopp/8.9.0
15 | libarchive/3.7.2
16 |
17 | [generators]
18 | CMakeDeps
19 | CMakeToolchain
20 |
21 | [layout]
22 | cmake_layout
23 |
--------------------------------------------------------------------------------
/src/enum.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : enum
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "common.hpp"
12 |
13 | #include "std/string.hpp"
14 | #include "std/concepts.hpp"
15 |
16 | namespace magic = magic_enum;
17 |
18 | // namespace ns_enum
19 | namespace ns_enum
20 | {
21 |
22 | // Concepts
23 | template
24 | concept Enum = std::is_enum_v>;
25 |
26 | // enum class IpcQuery {{{
27 | enum class IpcQuery
28 | {
29 | FILES,
30 | URLS,
31 | INSTALLED,
32 | NONE,
33 | }; // }}}
34 |
35 | // enum class ImageFormat {{{
36 | enum class ImageFormat
37 | {
38 | PNG,
39 | JPG,
40 | JPEG,
41 | }; // }}}
42 |
43 | // enum class Platform {{{
44 | enum class Platform
45 | {
46 | LINUX,
47 | WINE,
48 | RETROARCH,
49 | PCSX2,
50 | RPCS3,
51 | }; // enum class Platform }}}
52 |
53 | // enum class Op {{{
54 | enum class Op
55 | {
56 | ICON,
57 | ROM,
58 | LINUX,
59 | CORE,
60 | BIOS,
61 | KEYS,
62 | CONFIG,
63 | DATA,
64 | GUI,
65 | WINE,
66 | WINETRICKS,
67 | DXVK,
68 | VKD3D
69 | }; // enum class Op }}}
70 |
71 | // enum class Stage {{{
72 | enum class Stage
73 | {
74 | FETCH,
75 | INIT,
76 | PROJECT,
77 | INSTALL,
78 | SEARCH,
79 | SELECT,
80 | TEST,
81 | DESKTOP,
82 | COMPRESS,
83 | PACKAGE,
84 | }; // enum class Stage }}}
85 |
86 | // is_enum_entry() {{{
87 | template
88 | inline decltype(auto) is_enum_entry(T&& t)
89 | {
90 | return magic::enum_cast(ns_string::to_string(t), magic::case_insensitive).has_value();
91 | } // }}}
92 |
93 | // from_string() {{{
94 | template
95 | inline decltype(auto) from_string(T&& t)
96 | {
97 | auto opt = magic::enum_cast(ns_string::to_string(t), magic::case_insensitive);
98 |
99 | if ( ! opt ) { "Could not convert enum from '{}'"_throw(t); } // if
100 |
101 | return U{*opt};
102 | } // }}}
103 |
104 | // to_string() {{{
105 | template
106 | inline std::string to_string(T&& t)
107 | {
108 | return std::string(magic::enum_name(std::forward(t)));
109 | } // to_string() }}}
110 |
111 | // to_string_lower() {{{
112 | template
113 | inline std::string to_string_lower(T&& t)
114 | {
115 | return ns_string::to_lower(ns_string::to_string(magic::enum_name(std::forward(t))));
116 | } // to_string_lower() }}}
117 |
118 | // check_and() {{{
119 | template
120 | constexpr bool check_and(T&& t, Args&&... flags)
121 | {
122 | return static_cast(t) & ( static_cast(flags) & ... );
123 | } // check_and() }}}
124 |
125 | } // namespace ns_enum
126 |
127 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
128 |
--------------------------------------------------------------------------------
/src/lib/db/fetch.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : fetch
4 | ///
5 |
6 | #pragma once
7 |
8 | #include "../hope.hpp"
9 | #include "../db.hpp"
10 | #include "../../macro.hpp"
11 |
12 | namespace ns_db::ns_fetch
13 | {
14 |
15 | namespace
16 | {
17 |
18 | // Forward declarations
19 | class Fetch;
20 | std::expected read_impl(fs::path const& path_file_db);
21 |
22 | // platforms() {{{
23 | struct CoreUrl { std::string core; std::string url; };
24 | struct Platform
25 | {
26 | private:
27 | std::map m_url_layer;
28 | std::vector m_vec_core_url;
29 | public:
30 | virtual std::string get_layer(std::string identifier = "default")
31 | {
32 | ethrow_if(not m_url_layer.contains(identifier), "Layer '{}' not found for platform"_fmt(identifier));
33 | return m_url_layer[identifier];
34 | };
35 | virtual std::vector get_cores() const { return m_vec_core_url; };
36 | virtual ~Platform() {};
37 | friend std::expected read_impl(fs::path const& path_file_db);
38 | };
39 | struct Linux : public Platform {};
40 | struct Retroarch : public Platform {};
41 | struct Pcsx2 : public Platform {};
42 | struct Rpcs3 : public Platform {};
43 | struct Wine : public Platform {};
44 | // platforms() }}}
45 |
46 | // class Fetch {{{
47 | class Fetch
48 | {
49 | private:
50 | std::unique_ptr m_linux = std::make_unique();
51 | std::unique_ptr m_retroarch = std::make_unique();
52 | std::unique_ptr m_pcsx2 = std::make_unique();
53 | std::unique_ptr m_rpcs3 = std::make_unique();
54 | std::unique_ptr m_wine = std::make_unique();
55 | std::string m_version;
56 | Fetch() = default;
57 | public:
58 | std::unique_ptr get_platform(ns_enum::Platform platform) const
59 | {
60 | switch (platform)
61 | {
62 | case ns_enum::Platform::LINUX : return std::make_unique(*m_linux);
63 | case ns_enum::Platform::RETROARCH : return std::make_unique(*m_retroarch);
64 | case ns_enum::Platform::PCSX2 : return std::make_unique(*m_pcsx2);
65 | case ns_enum::Platform::RPCS3 : return std::make_unique(*m_rpcs3);
66 | case ns_enum::Platform::WINE : return std::make_unique(*m_wine);
67 | } // switch
68 | throw std::runtime_error("Unknown platform");
69 | } // get_platform
70 |
71 | friend std::expected read_impl(fs::path const& path_file_db);
72 | }; // class Fetch }}}
73 |
74 | // read_impl() {{{
75 | inline std::expected read_impl(fs::path const& path_file_db)
76 | {
77 | return ns_db::from_file>(path_file_db, [&](auto&& db) -> std::expected
78 | {
79 | Fetch fetch;
80 | // Linux
81 | fetch.m_linux->m_url_layer["default"] = ehope(db.template value("linux", "layer"));
82 | // Pcsx2
83 | fetch.m_pcsx2->m_url_layer["default"] = ehope(db.template value("pcsx2", "layer"));
84 | // Rpcs3
85 | fetch.m_rpcs3->m_url_layer["default"] = ehope(db.template value("rpcs3", "layer"));
86 | // Wine
87 | auto layers = ehope(db.value("wine", "layer"));
88 | for(auto const& key : layers.keys())
89 | {
90 | fetch.m_wine->m_url_layer[key] = ehope(db.template value("wine", "layer", key));
91 | } // for
92 | // Retroarch
93 | fetch.m_retroarch->m_url_layer["default"] = ehope(db.template value("retroarch", "layer"));
94 | auto cores = ehope(db.value("retroarch", "core"));
95 | for(auto const& key : cores.keys())
96 | {
97 | if (auto value = db.template value("retroarch", "core", key))
98 | {
99 | fetch.m_retroarch->m_vec_core_url.push_back(CoreUrl{ key, *value });
100 | } // if
101 | else
102 | {
103 | ns_log::write('e', "Could not get value for key '", key, "'");
104 | } // else
105 | } // for
106 | return fetch;
107 | }, ns_db::Mode::READ).value_or(std::unexpected("Could not read 'fetch' database"));
108 | } // read_impl() }}}
109 |
110 | } // namespace
111 |
112 | // read() {{{
113 | inline std::expected read(fs::path const& path_file_db)
114 | {
115 | return read_impl(path_file_db);
116 | } // read() }}}
117 |
118 | } // namespace ns_db::ns_fetch
119 |
120 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
121 |
--------------------------------------------------------------------------------
/src/lib/hope.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | /// @file : hope
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 |
10 | #define hope(expr,ferror,...) \
11 | ({ \
12 | auto _tmp = expr; \
13 | if (!_tmp.has_value()) \
14 | { \
15 | ferror(__VA_ARGS__); \
16 | ferror(_tmp.error()); \
17 | return std::unexpected(_tmp.error()); \
18 | } \
19 | _tmp.value(); \
20 | })
21 |
22 | #define ehope(expr, ...) hope(expr, [](Ts&&... ts){ ns_log::write('e', std::forward(ts)...); }, __VA_ARGS__)
23 | #define ihope(expr, ...) hope(expr, [](Ts&&... ts){ ns_log::write('i', std::forward(ts)...); }, __VA_ARGS__)
24 | #define dhope(expr, ...) hope(expr, [](Ts&&... ts){ ns_log::write('d', std::forward(ts)...); }, __VA_ARGS__)
25 |
26 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
27 |
--------------------------------------------------------------------------------
/src/lib/ipc.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : ipc
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #include "../common.hpp"
13 | #include "../std/concepts.hpp"
14 | #include "../std/string.hpp"
15 | #include "../std/filesystem.hpp"
16 |
17 | #include "../lib/log.hpp"
18 |
19 | namespace ns_ipc
20 | {
21 |
22 | namespace fs = std::filesystem;
23 |
24 | struct message_buffer
25 | {
26 | long message_type;
27 | char message_text[1024];
28 | };
29 |
30 | // class Ipc {{{
31 | class Ipc
32 | {
33 | private:
34 | key_t m_key;
35 | int m_message_queue_id;
36 | message_buffer m_buffer;
37 | Ipc();
38 | public:
39 | template
40 | void send(T&& t);
41 | friend Ipc& ipc();
42 | }; // class Ipc }}}
43 |
44 | // Ipc::Ipc() {{{
45 | inline Ipc::Ipc()
46 | : m_buffer({ .message_type = 1, .message_text = "" })
47 | {
48 | fs::path path_file_self = ns_fs::ns_path::file_self()._ret;
49 | ns_log::write('i', "Starting IPC for ", path_file_self);
50 |
51 | std::string identifier = ns_string::to_string(path_file_self);
52 | ns_log::write('i', "key identifier: ", identifier);
53 |
54 | // Use a unique key for the message queue.
55 | if(m_key = ftok(identifier.c_str(), 65); m_key == -1 )
56 | {
57 | perror("Could not generate token for message queue");
58 | "Could not generate key for message queue with identifier '{}': {}"_throw(identifier, strerror(errno));
59 | } // if
60 | ns_log::write('i', "Generated message_queue key: ", m_key);
61 |
62 | // Connect to the message queue
63 | if (m_message_queue_id = msgget(m_key, 0666); m_message_queue_id == -1 )
64 | {
65 | perror("Could not create message queue");
66 | "msgget failed, could not create message queue for identifier '{}': {}"_throw(identifier, strerror(errno));
67 | } // if
68 | ns_log::write('i', "Message queue id: ", m_message_queue_id);
69 | } // Ipc::Ipc() }}}
70 |
71 | // Ipc::send() {{{
72 | template
73 | void Ipc::send(T&& t)
74 | {
75 | std::string data = ns_string::to_string(t);
76 | // Limit data size
77 | size_t data_length = std::min(data.size(), sizeof(m_buffer));
78 | // Copy the contents of std::string to the message_text buffer
79 | strncpy(m_buffer.message_text, data.c_str(), data_length);
80 | // Ensure null termination
81 | m_buffer.message_text[data_length] = '\0';
82 | // Send message
83 | if ( msgsnd(m_message_queue_id, &m_buffer, data_length, 0) == -1 )
84 | {
85 | perror("Failure to send message");
86 | } // if
87 | } // Ipc::send() }}}
88 |
89 | // ipc() {{{
90 | inline Ipc& ipc()
91 | {
92 | static Ipc ipc;
93 | return ipc;
94 | } // ipc() }}}
95 |
96 | } // namespace ns_ipc
97 |
98 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
99 |
--------------------------------------------------------------------------------
/src/lib/log.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : log
4 | // @created : Sunday Jan 21, 2024 14:17:24 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | #include "../std/concepts.hpp"
14 | #include "../std/exception.hpp"
15 |
16 | namespace ns_log
17 | {
18 |
19 | namespace fs = std::filesystem;
20 |
21 | // logger_file() {{{
22 | inline void logger_file(fs::path const& path_file_log)
23 | {
24 | // Configure
25 | el::Configurations default_conf;
26 | // To default
27 | default_conf.setToDefault();
28 | // Values are always std::string
29 | default_conf.set(el::Level::Global
30 | , el::ConfigurationType::Format
31 | , "%levshort :: %msg");
32 | // Configuration file
33 | default_conf.set(el::Level::Global
34 | , el::ConfigurationType::Filename
35 | , path_file_log);
36 | default_conf.set(el::Level::Global
37 | , el::ConfigurationType::ToStandardOutput
38 | , "false");
39 | // default logger uses default configurations
40 | el::Loggers::reconfigureLogger("default", default_conf);
41 | } // logger_file() }}}
42 |
43 | // logger_stdout() {{{
44 | inline void logger_stdout()
45 | {
46 | // Configure
47 | el::Configurations default_conf;
48 | // To default
49 | default_conf.setToDefault();
50 | // Values are always std::string
51 | default_conf.set(el::Level::Global
52 | , el::ConfigurationType::Format
53 | , "%levshort :: %msg");
54 | default_conf.set(el::Level::Global
55 | , el::ConfigurationType::ToStandardOutput
56 | , "true");
57 | // default logger uses default configurations
58 | el::Loggers::reconfigureLogger("term", default_conf);
59 | } // logger_stdout() }}}
60 |
61 | // init() {{{
62 | inline void init(int argc
63 | , char** argv
64 | , fs::path path_file_log)
65 | {
66 | // Remove log file if exists
67 | fs::remove(path_file_log);
68 | // Start easylogging
69 | START_EASYLOGGINGPP(argc, argv);
70 | // Create loggers
71 | logger_file(path_file_log);
72 | logger_stdout();
73 | // Try to make canonical path for log file
74 | try
75 | {
76 | path_file_log = fs::canonical(path_file_log);
77 | LOG(INFO) << fmt::format(fmt::runtime("Log file {}"), path_file_log.string());
78 | } // try
79 | catch (std::exception const& e)
80 | {
81 | LOG(ERROR) << fmt::format("Could not make canonical path for log file '{}'", path_file_log.c_str());
82 | } // catch:
83 | } // init() }}}
84 |
85 | // write() {{{
86 | template
87 | void write(char level, T&&... t)
88 | {
89 | std::stringstream ss;
90 |
91 | if constexpr ( sizeof...(t) > 0 )
92 | {
93 | ( ss << ... << t );
94 | } // if
95 |
96 | switch (level)
97 | {
98 | case 'i':
99 | for(std::string line; std::getline(ss, line);)
100 | {
101 | CLOG(INFO, "term") << line;
102 | CLOG(INFO, "default") << line;
103 | }
104 | break;
105 | case 'e':
106 | for(std::string line; std::getline(ss, line);)
107 | {
108 | CLOG(INFO, "term") << line;
109 | CLOG(INFO, "default") << line;
110 | }; break;
111 | case 'd':
112 | for(std::string line; std::getline(ss, line);)
113 | {
114 | CLOG(INFO, "default") << line;
115 | }; break;
116 | } // switch: level
117 | } // write() }}}
118 |
119 | // fn: exception {{{
120 | inline void exception(auto&& fn)
121 | {
122 | if (auto expected = ns_exception::to_expected(fn); not expected)
123 | {
124 | write('e', expected.error());
125 | } // if
126 | } // }}}
127 |
128 | // fn: ec {{{
129 | template
130 | inline auto ec(F&& fn, Args&&... args) -> std::invoke_result_t
131 | {
132 | std::error_code ec;
133 | if constexpr ( std::same_as> )
134 | {
135 | fn(std::forward(args)..., ec);
136 | if ( ec ) { ns_log::write('e', ec.message()); } // if
137 | }
138 | else
139 | {
140 | auto ret = fn(std::forward(args)..., ec);
141 | if ( ec ) { ns_log::write('e', ec.message()); } // if
142 | return ret;
143 | } // else
144 | } // }}}
145 |
146 | } // namespace ns_log
147 |
148 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
149 |
--------------------------------------------------------------------------------
/src/lib/sha.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | /// @file : sha
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include // Include for SHA256 and SHA512
11 | #include // Include for SHA256 and SHA512
12 | #include // Include for StringSink and HashFilter
13 | #include // Include for HexEncoder
14 |
15 | #include "../common.hpp"
16 | #include "../macro.hpp"
17 | #include "../std/exception.hpp"
18 |
19 | namespace fs = std::filesystem;
20 |
21 | namespace ns_sha
22 | {
23 |
24 | enum class SHA_TYPE
25 | {
26 | SHA256,
27 | SHA512
28 | };
29 |
30 | namespace
31 | {
32 |
33 | // check_sha_impl() {{{
34 | inline bool check_sha_impl(fs::path path_file_src, fs::path path_file_sha, SHA_TYPE sha_type = SHA_TYPE::SHA256)
35 | {
36 | std::ifstream file_src(path_file_src, std::ifstream::binary);
37 | std::ifstream file_sha(path_file_sha, std::ifstream::in);
38 |
39 | if (!file_src.good()) { "Cannot open file '{}' "_throw(path_file_src); }
40 | if (!file_sha.good()) { "Cannot open file '{}' "_throw(path_file_sha); }
41 |
42 | ns_log::write('i', "Calculating SHA for: ", path_file_src);
43 |
44 | std::string sha_calculated;
45 |
46 | // Calculated SHA
47 | if ( sha_type == SHA_TYPE::SHA256 )
48 | {
49 | CryptoPP::SHA256 hash;
50 | CryptoPP::FileSource(file_src, true, new CryptoPP::HashFilter(hash, new CryptoPP::HexEncoder(new CryptoPP::StringSink(sha_calculated))));
51 | } // if
52 | else
53 | {
54 | CryptoPP::SHA512 hash;
55 | CryptoPP::FileSource(file_src, true, new CryptoPP::HashFilter(hash, new CryptoPP::HexEncoder(new CryptoPP::StringSink(sha_calculated))));
56 | } // else
57 |
58 | // Reference SHA
59 | std::string sha_reference;
60 | std::getline(file_sha, sha_reference);
61 | if ( sha_reference.find(' ') != std::string::npos )
62 | {
63 | sha_reference.erase(sha_reference.find(' '));
64 | } // if
65 |
66 | // Normalize to uppercase
67 | sha_calculated = ns_string::to_upper(sha_calculated);
68 | sha_reference = ns_string::to_upper(sha_reference);
69 |
70 | ns_log::write('i', "SHA Calculated: ", sha_calculated);
71 | ns_log::write('i', "SHA Reference : ", sha_reference);
72 |
73 | return sha_calculated == sha_reference;
74 | } // check_sha_impl() }}}
75 |
76 | } // namespace
77 |
78 | // check_sha() {{{
79 | inline bool check_sha(fs::path path_file_src, fs::path path_file_sha, SHA_TYPE sha_type = SHA_TYPE::SHA256)
80 | {
81 | auto expected_check = ns_exception::to_expected([&]{ return check_sha_impl(path_file_src, path_file_sha, sha_type); });
82 | ereturn_if(not expected_check, expected_check.error(), false);
83 | return *expected_check;
84 | } // check_sha() }}}
85 |
86 | } // namespace ns_sha
87 |
88 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
89 |
--------------------------------------------------------------------------------
/src/lib/zip.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : zip
4 | ///
5 |
6 | #pragma once
7 |
8 | #include "../std/filesystem.hpp"
9 |
10 | #include
11 |
12 | namespace fs = std::filesystem;
13 |
14 | namespace ns_zip
15 | {
16 |
17 | // list_regular_files() {{{
18 | inline std::vector list_regular_files(fs::path const& path_file_zip)
19 | {
20 | std::vector ret;
21 |
22 | libzippp::ZipArchive zf(path_file_zip);
23 |
24 | // Open zip file
25 | if ( not zf.open(libzippp::ZipArchive::ReadOnly) )
26 | {
27 | "Failed to open zip file"_throw(path_file_zip);
28 | } // if
29 |
30 | std::vector entries = zf.getEntries();
31 |
32 | for(auto it{entries.begin()}; it != entries.end(); ++it)
33 | {
34 | libzippp::ZipEntry entry = *it;
35 |
36 | if (entry.isDirectory()) { continue; } // if
37 | else { ret.push_back(entry.getName()); } // else
38 | } // for
39 |
40 | // Close file
41 | zf.close();
42 |
43 | return ret;
44 | } // list_regular_files() }}}
45 |
46 | // extract() {{{
47 | inline void extract(fs::path const& path_file_zip, fs::path const& path_dir_out)
48 | {
49 | libzippp::ZipArchive zf(path_file_zip);
50 |
51 | // Open zip file
52 | if ( not zf.open(libzippp::ZipArchive::ReadOnly) )
53 | {
54 | "Failed to open zip file"_throw(path_file_zip);
55 | } // if
56 |
57 | ns_log::write('i', "Extracting ", path_file_zip);
58 |
59 | std::vector entries = zf.getEntries();
60 |
61 | for(auto it{entries.begin()}; it != entries.end(); ++it)
62 | {
63 | libzippp::ZipEntry entry = *it;
64 |
65 | fs::path path_file_out = path_dir_out / entry.getName();
66 |
67 | if (entry.isDirectory())
68 | {
69 | // Create parent dirs
70 | ns_fs::ns_path::dir_create(path_file_out);
71 | } // if
72 | else
73 | {
74 | // Create parent dirs
75 | ns_fs::ns_path::dir_create(path_file_out.parent_path());
76 |
77 | // Open file to write
78 | std::ofstream out_file(path_file_out, std::ios::binary);
79 |
80 | // Write to file from zip
81 | entry.readContent(out_file);
82 |
83 | // Close file
84 | out_file.close();
85 | } // else
86 |
87 | } // for
88 |
89 | // Close file
90 | zf.close();
91 | } // extract() }}}
92 |
93 | } // namespace ns_zip
94 |
95 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
96 |
--------------------------------------------------------------------------------
/src/macro.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : macro
4 | ///
5 |
6 | #pragma once
7 |
8 | // Ec wrapper
9 | #define lec(fun, ...) \
10 | ns_log::ec([](Args&&... args){ return fun(std::forward(args)...); }, __VA_ARGS__)
11 |
12 | // Throw
13 | #define qthrow_if(cond, msg) \
14 | if (cond) { throw std::runtime_error(msg); }
15 |
16 | #define dthrow_if(cond, msg) \
17 | if (cond) { ns_log::write('d', msg); throw std::runtime_error(msg); }
18 |
19 | #define ithrow_if(cond, msg) \
20 | if (cond) { ns_log::write('i', msg); throw std::runtime_error(msg); }
21 |
22 | #define ethrow_if(cond, msg) \
23 | if (cond) { ns_log::write('e', msg); throw std::runtime_error(msg); }
24 |
25 | // Exit
26 | #define qexit_if(cond, ret) \
27 | if (cond) { exit(ret); }
28 |
29 | #define dexit_if(cond, msg, ret) \
30 | if (cond) { ns_log::write('d', msg); exit(ret); }
31 |
32 | #define iexit_if(cond, msg, ret) \
33 | if (cond) { ns_log::write('i', msg); exit(ret); }
34 |
35 | #define eexit_if(cond, msg, ret) \
36 | if (cond) { ns_log::write('e', msg); exit(ret); }
37 |
38 | // Abort
39 | #define eabort_if(cond, msg) \
40 | if ( (cond) ) { ns_log::write('e', msg); std::abort(); }
41 |
42 | // Return
43 | #define qreturn_if(cond, ...) \
44 | if (cond) { return __VA_ARGS__; }
45 |
46 | #define dreturn_if(cond, msg, ...) \
47 | if (cond) { ns_log::write('d', msg); return __VA_ARGS__; }
48 |
49 | #define ireturn_if(cond, msg, ...) \
50 | if (cond) { ns_log::write('i', msg); return __VA_ARGS__; }
51 |
52 | #define ereturn_if(cond, msg, ...) \
53 | if (cond) { ns_log::write('e', msg); return __VA_ARGS__; }
54 |
55 | // Break
56 | #define qbreak_if(cond) \
57 | if ( (cond) ) { break; }
58 |
59 | #define ebreak_if(cond, msg) \
60 | if ( (cond) ) { ns_log::write('e', msg); break; }
61 |
62 | #define ibreak_if(cond, msg) \
63 | if ( (cond) ) { ns_log::write('i', msg); break; }
64 |
65 | #define dbreak_if(cond, msg) \
66 | if ( (cond) ) { ns_log::write('d', msg); break; }
67 |
68 | // Continue
69 | #define qcontinue_if(cond) \
70 | if ( (cond) ) { continue; }
71 |
72 | #define econtinue_if(cond, msg) \
73 | if ( (cond) ) { ns_log::write('e', msg); continue; }
74 |
75 | #define icontinue_if(cond, msg) \
76 | if ( (cond) ) { ns_log::write('i', msg); continue; }
77 |
78 | #define dcontinue_if(cond, msg) \
79 | if ( (cond) ) { ns_log::write('d', msg); continue; }
80 |
81 | // Conditional log
82 | #define elog_if(cond, msg) \
83 | if ( (cond) ) { ns_log::write('e', msg); }
84 |
85 | #define ilog_if(cond, msg) \
86 | if ( (cond) ) { ns_log::write('i', msg); }
87 |
88 | #define dlog_if(cond, msg) \
89 | if ( (cond) ) { ns_log::write('d', msg); }
90 |
91 | // Expected
92 | #define elog_unexpected(expr) \
93 | if ( auto value = expr; not expr.has_value() ) { ns_log::write('e', expr.error()); }
94 |
95 | // Error log
96 | #define elogerror(expr) \
97 | if ( auto error = expr ) { ns_log::write('e', *error); }
98 |
99 | #define ilogerror(expr) \
100 | if ( auto error = expr ) { ns_log::write('i', *error); }
101 |
102 | #define dlogerror(expr) \
103 | if ( auto error = expr ) { ns_log::write('d', *error); }
104 |
--------------------------------------------------------------------------------
/src/std/concepts.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : concepts
4 | // @created : Tuesday Jan 23, 2024 19:59:31 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | namespace ns_concept
16 | {
17 | template
18 | concept Variant = requires(T){ std::variant_size_v; };
19 |
20 | template
21 | concept Enum = std::is_enum_v;
22 |
23 | template
24 | concept Iterable = requires(T t)
25 | {
26 | { t.begin() } -> std::input_iterator;
27 | { t.end() } -> std::input_iterator;
28 | };
29 |
30 | template
31 | concept IterableConst = requires(T t)
32 | {
33 | { t.cbegin() } -> std::input_iterator;
34 | { t.cend() } -> std::input_iterator;
35 | };
36 |
37 |
38 | // Helper to check if a type is a specialization of std::vector
39 | template
40 | struct is_vector : std::false_type {};
41 |
42 | template
43 | struct is_vector> : std::true_type {};
44 |
45 | // Define a concept based on the helper trait
46 | template
47 | concept IsVector = is_vector::value;
48 |
49 | template
50 | concept IterableForward = std::forward_iterator;
51 |
52 | template
53 | concept StringConvertible = std::is_convertible_v, std::string>;
54 |
55 | template
56 | concept StringConstructible = std::constructible_from>;
57 |
58 | template
59 | concept Numeric = std::integral>
60 | or std::floating_point>;
61 |
62 | template
63 | concept StreamInsertable = requires(T t, std::ostream& os)
64 | {
65 | { os << t } -> std::same_as;
66 | };
67 |
68 | template
69 | concept AsString = StringConvertible or StringConstructible or Numeric or StreamInsertable;
70 |
71 | } // namespace ns_concept
72 |
73 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
74 |
--------------------------------------------------------------------------------
/src/std/copy.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : copy
4 | // @created : Friday Jan 19, 2024 19:36:20 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include