something-x-dev 1.3.0.dev6__tar.gz → 1.4.0.dev7__tar.gz
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.
- {something_x_dev-1.3.0.dev6/something_x_dev.egg-info → something_x_dev-1.4.0.dev7}/PKG-INFO +1 -1
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/pages/device.py +32 -6
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/protocol.py +32 -10
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/pyproject.toml +1 -1
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7/something_x_dev.egg-info}/PKG-INFO +1 -1
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/LICENSE +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/README.md +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/__init__.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/application.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/bluetooth.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/data/__init__.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/data/com.something.x.omarchy.desktop +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/data/style.css +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/pages/__init__.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/pages/home.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/profiles.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/splash.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/tray.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/nothing_app/window.py +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/setup.cfg +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/SOURCES.txt +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/dependency_links.txt +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/entry_points.txt +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/requires.txt +0 -0
- {something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/top_level.txt +0 -0
|
@@ -93,19 +93,24 @@ class EarbudVisual(Gtk.DrawingArea):
|
|
|
93
93
|
self._left = -1
|
|
94
94
|
self._right = -1
|
|
95
95
|
self._case = -1
|
|
96
|
+
self._left_wearing = False
|
|
97
|
+
self._right_wearing = False
|
|
96
98
|
|
|
97
|
-
def update(
|
|
99
|
+
def update(
|
|
100
|
+
self, left: int, right: int, case: int, left_wearing: bool = False, right_wearing: bool = False
|
|
101
|
+
):
|
|
98
102
|
self._left, self._right, self._case = left, right, case
|
|
103
|
+
self._left_wearing, self._right_wearing = left_wearing, right_wearing
|
|
99
104
|
self.queue_draw()
|
|
100
105
|
|
|
101
106
|
def _draw(self, _area, cr, width, height):
|
|
102
107
|
cx = width / 2
|
|
103
108
|
cy = height / 2 - 8
|
|
104
|
-
self._draw_bud(cr, cx - 92, cy, self._left, "L")
|
|
105
|
-
self._draw_bud(cr, cx + 92, cy, self._right, "R")
|
|
109
|
+
self._draw_bud(cr, cx - 92, cy, self._left, "L", self._left_wearing)
|
|
110
|
+
self._draw_bud(cr, cx + 92, cy, self._right, "R", self._right_wearing)
|
|
106
111
|
self._draw_case(cr, cx, cy + 54, self._case)
|
|
107
112
|
|
|
108
|
-
def _draw_bud(self, cr, cx, cy, pct, label):
|
|
113
|
+
def _draw_bud(self, cr, cx, cy, pct, label, wearing: bool = False):
|
|
109
114
|
R = 42
|
|
110
115
|
r = 29
|
|
111
116
|
bc = _battery_color(pct) if pct >= 0 else (0.18, 0.18, 0.18)
|
|
@@ -166,12 +171,27 @@ class EarbudVisual(Gtk.DrawingArea):
|
|
|
166
171
|
cr.move_to(cx - te.width / 2 - te.x_bearing, cy - te.height / 2 - te.y_bearing)
|
|
167
172
|
cr.show_text(text)
|
|
168
173
|
|
|
174
|
+
# in-ear indicator dot (always rendered; glows red when wearing)
|
|
175
|
+
dot_y = cy + R + 8
|
|
176
|
+
if wearing:
|
|
177
|
+
rg = cairo.RadialGradient(cx, dot_y, 0, cx, dot_y, 9)
|
|
178
|
+
rg.add_color_stop_rgba(0, 0.87, 0.18, 0.18, 0.30)
|
|
179
|
+
rg.add_color_stop_rgba(1, 0.87, 0.18, 0.18, 0.0)
|
|
180
|
+
cr.set_source(rg)
|
|
181
|
+
cr.arc(cx, dot_y, 9, 0, 2 * math.pi)
|
|
182
|
+
cr.fill()
|
|
183
|
+
cr.set_source_rgba(0.87, 0.18, 0.18, 0.9)
|
|
184
|
+
else:
|
|
185
|
+
cr.set_source_rgba(1.0, 1.0, 1.0, 0.07)
|
|
186
|
+
cr.arc(cx, dot_y, 3, 0, 2 * math.pi)
|
|
187
|
+
cr.fill()
|
|
188
|
+
|
|
169
189
|
# L / R label below
|
|
170
190
|
cr.set_source_rgba(1.0, 1.0, 1.0, 0.20)
|
|
171
191
|
cr.select_font_face(_MONO, cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
|
172
192
|
cr.set_font_size(9)
|
|
173
193
|
te = cr.text_extents(label)
|
|
174
|
-
cr.move_to(cx - te.width / 2 - te.x_bearing, cy + R +
|
|
194
|
+
cr.move_to(cx - te.width / 2 - te.x_bearing, cy + R + 20)
|
|
175
195
|
cr.show_text(label)
|
|
176
196
|
|
|
177
197
|
def _draw_case(self, cr, cx, cy, pct):
|
|
@@ -508,7 +528,13 @@ class DevicePage(Gtk.Box):
|
|
|
508
528
|
|
|
509
529
|
def _on_state_changed(self, dev: NothingDevice):
|
|
510
530
|
state = dev.state
|
|
511
|
-
self._visual.update(
|
|
531
|
+
self._visual.update(
|
|
532
|
+
state.left_battery,
|
|
533
|
+
state.right_battery,
|
|
534
|
+
state.case_battery,
|
|
535
|
+
state.left_wearing,
|
|
536
|
+
state.right_wearing,
|
|
537
|
+
)
|
|
512
538
|
self._sync_anc_ui(state.anc_mode)
|
|
513
539
|
self._sync_eq_ui(state.eq_preset)
|
|
514
540
|
self._updating_ui = True
|
|
@@ -417,6 +417,8 @@ class NothingDevice(GObject.Object):
|
|
|
417
417
|
GLib.timeout_add(3000, self._activation_fallback)
|
|
418
418
|
elif cmd_id == _CMD_SET_ACTIVATED:
|
|
419
419
|
_log(f"[RX INFO] activation ACK payload={payload.hex()}")
|
|
420
|
+
if not self._activated:
|
|
421
|
+
GLib.timeout_add(2000, self._poll_earphone_status)
|
|
420
422
|
self._activated = True
|
|
421
423
|
from . import profiles
|
|
422
424
|
|
|
@@ -433,8 +435,15 @@ class NothingDevice(GObject.Object):
|
|
|
433
435
|
changed = self._parse_battery(payload)
|
|
434
436
|
elif cmd_id in (_CMD_NOISE_RED, _EVT_NOISE_RED):
|
|
435
437
|
changed = self._parse_anc(payload)
|
|
436
|
-
elif cmd_id
|
|
438
|
+
elif cmd_id == _CMD_EARPHONE:
|
|
437
439
|
changed = self._parse_earphone_status(payload)
|
|
440
|
+
elif cmd_id == _EVT_STATUS:
|
|
441
|
+
# The pushed event only carries accurate data for the bud that
|
|
442
|
+
# changed; the other entries are stale placeholders. Use it purely
|
|
443
|
+
# as a trigger and re-query for a fresh full snapshot.
|
|
444
|
+
if _DEBUG:
|
|
445
|
+
_log(f"[protocol] EVT_STATUS {payload.hex()} → re-query GET_EARPHONE")
|
|
446
|
+
self._x55_send(_CMD_EARPHONE)
|
|
438
447
|
elif cmd_id == _CMD_HOST_VERSION:
|
|
439
448
|
ver = payload.decode(errors="replace").strip("\x00").strip()
|
|
440
449
|
if ver and ver != self.state.firmware_version:
|
|
@@ -512,26 +521,29 @@ class NothingDevice(GObject.Object):
|
|
|
512
521
|
return changed
|
|
513
522
|
|
|
514
523
|
def _parse_earphone_status(self, payload: bytes) -> bool:
|
|
515
|
-
# payload: [count:1][type:1][val:1]...
|
|
516
|
-
#
|
|
517
|
-
#
|
|
524
|
+
# payload: [count:1][type:1][val:1]... (only GET responses reach here;
|
|
525
|
+
# they are a fresh full snapshot, unlike the EVT push frames)
|
|
526
|
+
# EarphoneStatus.java: bit0=inCase, bit2=inEar, bit7=isConnect
|
|
527
|
+
# type: 2=left, 3=right, 4=case, 5=tws, 6=stereo
|
|
518
528
|
if len(payload) < 3:
|
|
519
529
|
return False
|
|
520
530
|
count = payload[0]
|
|
521
531
|
changed = False
|
|
532
|
+
if _DEBUG:
|
|
533
|
+
_log(f"[protocol] earphone raw={payload.hex()}")
|
|
522
534
|
for i in range(1, 1 + count * 2, 2):
|
|
523
535
|
if i + 1 >= len(payload):
|
|
524
536
|
break
|
|
525
537
|
etype = payload[i]
|
|
526
538
|
val = payload[i + 1]
|
|
539
|
+
if etype not in (2, 3):
|
|
540
|
+
continue
|
|
527
541
|
in_ear = bool(val & 0x04)
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if etype == 2 and wearing != self.state.left_wearing:
|
|
531
|
-
self.state.left_wearing = wearing
|
|
542
|
+
if etype == 2 and in_ear != self.state.left_wearing:
|
|
543
|
+
self.state.left_wearing = in_ear
|
|
532
544
|
changed = True
|
|
533
|
-
elif etype == 3 and
|
|
534
|
-
self.state.right_wearing =
|
|
545
|
+
elif etype == 3 and in_ear != self.state.right_wearing:
|
|
546
|
+
self.state.right_wearing = in_ear
|
|
535
547
|
changed = True
|
|
536
548
|
if changed:
|
|
537
549
|
_log(f"[protocol] wearing L={self.state.left_wearing} R={self.state.right_wearing}")
|
|
@@ -632,10 +644,20 @@ class NothingDevice(GObject.Object):
|
|
|
632
644
|
).start()
|
|
633
645
|
break
|
|
634
646
|
|
|
647
|
+
def _poll_earphone_status(self):
|
|
648
|
+
# The firmware only computes a fresh per-bud snapshot when asked; the
|
|
649
|
+
# pushed EVT frames carry stale placeholder entries for the bud that
|
|
650
|
+
# didn't change. Polling keeps both buds' wearing state accurate.
|
|
651
|
+
if not self._rfcomm_connected:
|
|
652
|
+
return False
|
|
653
|
+
self._x55_send(_CMD_EARPHONE)
|
|
654
|
+
return True
|
|
655
|
+
|
|
635
656
|
def _activation_fallback(self):
|
|
636
657
|
if not self._activated and self._rfcomm_connected:
|
|
637
658
|
_log("[protocol] activation ACK not received within 3s — sending GET queries")
|
|
638
659
|
self._activated = True
|
|
660
|
+
GLib.timeout_add(2000, self._poll_earphone_status)
|
|
639
661
|
self._x55_send(_CMD_BATTERY)
|
|
640
662
|
self._x55_send(_CMD_NOISE_RED, bytes([0x03]))
|
|
641
663
|
self._x55_send(_CMD_EARPHONE)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/requires.txt
RENAMED
|
File without changes
|
{something_x_dev-1.3.0.dev6 → something_x_dev-1.4.0.dev7}/something_x_dev.egg-info/top_level.txt
RENAMED
|
File without changes
|