Omahub
← All plugins
S

Sony WF-1000XM4

by SpoilHeap

Left/right earbud and case battery for a Sony WF-1000XM4, read over its Bluetooth SPP status channel.

Security review

Review recommended · 1 finding

Deterministic scan — not a security guarantee

Low
Risk level
Low
Analyzed commit
7e38a82
Scanned
1 month ago

Flagged patterns appear only in documentation files (README / docs) — descriptive examples, not executable code.

  • Docs external_hosts README.md:12

    Downloads or connects to an external HTTP(S) host.

    git clone https://github.com/SpoilHeap/Omarchy.SonyXM4.git ~/.config/omarchy/plugins/io.github.spoilheap.sonyxm4

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
7e38a82
Reviewed
1 month ago

The plugin is a benign bar widget that reads battery levels from Sony WF-1000XM4 earbuds over Bluetooth. It uses standard system tools (bluetoothctl) and a local socket, with no network access, persistence, or credential handling. The only flagged item is a `git clone` URL in the README, which is documentation and not part of the plugin's runtime behavior.

  • The README contains a `git clone` command from an external host, but this is installation documentation and not executed by the plugin itself.
  • The Python helper runs `bluetoothctl` subprocesses, but only to query device info and connection state; no system modifications are made.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/SpoilHeap/Omarchy.SonyXM4 --enable
Hardware #bar

Omarchy.SonyXM4

An Omarchy shell plugin: left earbud, right earbud, and charging-case battery for a Sony WF-1000XM4, in the bar and broken out in a popup.

Bar icon and popup showing WF-1000XM4 connected, left/right earbud, and case battery

Install

git clone https://github.com/SpoilHeap/Omarchy.SonyXM4.git ~/.config/omarchy/plugins/io.github.spoilheap.sonyxm4
omarchy plugin enable io.github.spoilheap.sonyxm4
omarchy restart shell

No further setup: if your WF-1000XM4 is already paired (bluetoothctl devices shows it), the plugin finds it by name on its own. The deviceMac setting is only needed as an override — see Settings below — if you have more than one WF-1000XM4 paired at once, or renamed yours to something that no longer contains "WF-1000XM4".

Dependencies

  • python3 — stdlib only (socket, subprocess, json), no packages to install.
  • bluetoothctl (bluez-utils) — already present on any Omarchy install; used read-only, to list paired devices and check connection state.
  • Your earbuds paired once, the normal way, before installing this plugin.

No root, no extra groups, no background service, no network access, and no files written outside this plugin's own checkout — sony-xm4-status.py only opens a Bluetooth socket to a device already paired to your user session.

What this does, and how

The WF-1000XM4 doesn't expose battery over the standard BlueZ Battery1 D-Bus interface, and Sony hasn't published the protocol. Both had to be worked out by capturing what the earbuds actually send:

  • Battery rides RFCOMM channel 23, found by resolving the generic Serial Port Profile UUID (0x1101) that this device advertises over SDP. No request has to be sent — a couple of seconds after the socket connects, the earbuds push an unsolicited status burst on their own.
  • That burst is a flat run of TLV records, [0x03][field id][2-byte length][payload] back to back, no outer framing. The record with field id 0x03 is a 3-byte payload: [left%, right%, case%]. That's the one this plugin reads; the others in the burst (firmware/device info, a per-connection nonce) aren't understood and are ignored.

sony-xm4-status.py checks bluetoothctl info <mac> first, so it never blocks trying to open a socket to earbuds that are off or out of range. If they're connected, it opens channel 23, reads the burst, and prints the three percentages as JSON; if anything goes wrong it still exits 0 with an "ok": false error, so the widget always has something to render.

Settings are not supported. ANC, ambient sound, and Speak-to-Chat live behind a different, separate command channel (RFCOMM 9, an escaped 0x3e…0x3c-framed protocol distinct from the plain TLV battery channel). That channel was found and its framing decoded, but the actual command opcodes for the XM4's settings were not — commands reconstructed from publicly documented protocols for older Sony headphones (which use a different, simpler framing entirely) produced only a generic ACK from the XM4, with no audible or physical effect. Getting this working for real would need a real byte-level capture of the Sony Sound Connect app talking to the earbuds (e.g. a rooted-phone Bluetooth snoop log), which wasn't available. This is a known gap, not an oversight — see the commit history / issues for where this was left off if you'd like to pick it up.

Caveat on channel 23 itself: it was found empirically against one unit's firmware. RFCOMM channel numbers are normally stable per firmware revision, but if your earbuds report nothing, check bluetoothctl and, if you're comfortable reversing it yourself, re-scan for the channel that pushes data on connect (sdptool records <mac> or a channel sweep) and update the BATTERY_CHANNEL constant in sony-xm4-status.py.

Uninstall

omarchy plugin remove io.github.spoilheap.sonyxm4

No restart needed — remove unloads the plugin from the running shell immediately, deletes the checkout, and drops its entry from the bar layout in ~/.config/omarchy/shell.json. This plugin keeps no other state, key, or cache anywhere else, so there's nothing left to clean up afterward, and it doesn't touch Bluetooth pairing — your earbuds stay paired to the system.

(omarchy plugin disable io.github.spoilheap.sonyxm4 instead just turns it off without deleting anything, if you want it back later without a reinstall.)

Interactions

  • Bar icon — left: panel · right: refresh without opening
  • Panel — r refreshes, Tab moves to the neighboring bar panel, Esc closes
  • IPC — omarchy-shell io.github.spoilheap.sonyxm4 <open|close|toggle|refresh|status>
$ omarchy-shell io.github.spoilheap.sonyxm4 status
L 100%  ·  R 100%  ·  Case 20%

Settings

In this widget's entry in ~/.config/omarchy/shell.json, or via omarchy bar set io.github.spoilheap.sonyxm4 <key> <value>:

Key Default What it does
refreshIntervalSec 30 How often the helper is run
deviceMac "" Override MAC — leave empty to auto-detect by name (see Install)
omarchy bar set io.github.spoilheap.sonyxm4 deviceMac 'AA:BB:CC:DD:EE:FF'
omarchy bar set io.github.spoilheap.sonyxm4 refreshIntervalSec 60 --json

Files

File What it is
manifest.json plugin declaration and settings schema
Panel.qml the bar button and the popup
Service.qml runs the helper on a timer, holds the result
Model.js formatting — tiers, tooltip and status text
sony-xm4-status.py checks connection state, reads channel 23, prints one JSON object

sony-xm4-status.py always exits 0 with JSON, so a disconnected device or a socket error is a state the panel renders rather than an error it swallows. It can be run by hand to see exactly what the widget sees:

python3 ~/.config/omarchy/plugins/io.github.spoilheap.sonyxm4/sony-xm4-status.py
# or, to override auto-detect:
python3 ~/.config/omarchy/plugins/io.github.spoilheap.sonyxm4/sony-xm4-status.py AA:BB:CC:DD:EE:FF

Editing any file here reloads the plugin. If a change does not take — QML that is already loaded can hold on — force it with omarchy restart shell.