arrayview 0.29.0__py3-none-any.whl → 0.29.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
arrayview/_launcher.py CHANGED
@@ -677,9 +677,7 @@ def _activate_early_cli_native_shell(
677
677
  notified = bool(notify_result.get("notified"))
678
678
  except Exception:
679
679
  notified = False
680
- if notified and _wait_for_native_shell_or_viewer_connection(
681
- port, viewer_before=viewer_before
682
- ):
680
+ if notified and _wait_for_viewer_connection(port, before=viewer_before):
683
681
  return True
684
682
  _vprint(
685
683
  "[ArrayView] Native window did not connect to the backend; falling back to browser",
@@ -1216,8 +1214,12 @@ def _handle_cli_spawned_daemon(
1216
1214
  and not is_remote
1217
1215
  and not overlay_files
1218
1216
  and not compare_files
1217
+ and not sys.platform.startswith("linux")
1219
1218
  ):
1220
- url_shell_early = _shell_url(port, sid, name)
1219
+ # Linux QtWebEngine denies sessionStorage to the viewer iframe when its
1220
+ # parent shell was created from inline data. Let Linux open the normal
1221
+ # HTTP shell after the daemon is listening instead.
1222
+ url_shell_early = f"http://{_LOOPBACK_HOST}:{port}/shell"
1221
1223
  early_native_shell_opened, early_native_shell_proc = _open_webview_cli_tracked(
1222
1224
  url_shell_early, 1400, 900, shell_port=port
1223
1225
  )
arrayview/_viewer.html CHANGED
@@ -439,7 +439,6 @@
439
439
  #mv-cb { border: none; }
440
440
  .highlight { color: var(--highlight); font-weight: bold; }
441
441
  .dim-size { opacity: 0.28; font-size: 0.68em; font-weight: normal; }
442
- .dim-hover-idx { font-size: 0.68em; font-weight: normal; color: var(--dim-hover, #777); opacity: 1; }
443
442
  .active-dim { font-weight: bold; }
444
443
  .playing-dim { font-weight: bold; }
445
444
  .vfield-t-dim { color: var(--vfield-t-dim); }
@@ -3338,6 +3337,7 @@
3338
3337
  #mv-panes.mv-crosshair-active .mv-pane::after {
3339
3338
  content: ''; position: absolute; inset: 0; z-index: 4;
3340
3339
  box-shadow: inset 0 0 0 var(--mv-plane-border-width, 1px) var(--mv-plane-border, var(--active-dim));
3340
+ opacity: 0.45;
3341
3341
  border-radius: inherit; box-sizing: border-box;
3342
3342
  pointer-events: none;
3343
3343
  }
@@ -13885,7 +13885,6 @@
13885
13885
  if (!['circle', 'rect', 'freehand', 'floodfill'].includes(_roiShape)) _roiShape = 'circle';
13886
13886
  _roiVisible = true;
13887
13887
  hideAllPixelInfo();
13888
- _resetDimbarHover();
13889
13888
  canvas.style.cursor = 'crosshair';
13890
13889
  if (qmriActive) qmriViews.forEach(v => { v.canvas.style.cursor = 'crosshair'; });
13891
13890
  if (multiViewActive) mvViews.forEach(v => { v.canvas.style.cursor = 'crosshair'; });
@@ -16481,38 +16480,6 @@
16481
16480
  _updateMvPaneFill();
16482
16481
  }
16483
16482
 
16484
- function _dimHoverPadded(val, maxVal) {
16485
- const maxStr = String(maxVal);
16486
- const valStr = String(val);
16487
- const padLen = maxStr.length - valStr.length;
16488
- if (padLen <= 0) return '/' + valStr;
16489
- return '/<span style="opacity:0.28">' + '0'.repeat(padLen) + '</span>' + valStr;
16490
- }
16491
- function _updateDimbarHover(px, py, xLabel, yLabel, pz) {
16492
- // xLabel/yLabel: 'x','y','z' — which dimbar label to update (default x,y)
16493
- // pz: optional z index (for mosaic mode)
16494
- const xl = xLabel || 'x', yl = yLabel || 'y';
16495
- const xEl = document.getElementById('dim-' + xl + '-size');
16496
- const yEl = document.getElementById('dim-' + yl + '-size');
16497
- const zEl = document.getElementById('dim-z-size');
16498
- // Resolve label to shape dim index for padding width
16499
- const labelToDim = (multiViewActive || compareMvActive)
16500
- ? { x: (multiViewActive ? mvDims : compareMvDims)[0], y: (multiViewActive ? mvDims : compareMvDims)[1], z: (multiViewActive ? mvDims : compareMvDims)[2] }
16501
- : { x: dim_x, y: dim_y, z: dim_z };
16502
- const xMax = labelToDim[xl] >= 0 ? shape[labelToDim[xl]] : 1;
16503
- const yMax = labelToDim[yl] >= 0 ? shape[labelToDim[yl]] : 1;
16504
- if (xEl) { xEl.innerHTML = _dimHoverPadded(px, xMax); xEl.className = 'dim-hover-idx'; }
16505
- if (yEl) { yEl.innerHTML = _dimHoverPadded(py, yMax); yEl.className = 'dim-hover-idx'; }
16506
- if (zEl && pz != null) {
16507
- const zMax = dim_z >= 0 ? shape[dim_z] : 1;
16508
- zEl.innerHTML = _dimHoverPadded(pz + 1, zMax); zEl.className = 'dim-hover-idx';
16509
- }
16510
- }
16511
- function _resetDimbarHover() {
16512
- // Re-render dimbar to restore correct max sizes for all modes
16513
- renderInfo();
16514
- }
16515
-
16516
16483
  // Gnomon visibility: shown while a dim-bar token is hovered, or flashed
16517
16484
  // briefly when activeDim changes via h/l/←/→. Hidden otherwise.
16518
16485
  // In oblique mode the x/y labels no longer name axis-aligned directions,
@@ -17711,13 +17678,18 @@
17711
17678
 
17712
17679
  function saveState() {
17713
17680
  if (!sid) return;
17714
- sessionStorage.setItem('av_' + sid, JSON.stringify(collectStateSnapshot()));
17681
+ try {
17682
+ sessionStorage.setItem('av_' + sid, JSON.stringify(collectStateSnapshot()));
17683
+ } catch (e) {
17684
+ // Some embedded webviews deny storage access. Viewing must keep
17685
+ // working even when this optional convenience is unavailable.
17686
+ }
17715
17687
  }
17716
17688
 
17717
17689
  function restoreStateFromSessionStorage() {
17718
- const raw = sessionStorage.getItem('av_' + sid);
17719
- if (!raw) return false;
17720
17690
  try {
17691
+ const raw = sessionStorage.getItem('av_' + sid);
17692
+ if (!raw) return false;
17721
17693
  return applyStateSnapshot(JSON.parse(raw));
17722
17694
  } catch (e) {
17723
17695
  return false;
@@ -20117,7 +20089,6 @@
20117
20089
  showPixelInfo(document.getElementById('main-pixel-info'), { valueText: _pixelFmt(d.value, _activeNumericContext()), rawX, rawY }, e.clientX, e.clientY);
20118
20090
  }).catch(() => {});
20119
20091
  }
20120
- _updateDimbarHover(localX, localY, null, null, zIdx);
20121
20092
  return;
20122
20093
  }
