openlogi 0.6.26

OpenLogi command-line interface — a local-first companion for Logitech HID++ peripherals.
<?xml version="1.0" encoding="utf-8"?>

<!--
  Windows Installer authoring for the OpenLogi MSI (built with the WiX
  toolset v6 by release.yml's `windows-msi` job).

  - Per-user scope: installs to %LocalAppData%\Programs\OpenLogi without
    elevation; raw HID access and the input hook need no admin rights.
  - The packaged exe is the already-Authenticode-signed build from the
    `windows` job; the MSI itself is signed on top by the same job.
  - Build-time defines (passed via `wix build -d`):
      Version       numeric x.y.z ProductVersion (0.0.0 on non-tag dispatches)
      ExeFile       path to the signed openlogi-gui exe to package
      AgentExeFile  path to the signed openlogi-agent exe to package
      IconFile      optional override for the app .ico (Add/Remove Programs
                    entry); defaults to the committed design/icon/openlogi.ico
                    below, so the release workflow needs no extra define

  - Requires WixToolset.Util.wixext (util:CloseApplication below); the
    windows-msi job installs it pinned to the same version as wix itself.
-->

<!--
  One UpgradeCode per architecture, fixed forever: Windows Installer treats
  x64 and arm64 packages both as "64-bit", so a shared UpgradeCode would let
  FindRelatedProducts cross-grade one architecture over the other.
-->
<?if $(sys.BUILDARCH) = arm64 ?>
  <?define UpgradeCode = "2A989A66-36FE-4999-ACE2-3E79AE02D12A" ?>
<?else?>
  <?define UpgradeCode = "981D0C70-179E-4EE8-8F1E-0EB6877EAE94" ?>
<?endif?>

<!-- Resolved relative to this .wxs, so a plain
     `wix build packaging\windows\OpenLogi.wxs` finds it from any cwd. -->
<?ifndef IconFile ?>
  <?define IconFile = "$(sys.SOURCEFILEDIR)..\..\design\icon\openlogi.ico" ?>
<?endif?>

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
     xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
  <Package Name="OpenLogi"
           Manufacturer="AprilNEA"
           Version="$(var.Version)"
           UpgradeCode="$(var.UpgradeCode)"
           Language="1033"
           Scope="perUser"
           Compressed="yes">

    <MajorUpgrade DowngradeErrorMessage="A newer version of OpenLogi is already installed." />
    <MediaTemplate EmbedCab="yes" CompressionLevel="high" />

    <StandardDirectory Id="LocalAppDataFolder">
      <Directory Id="ProgramsFolder" Name="Programs">
        <Directory Id="INSTALLFOLDER" Name="OpenLogi" />
      </Directory>
    </StandardDirectory>

    <ComponentGroup Id="App" Directory="INSTALLFOLDER">
      <Component Id="MainExecutable">
        <!-- Installed as plain OpenLogi.exe: inside the install dir the file
             name is the product name. -->
        <File Id="OpenLogiExe" Name="OpenLogi.exe" Source="$(var.ExeFile)" KeyPath="yes" />
      </Component>
      <Component Id="AgentExecutable">
        <!-- The background agent owns all device I/O, the input hook, and the
             IPC server; the GUI is a thin client that spawns a sibling
             `openlogi-agent.exe` when the pipe is absent (agent_binary_path()
             in openlogi-gui's ipc_client.rs) — the name must match exactly. -->
        <File Id="OpenLogiAgentExe" Name="openlogi-agent.exe" Source="$(var.AgentExeFile)" KeyPath="yes" />
      </Component>
    </ComponentGroup>

    <!--
      Upgrades hit a file-in-use on both exes once the agent autostarts (HKCU
      Run key, registered by the agent on first run). The agent is headless —
      it owns no top-level window, so WM_CLOSE / end-session messages never
      reach it — hence TerminateProcess (which requires RebootPrompt="no").
      Terminating it is safe: it holds no unsaved state, and the GUI's
      spawn-retry (or the Run key at next login) restarts the new binary.
      The GUI does have a window, so ask it to close first; terminate as the
      fallback so silent upgrades (msiexec /qn, the future auto-updater) never
      stall on a FilesInUse prompt.

      Target matches by image NAME, machine-wide by design of the element —
      there is no per-directory scoping. That is the correct scope here, not
      an accident: both processes are per-user singletons (profile-scoped
      agent.lock / openlogi.lock), so the only same-user copies that can be
      running — whichever install they came from — are exactly the ones
      holding the IPC pipe and the locks the upgraded install needs freed.
      Other users' processes survive: this is a per-user package, and the
      deferred close action runs without the rights to terminate them.
    -->
    <util:CloseApplication Id="CloseAgent"
                           Target="openlogi-agent.exe"
                           CloseMessage="no"
                           RebootPrompt="no"
                           Timeout="2"
                           TerminateProcess="0" />
    <util:CloseApplication Id="CloseGui"
                           Target="OpenLogi.exe"
                           CloseMessage="yes"
                           RebootPrompt="no"
                           Timeout="5"
                           TerminateProcess="0" />

    <StandardDirectory Id="ProgramMenuFolder">
      <!-- Shortcuts cannot be a component KeyPath; the HKCU marker stands in,
           which also fits the per-user scope. -->
      <Component Id="StartMenuShortcut">
        <Shortcut Id="OpenLogiShortcut"
                  Name="OpenLogi"
                  Target="[INSTALLFOLDER]OpenLogi.exe"
                  WorkingDirectory="INSTALLFOLDER" />
        <RegistryValue Root="HKCU"
                       Key="Software\OpenLogi"
                       Name="Installed"
                       Type="integer"
                       Value="1"
                       KeyPath="yes" />
      </Component>
    </StandardDirectory>

    <Feature Id="Main">
      <!-- ComponentGroupRef pulls in both MainExecutable and AgentExecutable. -->
      <ComponentGroupRef Id="App" />
      <ComponentRef Id="StartMenuShortcut" />
    </Feature>

    <!-- Add/Remove Programs icon. The Start Menu shortcut needs no Icon of
         its own: it targets OpenLogi.exe, whose embedded icon resource (see
         crates/openlogi-gui/build.rs) the shell picks up automatically. -->
    <Icon Id="OpenLogiIco" SourceFile="$(var.IconFile)" />
    <Property Id="ARPPRODUCTICON" Value="OpenLogiIco" />
    <Property Id="ARPURLINFOABOUT" Value="https://openlogi.org" />
    <Property Id="ARPHELPLINK" Value="https://github.com/AprilNEA/OpenLogi" />
  </Package>
</Wix>