diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..2f69206 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,12 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "mkhl.direnv" + ] + } + }, + "image": "ghcr.io/cachix/devenv:latest", + "overrideCommand": false, + "updateContentCommand": "devenv test" +} diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..c420855 --- /dev/null +++ b/.envrc @@ -0,0 +1,10 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi + +watch_file flake.nix +watch_file flake.lock +if ! use flake . --impure +then + echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2 +fi diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 003206f..f5f0932 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -32,10 +32,10 @@ jobs: - name: Check if Python files or wemod file changed id: check_files run: | - if echo "${{ steps.changed_files.outputs.added_modified }}" | grep -E '\.py$|wemod' > /dev/null; then - echo "PYTHON_FILES_CHANGED=true" >> $GITHUB_ENV + if echo "${{ steps.changed_files.outputs.added_modified }}" | grep -E 'wemod_launcher/\.py$' > /dev/null; then + echo "PYTHON_FILES_CHANGED=true" >> "$GITHUB_ENV" else - echo "PYTHON_FILES_CHANGED=false" >> $GITHUB_ENV + echo "PYTHON_FILES_CHANGED=false" >> "$GITHUB_ENV" fi - name: Set up Python @@ -53,7 +53,7 @@ jobs: if: env.PYTHON_FILES_CHANGED == 'true' id: run_black run: | - black -l 78 -t py312 ./*.py ./wemod + black -l 78 -t py312 ./wemod_launcher/ - name: Increment version number if: steps.run_black.outcome == 'success' && env.PYTHON_FILES_CHANGED == 'true' @@ -64,13 +64,13 @@ jobs: new_ver = ver + 0.001; printf(" script_version = \"%.3f\"\n", new_ver); } - !/script_version = "/ { print $0 }' coreutils.py > coreutils.py.tmp && mv coreutils.py.tmp coreutils.py + !/script_version = "/ { print $0 }' wemod_launcher/coreutils.py > wemod_launcher/coreutils.py.tmp && mv wemod_launcher/coreutils.py.tmp wemod_launcher/coreutils.py - current_ver=$(awk '/script_version = "/ {print $3}' coreutils.py | tr -d '"') + current_ver=$(awk '/script_version = "/ {print $3}' wemod_launcher/coreutils.py | tr -d '"') awk -v ver="$current_ver" '/The WeMod Launcher is currently on version / { printf("**The WeMod Launcher is currently on version %.3f.**\n", ver); } - !/The WeMod Launcher is currently on version / { print $0 }' readme.md > readme.tmp && mv readme.tmp readme.md + !/The WeMod Launcher is currently on version / { print $0 }' README.md > README.tmp && mv README.tmp README.md - name: Commit changes on dev if: github.ref == 'refs/heads/dev' && steps.run_black.outcome == 'success' && env.PYTHON_FILES_CHANGED == 'true' @@ -79,7 +79,7 @@ jobs: git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' git add . - git update-index --chmod=+x *.py wemod + git update-index --chmod=+x wemod_launcher/**.py git commit -m 'Github actions automatic code formatting on dev branch' -m 'This is an automatic task, run by a GitHub workflow, to automatically format the Python code in the repo, with the code formatter "black"' || echo "No changes to commit" git push env: @@ -92,7 +92,7 @@ jobs: git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' git add . - git update-index --chmod=+x *.py wemod + git update-index --chmod=+x wemod_launcher/**.py git commit -m 'Github actions automatic code formatting on main branch' -m 'This is an automatic task, run by a GitHub workflow, to automatically format the Python code in the repo, with the code formatter "black"' || echo "No changes to commit" git push env: diff --git a/.gitignore b/.gitignore index fb6f192..a2acac1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,24 @@ .cache/ +logs/ wemod_bin/ wemod_data/ wemod_venv/ __pycache__/ + wemod.conf -wemod.log winetricks -pip.pyz +poetry.lock + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + +dist/ +.pdm-build/ +.pdm-python diff --git a/readme.md b/README.md similarity index 99% rename from readme.md rename to README.md index 40d9730..0ea5fbc 100644 --- a/readme.md +++ b/README.md @@ -1,6 +1,8 @@ # WeMod Launcher (Wemod for Linux) **The WeMod Launcher is currently on version 1.506.** +### This refactor is under development, do not use yet. + ## DISCLAIMER This project is *NOT* affiliated with, funded by, or paid by WeMod. The work done here is purely from the contributors who donate their time and efforts. diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..167dd13 --- /dev/null +++ b/default.nix @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2024 The DeckCheatz Developers +# +# SPDX-License-Identifier: Apache-2.0 + +(import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; }).defaultNix diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..414d0e6 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,66 @@ +{ pkgs +, self +, inputs +, ... +}: +let + freesimplegui = pkgs.python3Packages.buildPythonApplication rec { + pname = "freesimplegui"; + version = "5.1.1"; + src = pkgs.fetchPypi { + inherit pname version; + sha256 = "sha256-LwlGx6wiHJl5KRgcvnUm40L/9fwpGibR1yYoel3ZZPs="; + }; + }; +in +{ + packages = + with pkgs; + [ + git + pdm + pyright + ] + ++ (with pkgs.python3Packages; [ + requests + pyxdg + setuptools + pyinstaller + tkinter + ]) + ++ [ freesimplegui ]; + + languages = { + nix.enable = true; + python = { + enable = true; + package = pkgs.python313Full; + }; + shell.enable = true; + }; + + difftastic.enable = true; + devcontainer.enable = true; + + pre-commit.hooks = { + shellcheck.enable = true; + shfmt.enable = true; + # FIXME: Fix Flake8 lints. + # flake8.enable = true; + actionlint.enable = true; + nixpkgs-fmt.enable = true; + black = { + enable = true; + settings.flags = "-l 78 -t py312"; + }; + # FIXME: Fix Markdown lints. + # markdownlint.enable = true; + statix.enable = true; + }; + + enterShell = '' + export TK_LIBRARY="${pkgs.tk.outPath}/lib/${pkgs.tk.libPrefix}" + export TCL_LIBRARY="${pkgs.tcl.outPath}/lib/${pkgs.tcl.libPrefix}" + ''; + +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f2a3b9d --- /dev/null +++ b/flake.lock @@ -0,0 +1,335 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": [ + "devenv" + ], + "flake-compat": [ + "devenv" + ], + "git-hooks": [ + "devenv" + ], + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1728672398, + "narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=", + "owner": "cachix", + "repo": "cachix", + "rev": "aac51f698309fd0f381149214b7eee213c66ef0a", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat", + "git-hooks": "git-hooks", + "nix": "nix", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1731679695, + "narHash": "sha256-CHwXNY10oC3Tfvpd4Q4kqrltApJyxfzJYIh/uRWnnGg=", + "owner": "cachix", + "repo": "devenv", + "rev": "9f6cadacb9db82f541bbadd67e0189a2b850937e", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": [ + "devenv" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": [ + "devenv" + ] + }, + "locked": { + "lastModified": 1730302582, + "narHash": "sha256-W1MIJpADXQCgosJZT8qBYLRuZls2KSiKdpnTVdKBuvU=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "af8a16fe5c264f5e9e18bcee2859b40a656876cf", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "libgit2": { + "flake": false, + "locked": { + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", + "type": "github" + }, + "original": { + "owner": "libgit2", + "repo": "libgit2", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": [ + "devenv" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": [ + "devenv" + ], + "nixpkgs-regression": [ + "devenv" + ], + "pre-commit-hooks": [ + "devenv" + ] + }, + "locked": { + "lastModified": 1727438425, + "narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=", + "owner": "domenkozar", + "repo": "nix", + "rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.24", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1730531603, + "narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1717432640, + "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1716977621, + "narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "4267e705586473d3e5c8d50299e71503f16a6fb6", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1731319897, + "narHash": "sha256-PbABj4tnbWFMfBp6OcUK5iGy1QY+/Z96ZcLpooIbuEI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "dc460ec76cbff0e66e269457d7b728432263166c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1731586143, + "narHash": "sha256-oS4E2nFkTfAdr9bXEyp7ZOTOdKPU3Hy3gRTJg6bUMVw=", + "owner": "nix-community", + "repo": "pyproject.nix", + "rev": "3768b95bf7de200f6b19f32e8adf74eb8c55aa40", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "pyproject.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_4", + "pyproject-nix": "pyproject-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9e841b3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,98 @@ +{ + description = "wemod_launcher Flake"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + devenv.url = "github:cachix/devenv"; + pyproject-nix = { + url = "github:nix-community/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + nixConfig = { + extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; + extra-substituters = "https://devenv.cachix.org"; + }; + + outputs = + { self + , nixpkgs + , flake-utils + , devenv + , pyproject-nix + , ... + }@inputs: + flake-utils.lib.eachDefaultSystem + ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + packages = with pkgs; { + wemod-launcher = + with python3Packages; + let + freesimplegui = buildPythonApplication rec { + pname = "freesimplegui"; + version = "5.1.1"; + src = fetchPypi { + inherit pname version; + sha256 = "sha256-LwlGx6wiHJl5KRgcvnUm40L/9fwpGibR1yYoel3ZZPs="; + }; + }; + project = pyproject-nix.lib.project.loadPyproject { projectRoot = ./.; }; + python = python3Full; + attrs = project.renderers.buildPythonPackage { inherit python; }; + in + buildPythonApplication ( + lib.recursiveUpdate attrs { + propagatedBuildInputs = [ + freesimplegui + pyinstaller + pyxdg + requests + setuptools + tkinter + ]; + dependencies = [ freesimplegui ]; + doCheck = false; + pythonImportsCheck = [ "wemod_launcher" ]; + meta = with lib; { + mainProgram = "wemod-launcher"; + maintainers = with maintainers; [ shymega ]; + }; + } + ); + default = self.packages.${system}.wemod-launcher; + devenv-up = self.devShells.${system}.default.config.procfileScript; + devenv-test = self.devShells.${system}.default.config.test; + }; + + devShells.default = devenv.lib.mkShell { + inherit inputs pkgs; + modules = [ + ( + { pkgs + , config + , inputs + , ... + }: + { + imports = [ ./devenv.nix ]; + } + ) + ]; + }; + } + ) + // { + overlays.default = final: prev: { inherit (self.packages.${final.system}) wemod-launcher; }; + }; +} diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..099a3b6 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,248 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:a7ebdffdd8052b5027f405deef19c7bb0733c216ebd2a4b8a041669259a10c7f" + +[[metadata.targets]] +requires_python = ">=3.11" + +[[package]] +name = "altgraph" +version = "0.17.4" +summary = "Python graph (network) package" +groups = ["default"] +files = [ + {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, + {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default"] +files = [ + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "freesimplegui" +version = "5.1.1" +summary = "The free-forever Python GUI framework." +groups = ["default"] +files = [ + {file = "FreeSimpleGUI-5.1.1-py3-none-any.whl", hash = "sha256:d7629d5c94b55264d119bd2a89f52667d863ea7914d808e619aea29922ff842e"}, + {file = "freesimplegui-5.1.1.tar.gz", hash = "sha256:2f0946c7ac221c997929181cbe7526e342fff5fc291a26d1d726287a5dd964fb"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "macholib" +version = "1.16.3" +summary = "Mach-O header analysis and editing" +groups = ["default"] +marker = "sys_platform == \"darwin\"" +dependencies = [ + "altgraph>=0.17", +] +files = [ + {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, + {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, +] + +[[package]] +name = "packaging" +version = "24.2" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["default"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +requires_python = ">=3.6.0" +summary = "Python PE parsing module" +groups = ["default"] +marker = "sys_platform == \"win32\"" +files = [ + {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, + {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, +] + +[[package]] +name = "pyinstaller" +version = "4.5.1" +requires_python = ">=3.6" +summary = "PyInstaller bundles a Python application and all its dependencies into a single package." +groups = ["default"] +dependencies = [ + "altgraph", + "importlib-metadata; python_version < \"3.8\"", + "macholib>=1.8; sys_platform == \"darwin\"", + "pefile>=2017.8.1; sys_platform == \"win32\"", + "pyinstaller-hooks-contrib>=2020.6", + "pywin32-ctypes>=0.2.0; sys_platform == \"win32\"", + "setuptools", +] +files = [ + {file = "pyinstaller-4.5.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:ecc2baadeeefd2b6fbf39d13c65d4aa603afdda1c6aaaebc4577ba72893fee9e"}, + {file = "pyinstaller-4.5.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d848cd782ee0893d7ad9fe2bfe535206a79f0b6760cecc5f2add831258b9322"}, + {file = "pyinstaller-4.5.1-py3-none-manylinux2014_i686.whl", hash = "sha256:8f747b190e6ad30e2d2fd5da9a64636f61aac8c038c0b7f685efa92c782ea14f"}, + {file = "pyinstaller-4.5.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c587da8f521a7ce1b9efb4e3d0117cd63c92dc6cedff24590aeef89372f53012"}, + {file = "pyinstaller-4.5.1-py3-none-win32.whl", hash = "sha256:fed9f5e4802769a416a8f2ca171c6be961d1861cc05a0b71d20dfe05423137e9"}, + {file = "pyinstaller-4.5.1-py3-none-win_amd64.whl", hash = "sha256:aae456205c68355f9597411090576bb31b614e53976b4c102d072bbe5db8392a"}, + {file = "pyinstaller-4.5.1.tar.gz", hash = "sha256:30733baaf8971902286a0ddf77e5499ac5f7bf8e7c39163e83d4f8c696ef265e"}, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2024.10" +requires_python = ">=3.8" +summary = "Community maintained hooks for PyInstaller" +groups = ["default"] +dependencies = [ + "importlib-metadata>=4.6; python_version < \"3.10\"", + "packaging>=22.0", + "setuptools>=42.0.0", +] +files = [ + {file = "pyinstaller_hooks_contrib-2024.10-py3-none-any.whl", hash = "sha256:ad47db0e153683b4151e10d231cb91f2d93c85079e78d76d9e0f57ac6c8a5e10"}, + {file = "pyinstaller_hooks_contrib-2024.10.tar.gz", hash = "sha256:8a46655e5c5b0186b5e527399118a9b342f10513eb1425c483fa4f6d02e8800c"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +requires_python = ">=3.6" +summary = "A (partial) reimplementation of pywin32 using ctypes/cffi" +groups = ["default"] +marker = "sys_platform == \"win32\"" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + +[[package]] +name = "pyxdg" +version = "0.28" +summary = "PyXDG contains implementations of freedesktop.org standards in python." +groups = ["default"] +files = [ + {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"}, + {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "setuptools" +version = "75.6.0" +requires_python = ">=3.9" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" +groups = ["default"] +files = [ + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..77d92b2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "wemod-launcher" +version = "1.503" +description = "Tool made to launch the popular Game Trainer / Cheat tool WeMod along with your game (made for steam-runtime version in Linux)." +authors = [ + {name = "The DeckCheatz Team", email = "deckcheatz@noreply.github.com"}, +] +dependencies = [ + "freesimplegui>=5.1.1", + "requests>=2.31.0", + "pyxdg>=0.28", + "setuptools>=75.1.0.post0", + "pyinstaller>=4.5.1", +] +requires-python = ">=3.11" +readme = "README.md" +license = {text = "MIT"} + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[tool.pdm.scripts] +package-exe = {call = "wemod_launcher.pyinstaller:install"} + +[project.scripts] +wemod-launcher = "wemod_launcher.cli:main" + +[tool.pdm] +distribution = true diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8c9043c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -FreeSimpleGUI -requests diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..2949408 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-only + +# Navigate to the project root +cd "$(dirname "$0")/.." + +# Setup PDM +if ! ./scripts/pdm.sh; then + echo "PDM setup failed. Exiting." + exit 1 +fi + +# Build by running pyinstaller +./src/wemod_launcher/pyinstaller.py diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100644 index 0000000..bdbe5f1 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-only + +# Navigate to the project root +cd "$(dirname "$0")/.." + +# Setup PDM +if ! ./scripts/pdm.sh; then + echo "PDM setup failed. Exiting." + exit 1 +fi + +# Drop into the PDM shell +echo "Dropping into PDM shell..." +pdm run "$SHELL" diff --git a/scripts/pdm.sh b/scripts/pdm.sh new file mode 100644 index 0000000..4a31972 --- /dev/null +++ b/scripts/pdm.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-only + +# Navigate to the project root +cd "$(dirname "$0")/.." + +PDM_DIR="$HOME/.local/share/pdm/venv/bin" +PDM_BIN="$PDM_DIR/pdm" +PDM_ACTIVATE="$PDM_DIR/activate" + +install_pdm() { + echo "Installing PDM..." + curl -sSL https://pdm-project.org/install-pdm.py | python3 - +} + +activate_pdm() { + echo "Activating PDM..." + source "$PDM_ACTIVATE" + export PATH="$PDM_DIR:$PATH" +} + +install_dependencies() { + echo "Installing PDM updates and dependencies..." + pdm self update 2>/dev/null + pdm install +} + +run_installer() { + if [ ! -f "$PDM_BIN" ] || [ ! -f "$PDM_ACTIVATE" ]; then + install_pdm + else + activate_pdm + fi + + if install_dependencies; then + return 0 + else + echo "Possible issue with the PDM install. Reinstalling..." + install_pdm + if install_dependencies; then + return 0 + else + return 1 + fi + fi +} + +if run_installer; then + echo "PDM Project setup complete." +else + exit 1 +fi + diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100644 index 0000000..c2d1964 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-only + +# Navigate to the project root +cd "$(dirname "$0")/.." + +# Setup PDM +if ! ./scripts/pdm.sh; then + echo "PDM setup failed. Exiting." + exit 1 +fi + +# Run the application with passed arguments +pdm run wemod-launcher "$@" diff --git a/scripts/shell.sh b/scripts/shell.sh new file mode 100644 index 0000000..f38b9f3 --- /dev/null +++ b/scripts/shell.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-only + +# Navigate to the project root +cd "$(dirname "$0")/.." + +# Setup PDM +if ! ./scripts/pdm.sh; then + echo "PDM setup failed. Exiting." + exit 1 +fi + +# Set your preferred shell +read -p "Input your preferred shell path: " VAR + +if command -v $VAR &> /dev/null; then + # Drop into the user PDM shell + echo "Dropping into PDM shell..." + pdm run "$VAR" +else + # If command non-existent Drop into the default PDM shell + echo "The shell '$VAR' is non-existent" + echo "Falling back into default PDM shell..." + pdm run "$SHELL" +fi diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..46df7ff --- /dev/null +++ b/shell.nix @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 Dom Rodriguez +# +# SPDX-License-Identifier: GPL-3.0-only +(import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; }).shellNix diff --git a/src/wemod_launcher/__init__.py b/src/wemod_launcher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/wemod_launcher/cli.py b/src/wemod_launcher/cli.py new file mode 100644 index 0000000..5f7652c --- /dev/null +++ b/src/wemod_launcher/cli.py @@ -0,0 +1,14 @@ +from wemod_launcher.utils.logger import LoggingHandler +from wemod_launcher.utils.configuration import Configuration +from wemod_launcher.utils.consts import Consts +from wemod_launcher.gfx.welcome_screen import WelcomeScreenGfx + +log = LoggingHandler(__name__).get_logger() +cfg = Configuration() +consts = Consts() + + +def main(): + log.info("Welcome to WeMod Launcher!") + + WelcomeScreenGfx().run() diff --git a/constutils.py b/src/wemod_launcher/const_utils.py similarity index 97% rename from constutils.py rename to src/wemod_launcher/const_utils.py index 3597b19..c78b128 100755 --- a/constutils.py +++ b/src/wemod_launcher/const_utils.py @@ -10,7 +10,7 @@ from urllib import request # Import consts -from consts import ( +from wemod_launcher.consts import ( STEAM_COMPAT_FOLDER, BASE_STEAM_COMPAT, SCAN_FOLDER, @@ -18,14 +18,14 @@ INIT_FILE, ) -from coreutils import ( +from wemod_launcher.core_utils import ( exit_with_message, get_user_input, popup_options, show_message, ) -from corenodep import ( +from wemod_launcher.core_nodeps import ( load_conf_setting, save_conf_setting, parse_version, @@ -38,11 +38,11 @@ Optional, ) -from coreutils import ( +from wemod_launcher.core_utils import ( log, ) -from mainutils import ( +from wemod_launcher.main_utils import ( popup_execute, ) @@ -125,8 +125,8 @@ def ensure_wine(verstr: Optional[str] = None) -> str: ) -# Scan the steam compat folder for WeMod installed prefixes -def scanfolderforversions( +# Scan the steam compat folder for wemod installed prefixes +def scan_compat_for_versions( current_version_parts: List[Union[int, None]] = [None, None] ) -> List[Union[Optional[List[int]], Optional[str]]]: # At default, we don't know of any available version @@ -255,7 +255,7 @@ def scanfolderforversions( prefix_path_seven = folder_path if prefix_path_seven: - from mainutils import copy_folder_with_progress + from wemod_launcher.main_utils import copy_folder_with_progress prefixesfolder = os.path.join(SCAN_FOLDER, "prefix") os.makedirs(prefixesfolder, exists_ok=True) diff --git a/consts.py b/src/wemod_launcher/consts.py similarity index 70% rename from consts.py rename to src/wemod_launcher/consts.py index ca0b049..f4fb568 100755 --- a/consts.py +++ b/src/wemod_launcher/consts.py @@ -3,13 +3,19 @@ import os import sys +from pathlib import Path +from wemod_launcher.utils.configuration import Configuration +from wemod_launcher.utils.consts import Consts +from wemod_launcher.pfx.wine_utils import WineUtils -from corenodep import ( +#cfg: Configuration = Configuration() + +from wemod_launcher.core_nodeps import ( load_conf_setting, winpath, ) -from coreutils import ( +from wemod_launcher.core_utils import ( exit_with_message, log, ) @@ -65,24 +71,33 @@ def getbatcmd(): BAT_COMMAND = getbatcmd() - -# Function to grab the Steam Compat Data Path +# Function to grab the Steam Compat Data path def get_compat() -> str: ccompat = load_conf_setting("SteamCompatDataPath") wcompat = load_conf_setting("WinePrefixPath") if not wcompat and os.getenv("WINE_PREFIX_PATH"): - os.environ["WINEPREFIX"] = os.getenv("WINE_PREFIX_PATH") - ecompat = os.getenv("STEAM_COMPAT_DATA_PATH") + os.environ["WINEPREFIX"] = os.getenv( + "WINE_PREFIX_PATH", "" + ) # TODO: Either use try/except here, or a sane default. + ecompat = os.getenv( + "STEAM_COMPAT_DATA_PATH", "" + ) # TODO: Either use try/except here, or a sane default. nogame = False # STEAM_COMPAT_DATA_PATH not set if not ecompat: if os.getenv("WINEPREFIX") or wcompat: ecompat = wcompat if not ecompat: - ecompat = os.getenv("WINEPREFIX") + ecompat = os.getenv( + "WINEPREFIX", "" + ) # TODO: Either use try/except here, or a sane default. nogame = True - wine = os.getenv("WINE") - tools = os.getenv("STEAM_COMPAT_TOOL_PATHS") + wine = os.getenv( + "WINE", "" + ) # TODO: Either use try/except here, or a sane default. + tools = os.getenv( + "STEAM_COMPAT_TOOL_PATHS", "" + ) # TODO: Either use try/except here, or a sane default. # if tools set and wine not in compat tools if ( tools @@ -123,20 +138,35 @@ def get_compat() -> str: return ecompat -BASE_STEAM_COMPAT = get_compat() -STEAM_COMPAT_FOLDER = os.path.dirname(BASE_STEAM_COMPAT) +def get_scan_folder() -> str: + scan_folder: str + try: + scan_folder = os.getenv("SCANFOLDER") or "" + except KeyError: + scan_folder = "" + pass + + if not Path(scan_folder).exists(): + scan_folder = load_conf_setting("ScanFolder") or "" + + if not Path(scan_folder).exists(): + scan_folder = STEAM_COMPAT_FOLDER + + return scan_folder -def get_scan_folder(): - wscanfolder = os.getenv("SCANFOLDER") - cscanfolder = load_conf_setting("ScanFolder") - if not wscanfolder: - wscanfolder = cscanfolder - if not wscanfolder: - wscanfolder = STEAM_COMPAT_FOLDER - return wscanfolder +CONSTS = Consts() +WINE_UTILS = WineUtils() +SCRIPT_IMP_FILE = str(CONSTS.SCRIPT_PATH) +SCRIPT_PATH = str(CONSTS.SCRIPT_RUNTIME_DIR) +BAT_COMMAND = [ + "start", + WINE_UTILS.native_path(os.path.join(SCRIPT_IMP_FILE, "wemod.bat")), +] +BASE_STEAM_COMPAT = get_scan_folder() +STEAM_COMPAT_FOLDER = str(CONSTS.STEAM_COMPAT_DATA_DIR) SCAN_FOLDER = get_scan_folder() WINETRICKS = os.path.join(SCRIPT_PATH, "winetricks") WINEPREFIX = os.path.join(BASE_STEAM_COMPAT, "pfx") diff --git a/corenodep.py b/src/wemod_launcher/core_nodeps.py similarity index 95% rename from corenodep.py rename to src/wemod_launcher/core_nodeps.py index 6868f81..1b65c59 100755 --- a/corenodep.py +++ b/src/wemod_launcher/core_nodeps.py @@ -17,7 +17,8 @@ Union, ) -CONFIG_PATH = os.path.join(SCRIPT_PATH, "wemod.conf") +# CONFIG_PATH = os.path.join(SCRIPT_PATH, "wemod.conf") +CONFIG_PATH = os.path.expanduser("~/.config/wemod-launcher/wemod.conf") DEF_SECTION = "Settings" CONFIG = configparser.ConfigParser() CONFIG.optionxform = str @@ -36,7 +37,7 @@ def check_dependencies(requirements_file: str) -> bool: try: importlib.import_module(package) except ImportError: - from coreutils import log + from wemod_launcher.core_utils import log log(f"Package '{package}' is missing") ret = False @@ -56,7 +57,7 @@ def load_conf_setting( def save_conf_setting( setting: str, value: Optional[str] = None, section: str = DEF_SECTION ) -> None: - from coreutils import log + from wemod_launcher.core_utils import log if not isinstance(section, str): log("Error adding the given section, it wasn't a string") diff --git a/coreutils.py b/src/wemod_launcher/core_utils.py similarity index 99% rename from coreutils.py rename to src/wemod_launcher/core_utils.py index 08a079a..5be11e0 100755 --- a/coreutils.py +++ b/src/wemod_launcher/core_utils.py @@ -15,7 +15,7 @@ Any, ) -from corenodep import ( +from wemod_launcher.core_nodeps import ( join_lists_with_delimiter, load_conf_setting, save_conf_setting, @@ -326,7 +326,7 @@ def bat_respond(responsefile: str, bout: Optional[int]) -> Optional[bool]: def cache( file_path: str, default: Callable[[str], None], simple: bool = False ) -> str: - CACHE = os.path.join(SCRIPT_PATH, ".cache") + CACHE = "/tmp/wemod-launcher/.cache" if not os.path.isdir(CACHE): log("Cache dir not found. Creating...") os.mkdir(CACHE) diff --git a/src/wemod_launcher/gfx/__init__.py b/src/wemod_launcher/gfx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/wemod_launcher/gfx/welcome_screen.py b/src/wemod_launcher/gfx/welcome_screen.py new file mode 100644 index 0000000..fb3162e --- /dev/null +++ b/src/wemod_launcher/gfx/welcome_screen.py @@ -0,0 +1,43 @@ +from xdg.BaseDirectory import save_cache_path +import FreeSimpleGUI as sg +import requests +from pathlib import Path +from ..utils.logger import LoggingHandler + + +class WelcomeScreenGfx(object): + WEMOD_LOGO_URL: str = ( + "https://www.wemod.com/static/images/device-icons/favicon-192-ce0bc030f3.png" + ) + + def __init__(self): + ## TODO: Cache this image. + + self.__logger = LoggingHandler(__name__).get_logger() + + cache_path = ( + Path(save_cache_path("wemod_launcher")) / "assets/wemod_logo.png" + ) + if cache_path.exists(): + self.WEMOD_LOGO_RAW = open(str(cache_path), "rb").read() + else: + cache_path.parent.mkdir(parents=True, exist_ok=True) + self.__logger.debug("Downloading logo to cache, and memory.") + logo_raw = requests.get(self.WEMOD_LOGO_URL, stream=False).content + open(str(cache_path), "wb").write(logo_raw) + self.WEMOD_LOGO_RAW = open(str(cache_path), "rb").read() + + self.__logger.debug("WelcomeScreenGfx initialized") + + def run(self): + sg.theme("systemdefault") + + return ( + sg.popup_ok_cancel( + "Welcome to the WeMod Installer!\nPress ok to start the setup.", + title="WeMod Launcher Setup", + image=self.WEMOD_LOGO_RAW, + icon=self.WEMOD_LOGO_RAW, + ) + == "OK" + ) diff --git a/mainutils.py b/src/wemod_launcher/main_utils.py similarity index 99% rename from mainutils.py rename to src/wemod_launcher/main_utils.py index 48de504..e8b7149 100755 --- a/mainutils.py +++ b/src/wemod_launcher/main_utils.py @@ -15,11 +15,11 @@ ) -from corenodep import ( +from wemod_launcher.core_nodeps import ( parse_version, ) -from coreutils import ( +from wemod_launcher.core_utils import ( exit_with_message, save_conf_setting, load_conf_setting, @@ -235,7 +235,7 @@ def popup_download(title: str, link: str, file_name: str) -> str: status = [0, 0] - cache = os.path.join(SCRIPT_PATH, ".cache") + cache = "/tmp/wemod-launcher/.cache" if not os.path.isdir(cache): os.makedirs(cache) @@ -613,7 +613,7 @@ def flatpakrunner(): import subprocess import time - cachedir = os.path.join(SCRIPT_PATH, ".cache") + cachedir = "/tmp/wemod-launcher/.cache" os.makedirs(cachedir, exist_ok=True) flatpakrunfile = os.path.join(cachedir, "insideflatpak.tmp") diff --git a/src/wemod_launcher/pfx/__init__.py b/src/wemod_launcher/pfx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/wemod_launcher/pfx/wine_utils.py b/src/wemod_launcher/pfx/wine_utils.py new file mode 100644 index 0000000..b70b53a --- /dev/null +++ b/src/wemod_launcher/pfx/wine_utils.py @@ -0,0 +1,10 @@ +from pathlib import Path, PureWindowsPath, PurePosixPath, WindowsPath + + +class WineUtils: + @staticmethod + def native_path(path: str) -> PureWindowsPath | PurePosixPath: + if path.startswith("C:") or path.startswith("Z:"): + return PureWindowsPath(path) + + return PurePosixPath(path) diff --git a/setup.py b/src/wemod_launcher/prepare.py similarity index 88% rename from setup.py rename to src/wemod_launcher/prepare.py index fa779dd..feba5b9 100755 --- a/setup.py +++ b/src/wemod_launcher/prepare.py @@ -7,23 +7,23 @@ import shutil import subprocess -from coreutils import ( +from wemod_launcher.core_utils import ( log, pip, exit_with_message, ) -from corenodep import ( +from wemod_launcher.core_nodeps import ( load_conf_setting, save_conf_setting, check_dependencies, ) -from coreutils import ( +from wemod_launcher.core_utils import ( show_message, ) -from mainutils import ( +from wemod_launcher.main_utils import ( download_progress, ) @@ -190,7 +190,7 @@ def tk_check() -> None: def venv_manager() -> List[Optional[str]]: requirements_txt = os.path.join(SCRIPT_PATH, "requirements.txt") - tk_check() + # tk_check() if not check_dependencies(requirements_txt): pip_install = f"install -r '{requirements_txt}'" return_code = pip(pip_install) @@ -311,46 +311,38 @@ def self_update(path: List[Optional[str]]) -> List[Optional[str]]: return path -def check_flatpak(flatpak_cmd: Optional[List[str]]) -> List[str]: - if flatpak_cmd == None: - if "FLATPAK_ID" in os.environ or os.path.exists("/.flatpak-info"): - return ["True"] - return [] - else: - if "FLATPAK_ID" in os.environ or os.path.exists("/.flatpak-info"): - flatpak_start = [ - "flatpak-spawn", - "--host", - ] - - envlist = [ - "STEAM_COMPAT_TOOL_PATHS", - "STEAM_COMPAT_DATA_PATH", - "WINE_PREFIX_PATH", - "WINEPREFIX", - "WINE", - "SCANFOLDER", - "TROUBLESHOOT", - "WEMOD_LOG", - "WAIT_ON_GAMECLOSE", - "SELF_UPDATE", - "FORCE_UPDATE_WEMOD", - "REPO_STRING", - ] - for env in envlist: - if env in os.environ: - flatpak_start.append(f"--env={env}={os.environ[env]}") - infpr = os.getenv("WeModInfProtect", "1") - infpr = str(int(infpr) + 1) - - flatpak_start.append("--env=FROM_FLATPAK=true") - flatpak_start.append(f"--env=WeModInfProtect={infpr}") - flatpak_start.append("--") # Isolate command from command args - - if bool(flatpak_cmd): # if venv is set use it - flatpak_cmd = flatpak_start + flatpak_cmd - else: # if not use python executable - flatpak_cmd = flatpak_start + [sys.executable] +def check_flatpak(): + if "FLATPAK_ID" in os.environ or os.path.exists("/.flatpak-info"): + flatpak_start = [ + "flatpak-spawn", + "--host", + ] + + envlist = [ + "STEAM_COMPAT_TOOL_PATHS", + "STEAM_COMPAT_DATA_PATH", + "WINE_PREFIX_PATH", + "WINEPREFIX", + "WINE", + "SCANFOLDER", + "TROUBLESHOOT", + "WEMOD_LOG", + "WAIT_ON_GAMECLOSE", + "SELF_UPDATE", + "FORCE_UPDATE_WEMOD", + "REPO_STRING", + ] + for env in envlist: + if env in os.environ: + flatpak_start.append(f"--env={env}={os.environ[env]}") + infpr = os.getenv("WeModInfProtect", "1") + infpr = str(int(infpr) + 1) + + flatpak_start.append("--env=FROM_FLATPAK=true") + flatpak_start.append(f"--env=WeModInfProtect={infpr}") + flatpak_start.append("--") # Isolate command from command args + + flatpak_cmd = flatpak_start + [sys.executable] return flatpak_cmd diff --git a/src/wemod_launcher/pyinstaller.py b/src/wemod_launcher/pyinstaller.py new file mode 100644 index 0000000..57ecb7b --- /dev/null +++ b/src/wemod_launcher/pyinstaller.py @@ -0,0 +1,20 @@ +import subprocess +from pathlib import Path + +HERE = Path(__file__).parent.absolute() +PATH_TO_MAIN = str(HERE / "cli.py") + + +def install(): + arglist = [ + "pdm", + "run", + "pyinstaller", + "--onefile", + "--windowed", + "--clean", + "--hidden-import tkinter", + "--collect-all tkinter", + PATH_TO_MAIN, + ] + subprocess.run(" ".join(arglist), stdout=subprocess.PIPE, shell=True) diff --git a/src/wemod_launcher/utils/__init__.py b/src/wemod_launcher/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/wemod_launcher/utils/configuration.py b/src/wemod_launcher/utils/configuration.py new file mode 100644 index 0000000..652e596 --- /dev/null +++ b/src/wemod_launcher/utils/configuration.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: AGPL-3.0-only + +import os +import sys +from consts import Constants +import configparser + +CT = None +CT = Constants() + +class ConfigManager: + def __init__(self, env_vars=False, cmd=False, config_file=None): + # Use super().__setattr__ to set initial attributes + super().__setattr__('_env_vars', env_vars) + super().__setattr__('_cmd', cmd) + super().__setattr__('_config', {}) + super().__setattr__('_config_file', config_file or CT.config_path) + super().__setattr__('_logger_obj', None) + super().__setattr__('AppArgs', []) + super().__setattr__('PassArgs', []) + if cmd: + self._args_grabber() + + def __getattr__(self, name): + """ + Dynamically fetch values from the config or environment. + """ + # Use getattr to safely access attributes + env_vars = getattr(self, '_env_vars') + + if env_vars: + if name in os.environ: + return os.environ[name] + + self._load_config() + cmd = getattr(self, '_cmd') + + if cmd: + app_args = getattr(self, 'AppArgs') + for arg in app_args: + s, e = arg.split("=", 1) + s = s.strip() + if s == name: + return e.strip() + + # If the key exists in the config, return the value + config = getattr(self, '_config') + if name in config: + return config[name] + + # If the key does not exist in the config, return None + return None + + def __setattr__(self, name, value): + """ + Modify values in the config or environment. + """ + # Use getattr to safely access attributes + env_vars = getattr(self, '_env_vars', False) + + if env_vars: + os.environ[name] = str(value) + return + + # Use super().__setattr__ for special attributes + if name.startswith('_') or name in ['AppArgs', 'PassArgs']: + super().__setattr__(name, value) + return + + self._load_config() + config = getattr(self, '_config') + config[name] = str(value) # Convert to string for consistent storage + self._save_config() + + def __delattr__(self, name): + """ + Remove values from the config or environment. + """ + # Use getattr to safely access attributes + env_vars = getattr(self, '_env_vars', False) + + if env_vars: + if name in os.environ: + del os.environ[name] + return + + self._load_config() + config = getattr(self, '_config') + + if name not in config: + # You might want to replace this with proper logging + print(f"ERROR: Config key '{name}' does not exist") + return + + del config[name] + self._save_config() + + def _load_config(self, config_file=None): + """ + Load the configuration from a config file. + """ + config_file = config_file or self._config_file + + # Ensure the configuration file exists + if not os.path.exists(config_file): + try: + # Create the directory if it doesn't exist + os.makedirs(os.path.dirname(config_file), exist_ok=True) + # Create an empty configuration file + with open(config_file, 'w') as f: + pass + except OSError as e: + return + + config = configparser.ConfigParser() + config.read(config_file) + + # Load all options from the CT.config_section section + if CT.config_section in config: + self._config.update({ + key: config[CT.config_section].get(key) + for key in config[CT.config_section] + }) + + def _save_config(self, config_file=None): + """ + Save the current configuration to a config file. + """ + config_file = config_file or self._config_file + try: + config = configparser.ConfigParser() + + # Ensure the CT.config_section section exists + if CT.config_section not in config: + config.add_section(CT.config_section) + + # Set all config values in the CT.config_section section + for key, value in self._config.items(): + config.set(CT.config_section, key, str(value)) + + os.makedirs(os.path.dirname(config_file), exist_ok=True) + with open(config_file, "w") as configfile: + config.write(configfile) + except IOError as e: + # Logger can't be used here as it wound import in a loop + raise f"Error saving config file: {e}" + + def _args_grabber(self, args=sys.argv): + arguments = sys.argv[1:] + End = self.script_cli_end or CT.script_cli_end + Start = self.script_cli_start or CT.script_cli_start + AllowOnlyEnd = self.allow_only_cli_end or CT.allow_only_cli_end + EndIn = bool(End in arguments) + if EndIn and Start in arguments: + e = arguments.index(End) + s = arguments.index(Start) + 1 + if e > s+1: + self.AppArgs = arguments[s:e] + self.PassArgs = arguments[:s-1] + self.PassArgs += arguments[:e+1] + else: + arguments.remove(End) + arguments.remove(Start) + self.PassArgs = arguments + return + elif AllowOnlyEnd and End in arguments: + e = arguments.index(End) + if e > 0: + self.AppArgs = arguments[:e] + self.PassArgs = arguments[e+1:] + else: + self.PassArgs = arguments[1:] + + + diff --git a/src/wemod_launcher/utils/consts.py b/src/wemod_launcher/utils/consts.py new file mode 100644 index 0000000..6d43005 --- /dev/null +++ b/src/wemod_launcher/utils/consts.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: AGPL-3.0-only + +import os +import sys +from pathlib import Path + +class Constants: + def __init__(self): + # Static values dictionary + self._static_values = { + "app_name": "WeMod Launcher", + "version": "2.0.0", + "script_name": Path("wemod.py"), + "config_section": "Settings", + "conf_folder_name": "wemod-launcher", + "repo_name": "wemod-launcher", + "repo_user": "DeckCheatz", + "bat_start": ["start"], + "script_cli_end": "--wemod-launcher-cli-end", + "script_cli_start": "--wemod-launcher-cli-start", + "allow_only_cli_end": True + } + + # Lazy cache + self._lazy_cache = {} + + self._logger_obj = None + + def __getattr__(self, name): + """ + Intercept access to attributes at the class level. + Handles static values and lazy-loaded values via generators. + """ + # First, check if the attribute exists in the object's __dict__ + try: + return object.__getattribute__(self, name) + except AttributeError: + # Check if the key is a static value + if name in self._static_values: + return self._static_values[name] + + # Check if the value has been cached + if name in self._lazy_cache: + return self._lazy_cache[name] + + # If it's not cached, dynamically look for a generator method + generator_name = f"_{name}" + + # Use object.__getattribute__ to avoid recursion + try: + generator_func = object.__getattribute__(self, generator_name) + if callable(generator_func): + # Cache the result of the generator + result = generator_func() + self._lazy_cache[name] = result + return result + except AttributeError: + pass + + # If no generator method found, raise an AttributeError + raise AttributeError(f"No such constant: {name}") + + def get(self, key): + """Retrieve a value by key""" + return self._static_values.get(key) if key in self._static_values else getattr(self, key) + + def _is_compiled(self): + """Lazy-loaded method for checking if script is compiled""" + return getattr(sys, "frozen", False) + + def _script_path(self): + """ + Generator function for ScriptPath. + """ + if self.is_compiled: + path = Path(sys.executable).absolute() + return path + else: + path = Path(__file__).parent / self.script_name + path = path.absolute() + for i in range(1,3): + if path.is_file(): + return path.absolute() + else: + path = path.parent.parent / self.script_name + + # Logger can't be used here as it wound import in a loop + raise f"Error script file ({self.script_name}) was not found" + + def _script_stem(self): + return self.script_path.stem + + def _script_dir(self): + return self.script_path.parent.absolute() + + def _git_base(self): + if self.is_compiled: + return self.script_dir.absolute() + else: + path = self.script_path.parent / ".git" + for i in range(1,5): + if path.is_dir(): + return path.parent.absolute() + else: + path = path.parent.parent / ".git" + + # Logger can't be used here as it wound import in a loop + raise f"Error git base (.git folder) was not found" + + def _global_conf_dir(self): + global_conf = Path.home() / ".config" / self.conf_folder_name + global_conf.mkdir(mode=0o774, parents=True, exist_ok=True) + return global_conf + + def _config_path(self): + local_conf = self.script_dir / (self.script_stem + ".conf") + if local_conf.is_file(): + return local_conf + + local_git_conf = self.git_base / (self.script_stem + ".conf") + if local_git_conf.is_file(): + return local_git_conf + + global_conf = self.global_conf_dir / (self.script_stem + ".conf") + return global_conf + + def _config_dir(self): + return self.config_path.parent + + def _log_dir(self): + logs = self.git_base / "logs" + if self.is_compiled or not logs.is_dir(): + logs = self.global_conf_dir / "logs" + return logs + + def _bat_file(self): + bat = self.git_base / (self.script_stem + ".bat") + if self.IsCompiled or not bat.is_file(): + bat = self.global_conf_dir / (self.script_stem + ".bat") + + bat.parent.mkdir(mode=0o774, parents=True, exist_ok=True) + return bat + + def _bat_exists(self): + return self.bat_file.exists() + + def _winetricks(self): + tricks = self.git_base / "winetricks" + if self.is_compiled or not tricks.is_file(): + tricks = self.global_conf_dir / "winetricks" + + tricks.parent.mkdir(mode=0o774, parents=True, exist_ok=True) + return tricks + diff --git a/src/wemod_launcher/utils/logger.py b/src/wemod_launcher/utils/logger.py new file mode 100644 index 0000000..74a077e --- /dev/null +++ b/src/wemod_launcher/utils/logger.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: AGPL-3.0-only + +from configuration import ConfigManager +from consts import Constants +from pathlib import Path +import datetime +import logging +import sys +import os + +CT, CM = None, None +CT = Constants() +CM = ConfigManager() + +def Logger(): + # Define and register custom logging levels + CustomLogger.register_custom_levels({ + 'DOWNLOAD': logging.INFO - 3, + 'BUILDING': logging.INFO - 4, + 'GUI_CONSOLE': logging.INFO - 5, + 'STEAM': logging.INFO - 6 + }) + + log_obj = CustomLogger() + try: + log_obj.info("The logger was instantiated", module="logger") + except Exception as e: + print(f"{CT.AppName}: Unrecoverable Error, Logger not working:\n{e}") + exit(1) + + crit = False + for msg in log_obj.futuremsg: + log_obj.mlog(msg[0], "logger", msg[1]) + if msg[1] == "CRITICAL": + crit = True + if crit: + exit(1) + + log_obj.futuremsg = [] + return log_obj + + +class CustomLogger(logging.Logger): + def __init__(self): + name = CT.app_name + super().__init__(name) + self.futuremsg = [] + + level = CM.log_level + self.list_levels() + + if level in self.levels.keys() or level in self.levels: + self.setLevel(level) + elif level == None: + self.setLevel(logging.INFO) + else: + self.futuremsg.append([logging.WARN, f"The provided log level {level} was not found, using INFO level\nThe available levels are:\n{self.levels}"]) + self.setLevel(logging.INFO) + + log_dir = CM.log_dir + if log_dir == None: + log_dir = CT.log_dir + else: + log_dir = Path(log_dir) + + if not log_dir.is_absolute(): + rel = CM.log_rel + if rel: + log_dir = CT.git_base / log_dir + else: + log_dir = CT.global_conf_dir / log_dir + + log_dir.mkdir(mode=0o774, parents=True, exist_ok=True) + os.chmod(log_dir, 0o774) + + log_name = CM.log_name + if not log_name: + # Create a file handler with a filename that includes the current time + log_file_time = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + CM.log_name = log_file_time + ".log" + + log_file = log_dir / CM.log_name + + try: + handler = logging.FileHandler(log_file) + formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(mmodule)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + handler.setFormatter(formatter) + self.addHandler(handler) + except Exception as e: + self.futuremsg.append([logging.CRITICAL,f"Error, can't create log file: {e}"]) + + # Create a console handler + console_handler = logging.StreamHandler(sys.stdout) + console_formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(name)s | %(mmodule)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + console_handler.setFormatter(console_formatter) + self.addHandler(console_handler) + + @staticmethod + def register_custom_levels(levels): + """Register custom logging levels dynamically.""" + for name, level in levels.items(): + logging.addLevelName(level, name) + #CustomLogger.CUSTOM_LEVELS[name] = level + + def mlog(self, level, module = None, message=""): + if not module: + module = "main" + strmodule = str(module).replace("\n"," ").upper() + + strmsg = str(message) + splitmsg = strmsg.split("\n") + filteredmsg = [] + for msg in splitmsg: + if msg: + filteredmsg.append(msg) + + if len(filteredmsg) < 1 or (len(filteredmsg) == 1 and not filteredmsg[0]): + self.log(level, f"Empty message was logged for the module {strmodule}", extra={'mmodule': 'LOGGER'}) + elif len(filteredmsg) == 1: + self.log(level, f"{filteredmsg[0]}", extra={'mmodule': strmodule}) + elif len(filteredmsg) > 1: + self.log(level, f"Logging {len(filteredmsg)} Lines for the module {strmodule}", extra={'mmodule': 'LOGGER'}) + for msg in filteredmsg: + self.log(level, f"{msg}", extra={'mmodule': strmodule}) + + #self.log(level, f"Logged {len(filteredmsg)} Lines", extra={'mmodule': 'LOGGER'}) + + # Shortcut functions for logging with custom levels + def custom_log(self, level_name, message, module = None): + level = level or logging.INFO + self.mlog(level, message, module) + + def steam_output(self, message, module = None): + self.mlog(self.levels['STEAM'], module, f"{message}") + + def building_output(self, message, module = None): + self.mlog(self.levels['BUILDING'], module, f"{message}") + + def download_output(self, message, module = None): + self.mlog(self.levels['DOWNLOAD'], module, f"{message}") + + def gui_console_output(self, message, module = None): + self.mlog(self.levels['GUI_CONSOLE'], module, f"{message}") + + def debug(self, message, module = None): + self.mlog(logging.DEBUG, module, f"{message}") + + def info(self, message, module = None): + self.mlog(logging.INFO, module, f"{message}") + + def warning(self, message, module = None): + self.mlog(logging.WARNING, module, f"{message}") + + def error(self, message, module = None): + self.mlog(logging.ERROR, module, f"{message}") + + def critical(self, message, module = None): + self.mlog(logging.CRITICAL, module, f"{message}") + + def list_levels(self): + """Prints all available log levels including custom levels.""" + self.levels = {level: name for level, name in logging._levelToName.items() if isinstance(level, int)} + return self.levels diff --git a/wemod b/src/wemod_launcher/wemod similarity index 98% rename from wemod rename to src/wemod_launcher/wemod index 312ab8c..0a2e839 100755 --- a/wemod +++ b/src/wemod_launcher/wemod @@ -10,7 +10,7 @@ import subprocess from typing import Optional # Import core utils without download dependencies -from corenodep import ( +from core_nodeps import ( join_lists_with_delimiter, split_list_by_delimiter, load_conf_setting, @@ -21,7 +21,7 @@ from corenodep import ( ) # Import core utils -from coreutils import ( +from core_utils import ( exit_with_message, script_manager, popup_options, @@ -32,7 +32,7 @@ from coreutils import ( ) # Import main utils -from mainutils import ( +from main_utils import ( find_closest_compatible_release, unpack_zip_with_progress, get_github_releases, @@ -43,8 +43,8 @@ from mainutils import ( deref, ) -# Import from setup -from setup import ( +# Import from prepare +from prepare import ( check_flatpak, venv_manager, self_update, @@ -111,8 +111,8 @@ if __name__ == "__main__": # Import utils that need constants -from constutils import ( - scanfolderforversions, +from const_utils import ( + scan_compat_for_versions, troubleshooter, ensure_wine, winetricks, @@ -152,7 +152,7 @@ def syncwemod( save_conf_setting("PackagePrefix", package_prefix) package_prefix = "true" if package_prefix and folder == None and package_prefix.lower() == "true": - from mainutils import copy_folder_with_progress + from main_utils import copy_folder_with_progress log( "Prefix packaging was requested with PACKAGEPREFIX=true in front of the command" @@ -318,8 +318,9 @@ def init(proton: str, iswine: bool = False) -> None: f"Looking for compatible wine prefixes in '{STEAM_COMPAT_FOLDER}' with proton version '{current_version_parts[0]}.{current_version_parts[1]}'" ) - # Get closest version that has WeMod installed - closest_version, closest_prefix_folder = scanfolderforversions( + # Get closest version that has wemod installed + closest_version, closest_prefix_folder = scan_compat_for_versions( +>>>>>>> origin/refactor-shymega:src/wemod_launcher/wemod current_version_parts ) cut_version = parse_version(closest_version) @@ -329,7 +330,7 @@ def init(proton: str, iswine: bool = False) -> None: f"Found was '{cut_version[0]}.{cut_version[1]}' on '{current_version_parts[0]}.{current_version_parts[1]}'" ) - from mainutils import copy_folder_with_progress + from main_utils import copy_folder_with_progress response = "No" if ( diff --git a/src/wemod_launcher/wemod.py b/src/wemod_launcher/wemod.py new file mode 120000 index 0000000..cc5af10 --- /dev/null +++ b/src/wemod_launcher/wemod.py @@ -0,0 +1 @@ +wemod \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29