20123
20094
 
@@ -20131,11 +20102,6 @@
20131
20102
  imgW: canvas.width,
20132
20103
  imgH: canvas.height,
20133
20104
  displayEl: document.getElementById('main-pixel-info'),
20134
- onResult: (d, px, py) => {
20135
- if (_pixelInfoActive() && !multiViewActive && !compareActive) {
20136
- _updateDimbarHover(px, py);
20137
- }
20138
- },
20139
20105
  });
20140
20106
  });
20141
20107
  canvas.addEventListener('mouseleave', () => {
@@ -20154,8 +20120,7 @@
20154
20120
  if (_pixelInfoHeld) {
20155
20121
  _pixelInfoHeld = false;
20156
20122
  if (!_pixelInfoVisible) {
20157
- const piEl = document.getElementById('main-pixel-info');
20158
- if (piEl) piEl.style.display = 'none';
20123
+ hideAllPixelInfo();
20159
20124
  }
20160
20125
  }
20161
20126
  });
@@ -20924,7 +20889,6 @@
20924
20889
  if (!_pixelInfoVisible) {
20925
20890
  _clearHoverPins();
20926
20891
  hideAllPixelInfo();
20927
- _resetDimbarHover();
20928
20892
  const axEl = document.getElementById('main-axes-svg');
20929
20893
  if (axEl) {
20930
20894
  axEl.querySelector('.axes-lbl-x').textContent = 'x';
@@ -26047,21 +26011,6 @@
26047
26011
  imgH: view.lastH,
26048
26012
  view,
26049
26013
  displayEl: view.pixelInfoEl,
26050
- onResult: (d, px, py) => {
26051
- if (_pixelInfoActive()) {
26052
- // Map pane dims to dimbar x/y/z labels
26053
- const labels = ['x','y','z'];
26054
- const activeDims = multiViewActive ? mvDims : (compareMvActive ? compareMvDims : []);
26055
- const posX = activeDims.indexOf(view.dimX);
26056
- const posY = activeDims.indexOf(view.dimY);
26057
- const lblX = labels[posX] || 'x';
26058
- const lblY = labels[posY] || 'y';
26059
- const elX = document.getElementById('dim-' + lblX + '-size');
26060
- const elY = document.getElementById('dim-' + lblY + '-size');
26061
- if (elX) { elX.textContent = '/' + px; elX.className = 'dim-hover-idx'; }
26062
- if (elY) { elY.textContent = '/' + py; elY.className = 'dim-hover-idx'; }
26063
- }
26064
- },
26065
26014
  });
