diff --git a/.github/workflows/windows-build-release.yml b/.github/workflows/windows-build-release.yml new file mode 100644 index 00000000..318a29b6 --- /dev/null +++ b/.github/workflows/windows-build-release.yml @@ -0,0 +1,46 @@ +on: + workflow_dispatch: + push: + branches: + - master + - inno6-installer + pull_request: + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Freeze Installer + run: | + pyinstaller build.spec + - name: Build Installer + run: | + iscc installer.iss + - name: Freeze Portable + run: | + pyinstaller --distpath dist-portable build-portable.spec + - name: Zip Portable + shell: pwsh + run: | + Copy-Item -Path assets -Destination dist-portable\ -Recurse + Copy-Item -Path configs -Destination dist-portable\ -Recurse + Compress-Archive -Path dist-portable -DestinationPath GameFacePortable.zip + - name: Upload installer + uses: actions/upload-artifact@v3 + with: + name: 'Windows Installer Release' + path: '\a\project-gameface\project-gameface\Output\GameFace Installer.exe' + - name: Upload portable + uses: actions/upload-artifact@v3 + with: + name: 'Windows Portable Release' + path: '\a\project-gameface\project-gameface\GameFacePortable.zip' diff --git a/README.md b/README.md index 29d86a7a..ca0b6192 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,20 @@ Project Gameface helps gamers control their mouse cursor using their head moveme -# Download +# Download + +## Single portable directory + 1. Download the program from [Release section](../../releases/) 2. Run `run_app.exe` +## Installer + +1. Download the Gameface-Installer.exe from [Release section](../../releases/) +2. Install it +3. Run from your Windows shortucts/desktop + # Model used MediaPipe Face Landmark Detection API [Task Guide](https://developers.google.com/mediapipe/solutions/vision/face_landmarker) @@ -31,7 +40,7 @@ MediaPipe Face Landmark Detection API [Task Guide](https://developers.google.com ## Installation > Environment >- Windows ->- Python 3.9 +>- Python 3.10 ``` pip install -r requirements.txt ``` @@ -88,7 +97,13 @@ gesture_name: [device_name, action_name, threshold, trigger_type] # Build + +## Pyinstaller / Frozen app ``` pyinstaller build.spec ``` +# Build Installer + +1. Install [inno6](https://jrsoftware.org/isdl.php#stable) +2. Build using the `installer.iss` file diff --git a/assets/themes/google_theme.json b/assets/themes/google_theme.json index b9fe9d4e..7ff52a30 100644 --- a/assets/themes/google_theme.json +++ b/assets/themes/google_theme.json @@ -48,7 +48,7 @@ "corner_radius": 500, "border_width": 0, "button_length": 0, - "fg_Color": ["#444746", "#4A4D50"], + "fg_color": ["#444746", "#4A4D50"], "progress_color": ["#64DD17", "#1f538d"], "button_color": ["#8F8F8F", "#D5D9DE"], "button_hover_color": ["gray20", "gray100"], diff --git a/build-portable.spec b/build-portable.spec new file mode 100644 index 00000000..0f259e03 --- /dev/null +++ b/build-portable.spec @@ -0,0 +1,57 @@ +# -*- mode: python ; coding: utf-8 -*- + +from pathlib import Path +import mediapipe +import customtkinter + +block_cipher = None + +mp_init = Path(mediapipe.__file__) +mp_modules = Path(mp_init.parent,"modules") + +ctk_init = Path(customtkinter.__file__) +ctk_modules = Path(ctk_init.parent,"modules") + + + +app = Analysis( + ['run_app.py'], + pathex=[], + binaries=[], + datas=[(mp_modules.as_posix(), 'mediapipe/modules'), + ('assets','assets'), + ('configs','configs'), + (ctk_init.parent.as_posix(), 'customtkinter')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz_app = PYZ(app.pure, app.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz_app, + app.scripts, + app.binaries, + app.zipfiles, + app.datas, + [], + name='run_app', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='assets/images/icon.ico', +) diff --git a/build.spec b/build.spec index f9e3c58d..ed216f80 100644 --- a/build.spec +++ b/build.spec @@ -48,6 +48,7 @@ exe_app = EXE( disable_windowed_traceback=False, argv_emulation=False, target_arch=None, + icon='assets/images/icon.ico', codesign_identity=None, entitlements_file=None, ) diff --git a/installer.iss b/installer.iss new file mode 100644 index 00000000..5cd53243 --- /dev/null +++ b/installer.iss @@ -0,0 +1,46 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "Gameface" +#define MyAppVersion "1" +#define MyAppExeName "run_app.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{B266B614-4113-4DB7-9A30-4250FECA5009} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +DefaultDirName={autopf}\{#MyAppName} +DisableProgramGroupPage=yes +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +OutputBaseFilename=GameFace Installer +SetupIconFile=assets\images\icon.ico +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; +Name: "autostarticon"; Description: "{cm:AutoStartProgram,{#MyAppName}}"; GroupDescription: "{cm:AdditionalIcons}"; + + +[Files] +Source: "dist\project_gameface\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "dist\project_gameface\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Parameters: "/auto"; Tasks: autostarticon +; Name: "{commonstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Parameters: "/auto"; Tasks: autostarticon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Verb: runas; Flags: postinstall skipifsilent shellexec runascurrentuser waituntilterminated; + diff --git a/requirements.txt b/requirements.txt index 0c89912b..1bad4ace 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,8 @@ matplotlib==3.7.1 opencv-contrib-python==4.7.0.72 psutil==5.9.4 pyautogui==0.9.53 -customtkinter==5.1.2 -PyDirectInput==1.0.4 -pywin32==306 -mediapipe==0.9.3.0 \ No newline at end of file +customtkinter==5.2.0 +mediapipe==0.9.3.0 +PyDirectInput==1.0.4; sys_platform == 'win32' +pywin32==306; sys_platform == 'win32' +pyinstaller==5.11.0 diff --git a/run_app.py b/run_app.py index 2ddb7640..f2807bc5 100644 --- a/run_app.py +++ b/run_app.py @@ -14,6 +14,7 @@ import logging import sys +import os import customtkinter import src.gui as gui @@ -21,16 +22,22 @@ from src.pipeline import Pipeline FORMAT = "%(asctime)s %(levelname)s %(name)s: %(funcName)s: %(message)s" -logging.basicConfig(format=FORMAT, - level=logging.INFO, - handlers=[ - logging.FileHandler("log.txt", mode='w'), - logging.StreamHandler(sys.stdout) - ]) +log_path = os.environ['USERPROFILE']+'\Gameface' +if not os.path.isdir(log_path): + os.mkdir(log_path) + +logging.basicConfig( + format=FORMAT, + level=logging.INFO, + handlers=[ + logging.FileHandler(log_path+'\log.txt', mode="w"), + logging.StreamHandler(sys.stdout), + ], +) -class MainApp(gui.MainGui, Pipeline): +class MainApp(gui.MainGui, Pipeline): def __init__(self, tk_root): super().__init__(tk_root) # Wait for window drawing. diff --git a/src/config_manager.py b/src/config_manager.py index ad7a8bbc..ab6d60d4 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -18,6 +18,7 @@ import tkinter as tk import time import shutil +import os from pathlib import Path from src.singleton_meta import Singleton @@ -25,12 +26,20 @@ VERSION = "0.3.30" -DEFAULT_JSON = Path("configs/default.json") -BACKUP_PROFILE = Path("configs/default") +DEFAULT_JSON = Path(f"C:/Users/{os.getlogin()}/Gameface/configs/default.json") +BACKUP_PROFILE = Path(f"C:/Users/{os.getlogin()}/Gameface/configs/default") logger = logging.getLogger("ConfigManager") +if not os.path.isdir(f"C:/Users/{os.getlogin()}/Gameface/configs/"): + shutil.copytree("configs", f"C:/Users/{os.getlogin()}/Gameface/configs/") + os.mkdir(f"C:/Users/{os.getlogin()}/Gameface/configs/") + +if not os.path.isdir(f"C:/Users/{os.getlogin()}/Gameface/configs/default"): + os.mkdir(f"C:/Users/{os.getlogin()}/Gameface/configs/default") + + class ConfigManager(metaclass=Singleton): def __init__(self): diff --git a/src/gui/pages/page_select_camera.py b/src/gui/pages/page_select_camera.py index e540903a..7bf12c04 100644 --- a/src/gui/pages/page_select_camera.py +++ b/src/gui/pages/page_select_camera.py @@ -54,7 +54,7 @@ def __init__(self, master, **kwargs): self.label.grid(row=1, column=0, padx=10, pady=(20, 10), sticky="nw") # Empty radio buttons - self.radio_var = tkinter.IntVar(0) + self.radio_var = tkinter.IntVar(value=0) self.prev_radio_value = None self.radios = []