Skip to content

Commit

Permalink
Get connection info from D-Bus API
Browse files Browse the repository at this point in the history
There are device properties for RSSI and Tx power now and link quality is pointless.
  • Loading branch information
cschramm committed Oct 29, 2023
1 parent 80c633b commit 87ede5e
Show file tree
Hide file tree
Showing 5 changed files with 13 additions and 267 deletions.
88 changes: 13 additions & 75 deletions blueman/gui/manager/ManagerDeviceList.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from gettext import gettext as _
from typing import Optional, TYPE_CHECKING, List, Any, cast, Callable, Set, Dict
from typing import Optional, TYPE_CHECKING, List, Any, cast, Callable, Dict
import html
import logging
import cairo
Expand All @@ -16,12 +16,10 @@
from blueman.Functions import launch
from blueman.Sdp import ServiceUUID, OBEX_OBJPUSH_SVCLASS_ID
from blueman.gui.GtkAnimation import TreeRowFade, CellFade, AnimBase
from _blueman import ConnInfoReadError, conn_info

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gdk
Expand Down Expand Up @@ -79,8 +77,6 @@ def __init__(self, inst: "Blueman", adapter: Optional[str] = None) -> None:
self.props.has_tooltip = True
self.Blueman = inst

self._monitored_devices: Set[str] = set()

self.manager.connect_signal("battery-created", self.on_battery_created)
self.manager.connect_signal("battery-removed", self.on_battery_removed)
self._batteries: Dict[str, Battery] = {}
Expand Down Expand Up @@ -381,48 +377,6 @@ def row_setup_event(self, tree_iter: Gtk.TreeIter, device: Device) -> None:
except Exception as e:
logging.exception(e)

if device["Connected"]:
self._monitor_power_levels(tree_iter, device)

def _monitor_power_levels(self, tree_iter: Gtk.TreeIter, device: Device) -> None:
if device["Address"] in self._monitored_devices:
return

assert self.Adapter is not None
cinfo = conn_info(device["Address"], os.path.basename(self.Adapter.get_object_path()))
try:
cinfo.init()
except ConnInfoReadError:
logging.warning("Failed to get power levels, probably a LE device.")

model = self.liststore
assert isinstance(model, Gtk.TreeModel)
r = Gtk.TreeRowReference.new(model, model.get_path(tree_iter))
self._update_power_levels(tree_iter, device, cinfo)
GLib.timeout_add(1000, self._check_power_levels, r, cinfo, device["Address"])
self._monitored_devices.add(device["Address"])

def _check_power_levels(self, row_ref: Gtk.TreeRowReference, cinfo: conn_info, address: str) -> bool:
if not row_ref.valid():
logging.warning("stopping monitor (row does not exist)")
cinfo.deinit()
self._monitored_devices.remove(address)
return False

tree_iter = self.get_iter(row_ref.get_path())
assert tree_iter is not None

device = self.get(tree_iter, "device")["device"]

if device["Connected"]:
self._update_power_levels(tree_iter, device, cinfo)
return True
else:
cinfo.deinit()
self._disable_power_levels(tree_iter)
self._monitored_devices.remove(address)
return False

def row_update_event(self, tree_iter: Gtk.TreeIter, key: str, value: Any) -> None:
logging.info(f"{key} {value}")

Expand Down Expand Up @@ -457,9 +411,7 @@ def row_update_event(self, tree_iter: Gtk.TreeIter, key: str, value: Any) -> Non
elif key == "Connected":
self.set(tree_iter, connected=value)

if value:
self._monitor_power_levels(tree_iter, device)
else:
if not value:
self._disable_power_levels(tree_iter)
elif key == "Name":
self.set(tree_iter, no_name=False)
Expand All @@ -468,40 +420,26 @@ def row_update_event(self, tree_iter: Gtk.TreeIter, key: str, value: Any) -> Non
elif key == "Blocked":
self.set(tree_iter, blocked=value)

def _update_power_levels(self, tree_iter: Gtk.TreeIter, device: Device, cinfo: conn_info) -> None:
row = self.get(tree_iter, "cell_fader", "battery", "rssi", "lq", "tpl")

bars = {}
elif key == "RSSI":
self._update_bar(tree_iter, "rssi", 50 if value is None else max(50 + float(value) / 127 * 50, 10))

obj_path = device.get_object_path()
if obj_path in self._batteries:
bars["battery"] = self._batteries[obj_path]["Percentage"]
elif key == "TxPower":
self._update_bar(tree_iter, "tpl", 0 if value is None else max(float(value) / 127 * 100, 10))

