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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
+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
|
||||
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
|
||||
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:
|
||||
|
||||
|
||||
@@ -45,13 +45,33 @@ $env:DIKTE_ICO = $icon
|
||||
|
||||
# 2. The application --------------------------------------------------------
|
||||
# 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
|
||||
# `dikte` command runs.
|
||||
# what a shortcut starts, and dikte-cli.exe, which has a console and is what
|
||||
# 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") `
|
||||
--distpath (Join-Path $build "dist") --workpath (Join-Path $build "work") `
|
||||
--noconfirm --clean
|
||||
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 -----------------------------------------------------------------
|
||||
# Recording on Windows goes through ffmpeg's DirectShow input, and Windows
|
||||
# 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
|
||||
left there runs from any terminal without touching the PATH and without an
|
||||
administrator; the alternative is an environment variable edit that every
|
||||
open terminal misses. It names the console executable, which is the one that
|
||||
can print to the terminal it was typed in. }
|
||||
open terminal misses. It names dikte-cli.exe, the console executable, which
|
||||
is the one that can print to the terminal it was typed in. }
|
||||
|
||||
function ShimDir(): String;
|
||||
begin
|
||||
@@ -104,7 +104,7 @@ begin
|
||||
if CurStep = ssPostInstall then begin
|
||||
if DirExists(ShimDir()) then begin
|
||||
Shim := '@echo off' + #13#10
|
||||
+ '"' + ExpandConstant('{app}\dikte.exe') + '" %*' + #13#10;
|
||||
+ '"' + ExpandConstant('{app}\dikte-cli.exe') + '" %*' + #13#10;
|
||||
SaveStringToFile(ShimPath(), Shim, False);
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -84,12 +84,17 @@ executable = EXE( # noqa: F821
|
||||
# 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
|
||||
# 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
|
||||
archive,
|
||||
analysis.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name="dikte",
|
||||
name="dikte-cli",
|
||||
console=True,
|
||||
target_arch=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 pathlib
|
||||
import plistlib
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -493,7 +494,7 @@ class Windows(unittest.TestCase):
|
||||
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
|
||||
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)
|
||||
|
||||
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(), [])
|
||||
|
||||
|
||||
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__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user