26066
26015
  });
26067
26016
  cv.addEventListener('mouseleave', () => {
@@ -26072,7 +26021,6 @@
26072
26021
  if (tq) tq.classList.remove('visible');
26073
26022
  _roiSuppressPixelInfo = false;
26074
26023
  if (!mvDraggingView && view.pixelInfoEl) view.pixelInfoEl.style.display = 'none';
26075
- if (_pixelInfoActive()) _resetDimbarHover();
26076
26024
  });
26077
26025
  // Click-to-copy: multiview canvases
26078
26026
  let _mvClickDown = null;
@@ -26935,7 +26883,6 @@
26935
26883
  view,
26936
26884
  onResult: (d, px, py) => {
26937
26885
  if (_pixelInfoActive()) {
26938
- _updateDimbarHover(px, py);
26939
26886
  return; // hover mode: only show hovered pane's value
26940
26887
  }
26941
26888
  // Non-hover: fetch for all other qmri views at same pixel
@@ -26962,10 +26909,9 @@
26962
26909
  });
26963
26910
  });
26964
26911
  cv.addEventListener('mouseleave', () => {
26965
- if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
26966
- qmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
26967
- if (_pixelInfoActive()) _resetDimbarHover();
26968
- });
26912
+ if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
26913
+ qmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
26914
+ });
26969
26915
  // ROI drawing on qMRI canvases
26970
26916
  if (rectRoiMode) cv.style.cursor = 'crosshair';
26971
26917
  cv.addEventListener('mousemove', (e) => {
@@ -27029,6 +26975,11 @@
27029
26975
  }
27030
26976
  });
27031
26977
  cv.addEventListener('mousedown', (e) => {
26978
+ if (!rectRoiMode && e.button === 0 && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey
26979
+ && !_cropActive && !_segMode && !_rulerMode) {
26980
+ _pixelInfoHeld = true;
26981
+ cv.dispatchEvent(new MouseEvent('mousemove', { clientX: e.clientX, clientY: e.clientY, bubbles: true }));
26982
+ }
27032
26983
  if (!rectRoiMode || e.button !== 0 || e.shiftKey || e.metaKey) return;
27033
26984
  e.preventDefault(); sink.focus();
27034
26985
  const rect = cv.getBoundingClientRect();
@@ -27449,15 +27400,17 @@
27449
27400
  view,
27450
27401
  displayEl: view.pixelInfoEl,
27451
27402
  qmriRole: view.qmriRole,
27452
- onResult: (d, px, py) => {
27453
- if (_pixelInfoActive()) _updateDimbarHover(px, py);
27454
- },
27455
27403
  });
27456
27404
  });
27405
+ cv.addEventListener('mousedown', (e) => {
27406
+ if (e.button !== 0 || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey
27407
+ || _cropActive || _segMode || _rulerMode) return;
27408
+ _pixelInfoHeld = true;
27409
+ cv.dispatchEvent(new MouseEvent('mousemove', { clientX: e.clientX, clientY: e.clientY, bubbles: true }));
27410
+ });
27457
27411
  cv.addEventListener('mouseleave', () => {
27458
27412
  if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
27459
27413
  compareQmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
27460
- if (_pixelInfoActive()) _resetDimbarHover();
27461
27414
  });
