discstation 0.1.37 → 0.1.38

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.
@@ -221,6 +221,8 @@ String discLine = "Disc: none";
221
221
  String line1 = "Status: READY";
222
222
  String line2 = "";
223
223
  String line3 = "";
224
+ bool trayOpen = false; // host says the tray is open (eject wait / STANDBY:Tray open) - EJECT button then closes it
225
+ bool waitingIsTray = false; // the WAITING screen is the eject "close tray" wait, not the pre-burn confirm
224
226
  String waitingLine1 = "";
225
227
  String waitingLine2 = "";
226
228
  int progressPercent = -1;
@@ -456,7 +458,7 @@ void drawWaiting() {
456
458
  printUpper(waitingLine2);
457
459
 
458
460
  display.setCursor(2, 49);
459
- display.print("SHORT // GO");
461
+ display.print(waitingIsTray ? "EJECT BTN // CLOSE" : "SHORT // GO");
460
462
 
461
463
  display.display();
462
464
  }
@@ -774,6 +776,7 @@ void parseMessage(String msg) {
774
776
  Out.println(msg);
775
777
 
776
778
  if (msg.startsWith("HOME:")) {
779
+ trayOpen = false;
777
780
  drawHome();
778
781
 
779
782
  } else if (msg.startsWith("DISC:")) {
@@ -802,6 +805,7 @@ void parseMessage(String msg) {
802
805
  if (uiState == UI_HOME) drawHome();
803
806
 
804
807
  } else if (msg.startsWith("STANDBY:")) {
808
+ trayOpen = msg.substring(8) == "Tray open";
805
809
  drawStandby();
806
810
 
807
811
  } else if (msg.startsWith("TITLE:")) {
@@ -855,8 +859,13 @@ void parseMessage(String msg) {
855
859
  } else if (msg.startsWith("WAITING:")) {
856
860
  returnToHomeAt = 0;
857
861
  String rest = msg.substring(8);
862
+ waitingIsTray = rest.indexOf("close tray") >= 0;
863
+ if (waitingIsTray) trayOpen = true;
858
864
  int slash = rest.indexOf('/');
859
- if (slash >= 0) {
865
+ if (waitingIsTray) {
866
+ waitingLine1 = "TRAY OPEN";
867
+ waitingLine2 = "";
868
+ } else if (slash >= 0) {
860
869
  waitingLine1 = "Data: " + rest.substring(0, slash);
861
870
  waitingLine1.trim();
862
871
  waitingLine2 = "Disc: " + rest.substring(slash + 1);
@@ -989,14 +998,6 @@ void handleSelectPress(bool longPress) {
989
998
  drawStatus();
990
999
  }
991
1000
 
992
- } else if (uiState == UI_STANDBY) {
993
- Out.println("EJECT");
994
- line1 = "Ejecting...";
995
- line2 = "";
996
- line3 = "";
997
- returnToHomeAt = millis() + 5000;
998
- drawStatus();
999
-
1000
1001
  } else if (uiState == UI_STATUS && line1 == "Ejecting...") {
1001
1002
  // Manual escape if stuck on eject screen with no app response
1002
1003
  drawStandby();
@@ -1011,7 +1012,7 @@ void handleSelectPress(bool longPress) {
1011
1012
  if (longPress) {
1012
1013
  Out.println("CANCEL");
1013
1014
  drawHome();
1014
- } else {
1015
+ } else if (!waitingIsTray) { // tray close moved to the EJECT button
1015
1016
  Out.println("CONFIRM");
1016
1017
  line1 = "Confirmed!";
1017
1018
  line2 = "";
@@ -1144,7 +1145,15 @@ void handleEncoderCCW() {
1144
1145
  void handleEjectButton() {
1145
1146
  if (wakeDisplay()) { lastInputTime = millis(); return; }
1146
1147
  lastInputTime = millis();
1147
- if (uiState == UI_HOME || uiState == UI_STANDBY || uiState == UI_DISCONNECTED || uiState == UI_PLAY) {
1148
+ if (trayOpen && (uiState == UI_STANDBY || uiState == UI_HOME || (uiState == UI_WAITING && waitingIsTray))) {
1149
+ Out.println("CONFIRM"); // host closes the tray
1150
+ trayOpen = false;
1151
+ line1 = "Closing tray...";
1152
+ line2 = "";
1153
+ line3 = "";
1154
+ returnToHomeAt = millis() + 5000;
1155
+ drawStatus();
1156
+ } else if (uiState == UI_HOME || uiState == UI_STANDBY || uiState == UI_DISCONNECTED || uiState == UI_PLAY) {
1148
1157
  Out.println("EJECT");
1149
1158
  line1 = "Ejecting...";
1150
1159
  line2 = "";
package/install.sh CHANGED
@@ -52,6 +52,10 @@ if [[ ! -f "$CONFIG_DIR/server.crt" || ! -f "$CONFIG_DIR/server.key" ]]; then
52
52
  fi
53
53
 
54
54
  sudo usermod -aG dialout,cdrom "$USER" || true
55
+ # Don't let the kernel re-close an ejected tray whenever some process (a desktop
56
+ # volume monitor, a disc probe) opens the drive.
57
+ echo 'dev.cdrom.autoclose = 0' | sudo tee /etc/sysctl.d/90-discstation.conf >/dev/null \
58
+ && sudo sysctl -q -w dev.cdrom.autoclose=0 || true
55
59
  mkdir -p "$HOME/.config/systemd/user"
56
60
  cp "$ROOT_DIR/systemd/discstation.service" "$HOME/.config/systemd/user/discstation.service"
57
61
  systemctl --user daemon-reload
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discstation",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "DiscStation optical-media appliance host — one cross-OS installer",
5
5
  "bin": {
6
6
  "discstation-setup": "scripts/setup.mjs",
@@ -1002,30 +1002,41 @@ def eject_disc(ser, device):
1002
1002
  safe_send(ser, "ERROR:Eject failed")
1003
1003
  safe_send(ser, "STANDBY:Insert disc")
1004
1004
  return ok
1005
- subprocess.run(["sync"], timeout=5)
1005
+ # The kernel closes the tray again (dev.cdrom.autoclose) when anything opens
1006
+ # the drive while it's out, and every disc probe (blkid/lsdvd/wodim/mediainfo,
1007
+ # driven by the background poll and by the web/phone /disc-info requests)
1008
+ # does exactly that. So: mark the tray open BEFORE the eject command runs (new
1009
+ # probes now bail out) and hold _detect_lock so any probe already in flight
1010
+ # finishes first - otherwise a probe landing inside the few seconds the eject
1011
+ # takes shuts the tray right after it opens.
1012
+ with _detect_lock:
1013
+ _tray_open = True
1014
+ _tray_open_since = time.monotonic()
1015
+ subprocess.run(["sync"], timeout=5)
1006
1016
 
1007
- subprocess.run(["sg_raw", device, "1e", "00", "00", "00", "00", "00"],
1008
- timeout=5, capture_output=True)
1017
+ subprocess.run(["sg_raw", device, "1e", "00", "00", "00", "00", "00"],
1018
+ timeout=5, capture_output=True)
1009
1019
 
1010
- ok = False
1011
- for cmd in (["eject", device], ["sg_raw", device, "1b", "00", "00", "00", "02", "00"]):
1012
- if ok:
1013
- break
1014
- try:
1015
- r = subprocess.run(cmd, timeout=10, capture_output=True)
1016
- ok = r.returncode == 0
1020
+ ok = False
1021
+ for cmd in (["eject", device], ["sg_raw", device, "1b", "00", "00", "00", "02", "00"]):
1017
1022
  if ok:
1018
- print(f"{cmd[0]} eject ok")
1019
- else:
1020
- err = (r.stderr or r.stdout or b"failed").decode(errors="ignore").strip()[:40]
1021
- print(f"{cmd[0]} eject failed: {err}")
1022
- except Exception as e:
1023
- print(f"{cmd[0]} eject error: {e}")
1023
+ break
1024
+ try:
1025
+ r = subprocess.run(cmd, timeout=10, capture_output=True)
1026
+ ok = r.returncode == 0
1027
+ if ok:
1028
+ print(f"{cmd[0]} eject ok")
1029
+ else:
1030
+ err = (r.stderr or r.stdout or b"failed").decode(errors="ignore").strip()[:40]
1031
+ print(f"{cmd[0]} eject failed: {err}")
1032
+ except Exception as e:
1033
+ print(f"{cmd[0]} eject error: {e}")
1034
+ if not ok:
1035
+ _tray_open = False
1024
1036
 
1025
1037
  if ok:
1026
- _tray_open = True
1027
1038
  _tray_open_since = time.monotonic()
1028
- safe_send(ser, "WAITING:Press SELECT/to close tray")
1039
+ safe_send(ser, "WAITING:Press EJECT/to close tray")
1029
1040
  # WAITING: isn't in _record_web_status()'s prefix whitelist, so the
1030
1041
  # send above never reaches the web page - _tray_open has to be
1031
1042
  # published explicitly here, same as DISC: gets its own dedicated
@@ -1064,7 +1075,7 @@ def eject_disc(ser, device):
1064
1075
  continue
1065
1076
  if line == "PONG":
1066
1077
  continue
1067
- if line == "CONFIRM":
1078
+ if line in ("CONFIRM", "EJECT"): # EJECT button doubles as close while the tray is out
1068
1079
  print("Closing tray...")
1069
1080
  safe_send(ser, "STATUS:Closing tray...")
1070
1081
  for close_cmd in (
@@ -1265,7 +1276,7 @@ def _refresh_udev(device):
1265
1276
  def udev_cdrom_properties(device, refresh=False):
1266
1277
  if discstation_host.system_name() != "linux":
1267
1278
  return discstation_host.media_properties(device)
1268
- overlay = _refresh_udev(device) if refresh else {}
1279
+ overlay = _refresh_udev(device) if refresh and not _tray_open else {}
1269
1280
  properties = _udev_props_via_pyudev(device)
1270
1281
  if properties is None:
1271
1282
  # pyudev unavailable — fall back to parsing `udevadm info` output.
@@ -1306,7 +1317,7 @@ def _device_present(device):
1306
1317
 
1307
1318
 
1308
1319
  def is_blank_disc(device):
1309
- if not device:
1320
+ if not device or _tray_open: # probing an open tray re-closes it
1310
1321
  return False
1311
1322
  if not _device_present(device):
1312
1323
  return False
@@ -1357,7 +1368,7 @@ def is_blank_disc(device):
1357
1368
 
1358
1369
 
1359
1370
  def is_rewritable_disc(device):
1360
- if not device:
1371
+ if not device or _tray_open: # probing an open tray re-closes it
1361
1372
  return False
1362
1373
  """Return whether the inserted medium can be overwritten."""
1363
1374
  if not _device_present(device):
@@ -1714,6 +1725,8 @@ def _maybe_reset_stuck_drive(device, failed_probes):
1714
1725
 
1715
1726
 
1716
1727
  def _detect_disc_locked(device, settle, budget):
1728
+ if _tray_open: # an eject started while we waited for the lock
1729
+ return _disc_info(False, "none")
1717
1730
  deadline = time.monotonic() + (DISC_DETECT_BUDGET if budget is None else budget)
1718
1731
  props = udev_cdrom_properties(device, refresh=True)
1719
1732
 
@@ -4138,6 +4151,9 @@ def station_loop(ser):
4138
4151
  print(f"Menu: {line.split(':', 1)[1]}")
4139
4152
  continue
4140
4153
 
4154
+ if line == "EJECT" and _tray_open:
4155
+ line = "CONFIRM" # EJECT is a toggle: with the tray already out, close it
4156
+
4141
4157
  if line == "EJECT":
4142
4158
  try:
4143
4159
  device = discstation_burn.disc_device()