diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml
index 391a13c5..904d7482 100644
--- a/.github/workflows/build_win.yml
+++ b/.github/workflows/build_win.yml
@@ -19,7 +19,7 @@ jobs:
- name: Checkout Source
uses: actions/checkout@v4
- - name: Download Artifacts
+ - name: Download Assets
uses: actions/download-artifact@v4
with:
name: nw-assets
diff --git a/.github/workflows/build_win_launcher.yml b/.github/workflows/build_win_launcher.yml
new file mode 100644
index 00000000..901f8a0d
--- /dev/null
+++ b/.github/workflows/build_win_launcher.yml
@@ -0,0 +1,45 @@
+name: BuildWindowsLauncher
+
+on: workflow_dispatch
+
+jobs:
+ buildLauncherWin64:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout Source
+ uses: actions/checkout@v4
+
+ - name: Build Launcher
+ id: build
+ run: |
+ Set-Location setup\windows
+ .\make.ps1
+
+ - name: Upload Unsigned Artifacts
+ id: upload-unsigned-artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: launcher
+ path: setup/windows/novelWriter.exe
+ if-no-files-found: error
+ retention-days: 14
+
+ - name: Submit Signing Request
+ uses: signpath/github-action-submit-signing-request@v1
+ with:
+ api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
+ organization-id: "0471e52c-66fa-4e9a-bfb9-36167095ca3f"
+ project-slug: "novelWriter"
+ signing-policy-slug: "release-signing"
+ artifact-configuration-slug: "launcher"
+ github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
+ wait-for-completion: true
+ output-artifact-directory: "."
+
+ - name: Upload Signed Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: launcher-signed
+ path: novelWriter.exe
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 90e51a5d..fd69c45f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ setup.iss
/novelwriter.desktop
/novelWriter.pyw
/setup/macos/Info.plist
+/setup/windows/build
.venv
# Translations
diff --git a/setup/win_setup_embed.iss b/setup/win_setup_embed.iss
index f4c55ac9..e1d15de7 100644
--- a/setup/win_setup_embed.iss
+++ b/setup/win_setup_embed.iss
@@ -5,7 +5,6 @@
#define nwAppVersion "%%version%%"
#define nwAppPublisher "novelWriter"
#define nwAppURL "https://novelWriter.io"
-#define nwAppExeName "novelWriter.pyw"
[Setup]
AppId={{459A75D0-951F-4932-9809-6002EC8E733E}
@@ -50,15 +49,15 @@ Type: filesandordirs; Name: "{app}\novelwriter\*"
Source: "{#nwAppDir}\novelWriter\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
-Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; IconFilename: "{app}\novelwriter.ico"
-Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; IconFilename: "{app}\novelwriter.ico"; Tasks: desktopicon;
+Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\novelWriter.exe"; IconFilename: "{app}\novelWriter.ico"
+Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\novelWriter.exe"; IconFilename: "{app}\novelWriter.ico"; Tasks: desktopicon;
[Run]
-Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
+Filename: "{app}\novelWriter.exe"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Registry]
Root: HKA; Subkey: "Software\Classes\.nwx\OpenWithProgids"; ValueType: string; ValueName: "novelWriterProject.nwx"; ValueData: ""; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx"; ValueType: string; ValueName: ""; ValueData: "novelWriter Project File"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\novelwriter\assets\icons\x-novelwriter-project.ico"
-Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pythonw.exe"" ""{app}\{#nwAppExeName}"" ""%1"""
-Root: HKA; Subkey: "Software\Classes\Applications\{#nwAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""; Flags: uninsdeletekey
+Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\novelWriter.exe"" ""%1"""
+Root: HKA; Subkey: "Software\Classes\Applications\novelWriter.exe\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""; Flags: uninsdeletekey
diff --git a/setup/windows/CMakeLists.txt b/setup/windows/CMakeLists.txt
new file mode 100644
index 00000000..4f1ec280
--- /dev/null
+++ b/setup/windows/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(novelWriter LANGUAGES CXX)
+
+add_executable(novelWriter main.cpp launcher.rc)
diff --git a/setup/windows/launcher.rc b/setup/windows/launcher.rc
new file mode 100644
index 00000000..d8ad36e2
--- /dev/null
+++ b/setup/windows/launcher.rc
@@ -0,0 +1,22 @@
+IDI_ICON1 ICON DISCARDABLE "novelwriter.ico"
+1 VERSIONINFO
+FILEVERSION 1,0,0,0
+PRODUCTVERSION 1,0,0,0
+FILEOS 0x4
+FILETYPE 0x1
+{
+ BLOCK "StringFileInfo" {
+ BLOCK "040904b0" {
+ VALUE "CompanyName", "Veronica Berglyd Olsen\0"
+ VALUE "FileDescription", "novelWriter Launcher\0"
+ VALUE "FileVersion", "1.0.0.0\0"
+ VALUE "LegalCopyright", "© 2025 Veronica Berglyd Olsen\0"
+ VALUE "OriginalFilename", "novelWriter.exe\0"
+ VALUE "ProductName", "novelWriter\0"
+ VALUE "ProductVersion", "1.0.0.0\0"
+ }
+ }
+ BLOCK "VarFileInfo" {
+ VALUE "Translation", 0x0809, 1200
+ }
+}
diff --git a/setup/windows/main.cpp b/setup/windows/main.cpp
new file mode 100644
index 00000000..052f57c6
--- /dev/null
+++ b/setup/windows/main.cpp
@@ -0,0 +1,51 @@
+/*
+** novelWriter - Windows Launcher
+** ==============================
+**
+** This file is a part of novelWriter
+** Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful, but
+** WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+
+void _tmain( int argc, TCHAR *argv[] ) {
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&pi, sizeof(pi));
+
+ TCHAR fileName[MAX_PATH];
+ auto pathlen = GetModuleFileName(NULL, fileName, MAX_PATH);
+ fileName[pathlen - 15] = 0;
+ SetCurrentDirectory(fileName);
+ std::cout << "WorkDir: " << fileName << std::endl;
+
+ TCHAR cmd[MAX_PATH] = "pythonw.exe novelWriter.pyw";
+ if (argc > 1) {
+ _tcscat_s(cmd, TEXT(" "));
+ _tcscat_s(cmd, argv[1]);
+ }
+ std::cout << "Command: " << cmd << std::endl;
+
+ CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+
+ return;
+}
diff --git a/setup/windows/make.ps1 b/setup/windows/make.ps1
new file mode 100644
index 00000000..2056ce63
--- /dev/null
+++ b/setup/windows/make.ps1
@@ -0,0 +1,11 @@
+if (Test-Path .\build) {
+ Remove-Item .\build -Recurse
+}
+New-Item -Path . -Name build -ItemType Directory
+Set-Location .\build
+
+cmake -S .. -B .
+cmake --build . --config Release
+
+Set-Location ..
+Copy-Item .\build\Release\novelWriter.exe . -Force
diff --git a/setup/windows/novelWriter.exe b/setup/windows/novelWriter.exe
new file mode 100644
index 00000000..5026cddb
Binary files /dev/null and b/setup/windows/novelWriter.exe differ
diff --git a/setup/windows/novelWriter.ico b/setup/windows/novelWriter.ico
new file mode 100644
index 00000000..54301ffe
Binary files /dev/null and b/setup/windows/novelWriter.ico differ
diff --git a/utils/build_windows.py b/utils/build_windows.py
index c6401dfd..1a94632a 100644
--- a/utils/build_windows.py
+++ b/utils/build_windows.py
@@ -44,8 +44,9 @@ def prepareCode(outDir: Path) -> None:
ROOT_DIR / "CREDITS.md",
ROOT_DIR / "LICENSE.md",
ROOT_DIR / "requirements.txt",
- SETUP_DIR / "icons" / "novelwriter.ico",
SETUP_DIR / "iss_license.txt",
+ SETUP_DIR / "windows" / "novelWriter.ico",
+ SETUP_DIR / "windows" / "novelWriter.exe",
]
for item in files:
@@ -203,7 +204,7 @@ def main(args: argparse.Namespace) -> None:
outDir.mkdir()
libDir.mkdir()
- copySourceCode(outDir)
+ prepareCode(outDir)
embedPython(bldDir, outDir)
installRequirements(libDir)
removeRedundantQt(libDir)