27462
27415
  if (!httpTransport) {
27463
27416
  view.ws = createTransport(sidVal,
@@ -27936,20 +27889,6 @@
27936
27889
  imgH: view.lastH,
27937
27890
  view,
27938
27891
  displayEl: view.pixelInfoEl,
27939
- onResult: (d, px, py) => {
27940
- if (_pixelInfoActive()) {
27941
- const labels = ['x','y','z'];
27942
- const activeDims = compareMvDims || [];
27943
- const posX = activeDims.indexOf(view.dimX);
27944
- const posY = activeDims.indexOf(view.dimY);
27945
- const lblX = labels[posX] || 'x';
27946
- const lblY = labels[posY] || 'y';
27947
- const elX = document.getElementById('dim-' + lblX + '-size');
27948
- const elY = document.getElementById('dim-' + lblY + '-size');
27949
- if (elX) { elX.textContent = '/' + px; elX.className = 'dim-hover-idx'; }
27950
- if (elY) { elY.textContent = '/' + py; elY.className = 'dim-hover-idx'; }
27951
- }
27952
- },
27953
27892
  });
27954
27893
  });
27955
27894
  cv.addEventListener('mouseleave', () => {
@@ -27957,7 +27896,6 @@
27957
27896
  if (!cmvDraggingView) {
27958
27897
  compareMvViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
27959
27898
  }
27960
- if (_pixelInfoActive()) _resetDimbarHover();
27961
27899
  });
27962
27900
  // Click-to-copy: compare-multiview canvases
27963
27901
  let _cmvClickDown = null;
@@ -30116,7 +30054,6 @@
30116
30054
  displayEl: piEl,