# cinfo init may fail for bluetooth devices version 4 and up
# FIXME Workaround is horrible and we should show something better
if cinfo.failed:
bars.update({"rssi": 100.0, "tpl": 100.0})
else:
try:
bars["rssi"] = max(50 + float(cinfo.get_rssi()) / 127 * 50, 10)
except ConnInfoReadError:
bars["rssi"] = 50
try:
bars["tpl"] = max(50 + float(cinfo.get_tpl()) / 127 * 50, 10)
except ConnInfoReadError:
bars["tpl"] = 50
def _update_bar(self, tree_iter: Gtk.TreeIter, name: str, perc: float) -> None:
row = self.get(tree_iter, "cell_fader", "battery", "rssi", "tpl")
row[name] = perc

if row["battery"] == row["rssi"] == row["tpl"] == 0:
self._prepare_fader(row["cell_fader"]).animate(start=0.0, end=1.0, duration=400)

w = 14 * self.get_scale_factor()
h = 48 * self.get_scale_factor()

for (name, perc) in bars.items():
if round(row[name], -1) != round(perc, -1):
icon_name = f"blueman-{name}-{int(round(perc, -1))}.png"
icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(PIXMAP_PATH, icon_name), w, h, True)
self.set(tree_iter, **{name: perc, f"{name}_pb": icon})
if round(row[name], -1) != round(perc, -1):
icon_name = f"blueman-{name}-{int(round(perc, -1))}.png"
icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(PIXMAP_PATH, icon_name), w, h, True)
self.set(tree_iter, **{name: perc, f"{name}_pb": icon})

def _disable_power_levels(self, tree_iter: Gtk.TreeIter) -> None:
row = self.get(tree_iter, "cell_fader", "battery", "rssi", "tpl")
Expand Down
63 changes: 0 additions & 63 deletions module/_blueman.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,6 @@ cdef extern from "bluetooth/rfcomm.h":


cdef extern from "libblueman.h":
cdef struct conn_info_handles:
unsigned int handle
int dd


cdef int connection_init(int dev_id, char *addr, conn_info_handles *ci)
cdef int connection_get_rssi(conn_info_handles *ci, signed char *ret_rssi)
cdef int connection_get_tpl(conn_info_handles *ci, signed char *ret_tpl, unsigned char type)
cdef int connection_close(conn_info_handles *ci)
cdef int c_get_rfcomm_channel "get_rfcomm_channel" (unsigned short service_class, char* btd_addr)
cdef int get_rfcomm_list(rfcomm_dev_list_req **ret)
cdef int c_create_rfcomm_device "create_rfcomm_device" (char *local_address, char *remote_address, int channel)
Expand All @@ -94,11 +85,6 @@ class RFCOMMError(Exception):

ERR = {
-1:"Can't allocate memory",
-2:"HCI device open failed",
-3:"Not connected",
-4:"Get connection info failed",
-5:"Read RSSI failed",
-6:"Read transmit power level request failed",
-8:"Getting rfcomm list failed",
-9:"ERR_SOCKET_FAILED",
-12: "Can't bind RFCOMM socket",
Expand Down Expand Up @@ -205,55 +191,6 @@ def destroy_bridge(py_name="pan1"):
if err < 0:
raise BridgeException(-err)


class ConnInfoReadError(Exception):
pass

cdef class conn_info:
cdef conn_info_handles ci
cdef int hci
cdef char* addr
cdef public bint failed

def __init__(self, py_addr, py_hci_name="hci0"):
self.failed = False
py_bytes_addr = py_addr.encode("UTF-8")
cdef char* addr = py_bytes_addr

py_bytes_hci_name = py_hci_name.encode("UTF-8")
cdef char* hci_name = py_bytes_hci_name


self.hci = int(hci_name[3:])
self.addr = addr

def init(self):
res = connection_init(self.hci, self.addr, & self.ci)
if res < 0:
self.failed = True
raise ConnInfoReadError(ERR[res])

def deinit(self):
if self.failed:
return
connection_close(&self.ci)

def get_rssi(self):
cdef signed char rssi
res = connection_get_rssi(&self.ci, &rssi)
if res < 0:
raise ConnInfoReadError(ERR[res])

return rssi

def get_tpl(self, tp=0):
cdef signed char tpl
res = connection_get_tpl(&self.ci, &tpl, tp)
if res < 0:
raise ConnInfoReadError(ERR[res])

return tpl

def device_info(py_hci_name="hci0"):
py_bytes_hci_name = py_hci_name.encode("UTF-8")
cdef char* hci_name = py_bytes_hci_name
Expand Down
105 changes: 0 additions & 105 deletions module/libblueman.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
#include <linux/if_bridge.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
Expand Down Expand Up @@ -132,109 +130,6 @@ int _destroy_bridge(const char* name) {
return 0;
}

static int find_conn(int s, int dev_id, long arg)
{
struct hci_conn_list_req *cl;
struct hci_conn_info *ci;
int i;
int ret = 0;

if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl))))
goto out;

