mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Keep the two Windows executables from being one file
Windows matches a filename without regard to its case, so Dikte.exe and dikte.exe were one file in the installation directory: PyInstaller wrote the console build second and that is the only one that landed. Every launcher then opened a console window behind the tray, and closing it killed the application. The console one is dikte-cli.exe now, which the setup's shim names. The build reads the PE subsystem of both afterwards, since a filesystem that keeps the two names apart is exactly what the check would run on otherwise.
This commit is contained in:
+1
-1
@@ -70,7 +70,7 @@ there can tell that apart from an empty device list, which only means the tool
|
|||||||
that lists them is not installed.
|
that lists them is not installed.
|
||||||
|
|
||||||
The tests are split along the same line, and almost none of them are skipped.
|
The tests are split along the same line, and almost none of them are skipped.
|
||||||
1094 of the 1157 run on any machine, including every line of the Wayland, X11,
|
1096 of the 1159 run on any machine, including every line of the Wayland, X11,
|
||||||
macOS and Windows backends: the programs are faked at `shutil.which`, the
|
macOS and Windows backends: the programs are faked at `shutil.which`, the
|
||||||
frameworks and system libraries at the one function that loads them
|
frameworks and system libraries at the one function that loads them
|
||||||
(`paste._win_api`, `hotkey._win_input`). A test class says which system it is
|
(`paste._win_api`, `hotkey._win_input`). A test class says which system it is
|
||||||
|
|||||||
+3
-1
@@ -21,7 +21,9 @@ entry, a `dikte` command and, unless you untick it, a start at sign-in. It is
|
|||||||
signed with no certificate, so SmartScreen offers only **Don't run** until you
|
signed with no certificate, so SmartScreen offers only **Don't run** until you
|
||||||
press **More info**. Add/Remove Programs uninstalls it, and `dikte integrate`
|
press **More info**. Add/Remove Programs uninstalls it, and `dikte integrate`
|
||||||
and `dikte integrate --remove` are the sign-in entry on its own, for changing
|
and `dikte integrate --remove` are the sign-in entry on its own, for changing
|
||||||
your mind about that later.
|
your mind about that later. The `dikte` command is the same file install.ps1
|
||||||
|
writes, so over a checkout the setup takes it over and uninstalling takes it
|
||||||
|
away; run install.ps1 again to get the checkout's back.
|
||||||
|
|
||||||
From a checkout instead:
|
From a checkout instead:
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,33 @@ $env:DIKTE_ICO = $icon
|
|||||||
|
|
||||||
# 2. The application --------------------------------------------------------
|
# 2. The application --------------------------------------------------------
|
||||||
# Two executables in the one directory: Dikte.exe, which is windowed and is
|
# Two executables in the one directory: Dikte.exe, which is windowed and is
|
||||||
# what a shortcut starts, and dikte.exe, which has a console and is what the
|
# what a shortcut starts, and dikte-cli.exe, which has a console and is what
|
||||||
# `dikte` command runs.
|
# the `dikte` command runs. Their names differ by more than case on purpose;
|
||||||
|
# Windows would otherwise keep one file for both.
|
||||||
& python -m PyInstaller (Join-Path $root "packaging\dikte.spec") `
|
& python -m PyInstaller (Join-Path $root "packaging\dikte.spec") `
|
||||||
--distpath (Join-Path $build "dist") --workpath (Join-Path $build "work") `
|
--distpath (Join-Path $build "dist") --workpath (Join-Path $build "work") `
|
||||||
--noconfirm --clean
|
--noconfirm --clean
|
||||||
if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed" }
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed" }
|
||||||
|
|
||||||
|
# And the check that the two are really two. Windows matches a filename
|
||||||
|
# without regard to its case, so a rename that leaves them a case apart puts
|
||||||
|
# one file in the directory and whichever was written second is what both
|
||||||
|
# names find. It cost a windowed build once, silently: the machines the
|
||||||
|
# packaging is otherwise checked on all have case-sensitive filesystems, so
|
||||||
|
# this only ever shows up here. Subsystem 2 is windowed, 3 is a console, and
|
||||||
|
# it is the field the loader reads to decide which to give.
|
||||||
|
function Get-PeSubsystem($path) {
|
||||||
|
$bytes = [System.IO.File]::ReadAllBytes($path)
|
||||||
|
$header = [BitConverter]::ToInt32($bytes, 0x3C)
|
||||||
|
return [BitConverter]::ToUInt16($bytes, $header + 0x5C)
|
||||||
|
}
|
||||||
|
foreach ($pair in @(@("Dikte.exe", 2), @("dikte-cli.exe", 3))) {
|
||||||
|
$subsystem = Get-PeSubsystem (Join-Path $dist $pair[0])
|
||||||
|
if ($subsystem -ne $pair[1]) {
|
||||||
|
throw "$($pair[0]) is subsystem $subsystem, expected $($pair[1]): the two executables collided"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# 3. ffmpeg -----------------------------------------------------------------
|
# 3. ffmpeg -----------------------------------------------------------------
|
||||||
# Recording on Windows goes through ffmpeg's DirectShow input, and Windows
|
# Recording on Windows goes through ffmpeg's DirectShow input, and Windows
|
||||||
# ships nothing like it, so without this the download would be an application
|
# ships nothing like it, so without this the download would be an application
|
||||||
|
|||||||
+3
-3
@@ -84,8 +84,8 @@ Filename: "{app}\Dikte.exe"; Description: "Start Dikte"; \
|
|||||||
{ The `dikte` command. WindowsApps is already on the user's PATH, so a .cmd
|
{ The `dikte` command. WindowsApps is already on the user's PATH, so a .cmd
|
||||||
left there runs from any terminal without touching the PATH and without an
|
left there runs from any terminal without touching the PATH and without an
|
||||||
administrator; the alternative is an environment variable edit that every
|
administrator; the alternative is an environment variable edit that every
|
||||||
open terminal misses. It names the console executable, which is the one that
|
open terminal misses. It names dikte-cli.exe, the console executable, which
|
||||||
can print to the terminal it was typed in. }
|
is the one that can print to the terminal it was typed in. }
|
||||||
|
|
||||||
function ShimDir(): String;
|
function ShimDir(): String;
|
||||||
begin
|
begin
|
||||||
@@ -104,7 +104,7 @@ begin
|
|||||||
if CurStep = ssPostInstall then begin
|
if CurStep = ssPostInstall then begin
|
||||||
if DirExists(ShimDir()) then begin
|
if DirExists(ShimDir()) then begin
|
||||||
Shim := '@echo off' + #13#10
|
Shim := '@echo off' + #13#10
|
||||||
+ '"' + ExpandConstant('{app}\dikte.exe') + '" %*' + #13#10;
|
+ '"' + ExpandConstant('{app}\dikte-cli.exe') + '" %*' + #13#10;
|
||||||
SaveStringToFile(ShimPath(), Shim, False);
|
SaveStringToFile(ShimPath(), Shim, False);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -84,12 +84,17 @@ executable = EXE( # noqa: F821
|
|||||||
# print nothing to it and answer nothing to a script. Everywhere else the one
|
# print nothing to it and answer nothing to a script. Everywhere else the one
|
||||||
# executable does both jobs: a terminal that started it keeps its output, and
|
# executable does both jobs: a terminal that started it keeps its output, and
|
||||||
# nothing opens a window nobody asked for.
|
# nothing opens a window nobody asked for.
|
||||||
|
#
|
||||||
|
# Named apart from the windowed one rather than `dikte` beside `Dikte`.
|
||||||
|
# Windows matches a filename without regard to its case, so those two are one
|
||||||
|
# file in one directory: whichever PyInstaller writes second is the only one
|
||||||
|
# installed, and a build made on a case-sensitive filesystem never sees it.
|
||||||
console_executable = EXE( # noqa: F821
|
console_executable = EXE( # noqa: F821
|
||||||
archive,
|
archive,
|
||||||
analysis.scripts,
|
analysis.scripts,
|
||||||
[],
|
[],
|
||||||
exclude_binaries=True,
|
exclude_binaries=True,
|
||||||
name="dikte",
|
name="dikte-cli",
|
||||||
console=True,
|
console=True,
|
||||||
target_arch=None,
|
target_arch=None,
|
||||||
icon=os.environ.get("DIKTE_ICO") or None,
|
icon=os.environ.get("DIKTE_ICO") or None,
|
||||||
|
|||||||
+31
-1
@@ -9,6 +9,7 @@ has to notice AppImageLauncher's entry, which is not under the name ours is.
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import plistlib
|
import plistlib
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@@ -493,7 +494,7 @@ class Windows(unittest.TestCase):
|
|||||||
def test_the_windowed_executable_is_what_starts_at_sign_in(self):
|
def test_the_windowed_executable_is_what_starts_at_sign_in(self):
|
||||||
"""The console one is what the `dikte` command runs, and a sign-in that
|
"""The console one is what the `dikte` command runs, and a sign-in that
|
||||||
started that would open a console window nobody asked for."""
|
started that would open a console window nobody asked for."""
|
||||||
with Frozen(str(self.installed / "dikte.exe"), platform="win32"):
|
with Frozen(str(self.installed / "dikte-cli.exe"), platform="win32"):
|
||||||
self.assertEqual(integrate.target(), self.app)
|
self.assertEqual(integrate.target(), self.app)
|
||||||
|
|
||||||
def test_a_start_does_not_turn_it_on_for_somebody_who_said_no(self):
|
def test_a_start_does_not_turn_it_on_for_somebody_who_said_no(self):
|
||||||
@@ -520,5 +521,34 @@ class Windows(unittest.TestCase):
|
|||||||
self.assertEqual(self.remove(), [])
|
self.assertEqual(self.remove(), [])
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsExecutableNames(unittest.TestCase):
|
||||||
|
"""The two Windows executables, read out of the files that name them.
|
||||||
|
|
||||||
|
Windows matches a filename without regard to its case, so Dikte.exe and
|
||||||
|
dikte.exe are one file in one directory and whichever was written second is
|
||||||
|
the only one installed. Nothing else here would catch that: these tests and
|
||||||
|
the builds that check the packaging both run on filesystems where the two
|
||||||
|
names are two files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
root = pathlib.Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
def executables(self):
|
||||||
|
"""What the spec calls each one, in the order it builds them."""
|
||||||
|
spec = (self.root / "packaging" / "dikte.spec").read_text()
|
||||||
|
return [re.search(r'name="(.*?)"', block).group(1)
|
||||||
|
for block in spec.split("EXE(")[1:]]
|
||||||
|
|
||||||
|
def test_the_two_are_more_than_a_case_apart(self):
|
||||||
|
windowed, console = self.executables()
|
||||||
|
self.assertNotEqual(windowed.lower(), console.lower())
|
||||||
|
|
||||||
|
def test_the_command_runs_the_console_one(self):
|
||||||
|
"""The setup writes the shim, so it is the setup that has to be right."""
|
||||||
|
console = self.executables()[1]
|
||||||
|
setup = (self.root / "packaging" / "dikte.iss").read_text()
|
||||||
|
self.assertIn(f"{{app}}\\{console}.exe", setup)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user