2 |
rofi-bluetooth
3 |

4 |
5 | `bluetoothctl` `rofi` `dmenu`
6 |
7 |
8 |
9 | ## Installation
10 |
11 | Install from [AUR (rofi-bluetooth-git)](https://aur.archlinux.org/packages/rofi-bluetooth-git/), or:
12 |
13 | 1. Install dependencies: [rofi](https://github.com/davatorium/rofi), bluetoothctl (provided by `bluez-utils` in Arch) and [bc](https://archlinux.org/packages/extra/x86_64/bc/)
14 | 1. `git clone git@github.com:ClydeDroid/rofi-bluetooth.git`
15 | 1. `cd rofi-bluetooth`
16 | 1. `./rofi-bluetooth`
17 | 1. (Optional) For easy access, add the script somewhere in your `$PATH`.
18 |
19 | ### Polybar configuration
20 |
21 | `NOTE:` In order to properly display the bluetooth icon, you will need to use an iconic font in your bar, e.g. [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts)
22 |
23 | ```
24 | [module/bluetooth]
25 | type = custom/script
26 | exec = rofi-bluetooth --status
27 | interval = 1
28 | click-left = rofi-bluetooth &
29 | ```
30 |
31 | ### Waybar configuration
32 |
33 | ```
34 | "bluetooth": {
35 | // "controller": "controller1", // specify the alias of the controller if there are more than 1 on the system
36 | "format": " {status}",
37 | "format-disabled": "", // an empty format will hide the module
38 | "format-connected": " {num_connections} connected",
39 | "tooltip-format": "{controller_alias}\t{controller_address}",
40 | "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{device_enumerate}",
41 | "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
42 | "on-click": "rofi-bluetooth"
43 | },
44 | ```
45 |
46 | ### i3 keybinding
47 |
48 | ```
49 | bindsym $mod+b exec --no-startup-id rofi-bluetooth
50 | ```
51 |
52 | ### Hyprland keybinding
53 |
54 | ```
55 | bind = $mainMod, B, exec, rofi-bluetooth
56 | ```
57 |
58 | ### Thanks for the inspiration!
59 |
60 | - [firecat53/networkmanager-dmenu](https://github.com/firecat53/networkmanager-dmenu)
61 | - [x70b1's bluetoothctl polybar script](https://github.com/polybar/polybar-scripts/tree/master/polybar-scripts/system-bluetooth-bluetoothctl)
62 |
--------------------------------------------------------------------------------
/rofi-bluetooth:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # __ _ _ _ _ _ _
3 | # _ __ ___ / _(_) | |__ | |_ _ ___| |_ ___ ___ | |_| |__
4 | # | '__/ _ \| |_| |_____| '_ \| | | | |/ _ \ __/ _ \ / _ \| __| '_ \
5 | # | | | (_) | _| |_____| |_) | | |_| | __/ || (_) | (_) | |_| | | |
6 | # |_| \___/|_| |_| |_.__/|_|\__,_|\___|\__\___/ \___/ \__|_| |_|
7 | #
8 | # Author: Nick Clyde (clydedroid)
9 | #
10 | # A script that generates a rofi menu that uses bluetoothctl to
11 | # connect to bluetooth devices and display status info.
12 | #
13 | # Inspired by networkmanager-dmenu (https://github.com/firecat53/networkmanager-dmenu)
14 | # Thanks to x70b1 (https://github.com/polybar/polybar-scripts/tree/master/polybar-scripts/system-bluetooth-bluetoothctl)
15 | #
16 | # Depends on:
17 | # Arch repositories: rofi, bluez-utils (contains bluetoothctl), bc
18 |
19 | # Constants
20 | divider="---------"
21 | goback="Back"
22 |
23 | # Checks if bluetooth controller is powered on
24 | power_on() {
25 | if bluetoothctl show | grep -q "Powered: yes"; then
26 | return 0
27 | else
28 | return 1
29 | fi
30 | }
31 |
32 | # Toggles power state
33 | toggle_power() {
34 | if power_on; then
35 | bluetoothctl power off
36 | show_menu
37 | else
38 | if rfkill list bluetooth | grep -q 'blocked: yes'; then
39 | rfkill unblock bluetooth && sleep 3
40 | fi
41 | bluetoothctl power on
42 | show_menu
43 | fi
44 | }
45 |
46 | # Checks if controller is scanning for new devices
47 | scan_on() {
48 | if bluetoothctl show | grep -q "Discovering: yes"; then
49 | echo "Scan: on"
50 | return 0
51 | else
52 | echo "Scan: off"
53 | return 1
54 | fi
55 | }
56 |
57 | # Toggles scanning state
58 | toggle_scan() {
59 | if scan_on; then
60 | kill $(pgrep -f "bluetoothctl --timeout 5 scan on")
61 | bluetoothctl scan off
62 | show_menu
63 | else
64 | bluetoothctl --timeout 5 scan on
65 | echo "Scanning..."
66 | show_menu
67 | fi
68 | }
69 |
70 | # Checks if controller is able to pair to devices
71 | pairable_on() {
72 | if bluetoothctl show | grep -q "Pairable: yes"; then
73 | echo "Pairable: on"
74 | return 0
75 | else
76 | echo "Pairable: off"
77 | return 1
78 | fi
79 | }
80 |
81 | # Toggles pairable state
82 | toggle_pairable() {
83 | if pairable_on; then
84 | bluetoothctl pairable off
85 | show_menu
86 | else
87 | bluetoothctl pairable on
88 | show_menu
89 | fi
90 | }
91 |
92 | # Checks if controller is discoverable by other devices
93 | discoverable_on() {
94 | if bluetoothctl show | grep -q "Discoverable: yes"; then
95 | echo "Discoverable: on"
96 | return 0
97 | else
98 | echo "Discoverable: off"
99 | return 1
100 | fi
101 | }
102 |
103 | # Toggles discoverable state
104 | toggle_discoverable() {
105 | if discoverable_on; then
106 | bluetoothctl discoverable off
107 | show_menu
108 | else
109 | bluetoothctl discoverable on
110 | show_menu
111 | fi
112 | }
113 |
114 | # Checks if a device is connected
115 | device_connected() {
116 | device_info=$(bluetoothctl info "$1")
117 | if echo "$device_info" | grep -q "Connected: yes"; then
118 | return 0
119 | else
120 | return 1
121 | fi
122 | }
123 |
124 | # Toggles device connection
125 | toggle_connection() {
126 | if device_connected "$1"; then
127 | bluetoothctl disconnect "$1"
128 | device_menu "$device"
129 | else
130 | bluetoothctl connect "$1"
131 | device_menu "$device"
132 | fi
133 | }
134 |
135 | # Checks if a device is paired
136 | device_paired() {
137 | device_info=$(bluetoothctl info "$1")
138 | if echo "$device_info" | grep -q "Paired: yes"; then
139 | echo "Paired: yes"
140 | return 0
141 | else
142 | echo "Paired: no"
143 | return 1
144 | fi
145 | }
146 |
147 | # Toggles device paired state
148 | toggle_paired() {
149 | if device_paired "$1"; then
150 | bluetoothctl remove "$1"
151 | device_menu "$device"
152 | else
153 | bluetoothctl pair "$1"
154 | device_menu "$device"
155 | fi
156 | }
157 |
158 | # Checks if a device is trusted
159 | device_trusted() {
160 | device_info=$(bluetoothctl info "$1")
161 | if echo "$device_info" | grep -q "Trusted: yes"; then
162 | echo "Trusted: yes"
163 | return 0
164 | else
165 | echo "Trusted: no"
166 | return 1
167 | fi
168 | }
169 |
170 | # Toggles device connection
171 | toggle_trust() {
172 | if device_trusted "$1"; then
173 | bluetoothctl untrust "$1"
174 | device_menu "$device"
175 | else
176 | bluetoothctl trust "$1"
177 | device_menu "$device"
178 | fi
179 | }
180 |
181 | # Prints a short string with the current bluetooth status
182 | # Useful for status bars like polybar, etc.
183 | print_status() {
184 | if power_on; then
185 | printf ''
186 |
187 | paired_devices_cmd="devices Paired"
188 | # Check if an outdated version of bluetoothctl is used to preserve backwards compatibility
189 | if (( $(echo "$(bluetoothctl version | cut -d ' ' -f 2) < 5.65" | bc -l) )); then
190 | paired_devices_cmd="paired-devices"
191 | fi
192 |
193 | mapfile -t paired_devices < <(bluetoothctl $paired_devices_cmd | grep Device | cut -d ' ' -f 2)
194 | counter=0
195 |
196 | for device in "${paired_devices[@]}"; do
197 | if device_connected "$device"; then
198 | device_alias=$(bluetoothctl info "$device" | grep "Alias" | cut -d ' ' -f 2-)
199 |
200 | if [ $counter -gt 0 ]; then
201 | printf ", %s" "$device_alias"
202 | else
203 | printf " %s" "$device_alias"
204 | fi
205 |
206 | ((counter++))
207 | fi
208 | done
209 | printf "\n"
210 | else
211 | echo ""
212 | fi
213 | }
214 |
215 | # A submenu for a specific device that allows connecting, pairing, and trusting
216 | device_menu() {
217 | device=$1
218 |
219 | # Get device name and mac address
220 | device_name=$(echo "$device" | cut -d ' ' -f 3-)
221 | mac=$(echo "$device" | cut -d ' ' -f 2)
222 |
223 | # Build options
224 | if device_connected "$mac"; then
225 | connected="Connected: yes"
226 | else
227 | connected="Connected: no"
228 | fi
229 | paired=$(device_paired "$mac")
230 | trusted=$(device_trusted "$mac")
231 | options="$connected\n$paired\n$trusted\n$divider\n$goback\nExit"
232 |
233 | # Open rofi menu, read chosen option
234 | chosen="$(echo -e "$options" | $rofi_command "$device_name")"
235 |
236 | # Match chosen option to command
237 | case "$chosen" in
238 | "" | "$divider")
239 | echo "No option chosen."
240 | ;;
241 | "$connected")
242 | toggle_connection "$mac"
243 | ;;
244 | "$paired")
245 | toggle_paired "$mac"
246 | ;;
247 | "$trusted")
248 | toggle_trust "$mac"
249 | ;;
250 | "$goback")
251 | show_menu
252 | ;;
253 | esac
254 | }
255 |
256 | # Opens a rofi menu with current bluetooth status and options to connect
257 | show_menu() {
258 | # Get menu options
259 | if power_on; then
260 | power="Power: on"
261 |
262 | # Human-readable names of devices, one per line
263 | # If scan is off, will only list paired devices
264 | devices=$(bluetoothctl devices | grep Device | cut -d ' ' -f 3-)
265 |
266 | # Get controller flags
267 | scan=$(scan_on)
268 | pairable=$(pairable_on)
269 | discoverable=$(discoverable_on)
270 |
271 | # Options passed to rofi
272 | options="$devices\n$divider\n$power\n$scan\n$pairable\n$discoverable\nExit"
273 | else
274 | power="Power: off"
275 | options="$power\nExit"
276 | fi
277 |
278 | # Open rofi menu, read chosen option
279 | chosen="$(echo -e "$options" | $rofi_command "Bluetooth")"
280 |
281 | # Match chosen option to command
282 | case "$chosen" in
283 | "" | "$divider")
284 | echo "No option chosen."
285 | ;;
286 | "$power")
287 | toggle_power
288 | ;;
289 | "$scan")
290 | toggle_scan
291 | ;;
292 | "$discoverable")
293 | toggle_discoverable
294 | ;;
295 | "$pairable")
296 | toggle_pairable
297 | ;;
298 | *)
299 | device=$(bluetoothctl devices | grep "$chosen")
300 | # Open a submenu if a device is selected
301 | if [[ $device ]]; then device_menu "$device"; fi
302 | ;;
303 | esac
304 | }
305 |
306 | # Rofi command to pipe into, can add any options here
307 | rofi_command="rofi -dmenu $* -p"
308 |
309 | case "$1" in
310 | --status)
311 | print_status
312 | ;;
313 | *)
314 | show_menu
315 | ;;
316 | esac
317 |
--------------------------------------------------------------------------------