From d63ffa1171de2f361bd4501ad56bf79fc4018a4c Mon Sep 17 00:00:00 2001 From: Rene Kievits Date: Mon, 28 Nov 2022 06:47:05 +0100 Subject: [PATCH] finished bluetooth module, cleaned and documented --- awesome/src/modules/bluetooth/device.lua | 147 +++++++++----------- awesome/src/modules/bluetooth/init.lua | 166 +++++++++++------------ 2 files changed, 139 insertions(+), 174 deletions(-) diff --git a/awesome/src/modules/bluetooth/device.lua b/awesome/src/modules/bluetooth/device.lua index 6b01ebe..3f0746a 100644 --- a/awesome/src/modules/bluetooth/device.lua +++ b/awesome/src/modules/bluetooth/device.lua @@ -3,17 +3,17 @@ -------------------------------------- -- Awesome Libs -local awful = require("awful") -local dpi = require("beautiful").xresources.apply_dpi -local gtable = require("gears").table -local gcolor = require("gears").color -local gshape = require("gears").shape -local gfilesystem = require("gears").filesystem -local wibox = require("wibox") +local abutton = require("awful.button") +local awidget = require("awful.widget") local base = require("wibox.widget.base") +local dpi = require("beautiful").xresources.apply_dpi +local gcolor = require("gears").color +local gfilesystem = require("gears").filesystem +local gtable = require("gears").table local lgi = require("lgi") -local dbus_proxy = require("dbus_proxy") +local wibox = require("wibox") +-- Own libs local context_menu = require("src.modules.context_menu") local icondir = gfilesystem.get_configuration_dir() .. "src/assets/icons/bluetooth/" @@ -24,6 +24,8 @@ local capi = { local device = { mt = {} } +--#region wibox.widget.base boilerplate + function device:layout(_, width, height) if self._private.widget then return { base.place_widget_at(self._private.widget, 0, 0, width, height) } @@ -44,6 +46,9 @@ function device:get_widget() return self._private.widget end +--#endregion + +--- Connect to a device if not connected else disconnect function device:toggle_connect() if not self.device.Connected then @@ -72,6 +77,7 @@ function device:toggle_connect() end end +--- Pair to a device if not paired else unpair function device:toggle_pair() if self.device.Paired then self.device:PairAsync() @@ -80,12 +86,15 @@ function device:toggle_pair() end end +--- Trust a device if not trusted else untrust function device:toggle_trusted() self.device:Set("org.bluez.Device1", "Trusted", lgi.GLib.Variant("b", not self.device.Trusted)) self.device.Trusted = { signature = "b", value = not self.device.Trusted } - end +---Rename a device alias +---@param newname string New name, if empty the device name will be reset +---@return string name The new or old name depending if the string was empty or not function device:rename(newname) self.device:Set("org.bluez.Device1", "Alias", lgi.GLib.Variant("s", newname)) self.device.Alias = { signature = "s", value = newname } @@ -94,11 +103,10 @@ end function device.new(args) args = args or {} - args.device = args.device or {} local icon = device.Icon or "bluetooth-on" - local inputbox = awful.widget.inputbox { + local inputbox = awidget.inputbox { text = args.device.Alias or args.device.Name, halign = "left", valign = "center", @@ -112,43 +120,22 @@ function device.new(args) { image = gcolor.recolor_image( icondir .. icon .. ".svg", Theme_config.bluetooth_controller.icon_color), - id = "icon", resize = false, valign = "center", halign = "center", - forced_width = dpi(24), - forced_height = dpi(24), widget = wibox.widget.imagebox }, - id = "icon_container", - strategy = "max", + strategy = "exact", width = dpi(24), height = dpi(24), widget = wibox.container.constraint }, { - { - { - inputbox, - widget = wibox.container.constraint, - strategy = "min", - width = dpi(400), - id = "const" - }, - { - text = "Connecting...", - id = "connecting", - visible = false, - font = User_config.font.specify .. ", regular 10", - widget = wibox.widget.textbox - }, - id = "alias_container", - layout = wibox.layout.fixed.horizontal - }, - width = dpi(260), - height = dpi(40), - strategy = "max", - widget = wibox.container.constraint + inputbox, + widget = wibox.container.constraint, + strategy = "exact", + width = dpi(400), + id = "const" }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal @@ -166,35 +153,25 @@ function device.new(args) resize = false, valign = "center", halign = "center", - forced_width = dpi(24), - forced_height = dpi(24), widget = wibox.widget.imagebox }, - id = "place", - strategy = "max", + strategy = "exact", width = dpi(24), height = dpi(24), widget = wibox.container.constraint }, - id = "margin", margins = dpi(2), widget = wibox.container.margin }, - id = "backgr", - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.icon_shape, bg = Theme_config.bluetooth_controller.con_button_color, widget = wibox.container.background }, - id = "margin0", margin = dpi(5), widget = wibox.container.margin }, - id = "device_layout", layout = wibox.layout.align.horizontal }, - id = "device_margin", margins = dpi(5), widget = wibox.container.margin }, @@ -203,18 +180,15 @@ function device.new(args) border_color = Theme_config.bluetooth_controller.device_border_color, border_width = Theme_config.bluetooth_controller.device_border_width, id = "background", - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.device_shape, widget = wibox.container.background }) gtable.crush(ret, device, true) - if args.device then - ret.device = args.device - end + ret.device = args.device or {} + -- Set the image of the connection button depending on the connection state ret:get_children_by_id("con")[1].image = gcolor.recolor_image(ret.device.Connected and icondir .. "link.svg" or icondir .. "link-off.svg", Theme_config.bluetooth_controller.icon_color_dark) @@ -250,7 +224,7 @@ function device.new(args) widget = wibox.container.background, }, spacing = dpi(10), entries = { - { + { -- Connect/Disconnect a device name = ret.device.Connected and "Disconnect" or "Connect", icon = gcolor.recolor_image(ret.device.Connected and icondir .. "bluetooth-off.svg" or icondir .. "bluetooth-on.svg", @@ -260,7 +234,7 @@ function device.new(args) end, id = "connected" }, - { + { -- Pair/Unpair a device name = "Pair", icon = gcolor.recolor_image(ret.device.Paired and icondir .. "link-off.svg" or icondir .. "link.svg", @@ -269,7 +243,7 @@ function device.new(args) ret:toggle_pair() end }, - { + { -- Trust/Untrust a device name = ret.device.Trusted and "Untrust" or "Trust", icon = gcolor.recolor_image(ret.device.Trusted and icondir .. "untrusted.svg" or icondir .. "trusted.svg", Theme_config.bluetooth_controller.icon_color), @@ -278,7 +252,7 @@ function device.new(args) end, id = "trusted" }, - { + { -- Rename a device name = "Rename", icon = gcolor.recolor_image(icondir .. "edit.svg", Theme_config.bluetooth_controller.icon_color), callback = function() @@ -289,7 +263,7 @@ function device.new(args) end) end }, - { + { -- Remove a device name = "Remove", icon = gcolor.recolor_image(icondir .. "delete.svg", Theme_config.bluetooth_controller.icon_color), callback = function() @@ -299,33 +273,34 @@ function device.new(args) } } - ret:buttons( - gtable.join( - awful.button({}, 1, function() - ret:toggle_connect() - end), - awful.button({}, 3, function() - for _, value in ipairs(cm.widget.children) do - value.id = value.id or "" - if value.id:match("connected") then - value:get_children_by_id("text_role")[1].text = ret.device.Connected and "Disconnect" or "Connect" - value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Connected and - icondir .. "bluetooth-off.svg" or icondir .. "bluetooth-on.svg", - Theme_config.bluetooth_controller.icon_color) - elseif value.id:match("trusted") then - value:get_children_by_id("text_role")[1].text = ret.device.Trusted and "Untrust" or "Trust" - value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Trusted and - icondir .. "untrusted.svg" or icondir .. "trusted.svg", Theme_config.bluetooth_controller.icon_color) - elseif value.id:match("paired") then - value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Paired and - icondir .. "link-off.svg" or icondir .. "link.svg", Theme_config.bluetooth_controller.icon_color) - end + ret:buttons(gtable.join( + abutton({}, 1, function() + -- Toggle the connection state + ret:toggle_connect() + end), + abutton({}, 3, function() + -- Show the context menu and update its entrie names + for _, value in ipairs(cm.widget.children) do + value.id = value.id or "" + if value.id:match("connected") then + value:get_children_by_id("text_role")[1].text = ret.device.Connected and "Disconnect" or "Connect" + value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Connected and + icondir .. "bluetooth-off.svg" or icondir .. "bluetooth-on.svg", + Theme_config.bluetooth_controller.icon_color) + elseif value.id:match("trusted") then + value:get_children_by_id("text_role")[1].text = ret.device.Trusted and "Untrust" or "Trust" + value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Trusted and + icondir .. "untrusted.svg" or icondir .. "trusted.svg", Theme_config.bluetooth_controller.icon_color) + elseif value.id:match("paired") then + value:get_children_by_id("icon_role")[1].image = gcolor.recolor_image(ret.device.Paired and + icondir .. "link-off.svg" or icondir .. "link.svg", Theme_config.bluetooth_controller.icon_color) end - cm:toggle() - end) - ) - ) + end + cm:toggle() + end) + )) + -- Update the updated device icon capi.awesome.connect_signal(ret.device.object_path .. "_updated", function(d) ret:get_children_by_id("con")[1].image = gcolor.recolor_image(d.Connected and icondir .. "link.svg" or icondir .. "link-off.svg", diff --git a/awesome/src/modules/bluetooth/init.lua b/awesome/src/modules/bluetooth/init.lua index 7072f44..980a680 100644 --- a/awesome/src/modules/bluetooth/init.lua +++ b/awesome/src/modules/bluetooth/init.lua @@ -3,34 +3,39 @@ -------------------------------------- -- Awesome Libs -local awful = require("awful") -local dpi = require("beautiful").xresources.apply_dpi -local gtable = require("gears").table -local gcolor = require("gears").color -local gshape = require("gears").shape -local gfilesystem = require("gears").filesystem -local wibox = require("wibox") +local abutton = require("awful.button") +local aspawn = require("awful.spawn") local base = require("wibox.widget.base") local dbus_proxy = require("dbus_proxy") -local lgi = require("lgi") +local dpi = require("beautiful").xresources.apply_dpi +local gcolor = require("gears").color +local gfilesystem = require("gears").filesystem +local gshape = require("gears").shape +local gtable = require("gears").table local gtimer = require("gears.timer") +local lgi = require("lgi") local naughty = require("naughty") +local wibox = require("wibox") -local bt_device = require("src.modules.bluetooth.device") - +-- Third party libs local rubato = require("src.lib.rubato") -local icondir = gfilesystem.get_configuration_dir() .. "src/assets/icons/bluetooth/" - +-- Own libs +local bt_device = require("src.modules.bluetooth.device") local dnd_widget = require("awful.widget.toggle_widget") +local icondir = gfilesystem.get_configuration_dir() .. "src/assets/icons/bluetooth/" + local capi = { awesome = awesome, mouse = mouse, mousegrabber = mousegrabber, } + local bluetooth = { mt = {} } +--#region wibox.widget.base boilerplate + function bluetooth:layout(_, width, height) if self._private.widget then return { base.place_widget_at(self._private.widget, 0, 0, width, height) } @@ -51,36 +56,41 @@ function bluetooth:get_widget() return self._private.widget end +--#endregion + +---Get the list of paired devices +---@return table devices table of paired devices function bluetooth:get_paired_devices() return self:get_children_by_id("connected_device_list")[1].children end +---Get the list of discovered devices +---@return table devices table of discovered devices function bluetooth:get_discovered_devices() return self:get_children_by_id("discovered_device_list")[1].children end +--- Remove a device by first disconnecting it async then removing it function bluetooth:remove_device_information(device) - -- Either disconnect async and have to remove the device "twice" - -- or do it sync but awesome freezes for a second or two - print("bruh?") device:DisconnectAsync(function(_, _, out, err) - print(out, err) self._private.Adapter1:RemoveDevice(device.object_path) end) end +--- Add a new device into the devices list function bluetooth:add_device(device, object_path) + -- Get a reference to both lists local plist = self:get_children_by_id("connected_device_list")[1] local dlist = self:get_children_by_id("discovered_device_list")[1] + -- For the first list check if the device already exists and if its connection state changed + -- if it changed then remove it from the current list and put it into the other one for _, value in pairs(dlist.children) do -- I'm not sure why Connected is in both cases true when its a new connection but eh just take it, it works if value.device.Address:match(device.Address) and (device.Connected ~= value.device.Connected) then - print("Bad ", value.device.Alias) return elseif value.device.Address:match(device.Address) and (device.Connected == value.device.Connected) then - print("Good ", value.device.Alias) dlist:remove_widgets(value) plist:add(plist:add(bt_device { device = device, @@ -92,10 +102,13 @@ function bluetooth:add_device(device, object_path) return; end end + -- Just check if the device already exists in the list for _, value in pairs(plist.children) do if value.device.Address:match(device.Address) then return end end + -- If its paired add it to the paired list + -- else add it to the discovered list if device.Paired then plist:add(bt_device { device = device, @@ -115,6 +128,8 @@ function bluetooth:add_device(device, object_path) end end +---Remove a device from any list +---@param object_path string the object path of the device function bluetooth:remove_device(object_path) local plist = self:get_children_by_id("connected_device_list")[1] local dlist = self:get_children_by_id("discovered_device_list")[1] @@ -130,27 +145,17 @@ function bluetooth:remove_device(object_path) end end -function bluetooth:update_device(new_device, object_path) - for _, device in ipairs(self.devices.paired:get_children()) do - if device.path == object_path then - device.device:update(new_device) - end - end - for _, device in ipairs(self.devices.discovered:get_children()) do - if device.path == object_path then - device.device:update(new_device) - end - end -end - +---Start scanning for devices function bluetooth:scan() self._private.Adapter1:StartDiscovery() end +---Stop scanning for devices function bluetooth:stop_scan() self._private.Adapter1:StopDiscovery() end +---Toggle bluetooth on or off function bluetooth:toggle() local powered = self._private.Adapter1.Powered @@ -161,13 +166,18 @@ function bluetooth:toggle() } end +--- Open blueman-manager function bluetooth:open_settings() - awful.spawn("blueman-manager") + aspawn("blueman-manager") end +---Get a new device proxy and connect a PropertyChanged signal to it and +---add the device to the list +---@param object_path string the object path of the device function bluetooth:get_device_info(object_path) if (not object_path) or (not object_path:match("/org/bluez/hci0/dev")) then return end + -- New Device1 proxy local Device1 = dbus_proxy.Proxy:new { bus = dbus_proxy.Bus.SYSTEM, name = "org.bluez", @@ -175,6 +185,7 @@ function bluetooth:get_device_info(object_path) path = object_path } + -- New Properties proxy for the object_path local Device1Properties = dbus_proxy.Proxy:new { bus = dbus_proxy.Bus.SYSTEM, name = "org.bluez", @@ -182,8 +193,10 @@ function bluetooth:get_device_info(object_path) path = object_path } + -- Just return if the Device1 has no name, this usually means random devices with just a mac address if (not Device1.Name) or (Device1.Name == "") then return end + -- For some reason it notifies twice or thrice local just_notified = false local notify_timer = gtimer { @@ -195,6 +208,7 @@ function bluetooth:get_device_info(object_path) end } + -- Connect the PropertyChanged signal to update the device when a property changes and send a notification Device1Properties:connect_signal(function(_, _, changed_props) if changed_props["Connected"] ~= nil then if not just_notified then @@ -218,6 +232,8 @@ function bluetooth:get_device_info(object_path) self:add_device(Device1, object_path) end +---Send a notification +---@param powered boolean the powered state of the adapter local function send_state_notification(powered) naughty.notification { app_icon = gcolor.recolor_image(icondir .. "bluetooth-on.svg", Theme_config.bluetooth_controller.icon_color), @@ -241,25 +257,20 @@ function bluetooth.new(args) { { { - { - resize = false, - image = gcolor.recolor_image(icondir .. "menu-down.svg", - Theme_config.bluetooth_controller.connected_icon_color), - widget = wibox.widget.imagebox, - valign = "center", - halign = "center", - id = "icon" - }, - id = "center", - halign = "center", + resize = false, + image = gcolor.recolor_image(icondir .. "menu-down.svg", + Theme_config.bluetooth_controller.connected_icon_color), + widget = wibox.widget.imagebox, valign = "center", - widget = wibox.container.place, + halign = "center", + id = "icon" }, { { text = "Paired Devices", + valign = "center", + halign = "center", widget = wibox.widget.textbox, - id = "device_name" }, margins = dpi(5), widget = wibox.container.margin @@ -267,19 +278,15 @@ function bluetooth.new(args) id = "connected", layout = wibox.layout.fixed.horizontal }, - id = "connected_bg", bg = Theme_config.bluetooth_controller.connected_bg, fg = Theme_config.bluetooth_controller.connected_fg, - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.connected_shape, widget = wibox.container.background }, id = "connected_margin", widget = wibox.container.margin }, { - id = "connected_list", { { step = dpi(50), @@ -294,35 +301,29 @@ function bluetooth.new(args) }, border_color = Theme_config.bluetooth_controller.con_device_border_color, border_width = Theme_config.bluetooth_controller.con_device_border_width, - shape = function(cr, width, height) - gshape.partially_rounded_rect(cr, width, height, false, false, true, true, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.con_device_shape, widget = wibox.container.background, - forced_height = 0 + forced_height = 0, + id = "connected_list", }, { { { { - { - resize = false, - image = gcolor.recolor_image(icondir .. "menu-down.svg", - Theme_config.bluetooth_controller.discovered_icon_color), - widget = wibox.widget.imagebox, - valign = "center", - halign = "center", - id = "icon", - }, - id = "center", - halign = "center", + resize = false, + image = gcolor.recolor_image(icondir .. "menu-down.svg", + Theme_config.bluetooth_controller.discovered_icon_color), + widget = wibox.widget.imagebox, valign = "center", - widget = wibox.container.place, + halign = "center", + id = "icon", }, { { text = "Nearby Devices", + valign = "center", + halign = "center", widget = wibox.widget.textbox, - id = "device_name" }, margins = dpi(5), widget = wibox.container.margin @@ -333,9 +334,7 @@ function bluetooth.new(args) id = "discovered_bg", bg = Theme_config.bluetooth_controller.discovered_bg, fg = Theme_config.bluetooth_controller.discovered_fg, - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.discovered_shape, widget = wibox.container.background }, id = "discovered_margin", @@ -343,7 +342,6 @@ function bluetooth.new(args) widget = wibox.container.margin }, { - id = "discovered_list", { { id = "discovered_device_list", @@ -352,17 +350,15 @@ function bluetooth.new(args) layout = require("src.lib.overflow_widget.overflow").vertical, scrollbar_width = 0, }, - id = "margin", margins = dpi(10), widget = wibox.container.margin }, border_color = Theme_config.bluetooth_controller.con_device_border_color, border_width = Theme_config.bluetooth_controller.con_device_border_width, - shape = function(cr, width, height) - gshape.partially_rounded_rect(cr, width, height, false, false, true, true, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.con_device_shape, widget = wibox.container.background, - forced_height = 0 + forced_height = 0, + id = "discovered_list", }, { { -- action buttons @@ -390,43 +386,36 @@ function bluetooth.new(args) widget = wibox.container.margin, margins = dpi(5), }, - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(4)) - end, + shape = Theme_config.bluetooth_controller.refresh_shape, bg = Theme_config.bluetooth_controller.refresh_bg, id = "scan", widget = wibox.container.background }, layout = wibox.layout.align.horizontal }, - id = "marg_dnd", widget = wibox.container.margin, top = dpi(10), }, - id = "layout1", layout = wibox.layout.fixed.vertical }, - id = "margin", margins = dpi(15), widget = wibox.container.margin }, - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, dpi(8)) - end, + shape = Theme_config.bluetooth_controller.shape, border_color = Theme_config.bluetooth_controller.container_border_color, border_width = Theme_config.bluetooth_controller.container_border_width, bg = Theme_config.bluetooth_controller.container_bg, - id = "background", widget = wibox.container.background }, width = dpi(400), - forced_width = dpi(400), strategy = "exact", widget = wibox.container.constraint }) + -- Get a reference to the dnd button local dnd = ret:get_children_by_id("dnd")[1]:get_widget() + -- Toggle bluetooth on or off dnd:connect_signal("dnd::toggle", function(enable) ret:toggle() end) @@ -574,8 +563,9 @@ function bluetooth.new(args) ) --#endregion + -- Add buttons to the scan button ret:get_children_by_id("scan")[1]:buttons({ - awful.button({}, 1, function() + abutton({}, 1, function() ret:scan() end) })