something-x-dev 1.7.0.dev15__tar.gz → 1.8.0.dev18__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.
Files changed (49) hide show
  1. something_x_dev-1.8.0.dev18/.github/CODEOWNERS +1 -0
  2. something_x_dev-1.8.0.dev18/.github/workflows/ci.yml +64 -0
  3. something_x_dev-1.8.0.dev18/.github/workflows/release-dev.yml +108 -0
  4. something_x_dev-1.8.0.dev18/.github/workflows/release.yml +122 -0
  5. something_x_dev-1.8.0.dev18/.gitignore +46 -0
  6. something_x_dev-1.8.0.dev18/DEVICES.md +37 -0
  7. {something_x_dev-1.7.0.dev15/something_x_dev.egg-info → something_x_dev-1.8.0.dev18}/PKG-INFO +1 -1
  8. something_x_dev-1.8.0.dev18/PKGBUILD +24 -0
  9. something_x_dev-1.8.0.dev18/ROADMAP.md +69 -0
  10. something_x_dev-1.8.0.dev18/cliff.toml +41 -0
  11. something_x_dev-1.8.0.dev18/data/style.css +355 -0
  12. something_x_dev-1.8.0.dev18/docs/RELEASING.md +192 -0
  13. something_x_dev-1.8.0.dev18/docs/docs.html +802 -0
  14. something_x_dev-1.8.0.dev18/docs/index.html +588 -0
  15. something_x_dev-1.8.0.dev18/flake.nix +66 -0
  16. something_x_dev-1.8.0.dev18/main.py +10 -0
  17. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/__init__.py +4 -5
  18. something_x_dev-1.8.0.dev18/nothing_app/_version.py +24 -0
  19. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/pyproject.toml +31 -2
  20. something_x_dev-1.8.0.dev18/requirements.txt +3 -0
  21. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18/something_x_dev.egg-info}/PKG-INFO +1 -1
  22. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/something_x_dev.egg-info/SOURCES.txt +20 -0
  23. something_x_dev-1.8.0.dev18/somethingx +3 -0
  24. something_x_dev-1.8.0.dev18/tests/__init__.py +0 -0
  25. something_x_dev-1.8.0.dev18/tests/conftest.py +40 -0
  26. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/LICENSE +0 -0
  27. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/README.md +0 -0
  28. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/application.py +0 -0
  29. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/bluetooth.py +0 -0
  30. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/data/__init__.py +0 -0
  31. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/data/com.something.x.omarchy.desktop +0 -0
  32. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/data/style.css +0 -0
  33. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/pages/__init__.py +0 -0
  34. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/pages/device.py +0 -0
  35. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/pages/home.py +0 -0
  36. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/profiles.py +0 -0
  37. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/protocol.py +0 -0
  38. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/splash.py +0 -0
  39. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/tray.py +0 -0
  40. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/nothing_app/window.py +0 -0
  41. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/setup.cfg +0 -0
  42. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/something_x_dev.egg-info/dependency_links.txt +0 -0
  43. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/something_x_dev.egg-info/entry_points.txt +0 -0
  44. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/something_x_dev.egg-info/requires.txt +0 -0
  45. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/something_x_dev.egg-info/top_level.txt +0 -0
  46. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/tests/test_bluetooth.py +0 -0
  47. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/tests/test_crc.py +0 -0
  48. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/tests/test_profiles.py +0 -0
  49. {something_x_dev-1.7.0.dev15 → something_x_dev-1.8.0.dev18}/tests/test_protocol.py +0 -0
