diff --git a/.gitignore b/.gitignore index c46af629..ee9c8732 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,9 @@ Windows_and_Linux/config.json # Xcode user-specific data **/xcuserdata/ -**/project.xcworkspace/xcuserdata/ \ No newline at end of file +**/project.xcworkspace/xcuserdata/ + +Windows_and_Linux/.venv/ +Windows_and_Linux/packaging/ +Windows_and_Linux/dist/ +Windows_and_Linux/Writing Tools.spec \ No newline at end of file diff --git a/README's Linked Content/To Compile the Application Yourself.md b/README's Linked Content/To Compile the Application Yourself.md index 829c9e9e..b967a508 100644 --- a/README's Linked Content/To Compile the Application Yourself.md +++ b/README's Linked Content/To Compile the Application Yourself.md @@ -3,32 +3,59 @@ ### Windows and Linux Version build instructions: Here's how to compile it with PyInstaller and a virtual environment: -1. First, create and activate a virtual environment: +1. Open Terminal (or Command Prompt) and enter the Windows/Linux app folder: ```bash -# Install virtualenv if you haven't already -pip install virtualenv +cd /path/to/WritingTools/Windows_and_Linux +``` -# Create a new virtual environment -virtualenv myvenv +2. Create and activate a virtual environment: +```bash +python3 -m venv .venv -# Activate it -# On Windows: -myvenv\Scripts\activate -# On Linux: -source myvenv/bin/activate -``` +# Linux: +source .venv/bin/activate -2. Once activated, install the required packages: +# Windows (PowerShell): +.venv\Scripts\Activate.ps1 + +# Windows (cmd): +.venv\Scripts\activate.bat +``` +3. Install dependencies: ```bash +python -m pip install --upgrade pip pip install -r requirements.txt ``` -3. Build Writing Tools: +4. Build Writing Tools: ```bash python pyinstaller-build-script.py ``` +The compiled binary is written to: ~/Windows_and_Linux/dist/Writing Tools + +5. Linux only (optional, recommended): install locally with launcher icon: +```bash +bash build-and-install-local-linux.sh +``` + +This creates: +- `~/.local/bin/writing-tools` +- `~/.local/share/applications/writing-tools.desktop` + +Optional autostart: +```bash +bash build-and-install-local-linux.sh --enable-autostart +``` + +After local install, you can launch Writing Tools with: +```bash +writing-tools +``` + +No daily recompilation is needed unless you changed source code and want a newer build. + ### macOS Version (by [Aryamirsepasi](https://github.com/Aryamirsepasi)) build instructions: 1. **Install Xcode** diff --git a/README's Linked Content/To Run Writing Tools Directly from the Source Code.md b/README's Linked Content/To Run Writing Tools Directly from the Source Code.md index 329df109..37d97f82 100644 --- a/README's Linked Content/To Run Writing Tools Directly from the Source Code.md +++ b/README's Linked Content/To Run Writing Tools Directly from the Source Code.md @@ -34,5 +34,13 @@ Of course, you'll need to have [Python installed](https://www.python.org/downloa python3 main.py ``` +If you want a launcher icon in your app menu and a reusable local install (so you do not need to run from terminal every day), use: + +```bash +bash build-and-install-local-linux.sh +``` + +from the `Windows_and_Linux` folder. + ### [**◀️ Back to main page**](https://github.com/theJayTea/WritingTools) diff --git a/README.md b/README.md index c1a1fcd0..f4829ae4 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ Aside from being the only Windows/Linux program like Apple's Writing Tools, and ### **🐧 Linux (work-in-progress)**: [Run it from the source code](https://github.com/theJayTea/WritingTools/blob/main/README's%20Linked%20Content/To%20Run%20Writing%20Tools%20Directly%20from%20the%20Source%20Code.md) +[Compile and install locally with launcher icon](https://github.com/theJayTea/WritingTools/blob/main/README's%20Linked%20Content/To%20Compile%20the%20Application%20Yourself.md) + Writing Tools works well on x11. On Wayland, there are a few caveats: - [it works on XWayland apps](https://github.com/theJayTea/WritingTools/issues/34#issuecomment-2461633556) - [and it works if you disable Wayland for individual Flatpaks with Flatseal.](https://github.com/theJayTea/WritingTools/issues/93#issuecomment-2576511041) @@ -220,7 +222,7 @@ These instructions are for any Writing Tools version, using the OpenAI-Compatibl ## 👨‍💻 To Compile the Application Yourself: -[Instructions here!](https://github.com/theJayTea/WritingTools/blob/8713e5a5de63a7892b05a43b9753172e692768fb/README's%20Linked%20Content/To%20Compile%20the%20Application%20Yourself.md) +[Instructions here!](https://github.com/theJayTea/WritingTools/blob/main/README's%20Linked%20Content/To%20Compile%20the%20Application%20Yourself.md) ## 🌟 Contributors diff --git a/Windows_and_Linux/build-and-install-local-linux.sh b/Windows_and_Linux/build-and-install-local-linux.sh new file mode 100755 index 00000000..51c88f86 --- /dev/null +++ b/Windows_and_Linux/build-and-install-local-linux.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AUTOSTART_FLAG="" +SKIP_BUILD="no" + +print_usage() { + cat <<'USAGE' +Usage: + bash build-and-install-local-linux.sh [--enable-autostart|--disable-autostart] [--skip-build] + +Builds Writing Tools with PyInstaller, then installs it locally with: +- Launcher command: ~/.local/bin/writing-tools +- App menu entry: ~/.local/share/applications/writing-tools.desktop + +Options: + --enable-autostart Enable autostart after install + --disable-autostart Disable autostart after install + --skip-build Skip PyInstaller build and only run local install + -h, --help Show this help message +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --enable-autostart|--disable-autostart) + AUTOSTART_FLAG="$1" + ;; + --skip-build) + SKIP_BUILD="yes" + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "ERROR: Unknown option: $1" + print_usage + exit 1 + ;; + esac + shift +done + +if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required but was not found." + exit 1 +fi + +INSTALL_SCRIPT="${SCRIPT_DIR}/install-local-linux.sh" +if [[ ! -f "${INSTALL_SCRIPT}" ]]; then + echo "ERROR: Missing installer script at ${INSTALL_SCRIPT}" + exit 1 +fi + +pushd "${SCRIPT_DIR}" >/dev/null + +if [[ "${SKIP_BUILD}" != "yes" ]]; then + echo "Building with PyInstaller..." + python3 pyinstaller-build-script.py +else + echo "Skipping build as requested." +fi + +echo "Installing locally for current user..." +install_args=(--app-source "${SCRIPT_DIR}") +if [[ -n "${AUTOSTART_FLAG}" ]]; then + install_args+=("${AUTOSTART_FLAG}") +fi +bash "${INSTALL_SCRIPT}" "${install_args[@]}" + +popd >/dev/null + +echo "Done. Launch via: writing-tools" diff --git a/Windows_and_Linux/install-local-linux.sh b/Windows_and_Linux/install-local-linux.sh new file mode 100755 index 00000000..c3b04fd0 --- /dev/null +++ b/Windows_and_Linux/install-local-linux.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APP_SOURCE="${SCRIPT_DIR}" +ENABLE_AUTOSTART="" + +print_usage() { + cat <<'USAGE' +Usage: + bash install-local-linux.sh [--app-source ] [--enable-autostart|--disable-autostart] + +Installs Writing Tools for the current Linux user: +- App files: ~/.local/share/writingtools/app +- Launcher command: ~/.local/bin/writing-tools +- App menu entry: ~/.local/share/applications/writing-tools.desktop + +Options: + --app-source Source directory containing app assets and dist output + --enable-autostart Create ~/.config/autostart/writing-tools.desktop + --disable-autostart Remove ~/.config/autostart/writing-tools.desktop + -h, --help Show this help message +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --app-source) + shift + if [[ $# -eq 0 ]]; then + echo "ERROR: --app-source requires a value" + exit 1 + fi + APP_SOURCE="$1" + ;; + --enable-autostart) + ENABLE_AUTOSTART="yes" + ;; + --disable-autostart) + ENABLE_AUTOSTART="no" + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "ERROR: Unknown option: $1" + print_usage + exit 1 + ;; + esac + shift +done + +if [[ "${EUID}" -eq 0 ]]; then + echo "ERROR: Please run this as your regular desktop user, not root." + exit 1 +fi + +if [[ ! -d "${APP_SOURCE}" ]]; then + echo "ERROR: App source directory not found: ${APP_SOURCE}" + exit 1 +fi + +DIST_EXE="" +if [[ -x "${APP_SOURCE}/Writing Tools" ]]; then + DIST_EXE="${APP_SOURCE}/Writing Tools" +elif [[ -x "${APP_SOURCE}/dist/Writing Tools" ]]; then + DIST_EXE="${APP_SOURCE}/dist/Writing Tools" +else + echo "ERROR: Compiled binary not found in ${APP_SOURCE}" + echo "Build first with: python3 pyinstaller-build-script.py" + exit 1 +fi + +for required_dir in icons locales; do + if [[ ! -d "${APP_SOURCE}/${required_dir}" ]]; then + echo "ERROR: Missing required directory: ${APP_SOURCE}/${required_dir}" + exit 1 + fi +done + +for required_file in background.png background_dark.png background_popup.png background_popup_dark.png Latest_Version_for_Update_Check.txt options.json; do + if [[ ! -f "${APP_SOURCE}/${required_file}" ]]; then + echo "ERROR: Missing required file: ${APP_SOURCE}/${required_file}" + exit 1 + fi +done + +DATA_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}" +CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}" +INSTALL_ROOT="${DATA_HOME}/writingtools" +APP_DIR="${INSTALL_ROOT}/app" +BIN_DIR="${HOME}/.local/bin" +APPS_DIR="${DATA_HOME}/applications" +AUTOSTART_DIR="${CONFIG_HOME}/autostart" +LAUNCHER_PATH="${BIN_DIR}/writing-tools" +DESKTOP_PATH="${APPS_DIR}/writing-tools.desktop" +AUTOSTART_PATH="${AUTOSTART_DIR}/writing-tools.desktop" + +mkdir -p "${APP_DIR}" "${BIN_DIR}" "${APPS_DIR}" "${AUTOSTART_DIR}" + +install -m 0755 "${DIST_EXE}" "${APP_DIR}/Writing Tools" + +rm -rf "${APP_DIR}/icons" "${APP_DIR}/locales" +cp -a "${APP_SOURCE}/icons" "${APP_DIR}/icons" +cp -a "${APP_SOURCE}/locales" "${APP_DIR}/locales" + +for file_name in background.png background_dark.png background_popup.png background_popup_dark.png Latest_Version_for_Update_Check.txt; do + install -m 0644 "${APP_SOURCE}/${file_name}" "${APP_DIR}/${file_name}" +done + +# Preserve user-customized options on upgrades. +if [[ ! -f "${APP_DIR}/options.json" ]]; then + install -m 0644 "${APP_SOURCE}/options.json" "${APP_DIR}/options.json" +fi + +cat > "${LAUNCHER_PATH}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +APP_DIR="${XDG_DATA_HOME:-${HOME}/.local/share}/writingtools/app" +if [[ -d "${APP_DIR}/lib" ]]; then + export LD_LIBRARY_PATH="${APP_DIR}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" +fi +if [[ -d "${APP_DIR}/bin" ]]; then + export PATH="${APP_DIR}/bin:${PATH}" +fi +cd "${APP_DIR}" +exec "${APP_DIR}/Writing Tools" "$@" +EOF +chmod 0755 "${LAUNCHER_PATH}" + +cat > "${DESKTOP_PATH}" < "${AUTOSTART_PATH}" </dev/null 2>&1 && ! command -v xsel >/dev/null 2>&1 && ! command -v wl-copy >/dev/null 2>&1; then + echo "WARNING: No clipboard backend detected (xclip/xsel/wl-copy)." + echo " Install one to ensure copy/replace flow works." +fi + +if [[ "${XDG_SESSION_TYPE:-}" == "wayland" ]]; then + echo "NOTICE: Running in Wayland session; global hotkey/focus behavior may be limited." +fi diff --git a/Windows_and_Linux/pyinstaller-build-script.py b/Windows_and_Linux/pyinstaller-build-script.py index 56819886..b1a61560 100644 --- a/Windows_and_Linux/pyinstaller-build-script.py +++ b/Windows_and_Linux/pyinstaller-build-script.py @@ -1,9 +1,42 @@ -import os +import shutil import subprocess import sys +from pathlib import Path + + +ROOT_DIR = Path(__file__).resolve().parent +DIST_DIR = ROOT_DIR / 'dist' +BUILD_DIR = ROOT_DIR / 'build' +PYCACHE_DIR = ROOT_DIR / '__pycache__' + + +def remove_path(path: Path): + if path.is_dir(): + shutil.rmtree(path) + elif path.exists(): + path.unlink() + + +def require_path(path: Path, description: str): + if not path.exists(): + print(f"ERROR: Missing {description}: {path}") + sys.exit(1) + + +def run_preflight_checks(): + require_path(ROOT_DIR / 'main.py', 'entrypoint file') + require_path(ROOT_DIR / 'icons' / 'app_icon.ico', 'application icon') + + if shutil.which('pyinstaller') is None: + print('ERROR: pyinstaller is not available in PATH.') + print('Install dependencies first, then re-run this build script.') + print('Example: pip install -r requirements.txt') + sys.exit(1) def run_pyinstaller_build(): + run_preflight_checks() + pyinstaller_command = [ "pyinstaller", "--onefile", @@ -77,29 +110,28 @@ def run_pyinstaller_build(): try: # Remove previous build directories - if os.path.exists('dist'): - os.system("rmdir /s /q dist") - if os.path.exists('build'): - os.system("rmdir /s /q build") - if os.path.exists('__pycache__'): - os.system("rmdir /s /q __pycache__") + remove_path(DIST_DIR) + remove_path(BUILD_DIR) + remove_path(PYCACHE_DIR) # Run PyInstaller - subprocess.run(pyinstaller_command, check=True) - print("Build completed successfully!") + subprocess.run(pyinstaller_command, check=True, cwd=ROOT_DIR) + print(f"Build completed successfully! Output: {DIST_DIR / 'Writing Tools'}") # Clean up unnecessary files - if os.path.exists('build'): - os.system("rmdir /s /q build") - if os.path.exists('__pycache__'): - os.system("rmdir /s /q __pycache__") + remove_path(BUILD_DIR) + remove_path(PYCACHE_DIR) # No need to copy data files manually since they are included # in the executable using --add-data except subprocess.CalledProcessError as e: print(f"Build failed with error: {e}") + sys.exit(e.returncode or 1) + + except OSError as e: + print(f"Build failed with OS error: {e}") sys.exit(1) if __name__ == "__main__": - run_pyinstaller_build() \ No newline at end of file + run_pyinstaller_build()