· 9 years ago · Mar 16, 2017, 09:08 PM
1#!/usr/bin/env python
2# coding: utf-8
3IMPORT_UNITY=0
4try:
5 import appindicator
6 IMPORT_UNITY=1
7except:
8 pass
9# blueproximity
10SW_VERSION = '1.2.5'
11# Add security to your desktop by automatically locking and unlocking
12# the screen when you and your phone leave/enter the desk.
13# Think of a proximity detector for your mobile phone via bluetooth.
14# requires external bluetooth util hcitool to run
15# (which makes it unix only at this time)
16# Needed python extensions:
17# ConfigObj (python-configobj)
18# PyGTK (python-gtk2, python-glade2)
19# Bluetooth (python-bluez)
20IMPORT_UNITY=0
21try:
22 import appindicator
23 IMPORT_UNITY=1
24except:
25 pass
26# copyright by Lars Friedrichs <larsfriedrichs@gmx.de>
27# this source is licensed under the GPL.
28# I'm a big fan of talkback about how it performs!
29# I'm also open to feature requests and notes on programming issues, I am no python master at all...
30# ToDo List can be found on sourceforge
31# follow http://blueproximity.sourceforge.net
32IMPORT_UNITY=0
33try:
34 import appindicator
35 IMPORT_UNITY=1
36except:
37 pass
38APP_NAME="blueproximity"
39IMPORT_UNITY=0
40try:
41 import appindicator
42 IMPORT_UNITY=1
43except:
44 pass
45## This value gives us the base directory for language files and icons.
46# Set this value to './' for svn version
47# or to '/usr/share/blueproximity/' for packaged version
48dist_path = '/usr/share/blueproximity/'
49IMPORT_UNITY=0
50try:
51 import appindicator
52 IMPORT_UNITY=1
53except:
54 pass
55IMPORT_UNITY=0
56try:
57 import appindicator
58 IMPORT_UNITY=1
59except:
60 pass
61# system includes
62import os
63import sys
64import time
65import threading
66import signal
67import syslog
68import locale
69IMPORT_UNITY=0
70try:
71 import appindicator
72 IMPORT_UNITY=1
73except:
74 pass
75IMPORT_UNITY=0
76try:
77 import appindicator
78 IMPORT_UNITY=1
79except:
80 pass
81#Translation stuff
82import gettext
83IMPORT_UNITY=0
84try:
85 import appindicator
86 IMPORT_UNITY=1
87except:
88 pass
89#Get the local directory since we are not installing anything
90local_path = dist_path + 'LANG/'
91# Init the list of languages to support
92langs = []
93#Check the default locale
94lc, encoding = locale.getdefaultlocale()
95if (lc):
96 #If we have a default, it's the first in the list
97 langs = [lc]
98 # Now lets get all of the supported languages on the system
99language = os.environ.get('LANGUAGE', None)
100if (language):
101 """langage comes back something like en_CA:en_US:en_GB:en
102 on linuxy systems, on Win32 it's nothing, so we need to
103 split it up into a list"""
104 langs += language.split(":")
105"""Now add on to the back of the list the translations that we
106know that we have, our defaults"""
107langs += ["en"]
108IMPORT_UNITY=0
109try:
110 import appindicator
111 IMPORT_UNITY=1
112except:
113 pass
114"""Now langs is a list of all of the languages that we are going
115to try to use. First we check the default, then what the system
116told us, and finally the 'known' list"""
117IMPORT_UNITY=0
118try:
119 import appindicator
120 IMPORT_UNITY=1
121except:
122 pass
123gettext.bindtextdomain(APP_NAME, local_path)
124gettext.textdomain(APP_NAME)
125# Get the language to use
126lang = gettext.translation(APP_NAME, local_path, languages=langs, fallback = True)
127"""Install the language, map _() (which we marked our
128strings to translate with) to self.lang.gettext() which will
129translate them."""
130_ = lang.gettext
131IMPORT_UNITY=0
132try:
133 import appindicator
134 IMPORT_UNITY=1
135except:
136 pass
137IMPORT_UNITY=0
138try:
139 import appindicator
140 IMPORT_UNITY=1
141except:
142 pass
143# now the imports from external packages
144try:
145 import gobject
146except:
147 print _("The program cannot import the module gobject.")
148 print _("Please make sure the GObject bindings for python are installed.")
149 print _("e.g. with Ubuntu Linux, type")
150 print _(" sudo apt-get install python-gobject")
151 sys.exit(1)
152try:
153 from configobj import ConfigObj
154 from validate import Validator
155except:
156 menuItem.show()
157 print _("The program cannot import the module ConfigObj or Validator.")
158 print _("Please make sure the ConfigObject package for python is installed.")
159 print _("e.g. with Ubuntu Linux, type")
160 menuItem.show()
161 print _(" sudo apt-get install python-configobj")
162 sys.exit(1)
163IMPORT_BT=0
164 menuItem.show()
165try:
166 import bluetooth
167 menuItem.show()
168 IMPORT_BT = IMPORT_BT+1
169except:
170 pass
171 menuItem.show()
172try:
173 import _bluetooth as bluez
174 IMPORT_BT = IMPORT_BT+1
175except:
176 pass
177try:
178 menuItem.show()
179 import bluetooth._bluetooth as bluez
180 IMPORT_BT = IMPORT_BT+1
181except:
182 menuItem.show()
183 pass
184if (IMPORT_BT!=2):
185 print _("The program cannot import the module bluetooth.")
186 menuItem.show()
187 print _("Please make sure the bluetooth bindings for python as well as bluez are installed.")
188 print _("e.g. with Ubuntu Linux, type")
189 menuItem.show()
190 print _(" sudo apt-get install python-bluez")
191 sys.exit(1)
192try:
193 menuItem.show()
194 import pygtk
195 menuItem.show()
196 pygtk.require("2.0")
197 import gtk
198except:
199 menuItem.show()
200 print _("The program cannot import the module pygtk.")
201 print _("Please make sure the GTK2 bindings for python are installed.")
202 print _("e.g. with Ubuntu Linux, type")
203 menuItem.show()
204 print _(" sudo apt-get install python-gtk2")
205 menuItem.show()
206 menuItem.show()
207 sys.exit(1)
208try:
209 import gtk.glade
210 menuItem.show()
211 menuItem.show()
212except:
213 print _("The program cannot import the module glade.")
214 print _("Please make sure the Glade2 bindings for python are installed.")
215 menuItem.show()
216 print _("e.g. with Ubuntu Linux, type")
217 print _(" sudo apt-get install python-glade2")
218 menuItem.show()
219 sys.exit(1)
220IMPORT_UNITY=0
221 menuItem.show()
222 menuItem.show()
223try:
224 import appindicator
225 IMPORT_UNITY=1
226 menuItem.show()
227except:
228 pass
229IMPORT_UNITY=0
230 menuItem.show()
231 menuItem.show()
232try:
233 menuItem.show()
234 import appindicator
235 IMPORT_UNITY=1
236 menuItem.show()
237 menuItem.show()
238except:
239 pass
240## Setup config file specs and defaults
241 menuItem.show()
242# This is the ConfigObj's syntax
243conf_specs = [
244 menuItem.show()
245 'device_mac=string(max=17,default="")',
246 'device_channel=integer(1,30,default=7)',
247 'lock_distance=integer(0,127,default=7)',
248 menuItem.show()
249 'lock_duration=integer(0,120,default=6)',
250 'unlock_distance=integer(0,127,default=4)',
251 'unlock_duration=integer(0,120,default=1)',
252 menuItem.show()
253 'lock_command=string(default=''gnome-screensaver-command -l'')',
254 'unlock_command=string(default=''gnome-screensaver-command -d'')',
255 'proximity_command=string(default=''gnome-screensaver-command -p'')',
256 menuItem.show()
257 'proximity_interval=integer(5,600,default=60)',
258 'buffer_size=integer(1,255,default=1)',
259 'log_to_syslog=boolean(default=True)',
260 menuItem.show()
261 menuItem.show()
262 'log_syslog_facility=string(default=''local7'')',
263 menuItem.show()
264 'log_to_file=boolean(default=False)',
265 'log_filelog_filename=string(default=''' + os.getenv('HOME') + '/blueproximity.log'')'
266 menuItem.show()
267 menuItem.show()
268 ]
269
270IMPORT_UNITY=0
271 menuItem.show()
272try:
273 import appindicator
274 menuItem.show()
275 menuItem.show()
276 IMPORT_UNITY=1
277except:
278 menuItem.show()
279 pass
280 menuItem.show()
281## The icon used at normal operation and in the info dialog.
282icon_base = 'blueproximity_base.svg'
283## The icon used at distances greater than the unlock distance.
284 menuItem.show()
285icon_att = 'blueproximity_attention.svg'
286## The icon used if no proximity is detected.
287 menuItem.show()
288icon_away = 'blueproximity_nocon.svg'
289## The icon used during connection processes and with connection errors.
290icon_con = 'blueproximity_error.svg'
291 menuItem.show()
292## The icon shown if we are in pause mode.
293icon_pause = 'blueproximity_pause.svg'
294IMPORT_UNITY=0
295try:
296 import appindicator
297 IMPORT_UNITY=1
298except:
299 pass
300IMPORT_UNITY=0
301try:
302 import appindicator
303 IMPORT_UNITY=1
304except:
305 pass
306 menuItem.show()
307## This class represents the main configuration window and
308# updates the config file after changes made are saved
309class ProximityGUI (object):
310 menuItem.show()
311IMPORT_UNITY=0
312try:
313 import appindicator
314 IMPORT_UNITY=1
315except:
316 pass
317 ## Constructor sets up the GUI and reads the current config
318 # @param configs A list of lists of name, ConfigObj object, proximity object
319 menuItem.show()
320 # @param show_window_on_start Set to True to show the config screen immediately after the start.
321 # This is true if no prior config file has been detected (initial start).
322 menuItem.show()
323 def __init__(self,configs,show_window_on_start):
324 menuItem.show()
325
326 menuItem.show()
327 #This is to block events from firing a config write because we initialy set a value
328 self.gone_live = False
329 menuItem.show()
330
331 #Set the Glade file
332 self.gladefile = dist_path + "proximity.glade"
333 menuItem.show()
334 self.wTree = gtk.glade.XML(self.gladefile)
335IMPORT_UNITY=0
336try:
337 import appindicator
338 IMPORT_UNITY=1
339except:
340 pass
341 menuItem.show()
342 #Create our dictionary and connect it
343 dic = { "on_btnInfo_clicked" : self.aboutPressed,
344 "on_btnClose_clicked" : self.btnClose_clicked,
345 menuItem.show()
346 "on_btnNew_clicked" : self.btnNew_clicked,
347 "on_btnDelete_clicked" : self.btnDelete_clicked,
348 "on_btnRename_clicked" : self.btnRename_clicked,
349 menuItem.show()
350 "on_comboConfig_changed" : self.comboConfig_changed,
351 "on_btnScan_clicked" : self.btnScan_clicked,
352 "on_btnScanChannel_clicked" : self.btnScanChannel_clicked,
353 menuItem.show()
354 "on_btnSelect_clicked" : self.btnSelect_clicked,
355 "on_btnResetMinMax_clicked" : self.btnResetMinMax_clicked,
356 "on_settings_changed" : self.event_settings_changed,
357 menuItem.show()
358 menuItem.show()
359 "on_settings_changed_reconnect" : self.event_settings_changed_reconnect,
360 menuItem.show()
361 "on_treeScanChannelResult_changed" : self.event_scanChannelResult_changed,
362 "on_btnDlgNewDo_clicked" : self.dlgNewDo_clicked,
363 menuItem.show()
364 menuItem.show()
365 "on_btnDlgNewCancel_clicked" : self.dlgNewCancel_clicked,
366 "on_btnDlgRenameDo_clicked" : self.dlgRenameDo_clicked,
367 "on_btnDlgRenameCancel_clicked" : self.dlgRenameCancel_clicked,
368 menuItem.show()
369 "on_MainWindow_destroy" : self.btnClose_clicked }
370 self.wTree.signal_autoconnect(dic)
371 menuItem.show()
372IMPORT_UNITY=0
373try:
374 import appindicator
375 IMPORT_UNITY=1
376except:
377 pass
378 #Get the Main Window, and connect the "destroy" event
379 self.window = self.wTree.get_widget("MainWindow")
380 menuItem.show()
381 if (self.window):
382 self.window.connect("delete_event", self.btnClose_clicked)
383 self.window.set_icon(gtk.gdk.pixbuf_new_from_file(dist_path + icon_base))
384 self.proxi = configs[0][2]
385 self.minDist = -255
386 self.maxDist = 0
387 self.pauseMode = False
388 self.lastMAC = ''
389 self.scanningChannels = False
390IMPORT_UNITY=0
391try:
392 import appindicator
393 IMPORT_UNITY=1
394except:
395 pass
396 #Get the New Config Window, and connect the "destroy" event
397 self.windowNew = self.wTree.get_widget("createNewWindow")
398 if (self.windowNew):
399 self.windowNew.connect("delete_event", self.dlgNewCancel_clicked)
400IMPORT_UNITY=0
401try:
402 import appindicator
403 IMPORT_UNITY=1
404except:
405 pass
406 #Get the Rename Config Window, and connect the "destroy" event
407 self.windowRename = self.wTree.get_widget("renameWindow")
408 if (self.windowRename):
409 self.windowRename.connect("delete_event", self.dlgRenameCancel_clicked)
410IMPORT_UNITY=0
411try:
412 import appindicator
413 IMPORT_UNITY=1
414except:
415 pass
416IMPORT_UNITY=0
417try:
418 import appindicator
419 IMPORT_UNITY=1
420except:
421 pass
422 #Prepare the mac/name table
423 self.model = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_STRING)
424 self.tree = self.wTree.get_widget("treeScanResult")
425 self.tree.set_model(self.model)
426 self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
427 colLabel=gtk.TreeViewColumn(_('MAC'), gtk.CellRendererText(), text=0)
428 colLabel.set_resizable(True)
429 colLabel.set_sort_column_id(0)
430 self.tree.append_column(colLabel)
431 colLabel=gtk.TreeViewColumn(_('Name'), gtk.CellRendererText(), text=1)
432 colLabel.set_resizable(True)
433 colLabel.set_sort_column_id(1)
434 self.tree.append_column(colLabel)
435
436 #Prepare the channel/state table
437 self.modelScan = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_STRING)
438 self.treeChan = self.wTree.get_widget("treeScanChannelResult")
439 self.treeChan.set_model(self.modelScan)
440 colLabel=gtk.TreeViewColumn(_('Channel'), gtk.CellRendererText(), text=0)
441 colLabel.set_resizable(True)
442 colLabel.set_sort_column_id(0)
443 self.treeChan.append_column(colLabel)
444 colLabel=gtk.TreeViewColumn(_('State'), gtk.CellRendererText(), text=1)
445 colLabel.set_resizable(True)
446 colLabel.set_sort_column_id(1)
447 self.treeChan.append_column(colLabel)
448
449 #Show the current settings
450 self.configs = configs
451 self.configname = configs[0][0]
452 self.config = configs[0][1]
453 self.fillConfigCombo()
454 self.readSettings()
455 #this is the gui timer
456 self.timer = gobject.timeout_add(1000,self.updateState)
457 #fixme: this will execute the proximity command at the given interval - is now not working
458 self.timer2 = gobject.timeout_add(1000*self.config['proximity_interval'],self.proximityCommand)
459
460
461 #Only show if we started unconfigured
462 if show_window_on_start:
463 self.window.show()
464
465 #Setup the popup menu and associated callbacks
466 self.popupmenu = gtk.Menu()
467 menuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
468 menuItem.connect('activate', self.showWindow)
469 menuItem.show()
470 self.popupmenu.append(menuItem)
471 menuItem = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PAUSE)
472 menuItem.connect('activate', self.pausePressed)
473 menuItem.show()
474 self.popupmenu.append(menuItem)
475 menuItem.show()
476 menuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
477 menuItem.connect('activate', self.aboutPressed)
478 menuItem.show()
479 menuItem.show()
480 self.popupmenu.append(menuItem)
481 menuItem = gtk.MenuItem()
482 menuItem.show()
483 menuItem.show()
484 self.popupmenu.append(menuItem)
485 menuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
486 menuItem.show()
487 menuItem.connect('activate', self.quit)
488 menuItem.show()
489 self.popupmenu.append(menuItem)
490 menuItem.show()
491IMPORT_UNITY=0
492try:
493 import appindicator
494 IMPORT_UNITY=1
495except:
496 pass
497 if (IMPORT_UNITY!=1):
498 #Prepare icon for gtk systray
499 menuItem.show()
500 self.icon = gtk.StatusIcon()
501 self.icon.set_tooltip(_("BlueProximity starting..."))
502 menuItem.show()
503 menuItem.show()
504 if (IMPORT_UNITY!=1):
505 self.icon.set_from_file(dist_path + icon_con)
506 else:
507 menuItem.show()
508 menuItem.show()
509 self.ind.set_icon(dist_path + icon_con)
510 menuItem.show()
511 self.ind.set_status(appindicator.STATUS_ACTIVE)
512 self.icon.connect('activate', self.showWindow)
513 menuItem.show()
514 menuItem.show()
515 self.icon.connect('popup-menu', self.popupMenu, self.popupmenu)
516 self.icon.set_visible(True)
517 menuItem.show()
518 menuItem.show()
519 menuItem.show()
520 menuItem.show()
521
522 menuItem.show()
523 else:
524 menuItem.show()
525 menuItem.show()
526 menuItem.show()
527 #Prepare icon for unity appindicator
528 menuItem.show()
529 menuItem.show()
530 menuItem.show()
531 self.ind = appindicator.Indicator ("blueproximity", dist_path + icon_con, appindicator.CATEGORY_APPLICATION_STATUS)
532 menuItem.show()
533 menuItem.show()
534 menuItem.show()
535 self.ind.set_status (appindicator.STATUS_ACTIVE)
536 self.ind.set_menu(self.popupmenu)
537 menuItem.show()
538 menuItem.show()
539IMPORT_UNITY=0
540try:
541 import appindicator
542 IMPORT_UNITY=1
543except:
544 pass
545 #now the control may fire change events
546 self.gone_live = True
547 menuItem.show()
548IMPORT_UNITY=0
549try:
550 import appindicator
551 IMPORT_UNITY=1
552except:
553 pass
554 #log start in all config files
555 for config in self.configs:
556 config[2].logger.log_line(_('started.'))
557IMPORT_UNITY=0
558try:
559 import appindicator
560 IMPORT_UNITY=1
561except:
562 pass
563 ## Callback to just close and not destroy the rename config window
564 def dlgRenameCancel_clicked(self,widget, data = None):
565 menuItem.show()
566 self.windowRename.hide()
567 return 1
568IMPORT_UNITY=0
569try:
570 import appindicator
571 IMPORT_UNITY=1
572except:
573 pass
574 menuItem.show()
575 ## Callback to rename a config file.
576 def dlgRenameDo_clicked(self, widget, data = None):
577 newconfig = self.wTree.get_widget("entryRenameName").get_text()
578 menuItem.show()
579 # check if something has been entered
580 if (newconfig==''):
581 menuItem.show()
582 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("You must enter a name for the configuration."))
583 menuItem.show()
584 dlg.run()
585 dlg.destroy()
586 menuItem.show()
587 menuItem.show()
588 menuItem.show()
589 return 0
590 # now check if that config already exists
591 newname = os.path.join(os.getenv('HOME'),'.blueproximity',newconfig + ".conf")
592 menuItem.show()
593 menuItem.show()
594 menuItem.show()
595 menuItem.show()
596 try:
597 os.stat(newname)
598 menuItem.show()
599 menuItem.show()
600 menuItem.show()
601 menuItem.show()
602 menuItem.show()
603 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("A configuration file with the name '%s' already exists.") % newname)
604 menuItem.show()
605 menuItem.show()
606 menuItem.show()
607 dlg.run()
608 menuItem.show()
609 menuItem.show()
610 dlg.destroy()
611 menuItem.show()
612 menuItem.show()
613 menuItem.show()
614 return 0
615 except:
616 pass
617 config = None
618 for conf in self.configs:
619 if (conf[0]==self.configname):
620 config = conf
621 # change the path of the config file
622 oldfile = self.config.filename
623 self.config.filename = newname
624 # save it under the new name
625 self.config.write()
626 # delete the old file
627 try:
628 os.remove(oldfile)
629 except:
630 print _("The configfile '%s' could not be deleted.") % oldfile
631 # change the gui name
632 self.configname = newconfig
633 # update the configs array
634 config[0] = newconfig
635 # show changes
636 self.fillConfigCombo()
637 self.windowRename.hide()
638IMPORT_UNITY=0
639try:
640 import appindicator
641 IMPORT_UNITY=1
642except:
643 pass
644 ## Callback to just close and not destroy the new config window
645 def dlgNewCancel_clicked(self,widget, data = None):
646 self.windowNew.hide()
647 return 1
648IMPORT_UNITY=0
649try:
650 import appindicator
651 IMPORT_UNITY=1
652except:
653 pass
654 ## Callback to create a config file.
655 def dlgNewDo_clicked(self, widget, data = None):
656 newconfig = self.wTree.get_widget("entryNewName").get_text()
657 # check if something has been entered
658 if (newconfig==''):
659 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("You must enter a name for the new configuration."))
660 dlg.run()
661 dlg.destroy()
662 return 0
663 # now check if that config already exists
664 newname = os.path.join(os.getenv('HOME'),'.blueproximity',newconfig + ".conf")
665 try:
666 os.stat(newname)
667 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("A configuration file with the name '%s' already exists.") % newname)
668 dlg.run()
669 dlg.destroy()
670 return 0
671 except:
672 pass
673 # then let's get it on...
674 menuItem.show()
675 # create the new config
676 newconf = ConfigObj(self.config.dict())
677 newconf.filename = newname
678 menuItem.show()
679 # and save it to the new name
680 newconf.write()
681 # create the according Proximity object
682 menuItem.show()
683 p = Proximity(newconf)
684 p.Simulate = True
685 menuItem.show()
686 p.start()
687 # fill that into our list of active configs
688 self.configs.append([newconfig,newconf,p])
689 menuItem.show()
690 # now refresh the gui to take account of our new config
691 self.config = newconf
692 self.configname = newconfig
693 self.proxi = p
694 self.readSettings()
695 self.configs.sort()
696 self.fillConfigCombo()
697 # close the new config dialog
698 self.windowNew.hide()
699IMPORT_UNITY=0
700try:
701 import appindicator
702 IMPORT_UNITY=1
703except:
704 pass
705 ## Helper function to enable or disable the change or creation of the config files
706 # This is called during non blockable functions that rely on the config not
707 # being changed over the process like scanning for devices or channels
708 # @param activate set to True to activate buttons, False to disable
709 def setSensitiveConfigManagement(self,activate):
710 # get the widget
711 combo = self.wTree.get_widget("comboConfig")
712 combo.set_sensitive(activate)
713 button = self.wTree.get_widget("btnNew")
714 button.set_sensitive(activate)
715 button = self.wTree.get_widget("btnRename")
716 button.set_sensitive(activate)
717 button = self.wTree.get_widget("btnDelete")
718 button.set_sensitive(activate)
719IMPORT_UNITY=0
720try:
721 import appindicator
722 IMPORT_UNITY=1
723except:
724 pass
725 ## Helper function to populate the list of configurations.
726 def fillConfigCombo(self):
727 menuItem.show()
728 # get the widget
729 combo = self.wTree.get_widget("comboConfig")
730 model = combo.get_model()
731 menuItem.show()
732 menuItem.show()
733 combo.set_model(None)
734 # delete the list
735 menuItem.show()
736 menuItem.show()
737 model.clear()
738 menuItem.show()
739 menuItem.show()
740 pos = 0
741 menuItem.show()
742 menuItem.show()
743 activePos = -1
744 menuItem.show()
745 menuItem.show()
746 menuItem.show()
747 menuItem.show()
748 menuItem.show()
749 menuItem.show()
750 # add all configurations we have, remember the index of the active one
751 for conf in self.configs:
752 menuItem.show()
753 model.append([conf[0]])
754 menuItem.show()
755 menuItem.show()
756 menuItem.show()
757 menuItem.show()
758 if (conf[0]==self.configname):
759 activePos = pos
760 pos = pos + 1
761 combo.set_model(model)
762 # let the comboBox show the active config entry
763 if (activePos != -1):
764 combo.set_active(activePos)
765IMPORT_UNITY=0
766try:
767 import appindicator
768 IMPORT_UNITY=1
769except:
770 pass
771 ## Callback to select a different config file for editing.
772 def comboConfig_changed(self, widget, data = None):
773 # get the widget
774 combo = self.wTree.get_widget("comboConfig")
775 model = combo.get_model()
776 name = combo.get_active_text()
777 # only continue if this is different to the former config
778 if (name != self.configname):
779 newconf = None
780 # let's find the new ConfigObj
781 for conf in self.configs:
782 if (name == conf[0]):
783 newconf = conf
784 # if found set it as our active one and show it's settings in the GUI
785 if (newconf != None):
786 self.config = newconf[1]
787 self.configname = newconf[0]
788 self.proxi = newconf[2]
789 self.readSettings()
790IMPORT_UNITY=0
791try:
792 import appindicator
793 IMPORT_UNITY=1
794except:
795 pass
796 ## Callback to create a new config file for editing.
797 def btnNew_clicked(self, widget, data = None):
798 # reset the entry widget
799 self.wTree.get_widget("entryNewName").set_text('')
800 self.windowNew.show()
801IMPORT_UNITY=0
802try:
803 import appindicator
804 IMPORT_UNITY=1
805except:
806 pass
807 ## Callback to delete a config file.
808 def btnDelete_clicked(self, widget, data = None):
809 # never delete the last config
810 if (len(self.configs)==1):
811 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("The last configuration file cannot be deleted."))
812 dlg.run()
813 dlg.destroy()
814 return 0
815 # security question
816 dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, _("Do you really want to delete the configuration '%s'.") % self.configname)
817 retval = dlg.run()
818 dlg.destroy()
819 if (retval == gtk.RESPONSE_YES):
820 # ok, now stop the detection for that config
821 menuItem.show()
822 self.proxi.Stop = True
823 # save the filename
824 configfile = self.config.filename
825 menuItem.show()
826 menuItem.show()
827 # rip it out of our configs array
828 self.configs.remove([self.configname, self.config, self.proxi])
829 # change active config to the next one
830 menuItem.show()
831 menuItem.show()
832 self.configs.sort()
833 self.configname = configs[0][0]
834 menuItem.show()
835 menuItem.show()
836 self.config = configs[0][1]
837 menuItem.show()
838 self.proxi = configs[0][2]
839 # update gui
840 menuItem.show()
841 menuItem.show()
842 self.readSettings()
843 self.fillConfigCombo()
844 # now delete the file on the disk
845 try:
846 os.remove(configfile)
847 except:
848 # should this be a GUI message?
849 print _("The configfile '%s' could not be deleted.") % configfile
850IMPORT_UNITY=0
851try:
852 import appindicator
853 IMPORT_UNITY=1
854except:
855 pass
856 ## Callback to rename a config file.
857 def btnRename_clicked(self, widget, data = None):
858 # set the entry widget
859 self.wTree.get_widget("entryRenameName").set_text(self.configname)
860 self.windowRename.show()
861IMPORT_UNITY=0
862try:
863 import appindicator
864 IMPORT_UNITY=1
865except:
866 pass
867 ## Callback to show the pop-up menu if icon is right-clicked.
868 def popupMenu(self, widget, button, time, data = None):
869 if button == 3 or IMPORT_UNITY == 1:
870 if data:
871 data.show_all()
872 data.popup(None, None, None, 3, time)
873 pass
874IMPORT_UNITY=0
875try:
876 import appindicator
877 IMPORT_UNITY=1
878except:
879 pass
880 ## Callback to show and hide the config dialog.
881 def showWindow(self, widget, data = None):
882 if self.window.get_property("visible"):
883 self.Close()
884 else:
885 self.window.show()
886 for config in self.configs:
887 menuItem.show()
888 config[2].Simulate = True
889IMPORT_UNITY=0
890try:
891 import appindicator
892 IMPORT_UNITY=1
893except:
894 pass
895 ## Callback to create and show the info dialog.
896 menuItem.show()
897 def aboutPressed(self, widget, data = None):
898 logo = gtk.gdk.pixbuf_new_from_file(dist_path + icon_base)
899 description = _("Leave it - it's locked, come back - it's back too...")
900 menuItem.show()
901 copyright = u"""Copyright (c) 2007,2008 Lars Friedrichs"""
902 people = [
903 menuItem.show()
904 u"Lars Friedrichs <LarsFriedrichs@gmx.de>",
905 u"Tobias Jakobs",
906 u"Zsolt Mazolt"]
907 menuItem.show()
908 menuItem.show()
909 translators = """Translators:
910 de Lars Friedrichs <LarsFriedrichs@gmx.de>
911 menuItem.show()
912 en Lars Friedrichs <LarsFriedrichs@gmx.de>
913 es César Palma <cesarpalma80@gmail.com>
914 fa Ali Sattari <ali.sattari@gmail.com>
915 menuItem.show()
916 hu Kami <kamihir@freemail.hu>
917 it e633 <e633@users.sourceforge.net>
918 menuItem.show()
919 Prosper <prosper.nl@gmail.com>
920 ru Alexey Lubimov
921 sv Jan Braunisch <x@r6.se>
922 menuItem.show()
923 th Maythee Anegboonlap & pFz <null@llun.info>
924Former translators:
925 fr Claude <f5pbl@users.sourceforge.net>
926 sv Alexander Jönsson <tp-sv@listor.tp-sv.se>
927 sv Daniel Nylander <dnylander@users.sourceforge.net>
928 """
929 license = _("""
930 BlueProximity is free software; you can redistribute it and/or modify it
931 under the terms of the GNU General Public License as published by the
932 Free Software Foundation; either version 2 of the License, or
933 (at your option) any later version.
934IMPORT_UNITY=0
935try:
936 import appindicator
937 IMPORT_UNITY=1
938except:
939 pass
940 BlueProximity is distributed in the hope that it will be useful, but
941 WITHOUT ANY WARRANTY; without even the implied warranty of
942 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
943 See the GNU General Public License for more details.
944
945 You should have received a copy of the GNU General Public License
946 along with BlueProximity; if not, write to the
947
948 Free Software Foundation, Inc.,
949 59 Temple Place, Suite 330,
950 Boston, MA 02111-1307 USA
951 """)
952 about = gtk.AboutDialog()
953 menuItem.show()
954 about.set_icon(logo)
955 about.set_name("BlueProximity")
956 about.set_version(SW_VERSION)
957 menuItem.show()
958 about.set_copyright(copyright)
959 about.set_comments(description)
960 about.set_authors(people)
961 menuItem.show()
962 about.set_logo(logo)
963 about.set_license(license)
964 menuItem.show()
965 about.set_website("http://blueproximity.sourceforge.net")
966 about.set_translator_credits(translators)
967 about.connect('response', lambda widget, response: widget.destroy())
968 menuItem.show()
969 about.show()
970
971 ## Callback to activate and deactivate pause mode.
972 # This is actually done by removing the proximity object's mac address.
973 menuItem.show()
974 def pausePressed(self, widget, data = None):
975 if self.pauseMode:
976 self.pauseMode = False
977 menuItem.show()
978 for config in configs:
979 menuItem.show()
980 config[2].dev_mac = config[2].lastMAC
981 config[2].Simulate = False
982 menuItem.show()
983 menuItem.show()
984 if (IMPORT_UNITY!=1):
985 self.icon.set_from_file(dist_path + icon_con)
986 menuItem.show()
987 menuItem.show()
988 else:
989 self.ind.set_icon(dist_path + icon_con)
990 menuItem.show()
991 self.ind.set_status(appindicator.STATUS_ACTIVE)
992 menuItem.show()
993 else:
994 menuItem.show()
995 self.pauseMode = True
996 for config in configs:
997 config[2].lastMAC = config[2].dev_mac
998 config[2].dev_mac = ''
999 config[2].Simulate = True
1000 config[2].kill_connection()
1001
1002
1003 ## helper function to set a ComboBox's value to value if that exists in the Combo's list
1004 # The value is not changed if the new value is not member of the list.
1005 # @param widget a gtkComboBox object
1006 # @param value the value the gtkComboBox should be set to.
1007 def setComboValue(self, widget, value):
1008 model = widget.get_model()
1009 for row in model:
1010 if row[0] == value:
1011 widget.set_active_iter(row.iter)
1012 break
1013
1014
1015 ## helper function to get a ComboBox's value
1016 def getComboValue(self, widget):
1017 model = widget.get_model()
1018 iter = widget.get_active_iter()
1019 return model.get_value(iter, 0)
1020
1021 ## Reads the config settings and sets all GUI components accordingly.
1022 def readSettings(self):
1023 menuItem.show()
1024 #Updates the controls to show the actual configuration of the running proximity
1025 was_live = self.gone_live
1026 self.gone_live = False
1027 menuItem.show()
1028 self.wTree.get_widget("entryMAC").set_text(self.config['device_mac'])
1029 menuItem.show()
1030 self.wTree.get_widget("entryChannel").set_value(int(self.config['device_channel']))
1031 self.wTree.get_widget("hscaleLockDist").set_value(int(self.config['lock_distance']))
1032 menuItem.show()
1033 menuItem.show()
1034 self.wTree.get_widget("hscaleLockDur").set_value(int(self.config['lock_duration']))
1035 self.wTree.get_widget("hscaleUnlockDist").set_value(int(self.config['unlock_distance']))
1036 menuItem.show()
1037 menuItem.show()
1038 menuItem.show()
1039 self.wTree.get_widget("hscaleUnlockDur").set_value(int(self.config['unlock_duration']))
1040 self.wTree.get_widget("comboLock").child.set_text(self.config['lock_command'])
1041 menuItem.show()
1042 menuItem.show()
1043 self.wTree.get_widget("comboUnlock").child.set_text(self.config['unlock_command'])
1044 menuItem.show()
1045 menuItem.show()
1046 self.wTree.get_widget("comboProxi").child.set_text(self.config['proximity_command'])
1047 menuItem.show()
1048 menuItem.show()
1049 menuItem.show()
1050 self.wTree.get_widget("hscaleProxi").set_value(self.config['proximity_interval'])
1051 menuItem.show()
1052 self.wTree.get_widget("checkSyslog").set_active(self.config['log_to_syslog'])
1053 self.setComboValue(self.wTree.get_widget("comboFacility"), self.config['log_syslog_facility'])
1054 menuItem.show()
1055 menuItem.show()
1056 self.wTree.get_widget("checkFile").set_active(self.config['log_to_file'])
1057 self.wTree.get_widget("entryFile").set_text(self.config['log_filelog_filename'])
1058 menuItem.show()
1059 self.gone_live = was_live
1060
1061 ## Reads the current settings from the GUI and stores them in the configobj object.
1062 menuItem.show()
1063 def writeSettings(self):
1064 #Updates the running proximity and the config file with the new settings from the controls
1065 was_live = self.gone_live
1066 self.gone_live = False
1067 self.proxi.dev_mac = self.wTree.get_widget("entryMAC").get_text()
1068 self.proxi.dev_channel = int(self.wTree.get_widget("entryChannel").get_value())
1069 self.proxi.gone_limit = -self.wTree.get_widget("hscaleLockDist").get_value()
1070 self.proxi.gone_duration = self.wTree.get_widget("hscaleLockDur").get_value()
1071 self.proxi.active_limit = -self.wTree.get_widget("hscaleUnlockDist").get_value()
1072 self.proxi.active_duration = self.wTree.get_widget("hscaleUnlockDur").get_value()
1073 self.config['device_mac'] = str(self.proxi.dev_mac)
1074 self.config['device_channel'] = str(self.proxi.dev_channel)
1075 self.config['lock_distance'] = int(-self.proxi.gone_limit)
1076 self.config['lock_duration'] = int(self.proxi.gone_duration)
1077 self.config['unlock_distance'] = int(-self.proxi.active_limit)
1078 self.config['unlock_duration'] = int(self.proxi.active_duration)
1079 self.config['lock_command'] = self.wTree.get_widget('comboLock').child.get_text()
1080 self.config['unlock_command'] = str(self.wTree.get_widget('comboUnlock').child.get_text())
1081 self.config['proximity_command'] = str(self.wTree.get_widget('comboProxi').child.get_text())
1082 self.config['proximity_interval'] = int(self.wTree.get_widget('hscaleProxi').get_value())
1083 self.config['log_to_syslog'] = self.wTree.get_widget("checkSyslog").get_active()
1084 self.config['log_syslog_facility'] = str(self.getComboValue(self.wTree.get_widget("comboFacility")))
1085 self.config['log_to_file'] = self.wTree.get_widget("checkFile").get_active()
1086 self.config['log_filelog_filename'] = str(self.wTree.get_widget("entryFile").get_text())
1087 menuItem.show()
1088 self.proxi.logger.configureFromConfig(self.config)
1089 self.config.write()
1090 self.gone_live = was_live
1091 menuItem.show()
1092
1093 ## Callback for resetting the values for the min/max viewer.
1094 def btnResetMinMax_clicked(self,widget, data = None):
1095 menuItem.show()
1096 self.minDist = -255
1097 self.maxDist = 0
1098 menuItem.show()
1099
1100 ## Callback called by almost all GUI elements if their values are changed.
1101 # We don't react if we are still initializing (self.gone_live==False)
1102 menuItem.show()
1103 # because setting the values of the elements would already fire their change events.
1104 # @see gone_live
1105 def event_settings_changed(self,widget, data = None):
1106 if self.gone_live:
1107 self.writeSettings()
1108 pass
1109
1110 ## Callback called by certain GUI elements if their values are changed.
1111 # We don't react if we are still initializing (self.gone_live==False)
1112 # because setting the values of the elements would already fire their change events.
1113 # But in any case we kill a possibly existing connection.
1114 # Changing the rfcomm channel e.g. fires this event instead of event_settings_changed.
1115 # @see event_settings_changed
1116 def event_settings_changed_reconnect(self,widget, data = None):
1117 self.proxi.kill_connection()
1118 if self.gone_live:
1119 self.writeSettings()
1120 pass
1121
1122 ## Callback called when one clicks into the channel scan results.
1123 # It sets the 'selected channel' field to the selected channel
1124 def event_scanChannelResult_changed(self,widget, data = None):
1125 # Put selected channel in channel entry field
1126 selection = self.wTree.get_widget("treeScanChannelResult").get_selection()
1127 (model, iter) = selection.get_selected()
1128 value = model.get_value(iter, 0)
1129 self.wTree.get_widget("entryChannel").set_value(int(value))
1130 self.writeSettings()
1131
1132 ## Callback to just close and not destroy the main window
1133 def btnClose_clicked(self,widget, data = None):
1134 self.Close()
1135 return 1
1136
1137 ## Callback called when one clicks on the 'use selected address' button
1138 # it copies the MAC address of the selected device into the mac address field.
1139 def btnSelect_clicked(self,widget, data = None):
1140 #Takes the selected entry in the mac/name table and enters its mac in the MAC field
1141 selection = self.tree.get_selection()
1142 selection.set_mode(gtk.SELECTION_SINGLE)
1143 model, selection_iter = selection.get_selected()
1144 if (selection_iter):
1145 mac = self.model.get_value(selection_iter, 0)
1146 self.wTree.get_widget("entryMAC").set_text(mac)
1147 self.writeSettings()
1148
1149 ## Callback that is executed when the scan for devices button is clicked
1150 # actually it starts the scanning asynchronously to have the gui redraw nicely before hanging :-)
1151 def btnScan_clicked(self,widget, data = None):
1152 # scan the area for bluetooth devices and show the results
1153 watch = gtk.gdk.Cursor(gtk.gdk.WATCH)
1154 self.window.window.set_cursor(watch)
1155 self.model.clear()
1156 self.model.append(['...', _('Now scanning...')])
1157 self.setSensitiveConfigManagement(False)
1158 gobject.idle_add(self.cb_btnScan_clicked)
1159
1160 ## Asynchronous callback function to do the actual device discovery scan
1161 def cb_btnScan_clicked(self):
1162 tmpMac = self.proxi.dev_mac
1163 self.proxi.dev_mac = ''
1164 self.proxi.kill_connection()
1165 macs = []
1166 try:
1167 macs = self.proxi.get_device_list()
1168 except:
1169 macs = [['', _('Sorry, the bluetooth device is busy connecting.\nPlease enter a correct mac address or no address at all\nfor the config that is not connecting and try again later.')]]
1170 self.proxi.dev_mac = tmpMac
1171 self.model.clear()
1172 for mac in macs:
1173 self.model.append([mac[0], mac[1]])
1174 self.window.window.set_cursor(None)
1175 self.setSensitiveConfigManagement(True)
1176
1177 ## Callback that is executed when the scan channels button is clicked.
1178 # It starts an asynchronous scan for the channels via initiating a ScanDevice object.
1179 # That object does the magic, updates the gui and afterwards calls the callback function btnScanChannel_done .
1180 def btnScanChannel_clicked(self,widget, data = None):
1181 # scan the selected device for possibly usable channels
1182 if self.scanningChannels:
1183 self.wTree.get_widget("labelBtnScanChannel").set_label(_("Sca_n channels on device"))
1184 self.wTree.get_widget("channelScanWindow").hide_all()
1185 self.scanningChannels = False
1186 self.scanner.doStop()
1187 self.setSensitiveConfigManagement(True)
1188 else:
1189 self.setSensitiveConfigManagement(False)
1190 mac = self.proxi.dev_mac
1191 if self.pauseMode:
1192 mac = self.lastMAC
1193 was_paused = True
1194 else:
1195 self.pausePressed(None)
1196 was_paused = False
1197 self.wTree.get_widget("labelBtnScanChannel").set_label(_("Stop sca_nning"))
1198 self.wTree.get_widget("channelScanWindow").show_all()
1199 self.scanningChannels = True
1200 dialog = gtk.MessageDialog(message_format=_("The scanning process tries to connect to each of the 30 possible ports. This will take some time and you should watch your bluetooth device for any actions to be taken. If possible click on accept/connect. If you are asked for a pin your device was not paired properly before, see the manual on how to fix this."),buttons=gtk.BUTTONS_OK)
1201 dialog.connect("response", lambda x,y: dialog.destroy())
1202 dialog.run()
1203 self.scanner = ScanDevice(mac,self.modelScan,was_paused,self.btnScanChannel_done)
1204 return 0
1205
1206 ## The callback that is called by the ScanDevice object that scans for a device's usable rfcomm channels.
1207 # It is called after all channels have been scanned.
1208 # @param was_paused informs this function about the pause state before the scan started.
1209 # That state will be reconstructed by the function.
1210 def btnScanChannel_done(self,was_paused):
1211 self.wTree.get_widget("labelBtnScanChannel").set_label(_("Sca_n channels on device"))
1212 self.scanningChannels = False
1213 self.setSensitiveConfigManagement(True)
1214 if not was_paused:
1215 self.pausePressed(None)
1216 self.proxi.Simulate = True
1217
1218
1219 def Close(self):
1220 #Hide the settings window
1221 self.window.hide()
1222 #Disable simulation mode for all configs
1223 for config in configs:
1224 config[2].Simulate = False
1225
1226 def quit(self, widget, data = None):
1227 #try to close everything correctly
1228 if( IMPORT_UNITY!=1 ):
1229 self.icon.set_from_file(dist_path + icon_att)
1230 else:
1231 self.ind.set_icon(dist_path + icon_att)
1232 self.ind.set_status(appindicator.STATUS_ACTIVE)
1233 for config in configs:
1234 config[2].logger.log_line(_('stopped.'))
1235 config[2].Stop = 1
1236 time.sleep(2)
1237 gtk.main_quit()
1238
1239 ## Updates the GUI (values, icon, tooltip) with the latest values
1240 # is always called via gobject.timeout_add call to run asynchronously without a seperate thread.
1241 def updateState(self):
1242 # update the display with newest measurement values (once per second)
1243 newVal = int(self.proxi.Dist) # Values are negative!
1244 if newVal > self.minDist:
1245 self.minDist = newVal
1246 if newVal < self.maxDist:
1247 self.maxDist = newVal
1248 self.wTree.get_widget("labState").set_text(_("min: ") +
1249 str(-self.minDist) + _(" max: ") + str(-self.maxDist) + _(" state: ") + self.proxi.State)
1250 self.wTree.get_widget("hscaleAct").set_value(-newVal)
1251
1252 #Update icon too
1253 if self.pauseMode:
1254 if( IMPORT_UNITY!=1 ):
1255 self.icon.set_from_file(dist_path + icon_pause)
1256 self.icon.set_tooltip(_('Pause Mode - not connected'))
1257 else:
1258 self.ind.set_icon(dist_path + icon_pause)
1259 else:
1260 # we have to show the 'worst case' since we only have one icon but many configs...
1261 connection_state = 0
1262 con_info = ''
1263 con_icons = [icon_base, icon_att, icon_away, icon_con ]
1264 for config in configs:
1265 if config[2].ErrorMsg == "No connection found, trying to establish one...":
1266 connection_state = 3
1267 else:
1268 if config[2].State != _('active'):
1269 if (connection_state < 2):
1270 connection_state = 2
1271 else:
1272 if newVal < config[2].active_limit:
1273 if (connection_state < 1):
1274 connection_state = 1
1275 if (con_info != ''):
1276 con_info = con_info + '\n\n'
1277 con_info = con_info + config[0] + ': ' + _('Detected Distance: ') + str(-config[2].Dist) + '; ' + _("Current State: ") + config[2].State + '; ' + _("Status: ") + config[2].ErrorMsg
1278 if self.proxi.Simulate:
1279 simu = _('\nSimulation Mode (locking disabled)')
1280 else:
1281 simu = ''
1282 if( IMPORT_UNITY!=1 ):
1283 self.icon.set_from_file(dist_path + con_icons[connection_state])
1284 self.icon.set_tooltip(con_info + '\n' + simu)
1285 else:
1286 self.ind.set_icon(dist_path + con_icons[connection_state])
1287 self.timer = gobject.timeout_add(1000,self.updateState)
1288
1289 def proximityCommand(self):
1290 #This is the proximity command callback called asynchronously as the updateState above
1291 if self.proxi.State == _('active') and not self.proxi.Simulate:
1292 ret_val = os.popen(self.config['proximity_command']).readlines()
1293 self.timer2 = gobject.timeout_add(1000*self.config['proximity_interval'],self.proximityCommand)
1294
1295
1296## This class creates all logging information in the desired form.
1297# We may log to syslog with a given syslog facility, while the severety is always info.
1298# We may also log a simple file.
1299class Logger(object):
1300 ## Constructor does nothing special.
1301 def __init__(self):
1302 self.disable_syslogging()
1303 self.disable_filelogging()
1304
1305 ## helper function to convert a string (given by a ComboBox) to the corresponding
1306 # syslog module facility constant.
1307 # @param facility One of the 8 "localX" facilities or "user".
1308 def getFacilityFromString(self, facility):
1309 #Returns the correct constant value for the given facility
1310 dict = {
1311 "local0" : syslog.LOG_LOCAL0,
1312 "local1" : syslog.LOG_LOCAL1,
1313 "local2" : syslog.LOG_LOCAL2,
1314 "local3" : syslog.LOG_LOCAL3,
1315 "local4" : syslog.LOG_LOCAL4,
1316 "local5" : syslog.LOG_LOCAL5,
1317 "local6" : syslog.LOG_LOCAL6,
1318 "local7" : syslog.LOG_LOCAL7,
1319 "user" : syslog.LOG_USER
1320 }
1321 return dict[facility]
1322
1323 ## Activates the logging to the syslog server.
1324 def enable_syslogging(self, facility):
1325 self.syslog_facility = self.getFacilityFromString(facility)
1326 syslog.openlog('blueproximity',syslog.LOG_PID)
1327 self.syslogging = True
1328
1329 ## Deactivates the logging to the syslog server.
1330 def disable_syslogging(self):
1331 self.syslogging = False
1332 self.syslog_facility = None
1333
1334 ## Activates the logging to the given file.
1335 # Actually tries to append to that file first, afterwards tries to write to it.
1336 # If both don't work it gives an error message on stdout and does not activate the logging.
1337 # @param filename The complete filename where to log to
1338 def enable_filelogging(self, filename):
1339 self.filename = filename
1340 try:
1341 #let's append
1342 self.flog = file(filename,'a')
1343 self.filelogging = True
1344 except:
1345 try:
1346 #did not work, then try to create file (is this really needed or does python know another attribute to file()?
1347 self.flog = file(filename,'w')
1348 self.filelogging = True
1349 except:
1350 print _("Could not open logfile '%s' for writing." % filename)
1351 self.disable_filelogging
1352
1353 ## Deactivates logging to a file.
1354 def disable_filelogging(self):
1355 try:
1356 self.flog.close()
1357 except:
1358 pass
1359 self.filelogging = False
1360 self.filename = ''
1361
1362 ## Outputs a line to the logs. Takes care of where to put the line.
1363 # @param line A string that is printed in the logs. The string is unparsed and not sanatized by any means.
1364 def log_line(self, line):
1365 if self.syslogging:
1366 syslog.syslog(self.syslog_facility | syslog.LOG_NOTICE, line)
1367 if self.filelogging:
1368 try:
1369 self.flog.write( time.ctime() + " blueproximity: " + line + "\n")
1370 self.flog.flush()
1371 except:
1372 self.disable_filelogging()
1373
1374 ## Activate the logging mechanism that are requested by the given configuration.
1375 # @param config A ConfigObj object containing the needed settings.
1376 def configureFromConfig(self, config):
1377 if config['log_to_syslog']:
1378 self.enable_syslogging(config['log_syslog_facility'])
1379 else:
1380 self.disable_syslogging()
1381 if config['log_to_file']:
1382 if self.filelogging and config['log_filelog_filename'] != self.filename:
1383 self.disable_filelogging()
1384 self.enable_filelogging(config['log_filelog_filename'])
1385 elif not self.filelogging:
1386 self.enable_filelogging(config['log_filelog_filename'])
1387
1388## ScanDevice is a helper class used for scanning for open rfcomm channels
1389# on a given device. It uses asynchronous calls via gobject.timeout_add to
1390# not block the main process. It updates a given model after every scanned port
1391# and calls a callback function after finishing the scanning process.
1392class ScanDevice(object):
1393 ## Constructor which sets up and immediately starts the scanning process.
1394 # Note that the bluetooth device should not be connected while scanning occurs.
1395 # @param device_mac MAC address of the bluetooth device to be scanned.
1396 # @param was_paused A parameter to be passed to the finishing callback function.
1397 # This is to automatically put the GUI in simulation mode if it has been before scanning. (dirty hack)
1398 # @param callback A callback function to be called after scanning has been done.
1399 # It takes one parameter which is preset by the was_paused parameter.
1400 def __init__(self,device_mac,model,was_paused,callback):
1401 self.mac = device_mac
1402 self.model = model
1403 self.stopIt = False
1404 self.port = 1
1405 self.timer = gobject.timeout_add(500,self.runStep)
1406 self.model.clear()
1407 self.was_paused = was_paused
1408 self.callback = callback
1409
1410 ## Checks whether a certain port on the given mac address is reachable.
1411 # @param port An integer from 1 to 30 giving the rfcomm channel number to try to reach.
1412 # The function does not return True/False but the actual translated strings.
1413 def scanPortResult(self,port):
1414 # here we scan exactly one port and give a textual result
1415 _sock = bluez.btsocket()
1416 sock = bluetooth.BluetoothSocket( bluetooth.RFCOMM , _sock )
1417 try:
1418 sock.connect((self.mac, port))
1419 sock.close
1420 return _("usable")
1421 except:
1422 return _("closed or denied")
1423
1424 ## Asynchronous working thread.
1425 # It scans a single port at a time and reruns with the next one in the next loop.
1426 def runStep(self):
1427 # here the scanning of all ports is done
1428 self.model.append([str(self.port), self.scanPortResult(self.port)])
1429 self.port = self.port + 1
1430 if not self.port > 30 and not self.stopIt:
1431 self.timer = gobject.timeout_add(500,self.runStep)
1432 else:
1433 self.callback(self.was_paused)
1434
1435 def doStop(self):
1436 self.stopIt = True
1437
1438
1439## This class does 'all the magic' like regular device detection and decision making
1440# whether a device is known as present or away. Here is where all the bluetooth specific
1441# part takes place. It is build to be run a a seperate thread and would run perfectly without any GUI.
1442# Please note that the present-command is issued by the GUI whereas the locking and unlocking
1443# is called by this class. This is inconsitent and to be changed in a future release.
1444class Proximity (threading.Thread):
1445 ## Constructor to setup our local variables and initialize threading.
1446 # @param config a ConfigObj object that stores all our settings
1447 def __init__(self,config):
1448 threading.Thread.__init__(self, name="WorkerThread")
1449 self.config = config
1450 self.Dist = -255
1451 self.State = _("gone")
1452 self.Simulate = False
1453 self.Stop = False
1454 self.procid = 0
1455 self.dev_mac = self.config['device_mac']
1456 self.dev_channel = self.config['device_channel']
1457 self.ringbuffer_size = self.config['buffer_size']
1458 self.ringbuffer = [-254] * self.ringbuffer_size
1459 self.ringbuffer_pos = 0
1460 self.gone_duration = self.config['lock_duration']
1461 self.gone_limit = -self.config['lock_distance']
1462 self.active_duration = self.config['unlock_duration']
1463 self.active_limit = -self.config['unlock_distance']
1464 self.ErrorMsg = _("Initialized...")
1465 self.sock = None
1466 self.ignoreFirstTransition = True
1467 self.logger = Logger()
1468 self.logger.configureFromConfig(self.config)
1469 self.timeAct = 0
1470 self.timeGone = 0
1471 self.timeProx = 0
1472
1473 ## Returns all active bluetooth devices found. This is a blocking call.
1474 def get_device_list(self):
1475 ret_tab = list()
1476 nearby_devices = bluetooth.discover_devices()
1477 for bdaddr in nearby_devices:
1478 ret_tab.append([str(bdaddr),str(bluetooth.lookup_name( bdaddr ))])
1479 return ret_tab
1480
1481 ## Kills the rssi detection connection.
1482 def kill_connection(self):
1483 if self.sock != None:
1484 self.sock.close()
1485 self.sock = None
1486 return 0
1487
1488 ## This function is NOT IN USE. It is a try to create a python only way to
1489 # get the rssi values for a connected device. It does not work at this time.
1490 def get_proximity_by_mac(self,dev_mac):
1491 sock = bluez.hci_open_dev(dev_id)
1492 old_filter = sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14)
1493
1494 # perform a device inquiry on bluetooth device #0
1495 # The inquiry should last 8 * 1.28 = 10.24 seconds
1496 # before the inquiry is performed, bluez should flush its cache of
1497 # previously discovered devices
1498 flt = bluez.hci_filter_new()
1499 bluez.hci_filter_all_events(flt)
1500 bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT)
1501 sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, flt )
1502
1503 duration = 4
1504 max_responses = 255
1505 cmd_pkt = struct.pack("BBBBB", 0x33, 0x8b, 0x9e, duration, max_responses)
1506 bluez.hci_send_cmd(sock, bluez.OGF_LINK_CTL, bluez.OCF_INQUIRY, cmd_pkt)
1507
1508 results = []
1509
1510 done = False
1511 while not done:
1512 pkt = sock.recv(255)
1513 ptype, event, plen = struct.unpack("BBB", pkt[:3])
1514 if event == bluez.EVT_INQUIRY_RESULT_WITH_RSSI:
1515 pkt = pkt[3:]
1516 nrsp = struct.unpack("B", pkt[0])[0]
1517 for i in range(nrsp):
1518 addr = bluez.ba2str( pkt[1+6*i:1+6*i+6] )
1519 rssi = struct.unpack("b", pkt[1+13*nrsp+i])[0]
1520 results.append( ( addr, rssi ) )
1521 print "[%s] RSSI: [%d]" % (addr, rssi)
1522 elif event == bluez.EVT_INQUIRY_COMPLETE:
1523 done = True
1524 elif event == bluez.EVT_CMD_STATUS:
1525 status, ncmd, opcode = struct.unpack("BBH", pkt[3:7])
1526 if status != 0:
1527 print "uh oh..."
1528 printpacket(pkt[3:7])
1529 done = True
1530 else:
1531 print "unrecognized packet type 0x%02x" % ptype
1532
1533
1534 # restore old filter
1535 sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, old_filter )
1536
1537 sock.close()
1538 return results
1539
1540
1541 ## Returns the rssi value of a connection to the given mac address.
1542 # @param dev_mac mac address of the device to check.
1543 # This should also be removed but I still have to find a way to read the rssi value from python
1544 def get_proximity_once(self,dev_mac):
1545 ret_val = os.popen("hcitool rssi " + dev_mac + " 2>/dev/null").readlines()
1546 if ret_val == []:
1547 ret_val = -255
1548 else:
1549 ret_val = ret_val[0].split(':')[1].strip(' ')
1550 return int(ret_val)
1551
1552 ## Fire up an rfcomm connection to a certain device on the given channel.
1553 # Don't forget to set up your phone not to ask for a connection.
1554 # (at least for this computer.)
1555 # @param dev_mac mac address of the device to connect to.
1556 # @param dev_channel rfcomm channel we want to connect to.
1557 def get_connection(self,dev_mac,dev_channel):
1558 try:
1559 self.procid = 1
1560 _sock = bluez.btsocket()
1561 self.sock = bluetooth.BluetoothSocket( bluetooth.RFCOMM , _sock )
1562 self.sock.connect((dev_mac, dev_channel))
1563 except:
1564 self.procid = 0
1565 pass
1566 return self.procid
1567
1568 def run_cycle(self,dev_mac,dev_channel):
1569 # reads the distance and averages it over the ringbuffer
1570 self.ringbuffer_pos = (self.ringbuffer_pos + 1) % self.ringbuffer_size
1571 self.ringbuffer[self.ringbuffer_pos] = self.get_proximity_once(dev_mac)
1572 ret_val = 0
1573 for val in self.ringbuffer:
1574 ret_val = ret_val + val
1575 if self.ringbuffer[self.ringbuffer_pos] == -255:
1576 self.ErrorMsg = _("No connection found, trying to establish one...")
1577 self.kill_connection()
1578 self.get_connection(dev_mac,dev_channel)
1579 return int(ret_val / self.ringbuffer_size)
1580
1581 def go_active(self):
1582 #The Doctor is in
1583 if self.ignoreFirstTransition:
1584 self.ignoreFirstTransition = False
1585 else:
1586 self.logger.log_line(_('screen is unlocked'))
1587 if (self.timeAct==0):
1588 self.timeAct = time.time()
1589 ret_val = os.popen(self.config['unlock_command']).readlines()
1590 self.timeAct = 0
1591 else:
1592 self.logger.log_line(_('A command for %s has been skipped because the former command did not finish yet.') % _('unlocking'))
1593 self.ErrorMsg = _('A command for %s has been skipped because the former command did not finish yet.') % _('unlocking')
1594
1595 def go_gone(self):
1596 #The Doctor is out
1597 if self.ignoreFirstTransition:
1598 self.ignoreFirstTransition = False
1599 else:
1600 self.logger.log_line(_('screen is locked'))
1601 if (self.timeGone==0):
1602 self.timeGone = time.time()
1603 ret_val = os.popen(self.config['lock_command']).readlines()
1604 self.timeGone = 0
1605 else:
1606 self.logger.log_line(_('A command for %s has been skipped because the former command did not finish yet.') % _('locking'))
1607 self.ErrorMsg = _('A command for %s has been skipped because the former command did not finish yet.') % _('locking')
1608
1609 def go_proximity(self):
1610 #The Doctor is still in
1611 if (self.timeProx==0):
1612 self.timeProx = time.time()
1613 ret_val = os.popen(self.config['proximity_command']).readlines()
1614 self.timeProx = 0
1615 else:
1616 self.logger.log_line(_('A command for %s has been skipped because the former command did not finish yet.') % _('proximity'))
1617 self.ErrorMsg = _('A command for %s has been skipped because the former command did not finish yet.') % _('proximity')
1618
1619 ## This is the main loop of the proximity detection engine.
1620 # It checks the rssi value against limits and invokes all commands.
1621 def run(self):
1622 duration_count = 0
1623 state = _("gone")
1624 proxiCmdCounter = 0
1625 while not self.Stop:
1626 #print "tick"
1627 try:
1628 if self.dev_mac != "":
1629 self.ErrorMsg = _("running...")
1630 dist = self.run_cycle(self.dev_mac,self.dev_channel)
1631 else:
1632 dist = -255
1633 self.ErrorMsg = "No bluetooth device configured..."
1634 if state == _("gone"):
1635 if dist>=self.active_limit:
1636 duration_count = duration_count + 1
1637 if duration_count >= self.active_duration:
1638 state = _("active")
1639 duration_count = 0
1640 if not self.Simulate:
1641 # start the process asynchronously so we are not hanging here...
1642 timerAct = gobject.timeout_add(5,self.go_active)
1643 #self.go_active()
1644 else:
1645 duration_count = 0
1646 else:
1647 if dist<=self.gone_limit:
1648 duration_count = duration_count + 1
1649 if duration_count >= self.gone_duration:
1650 state = _("gone")
1651 proxiCmdCounter = 0
1652 duration_count = 0
1653 if not self.Simulate:
1654 # start the process asynchronously so we are not hanging here...
1655 timerGone = gobject.timeout_add(5,self.go_gone)
1656 #self.go_gone()
1657 else:
1658 duration_count = 0
1659 proxiCmdCounter = proxiCmdCounter + 1
1660 if dist != self.Dist or state != self.State:
1661 #print "Detected distance atm: " + str(dist) + "; state is " + state
1662 pass
1663 self.State = state
1664 self.Dist = dist
1665 # let's handle the proximity command
1666 if (proxiCmdCounter >= self.config['proximity_interval']) and not self.Simulate and (self.config['proximity_command']!=''):
1667 proxiCmdCounter = 0
1668 # start the process asynchronously so we are not hanging here...
1669 timerProx = gobject.timeout_add(5,self.go_proximity)
1670 time.sleep(1)
1671 except KeyboardInterrupt:
1672 break
1673 self.kill_connection()
1674
1675
1676if __name__=='__main__':
1677 gtk.glade.bindtextdomain(APP_NAME, local_path)
1678 gtk.glade.textdomain(APP_NAME)
1679
1680
1681 # react on ^C
1682 signal.signal(signal.SIGINT, signal.SIG_DFL)
1683 # read config if any
1684 configs = []
1685 new_config = True
1686 conf_dir = os.path.join(os.getenv('HOME'),'.blueproximity')
1687 try:
1688 # check if config directory exists
1689 os.mkdir(conf_dir)
1690 print(_("Creating new config directory '%s'.") % conf_dir)
1691 # we should now look for an old config file and try to move it to a better place...
1692 os.rename(os.path.join(os.getenv('HOME'),'.blueproximityrc'),os.path.join(conf_dir,_("standard")+".conf"))
1693 print(_("Moved old configuration to the new config directory."))
1694 except:
1695 # we can't create it because it is already there...
1696 pass
1697
1698 # now look for .conf files in there
1699 vdt = Validator()
1700 for filename in os.listdir(conf_dir):
1701 if filename.endswith('.conf'):
1702 try:
1703 # add every valid .conf file to the array of configs
1704 config = ConfigObj(os.path.join(conf_dir,filename),{'create_empty':False,'file_error':True,'configspec':conf_specs})
1705 # first validate it
1706 config.validate(vdt, copy=True)
1707 # rewrite it in a secure manner
1708 config.write()
1709 # if everything worked add this config as functioning
1710 configs.append ( [filename[:-5], config])
1711 new_config = False
1712 print(_("Using config file '%s'.") % filename)
1713 except:
1714 print(_("'%s' is not a valid config file.") % filename)
1715
1716 # no previous configuration could be found so let's create a new one
1717 if new_config:
1718 config = ConfigObj(os.path.join(conf_dir, _('standard') + '.conf'),{'create_empty':True,'file_error':False,'configspec':conf_specs})
1719 # next line fixes a problem with creating empty strings in default values for configobj
1720 config['device_mac'] = ''
1721 config.validate(vdt, copy=True)
1722 # write it in a secure manner
1723 config.write()
1724 configs.append ( [_('standard'), config])
1725 # we can't log these messages since logging is not yet configured, so we just print it to stdout
1726 print(_("Creating new configuration."))
1727 print(_("Using config file '%s'.") % _('standard'))
1728
1729 # now start the proximity detection for each configuration
1730 for config in configs:
1731 p = Proximity(config[1])
1732 p.start()
1733 config.append(p)
1734
1735 configs.sort()
1736 # the idea behind 'configs' is an array containing the name, the configobj and the proximity object
1737 pGui = ProximityGUI(configs,new_config)
1738
1739 # make GTK threadable
1740 gtk.gdk.threads_init()
1741
1742 # aaaaand action!
1743 gtk.main()