<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arsh Singh</title>
    <description>The latest articles on DEV Community by Arsh Singh (@arsh_singh_93174c38ca7cc6).</description>
    <link>https://dev.to/arsh_singh_93174c38ca7cc6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4110162%2Fda80d5d3-b814-41b2-a1f9-a75b3a173c0a.jpg</url>
      <title>DEV Community: Arsh Singh</title>
      <link>https://dev.to/arsh_singh_93174c38ca7cc6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arsh_singh_93174c38ca7cc6"/>
    <language>en</language>
    <item>
      <title>Tk scales your fonts for high DPI but not your layout</title>
      <dc:creator>Arsh Singh</dc:creator>
      <pubDate>Fri, 04 Sep 2026 17:04:36 +0000</pubDate>
      <link>https://dev.to/arsh_singh_93174c38ca7cc6/tk-scales-your-fonts-for-high-dpi-but-not-your-layout-hii</link>
      <guid>https://dev.to/arsh_singh_93174c38ca7cc6/tk-scales-your-fonts-for-high-dpi-but-not-your-layout-hii</guid>
      <description>&lt;p&gt;Open a Tkinter app on a display set to 150% and the text is often too big&lt;br&gt;
for the boxes it sits in. This is why, and the fix that made it stop for&lt;br&gt;
me.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/idk-arsh/multiterm" rel="noopener noreferrer"&gt;MultiTerm&lt;/a&gt; is a terminal emulator&lt;br&gt;
whose interface, terminal grid and chrome alike, is drawn on Tk canvases.&lt;br&gt;
At 100% scaling it looked right. On a display set to 150%, the labels grew&lt;br&gt;
by half and the bars they sat in did not. The shell picker label was cut&lt;br&gt;
off under the next control. The terminal grid was unaffected, because its&lt;br&gt;
cell size is measured from the font. Everything positioned with a literal&lt;br&gt;
number was wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Tk does on Windows
&lt;/h2&gt;

&lt;p&gt;Tk has one DPI setting: &lt;code&gt;tk scaling&lt;/code&gt;, the number of screen pixels in a&lt;br&gt;
typographic point. At 96 dpi that is 96/72, or 1.333. On a 150% display&lt;br&gt;
Windows reports 144 dpi and, if the process has declared itself DPI-aware&lt;br&gt;
(MultiTerm calls &lt;code&gt;SetProcessDpiAwareness(1)&lt;/code&gt; at startup so text is not&lt;br&gt;
blurred), Tk sets &lt;code&gt;tk scaling&lt;/code&gt; to 2.0.&lt;/p&gt;

&lt;p&gt;That value affects exactly one thing: fonts whose size is a positive&lt;br&gt;
number, which Tk reads as points. A size 10 font becomes 13 pixels at 100%&lt;br&gt;
and 20 pixels at 150%. Correct, and automatic.&lt;/p&gt;

&lt;p&gt;Nothing else is touched. &lt;code&gt;canvas.create_rectangle(0, 0, 200, 32)&lt;/code&gt; is 200 by&lt;br&gt;
32 pixels on every display. &lt;code&gt;pady=6&lt;/code&gt; is six pixels. A &lt;code&gt;HEADER_HEIGHT = 44&lt;/code&gt;&lt;br&gt;
constant is 44 pixels. The text scaled by 1.5, the box by 1.0, and the gap&lt;br&gt;
between those two numbers is the bug.&lt;/p&gt;

&lt;p&gt;Scaling the layout to match is not enough on its own. If you multiply every&lt;br&gt;
constant by &lt;code&gt;tk scaling / 1.333&lt;/code&gt;, layout and fonts each scale once, which&lt;br&gt;
sounds right, until you use a pixel-sized font somewhere, or override the&lt;br&gt;
scaling for a test, or run on a machine where Tk's number and yours were&lt;br&gt;
computed differently. Two mechanisms, two opinions.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix: one factor, applied by you
&lt;/h2&gt;

&lt;p&gt;Take the scale away from Tk and apply it yourself, to fonts and layout&lt;br&gt;
alike. This is the whole of MultiTerm's &lt;code&gt;ui.py&lt;/code&gt;, minus docstrings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;BASE_SCALING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.3333333&lt;/span&gt;   &lt;span class="c1"&gt;# Tk's pixels-per-point at 96 dpi
&lt;/span&gt;&lt;span class="n"&gt;PT_TO_PX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.3333333&lt;/span&gt;
&lt;span class="n"&gt;SCALE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;
    &lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MULTITERM_UI_SCALE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;SCALE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scaling&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;SCALE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reported&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;BASE_SCALING&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Fonts are sized in pixels from here on, so keep Tk out of it.