30117
30055
  onResult: (d, px, py) => {
30118
30056
  if (!_pixelInfoActive()) return;
30119
- _updateDimbarHover(px, py);
30120
30057
  // Show value on other compare panes at the same pixel
30121
30058
  const ver = indices.join(',') + ':' + dim_x + ':' + dim_y + ':' + complexMode + ':' + logScale;
30122
30059
  compareCanvases.forEach((ocv, j) => {
@@ -30145,7 +30082,6 @@
30145
30082
  cv.addEventListener('mouseleave', () => {
30146
30083
  _comparePiEls.forEach(p => { if (p) p.style.display = 'none'; });
30147
30084
  hideCompareCrosshairs();
30148
- if (_pixelInfoActive()) _resetDimbarHover();
30149
30085
  });
30150
30086
  });
30151
30087
  // Click-to-copy: compare canvases
@@ -44,7 +44,7 @@ def _vscode_app_bundle() -> str | None:
44
44
 
45
45
  _VSCODE_EXT_INSTALLED = False # cached so we only check once per process
46
46
  _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
47
- _VSCODE_EXT_VERSION = "0.14.33" # current bundled extension version
47
+ _VSCODE_EXT_VERSION = "0.14.35" # current bundled extension version
48
48
 
49
49
  def _bundled_vscode_vsix_version(vsix_path: str) -> str | None:
50
50
  """Return the bundled opener extension version recorded inside the VSIX."""
@@ -163,7 +163,11 @@ def _find_arrayview_window_id() -> str | None:
163
163
  pass
164
164
 
165
165
  # tmux: process tree is detached from VS Code terminal; check all session clients.
166
+ # Only accept the fallback if every attached client for this tmux session
167
+ # reports the same window ID. Returning the first client is unsafe when the
168
+ # same session is attached from multiple VS Code windows.
166
169
  if os.environ.get("TERM_PROGRAM") == "tmux":
170
+ tmux_window_ids: set[str] = set()
167
171
  try:
168
172
  r_sid = subprocess.run(
169
173
  ["tmux", "display-message", "-p", "#{session_id}"],
@@ -187,7 +191,9 @@ def _find_arrayview_window_id() -> str | None:
187
191
  with open(f"/proc/{client_pid}/environ", "rb") as fh:
188
192
  for entry in fh.read().split(b"\0"):
189
193
  if entry.startswith(b"ARRAYVIEW_WINDOW_ID="):
190
- return entry[len(b"ARRAYVIEW_WINDOW_ID="):].decode()
194
+ val = entry[len(b"ARRAYVIEW_WINDOW_ID="):].decode()
195
+ if val:
196
+ tmux_window_ids.add(val)
191
197
  except Exception:
192
198
  pass
193
199
  # macOS: ps ewwww
@@ -198,11 +204,21 @@ def _find_arrayview_window_id() -> str | None:
198
204
  )
199
205
  for token in r.stdout.split():
200
206
  if token.startswith("ARRAYVIEW_WINDOW_ID="):
201
- return token[len("ARRAYVIEW_WINDOW_ID="):]
207
+ val = token[len("ARRAYVIEW_WINDOW_ID="):]
208
+ if val:
209
+ tmux_window_ids.add(val)
202
210
  except Exception:
203
211
  pass
204
212
  except Exception:
205
213
  pass
214
+ if len(tmux_window_ids) == 1:
215
+ return next(iter(tmux_window_ids))
216
+ if len(tmux_window_ids) > 1:
217
+ _vprint(
218
+ "[ArrayView] signal: tmux clients report multiple "
219
+ f"ARRAYVIEW_WINDOW_ID values: {sorted(tmux_window_ids)}",
220
+ flush=True,
221
+ )
206
222
 
207
223
  return None
208
224
 
@@ -307,6 +323,38 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
307
323
  data.setdefault("maxAgeMs", _VSCODE_SIGNAL_MAX_AGE_MS)
308
324
  data.setdefault("requestId", uuid.uuid4().hex)
309
325
 
326
+ def _focused_window_fallback() -> tuple[str, ...]:
327
+ """Let the focused extension window claim an untargeted request."""
328
+ data["broadcast"] = True
329
+ return (
330
+ (_VSCODE_SIGNAL_FILENAME,)
331
+ if skip_compat
332
+ else (_VSCODE_SIGNAL_FILENAME, *_VSCODE_COMPAT_SIGNAL_FILENAMES)
333
+ )
334
+
335
+ def _registrations_are_remote(window_files: list[str]) -> bool:
336
+ """Return whether every registration belongs to a remote extension host."""
337
+ if not window_files:
338
+ return False
339
+ for window_file in window_files:
340
+ try:
341
+ with open(os.path.join(signal_dir, window_file)) as registration:
342
+ registration_data = json.load(registration)
343
+ remote_name = registration_data.get("remoteName")
344
+ if remote_name:
345
+ continue
346
+ pid = int(registration_data["pid"])
347
+ with open(f"/proc/{pid}/cmdline", "rb") as command_file:
348
+ command = command_file.read().replace(b"\0", b" ").decode()
349
+ if (
350
+ "/.vscode/cli/servers/" not in command
351
+ and "/.vscode-server/" not in command
352
+ ):
353
+ return False
354
+ except Exception:
355
+ return False
356
+ return True
357
+
310
358
  # --- Primary: ARRAYVIEW_WINDOW_ID (all platforms) ---
311
359
  # The extension injects this env var into every terminal via
312
360
  # EnvironmentVariableCollection. It uniquely identifies the VS Code
@@ -323,53 +371,8 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
323
371
  except Exception:
324
372
  uses_pid = env_wid.isdigit()
325
373
 
326
- # Guard against stale ARRAYVIEW_WINDOW_ID: when the extension
327
- # host restarts (e.g. tunnel reconnection), the old process may
328
- # still be alive but is no longer connected to a VS Code client.
329
- # Detect this by looking for a newer registration from the same
330
- # server (same first ppid — the tunnel/server parent process).
331
- # Only needed for PID-based IDs (fallbackId=True); hookTag IDs
332
- # are unique per IPC socket and never go stale across restarts.
333
- #
334
- # Skip on macOS: all extension hosts are direct children of the
335
- # same main Electron process (ppids[0] == Electron PID for every
336
- # window), so the "same ppids[0]" heuristic matches ALL concurrent
337
- # windows and incorrectly redirects signals to the wrong window.
338
- # The stable window ID feature (v0.14.0+) already handles restart
339
- # continuity on macOS via EnvironmentVariableCollection, making
340
- # this stale-redirect unnecessary there.
341
- if uses_pid and sys.platform != "darwin" and not _is_vscode_remote():
342
- _env_ts = _reg_data.get("ts", 0)
343
- _env_ppids = _reg_data.get("ppids", [])
344
- try:
345
- for _fname in os.listdir(signal_dir):
346
- if not (_fname.startswith("window-") and _fname.endswith(".json")):
347
- continue
348
- _other_wid = _fname[7:-5]
349
- if _other_wid == env_wid:
350
- continue
351
- with open(os.path.join(signal_dir, _fname)) as _f:
352
- _other = json.load(_f)
353
- _other_ts = _other.get("ts", 0)
354
- _other_ppids = _other.get("ppids", [])
355
- if (
356
- _other_ts > _env_ts
357
- and len(_env_ppids) >= 1
358
- and len(_other_ppids) >= 1
359
- and _env_ppids[0] == _other_ppids[0]
360
- ):
361
- _vprint(
362
- f"[ArrayView] signal: ARRAYVIEW_WINDOW_ID={env_wid} is stale "
363
- f"(newer registration {_other_wid} found), redirecting",
364
- flush=True,
365
- )
366
- env_wid = _other_wid
367
- _reg_data = _other
368
- uses_pid = _other.get("fallbackId", False)
369
- _env_ts = _other_ts
370
- except Exception:
371
- pass
372
-
374
+ # Trust an exact window ID. Redirecting to a newer same-parent
375
+ # registration can silently open the tab in a live sibling window.
373
376
  _prefix = "pid" if uses_pid else "ipc"
374
377
  filenames = (f"open-request-{_prefix}-{env_wid}.json",)
375
378
  targeted_via_env = True
@@ -410,9 +413,18 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
410
413
  flush=True,
411
414
  )
