something-x-dev 1.2.3.dev1__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.
- nothing_app/__init__.py +2 -0
- nothing_app/application.py +244 -0
- nothing_app/bluetooth.py +212 -0
- nothing_app/data/__init__.py +0 -0
- nothing_app/data/com.something.x.omarchy.desktop +13 -0
- nothing_app/data/style.css +530 -0
- nothing_app/pages/__init__.py +0 -0
- nothing_app/pages/device.py +599 -0
- nothing_app/pages/home.py +210 -0
- nothing_app/profiles.py +41 -0
- nothing_app/protocol.py +650 -0
- nothing_app/splash.py +181 -0
- nothing_app/window.py +89 -0
- something_x_dev-1.2.3.dev1.dist-info/METADATA +201 -0
- something_x_dev-1.2.3.dev1.dist-info/RECORD +19 -0
- something_x_dev-1.2.3.dev1.dist-info/WHEEL +5 -0
- something_x_dev-1.2.3.dev1.dist-info/entry_points.txt +2 -0
- something_x_dev-1.2.3.dev1.dist-info/licenses/LICENSE +21 -0
- something_x_dev-1.2.3.dev1.dist-info/top_level.txt +1 -0
nothing_app/__init__.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import importlib.resources
|
|
6
|
+
import gi
|
|
7
|
+
|
|
8
|
+
gi.require_version("Gtk", "4.0")
|
|
9
|
+
gi.require_version("Adw", "1")
|
|
10
|
+
gi.require_version("Gdk", "4.0")
|
|
11
|
+
from gi.repository import Gtk, Adw, Gdk, Gio, GLib
|
|
12
|
+
|
|
13
|
+
from .bluetooth import BluetoothManager
|
|
14
|
+
from .window import SomethingXWindow
|
|
15
|
+
from .splash import SplashScreen
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _install_desktop_file():
|
|
19
|
+
dest_dir = os.path.expanduser("~/.local/share/applications")
|
|
20
|
+
dest = os.path.join(dest_dir, "com.something.x.omarchy.desktop")
|
|
21
|
+
if os.path.exists(dest):
|
|
22
|
+
return
|
|
23
|
+
try:
|
|
24
|
+
ref = importlib.resources.files("nothing_app.data").joinpath("com.something.x.omarchy.desktop")
|
|
25
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
26
|
+
with importlib.resources.as_file(ref) as src:
|
|
27
|
+
shutil.copy2(src, dest)
|
|
28
|
+
subprocess.run(["update-desktop-database", dest_dir], capture_output=True)
|
|
29
|
+
print("[app] desktop file installed to ~/.local/share/applications/")
|
|
30
|
+
except Exception as exc:
|
|
31
|
+
print(f"[app] desktop file install skipped: {exc}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _css_path() -> str:
|
|
35
|
+
try:
|
|
36
|
+
ref = importlib.resources.files("nothing_app.data").joinpath("style.css")
|
|
37
|
+
return str(ref)
|
|
38
|
+
except Exception:
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
return os.path.join(os.path.dirname(__file__), "data", "style.css")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SomethingXApplication(Adw.Application):
|
|
45
|
+
def __init__(self):
|
|
46
|
+
super().__init__(
|
|
47
|
+
application_id="com.something.x.omarchy",
|
|
48
|
+
flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
|
|
49
|
+
)
|
|
50
|
+
self._bt: BluetoothManager | None = None
|
|
51
|
+
self._splash: SplashScreen | None = None
|
|
52
|
+
self._window: SomethingXWindow | None = None
|
|
53
|
+
self.connect("activate", self._on_activate)
|
|
54
|
+
|
|
55
|
+
def _on_activate(self, _app):
|
|
56
|
+
# Second launch while already running: just show the existing window
|
|
57
|
+
if self._window is not None:
|
|
58
|
+
self._window.present()
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
_install_desktop_file()
|
|
62
|
+
Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_DARK)
|
|
63
|
+
self._load_css()
|
|
64
|
+
self._bt = BluetoothManager()
|
|
65
|
+
splash = SplashScreen(on_done=self._on_splash_done)
|
|
66
|
+
splash.set_application(self)
|
|
67
|
+
self._splash = splash
|
|
68
|
+
splash.present()
|
|
69
|
+
splash.start()
|
|
70
|
+
|
|
71
|
+
def _on_splash_done(self):
|
|
72
|
+
win = SomethingXWindow(bt_manager=self._bt, application=self)
|
|
73
|
+
win.connect("close-request", self._on_window_close)
|
|
74
|
+
self._window = win
|
|
75
|
+
win.present()
|
|
76
|
+
if self._splash:
|
|
77
|
+
self._splash.destroy()
|
|
78
|
+
self._splash = None
|
|
79
|
+
|
|
80
|
+
def _on_window_close(self, _win):
|
|
81
|
+
# Hide instead of destroy so the app keeps running in background
|
|
82
|
+
self._window.hide()
|
|
83
|
+
subprocess.Popen(
|
|
84
|
+
[
|
|
85
|
+
"notify-send",
|
|
86
|
+
"-i",
|
|
87
|
+
"audio-headphones",
|
|
88
|
+
"Something X",
|
|
89
|
+
"Running in background. Launch again to reopen.",
|
|
90
|
+
],
|
|
91
|
+
start_new_session=True,
|
|
92
|
+
)
|
|
93
|
+
return True # prevent default close/destroy
|
|
94
|
+
|
|
95
|
+
def _load_css(self):
|
|
96
|
+
provider = Gtk.CssProvider()
|
|
97
|
+
css = _css_path()
|
|
98
|
+
try:
|
|
99
|
+
provider.load_from_path(css)
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
print(f"[app] CSS load failed ({css}): {exc}")
|
|
102
|
+
|
|
103
|
+
display = Gdk.Display.get_default()
|
|
104
|
+
if display:
|
|
105
|
+
Gtk.StyleContext.add_provider_for_display(
|
|
106
|
+
display,
|
|
107
|
+
provider,
|
|
108
|
+
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ── CLI quick-toggle mode ─────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
_ANC_ALIASES = {
|
|
115
|
+
"off": 0,
|
|
116
|
+
"0": 0,
|
|
117
|
+
"on": 1,
|
|
118
|
+
"anc": 1,
|
|
119
|
+
"noise": 1,
|
|
120
|
+
"transparency": 2,
|
|
121
|
+
"trans": 2,
|
|
122
|
+
"passthrough": 2,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_EQ_ALIASES = {
|
|
126
|
+
"balanced": "Balanced",
|
|
127
|
+
"bass": "More Bass",
|
|
128
|
+
"treble": "More Treble",
|
|
129
|
+
"voice": "Voice",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _run_cli(argv: list[str]) -> int:
|
|
134
|
+
from . import protocol as _proto
|
|
135
|
+
|
|
136
|
+
_proto._QUIET = True
|
|
137
|
+
from .protocol import NothingDevice, ANCMode
|
|
138
|
+
from . import profiles
|
|
139
|
+
|
|
140
|
+
address = None
|
|
141
|
+
if "--device" in argv:
|
|
142
|
+
idx = argv.index("--device")
|
|
143
|
+
if idx + 1 < len(argv):
|
|
144
|
+
address = argv[idx + 1]
|
|
145
|
+
|
|
146
|
+
if address is None:
|
|
147
|
+
address = profiles.get_last_device()
|
|
148
|
+
|
|
149
|
+
if address is None:
|
|
150
|
+
print(
|
|
151
|
+
"No known device. Open the GUI and connect to a device first,\n"
|
|
152
|
+
"or pass --device AA:BB:CC:DD:EE:FF.",
|
|
153
|
+
file=sys.stderr,
|
|
154
|
+
)
|
|
155
|
+
return 1
|
|
156
|
+
|
|
157
|
+
loop = GLib.MainLoop()
|
|
158
|
+
dev = NothingDevice(address)
|
|
159
|
+
exit_code = [0]
|
|
160
|
+
_acted = [False]
|
|
161
|
+
|
|
162
|
+
def _act():
|
|
163
|
+
if _acted[0]:
|
|
164
|
+
return False
|
|
165
|
+
_acted[0] = True
|
|
166
|
+
|
|
167
|
+
if "--battery" in argv:
|
|
168
|
+
s = dev.state
|
|
169
|
+
parts = []
|
|
170
|
+
if s.left_battery >= 0:
|
|
171
|
+
parts.append(f"Left: {s.left_battery}%")
|
|
172
|
+
if s.right_battery >= 0:
|
|
173
|
+
parts.append(f"Right: {s.right_battery}%")
|
|
174
|
+
if s.case_battery >= 0:
|
|
175
|
+
parts.append(f"Case: {s.case_battery}%")
|
|
176
|
+
print(" ".join(parts) if parts else "No battery data received.")
|
|
177
|
+
|
|
178
|
+
if "--anc" in argv:
|
|
179
|
+
idx = argv.index("--anc")
|
|
180
|
+
val = argv[idx + 1] if idx + 1 < len(argv) else ""
|
|
181
|
+
mode = _ANC_ALIASES.get(val.lower())
|
|
182
|
+
if mode is None:
|
|
183
|
+
print(f"Unknown ANC value '{val}'. Use: off, on, transparency", file=sys.stderr)
|
|
184
|
+
exit_code[0] = 1
|
|
185
|
+
else:
|
|
186
|
+
dev.set_anc_mode(mode)
|
|
187
|
+
print(f"ANC → {ANCMode.LABELS.get(mode)}")
|
|
188
|
+
|
|
189
|
+
if "--eq" in argv:
|
|
190
|
+
idx = argv.index("--eq")
|
|
191
|
+
val = argv[idx + 1] if idx + 1 < len(argv) else ""
|
|
192
|
+
preset = _EQ_ALIASES.get(val.lower())
|
|
193
|
+
if preset is None:
|
|
194
|
+
print(f"Unknown EQ preset '{val}'. Use: balanced, bass, treble, voice", file=sys.stderr)
|
|
195
|
+
exit_code[0] = 1
|
|
196
|
+
else:
|
|
197
|
+
dev.set_eq_preset(preset)
|
|
198
|
+
print(f"EQ → {preset}")
|
|
199
|
+
|
|
200
|
+
GLib.timeout_add(600, loop.quit)
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def _on_state_changed(_d):
|
|
204
|
+
if dev.state.left_battery >= 0 or dev.state.right_battery >= 0:
|
|
205
|
+
_act()
|
|
206
|
+
|
|
207
|
+
def _on_timeout():
|
|
208
|
+
print("Timeout: device did not respond in time.", file=sys.stderr)
|
|
209
|
+
exit_code[0] = 1
|
|
210
|
+
loop.quit()
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
dev.connect("state-changed", _on_state_changed)
|
|
214
|
+
dev.connect_rfcomm()
|
|
215
|
+
GLib.timeout_add(12000, _on_timeout)
|
|
216
|
+
loop.run()
|
|
217
|
+
dev.disconnect_rfcomm()
|
|
218
|
+
return exit_code[0]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _print_help():
|
|
222
|
+
print(
|
|
223
|
+
"Usage:\n"
|
|
224
|
+
" something-x launch GUI\n"
|
|
225
|
+
" something-x --battery print battery levels\n"
|
|
226
|
+
" something-x --anc off|on|transparency set ANC mode\n"
|
|
227
|
+
" something-x --eq balanced|bass|treble|voice set EQ preset\n"
|
|
228
|
+
" something-x --device AA:BB:CC:DD:EE:FF target specific device\n"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def main():
|
|
233
|
+
argv = sys.argv[1:]
|
|
234
|
+
cli_flags = {"--battery", "--anc", "--eq"}
|
|
235
|
+
|
|
236
|
+
if "--help" in argv or "-h" in argv:
|
|
237
|
+
_print_help()
|
|
238
|
+
sys.exit(0)
|
|
239
|
+
|
|
240
|
+
if any(f in argv for f in cli_flags):
|
|
241
|
+
sys.exit(_run_cli(argv))
|
|
242
|
+
|
|
243
|
+
app = SomethingXApplication()
|
|
244
|
+
sys.exit(app.run(sys.argv))
|
nothing_app/bluetooth.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import dbus
|
|
2
|
+
import dbus.mainloop.glib
|
|
3
|
+
from gi.repository import GLib, GObject
|
|
4
|
+
|
|
5
|
+
BLUEZ_SERVICE = "org.bluez"
|
|
6
|
+
ADAPTER_IFACE = "org.bluez.Adapter1"
|
|
7
|
+
DEVICE_IFACE = "org.bluez.Device1"
|
|
8
|
+
BATTERY_IFACE = "org.bluez.Battery1"
|
|
9
|
+
OBJ_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
|
|
10
|
+
PROPERTIES_IFACE = "org.freedesktop.DBus.Properties"
|
|
11
|
+
|
|
12
|
+
NOTHING_PATTERNS = (
|
|
13
|
+
"nothing ear",
|
|
14
|
+
"ear (1)",
|
|
15
|
+
"ear (2)",
|
|
16
|
+
"ear (a)",
|
|
17
|
+
"ear (stick)",
|
|
18
|
+
"cmf buds",
|
|
19
|
+
"cmf earphone",
|
|
20
|
+
"nothing phone",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BluetoothDevice:
|
|
25
|
+
def __init__(self, path: str, props: dict):
|
|
26
|
+
self.path = path
|
|
27
|
+
self.address: str = str(props.get("Address", ""))
|
|
28
|
+
self.name: str = str(props.get("Name", props.get("Alias", "Unknown Device")))
|
|
29
|
+
self.connected: bool = bool(props.get("Connected", False))
|
|
30
|
+
self.paired: bool = bool(props.get("Paired", False))
|
|
31
|
+
self.battery: int | None = None
|
|
32
|
+
self.icon: str = str(props.get("Icon", "audio-headphones"))
|
|
33
|
+
self.is_nothing: bool = any(p in self.name.lower() for p in NOTHING_PATTERNS)
|
|
34
|
+
|
|
35
|
+
def update(self, changed: dict):
|
|
36
|
+
if "Connected" in changed:
|
|
37
|
+
self.connected = bool(changed["Connected"])
|
|
38
|
+
if "Name" in changed:
|
|
39
|
+
self.name = str(changed["Name"])
|
|
40
|
+
if "Alias" in changed and not self.name:
|
|
41
|
+
self.name = str(changed["Alias"])
|
|
42
|
+
|
|
43
|
+
def __repr__(self):
|
|
44
|
+
return f"<BTDevice {self.name!r} {'●' if self.connected else '○'}>"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BluetoothManager(GObject.Object):
|
|
48
|
+
__gsignals__ = {
|
|
49
|
+
"devices-changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
|
|
50
|
+
"device-connected": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
|
51
|
+
"device-disconnected": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.devices: dict[str, BluetoothDevice] = {}
|
|
57
|
+
self._bus: dbus.SystemBus | None = None
|
|
58
|
+
self._available = False
|
|
59
|
+
self._init_dbus()
|
|
60
|
+
|
|
61
|
+
def _init_dbus(self):
|
|
62
|
+
try:
|
|
63
|
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
|
64
|
+
self._bus = dbus.SystemBus()
|
|
65
|
+
self._refresh()
|
|
66
|
+
self._subscribe()
|
|
67
|
+
self._available = True
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
print(f"[bluetooth] D-Bus init failed: {exc}")
|
|
70
|
+
|
|
71
|
+
def _refresh(self):
|
|
72
|
+
if not self._bus:
|
|
73
|
+
return
|
|
74
|
+
try:
|
|
75
|
+
mgr = dbus.Interface(
|
|
76
|
+
self._bus.get_object(BLUEZ_SERVICE, "/"),
|
|
77
|
+
OBJ_MANAGER_IFACE,
|
|
78
|
+
)
|
|
79
|
+
objects = mgr.GetManagedObjects()
|
|
80
|
+
self.devices = {}
|
|
81
|
+
for path, ifaces in objects.items():
|
|
82
|
+
if DEVICE_IFACE not in ifaces:
|
|
83
|
+
continue
|
|
84
|
+
props = {str(k): v for k, v in ifaces[DEVICE_IFACE].items()}
|
|
85
|
+
dev = BluetoothDevice(str(path), props)
|
|
86
|
+
if BATTERY_IFACE in ifaces:
|
|
87
|
+
dev.battery = int(ifaces[BATTERY_IFACE].get("Percentage", 0))
|
|
88
|
+
self.devices[str(path)] = dev
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
print(f"[bluetooth] Refresh failed: {exc}")
|
|
91
|
+
|
|
92
|
+
def _subscribe(self):
|
|
93
|
+
if not self._bus:
|
|
94
|
+
return
|
|
95
|
+
try:
|
|
96
|
+
self._bus.add_signal_receiver(
|
|
97
|
+
self._on_props_changed,
|
|
98
|
+
signal_name="PropertiesChanged",
|
|
99
|
+
dbus_interface=PROPERTIES_IFACE,
|
|
100
|
+
path_keyword="path",
|
|
101
|
+
)
|
|
102
|
+
self._bus.add_signal_receiver(
|
|
103
|
+
self._on_ifaces_added,
|
|
104
|
+
signal_name="InterfacesAdded",
|
|
105
|
+
dbus_interface=OBJ_MANAGER_IFACE,
|
|
106
|
+
bus_name=BLUEZ_SERVICE,
|
|
107
|
+
)
|
|
108
|
+
self._bus.add_signal_receiver(
|
|
109
|
+
self._on_ifaces_removed,
|
|
110
|
+
signal_name="InterfacesRemoved",
|
|
111
|
+
dbus_interface=OBJ_MANAGER_IFACE,
|
|
112
|
+
bus_name=BLUEZ_SERVICE,
|
|
113
|
+
)
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
print(f"[bluetooth] Signal subscribe failed: {exc}")
|
|
116
|
+
|
|
117
|
+
def _on_props_changed(self, interface, changed, _invalidated=None, path=None):
|
|
118
|
+
if interface != DEVICE_IFACE:
|
|
119
|
+
return
|
|
120
|
+
if not path:
|
|
121
|
+
return
|
|
122
|
+
path = str(path)
|
|
123
|
+
if path not in self.devices:
|
|
124
|
+
return
|
|
125
|
+
dev = self.devices[path]
|
|
126
|
+
old_connected = dev.connected
|
|
127
|
+
dev.update({str(k): v for k, v in changed.items()})
|
|
128
|
+
if dev.connected != old_connected:
|
|
129
|
+
sig = "device-connected" if dev.connected else "device-disconnected"
|
|
130
|
+
GLib.idle_add(self.emit, sig, path)
|
|
131
|
+
GLib.idle_add(self.emit, "devices-changed")
|
|
132
|
+
|
|
133
|
+
def _on_ifaces_added(self, path, ifaces):
|
|
134
|
+
if DEVICE_IFACE not in ifaces:
|
|
135
|
+
return
|
|
136
|
+
props = {str(k): v for k, v in ifaces[DEVICE_IFACE].items()}
|
|
137
|
+
dev = BluetoothDevice(str(path), props)
|
|
138
|
+
if BATTERY_IFACE in ifaces:
|
|
139
|
+
dev.battery = int(ifaces[BATTERY_IFACE].get("Percentage", 0))
|
|
140
|
+
self.devices[str(path)] = dev
|
|
141
|
+
GLib.idle_add(self.emit, "devices-changed")
|
|
142
|
+
|
|
143
|
+
def _on_ifaces_removed(self, path, ifaces):
|
|
144
|
+
path = str(path)
|
|
145
|
+
if DEVICE_IFACE in ifaces and path in self.devices:
|
|
146
|
+
del self.devices[path]
|
|
147
|
+
GLib.idle_add(self.emit, "devices-changed")
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def available(self) -> bool:
|
|
151
|
+
return self._available
|
|
152
|
+
|
|
153
|
+
def get_all(self) -> list[BluetoothDevice]:
|
|
154
|
+
return sorted(self.devices.values(), key=lambda d: (not d.connected, d.name))
|
|
155
|
+
|
|
156
|
+
def get_nothing_devices(self) -> list[BluetoothDevice]:
|
|
157
|
+
return [d for d in self.get_all() if d.is_nothing]
|
|
158
|
+
|
|
159
|
+
def refresh(self):
|
|
160
|
+
self._refresh()
|
|
161
|
+
self.emit("devices-changed")
|
|
162
|
+
|
|
163
|
+
def connect_device(self, path: str, on_error=None):
|
|
164
|
+
if not self._bus:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
def _err(e):
|
|
168
|
+
print(f"[BT] connect error: {e}")
|
|
169
|
+
if on_error:
|
|
170
|
+
GLib.idle_add(on_error)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
iface = dbus.Interface(self._bus.get_object(BLUEZ_SERVICE, path), DEVICE_IFACE)
|
|
174
|
+
iface.Connect(reply_handler=lambda: None, error_handler=_err)
|
|
175
|
+
except Exception as exc:
|
|
176
|
+
print(f"[bluetooth] connect {path}: {exc}")
|
|
177
|
+
if on_error:
|
|
178
|
+
GLib.idle_add(on_error)
|
|
179
|
+
|
|
180
|
+
def disconnect_device(self, path: str):
|
|
181
|
+
if not self._bus:
|
|
182
|
+
return
|
|
183
|
+
try:
|
|
184
|
+
iface = dbus.Interface(self._bus.get_object(BLUEZ_SERVICE, path), DEVICE_IFACE)
|
|
185
|
+
iface.Disconnect(
|
|
186
|
+
reply_handler=lambda: None,
|
|
187
|
+
error_handler=lambda e: print(f"[BT] disconnect error: {e}"),
|
|
188
|
+
)
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
print(f"[bluetooth] disconnect {path}: {exc}")
|
|
191
|
+
|
|
192
|
+
def start_discovery(self):
|
|
193
|
+
if not self._bus:
|
|
194
|
+
return
|
|
195
|
+
try:
|
|
196
|
+
mgr = dbus.Interface(self._bus.get_object(BLUEZ_SERVICE, "/"), OBJ_MANAGER_IFACE)
|
|
197
|
+
for path, ifaces in mgr.GetManagedObjects().items():
|
|
198
|
+
if ADAPTER_IFACE in ifaces:
|
|
199
|
+
adapter = dbus.Interface(self._bus.get_object(BLUEZ_SERVICE, path), ADAPTER_IFACE)
|
|
200
|
+
adapter.StartDiscovery()
|
|
201
|
+
GLib.timeout_add_seconds(30, self._stop_discovery_on_path, str(path))
|
|
202
|
+
break
|
|
203
|
+
except Exception as exc:
|
|
204
|
+
print(f"[bluetooth] discovery start: {exc}")
|
|
205
|
+
|
|
206
|
+
def _stop_discovery_on_path(self, path: str):
|
|
207
|
+
try:
|
|
208
|
+
adapter = dbus.Interface(self._bus.get_object(BLUEZ_SERVICE, path), ADAPTER_IFACE)
|
|
209
|
+
adapter.StopDiscovery()
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[Desktop Entry]
|
|
2
|
+
Version=1.0
|
|
3
|
+
Type=Application
|
|
4
|
+
Name=Something X
|
|
5
|
+
GenericName=Bluetooth Device Manager
|
|
6
|
+
Comment=Manage Nothing and CMF Bluetooth devices
|
|
7
|
+
Exec=something-x
|
|
8
|
+
Icon=audio-headphones
|
|
9
|
+
Categories=Utility;GTK;HardwareSettings;
|
|
10
|
+
Keywords=bluetooth;nothing;ear;cmf;audio;headphones;earbuds;
|
|
11
|
+
Terminal=false
|
|
12
|
+
StartupNotify=true
|
|
13
|
+
StartupWMClass=com.something.x.omarchy
|