&lt;/span&gt;    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scaling&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BASE_SCALING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A design pixel, scaled for this display.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;font_px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Font size in pixels (negative), scaled. Tk never touches these.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PT_TO_PX&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three moves.&lt;/p&gt;

&lt;p&gt;Read the scale Tk already worked out, divide by the 96 dpi baseline, and&lt;br&gt;
keep the result as a single number. At 150% that is 1.5.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;tk scaling&lt;/code&gt; back to the baseline so Tk stops scaling anything itself.&lt;/p&gt;

&lt;p&gt;Give fonts negative sizes. In Tk a negative font size means pixels, and Tk&lt;br&gt;
leaves pixel sizes alone. &lt;code&gt;font_px(10)&lt;/code&gt; returns -13 at 100% and -20 at&lt;br&gt;
150%, the same pixel sizes Tk would have chosen, except now they come from&lt;br&gt;
the same factor as the layout.&lt;/p&gt;

&lt;p&gt;Then every pixel constant goes through &lt;code&gt;px()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;HEADER_H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="n"&gt;label_font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tkfont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;font_px&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fonts and layout grow together and there is nothing left to disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it without a high-DPI monitor
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MULTITERM_UI_SCALE&lt;/code&gt; override is the piece I would keep if I threw the&lt;br&gt;
rest away. Set it to 1.5 and the app renders as it would on a 150% display,&lt;br&gt;
on any display. MultiTerm's GUI test can be run that way and it walks the&lt;br&gt;
canvas items to check two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no text item extends past the shape it is drawn on&lt;/li&gt;
&lt;li&gt;no header label overlaps a control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comment above that check reads "a clipped Command Prompt shipped once".&lt;br&gt;
That is the entire reason it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that do not work
&lt;/h2&gt;

&lt;p&gt;Scaling only the layout. Fonts are then scaled twice.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;tk scaling&lt;/code&gt; to 1.0 instead of 1.333. Fonts stop scaling with DPI,&lt;br&gt;
but now one point is one pixel, so a 10pt font renders at 10px instead of&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pin to the 96 dpi value, not to 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reading the DPI yourself through &lt;code&gt;GetDpiForSystem&lt;/code&gt; or similar. It works,&lt;br&gt;
but Tk has already asked Windows and put the answer in &lt;code&gt;tk scaling&lt;/code&gt;. Two&lt;br&gt;
sources for the same number is how the original bug happened.&lt;/p&gt;

&lt;p&gt;Letting the factor drop below 1.0. The code clamps at 1.0 on purpose so a&lt;br&gt;
low reported DPI never shrinks the interface.&lt;/p&gt;




&lt;p&gt;MultiTerm is a free, MIT-licensed multi-pane terminal for Windows with&lt;br&gt;
workspaces, per-folder startup commands and broadcast typing. The module&lt;br&gt;
above is&lt;br&gt;
&lt;a href="https://github.com/idk-arsh/multiterm/blob/main/multiterm/ui.py" rel="noopener noreferrer"&gt;multiterm/ui.py&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tkinter</category>
      <category>ui</category>
      <category>windows</category>
    </item>
    <item>
      <title>PyInstaller silently kills every ConPTY child because it doesn't bundle OpenConsole.exe</title>
      <dc:creator>Arsh Singh</dc:creator>
      <pubDate>Fri, 04 Sep 2026 17:03:23 +0000</pubDate>
      <link>https://dev.to/arsh_singh_93174c38ca7cc6/pyinstaller-silently-kills-every-conpty-child-because-it-doesnt-bundle-openconsoleexe-3gik</link>
      <guid>https://dev.to/arsh_singh_93174c38ca7cc6/pyinstaller-silently-kills-every-conpty-child-because-it-doesnt-bundle-openconsoleexe-3gik</guid>
      <description>&lt;p&gt;I wrote a terminal emulator in Python. It ran fine from source. After&lt;br&gt;
