summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.extract.sh29
-rw-r--r--.gitignore2
-rw-r--r--Makefile19
-rw-r--r--README.md59
-rw-r--r--Settings.ui507
-rw-r--r--bluetooth.js109
-rw-r--r--bluetooth_legacy.js124
-rw-r--r--convenience.js93
-rw-r--r--debian/changelog159
-rw-r--r--debian/clean1
-rw-r--r--debian/control17
-rw-r--r--debian/copyright38
-rw-r--r--debian/install5
-rwxr-xr-xdebian/rules7
-rw-r--r--debian/upstream/metadata4
-rw-r--r--debian/watch2
-rw-r--r--extension.js305
-rw-r--r--metadata.json7
-rw-r--r--po/bluetooth-quick-connect.pot66
-rw-r--r--po/cs.po67
-rw-r--r--po/de.po66
-rw-r--r--po/el.po67
-rw-r--r--po/es.po69
-rw-r--r--po/fr.po67
-rw-r--r--po/nl.po66
-rw-r--r--po/oc.po66
-rw-r--r--po/pl.po68
-rw-r--r--power.js37
-rw-r--r--prefs.js48
-rw-r--r--schemas/org.gnome.shell.extensions.bluetooth-quick-connect.gschema.xml20
-rw-r--r--settings.js72
-rw-r--r--ui.js298
-rw-r--r--utils.js82
33 files changed, 2179 insertions, 467 deletions
diff --git a/.extract.sh b/.extract.sh
new file mode 100755
index 0000000..b26f890
--- /dev/null
+++ b/.extract.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+if [[ $# -ne 1 ]]; then
+ echo "usage $0 dir" >&2
+ exit 1
+fi
+
+dir="$1"
+
+if [[ -e $dir ]]; then
+ echo "Error: $dir already exists" >&2
+ exit 1
+fi
+
+mkdir -p "$dir"
+cd "$dir"
+
+GS=/usr/lib64/gnome-shell/libgnome-shell.so
+
+for r in $(gresource list $GS); do
+ t="${r/#\/org\/gnome\/shell\/}"
+ mkdir -p $(dirname $t)
+ echo Extracting $t
+ gresource extract $GS $r >$t
+done
+
+echo
+echo "Now add the following to /etc/environment and restart gnome-shell"
+echo "if you want to run with these extracted source files."
+echo "GNOME_SHELL_JS=$PWD"
diff --git a/.gitignore b/.gitignore
index e666a92..1c0e14a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/.idea
/schemas/gschemas.compiled
+/.gnome-shell
+/bluetooth-quick-connect@bjarosze.gmail.com.shell-extension.zip \ No newline at end of file
diff --git a/Makefile b/Makefile
index b34acee..5a4054e 100644
--- a/Makefile
+++ b/Makefile
@@ -2,13 +2,20 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# (see extension.js for details)
-all:
- glib-compile-schemas --strict --targetdir=schemas schemas
-
-dist: all
- zip gnome-bluetooth-quick-connect.zip -9r *
+dist:
+ gnome-extensions pack -f --extra-source=bluetooth.js --extra-source=bluetooth_legacy.js \
+ --extra-source=power.js --extra-source=quickSettings.js --extra-source=settings.js \
+ --extra-source=Settings.ui --extra-source=ui.js --extra-source=utils.js -o .
clean:
rm -fr gnome-bluetooth-quick-connect.zip schemas/gschemas.compiled
-.PHONY: all clean dist
+install: dist
+ gnome-extensions install -f bluetooth-quick-connect@bjarosze.gmail.com.shell-extension.zip
+
+translation:
+ mkdir -p po
+ xgettext --from-code=UTF-8 extension.js quickSettings.js ui.js Settings.ui \
+ -o po/bluetooth-quick-connect.pot
+
+.PHONY: clean dist
diff --git a/README.md b/README.md
index 5a6d8cb..2a654ad 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,64 @@
-# Bluetooth quick connect
+# Bluetooth Quick Connect
This extension allows paired Bluetooth devices to be connected and
-disconnected via the GNOME system menu, without having to enter the
+disconnected via the GNOME system menu, without need to enter the
Settings app every time.
-## Installation from extensions.gnome.org
+## Installation
+
+### Requirements
+
+ * bluez (on ubuntu: `sudo apt install bluez`)
+
+### Installation from extensions.gnome.org
https://extensions.gnome.org/extension/1401/bluetooth-quick-connect/
-## Installation from source code
+### Installation from source code
```
git clone https://github.com/bjarosze/gnome-bluetooth-quick-connect
cd gnome-bluetooth-quick-connect
-make
-rm -r ~/.local/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
-cp -r gnome-bluetooth-quick-connect ~/.local/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
+make install
+```
+
+## Battery level
+
+Headset battery (currently) requires enabling experimental features in bluez.
+See https://github.com/bjarosze/gnome-bluetooth-quick-connect/pull/42 for more details.
+
+## Troubleshooting
+
+### Connecting and disconnecting does not work
+
+This extensions calls `bluetoothctl` under the hood. If something does not work
+you can try to execute `bluetoothctl` command in terminal and see what is wrong.
+
+#### Paired devices
+```bash
+bluetoothctl -- paired-devices
+```
+
+#### Connecting
+```bash
+bluetoothctl -- connect <mac address>
+```
+
+#### Disconnecting
+```bash
+bluetoothctl -- disconnect <mac address>
+```
+
+#### Reconnecting
+```bash
+bluetoothctl -- disconnect <mac> && sleep 7 && bluetoothctl -- connect <mac>
+```
+
+### Reconnecting does not work
+
+Not sure why, but sometimes bluetoothctl does not want to connect
+device after it was disconnected. Reinstalling bluez and rebooting system helped on my ubuntu.
+```
+$ sudo apt purge bluez gnome-bluetooth pulseaudio-module-bluetooth
+$ sudo apt install bluez gnome-bluetooth pulseaudio-module-bluetooth
```
diff --git a/Settings.ui b/Settings.ui
index 047b045..3743838 100644
--- a/Settings.ui
+++ b/Settings.ui
@@ -1,79 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
<!--
Copyright 2018 Bartosz Jaroszewski
SPDX-License-Identifier: GPL-2.0-or-later
(see extension.js for details)
-->
-<!-- Generated with glade 3.20.0 -->
-<interface>
- <requires lib="gtk+" version="3.12"/>
+<interface domain="bluetooth-quick-connect">
+ <requires lib="gtk+" version="3.12" />
<object class="GtkAdjustment" id="auto_power_off_interval_adjustment">
<property name="lower">1</property>
<property name="upper">3600</property>
- <property name="step_increment">1</property>
- <property name="page_increment">5</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">5</property>
</object>
<object class="GtkBox" id="auto_power_off_settings">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">24</property>
- <property name="margin_right">24</property>
- <property name="margin_top">24</property>
- <property name="margin_bottom">24</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">24</property>
+ <property name="margin-end">24</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
<property name="orientation">vertical</property>
<property name="spacing">24</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label_xalign">0</property>
- <property name="shadow_type">none</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
<child>
<object class="GtkListBox">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="selection_mode">none</property>
+ <property name="can-focus">False</property>
+ <property name="selection-mode">none</property>
<child>
<object class="GtkListBoxRow">
<property name="visible">True</property>
- <property name="can_focus">True</property>
+ <property name="can-focus">True</property>
<child>
- <object class="GtkGrid">
+ <object class="GtkBox">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">12</property>
- <property name="margin_right">12</property>
- <property name="margin_top">12</property>
- <property name="margin_bottom">12</property>
- <property name="column_spacing">32</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">Checking idle inteval (seconds)</property>
- <property name="xalign">0</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Checking idle interval (seconds)</property>
</object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
</child>
<child>
<object class="GtkSpinButton" id="auto_power_off_interval">
<property name="visible">True</property>
- <property name="can_focus">True</property>
+ <property name="can-focus">True</property>
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="text">60</property>
<property name="adjustment">auto_power_off_interval_adjustment</property>
- <property name="climb_rate">1</property>
- <property name="snap_to_ticks">True</property>
+ <property name="climb-rate">1</property>
+ <property name="snap-to-ticks">True</property>
<property name="numeric">True</property>
</object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
+
</child>
</object>
</child>
@@ -82,167 +72,380 @@ SPDX-License-Identifier: GPL-2.0-or-later
</object>
</child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
</child>
</object>
- <object class="GtkBox" id="bluetooth_quick_connect_settings">
+ <object class="GtkScrolledWindow" id="items_container">
+ <!-- <property name="width-request">450</property> -->
+ <!-- <property name="height-request">692</property> -->
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">24</property>
- <property name="margin_right">24</property>
- <property name="margin_top">24</property>
- <property name="margin_bottom">24</property>
- <property name="orientation">vertical</property>
- <property name="spacing">24</property>
+ <property name="can-focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="hscrollbar-policy">never</property>
<child>
- <object class="GtkFrame">
+ <object class="GtkViewport" id="main_prefs_vp">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label_xalign">0</property>
- <property name="shadow_type">in</property>
+ <property name="can-focus">False</property>
<child>
- <object class="GtkListBox">
+ <object class="GtkBox" id="bluetooth_quick_connect_settings">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="selection_mode">none</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">24</property>
+ <property name="margin-end">24</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">24</property>
<child>
- <object class="GtkListBoxRow">
+ <object class="GtkFrame">
<property name="visible">True</property>
- <property name="can_focus">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
<child>
- <object class="GtkGrid">
+ <object class="GtkListBox">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">12</property>
- <property name="margin_right">12</property>
- <property name="margin_top">12</property>
- <property name="margin_bottom">12</property>
- <property name="column_spacing">32</property>
+ <property name="can-focus">False</property>
+ <property name="selection-mode">none</property>
<child>
- <object class="GtkSwitch" id="auto_power_on_switch">
+ <object class="GtkListBoxRow">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="halign">end</property>
- <property name="valign">center</property>
+ <property name="can-focus">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkSwitch" id="auto_power_on_switch">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="auto_power_on_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Enable bluetooth when menu opened</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
</child>
<child>
- <object class="GtkLabel" id="auto_power_on_label">
+ <object class="GtkListBoxRow">
+ <property name="width-request">100</property>
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="label" translatable="yes">Enable bluetooth when menu opened</property>
- <property name="xalign">0</property>
+ <property name="can-focus">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkLabel" id="auto_power_off_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Disable bluetooth if idle</property>
+ <property name="use-markup">True</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="auto_power_off_settings_button">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage" id="image_window_previews_options2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">emblem-system-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="circular" />
+ </style>
+ </object>
+
+ </child>
+ <child>
+ <object class="GtkSwitch" id="auto_power_off_switch">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ </object>
+
+ </child>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
</child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkListBoxRow">
- <property name="width_request">100</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <child>
- <object class="GtkGrid">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">12</property>
- <property name="margin_right">12</property>
- <property name="margin_top">12</property>
- <property name="margin_bottom">12</property>
- <property name="column_spacing">32</property>
<child>
- <object class="GtkLabel" id="auto_power_off_label">
+ <object class="GtkListBoxRow">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="label" translatable="yes">Disable bluetooth if idle</property>
- <property name="use_markup">True</property>
- <property name="xalign">0</property>
+ <property name="can-focus">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkSwitch" id="keep_menu_on_toggle">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="keep_menu_on_toggle_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Keep the menu open after toggling the connection (restart required)</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkListBoxRow">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
+ <property name="can-focus">True</property>
<child>
- <object class="GtkButton" id="auto_power_off_settings_button">
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="xalign">0.46000000834465027</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
<child>
- <object class="GtkImage" id="image_window_previews_options2">
+ <object class="GtkSwitch" id="refresh_button_on">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon_name">emblem-system-symbolic</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="refresh_button_on_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Show reconnect button (restart required)</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
</object>
</child>
- <style>
- <class name="circular"/>
- </style>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
<child>
- <object class="GtkSwitch" id="auto_power_off_switch">
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="halign">end</property>
- <property name="valign">center</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkSwitch" id="debug_mode_on">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="debug_mode_on_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Debug mode (restart required)</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkSwitch" id="show_battery_value_on">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="show_battery_value_on_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Show battery value (restart required)</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="column-spacing">32</property>
+ <child>
+ <object class="GtkSwitch" id="show_battery_icon_on">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="show_battery_icon_on_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Show battery icon (restart required)</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
</child>
</object>
</child>
+ <child type="label_item">
+ <placeholder />
+ </child>
</object>
+
</child>
</object>
</child>
- <child type="label_item">
- <placeholder/>
- </child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
</child>
</object>
</interface>
diff --git a/bluetooth.js b/bluetooth.js
new file mode 100644
index 0000000..d9dff16
--- /dev/null
+++ b/bluetooth.js
@@ -0,0 +1,109 @@
+// Copyright 2018 Bartosz Jaroszewski
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const GnomeBluetooth = imports.gi.GnomeBluetooth;
+const Signals = imports.signals;
+const GLib = imports.gi.GLib;
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+const Utils = Me.imports.utils;
+
+var BluetoothController = class {
+ constructor() {
+ this._client = new GnomeBluetooth.Client();
+ this._deviceNotifyConnected = new Set();
+ this._store = this._client.get_devices();
+ }
+
+ enable() {
+ this._client.connect('notify::default-adapter', () => {
+ this._deviceNotifyConnected.clear();
+ this.emit('default-adapter-changed');
+ });
+ this._client.connect('notify::default-adapter-powered', () => {
+ this._deviceNotifyConnected.clear();
+ this.emit('default-adapter-changed');
+ });
+ this._client.connect('device-removed', (c, path) => {
+ this._deviceNotifyConnected.delete(path);
+ this.emit('device-deleted');
+ });
+ this._client.connect('device-added', (c, device) => {
+ this._connectDeviceNotify(device);
+ this.emit('device-inserted', new BluetoothDevice(device));
+ });
+ }
+
+ _connectDeviceNotify(device) {
+ const path = device.get_object_path();
+
+ if (this._deviceNotifyConnected.has(path))
+ return;
+
+ device.connect('notify', (device) => {
+ this.emit('device-changed', new BluetoothDevice(device));
+ });
+ }
+
+ getDevices() {
+ let devices = [];
+
+ for (let i = 0; i < this._store.get_n_items(); i++) {
+ let device = new BluetoothDevice(this._store.get_item(i));
+ devices.push(device);
+ }
+
+ return devices;
+ }
+
+ getConnectedDevices() {
+ return this.getDevices().filter((device) => {
+ return device.isConnected;
+ });
+ }
+
+ destroy() {
+ this._disconnectSignals();
+ }
+}
+
+Signals.addSignalMethods(BluetoothController.prototype);
+Utils.addSignalsHelperMethods(BluetoothController.prototype);
+
+var BluetoothDevice = class {
+ constructor(dev) {
+ this.update(dev);
+ }
+
+ update(dev) {
+ this.name = dev.alias || dev.name;
+ this.isConnected = dev.connected;
+ this.isPaired = dev.paired;
+ this.mac = dev.address;
+ }
+
+ disconnect() {
+ Utils.spawn(`bluetoothctl -- disconnect ${this.mac}`)
+ }
+
+ connect() {
+ Utils.spawn(`bluetoothctl -- connect ${this.mac}`)
+ }
+
+ reconnect() {
+ Utils.spawn(`bluetoothctl -- disconnect ${this.mac} && sleep 7 && bluetoothctl -- connect ${this.mac}`)
+ }
+}
diff --git a/bluetooth_legacy.js b/bluetooth_legacy.js
new file mode 100644
index 0000000..e266187
--- /dev/null
+++ b/bluetooth_legacy.js
@@ -0,0 +1,124 @@
+// Copyright 2018 Bartosz Jaroszewski
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const GnomeBluetooth = imports.gi.GnomeBluetooth;
+const Signals = imports.signals;
+const GLib = imports.gi.GLib;
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+const Utils = Me.imports.utils;
+
+var BluetoothController = class {
+ constructor() {
+ this._client = new GnomeBluetooth.Client();
+ this._model = this._client.get_model();
+ }
+
+ enable() {
+ this._connectSignal(this._model, 'row-changed', (arg0, arg1, iter) => {
+ if (iter) {
+ let device = this._buildDevice(iter);
+ if (device.isDefault)
+ this.emit('default-adapter-changed', device);
+ else
+ this.emit('device-changed', device);
+ }
+
+ });
+ this._connectSignal(this._model, 'row-deleted', () => {
+ this.emit('device-deleted');
+ });
+ this._connectSignal(this._model, 'row-inserted', (arg0, arg1, iter) => {
+ if (iter) {
+ let device = this._buildDevice(iter);
+ this.emit('device-inserted', device);
+ }
+ });
+ }
+
+ getDevices() {
+ let adapter = this._getDefaultAdapter();
+ if (!adapter)
+ return [];
+
+ let devices = [];
+
+ let [ret, iter] = this._model.iter_children(adapter);
+ while (ret) {
+ let device = this._buildDevice(iter);
+ devices.push(device);
+ ret = this._model.iter_next(iter);
+ }
+
+ return devices;
+ }
+
+ getConnectedDevices() {
+ return this.getDevices().filter((device) => {
+ return device.isConnected;
+ });
+ }
+
+ destroy() {
+ this._disconnectSignals();
+ }
+
+ _getDefaultAdapter() {
+ let [ret, iter] = this._model.get_iter_first();
+ while (ret) {
+ let isDefault = this._model.get_value(iter, GnomeBluetooth.Column.DEFAULT);
+ let isPowered = this._model.get_value(iter, GnomeBluetooth.Column.POWERED);
+ if (isDefault && isPowered)
+ return iter;
+ ret = this._model.iter_next(iter);
+ }
+ return null;
+ }
+
+ _buildDevice(iter) {
+ return new BluetoothDevice(this._model, iter);
+ }
+}
+
+Signals.addSignalMethods(BluetoothController.prototype);
+Utils.addSignalsHelperMethods(BluetoothController.prototype);
+
+var BluetoothDevice = class {
+ constructor(model, iter) {
+ this._model = model;
+ this.update(iter);
+ }
+
+ update(iter) {
+ this.name = this._model.get_value(iter, GnomeBluetooth.Column.ALIAS) || this._model.get_value(iter, GnomeBluetooth.Column.NAME);
+ this.isConnected = this._model.get_value(iter, GnomeBluetooth.Column.CONNECTED);
+ this.isPaired = this._model.get_value(iter, GnomeBluetooth.Column.PAIRED);
+ this.mac = this._model.get_value(iter, GnomeBluetooth.Column.ADDRESS);
+ this.isDefault = this._model.get_value(iter, GnomeBluetooth.Column.DEFAULT);
+ }
+
+ disconnect() {
+ Utils.spawn(`bluetoothctl -- disconnect ${this.mac}`)
+ }
+
+ connect() {
+ Utils.spawn(`bluetoothctl -- connect ${this.mac}`)
+ }
+
+ reconnect() {
+ Utils.spawn(`bluetoothctl -- disconnect ${this.mac} && sleep 7 && bluetoothctl -- connect ${this.mac}`)
+ }
+}
diff --git a/convenience.js b/convenience.js
deleted file mode 100644
index 1c866b2..0000000
--- a/convenience.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the GNOME nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-const Gettext = imports.gettext;
-const Gio = imports.gi.Gio;
-
-const Config = imports.misc.config;
-const ExtensionUtils = imports.misc.extensionUtils;
-
-/**
- * initTranslations:
- * @domain: (optional): the gettext domain to use
- *
- * Initialize Gettext to load translations from extensionsdir/locale.
- * If @domain is not provided, it will be taken from metadata['gettext-domain']
- */
-function initTranslations(domain) {
- let extension = ExtensionUtils.getCurrentExtension();
-
- domain = domain || extension.metadata['gettext-domain'];
-
- // check if this extension was built with "make zip-file", and thus
- // has the locale files in a subfolder
- // otherwise assume that extension has been installed in the
- // same prefix as gnome-shell
- let localeDir = extension.dir.get_child('locale');
- if (localeDir.query_exists(null))
- Gettext.bindtextdomain(domain, localeDir.get_path());
- else
- Gettext.bindtextdomain(domain, Config.LOCALEDIR);
-}
-
-/**
- * getSettings:
- * @schema: (optional): the GSettings schema id
- *
- * Builds and return a GSettings schema for @schema, using schema files
- * in extensionsdir/schemas. If @schema is not provided, it is taken from
- * metadata['settings-schema'].
- */
-function getSettings(schema) {
- let extension = ExtensionUtils.getCurrentExtension();
-
- schema = schema || extension.metadata['settings-schema'];
-
- const GioSSS = Gio.SettingsSchemaSource;
-
- // check if this extension was built with "make zip-file", and thus
- // has the schema files in a subfolder
- // otherwise assume that extension has been installed in the
- // same prefix as gnome-shell (and therefore schemas are available
- // in the standard folders)
- let schemaDir = extension.dir.get_child('schemas');
- let schemaSource;
-
- if (schemaDir.query_exists(null))
- schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
- GioSSS.get_default(),
- false);
- else
- schemaSource = GioSSS.get_default();
-
- let schemaObj = schemaSource.lookup(schema, true);
- if (!schemaObj)
- throw new Error('Schema ' + schema + ' could not be found for extension '
- + extension.metadata.uuid + '. Please check your installation.');
-
- return new Gio.Settings({ settings_schema: schemaObj });
-}
diff --git a/debian/changelog b/debian/changelog
index dd4244e..0fb7df9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,162 @@
+gnome-shell-extension-bluetooth-quick-connect (36-2) unstable; urgency=medium
+
+ * d/clean: Fix FTBFS-twice-in-a-row by deleting build result
+ (Closes: #1044877)
+ * Upload to unstable (part of transition: #1043144)
+
+ -- Simon McVittie <smcv@debian.org> Sun, 20 Aug 2023 23:06:19 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (36-1) experimental; urgency=medium
+
+ * New upstream release
+ * d/control: gnome-shell 44 is required for this version
+ (Closes: #1041566)
+
+ -- Simon McVittie <smcv@debian.org> Thu, 03 Aug 2023 18:42:54 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (33-1) unstable; urgency=medium
+
+ * New upstream release
+ - New translation: oc
+ - No code changes
+
+ -- Simon McVittie <smcv@debian.org> Fri, 24 Feb 2023 16:49:25 +0000
+
+gnome-shell-extension-bluetooth-quick-connect (32-1) unstable; urgency=medium
+
+ * New upstream release
+ - Sort devices in the menu
+ - Initial translation support: de, es, fr, nl, pl
+ * d/copyright: Update
+ * Standards-Version: 4.6.2 (no changes required)
+ * Build-depend on gnome-shell, for the gnome-extensions tool
+ * d/rules: Include all files from upstream's `make dist`
+
+ -- Simon McVittie <smcv@debian.org> Mon, 13 Feb 2023 10:08:49 +0000
+
+gnome-shell-extension-bluetooth-quick-connect (30-1) unstable; urgency=medium
+
+ * New upstream release
+ * Drop patch, applied upstream
+ * Fix attribution in previous changelog entry
+
+ -- Simon McVittie <smcv@debian.org> Mon, 26 Sep 2022 19:48:32 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (29-3) unstable; urgency=medium
+
+ * Fix "this._proxy is undefined" error with some configuration options.
+ Thanks to frosth555 and WorkingRobot on Github
+
+ -- Simon McVittie <smcv@debian.org> Sat, 24 Sep 2022 20:35:20 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (29-2) unstable; urgency=medium
+
+ * Add proposed patch to integrate with GNOME Shell 43 (Closes: #1018186)
+ * Standards-Version: 4.6.1 (no changes required)
+
+ -- Simon McVittie <smcv@debian.org> Sun, 18 Sep 2022 20:22:29 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (29-1) unstable; urgency=medium
+
+ * New upstream release
+ * Drop patches, applied upstream
+
+ -- Simon McVittie <smcv@debian.org> Sun, 17 Apr 2022 16:40:23 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (26-2) unstable; urgency=medium
+
+ * Add patches for compatibility with GNOME Shell 42 (Closes: #1008120)
+
+ -- Simon McVittie <smcv@debian.org> Fri, 01 Apr 2022 12:00:02 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (26-1) unstable; urgency=medium
+
+ * New upstream release
+ - Drop patch for compatibility with GNOME 41, applied upstream
+
+ -- Simon McVittie <smcv@debian.org> Mon, 13 Dec 2021 12:15:27 +0000
+
+gnome-shell-extension-bluetooth-quick-connect (23-2) unstable; urgency=medium
+
+ * Declare compatibility with GNOME Shell 41 (Closes: #996047)
+
+ -- Simon McVittie <smcv@debian.org> Sun, 17 Oct 2021 11:54:25 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (23-1) unstable; urgency=medium
+
+ * New upstream release
+ * Suggest using gnome-extensions-app instead of gnome-shell-extension-prefs
+ * Add missing dependency on bluez
+ * Update standards version to 4.6.0 (no changes needed)
+
+ -- Simon McVittie <smcv@debian.org> Wed, 06 Oct 2021 15:57:28 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (20-2) unstable; urgency=medium
+
+ * Release to unstable
+
+ -- Simon McVittie <smcv@debian.org> Sat, 11 Sep 2021 22:15:35 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (20-1) experimental; urgency=medium
+
+ * New upstream release for GNOME 40 (Closes: #993057)
+ * d/watch: Update for Github changes
+ * Use debhelper compat level 13
+ * Disable dh_auto_install.
+ It doesn't do the right thing for upstream's `make install`.
+
+ -- Simon McVittie <smcv@debian.org> Tue, 31 Aug 2021 10:33:02 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (16-1) unstable; urgency=medium
+
+ * New upstream release
+ - Drop patch series
+ - The code is identical to the patched versions 13-2 and 13-3
+
+ -- Simon McVittie <smcv@debian.org> Sat, 31 Oct 2020 11:32:12 +0000
+
+gnome-shell-extension-bluetooth-quick-connect (13-3) unstable; urgency=medium
+
+ * Release to unstable
+
+ -- Simon McVittie <smcv@debian.org> Sat, 26 Sep 2020 11:30:25 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (13-2) experimental; urgency=medium
+
+ * d/patches: Update to upstream v13-7-g662250e
+ - Drop compatibility with GNOME Shell < 3.37.x
+ - Be compatible with GNOME Shell >= 3.37.x
+
+ -- Simon McVittie <smcv@debian.org> Mon, 07 Sep 2020 18:07:52 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (13-1) unstable; urgency=medium
+
+ * New upstream release
+ - d/copyright: Update
+ - d/control: Adjust dependencies.
+ This version drops compatibility with GNOME Shell older than 3.36.
+ - Drop patch to declare compatibility with GNOME 3.36, no longer required
+ - Install schemas to expected location.
+ The new version is more specific about where to find them.
+
+ -- Simon McVittie <smcv@debian.org> Mon, 01 Jun 2020 11:55:44 +0100
+
+gnome-shell-extension-bluetooth-quick-connect (10-3) unstable; urgency=medium
+
+ * Bump maximum GNOME Shell version to 3.36
+ * Declare GNOME Shell 3.34, 3.36 compatibility in metadata
+ * Standards-Version: 4.5.0 (no changes required)
+ * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository,
+ Repository-Browse.
+
+ -- Simon McVittie <smcv@debian.org> Tue, 24 Mar 2020 20:06:38 +0000
+
+gnome-shell-extension-bluetooth-quick-connect (10-2) unstable; urgency=medium
+
+ * Expand supported version range to include GNOME Shell 3.34
+
+ -- Simon McVittie <smcv@debian.org> Wed, 11 Sep 2019 12:06:27 +0100
+
gnome-shell-extension-bluetooth-quick-connect (10-1) unstable; urgency=medium
* New upstream release
diff --git a/debian/clean b/debian/clean
new file mode 100644
index 0000000..c4c4ffc
--- /dev/null
+++ b/debian/clean
@@ -0,0 +1 @@
+*.zip
diff --git a/debian/control b/debian/control
index 537c312..bc06615 100644
--- a/debian/control
+++ b/debian/control
@@ -4,10 +4,13 @@ Priority: optional
Maintainer: Simon McVittie <smcv@debian.org>
Uploaders: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
Build-Depends:
- debhelper-compat (= 12),
+ debhelper-compat (= 13),
gettext,
+ gnome-shell (>= 44~),
+ gnome-shell (<< 45~),
libglib2.0-bin,
-Standards-Version: 4.4.0
+ unzip,
+Standards-Version: 4.6.2
Homepage: https://github.com/bjarosze/gnome-bluetooth-quick-connect
Vcs-Git: https://salsa.debian.org/gnome-team/shell-extensions/gnome-shell-extension-bluetooth-quick-connect.git
Vcs-Browser: https://salsa.debian.org/gnome-team/shell-extensions/gnome-shell-extension-bluetooth-quick-connect
@@ -16,15 +19,17 @@ Rules-Requires-Root: no
Package: gnome-shell-extension-bluetooth-quick-connect
Architecture: all
Depends:
- gnome-shell (<< 3.33),
- gnome-shell (>= 3.28),
+ bluez,
+ gnome-shell (<< 45~),
+ gnome-shell (>= 44~),
${misc:Depends},
Recommends:
- gnome-tweaks,
+ gnome-shell-extension-prefs,
Description: GNOME Shell extension to connect paired Bluetooth devices
This GNOME Shell extension adds entries to the shell's System menu
to provide a quick way to connect and disconnect Bluetooth devices that
were previously paired with the computer.
.
Please note that each user will need to enable the extension manually, for
- example using the gnome-tweaks application.
+ example using gnome-extensions-app, which is part of the
+ gnome-shell-extension-prefs package.
diff --git a/debian/copyright b/debian/copyright
index c92cff3..f8c1e9b 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,19 +4,18 @@ Source: https://github.com/bjarosze/gnome-bluetooth-quick-connect
Files: *
Copyright:
- © 2018 Bartosz Jaroszewski
+ © 2018-2023 Bartosz Jaroszewski
+ © 2010-2022 GNOME Shell contributors
+ © 2022 Simon McVittie
+ © 2023 Óscar Fernández Díaz
+ © 2023 Heimen Stoffels
+ © 2023 Robin Grenet
License: GPL-2+
-Files:
- convenience.js
-Copyright:
- © 2011-2012 Giovanni Campagna
-License: BSD-3-clause
-
Files: debian/*
Copyright:
© 2014-2015 Tobias Frost
- © 2016-2019 Simon McVittie
+ © 2016-2023 Simon McVittie
License: GPL-2+
License: GPL-2+
@@ -32,26 +31,3 @@ License: GPL-2+
Comment:
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
-
-License: BSD-3-clause
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the GNOME nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
- .
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/install b/debian/install
index 4f2d4c2..151c5ef 100644
--- a/debian/install
+++ b/debian/install
@@ -1,4 +1 @@
-*.js usr/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
-*.json usr/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
-*.ui usr/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
-schemas/*.xml usr/share/glib-2.0/schemas
+usr/share/gnome-shell/extensions/bluetooth-quick-connect@bjarosze.gmail.com
diff --git a/debian/rules b/debian/rules
index 3c746ff..90c8f49 100755
--- a/debian/rules
+++ b/debian/rules
@@ -3,5 +3,12 @@
include /usr/share/dpkg/default.mk
+extension_id=bluetooth-quick-connect@bjarosze.gmail.com
+install_dir=$(CURDIR)/debian/tmp/usr/share/gnome-shell/extensions/$(extension_id)
+
%:
dh $@
+
+override_dh_auto_install:
+ install -d $(install_dir)
+ unzip -d $(install_dir) $(extension_id).shell-extension.zip
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
new file mode 100644
index 0000000..53ad048
--- /dev/null
+++ b/debian/upstream/metadata
@@ -0,0 +1,4 @@
+Bug-Database: https://github.com/bjarosze/gnome-bluetooth-quick-connect/issues
+Bug-Submit: https://github.com/bjarosze/gnome-bluetooth-quick-connect/issues/new
+Repository: https://github.com/bjarosze/gnome-bluetooth-quick-connect.git
+Repository-Browse: https://github.com/bjarosze/gnome-bluetooth-quick-connect
diff --git a/debian/watch b/debian/watch
index f64ea64..0382f1e 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,3 +1,3 @@
version=4
opts=filenamemangle=s/.+\/v?@ANY_VERSION@(@ARCHIVE_EXT@)/@PACKAGE@-$1$2/,dversionmangle=s/\+git[0-9.]+$// \
-https://github.com/bjarosze/gnome-bluetooth-quick-connect/tags .*/archive/v?@ANY_VERSION@@ARCHIVE_EXT@
+https://github.com/bjarosze/gnome-bluetooth-quick-connect/tags .*/archive/refs/tags/v?@ANY_VERSION@@ARCHIVE_EXT@
diff --git a/extension.js b/extension.js
index 403d956..7935288 100644
--- a/extension.js
+++ b/extension.js
@@ -15,227 +15,248 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Main = imports.ui.main;
-const GnomeBluetooth = imports.gi.GnomeBluetooth;
-const PopupMenu = imports.ui.popupMenu;
-const Util = imports.misc.util;
const GLib = imports.gi.GLib;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
-const Convenience = Me.imports.convenience;
-
-class BluetoothDevice {
- constructor(model, device) {
- this._name = model.get_value(device, GnomeBluetooth.Column.NAME);
- this._isConnected = model.get_value(device, GnomeBluetooth.Column.CONNECTED);
- this._isPaired = model.get_value(device, GnomeBluetooth.Column.PAIRED);
- this._mac = model.get_value(device, GnomeBluetooth.Column.ADDRESS);
- }
-
- get name() {
- return this._name;
- }
+const UiExtension = Me.imports.ui;
- get isConnected() {
- return this._isConnected;
- }
+const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
+const _ = Gettext.gettext;
- get isPaired() {
- return this._isPaired;
- }
+const Bluetooth = imports.gi.GnomeBluetooth.Client.prototype.get_devices === undefined ?
+ Me.imports.bluetooth_legacy :
+ Me.imports.bluetooth;
- get mac() {
- return this._mac;
- }
+const Utils = Me.imports.utils;
+const Settings = Me.imports.settings.Settings;
+const BatteryProvider = Me.imports.power.UPowerBatteryProvider;
- get item() {
- if (!this._item)
- this._buildMenuItem();
-
- return this._item;
- }
+const PopupMenu = imports.ui.popupMenu;
- _buildMenuItem() {
- this._item = new PopupMenu.PopupSwitchMenuItem(this.name, this.isConnected);
- this._item.isDeviceSwitcher = true;
- this._item.connect('toggled', (item, state) => {
- if (state)
- this._connect();
- else
- this._disconnect();
- });
- }
- _disconnect() {
- this._call_bluetoothctl(`disconnect ${this.mac}`)
- }
+class BluetoothQuickConnect {
+ constructor(quickSettings, bluetooth, settings) {
+ this._logger = new Utils.Logger(settings);
+ this._logger.info('Initializing extension');
+ if (quickSettings) {
+ let btIndicator = quickSettings._bluetooth;
+ let bluetoothToggle = btIndicator.quickSettingsItems[0];
+ bluetoothToggle._updateDeviceVisibility = () => {
+ bluetoothToggle._deviceSection.actor.visible = false;
+ bluetoothToggle._placeholderItem.actor.visible = false;
+ }
+ bluetoothToggle._updateDeviceVisibility();
- _connect() {
- this._call_bluetoothctl(`connect ${this.mac}`)
- }
+ this._proxy = bluetoothToggle._client._proxy;
+ this._menu = new PopupMenu.PopupMenuSection();
- _call_bluetoothctl(command) {
- let btctl_command = `echo -e "${command}\\n" | bluetoothctl`;
- Util.spawn(['/usr/bin/env', 'bash', '-c', btctl_command]);
- }
-}
-
-class BluetoothQuickConnect {
- constructor(bluetooth, settings) {
- this._menu = bluetooth._item.menu;
- this._proxy = bluetooth._proxy;
+ bluetoothToggle.menu.addMenuItem(this._menu, 0);
+ } else {
+ this._menu = bluetooth._item.menu;
+ this._proxy = bluetooth._proxy;
+ }
+ this._controller = new Bluetooth.BluetoothController();
this._settings = settings;
+ this._battery_provider = new BatteryProvider(this._logger);
- this._signals = [];
+ this._items = {};
}
enable() {
- this._loadBluetoothModel();
+ this._logger.info('Enabling extension');
+ this._controller.enable();
+ this._refresh();
+ this._connectControllerSignals();
+ this._connectIdleMonitor();
+ this._connectMenuSignals();
+ }
+
+ _connectMenuSignals() {
this._connectSignal(this._menu, 'open-state-changed', (menu, isOpen) => {
- if (isOpen && this._autoPowerOnEnabled())
+ this._logger.info(`Menu toggled: ${isOpen}`);
+ if (isOpen)
+ this._disconnectIdleMonitor();
+ else
+ this._connectIdleMonitor();
+
+ if (isOpen && this._settings.isAutoPowerOnEnabled() && this._proxy.BluetoothAirplaneMode) {
+ this._logger.info('Disabling airplane mode');
this._proxy.BluetoothAirplaneMode = false;
+ }
});
-
- this._connectSignal(this._model, 'row-changed', () => this._sync());
- this._connectSignal(this._model, 'row-deleted', () => this._sync());
- this._connectSignal(this._model, 'row-inserted', () => this._sync());
-
- this._idleMonitor();
- if (!this._proxy.BluetoothAirplaneMode) {
- this._sync();
- }
}
disable() {
+ this._logger.info('Disabling extension');
this._destroy();
}
test() {
try {
+ this._logger.info('Testing bluetoothctl');
GLib.spawn_command_line_sync("bluetoothctl --version");
- } catch(error) {
- Main.notifyError(`Bluetooth quick connect: error trying to execute "bluetoothctl"`);
+ this._logger.info('Test succeeded');
+ } catch (error) {
+ Main.notifyError(_("Bluetooth Quick Connect"), _("Error trying to execute \"bluetoothctl\""));
+ this._logger.info('Test failed');
}
}
- _idleMonitor() {
- this._idleMonitorId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._autoPowerOffCheckingInterval() * 1000, () => {
- if (this._autoPowerOffEnabled() && this._getConnectedDevices().length === 0)
- this._proxy.BluetoothAirplaneMode = true;
+ _connectControllerSignals() {
+ this._logger.info('Connecting bluetooth controller signals');
- return true;
+ this._connectSignal(this._controller, 'default-adapter-changed', (ctrl) => {
+ this._logger.info('Default adapter changed event');
+ this._refresh();
});
- }
- _connectSignal(subject, signal_name, method) {
- let signal_id = subject.connect(signal_name, method);
- this._signals.push({
- subject: subject,
- signal_id: signal_id
+ this._connectSignal(this._controller, 'device-inserted', (ctrl, device) => {
+ this._logger.info(`Device inserted event: ${device.name}`);
+ if (device.isPaired) {
+ this._addMenuItem(device);
+ } else {
+ this._logger.info(`Device ${device.name} not paired, ignoring`);
+ }
+ });
+
+ this._connectSignal(this._controller, 'device-changed', (ctrl, device) => {
+ this._logger.info(`Device changed event: ${device.name}`);
+ if (device.isPaired)
+ this._syncMenuItem(device);
+ else
+ this._logger.info(`Skipping change event for unpaired device ${device.name}`);
+ });
+
+ this._connectSignal(this._controller, 'device-deleted', () => {
+ this._logger.info(`Device deleted event`);
+ this._refresh();
});
}
- _loadBluetoothModel() {
- this._client = new GnomeBluetooth.Client();
- this._model = this._client.get_model();
+ _syncMenuItem(device) {
+ this._logger.info(`Synchronizing device menu item: ${device.name}`);
+ let item = this._items[device.mac] || this._addMenuItem(device);
+
+ item.sync(device);
}
- _getDefaultAdapter() {
- let [ret, iter] = this._model.get_iter_first();
- while (ret) {
- let isDefault = this._model.get_value(iter, GnomeBluetooth.Column.DEFAULT);
- let isPowered = this._model.get_value(iter, GnomeBluetooth.Column.POWERED);
- if (isDefault && isPowered)
- return iter;
- ret = this._model.iter_next(iter);
- }
- return null;
+ _addMenuItem(device) {
+ this._logger.info(`Adding device menu item: ${device.name} ${device.mac}`);
+
+ let menuItem = new UiExtension.PopupBluetoothDeviceMenuItem(
+ device,
+ this._battery_provider,
+ this._logger,
+ {
+ showRefreshButton: this._settings.isShowRefreshButtonEnabled(),
+ closeMenuOnAction: !this._settings.isKeepMenuOnToggleEnabled(),
+ showBatteryValue: this._settings.isShowBatteryValueEnabled(),
+ showBatteryIcon: this._settings.isShowBatteryIconEnabled()
+ }
+ );
+
+ this._items[device.mac] = menuItem;
+ this._menu.addMenuItem(menuItem);
+
+ return menuItem;
}
- _getDevices() {
- let adapter = this._getDefaultAdapter();
- if (!adapter)
- return [];
+ _connectIdleMonitor() {
+ if (this._idleMonitorId) return;
- let devices = [];
+ this._logger.info('Connecting idle monitor');
- let [ret, iter] = this._model.iter_children(adapter);
- while (ret) {
- devices.push(new BluetoothDevice(this._model, iter));
- ret = this._model.iter_next(iter);
- }
+ this._idleMonitorId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._settings.autoPowerOffCheckingInterval() * 1000, () => {
+ if (this._settings.isAutoPowerOffEnabled() && this._controller.getConnectedDevices().length === 0)
+ this._proxy.BluetoothAirplaneMode = true;
- return devices;
+ return true;
+ });
}
- _getPairedDevices() {
- return this._getDevices().filter((device) => {
- return device.isPaired || device.isConnected;
- });
+ _disconnectIdleMonitor() {
+ if (!this._idleMonitorId) return;
+
+ this._logger.info('Disconnecting idle monitor');
+
+ GLib.Source.remove(this._idleMonitorId);
+ this._idleMonitorId = null;
}
- _getConnectedDevices() {
- return this._getDevices().filter((device) => {
- return device.isConnected;
+ _connectSignal(subject, signal_name, method) {
+ let signal_id = subject.connect(signal_name, method);
+ this._signals.push({
+ subject: subject,
+ signal_id: signal_id
});
}
- _sync() {
+ _refresh() {
this._removeDevicesFromMenu();
this._addDevicesToMenu();
+
+ this._logger.info('Refreshing devices list');
}
_addDevicesToMenu() {
- this._getPairedDevices().forEach((device) => {
- this._menu.addMenuItem(device.item, 1);
+ this._controller.getDevices().sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ }).forEach((device) => {
+ if (device.isPaired) {
+ let item = this._addMenuItem(device);
+ } else {
+ this._logger.info(`skipping adding device ${device.name}`);
+ }
});
}
_removeDevicesFromMenu() {
- this._menu._getMenuItems().forEach((item) => {
- if (item.isDeviceSwitcher) {
- item.destroy();
- }
+ Object.values(this._items).forEach((item) => {
+ item.disconnectSignals();
+ item.destroy();
});
+
+ this._items = {};
}
_destroy() {
- this._signals.forEach((signal) => {
- signal.subject.disconnect(signal.signal_id);
- });
- this._signals = [];
+ this._disconnectSignals();
this._removeDevicesFromMenu();
-
- if (this._idleMonitorId)
- GLib.Source.remove(this._idleMonitorId);
- }
-
- _autoPowerOnEnabled() {
- return this._settings.get_boolean('bluetooth-auto-power-on');
- }
-
- _autoPowerOffEnabled() {
- return this._settings.get_boolean('bluetooth-auto-power-off');
- }
-
- _autoPowerOffCheckingInterval() {
- return this._settings.get_int('bluetooth-auto-power-off-interval');
+ this._disconnectIdleMonitor();
+ if (this._controller)
+ this._controller.destroy();
}
}
+Utils.addSignalsHelperMethods(BluetoothQuickConnect.prototype);
+
let bluetoothQuickConnect = null;
function init() {
- let bluetooth = Main.panel.statusArea.aggregateMenu._bluetooth;
- let settings = Convenience.getSettings();
- bluetoothQuickConnect = new BluetoothQuickConnect(bluetooth, settings);
+ ExtensionUtils.initTranslations(Me.metadata['gettext-domain']);
}
function enable() {
+ if (Main.panel.statusArea.quickSettings) {
+ bluetoothQuickConnect = new BluetoothQuickConnect(
+ Main.panel.statusArea.quickSettings,
+ null,
+ new Settings()
+ );
+ } else {
+ bluetoothQuickConnect = new BluetoothQuickConnect(
+ null,
+ Main.panel.statusArea.aggregateMenu._bluetooth,
+ new Settings()
+ );
+ }
+
bluetoothQuickConnect.test();
bluetoothQuickConnect.enable();
}
function disable() {
bluetoothQuickConnect.disable();
+ bluetoothQuickConnect = null;
}
diff --git a/metadata.json b/metadata.json
index dea519a..70b3688 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,14 +1,11 @@
{
- "name": "Bluetooth quick connect",
+ "name": "Bluetooth Quick Connect",
"description": "Allow to connect to paired devices from gnome control panel.\n",
"uuid": "bluetooth-quick-connect@bjarosze.gmail.com",
"url": "https://github.com/bjarosze/gnome-bluetooth-quick-connect",
"settings-schema": "org.gnome.shell.extensions.bluetooth-quick-connect",
"gettext-domain": "bluetooth-quick-connect",
"shell-version": [
- "3.26.2",
- "3.28",
- "3.30",
- "3.32"
+ "44"
]
}
diff --git a/po/bluetooth-quick-connect.pot b/po/bluetooth-quick-connect.pot
new file mode 100644
index 0000000..6c31ed6
--- /dev/null
+++ b/po/bluetooth-quick-connect.pot
@@ -0,0 +1,66 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr ""
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr ""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr ""
+
+#: ui.js:197
+msgid "Wait"
+msgstr ""
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr ""
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr ""
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr ""
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr ""
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr ""
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr ""
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr ""
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr ""
diff --git a/po/cs.po b/po/cs.po
new file mode 100644
index 0000000..e537edf
--- /dev/null
+++ b/po/cs.po
@@ -0,0 +1,67 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR Amerey.eu <info@amerey.eu>, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-02-28 14:39+0100\n"
+"Last-Translator: Amerey.eu <info@amerey.eu>\n"
+"Language-Team: \n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Rychlé připojení Bluetooth"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Chyba při pokusu o spuštění \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Nastavení Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Počkejte"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Kontrola intervalu nečinnosti (v sekundách)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Při otevření nabídky povolit bluetooth"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Při nečinnosti deaktivovat bluetooth"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Po přepnutí připojení nechte nabídku otevřenou (vyžaduje restart)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Zobrazit tlačítko pro opětovné připojení (vyžaduje restart)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Režim ladění (vyžaduje restart)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Zobrazit hodnotu baterie (vyžaduje restart)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Zobrazit ikonu baterie (vyžaduje restart)"
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..b4aa518
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,66 @@
+# German translation for Bluetooth Quick Connect.
+# Copyright (C) 2023 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# <j.r@jugendhacker.de>, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-02-05 13:18+0100\n"
+"Last-Translator: <j.r@jugendhacker.de>\n"
+"Language-Team: \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth Quick Connect"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Fehler beim Ausführen von \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Bluetooth Einstellungen"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Warte"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Intervall für die Inaktivitätsprüfung (Sekunden)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Bluetooth beim Öffnen des Menüs aktivieren"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Bluetooth bei Inaktivität deaktivieren"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Das Menü beim Umschalten einer Verbindung offen halten (Neustart benötigt)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Knopf zum Neuverbinden anzeigen (Neustart benötigt)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Debug Modus (Neustart benötigt)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Batteriestand anzeigen (Neustart benötigt)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Batteriesymbol anziegen (Neustart benötigt)"
diff --git a/po/el.po b/po/el.po
new file mode 100644
index 0000000..b0d3586
--- /dev/null
+++ b/po/el.po
@@ -0,0 +1,67 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-03-23 15:46+0200\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Γρήγορη σύνδεση Bluetooth"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Σφάλμα κατά την προσπάθεια εκτέλεσης του \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Ρυθμίσεις Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Αναμονή"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Έλεγχος διαστήματος αδράνειας (δευτερόλεπτα)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Ενεργοποίηση bluetooth όταν ανοίγει το μενού"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Απενεργοποίηση του bluetooth σε αδράνεια"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Διατηρήστε το μενού ανοιχτό μετά την εναλλαγή της σύνδεσης (απαιτείται επανεκκίνηση)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Εμφάνιση κουμπιού επανασύνδεσης (απαιτείται επανεκκίνηση)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Λειτουργία εντοπισμού σφαλμάτων (απαιτείται επανεκκίνηση)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Εμφάνιση τιμής μπαταρίας (απαιτείται επανεκκίνηση)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Εμφάνιση εικονιδίου μπαταρίας (απαιτείται επανεκκίνηση)"
diff --git a/po/es.po b/po/es.po
new file mode 100644
index 0000000..953944f
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,69 @@
+# Spanish translation for Bluetooth Quick Connect.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Óscar Fernández Díaz <42654671+oscfdezdz@users.noreply.github.com>, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:25+0100\n"
+"PO-Revision-Date: 2023-01-26 17:28+0100\n"
+"Last-Translator: Óscar Fernández Díaz <42654671+oscfdezdz@users.noreply."
+"github.com>\n"
+"Language-Team: \n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth Quick Connect"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Error al intentar ejecutar \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Configuración de Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Espere"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Comprobar intervalo de inactividad (segundos)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Activar Bluetooth al abrir el menú"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Desactivar Bluetooth si está inactivo"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr ""
+"Mantener el menú abierto después de cambiar la conexión (requiere reinicio)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Mostrar botón de reconexión (requiere reinicio)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Modo depuración (requiere reinicio)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Mostrar valor de la batería (requiere reinicio)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Mostrar icono de batería (requiere reinicio)"
diff --git a/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..ce746fe
--- /dev/null
+++ b/po/fr.po
@@ -0,0 +1,67 @@
+# French translation for Bluetooth Quick Connect.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Robin Grenet <robin.grenet@wanadoo.fr>, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-02-02 15:20+0100\n"
+"Last-Translator: Robin Grenet <robin.grenet@wanadoo.fr>\n"
+"Language-Team: \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth Quick Connect"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Erreur lors de l'exécution de \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Paramètres Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Patientez"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Intervalle de vérification de l'inactivité (secondes)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Activer le Bluetooth à l'ouverture du menu"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Désactiver le Bluetooth en cas d'inactivité"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Garder le menu ouvert après la connexion/déconnexion (redémarrage requis)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Afficher le bouton de reconnexion (redémarrage requis)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Mode débogage (redémarrage requis)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Afficher le pourcentage de batterie (redémarrage requis)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Afficher l'icône de niveau de batterie (redémarrage requis)"
diff --git a/po/nl.po b/po/nl.po
new file mode 100644
index 0000000..28bef51
--- /dev/null
+++ b/po/nl.po
@@ -0,0 +1,66 @@
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Heimen Stoffels <vistausss@fastmail.com>, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-02-01 19:21+0100\n"
+"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
+"Language-Team: Dutch\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 22.12.1\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth - snel verbinden"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "‘bluetoothctl’ kan niet worden uitgevoerd"
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Bluetoothvoorkeuren"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Wachten"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Controle tijdens inactiviteit (in seconden)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Bluetooth inschakelen na openen van menu"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Bluetooth uitschakelen tijdens inactiviteit"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Menu openhouden na in-/uitschakelen (herstart vereist)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Knop ‘Opnieuw verbinden’ tonen (herstart vereist)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Foutopsporingsmodus (herstart vereist)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Accupercentage tonen (herstart vereist)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Accupictogram tonen (herstart vereist)"
diff --git a/po/oc.po b/po/oc.po
new file mode 100644
index 0000000..77fcb9e
--- /dev/null
+++ b/po/oc.po
@@ -0,0 +1,66 @@
+# Occitan locale file.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Quentin PAGÈS, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:28+0100\n"
+"PO-Revision-Date: 2023-02-10 09:35+0100\n"
+"Last-Translator: Quentin PAGÈS\n"
+"Language-Team: \n"
+"Language: oc\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth Quick Connect"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Error en ensajant d’executar « bluetoothctl »"
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Paramètres Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Esperatz"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Interval de verificacion d’inactivitat (en segondas)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Activar lo bluetooth quand lo menú es dobèrt"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Desactivar lo bluetooth en inactivitat"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr "Gardar lo menú dobèrt aprèp aver basculat la connexion (reaviada requerida)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Afichar lo boton de reconnexion (reaviada requerida)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Mòde de desbugatge (reaviada requerida)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Afichar la valor de la batariá (reaviada requerida)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Afichar l’icòna de la batariá (reaviada requerida)"
diff --git a/po/pl.po b/po/pl.po
new file mode 100644
index 0000000..8010db3
--- /dev/null
+++ b/po/pl.po
@@ -0,0 +1,68 @@
+# Spanish translation for Bluetooth Quick Connect.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Bartosz Jaroszewski, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-26 17:25+0100\n"
+"PO-Revision-Date: 2023-01-26 17:28+0100\n"
+"Last-Translator: Bartosz Jaroszewski\n"
+"Language-Team: \n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: extension.js:97
+msgid "Bluetooth Quick Connect"
+msgstr "Bluetooth Quick Connect"
+
+#: extension.js:97
+msgid "Error trying to execute \"bluetoothctl\""
+msgstr "Błąd wywołania \"bluetoothctl\""
+
+#: quickSettings.js:43
+msgid "Bluetooth Settings"
+msgstr "Ustawienia Bluetooth"
+
+#: ui.js:197
+msgid "Wait"
+msgstr "Czekaj"
+
+#: Settings.ui:51
+msgid "Checking idle interval (seconds)"
+msgstr "Sprawdzanie bezczynności co (sekundy)"
+
+#: Settings.ui:140
+msgid "Enable bluetooth when menu opened"
+msgstr "Rozwinięcie listy włącza bluetooth jeśli wyłączony"
+
+#: Settings.ui:172
+msgid "Disable bluetooth if idle"
+msgstr "Wyłącz bluetooth jeśli bezczynny"
+
+#: Settings.ui:255
+msgid "Keep the menu open after toggling the connection (restart required)"
+msgstr ""
+"Nie zamykaj listy po połączeniu/rozłączeniu urządzenia (wymagany restart)"
+
+#: Settings.ui:298
+msgid "Show reconnect button (restart required)"
+msgstr "Pokaż guzik ponownego połączenia urządzenia (wymagany restart)"
+
+#: Settings.ui:341
+msgid "Debug mode (restart required)"
+msgstr "Tryb debugowania (wymagany restart)"
+
+#: Settings.ui:384
+msgid "Show battery value (restart required)"
+msgstr "Pokaż procentowy poziom baterii (wymagany restart)"
+
+#: Settings.ui:427
+msgid "Show battery icon (restart required)"
+msgstr "Pokaż ikonę poziomu baterii (wymagany restart)"
diff --git a/power.js b/power.js
new file mode 100644
index 0000000..e15e24b
--- /dev/null
+++ b/power.js
@@ -0,0 +1,37 @@
+const ExtensionUtils = imports.misc.extensionUtils;
+const UPower = imports.gi.UPowerGlib;
+const Me = ExtensionUtils.getCurrentExtension();
+const Utils = Me.imports.utils;
+
+var UPowerBatteryProvider = class {
+ constructor(logger) {
+ this._upower_client = UPower.Client.new();
+ this._logger = logger;
+ }
+
+ locateBatteryDevice(device) {
+ // upower has no field in the devices that indicate that a battery is
+ // from a bluetooth device, so we must try and find by the provided mac.
+ // Problem is, the native_path field has macs in all kinds of forms ...
+ let _mac_addrs = [
+ device.mac.toUpperCase(),
+ device.mac.replace(/:/g, "_").toUpperCase(),
+ ];
+
+ let _battery_devices = this._upower_client.get_devices();
+ let _bateries = _battery_devices.filter(bat => {
+ let _native_path = bat.native_path.toUpperCase();
+ return _mac_addrs.some(mac => _native_path.includes(mac));
+ });
+
+ if (_bateries.length > 1) {
+ this._logger.warn(`device ${device.name} matched more than one UPower device by native_path`);
+ _bateries = [];
+ }
+
+ let _battery_native_path = _bateries.map(bat => bat.native_path)[0] || "NOT-FOUND";
+ this._logger.info(`battery: ${_battery_native_path}`);
+
+ return _bateries;
+ }
+}
diff --git a/prefs.js b/prefs.js
index 2a94e8d..2b567cb 100644
--- a/prefs.js
+++ b/prefs.js
@@ -5,48 +5,42 @@
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
-const Gettext = imports.gettext.domain('gnome-shell-extensions');
-const _ = Gettext.gettext;
-
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
-const Convenience = Me.imports.convenience;
+const Settings = Me.imports.settings.Settings;
+const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
+const _ = Gettext.gettext;
class SettingsBuilder {
-
constructor() {
- this._settings = new Convenience.getSettings();
+ this._settings = new Settings().settings;
this._builder = new Gtk.Builder();
}
build() {
this._builder.add_from_file(Me.path + '/Settings.ui');
- this._settingsBox = this._builder.get_object('bluetooth_quick_connect_settings');
-
-
- this._viewport = new Gtk.Viewport();
- this._viewport.add(this._settingsBox);
- this._widget = new Gtk.ScrolledWindow();
- this._widget.add(this._viewport);
+ this._widget = this._builder.get_object('items_container')
this._builder.get_object('auto_power_off_settings_button').connect('clicked', () => {
- let dialog = new Gtk.Dialog({ title: 'Auto power off settings',
- transient_for: this._widget.get_toplevel(),
+ let dialog = new Gtk.Dialog({
+ title: 'Auto power off settings',
+ transient_for: this._widget.get_ancestor(Gtk.Window),
use_header_bar: true,
- modal: true });
+ modal: true
+ });
let box = this._builder.get_object('auto_power_off_settings');
- dialog.get_content_area().add(box);
+ dialog.get_content_area().append(box);
dialog.connect('response', (dialog) => {
dialog.get_content_area().remove(box);
dialog.destroy();
});
- dialog.show_all();
+ dialog.show();
});
@@ -64,18 +58,32 @@ class SettingsBuilder {
let autoPowerOffInterval = this._builder.get_object('auto_power_off_interval');
this._settings.bind('bluetooth-auto-power-off-interval', autoPowerOffInterval, 'value', Gio.SettingsBindFlags.DEFAULT);
+
+ let keepMenuOnToggleSwitch = this._builder.get_object('keep_menu_on_toggle');
+ this._settings.bind('keep-menu-on-toggle', keepMenuOnToggleSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+ let refreshButtonOnSwitch = this._builder.get_object('refresh_button_on');
+ this._settings.bind('refresh-button-on', refreshButtonOnSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+ let debugModeOnSwitch = this._builder.get_object('debug_mode_on');
+ this._settings.bind('debug-mode-on', debugModeOnSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+ let batteryValueOnSwitch = this._builder.get_object('show_battery_value_on');
+ this._settings.bind('show-battery-value-on', batteryValueOnSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+ let batteryIconOnSwitch = this._builder.get_object('show_battery_icon_on');
+ this._settings.bind('show-battery-icon-on', batteryIconOnSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
}
}
function init() {
- // Convenience.initTranslations();
+ ExtensionUtils.initTranslations(Me.metadata['gettext-domain']);
}
function buildPrefsWidget() {
let settings = new SettingsBuilder();
let widget = settings.build();
- widget.show_all();
return widget;
}
diff --git a/schemas/org.gnome.shell.extensions.bluetooth-quick-connect.gschema.xml b/schemas/org.gnome.shell.extensions.bluetooth-quick-connect.gschema.xml
index 684af11..8640537 100644
--- a/schemas/org.gnome.shell.extensions.bluetooth-quick-connect.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.bluetooth-quick-connect.gschema.xml
@@ -18,5 +18,25 @@ SPDX-License-Identifier: GPL-2.0-or-later
<default>60</default>
<summary>Checking interval for power off idling bluetooth</summary>
</key>
+ <key name="keep-menu-on-toggle" type="b">
+ <default>false</default>
+ <summary>Keep the menu open after toggling the connection</summary>
+ </key>
+ <key name="refresh-button-on" type="b">
+ <default>false</default>
+ <summary>Show reconnect button</summary>
+ </key>
+ <key name="debug-mode-on" type="b">
+ <default>false</default>
+ <summary>Debug mode</summary>
+ </key>
+ <key name="show-battery-value-on" type="b">
+ <default>false</default>
+ <summary>Show battery level as value</summary>
+ </key>
+ <key name="show-battery-icon-on" type="b">
+ <default>true</default>
+ <summary>Show battery level as icon</summary>
+ </key>
</schema>
</schemalist>
diff --git a/settings.js b/settings.js
new file mode 100644
index 0000000..d77d98e
--- /dev/null
+++ b/settings.js
@@ -0,0 +1,72 @@
+// Copyright 2018 Bartosz Jaroszewski
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Gio = imports.gi.Gio;
+const GioSSS = Gio.SettingsSchemaSource;
+
+var Settings = class Settings {
+ constructor() {
+ this.settings = this._loadSettings();
+ }
+
+ isAutoPowerOnEnabled() {
+ return this.settings.get_boolean('bluetooth-auto-power-on');
+ }
+
+ isAutoPowerOffEnabled() {
+ return this.settings.get_boolean('bluetooth-auto-power-off');
+ }
+
+ autoPowerOffCheckingInterval() {
+ return this.settings.get_int('bluetooth-auto-power-off-interval');
+ }
+
+ isKeepMenuOnToggleEnabled() {
+ return this.settings.get_boolean('keep-menu-on-toggle');
+ }
+
+ isShowRefreshButtonEnabled() {
+ return this.settings.get_boolean('refresh-button-on');
+ }
+
+ isDebugModeEnabled() {
+ return this.settings.get_boolean('debug-mode-on');
+ }
+
+ isShowBatteryValueEnabled() {
+ return this.settings.get_boolean('show-battery-value-on');
+ }
+
+ isShowBatteryIconEnabled() {
+ return this.settings.get_boolean('show-battery-icon-on');
+ }
+
+ _loadSettings() {
+ let extension = ExtensionUtils.getCurrentExtension();
+ let schema = extension.metadata['settings-schema'];
+
+ let schemaSource = GioSSS.new_from_directory(
+ extension.dir.get_child('schemas').get_path(),
+ GioSSS.get_default(),
+ false
+ );
+
+ let schemaObj = schemaSource.lookup(schema, true);
+
+ return new Gio.Settings({ settings_schema: schemaObj });
+ }
+}; \ No newline at end of file
diff --git a/ui.js b/ui.js
new file mode 100644
index 0000000..65ca5e9
--- /dev/null
+++ b/ui.js
@@ -0,0 +1,298 @@
+// Copyright 2018 Bartosz Jaroszewski
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+
+const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
+const _ = Gettext.gettext;
+
+const Clutter = imports.gi.Clutter;
+const GObject = imports.gi.GObject;
+const St = imports.gi.St;
+const GLib = imports.gi.GLib;
+const PopupMenu = imports.ui.popupMenu;
+
+var PopupBluetoothDeviceMenuItem = GObject.registerClass(
+ class PopupSwitchWithButtonMenuItem extends PopupMenu.PopupSwitchMenuItem {
+ _init(device, batteryProvider, logger, params) {
+ let label = device.name || '(unknown)';
+ super._init(label, device.isConnected, {});
+
+ this._logger = logger;
+
+ this._device = device;
+ this._optBatDevice = [];
+ this._batteryProvider = batteryProvider;
+ this._batteryDeviceChangeSignal = null;
+ this._batteryDeviceLocateTimeout = null;
+
+ this._showRefreshButton = params.showRefreshButton;
+ this._closeMenuOnAction = params.closeMenuOnAction;
+
+ this.label.x_expand = true;
+ this._statusBin.x_expand = false;
+
+ this._refreshButton = this._buildRefreshButton();
+ this._pendingLabel = this._buildPendingLabel();
+ this._connectToggledEvent();
+
+ this._batteryInfo = new BatteryInfoWidget(params.showBatteryValue, params.showBatteryIcon);
+ this.insert_child_at_index(this._batteryInfo, this.get_n_children() - 1);
+
+ this.insert_child_at_index(this._refreshButton, this.get_n_children() - 1);
+ this.add_child(this._pendingLabel);
+
+ this.sync(device);
+ }
+
+ _tryLocateBatteryWithTimeout(count = 10) {
+
+ let device = this._device;
+
+ this._logger.info(`looking up battery info for ${device.name}`);
+
+ this._batteryDeviceLocateTimeout = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ 1000,
+ () => {
+ this._logger.info(`Looking up battery info for ${device.name}`);
+ let optBat = this._batteryProvider.locateBatteryDevice(device);
+
+ if (optBat.length) {
+ this._batteryFound(optBat);
+ this._batteryDeviceLocateTimeout = null;
+ } else if (count) {
+ // try again
+ this._tryLocateBatteryWithTimeout(count - 1);
+ } else {
+ this._logger.info(`Did not find a battery for device ${device.name}`);
+ this._batteryDeviceLocateTimeout = null;
+ }
+ });
+ }
+
+ _batteryFound(optBatDevice) {
+ this._optBatDevice = optBatDevice;
+
+ for (const bat of this._optBatDevice) {
+ this._batteryInfo.show();
+ this._batteryInfo.setPercentage(bat.percentage);
+
+ this._batteryDeviceChangeSignal = bat.connect("notify", (_dev, pspec) => {
+ if (pspec.name == 'percentage') {
+ this._logger.info(`${_dev.native_path} notified ${pspec.name}, percentage is ${_dev.percentage}`);
+ this._batteryInfo.setPercentage(bat.percentage);
+ }
+ });
+ }
+ }
+
+ disconnectSignals() {
+ this._optBatDevice.map(bat => bat.disconnect(this._batteryDeviceChangeSignal));
+ this._batteryDeviceChangeSignal = null;
+
+ if (this._batteryDeviceLocateTimeout != null) {
+ GLib.Source.remove(this._batteryDeviceLocateTimeout);
+ this._batteryDeviceLocateTimeout = null;
+ }
+
+ if (this._afterReconnectTimeout != null) {
+ GLib.Source.remove(this._afterReconnectTimeout);
+ this._afterReconnectTimeout = null
+ }
+
+ if (this._afterToggleTimeout != null) {
+ GLib.Source.remove(this._afterToggleTimeout);
+ this._afterToggleTimeout = null
+ }
+ }
+
+ sync(device) {
+ this.disconnectSignals();
+
+ this._optBatDevice = [];
+ this._batteryInfo.visible = false;
+
+ this._device = device;
+
+ this._switch.state = device.isConnected;
+ this.visible = device.isPaired;
+ if (this._showRefreshButton && device.isConnected)
+ this._refreshButton.show();
+ else
+ this._refreshButton.hide();
+
+ this._disablePending();
+
+ if (device.isConnected)
+ this._tryLocateBatteryWithTimeout();
+
+ }
+
+ _buildRefreshButton() {
+ let icon = new St.Icon({
+ icon_name: 'view-refresh',
+ style_class: 'popup-menu-icon',
+ opacity: 155
+ });
+
+ let button = new St.Button({
+ child: icon,
+ x_align: St.Align.END
+ });
+
+ button.connect("enter-event", (widget) => {
+ widget.child.ease( {
+ opacity: 255,
+ time: 0.05,
+ transition: Clutter.AnimationMode.LINEAR
+ }
+ );
+ });
+
+ button.connect("leave-event", (widget) => {
+ widget.child.ease( {
+ opacity: 155,
+ time: 0.05,
+ transition: Clutter.AnimationMode.LINEAR
+ }
+ );
+ });
+
+ button.connect('clicked', () => {
+ this._enablePending();
+ this._device.reconnect();
+ this._afterReconnectTimeout = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ 10000,
+ () => {
+ this._disablePending();
+ this._afterReconnectTimeout = null;
+ }
+ );
+
+ if (this._closeMenuOnAction)
+ this.emit('activate', Clutter.get_current_event());
+ });
+
+ return button;
+ }
+
+ _buildPendingLabel() {
+ let label = new St.Label({text: _("Wait")});
+ label.hide();
+
+ return label;
+ }
+
+ _connectToggledEvent() {
+ this.connect('toggled', (item, state) => {
+ if (state)
+ this._device.connect();
+ else
+ this._device.disconnect();
+
+ // in case there is no change on device
+ this._afterToggleTimeout = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ 10000,
+ () => {
+ this._disablePending();
+ this._afterToggleTimeout = null;
+ }
+ );
+ });
+ }
+
+ activate(event) {
+ if (this._switch.mapped) {
+ this.toggle();
+ this._switch.toggle(); // toggle back, state will be updated by signal
+ }
+
+ // we allow pressing space to toggle the switch
+ // without closing the menu
+ if (event.type() == Clutter.EventType.KEY_PRESS &&
+ event.get_key_symbol() == Clutter.KEY_space)
+ return;
+
+ if (this._closeMenuOnAction)
+ this.emit('activate', event);
+ }
+
+ toggle() {
+ super.toggle();
+ this._enablePending();
+ }
+
+ _enablePending() {
+ this._refreshButton.reactive = false;
+ this._switch.hide();
+ this._pendingLabel.show();
+ this.reactive = false;
+ }
+
+ _disablePending() {
+ this._refreshButton.reactive = true;
+ this._switch.show();
+ this._pendingLabel.hide();
+ this.reactive = true;
+ }
+ }
+);
+
+var BatteryInfoWidget = GObject.registerClass(
+ class BatteryInfoWidget extends St.BoxLayout {
+ _init(showBatteryValue, showBatteryIcon) {
+ super._init({ visible: false, style: 'spacing: 3px;' });
+ this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
+ this.add_child(this._icon);
+ this._icon.icon_name = null;
+
+ // dirty trick: instantiate the label with text 100%, so we can set
+ // the natural width of the label in case monospace has no effect
+ this._label = new St.Label({
+ x_align: Clutter.ActorAlign.START,
+ y_align: Clutter.ActorAlign.CENTER,
+ style_class: 'monospace',
+ text: '100%'
+ });
+
+ this._label.natural_width = this._label.width;
+ this._label.text = "";
+
+ this.add_child(this._label);
+
+ if (!showBatteryValue) this._label.hide();
+ if (!showBatteryIcon) this._icon.hide();
+ }
+
+ setPercentage(value) {
+ if (value == null) {
+ this._label.text = '';
+ this._icon.icon_name = 'battery-missing-symbolic';
+ } else {
+ this._label.text = '%d%%'.format(value);
+
+ let fillLevel = 10 * Math.floor(value / 10);
+ let iconName = 'battery-level-%d-symbolic'.format(fillLevel);
+ this._icon.icon_name = iconName;
+ }
+ }
+ }
+);
diff --git a/utils.js b/utils.js
new file mode 100644
index 0000000..5c7481d
--- /dev/null
+++ b/utils.js
@@ -0,0 +1,82 @@
+// Copyright 2018 Bartosz Jaroszewski
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const GLib = imports.gi.GLib;
+
+function spawn(command, callback) {
+ let [status, pid] = GLib.spawn_async(
+ null,
+ ['/usr/bin/env', 'bash', '-c', command],
+ null,
+ GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
+ null
+ );
+
+ // ensure we always close the pid to avoid zombie processes
+ GLib.child_watch_add(
+ GLib.PRIORITY_DEFAULT, pid,
+ (_pid, _status) => {
+ try {
+ if (callback) {
+ callback(_pid, _status);
+ }
+ } finally {
+ GLib.spawn_close_pid(_pid);
+ }
+ });
+}
+
+
+function isDebugModeEnabled() {
+ return new Settings().isDebugModeEnabled();
+}
+
+var Logger = class Logger {
+ constructor(settings) {
+ this._enabled = settings.isDebugModeEnabled();
+ }
+
+ info(message) {
+ if (!this._enabled) return;
+
+ log(`[bluetooth-quick-connect] ${message}`);
+ }
+
+ warn(message) {
+ log(`[bluetooth-quick-connect WARNING] ${message}`);
+ }
+};
+
+function addSignalsHelperMethods(prototype) {
+ prototype._connectSignal = function (subject, signal_name, method) {
+ if (!this._signals) this._signals = [];
+
+ let signal_id = subject.connect(signal_name, method);
+ this._signals.push({
+ subject: subject,
+ signal_id: signal_id
+ });
+ }
+
+ prototype._disconnectSignals = function () {
+ if (!this._signals) return;
+
+ this._signals.forEach((signal) => {
+ signal.subject.disconnect(signal.signal_id);
+ });
+ this._signals = [];
+ };
+}