412
415
  elif len(_all_windows) > 1:
413
- if _is_vscode_remote():
416
+ if _registrations_are_remote(_all_windows):
417
+ filenames = _focused_window_fallback()
418
+ targeted_via_env = True
419
+ _vprint(
420
+ "[ArrayView] signal: stale ARRAYVIEW_WINDOW_ID with "
421
+ f"{len(_all_windows)} remote windows; "
422
+ "using focused-window fallback",
423
+ flush=True,
424
+ )
425
+ else:
414
426
  print(
415
- "[ArrayView] VS Code tunnel window is ambiguous: "
427
+ "[ArrayView] VS Code window is ambiguous: "
416
428
  f"ARRAYVIEW_WINDOW_ID={env_wid!r} is not registered "
417
429
  f"and {len(_all_windows)} VS Code windows are active. "
418
430
  "Open a fresh terminal in the target VS Code window "
@@ -420,23 +432,6 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
420
432
  flush=True,
421
433
  )
422
434
  return False
423
- else:
424
- _wfiles = []
425
- for _fn in _all_windows:
426
- _wid = _fn[7:-5]
427
- _wfiles.append(
428
- f"open-request-pid-{_wid}.json"
429
- if _wid.isdigit()
430
- else f"open-request-ipc-{_wid}.json"
431
- )
432
- data["broadcast"] = True
433
- filenames = tuple(_wfiles)
434
- targeted_via_env = True
435
- _vprint(
436
- f"[ArrayView] signal: {len(_all_windows)} windows registered, "
437
- f"broadcasting with focus guard",
438
- flush=True,
439
- )
440
435
  # else: 0 windows — fall through to subsequent targeting
441
436
 
442
437
  if targeted_via_env:
@@ -491,10 +486,30 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
491
486
  except Exception:
492
487
  pass
493
488
  if len(_wfiles) > 1:
494
- data["broadcast"] = True
495
- filenames = (
496
- tuple(_wfiles) if _wfiles else (_VSCODE_SIGNAL_FILENAME,)
497
- )
489
+ _window_files = [
490
+ f"window-{name.split('-', 3)[-1][:-5]}.json"
491
+ for name in _wfiles
492
+ ]
493
+ if _registrations_are_remote(_window_files):
494
+ filenames = _focused_window_fallback()
495
+ _vprint(
496
+ "[ArrayView] signal: no exact match for local "
497
+ f"terminal with {len(_wfiles)} remote windows; "
498
+ "using focused-window fallback",
499
+ flush=True,
500
+ )
501
+ else:
502
+ print(
503
+ "[ArrayView] VS Code window is ambiguous: "
504
+ "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
505
+ f"and {len(_wfiles)} VS Code windows are active. "
506
+ "Open a fresh terminal in the target VS Code window "
507
+ "and run ArrayView again.",
508
+ flush=True,
509
+ )
510
+ return False
511
+ else:
512
+ filenames = tuple(_wfiles) if _wfiles else (_VSCODE_SIGNAL_FILENAME,)
498
513
  else:
