Skip to content

Commit ce959d8

Browse files
grauxmusicclaude
andcommitted
fix(windows): open companion window on click + prevent UI freeze
- Click "Open Water" now calls requestShowWindow() via TCP instead of opening water.95ent.ai in the system browser. Falls back to watermorph:// protocol if companion is not running. - Companion handles SHOW_WINDOW command by calling win.show()/focus(). - Reduced connect timeout 3000ms → 200ms + 5s retry throttle to prevent the message thread from freezing when companion is not running. - Fixed titleBarStyle on Windows: use hiddenInset + titleBarOverlay (branded mauve #8B5CF6) instead of 'hidden' which breaks on Windows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 17e22b3 commit ce959d8

4 files changed

Lines changed: 59 additions & 6 deletions

File tree

CompanionWin/main.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,22 @@ let dawState = { bpm: 0, key: '', isConnected: false }
2929
// ── Window ───────────────────────────────────────────────────────────────────
3030

3131
function createWindow() {
32+
// titleBarStyle 'hidden' doesn't work correctly on Windows (content area
33+
// doesn't fill under the title bar). Use titleBarOverlay for Windows
34+
// custom chrome, which keeps a native draggable area but lets us color it.
35+
const isMac = process.platform === 'darwin'
3236
win = new BrowserWindow({
3337
width: 420,
3438
height: 700,
3539
minWidth: 340,
3640
minHeight: 480,
3741
frame: true,
38-
titleBarStyle: 'hidden',
42+
titleBarStyle: isMac ? 'hidden' : 'hiddenInset',
43+
titleBarOverlay: isMac ? false : {
44+
color: '#0a0a0a',
45+
symbolColor: '#8B5CF6', // mauve — Water brand
46+
height: 28,
47+
},
3948
backgroundColor: '#0a0a0a',
4049
title: 'Water Morph',
4150
skipTaskbar: false,
@@ -149,6 +158,11 @@ function handleLine(conn, line) {
149158
// The companion still sends OK to keep the plugin happy.
150159
conn.write('OK\n')
151160

161+
} else if (line === 'SHOW_WINDOW') {
162+
// Plugin clicked "Open Water" — bring companion window to front
163+
if (win) { win.show(); win.focus() }
164+
conn.write('OK\n')
165+
152166
} else if (line.startsWith('SYNC ')) {
153167
const parts = line.slice(5).split(' ')
154168
const bpm = parseFloat(parts[0] || '0') || 0

Source/CompanionLink.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "CompanionLink.h"
22

3-
static constexpr int kPort = 59812;
4-
static constexpr int kTimeoutMs = 3000;
3+
static constexpr int kPort = 59812;
54

65
//==============================================================================
76
CompanionLink& CompanionLink::get()
@@ -18,17 +17,24 @@ CompanionLink::~CompanionLink()
1817
//==============================================================================
1918
bool CompanionLink::tryConnect()
2019
{
20+
// Throttle reconnect attempts to prevent blocking the message thread.
21+
// kConnectTimeoutMs is only 200ms but we still cap to once every 5s.
22+
const int64_t now = juce::Time::currentTimeMillis();
23+
if (now - lastConnectAttemptMs_ < kReconnectThrottleMs)
24+
return false;
25+
lastConnectAttemptMs_ = now;
26+
2127
disconnect();
2228
socket = std::make_unique<juce::StreamingSocket>();
2329

24-
if (! socket->connect ("127.0.0.1", kPort, kTimeoutMs))
30+
if (! socket->connect ("127.0.0.1", kPort, kConnectTimeoutMs))
2531
{
2632
socket.reset();
2733
return false;
2834
}
2935

3036
// Wait for READY\n handshake
31-
if (socket->waitUntilReady (true, kTimeoutMs) != 1)
37+
if (socket->waitUntilReady (true, kConnectTimeoutMs) != 1)
3238
{
3339
socket.reset();
3440
return false;
@@ -42,6 +48,8 @@ bool CompanionLink::tryConnect()
4248
return false;
4349
}
4450

51+
// Reset throttle on success so a reconnect after disconnect is instant
52+
lastConnectAttemptMs_ = 0;
4553
return true;
4654
}
4755

@@ -114,6 +122,23 @@ void CompanionLink::sendTransport (bool playing, double timeSecs, double ppq)
114122
disconnect();
115123
}
116124

125+
//==============================================================================
126+
void CompanionLink::requestShowWindow()
127+
{
128+
if (! isConnected()) tryConnect();
129+
if (! isConnected())
130+
{
131+
// Companion not running — launch it via registered watermorph:// protocol.
132+
// The companion registered itself as the handler at install time.
133+
juce::URL ("watermorph://show").launchInDefaultBrowser();
134+
return;
135+
}
136+
137+
const juce::String cmd = "SHOW_WINDOW\n";
138+
if (socket->write (cmd.toRawUTF8(), (int) cmd.getNumBytesAsUTF8()) <= 0)
139+
disconnect();
140+
}
141+
117142
//==============================================================================
118143
bool CompanionLink::isConnected() const noexcept
119144
{

Source/CompanionLink.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ class CompanionLink
4848
//==========================================================================
4949
void sendTransport (bool playing, double timeSecs, double ppq = 0.0);
5050

51+
//==========================================================================
52+
// Ask the companion to bring its window to front.
53+
// Falls back gracefully if companion is not running.
54+
//==========================================================================
55+
void requestShowWindow();
56+
5157
//==========================================================================
5258
bool isConnected() const noexcept;
5359

@@ -57,6 +63,12 @@ class CompanionLink
5763

5864
std::unique_ptr<juce::StreamingSocket> socket;
5965

66+
// Throttle reconnect attempts — tryConnect() blocks for kConnectTimeoutMs,
67+
// so we cap retries to once every kReconnectThrottleMs to avoid UI freeze.
68+
int64_t lastConnectAttemptMs_ { 0 };
69+
static constexpr int kConnectTimeoutMs = 200; // was 3000 — prevents UI freeze
70+
static constexpr int kReconnectThrottleMs = 5000; // retry at most every 5 s
71+
6072
bool tryConnect();
6173
void disconnect();
6274

Source/PluginEditor.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@ void MorphAudioProcessorEditor::mouseUp (const juce::MouseEvent& e)
254254

255255
if (helpRect_.contains (pt))
256256
{
257-
juce::URL ("https://water.95ent.ai").launchInDefaultBrowser();
257+
// Show the companion window (Electron app). Falls back to watermorph://
258+
// protocol if companion is not connected, which launches it if installed.
259+
CompanionLink::get().requestShowWindow();
258260
return;
259261
}
260262

0 commit comments

Comments
 (0)