packaging it with PyInstaller, every shell it opened exited the instant it&lt;br&gt;
started. No exception, no log line, no dialog. This post is the explanation&lt;br&gt;
I wish had existed when I went looking for one.&lt;/p&gt;
&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/idk-arsh/multiterm" rel="noopener noreferrer"&gt;MultiTerm&lt;/a&gt; runs several Windows&lt;br&gt;
shells side by side. Each pane is a child process attached to a&lt;br&gt;
pseudo-console, which on Windows 10 and later means&lt;br&gt;
&lt;a href="https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/" rel="noopener noreferrer"&gt;ConPTY&lt;/a&gt;.&lt;br&gt;
The spawning goes through &lt;a href="https://github.com/andfoy/pywinpty" rel="noopener noreferrer"&gt;pywinpty&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;winpty&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PtyProcess&lt;/span&gt;
&lt;span class="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PtyProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cmd.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a normal Python install this works for cmd, PowerShell, Git Bash and&lt;br&gt;
WSL.&lt;/p&gt;
&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Build a one-file exe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;pyinstaller&lt;/span&gt; &lt;span class="na"&gt;--onefile --windowed --name &lt;/span&gt;&lt;span class="kd"&gt;MultiTerm&lt;/span&gt; &lt;span class="kd"&gt;main&lt;/span&gt;.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it and every pane shows "process exited" before a prompt appears. The&lt;br&gt;
exit code is &lt;code&gt;3221225786&lt;/code&gt;, which is &lt;code&gt;0xC000013A&lt;/code&gt;, which is&lt;br&gt;
&lt;code&gt;STATUS_CONTROL_C_EXIT&lt;/code&gt;. The shell is saying it was killed by Ctrl+C. Nobody&lt;br&gt;
pressed Ctrl+C.&lt;/p&gt;

&lt;p&gt;Every shell fails the same way, so the shell is not the problem. It fails&lt;br&gt;
before the first prompt, so it is not something the user did. And it only&lt;br&gt;
fails in the packaged build, so packaging is the difference. That narrows it&lt;br&gt;
down to "something PyInstaller left behind", which is still a lot of things.&lt;/p&gt;
&lt;h2&gt;
  
  
  What ConPTY actually is
&lt;/h2&gt;

&lt;p&gt;ConPTY is not just a library call. &lt;code&gt;CreatePseudoConsole&lt;/code&gt; starts a separate&lt;br&gt;
console host process. That process owns the screen buffer, does the VT&lt;br&gt;
translation, and sits between your program and the shell. The shell is a&lt;br&gt;
child of the console host, not of you.&lt;/p&gt;

&lt;p&gt;pywinpty does not rely on the console host built into Windows. Next to its&lt;br&gt;
extension module it ships &lt;code&gt;conpty.dll&lt;/code&gt; and &lt;code&gt;OpenConsole.exe&lt;/code&gt;, the&lt;br&gt;
open-source console host from the Windows Terminal project, so that ConPTY&lt;br&gt;
behaves the same on every Windows 10 build instead of depending on whatever&lt;br&gt;
&lt;code&gt;conhost.exe&lt;/code&gt; the OS happens to have. You can see this in the DLL itself:&lt;br&gt;
its string table contains both &lt;code&gt;\conhost.exe&lt;/code&gt; and &lt;code&gt;OpenConsole.exe&lt;/code&gt;. The&lt;br&gt;
chain when a pane opens is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your app  -&amp;gt;  conpty.dll  -&amp;gt;  OpenConsole.exe  -&amp;gt;  cmd.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take &lt;code&gt;OpenConsole.exe&lt;/code&gt; away and the DLL still returns a process handle, so&lt;br&gt;
the Python side sees a successful spawn. The shell then finds itself with no&lt;br&gt;
console host behind it and exits. A Windows console process that loses its&lt;br&gt;
console exits with &lt;code&gt;STATUS_CONTROL_C_EXIT&lt;/code&gt;, the same code as an unhandled&lt;br&gt;
Ctrl+C, which is why the exit code points you at the wrong thing. That last&lt;br&gt;
step is my reading of the behaviour, not something I traced through the&lt;br&gt;
kernel, but it matches what you see.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why PyInstaller misses it
&lt;/h2&gt;