499
514
  filenames = (
500
515
  _VSCODE_SIGNAL_FILENAME,
@@ -535,13 +550,33 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
535
550
  except Exception:
536
551
  pass
537
552
  if len(window_files) > 1:
538
- # Mark as broadcast so extensions check focus before opening
539
- data["broadcast"] = True
540
- filenames = (
541
- tuple(window_files)
542
- if window_files
543
- else (_VSCODE_SIGNAL_FILENAME,)
544
- )
553
+ _registration_files = [
554
+ fname
555
+ for fname in os.listdir(signal_dir_temp)
556
+ if fname.startswith("window-") and fname.endswith(".json")
557
+ ]
558
+ if _registrations_are_remote(_registration_files):
559
+ filenames = _focused_window_fallback()
560
+ _vprint(
561
+ "[ArrayView] signal: local terminal has no exact "
562
+ f"match among {len(window_files)} remote windows; "
563
+ "using focused-window fallback",
564
+ flush=True,
565
+ )
566
+ else:
567
+ print(
568
+ "[ArrayView] VS Code window is ambiguous: "
569
+ "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
570
+ f"and {len(window_files)} VS Code windows are active. "
571
+ "Open a fresh terminal in the target VS Code window "
572
+ "and run ArrayView again.",
573
+ flush=True,
574
+ )
575
+ return False
576
+ else:
577
+ filenames = (
578
+ tuple(window_files) if window_files else (_VSCODE_SIGNAL_FILENAME,)
579
+ )
545
580
  else:
546
581
  # Not in VS Code terminal: use shared file
547
582
  filenames = (
@@ -604,15 +639,13 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
604
639
  flush=True,
605
640
  )
606
641
  elif len(_all_windows_r) > 1:
607
- print(
608
- "[ArrayView] VS Code tunnel window is ambiguous: "
609
- f"ARRAYVIEW_WINDOW_ID={env_wid!r} is not registered "
610
- f"and {len(_all_windows_r)} VS Code windows are active. "
611
- "Open a fresh terminal in the target VS Code window "
612
- "and run ArrayView again.",
642
+ filenames = _focused_window_fallback()
643
+ _vprint(
644
+ "[ArrayView] signal: stale remote ARRAYVIEW_WINDOW_ID with "
645
+ f"{len(_all_windows_r)} registered windows; "
646
+ "using focused-window fallback",
613
647
  flush=True,
614
648
  )
615
- return False
616
649
  else:
617
650
  env_wid = None # 0 windows: keep existing fallback
618
651
 
@@ -639,14 +672,13 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
639
672
  flush=True,
640
673
  )
641
674
  elif len(_all_windows_r) > 1:
642
- print(
643
- "[ArrayView] VS Code tunnel window is ambiguous: "
644
- "ARRAYVIEW_WINDOW_ID is not available and multiple "
645
- "VS Code windows are active. Open a fresh terminal in "
646
- "the target VS Code window and run ArrayView again.",
675
+ filenames = _focused_window_fallback()
676
+ _vprint(
677
+ "[ArrayView] signal: remote window ID unavailable with "
678
+ f"{len(_all_windows_r)} registered windows; "
679
+ "using focused-window fallback",
647
680
  flush=True,
648
681
  )
649
- return False
650
682
  else:
651
683
  _vprint(f"[ArrayView] signal: no registered remote window, using shared fallback", flush=True)
652
684
  filenames = (
@@ -800,4 +832,3 @@ def _cleanup_zombie_registrations(verbose: bool = False) -> int:
800
832
  pass
801
833
 
802
834
  return removed
803
-
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.29.0
3
+ Version: 0.29.2
4
4
  Summary: Fast multi-dimensional array viewer
5
5
  Project-URL: Home, https://github.com/oscarvanderheide/arrayview
6
6
  Project-URL: Source, https://github.com/oscarvanderheide/arrayview
@@ -10,7 +10,7 @@ arrayview/_icon.png,sha256=a8RTAbvweLhh3hzD5eGg76Peq-KSDcS6YrCphRb_HEs,2748
10
10
  arrayview/_imaging.py,sha256=MQX1uy4m1lofOeoLDhCp6cHO5p_V5fXm4qlSu9Ykn-c,534
11
11
  arrayview/_io.py,sha256=JX3_vn0C9JLhc_NhC5QwamsjQWDhbk7080E9JrSfWJk,28531
12
12
  arrayview/_launch_plan.py,sha256=hC9SoijWlYRvabwodIPePOnysVOVsnYcvlUYo4farIs,7277
13
- arrayview/_launcher.py,sha256=UUBBwNUWIc-BaW5fUqXt7ben7OniTEqNTFPY4mxv1zY,134312
13
+ arrayview/_launcher.py,sha256=NNQshRVqJQNC5we1fU-1uVzCC1oWm4QeR7cndg--HXM,134555
14
14
  arrayview/_lifecycle.py,sha256=oFD05gNYAu6OBIUFcKmPfT-_Hp03KaI2oK8komUXtJY,937
15
15
  arrayview/_overlays.py,sha256=C-qDZFBOgwJQL84XxFoMAX3qyfNa2EI5N-AlOvXeoTI,1634
16
16
  arrayview/_platform.py,sha256=U1XPqEjPeqy3l_xJh9Rmn_3d_6Fh_clJUcA0uR6QBGA,19043
@@ -33,15 +33,15 @@ arrayview/_shell.html,sha256=xzHIB-kRX12okubZoqdrgsWDY8CCLteg5jtXxDQru8U,11624
33
33
  arrayview/_synthetic_mri.py,sha256=CNL-2WC835i6WCqkvmWBCO6WQ5w7HE5trNb8OyEfe64,7365
34
34
  arrayview/_torch.py,sha256=13sqIMCUu_-b9SQ9KfDCExDz2UMStL87EDVjnbJxAEo,7536
35
35
  arrayview/_vectorfield.py,sha256=eDqMCMEGjO8Jdt-6Auh01cz3WD9dbF7xYUWD9xHAf1E,8115
36
- arrayview/_viewer.html,sha256=axWaarHTQ3EWuo7cL0b4jCZoiVWc1Vdu_KtnGCw-FeU,1761564
36
+ arrayview/_viewer.html,sha256=6QgxrHju1uv7rk5StEjXUxoX5TCSXEA76OIyx7PM5iI,1757428
37
37
  arrayview/_vscode.py,sha256=uY3SYJYjgzuYCbdycmm53ZaPNJIPPtGiIQZKBUobMz4,871
38
38
  arrayview/_vscode_browser.py,sha256=InU9EPDALA5ZZsAph6wNi7EwrZgbfn66uZy1iy4yQy8,7932
39
- arrayview/_vscode_extension.py,sha256=UzgMnV7VUGaIFB0BUhbS-aWi58VI1Ny70DoWTZ5LSXs,13423
40
- arrayview/_vscode_signal.py,sha256=3wvAG62WXsJII8iOERk1Z84s7stx51KBEGy4otKjCoI,35249
41
- arrayview/arrayview-opener.vsix,sha256=ezMBlbUfbEDRtfc3PZvc8yabnc0Wxxz8KLXY2vYN_qA,19266
39
+ arrayview/_vscode_extension.py,sha256=B7rTmFqzlxOB6rN0U_gntoIlqvOl7VRQA9SW--PXR_o,13423
40
+ arrayview/_vscode_signal.py,sha256=rJqe5EjtekKA06d4fc1GEoE0kSq_--TsxFMfj-SAW0w,36678
41
+ arrayview/arrayview-opener.vsix,sha256=1KWu52Mdk7TexvcnS5OpjyGubqUxNG5uPjkcYt1SWAY,19874
42
42
  arrayview/gsap.min.js,sha256=VGihm4idI0E1uqrWezWsiScB9F_jMosn7qs6pPSkeac,72304
43
- arrayview-0.29.0.dist-info/METADATA,sha256=L0hiVR0B8Nkw8sqcMisXxHfoWH3sgag97rap9a6qlLM,2054
44
- arrayview-0.29.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
45
- arrayview-0.29.0.dist-info/entry_points.txt,sha256=FQSz3M11B-zaVLDyXofKUDvMhHASz7OjmoLgI4nn7ko,105
46
- arrayview-0.29.0.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
47
- arrayview-0.29.0.dist-info/RECORD,,
43
+ arrayview-0.29.2.dist-info/METADATA,sha256=YUYJgmRoLFLN-n4QPLRwWmiSIbevuWoGrhQ5VAZiLFQ,2054
44
+ arrayview-0.29.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
45
+ arrayview-0.29.2.dist-info/entry_points.txt,sha256=FQSz3M11B-zaVLDyXofKUDvMhHASz7OjmoLgI4nn7ko,105
46
+ arrayview-0.29.2.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
47
+ arrayview-0.29.2.dist-info/RECORD,,