cl->dev_id = dev_id;
cl->conn_num = 10;
ci = cl->conn_info;

if (ioctl(s, HCIGETCONNLIST, (void *) cl))
goto out;

for (i = 0; i < cl->conn_num; i++, ci++)
if (!bacmp((bdaddr_t *) arg, &ci->bdaddr)) {
ret = 1;
goto out;
}

out:
free(cl);
return ret;
}



int connection_init(int dev_id, char *addr, struct conn_info_handles *ci)
{
struct hci_conn_info_req *cr = NULL;
bdaddr_t bdaddr;

int dd;
int ret = 1;

str2ba(addr, &bdaddr);

if (dev_id < 0) {
dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
if (dev_id < 0) {
ret = ERR_NOT_CONNECTED;
goto out;
}
}

dd = hci_open_dev(dev_id);
if (dd < 0) {
ret = ERR_HCI_DEV_OPEN_FAILED;
goto out;
}

cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
if (!cr) {
ret = ERR_CANNOT_ALLOCATE;
goto out;
}

bacpy(&cr->bdaddr, &bdaddr);
cr->type = ACL_LINK;
if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
ret = ERR_GET_CONN_INFO_FAILED;
goto out;
}

ci->dd = dd;
ci->handle = cr->conn_info->handle;

out:
if (cr)
free(cr);

return ret;
}

int connection_get_rssi(struct conn_info_handles *ci, int8_t *ret_rssi)
{
int8_t rssi;
if (hci_read_rssi(ci->dd, htobs(ci->handle), &rssi, 1000) < 0) {
return ERR_READ_RSSI_FAILED;
}
*ret_rssi = rssi;
return 1;

}

int connection_get_tpl(struct conn_info_handles *ci, int8_t *ret_tpl, uint8_t type)
{
int8_t level;
if (hci_read_transmit_power_level(ci->dd, htobs(ci->handle), type, &level, 1000) < 0) {
return ERR_READ_TPL_FAILED;
}
*ret_tpl = level;
return 1;
}

int connection_close(struct conn_info_handles *ci)
{
hci_close_dev(ci->dd);
return 1;
}

int
get_rfcomm_channel(uint16_t service_class, char* btd_addr) {
Expand Down
14 changes: 0 additions & 14 deletions module/libblueman.h
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
#pragma once
#define ERR_CANNOT_ALLOCATE -1
#define ERR_HCI_DEV_OPEN_FAILED -2
#define ERR_NOT_CONNECTED -3
#define ERR_GET_CONN_INFO_FAILED -4
#define ERR_READ_RSSI_FAILED -5
#define ERR_READ_TPL_FAILED -6
#define ERR_GET_RFCOMM_LIST_FAILED -8
#define ERR_SOCKET_FAILED -9
#define ERR_BIND_FAILED -12
#define ERR_CONNECT_FAILED -13
#define ERR_CREATE_DEV_FAILED -14
#define ERR_RELEASE_DEV_FAILED -15

struct conn_info_handles {
unsigned int handle;
int dd;
};

int connection_init(int dev_id, char *addr, struct conn_info_handles *ci);
int connection_get_rssi(struct conn_info_handles *ci, int8_t *ret_rssi);
int connection_get_tpl(struct conn_info_handles *ci, int8_t *ret_tpl, uint8_t type);
int connection_close(struct conn_info_handles *ci);
int get_rfcomm_channel(uint16_t uuid, char* btd_addr);
int get_rfcomm_list(struct rfcomm_dev_list_req **result);
int create_rfcomm_device(char *local_address, char *remote_address, int channel);
Expand Down
10 changes: 0 additions & 10 deletions stubs/_blueman.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,8 @@ class BridgeException(Exception):
errno: int
def __init__(self, errno: int) -> None: ...

class ConnInfoReadError(Exception): ...

class RFCOMMError(Exception): ...

class conn_info:
failed: bool
def __init__(self, addr: str, hci_name: str) -> None: ...
def deinit(self) -> None: ...
def get_rssi(self) -> int: ...
def get_tpl(self) -> int: ...
def init(self) -> None: ...

def create_bridge(name: str = "pan1") -> None: ...
def create_rfcomm_device(local_address: str, remote_address: str, channel: int) -> int: ...
def destroy_bridge(name: str = "pan1") -> None: ...
Expand Down

0 comments on commit 87ede5e

Please sign in to comment.