[gnome-bluetooth] Locking your X session using the Gnome Bluetooth subsystem

Philip L. McMahon gnome-bluetooth at doorbot.com
Sun Feb 6 21:20:09 GMT 2005


After reading Chris Metcalf's site (chrismetcalf.net) and his script to use l2ping to check for a Bluetooth device and then lock the screen if that device was unavailable, I decided to put together a Python-based Gnome applet to do the same thing.

Bluetooth Device Monitor Applet, version 0.21

It includes...
* Per-session retention of user preferences
* Uses the Gnome Bluetooth Chooser to select the monitored device
* Allows modification of the timeout callback delay and executed command
* Applet icon resizes to the panel when applet is started
* Tooltips notify user of Bluetooth/applet status

But needs code to...
* Handle close of Bluetooth device Chooser dialog better -- can lead to the applet crashing
* Resize icon as panel size changes
* Force a Bluetooth ping when user left-clicks on the applet
* Save/load preferences in Gnome registry

I hope you find this applet useful. I'm not a programmer, so I'm sure there is room for improvement.

Thank you to Edd Dumbill for the Gnome Bluetooth subsystem, and for encouraging me to post this code here.

You can find the release notes on my website:
http://memescene.com/archives/2005/02/06/btdevmon-021/

And the code can be downloaded here in case the attachment doesn't make it through:
http://memescene.com/wp-content/bluetoothdevmon_0.21_200502061251.py

- Philip L. McMahon
-------------- next part --------------
#!/usr/bin/env python
#
#  Version 0.21
#
#
# TODO
#
# Handle close of Bluetooth device Chooser dialog better, currently can lead to the applet crashing
# Resize icon as panel size changes
# Force a bluetooth ping check when user left-clicks on the applet
# Save/load preferences in Gnome registry
#
#
# REQUIREMENTS
# Adjust your /etc/sudoers appropriately:
#	ALL   ALL = NOPASSWD: /usr/bin/l2ping -s 1 -c 1 ??:??:??:??:??:??
#
#
#        +-----------------------------------------------------------------------------+
#        | GPL                                                                         |
#        +-----------------------------------------------------------------------------+
#        | Copyright (c) 2004 Philip L. McMahon <plmcmahon at doorbot.com>                |
#        | Based on code by Arturo Gonzalez Ferrer <arturogf at ugr.es>                   |
#        | Based on code by Edd Dumbill <edd at usefulinc.com>                            |
#        | Bluetooth ping idea from Chris Metcalf @ chrismetcalf.net                   |
#        |                                                                             |
#        | 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, write to the Free Software                 |
#        | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
#        +-----------------------------------------------------------------------------+

import sys
import os
import os.path
import pygtk
pygtk.require("2.0")
import gtk
import gnome
import gnome.ui
import gnome.applet
import gobject
from gnomebt import controller as GnomebtController
from gnomebt import chooser as GnomebtChooser

# --------------------------------------------------------
#
# Our variables until I figure out how to save preferences
#
# --------------------------------------------------------

# Timeout delay between bluetooth pings, in milliseconds (1000 = 1 second)
__DEFAULT_TIMEOUT__ = 30000

# The command to run when the device is out of range
__DEFAULT_COMMAND__ = 'xscreensaver-command -lock'
# --------------------------------------------------------

name = "bluetoothdevmon"
version = "0.21"

