Introduces a plugin to display information about Galaxy Buds devices using (#860)

the earbuds tool from https://github.com/JojiiOfficial/LiveBudsCli

* Displays per bud battery level
* Displays case battery level
* Displays placement status
* Displays and sets both Ambient sound (AMB) and Active noise control (ANC)
* Has commands to connect/disconnect
* Dynamic coloring based on battery level
* Equalizer setting
* Touchpad lock
This commit is contained in:
Giancarlo Razzolini 2024-06-13 14:10:08 -03:00 committed by GitHub
parent c899ec1c5a
commit f3c539ad78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 827 additions and 0 deletions

237
i3pystatus/buds.py Normal file
View File

@ -0,0 +1,237 @@
from enum import Enum, IntEnum
from json import JSONDecodeError, loads
from i3pystatus import IntervalModule
from i3pystatus.core.command import run_through_shell
from i3pystatus.core.color import ColorRangeModule
class BudsEqualizer(Enum):
off = 0
bass = 1
soft = 2
dynamic = 3
clear = 4
treble = 5
class BudsPlacementStatus(IntEnum):
wearing = 1
idle = 2
case = 3
class Buds(IntervalModule, ColorRangeModule):
earbuds_binary = "earbuds"
"""
Displays information about Galaxy Buds devices
Requires the earbuds tool from https://github.com/JojiiOfficial/LiveBudsCli
.. rubric :: Available formatters
* {amb} Displays the current ambient sound control status.
* {anc} Displays the current active noise control status.
* {battery} Displays combined battery level for left and right.
If both are at the same level, it simply returns the battery level.
If they have different levels and the drift threshold is enabled, provided
they do not exceed the threshold, display the smaller level.
If they have different battery levels, it returns both levels, if the threshold
is exceeded.
* `{left_battery}` Displays the left bud battery level.
* `{right_battery}` Displays the right bud battery level.
* `{battery_case} Displays the case battery level, if one of the buds is on the case.
* `{device_model}` The model of the device.
* `{equalizer} Displays current equalizer setting, only if the equalizer is on.
* `{placement_left}` A placement indicator for the left bud, if it's on the (C)ase, (I)dle or being (W)ear.
* `{placement_right}` A placement indicator for the right bud, if it's on the (C)ase, (I)dle or being (W)ear.
* `{touchpad}` Displays if the touchpad is locked, and only if it is locked. A T(ouchpad)L(ocked) string indicates
the touchpad is locked.
"""
settings = (
("format", "Format string used for output"),
("interval", "Interval to run the module"),
("hide_no_device", "Hide the output if no device is connected"),
("battery_drift_threshold", "Drift threshold."),
("use_battery_drift_threshold", "Whether to display combined or separate levels, based on drift"),
("connected_color", "Output color for when the device is connected"),
("disconnected_color", "Output color for when the device is disconnected"),
("dynamic_color", "Output color based on battery level. Overrides connected_color"),
("start_color", "Hex or English name for start of color range, eg '#00FF00' or 'green'"),
("end_color", "Hex or English name for end of color range, eg '#FF0000' or 'red'")
)
format = (
"{device_model} "
"L{placement_left}"
"{battery}"
"R{placement_right}"
"{battery_case}"
"{amb}"
"{anc}"
"{equalizer}"
"{touchpad}"
)
hide_no_device = False
battery_limit = 100
battery_drift_threshold = 3
use_battery_drift_threshold = True
connected_color = "#00FF00"
disconnected_color = "#FFFFFF"
dynamic_color = True
colors = []
on_leftclick = 'toggle_anc'
on_rightclick = 'toggle_amb'
on_doubleleftclick = 'connect'
on_doublerightclick = 'disconnect'
on_middleclick = ['equalizer_set', BudsEqualizer.off]
on_downscroll = ['equalizer_set', -1]
on_upscroll = ['equalizer_set', +1]
on_doublemiddleclick = 'restart_daemon'
on_doubleupscroll = ['touchpad_set', 'true']
on_doubledownscroll = ['touchpad_set', 'false']
def init(self):
if not self.dynamic_color:
self.end_color = self.start_color = self.connected_color
# battery discharges from battery_limit to 0
self.colors = self.get_hex_color_range(self.end_color, self.start_color, self.battery_limit)
def run(self):
try:
status = loads(run_through_shell(f"{self.earbuds_binary} status -o json -q").out)
except JSONDecodeError:
self.output = None
else:
payload = status.get("payload")
if payload:
amb = payload.get("ambient_sound_enabled")
anc = payload.get("noise_reduction")
left_battery = payload.get("batt_left")
right_battery = payload.get("batt_right")
equalizer_type = BudsEqualizer(payload.get("equalizer_type", 0))
placement_left = payload.get("placement_left")
placement_right = payload.get("placement_right")
tab_lock_status = payload.get("tab_lock_status")
# determine touchpad lock status
touchpad = ""
if tab_lock_status:
touch_an_hold_on = tab_lock_status.get("touch_an_hold_on")
tap_on = tab_lock_status.get("tap_on")
if not touch_an_hold_on and not tap_on:
touchpad = " TL"
# determine battery level and color to display
battery_display = f"{left_battery} {right_battery}"
color = self.connected_color
combined_level = min(left_battery, right_battery)
# if one bud has battery depleted, invert the logic.
if left_battery == 0 or right_battery == 0:
combined_level = max(left_battery, right_battery)
if self.use_battery_drift_threshold:
drift = abs(left_battery - right_battery)
# only use drift if buds aren't on case, otherwise show both.
if drift <= self.battery_drift_threshold and not (
placement_left == BudsPlacementStatus.case or
placement_right == BudsPlacementStatus.case
):
battery_display = f"{combined_level}"
if self.dynamic_color:
color = self.get_gradient(
combined_level,
self.colors,
self.battery_limit
)
fdict = {
"amb": " AMB" if amb else "",
"anc": " ANC" if anc else "",
"battery": battery_display,
"left_battery": left_battery,
"right_battery": right_battery,
"battery_case":
f' {payload.get("batt_case")}C' if placement_left == BudsPlacementStatus.case
or placement_right == BudsPlacementStatus.case else "",
"device_model": payload.get("model"),
"equalizer": "" if equalizer_type == BudsEqualizer.off else f" {equalizer_type.name.capitalize()}",
"placement_left": self.translate_placement(placement_left),
"placement_right": self.translate_placement(placement_right),
"touchpad": touchpad
}
self.output = {
"full_text": self.format.format(**fdict),
"color": color
}
return payload
else:
if not self.hide_no_device:
self.output = {
"full_text": "Disconnected",
"color": self.disconnected_color
}
else:
self.output = None
return
def connect(self):
run_through_shell(f"{self.earbuds_binary} connect")
def disconnect(self):
run_through_shell(f"{self.earbuds_binary} disconnect")
def equalizer_set(self, adjustment):
payload = self.run()
if payload:
current_eq = int(payload.get("equalizer_type", 0)) # Default to 0 if not found
if isinstance(adjustment, BudsEqualizer):
new_eq_value = adjustment.value
else: # Adjustment is -1 or +1
# Calculate new equalizer setting, ensuring it wraps correctly within bounds
new_eq_value = (current_eq + adjustment) % len(BudsEqualizer)
# Find the enum member corresponding to the new equalizer value
new_eq_setting = BudsEqualizer(new_eq_value)
# Execute the command with the new equalizer setting
run_through_shell(f"{self.earbuds_binary} set equalizer {new_eq_setting.name}")
def restart_daemon(self):
run_through_shell(f"{self.earbuds_binary} -kd")
def toggle_amb(self):
payload = self.run()
if payload:
amb = payload.get("ambient_sound_enabled")
if amb:
run_through_shell(f"{self.earbuds_binary} set ambientsound 0")
else:
run_through_shell(f"{self.earbuds_binary} set ambientsound 1")
def toggle_anc(self):
payload = self.run()
if payload:
anc = payload.get("noise_reduction")
if anc:
run_through_shell(f"{self.earbuds_binary} set anc false")
else:
run_through_shell(f"{self.earbuds_binary} set anc true")
def touchpad_set(self, setting):
run_through_shell(f"{self.earbuds_binary} set touchpad {setting}")
@staticmethod
def translate_placement(placement):
mapping = {
BudsPlacementStatus.wearing.value: "W",
BudsPlacementStatus.idle.value: "I",
BudsPlacementStatus.case.value: "C",
}
return mapping.get(placement, "?")

48
tests/test_buds.json Normal file
View File

@ -0,0 +1,48 @@
{
"connected_payload": {
"status": "success",
"device": "00:00:00:00:00:00",
"status_message": null,
"payload": {
"address": "00:00:00:00:00:00",
"ready": true,
"batt_left": 53,
"batt_right": 48,
"batt_case": 88,
"placement_left": 1,
"placement_right": 1,
"equalizer_type": 0,
"touchpads_blocked": false,
"noise_reduction": false,
"did_battery_notify": false,
"touchpad_option_left": 2,
"touchpad_option_right": 2,
"paused_music_earlier": false,
"debug": {
"voltage_left": 0.0,
"voltage_right": 0.0,
"temperature_left": 36.0,
"temperature_right": 37.0,
"current_left": 0.0,
"current_right": 0.0
},
"model": "Buds2",
"ambient_sound_enabled": false,
"ambient_sound_volume": 0,
"extra_high_ambient_volume": false,
"tab_lock_status": {
"touch_an_hold_on": true,
"triple_tap_on": true,
"double_tap_on": true,
"tap_on": true,
"touch_controls_on": true
}
}
},
"disconnected_payload": {
"status": "error",
"device": "",
"status_message": "No connected device found",
"payload": null
}
}

542
tests/test_buds.py Normal file
View File

@ -0,0 +1,542 @@
import json
import unittest
from copy import deepcopy
from unittest.mock import patch
from i3pystatus.core.color import ColorRangeModule
from i3pystatus.buds import Buds, BudsEqualizer, BudsPlacementStatus
class TestBuds(unittest.TestCase):
def setUp(self):
self.buds = Buds()
with open('test_buds.json', 'rb') as file:
self.payload = json.load(file)
@patch('i3pystatus.buds.run_through_shell')
def test_run_device_connected(self, mock_run):
# Setup: Use json.dumps as we expect JSON
payload = self.payload.get('connected_payload')
mock_run.return_value.out = json.dumps(payload)
# Action: Call run() and save return for comparison
buds_run_return = self.buds.run()
# Verify: Assert called with right params
mock_run.assert_called_with(f"{self.buds.earbuds_binary} status -o json -q")
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Assert correct output
self.assertEqual(expected_output, self.buds.output)
# Verify: run() return is equal to payload
self.assertDictEqual(payload.get('payload'), buds_run_return)
@patch('i3pystatus.buds.run_through_shell')
def test_run_device_disconnected(self, mock_run):
# Setup: Use json.dumps as we expect JSON
mock_run.return_value.out = json.dumps(self.payload.get('disconnected_payload'))
# Action: Call run() and save return for comparison
buds_run_return = self.buds.run()
# Verify: Assert called with right params
mock_run.assert_called_with(f"{self.buds.earbuds_binary} status -o json -q")
expected_output = {
"full_text": "Disconnected",
"color": self.buds.disconnected_color
}
# Verify: Assert correct output
self.assertEqual(expected_output, self.buds.output)
# Verify: run() return should be none
self.assertIsNone(buds_run_return)
@patch('i3pystatus.buds.run_through_shell')
def test_toggle_amb(self, mock_run):
# Setup: AMB is initially disabled
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['ambient_sound_enabled'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Toggle AMB
self.buds.toggle_amb()
# Verify: The correct command is sent to enable AMB
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set ambientsound 1")
# Setup: Change the payload again to update the AMB status
modified_payload['payload']['ambient_sound_enabled'] = True
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays AMB is enabled
expected_output = {
"full_text": "Buds2 LW53 48RW AMB",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
# Action: Toggle AMB again
self.buds.toggle_amb()
# Verify: The correct command is sent to disable AMB this time
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set ambientsound 0")
# Setup: Change the payload one last time to update the AMB status
modified_payload['payload']['ambient_sound_enabled'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays AMB is disabled
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_toggle_anc(self, mock_run):
# Setup: ANC is initially disabled
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['noise_reduction'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Toggle ANC
self.buds.toggle_anc()
# Verify: The correct command is sent to enable ANC
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set anc true")
# Setup: Change the payload again to update the ANC status
modified_payload['payload']['noise_reduction'] = True
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays ANC is enabled
expected_output = {
"full_text": "Buds2 LW53 48RW ANC",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
# Action: Toggle ANC again
self.buds.toggle_anc()
# Verify: The correct command is sent to disable ANC this time
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set anc false")
# Setup: Change the payload one last time to update the ANC status
modified_payload['payload']['noise_reduction'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays ANC is disabled
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery(self, mock_run):
# Setup: Equal left and right battery value
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = modified_payload['payload']['batt_right']
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_output = {
"full_text": "Buds2 LW48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
# Setup: Different left and right battery value
mock_run.return_value.out = json.dumps(self.payload.get('connected_payload'))
# Action: Call run() again to update the output
self.buds.run()
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery_drift(self, mock_run):
# Setup: Different battery level, should show smaller
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = modified_payload['payload']['batt_right']
modified_payload['payload']['batt_left'] -= self.buds.battery_drift_threshold
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_level = min(modified_payload['payload']['batt_left'], modified_payload['payload']['batt_right'])
expected_output = {
# Verify: The level should be the smallest one
"full_text":
f"Buds2 LW{expected_level}RW",
"color": self.buds.get_gradient(
expected_level,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
# Setup: One battery is at level 0, should show the other
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = 0
modified_payload['payload']['batt_right'] = 0 + self.buds.battery_drift_threshold
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_level = max(modified_payload['payload']['batt_left'], modified_payload['payload']['batt_right'])
expected_output = {
# Verify: The level should be the biggest one
"full_text":
f"Buds2 LW{expected_level}RW",
"color": self.buds.get_gradient(
expected_level,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery_drift_case(self, mock_run):
# Setup: Change status of one buds to be on the case
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['placement_left'] = BudsPlacementStatus.case
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_output = {
"full_text": f"Buds2 LC53 48RW 88C",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_connect(self, mock_run):
# Action: Call connect
self.buds.connect()
# Verify: The correct command is sent to connect
mock_run.assert_called_with(f"{self.buds.earbuds_binary} connect")
@patch('i3pystatus.buds.run_through_shell')
def test_disconnect(self, mock_run):
# Action: Call disconnect
self.buds.disconnect()
# Verify: The correct command is sent to disconnect
mock_run.assert_called_with(f"{self.buds.earbuds_binary} disconnect")
@patch('i3pystatus.buds.run_through_shell')
def test_restart_daemin(self, mock_run):
# Action: Call restart_daemon
self.buds.restart_daemon()
# Verify: The correct command is sent to restart the daemon
mock_run.assert_called_with(f"{self.buds.earbuds_binary} -kd")
def run_placement_helper(self, mock_run, placement_left, placement_right, case_battery, expected_display):
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['placement_left'] = placement_left
modified_payload['payload']['placement_right'] = placement_right
if case_battery is not None:
modified_payload['payload']['batt_case'] = case_battery
mock_run.return_value.out = json.dumps(modified_payload)
self.buds.run()
expected_output = {
"full_text": expected_display,
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_wearing(self, mock_run):
self.run_placement_helper(
mock_run,
BudsPlacementStatus.wearing.value,
BudsPlacementStatus.wearing.value,
None,
"Buds2 LW53 48RW"
)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_idle(self, mock_run):
self.run_placement_helper(
mock_run,
BudsPlacementStatus.idle.value,
BudsPlacementStatus.idle.value,
None,
"Buds2 LI53 48RI"
)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_case_with_battery(self, mock_run):
# Verify: Case battery is returned if a bud is on the case
self.run_placement_helper(
mock_run,
BudsPlacementStatus.case.value,
BudsPlacementStatus.case.value,
88,
"Buds2 LC53 48RC 88C"
)
@patch('i3pystatus.buds.run_through_shell')
def test_battery_level_dynamic_color(self, mock_run):
# Setup: Build the colors array independently of our class
colors = ColorRangeModule.get_hex_color_range(
self.buds.end_color,
self.buds.start_color,
self.buds.battery_limit
)
modified_payload = deepcopy(self.payload.get('connected_payload'))
for battery_level in range(0, self.buds.battery_limit + 1):
# Setup: Make both levels equal
modified_payload['payload']['batt_left'] = battery_level
modified_payload['payload']['batt_right'] = battery_level
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_output = {
"full_text": f"Buds2 LW{battery_level}RW",
"color": self.buds.get_gradient(
battery_level,
colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_set_equalizer_direct(self, mock_run):
for eq_setting in BudsEqualizer:
with self.subTest(msg=f"Failed testing equalizer {eq_setting.name}", eq_setting=eq_setting):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set function with each equalizer setting
self.buds.equalizer_set(eq_setting)
expected_command = f"{self.buds.earbuds_binary} set equalizer {eq_setting.name}"
# Verify: Correct equalizer command is used
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = eq_setting.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {eq_setting.name.capitalize()}" if eq_setting.name != "off" else ""
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_increment_equalizer(self, mock_run):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set to increment by one the equalizer setting
self.buds.equalizer_set(+1)
# Verify: Correct equalizer command is used
expected_command = f"{self.buds.earbuds_binary} set equalizer {BudsEqualizer.bass.name}"
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = BudsEqualizer.bass.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {BudsEqualizer.bass.name.capitalize()}"
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_decrement_equalizer_from_off(self, mock_run):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set to decrement by one the equalizer setting
self.buds.equalizer_set(-1)
# Verify: Correct equalizer command is used
expected_command = f"{self.buds.earbuds_binary} set equalizer {BudsEqualizer.treble.name}"
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = BudsEqualizer.treble.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {BudsEqualizer.treble.name.capitalize()}"
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
def run_touchpad_set(self, mock_run, setting_value):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set with the appropriate setting
self.buds.touchpad_set(f'{setting_value}')
# Verify: Correct command to disable the touchpad is called
expected_command = f"{self.buds.earbuds_binary} set touchpad {setting_value}"
mock_run.assert_called_with(expected_command)
# Setup: Modify the payload if we are disabling the touchpad
if setting_value == 'false':
modified_payload['payload']['tab_lock_status']['touch_an_hold_on'] = False
modified_payload['payload']['tab_lock_status']['tap_on'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
# Setup:
expected_output = {
"full_text": f"Buds2 LW53 48RW{' TL' if setting_value == 'false' else ''}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_touchpad_disable(self, mock_run):
self.run_touchpad_set(mock_run, "false")
@patch('i3pystatus.buds.run_through_shell')
def test_touchpad_enable(self, mock_run):
self.run_touchpad_set(mock_run, "true")