@@ -0,0 +1 @@
1
+ * @SoaOaoS
@@ -0,0 +1,64 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches-ignore: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write # needed for reviewdog to post inline comments
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint & format
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.11"
24
+
25
+ - name: Install ruff
26
+ run: pip install ruff
27
+
28
+ - name: Setup reviewdog
29
+ uses: reviewdog/action-setup@v1
30
+ with:
31
+ reviewdog_version: latest
32
+
33
+ # Posts inline review comments on the PR diff for every ruff finding
34
+ - name: Lint with reviewdog
35
+ run: ruff check --output-format rdjson nothing_app/ | reviewdog -f=rdjson -reporter=github-pr-review -fail-on-error
36
+ env:
37
+ REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38
+
39
+ # Still fail the job if formatting is wrong
40
+ - name: Check formatting
41
+ run: ruff format --check nothing_app/
42
+
43
+ test:
44
+ name: Unit tests
45
+ runs-on: ubuntu-latest
46
+
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - name: Install system dependencies
51
+ run: |
52
+ sudo apt-get update -q
53
+ sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-4.0 libgirepository1.0-dev
54
+
55
+ # Use a venv that inherits system site-packages so apt-installed gi is visible.
56
+ # PyGObject cannot be pip-built without Cairo/GLib headers; use the distro package.
57
+ - name: Create venv with system site-packages
58
+ run: python3 -m venv --system-site-packages .venv
59
+
60
+ - name: Install test dependencies
61
+ run: .venv/bin/pip install pytest pytest-cov
62
+
63
+ - name: Run tests
64
+ run: .venv/bin/pytest --tb=short --cov=nothing_app --cov-report=term-missing
@@ -0,0 +1,108 @@
1
+ name: Release Dev
2
+
3
+ on:
4
+ push:
5
+ branches: [develop]
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ jobs:
12
+ release-dev:
13
+ runs-on: ubuntu-latest
14
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ # ── Compute next version with python-semantic-release ───────────────
22
+ # PSR reads ALL commits since the last vX.Y.Z tag and applies the
23
+ # highest bump type (feat beats fix, breaking beats all).
24
+ # We strip PSR's own prerelease counter and use github.run_number instead.
25
+ - name: Compute dev version
26
+ id: version
27
+ run: |
28
+ pip install python-semantic-release
29
+ SHORT_SHA=$(git rev-parse --short HEAD)
30
+
31
+ # --print: compute next version with no side effects
32
+ # On develop (prerelease=true in pyproject.toml) PSR outputs e.g. "1.8.0.dev1"
33
+ NEXT_PRE=$(semantic-release version --print 2>/dev/null || echo "")
34
+
35
+ if [ -n "$NEXT_PRE" ]; then
36
+ # Strip PSR's counter (.dev1 / -dev.1) — we use run_number instead
37
+ NEXT_BASE=$(echo "$NEXT_PRE" | sed -E 's/[.-]dev[.-]?[0-9]*//')
38
+ DEV_VERSION="${NEXT_BASE}.dev${{ github.run_number }}"
39
+ else
40
+ # No user-facing commits — still build, bump patch as fallback
41
+ LAST=$(git describe --tags --abbrev=0 --match "v[0-9]*.[0-9]*.[0-9]*" 2>/dev/null | sed 's/^v//' || echo "0.0.0")
42
+ MAJOR=$(echo $LAST | cut -d. -f1)
43
+ MINOR=$(echo $LAST | cut -d. -f2)
44
+ PATCH=$(echo $LAST | cut -d. -f3)
45
+ DEV_VERSION="${MAJOR}.${MINOR}.$((PATCH+1)).dev${{ github.run_number }}"
46
+ fi
47
+
48
+ echo "Dev version: $DEV_VERSION (sha: $SHORT_SHA)"
49
+ echo "dev_version=$DEV_VERSION" >> $GITHUB_OUTPUT
50
+ echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
51
+
52
+ # ── Patch package name for dev channel ─────────────────────────────
53
+ - name: Patch package name
54
+ run: |
55
+ sed -i 's/^name\s*=\s*"something-x"/name = "something-x-dev"/' pyproject.toml
56
+ sed -i 's/^something-x = /something-x-dev = /' pyproject.toml
57
+
58
+ # ── Build ───────────────────────────────────────────────────────────
59
+ - name: Set up Python
60
+ uses: actions/setup-python@v5
61
+ with:
62
+ python-version: "3.11"
63
+
64
+ - name: Build distribution
65
+ env:
66
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.version.outputs.dev_version }}
67
+ run: |
68
+ pip install build
69
+ python -m build
70
+ ls dist/
71
+
72
+ # ── Release notes ───────────────────────────────────────────────────
73
+ - name: Generate pre-release notes
74
+ run: |
75
+ pip install git-cliff
76
+ git cliff --unreleased --strip all 2>/dev/null > /tmp/cliff_body.md
77
+ [ -s /tmp/cliff_body.md ] || echo "No user-facing changes since last release." > /tmp/cliff_body.md
78
+
79
+ cat > /tmp/release_notes.md <<HEADER
80
+ ## something-x-dev ${{ steps.version.outputs.dev_version }}
81
+
82
+ > Pre-release build from \`develop\` — not for production use.
83
+ > **Commit**: \`${{ steps.version.outputs.short_sha }}\`
84
+
85
+ ### Changes since last stable release
86
+
87
+ HEADER
88
+ cat /tmp/cliff_body.md >> /tmp/release_notes.md
89
+ cat >> /tmp/release_notes.md <<'FOOTER'
90
+
91
+ ### Install
92
+ ```bash
93
+ pip install something-x-dev==${{ steps.version.outputs.dev_version }}
94
+ ```
95
+ FOOTER
96
+
97
+ # ── GitHub pre-release ──────────────────────────────────────────────
98
+ - name: Create GitHub pre-release
99
+ uses: softprops/action-gh-release@v2
100
+ with:
101
+ tag_name: "dev-${{ steps.version.outputs.short_sha }}"
102
+ prerelease: true
103
+ body_path: /tmp/release_notes.md
104
+ files: dist/*
105
+
106
+ # ── Publish to PyPI ─────────────────────────────────────────────────
107
+ - name: Publish to PyPI
108
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,122 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: ubuntu-latest
14
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+ token: ${{ secrets.RELEASE_PAT }}
21
+
22
+ # ── Compute stable version with python-semantic-release ─────────────
23
+ # PSR reads ALL commits since last vX.Y.Z tag, highest bump wins.
24
+ # docs/chore/ci/test commits produce no output → release skipped.
25
+ - name: Compute release version
26
+ id: version
27
+ run: |
28
+ pip install python-semantic-release
29
+ NEW=$(semantic-release version --print 2>/dev/null || echo "")
30
+ if [ -z "$NEW" ]; then
31
+ echo "No releasable commits (docs/chore/style/ci/test only). Skipping."
32
+ echo "skip=true" >> $GITHUB_OUTPUT
33
+ exit 0
34
+ fi
35
+ echo "New version: $NEW"
36
+ echo "new=$NEW" >> $GITHUB_OUTPUT
37
+ echo "skip=false" >> $GITHUB_OUTPUT
38
+
39
+ # ── Tag and push ────────────────────────────────────────────────────
40
+ - name: Create and push release tag
41
+ if: steps.version.outputs.skip != 'true'
42
+ run: |
43
+ git config user.name "github-actions[bot]"
44
+ git config user.email "github-actions[bot]@users.noreply.github.com"
45
+ git tag "v${{ steps.version.outputs.new }}"
46
+ git push origin "v${{ steps.version.outputs.new }}"
47
+
48
+ # ── Build ───────────────────────────────────────────────────────────
49
+ - name: Set up Python
50
+ if: steps.version.outputs.skip != 'true'
51
+ uses: actions/setup-python@v5
52
+ with:
53
+ python-version: "3.11"
54
+
55
+ - name: Build distribution
56
+ if: steps.version.outputs.skip != 'true'
57
+ env:
58
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.version.outputs.new }}
59
+ run: |
60
+ pip install build
61
+ python -m build
62
+ ls dist/
63
+
64
+ # ── Release notes ───────────────────────────────────────────────────
65
+ - name: Generate release notes
66
+ if: steps.version.outputs.skip != 'true'
67
+ run: |
68
+ pip install git-cliff
69
+ git cliff --latest --strip all 2>/dev/null > /tmp/cliff_body.md
70
+ [ -s /tmp/cliff_body.md ] || echo "No user-facing changes in this release." > /tmp/cliff_body.md
71
+
72
+ cat > /tmp/release_notes.md <<HEADER
73
+ ## Something X v${{ steps.version.outputs.new }}
74
+
75
+ HEADER
76
+ cat /tmp/cliff_body.md >> /tmp/release_notes.md
77
+ cat >> /tmp/release_notes.md <<'FOOTER'
78
+
79
+ ### Install
80
+ ```bash
81
+ # Arch / Omarchy
82
+ sudo pacman -S python-gobject python-cairo gtk4 libadwaita
83
+ pip install something-x==${{ steps.version.outputs.new }}
84
+ ```
85
+ FOOTER
86
+
87
+ # ── GitHub Release ──────────────────────────────────────────────────
88
+ - name: Create GitHub Release
89
+ if: steps.version.outputs.skip != 'true'
90
+ uses: softprops/action-gh-release@v2
91
+ with:
92
+ tag_name: "v${{ steps.version.outputs.new }}"
93
+ body_path: /tmp/release_notes.md
94
+ files: dist/*
95
+
96
+ # ── Publish to PyPI ─────────────────────────────────────────────────
97
+ - name: Publish to PyPI
98
+ if: steps.version.outputs.skip != 'true'
99
+ uses: pypa/gh-action-pypi-publish@release/v1
100
+
101
+ # ── Update AUR ──────────────────────────────────────────────────────
102
+ - name: Update PKGBUILD
103
+ if: steps.version.outputs.skip != 'true'
104
+ env:
105
+ NEW_VERSION: ${{ steps.version.outputs.new }}
106
+ run: |
107
+ SHA256=$(sha256sum dist/something_x-${NEW_VERSION}.tar.gz | awk '{print $1}')
108
+ sed -i "s/^pkgver=.*/pkgver=${NEW_VERSION}/" PKGBUILD
109
+ sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD
110
+ sed -i "s/sha256sums=('[^']*')/sha256sums=('${SHA256}')/" PKGBUILD
111
+
112
+ - name: Deploy to AUR
113
+ if: steps.version.outputs.skip != 'true'
114
+ uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
115
+ with:
116
+ pkgname: something-x
117
+ pkgbuild: ./PKGBUILD
118
+ commit_username: github-actions[bot]
119
+ commit_email: github-actions[bot]@users.noreply.github.com
120
+ ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
121
+ commit_message: "Update to ${{ steps.version.outputs.new }}"
122
+ allow_empty_commits: false
@@ -0,0 +1,46 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ .eggs/
10
+ MANIFEST
11
+
12
+ # Build / distribution
13
+ dist/
14
+ build/
15
+ *.whl
16
+ *.tar.gz
17
+
18
+ # Virtualenvs
19
+ .venv/
20
+ venv/
21
+ env/
22
+ .env
23
+ ENV/
24
+
25
+ # Tools
26
+ .cache/
27
+ .mypy_cache/
28
+ .ruff_cache/
29
+ .pytest_cache/
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # OS
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # setuptools-scm generated version file (written at install/build time)
41
+ nothing_app/_version.py
42
+
43
+ # APK reverse-engineering artifacts (local research only)
44
+ *apkm*
45
+ apkm_decompiled/
46
+ apkm_extracted/
@@ -0,0 +1,37 @@
1
+ # Something X — Device & Platform Compatibility
2
+
3
+ ---
4
+
5
+ ## Supported devices
6
+
7
+ The RFCOMM `0x55` protocol is shared across the entire Nothing Ear lineup. Confirmed working on **Nothing Ear (2)**; the same activation sequence, ANC wire values, and EQ command IDs are present in the APK for all models listed below.
8
+
9
+ | Device | Protocol confirmed | Community reports |
10
+ |---|---|---|
11
+ | Nothing Ear (2) | ✅ reverse-engineered from APK | ✅ |
12
+ | Nothing Ear (1) | ✅ same APK protocol class | needs field testing |
13
+ | Nothing Ear (a) | ✅ same APK protocol class | needs field testing |
14
+ | Nothing Ear (stick) | ✅ (no ANC cmd) | needs field testing |
15
+ | CMF Buds / Buds Pro | ⚠️ likely same, different cmd IDs possible | needs field testing |
16
+ | Nothing Ear (open) | ❓ ANC not applicable | not yet tested |
17
+ | CMF Watch | ❓ different protocol expected | not started |
18
+
19
+ **If your device doesn't work:** open an issue with the raw RFCOMM dump (run with `SOMETHING_X_DEBUG=1 ./somethingx`).
20
+
21
+ ---
22
+
23
+ ## Linux distros
24
+
25
+ The app runs on any Linux system with BlueZ. System package names vary:
26
+
27
+ | Distro | Install command |
28
+ |---|---|
29
+ | Arch / Omarchy | `sudo pacman -S python-gobject python-dbus python-cairo gtk4 libadwaita` |
30
+ | Ubuntu 24.04+ | `sudo apt install python3-gi python3-dbus python3-cairo gir1.2-gtk-4.0 gir1.2-adw-1` |
31
+ | Fedora 39+ | `sudo dnf install python3-gobject python3-dbus python3-cairo gtk4 libadwaita` |
32
+ | openSUSE | `sudo zypper install python3-gobject python3-dbus python3-cairo gtk4 libadwaita` |
33
+ | NixOS | see `flake.nix` |
34
+
35
+ Requirements: `bluetoothd` running, BlueZ ≥ 5.6, GTK4 ≥ 4.6, libadwaita ≥ 1.2.
36
+
37
+ Volume control requires `pactl` — available on any PulseAudio or PipeWire system.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: something-x-dev
3
- Version: 1.7.0.dev15
3
+ Version: 1.8.0.dev18
4
4
  Summary: Something X device manager for Omarchy / Linux
5
5
  Author: Raphael
6
6
  License: MIT
@@ -0,0 +1,24 @@
1
+ # Maintainer: Raphael <raphael.girard.iut@gmail.com>
2
+ pkgname=something-x
3
+ pkgver=1.0.0
4
+ pkgrel=1
5
+ pkgdesc="GTK4 device manager for Nothing earbuds on Linux"
6
+ arch=('any')
7
+ url="https://github.com/SoaOaoS/nothingonmarchy"
8
+ license=('MIT')
9
+ depends=('python' 'python-gobject' 'python-dbus' 'gtk4' 'libadwaita')
10
+ makedepends=('python-build' 'python-installer' 'python-wheel' 'python-setuptools')
11
+ optdepends=('libpulse: volume control via pactl')
12
+ source=("https://files.pythonhosted.org/packages/source/s/something-x/something_x-${pkgver}.tar.gz")
13
+ sha256sums=('SKIP')
14
+
15
+ build() {
16
+ cd "something_x-${pkgver}"
17
+ python -m build --wheel --no-isolation
18
+ }
19
+
20
+ package() {
21
+ cd "something_x-${pkgver}"
22
+ python -m installer --destdir="$pkgdir" dist/*.whl
23
+ install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
24
+ }
@@ -0,0 +1,69 @@
1
+ # Something X — Roadmap
2
+
3
+ ---
4
+
5
+ ## Known Issues
6
+
7
+ - Window control buttons (close/minimize/maximize) in the headerbar had a double-hover effect on non-tiling desktops due to app CSS overriding the system theme — fixed on `feat/fix-headerbar-window-controls`.
8
+
9
+ ---
10
+
11
+ ## v1.x — Near term
12
+
13
+ ### Docs
14
+ - [x] **GitHub Pages** — project landing page + full documentation site (`docs/`)
15
+
16
+ ### Distribution
17
+ - [x] AUR package — AUR package for Arch users to replace pip
18
+ - [x] **NixOS flake** — `flake.nix` for Nix users
19
+ - [x] **`.desktop` file** — ships and auto-installs so the app appears in Walker/Rofi/GNOME launcher
20
+
21
+ ### Features
22
+ - [x] **Low battery desktop notification** — `notify-send` when any bud drops below 20 %
23
+ - [x] **Background mode** — closing the window hides it; relaunch shows it again (Gio single-instance)
24
+ - [x] **Per-device profiles** — ANC + EQ saved per device address to `~/.config/something-x/profiles.json`, restored automatically on reconnect
25
+ - [x] **CLI quick-toggle** — `something-x --anc off|on|transparency`, `something-x --eq bass`, `something-x --battery`
26
+ - [x] **System tray icon** — show battery on hover via StatusNotifierItem (Waybar/SNI)
27
+ - [x] **Wearing detection display** — show L/R in-ear status on the earbud visual
28
+ - [x] **Auto-connect RFCOMM** — connect as soon as BlueZ reports the device connected, no tap needed
29
+
30
+ ### Fixes
31
+ - [x] **Headerbar button hover** — window controls now respect the system theme on all desktops
32
+
33
+ ### Protocol
34
+ - [x] **Debug mode** — `SOMETHING_X_DEBUG=1` env var dumps raw RFCOMM frames for contributors
35
+ - [x] **Graceful ANC mode detection** — infer supported modes from RFCOMM level response; hide unsupported buttons
36
+
37
+ ---
38
+
39
+ ## v2.x — Medium term
40
+
41
+ ### Features
42
+ - [ ] **Multiple devices simultaneously** — open two device pages, manage both at once
43
+ - [ ] **Quick-settings panel** — small floating overlay triggered by keybind
44
+ - [ ] **Touch controls remapping** — if the protocol exposes it (found in APK, not yet implemented)
45
+ - [ ] **Equalizer graph** — visual EQ curve instead of preset pills; custom presets with sliders
46
+ - [ ] **Firmware update check** — compare device firmware against latest known version, show badge
47
+
48
+ ### Tech
49
+ - [ ] **Async BlueZ** — replace `dbus-python` with `dbus-fast` for non-blocking I/O
50
+ - [ ] **Unit tests** — protocol encode/decode, CRC, ANC mode mapping
51
+
52
+ ---
53
+
54
+ ## v3.x — Long term / ideas
55
+
56
+ - [ ] **CMF Watch support** — different protocol; would need fresh APK reverse-engineering
57
+ - [ ] **Nothing Phone integration** — glyph control, notification mirroring
58
+ - [ ] **GNOME Shell extension** — battery indicator in the top bar
59
+ - [ ] **KDE Plasma widget** — same idea for KDE users
60
+ - [ ] **macOS port** — replace BlueZ D-Bus with CoreBluetooth via `pyobjc` (long shot)
61
+
62
+ ---
63
+
64
+ ## Won't do
65
+
66
+ - **Windows** — no BlueZ, RFCOMM access requires a different stack entirely
67
+ - **Wayland screenshot / screen capture control** — out of scope
68
+ - **Streaming / media playback control** — MPRIS already handles this system-wide
69
+
@@ -0,0 +1,41 @@
1
+ [changelog]
2
+ header = ""
3
+ body = """
4
+ {% for group, commits in commits | group_by(attribute="group") %}
5
+ ### {{ group }}
6
+ {% for commit in commits | sort(attribute="message") %}
7
+ - {% if commit.scope %}**{{ commit.scope }}**: {% endif %}\
8
+ {{ commit.message | upper_first }}\
9
+ {% endfor %}
10
+ {% endfor %}
11
+ """
12
+ footer = ""
13
+ trim = true
14
+
15
+ [git]
16
+ conventional_commits = true
17
+ filter_unconventional = true
18
+ split_commits = false
19
+ commit_parsers = [
20
+ { message = "^feat", group = "Features" },
21
+ { message = "^fix", group = "Bug Fixes" },
22
+ { message = "^perf", group = "Performance" },
23
+ { message = "^refactor", group = "Refactoring" },
24
+ { message = "^docs", group = "Documentation" },
25
+ { message = "^style", group = "Styling" },
26
+ { message = "^test", group = "Testing" },
27
+ # skip anything that isn't useful in a user-facing changelog
28
+ { message = "^chore: release", skip = true },
29
+ { message = "^chore", skip = true },
30
+ { message = "^ci", skip = true },
31
+ { message = "^build", skip = true },
32
+ # skip all merge commits regardless of how they are worded
33
+ { message = "^[Mm]erge[ /]", skip = true },
34
+ { message = "^Merged", skip = true },
35
+ ]
36
+ filter_commits = false
37
+ # semver release tags only; dev-* tags are ignored for "previous release" calculation
38
+ tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"
39
+ ignore_tags = "dev-.*"
40
+ topo_order = false
41
+ sort_commits = "oldest"