icon_names = {
  0: "btdevice-misc.png",
  1: "btdevice-computer.png",
  2: "btdevice-phone.png",
  3: "btdevice-lan.png",
  4: "btdevice-audiovideo.png",
  5: "btdevice-peripheral.png",
  6: "btdevice-imaging.png"
}
class bluetoothdevmon(gnome.applet.Applet):


    def cleanup(self,event,widget):
        del self.applet


    # Recurring callback, checks if the Bluetooth device is available
    def timeout_callback(self,event):
        # Probably should reinitialize bluetooth every time, so there is no need for user interaction once the BT device/radio is enabled
        # Bluetooth stuff
        # Delete old connection
        del self.bluetoothconn
        print 'Initializing Bluetooth object...'
        self.bluetoothconn = GnomebtController.Controller()
        #self.bluetoothconn.connect ("device_name", self.on_device_name)
        #self.bluetoothconn.connect ("status_change", self.on_status_change)
        #self.bluetoothconn.connect ("add_device", self.on_add_device)
        if self.bluetoothconn.is_initialised() == gtk.FALSE:
            print 'Could not find a Bluetooth adapter on this system. Is your Bluetooth adapter connected and the radio turned on?'
            # Update tooltip!
            self.tooltips.set_tip(self.ev_box, 'Bluetooth is not initialized!')
            # Add an emblem/overlay on icon to show that BT is disabled?
            # EG, the "stop" stock icon? or an "X"
        else:
            self.tooltips.set_tip(self.ev_box, 'Monitoring device "' + self.target_bdname + '"')
            # This command requires root privs... due to use of raw sockets(?)
            # Adjust your /etc/sudoers appropriately:
            #	ALL   ALL = NOPASSWD: /usr/bin/l2ping -s 1 -c 1 ??:??:??:??:??:??
            # Should check if sudo is installed...
            # Should also check for l2ping
            # Run command
            # When this is running, the applet won't respond to clicks (eg, in the prefs dialog)
            #retval = 0
            print 'Attempting to ping ' + self.target_bdaddr
            retval = os.system('sudo l2ping -s 1 -c 1 ' + self.target_bdaddr)
            if retval != 0:
                # No device
                # Update tooltip?
                print 'Unable to locate Bluetooth device "' + self.target_bdname + '"'
                print 'Executing command "' + self.exec_command + '"'
                retval = os.system(self.exec_command)
                print 'Command returned a value of',retval
                # # return 0
            else:
                # Found device
                print 'Successfully located "' + self.target_bdname + '"'
        # We always return 1, or the timeout gets deleted
        return 1


    def about_callback(self, widget, data):
        self.about = gnome.ui.About("Bluetooth Device Monitor", "0.1", "(C) 2004 Philip L. McMahon",
"Monitors a Bluetooth device and performs an action when that device is no longer available.", ["Philip L. McMahon"], ["Documentation coming soon."])
        self.about.show()


    def preferences_callback(self, widget, data):
        self.dialog = gtk.Dialog("Bluetooth Device Monitor Preferences",
                     None,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                     (gtk.STOCK_OK, gtk.RESPONSE_OK,
                      gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        self.dialog.connect ("response", self.on_preferences_response)
        # Add some widgets...
        # Device menu
        if self.target_bdaddr is None:
            buttontext = 'Select a device...'
        else:
            buttontext = 'Monitoring: ' + (self.target_bdname or self.target_bdaddr)
        self.devbtn = gtk.Button(buttontext, None)
        self.devbtn.connect ("clicked", self.on_devbtn_click)
        self.dialog.vbox.add( self.devbtn)
        # Timeout
        self.timeoutbtn = gtk.SpinButton( gtk.Adjustment( self.timeout_interval, 1, 86400, 1, 10) )
        self.dialog.vbox.add(self.timeoutbtn)
        # Text entry field
        self.commandentry = gtk.Entry()
        self.commandentry.set_text(self.exec_command )
        self.dialog.vbox.add( self.commandentry)
        self.dialog.show_all()


    def on_devbtn_click(self, button):
        self.dialog.hide()
        self.btchooser.show()


    def on_add_device(self):
        print 'Device added event'


    def on_device_name(self):
        print 'Device name event -- does this mean a device name was changed?'
        # Rebuild device list?


    def on_status_change(self):
        print 'Status change!'


    def on_change_orient(self,arg1,data):
        print 'Orientation change'
        #self.update_applet_icon()


    def update_applet_icon(self, deviceclass = 0):
        print 'Resizing applet icon'
        # Update the applet icon to match the currently monitored BT device type
        temp = gtk.gdk.pixbuf_new_from_file (self.icon_name (deviceclass) )
        # Why does this grow by 2 pixels after every iteration?
        # panel is always 2 pixels larger than the icon? get size from panelicon instead of parent
        temph = self.applet.get_size() - 2
        # new width will be the ratio between the panel height and the old height
        tempw = int ( temp.get_width() * temph / temp.get_height() )
        temp = temp.scale_simple(tempw, temph, gtk.gdk.INTERP_BILINEAR)
        self.ev_box.remove(self.panelicon)
        self.panelicon.set_from_pixbuf( temp )
        self.ev_box.add(self.panelicon)


    def update_applet_size(self):
        pass


    #def change_orientation(self,arg1,data):
    #        print 'Orientiation changed!'
    #
    #        self.orientation = self.applet.get_orient()
    #        # first remove the children of the current box
    #        for i in range(len(self.numbers)):
    #            self.box.remove(self.numbers[i])
    #        # now remove the box itself
    #        self.big_evbox.remove(self.box)
    #
    #        # time to create the new box
    #        if self.orientation == gnome.applet.ORIENT_UP or self.orientation == gnome.applet.ORIENT_DOWN:
    #            self.box = gtk.HBox()
    #        else:
    #            self.box = gtk.VBox()
    #
    #        # and now fill it with the numbers
    #        for i in range(len(self.numbers)):
    #            self.box.pack_start(self.numbers[i])
    #
    #        # final steps
    #        self.big_evbox.add(self.box)
    #        self.box.show()


    def on_chooser_close(self, dialog):
        print 'Chooser was closed'
        print 'This probably means the Chooser dialog was destroyed...'
        print 'Expect a crash if you click the top button in the preferences window'
        #self.btchooser = GnomebtChooser.Chooser(self.bluetoothconn)


    def change_timeout_delay(self, new_timeout = 0):
        # Default is to remove the timeout, but not add the new one

        # Check if old source_id was null (meaning it wasn't set)
        if self.timeout_sourceid is not None:
            # Remove timeout
            print 'Removing old timeout: source_id was', self.timeout_sourceid
            gobject.source_remove(self.timeout_sourceid)
        # If new timeout is non-zero, add it
        if new_timeout != 0:
            print 'Adding new timeout of', new_timeout, 'ms'
            self.timeout_sourceid = gtk.timeout_add(self.timeout_interval,self.timeout_callback, self)
            print 'Successful, used source_id', self.timeout_sourceid
        else:
            # Unset the old source_id variable
            print 'Unsetting source_id variable'
            self.timeout_sourceid = None


    def on_chooser_response(self, dialog, response):
        print 'Previous BT device:',self.target_bdaddr
        if response == gtk.RESPONSE_OK:
            self.target_bdaddr = self.btchooser.get_bdaddr()
            # Rebuild timeout?
            # Update icon?
            print 'User selected BT device:',self.target_bdaddr
            print 'Retrieving device information...'
            devs = self.bluetoothconn.known_devices ()
            for d in devs:
                # Update our currently selected device
                if d['bdaddr'] == self.target_bdaddr:
                    #self.target_bdaddr = d['bdaddr']
                    self.target_bdclass = d['deviceclass']
                    self.target_bdname = (self.bluetoothconn.get_device_preferred_name(d['bdaddr']) or "Unnamed")
                    print 'Name:',self.target_bdname,'(',self.target_bdaddr,',', self.target_bdclass,')'
                    self.update_applet_icon(self.target_bdclass)
                    self.change_timeout_delay(self.timeout_interval)
                    # Update preferences button?
                    self.tooltips.set_tip(self.ev_box, 'Monitoring device "' + self.target_bdname + '"');
                else:
                    # We should update the tooltip?
                    # But we can still monitor a device that is "unknown", because we have the MAC address
                    pass
        else:
            print 'Keeping old device address'


    def on_preferences_response(self, dialog, response):
        print 'Closing preferences dialog'
        print 'Response was:',response
        if response == gtk.RESPONSE_OK:
            print 'Saving preferences changes'
            # Get timeout interval
            self.timeout_interval = self.timeoutbtn.get_value_as_int()
            # Update delay
            # Note a malicious user could change the delay to sidestep autoscreen lock
            print 'Attemping to change timeout delay to new preferences value'
            self.change_timeout_delay(self.timeout_interval)
            # Get exec command
            # Note a malicious user could change the command to sidestep autoscreen lock
            self.exec_command = self.commandentry.get_text()
            print 'Updated exec command to "'+self.exec_command+'"'
        else:
            print 'Discarding preferences changes.'
        print 'Hiding preferences dialog...'
        self.dialog.hide()


    def __init__(self,applet,iid):
        # Define our variables
        # Device's MAC address
        self.target_bdaddr = None
        # User-selected device's preferred name
        self.target_bdname = None
        # Device class
        self.target_bdclass = 0

        # The timeout between device polls
        # Default is 60 seconds (60000 milliseconds)
        self.timeout_interval = __DEFAULT_TIMEOUT__
        # Default command to execute when Bluetooth device is unavailable
        self.exec_command = __DEFAULT_COMMAND__
        # Timeout source id, for adding and removing timeouts
        self.timeout_sourceid = None
        # This is needed to setup the applet's context menu
        self.menu = """
      <popup name="button3">

         <menuitem name="Item 1" verb="Preferences" label="Preferences"
                   pixtype="stock" pixname="gtk-properties"/>
         <menuitem name="Item 2" verb="About" label="About"
                   pixtype="stock" pixname="gnome-stock-about"/>
      </popup>
"""
        gnome.init(name, version)
        # Set default icon
        # Read preferences
        # Check if bluetooth is enabled
        # Enumerate available devices
        # If saved device MAC isn't in the known dev list, we should skip the timeout and set a tooltip that says "please select a device"
        # Otherwise, read the device name, etc
        # Start monitoring the device
        # Set icon to match device type

        self.applet = applet
        self.bluetoothconn = GnomebtController.Controller()
        self.btchooser = GnomebtChooser.Chooser(self.bluetoothconn)
        self.btchooser.show()
        self.btchooser.connect ("response", self.on_chooser_response)
        # We want this to be hidden, and not be destroyed when user clicks close box
        self.btchooser.connect ("close", self.on_chooser_close)
        self.btchooser.connect ("destroy", self.on_chooser_close)
        self.tooltips = gtk.Tooltips()
        self.vbox = gtk.VBox()
        applet.add(self.vbox)
        # add the second button event for the popup menu and the enter mouse event to change the tooltip value
        self.ev_box = gtk.EventBox()
        #self.ev_box.connect("button-press-event",self.button_press)
        #self.ev_box.connect("enter-notify-event", self.update_info)
        self.vbox.add(self.ev_box)
        self.panelicon = gtk.Image()
        self.panelicon.set_from_stock (gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
        #applet.add(self.panelicon)
        self.ev_box.add(self.panelicon)
        applet.setup_menu(self.menu, [("Preferences", self.preferences_callback), ("About", self.about_callback) ], None)
        applet.connect("destroy",self.cleanup,None)
        #applet.connect("change-orient",self.on_change_orient,None)
        self.update_applet_icon()
        self.tooltips.set_tip(self.ev_box, 'Starting Bluetooth Device Monitor Applet...');
        applet.show_all()


    def icon_name (self, deviceclass = 0):
        cls = (deviceclass >> 8) & 0x1f
        if cls > 6:
            cls = 0
        return self.image_file (icon_names[cls])


    def image_file (self, fname):
        for d in [os.path.join ('/usr/share/gnome-bluetooth', "pixmaps"), "../pixmaps"]:
            if os.path.isfile (os.path.join (d, fname)):
                return os.path.join (d, fname)
        return None


def bluetoothdevmon_applet_factory(applet, iid):
    bluetoothdevmon(applet,iid)
    return gtk.TRUE


if len(sys.argv) == 2 and sys.argv[1] == "run-in-window":
    main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    main_window.set_default_size (100, 100)
    main_window.set_title("Bluetooth Device Monitor Applet")
    main_window.connect("destroy", gtk.main_quit)
    app = gnome.applet.Applet()
    bluetoothdevmon_applet_factory(app, None)
    app.reparent(main_window)
    main_window.show_all()
    gtk.main()
    sys.exit()



if __name__ == '__main__':
    gnome.applet.bonobo_factory("OAFIID:GNOME_BluetoothDeviceMonitorApplet_Factory",
                                gnome.applet.Applet.__gtype__,
                                "hello", "0", bluetoothdevmon_applet_factory)


More information about the gnome-bluetooth mailing list