&lt;p&gt;PyInstaller finds binary dependencies by reading DLL import tables. It sees&lt;br&gt;
that the &lt;code&gt;winpty&lt;/code&gt; extension imports &lt;code&gt;conpty.dll&lt;/code&gt; and &lt;code&gt;winpty.dll&lt;/code&gt; and&lt;br&gt;
bundles them. It cannot see that &lt;code&gt;conpty.dll&lt;/code&gt; will later &lt;code&gt;CreateProcess&lt;/code&gt; an&lt;br&gt;
exe sitting next to it, because that is a string inside the DLL, not an&lt;br&gt;
import. So the two DLLs travel and the two exes (&lt;code&gt;OpenConsole.exe&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;winpty-agent.exe&lt;/code&gt; for the older winpty backend) stay in &lt;code&gt;site-packages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is no hook for &lt;code&gt;winpty&lt;/code&gt; in &lt;code&gt;pyinstaller-hooks-contrib&lt;/code&gt; as of the&lt;br&gt;
version I have (2026.7), so nothing fills the gap for you.&lt;/p&gt;

&lt;p&gt;From source, the whole &lt;code&gt;winpty&lt;/code&gt; folder is present and the exes are found.&lt;br&gt;
Packaged, they are not. Same code, different neighbours.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Add the helpers yourself. This is the build script MultiTerm ships,&lt;br&gt;
trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;HELPERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OpenConsole.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winpty-agent.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conpty.dll&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winpty.dll&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;winpty_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;winpty&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winpty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;winpty_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PyInstaller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--noconfirm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--clean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--onefile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--windowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MultiTerm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--hidden-import&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winpty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;HELPERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--add-binary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s;winpty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line that matters is &lt;code&gt;--add-binary &amp;lt;path&amp;gt;;winpty&lt;/code&gt;. The destination has&lt;br&gt;
to be the &lt;code&gt;winpty&lt;/code&gt; folder so the exe lands next to &lt;code&gt;conpty.dll&lt;/code&gt; inside the&lt;br&gt;
extracted bundle, which is where the DLL looks. Drop it at the bundle root&lt;br&gt;
and the shells die exactly as before.&lt;/p&gt;

&lt;p&gt;In a spec file, the equivalent is listing the four paths in &lt;code&gt;binaries=[...]&lt;/code&gt;&lt;br&gt;
with &lt;code&gt;'winpty'&lt;/code&gt; as the destination.&lt;/p&gt;
&lt;h2&gt;
  
  
  A second problem you hit right after
&lt;/h2&gt;

&lt;p&gt;With the console host in place, cmd and PowerShell work. Git Bash still&lt;br&gt;
dies. Different cause.&lt;/p&gt;

&lt;p&gt;PyInstaller's one-file bootloader extracts everything to a temp folder,&lt;br&gt;
puts that folder at the front of &lt;code&gt;PATH&lt;/code&gt;, and exports &lt;code&gt;_MEIPASS2&lt;/code&gt; and a few&lt;br&gt;
&lt;code&gt;_PYI_*&lt;/code&gt; variables so the frozen interpreter can find itself. A child shell&lt;br&gt;
inherits all of it. Git Bash then resolves some of its own DLLs to the&lt;br&gt;
bundled copies at the front of &lt;code&gt;PATH&lt;/code&gt; and crashes on load.&lt;/p&gt;

&lt;p&gt;Strip that out before spawning anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;child_env&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_MEIPASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normcase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pathsep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pathsep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normcase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_PYI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_MEIPASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_MEIPASS2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This applies to any frozen Python program that launches child processes,&lt;br&gt;
not only terminals.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to recognise it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Works from source, fails packaged.&lt;/li&gt;
&lt;li&gt;Child exit code &lt;code&gt;3221225786&lt;/code&gt; (&lt;code&gt;0xC000013A&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PtyProcess.spawn&lt;/code&gt; raised nothing.&lt;/li&gt;
&lt;li&gt;While the packaged app is running, &lt;code&gt;dir %TEMP%\_MEI*\winpty&lt;/code&gt; shows two
DLLs and no exes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all four match, add the binaries and move on with your day.&lt;/p&gt;




&lt;p&gt;MultiTerm is a free, MIT-licensed multi-pane terminal for Windows with&lt;br&gt;
workspaces that remember their folders and startup commands, and broadcast&lt;br&gt;
typing across panes. The full build script is&lt;br&gt;
&lt;a href="https://github.com/idk-arsh/multiterm/blob/main/tools/build_exe.py" rel="noopener noreferrer"&gt;tools/build_exe.py&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>python</category>
      <category>terminal</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
