when skipping backwards or changing the playback position of a song qdbus6 or bluez is not refreshing if I rewind or scan in any direction I do not get a proper reading in the "Position" value instead it continues counting in milliseconds until it the song ends on the source device Browsable: true Device: Name: Music Playlist: Position: 45044 Repeat: off Searchable: true Shuffle: alltracks Status: playing Subtype: None Track: Album: Devil (feat. Bubi) - Single Artist: Ironmouse, shirobeats & HalaCG Duration: 189677 Genre: Pop Item: NumberOfTracks: 550 Title: Devil (feat. Bubi) TrackNumber: 100 Type: Audio I have tried manually issuing various control commands to see if there is something that can do it but I don't have the "Update" command so I am having trouble figuring out what is going on. this is what it does when I adjust the position on the playback device and it goes past the playback range instead of of resetting when adjusted. Browsable: true Device: Name: Music Playlist: Position: 89961 Repeat: off Searchable: true Shuffle: alltracks Status: playing Subtype: None Track: Album: New Classics (2010-2016) Artist: dj-Nate Duration: 88163 Genre: Dance Item: NumberOfTracks: 550 Title: Dirty Thumpin' Saws TrackNumber: 107 Type: Audio the code for the entire app I am having is here import curses import subprocess import argparse import os import time def run_qdbus6(args): try: return subprocess.check_output(["qdbus6", "--system"] + args, text=True) except Exception: return "" def parse_status(output: str) -> dict: info = {} for line in output.splitlines(): if ':' in line: k, v = line.split(':', 1) info[k.strip()] = v.strip() return info def ms_to_mins_secs(ms): seconds = int(ms) // 1000 mins = seconds // 60 secs = seconds % 60 return f"{mins}:{secs:02}" def check_device_connected(mac_addr): """Returns True if the device is connected, False otherwise.""" dev_path = f"/org/bluez/hci0/dev_{mac_addr.replace(':', '_')}" try: output = subprocess.check_output( ["qdbus6", "--system", "org.bluez", dev_path, "org.freedesktop.DBus.Properties.Get", "org.bluez.Device1", "Connected"], text=True, stderr=subprocess.DEVNULL ) return "true" in output.lower() except Exception: return False def figlet_centered(stdscr, text, color_pair=0): max_y, max_x = stdscr.getmaxyx() try: figlet_out = subprocess.check_output( ["figlet", "-f", "slant", "-l", "-w", str(max_x), text], text=True, stderr=subprocess.DEVNULL ) except Exception: figlet_out = text lines = figlet_out.splitlines() y0 = max((max_y - len(lines)) // 2, 0) for i, line in enumerate(lines): x0 = max((max_x - len(line)) // 2, 0) try: stdscr.addstr(y0 + i, x0, line, curses.color_pair(color_pair)) except curses.error: pass def draw_ui(stdscr, info, highlight_idx=None, button_boxes=None, color_pair=0): stdscr.clear() max_y, max_x = stdscr.getmaxyx() title = info.get("Title", "Unknown Title") artist = info.get("Artist", "Unknown Artist") album = info.get("Track: Album", info.get("Album", "Unknown Album")) status = info.get("Status", "paused").capitalize() position = int(info.get("Position", "0")) duration = int(info.get("Duration", "1")) elapsed = ms_to_mins_secs(position) remaining = ms_to_mins_secs(max(duration - position, 0)) prog_pos = position // 1000 prog_dur = max(duration // 1000, 1) stdscr.addstr(0, 0, "=" * max_x, curses.color_pair(color_pair)) stdscr.addstr(1, 0, title.center(max_x), curses.color_pair(color_pair)) stdscr.addstr(2, 0, artist.center(max_x), curses.color_pair(color_pair)) stdscr.addstr(3, 0, album.center(max_x), curses.color_pair(color_pair)) stdscr.addstr(4, 0, "=" * max_x, curses.color_pair(color_pair)) stdscr.addstr(5, 0, f"Status: {status}", curses.color_pair(color_pair)) bar_length = max(max_x - 23, 10) filled_length = int(bar_length * prog_pos // prog_dur) if prog_dur else 0 bar = "[" + "=" * filled_length + ">" + " " * (bar_length - filled_length - 1) + "]" stdscr.addstr(7, 0, f"{elapsed.rjust(5)} {bar} -{remaining}", curses.color_pair(color_pair)) labels = ["⏮", "⏯" if status.lower() != "playing" else "⏸", "⏭"] button_boxes.clear() btn_y = 9 btn_h = 5 btn_w = 13 gap = 6 total_btns = 3 * btn_w + 2 * gap start_x = (max_x - total_btns) // 2 for i, label in enumerate(labels): x = start_x + i * (btn_w + gap) y = btn_y box = (y, x, y + btn_h, x + btn_w) button_boxes.append(box) for bx in range(x, x+btn_w): stdscr.addch(y, bx, curses.ACS_HLINE, curses.color_pair(color_pair)) stdscr.addch(y+btn_h-1, bx, curses.ACS_HLINE, curses.color_pair(color_pair)) for by in range(y, y+btn_h): stdscr.addch(by, x, curses.ACS_VLINE, curses.color_pair(color_pair)) stdscr.addch(by, x+btn_w-1, curses.ACS_VLINE, curses.color_pair(color_pair)) stdscr.addch(y, x, curses.ACS_ULCORNER, curses.color_pair(color_pair)) stdscr.addch(y, x+btn_w-1, curses.ACS_URCORNER, curses.color_pair(color_pair)) stdscr.addch(y+btn_h-1, x, curses.ACS_LLCORNER, curses.color_pair(color_pair)) stdscr.addch(y+btn_h-1, x+btn_w-1, curses.ACS_LRCORNER, curses.color_pair(color_pair)) for by in range(y+1, y+btn_h-1): stdscr.addstr(by, x+1, " " * (btn_w-2), curses.color_pair(color_pair) | (curses.A_REVERSE if highlight_idx == i else curses.A_NORMAL)) stdscr.addstr(y + btn_h//2, x + (btn_w-len(label))//2, label, curses.A_BOLD | curses.color_pair(color_pair) | (curses.A_REVERSE if highlight_idx == i else 0)) stdscr.addstr(btn_y+btn_h+1, 0, "Controls: [p] Play/Pause [n] Next Previous [q] Quit".center(max_x), curses.color_pair(color_pair)) stdscr.addstr(btn_y+btn_h+2, 0, "=" * max_x, curses.color_pair(color_pair)) stdscr.refresh() def which_button(mx, my, button_boxes): for idx, (y1, x1, y2, x2) in enumerate(button_boxes): if y1 <= my < y2 and x1 <= mx < x2: return idx return None def mac_to_bluez(mac): return mac.replace(":", "_").upper() def bluez_to_mac(bluez_mac): # Accepts MAC with underscores, returns colon-separated MAC return bluez_mac.replace("_", ":").upper() def device_not_found_screen(stdscr, bluez_mac, color_pair, autoretry_interval=1.0): shown_mac = bluez_to_mac(bluez_mac) retry_counter = 0 while True: stdscr.clear() figlet_centered(stdscr, shown_mac, color_pair=color_pair) msg = f"Device not connected. Waiting for device... (auto-retry every {autoretry_interval:.1f}s, press [q] to quit)".center(stdscr.getmaxyx()[1]) stdscr.addstr(stdscr.getmaxyx()[0]-2, 0, msg, curses.color_pair(color_pair)) stdscr.refresh() wait_start = time.time() # Wait for either timeout or 'q' while True: key = stdscr.getch() if key in [ord('q'), ord('Q')]: return False elif key == ord('r'): # legacy, not needed, but kept for any custom testing break # After interval, break from inner loop to retry connection if time.time() - wait_start > autoretry_interval: break time.sleep(0.05) # After waiting, check connection mac_addr = shown_mac if check_device_connected(shown_mac): return True def main(stdscr, mac_addr, color_pair, autorefresh_interval=1.0): curses.curs_set(0) curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION) stdscr.nodelay(True) bluez_mac = mac_to_bluez(mac_addr) PLAYER_PATH = f"/org/bluez/hci0/dev_{bluez_mac}/player0" button_boxes = [] def refresh_info(): status_out = run_qdbus6([ "org.bluez", PLAYER_PATH, "org.freedesktop.DBus.Properties.GetAll", "org.bluez.MediaPlayer1" ]) return parse_status(status_out) # Device connect/retry loop (initial) while not check_device_connected(mac_addr): found = device_not_found_screen(stdscr, bluez_mac, color_pair, autoretry_interval=autorefresh_interval) if not found: return info = refresh_info() draw_ui(stdscr, info, None, button_boxes, color_pair=color_pair) last_refresh = time.time() while True: try: key = stdscr.getch() now = time.time() # At each loop, check if device is still connected if not check_device_connected(mac_addr): found = device_not_found_screen(stdscr, bluez_mac, color_pair, autoretry_interval=autorefresh_interval) if not found: return info = refresh_info() draw_ui(stdscr, info, None, button_boxes, color_pair=color_pair) last_refresh = time.time() continue # Autorefresh every autorefresh_interval seconds if now - last_refresh > autorefresh_interval: info = refresh_info() draw_ui(stdscr, info, None, button_boxes, color_pair=color_pair) last_refresh = now if key == ord('q'): break elif key in [ord('p'), ord(' ')]: status = info.get("Status", "paused").lower() if status == "playing": run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Pause" ]) else: run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Play" ]) info = refresh_info() draw_ui(stdscr, info, 1, button_boxes, color_pair=color_pair) last_refresh = time.time() elif key in [ord('n'), curses.KEY_RIGHT]: run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Next" ]) info = refresh_info() draw_ui(stdscr, info, 2, button_boxes, color_pair=color_pair) last_refresh = time.time() elif key in [ord('b'), curses.KEY_LEFT]: run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Previous" ]) info = refresh_info() draw_ui(stdscr, info, 0, button_boxes, color_pair=color_pair) last_refresh = time.time() elif key == curses.KEY_MOUSE: _, mx, my, _, bstate = curses.getmouse() btn_idx = which_button(mx, my, button_boxes) if btn_idx == 0: # prev run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Previous" ]) elif btn_idx == 1: # play/pause status = info.get("Status", "paused").lower() if status == "playing": run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Pause" ]) else: run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Play" ]) elif btn_idx == 2: # next run_qdbus6([ "org.bluez", PLAYER_PATH, "org.bluez.MediaPlayer1.Next" ]) info = refresh_info() draw_ui(stdscr, info, btn_idx, button_boxes, color_pair=color_pair) last_refresh = time.time() elif key == -1: pass except KeyboardInterrupt: break def parse_args(): parser = argparse.ArgumentParser(description="Bluetooth Music Player UI with Mouse and Keyboard Control") parser.add_argument('-D', '--device', type=str, required=True, help="Bluetooth MAC address of the device") parser.add_argument('--color', type=str, default="white", help="Color theme (default: white)") parser.add_argument('--autorefresh', type=float, default=1.0, help="Autorefresh interval in seconds (default: 1.0)") return parser.parse_args() def color_theme(theme): colors = dict( black=curses.COLOR_BLACK, red=curses.COLOR_RED, green=curses.COLOR_GREEN, yellow=curses.COLOR_YELLOW, blue=curses.COLOR_BLUE, magenta=curses.COLOR_MAGENTA, cyan=curses.COLOR_CYAN, white=curses.COLOR_WHITE ) base = colors.get(theme.lower(), curses.COLOR_WHITE) curses.init_pair(1, base, curses.COLOR_BLACK) return 1 def run(): args = parse_args() mac_addr = args.device def wrapped(stdscr): curses.start_color() color_pair = color_theme(args.color) main(stdscr, mac_addr, color_pair, autorefresh_interval=args.autorefresh) curses.wrapper(wrapped) if __name__ == "__main__": run() I noticed there were various another questions here about this issue but I did not see an answer on any of them. Continue reading...