Add support for sending files via Bluetooth

git-svn-id: svn://svn.berlios.de/gpodder/trunk@536 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
Thomas Perl 2008-01-21 09:52:09 +00:00
parent d6807c3a92
commit a445089fc7
6 changed files with 564 additions and 4 deletions

View File

@ -1,3 +1,23 @@
Mon, 21 Jan 2008 10:47:49 +0100 <thp@perli.net>
Add support for sending files via Bluetooth
* data/gpodder.glade: Add "Bluetooth" configuration page to
preferences dialog
* src/gpodder/config.py: Add Bluetooth-related configuration options
* src/gpodder/gui.py: Add support for sending (and converting) files
for bluetooth file transfer; add glue code for preferences dialog and
configuration manager
* src/gpodder/libgpodder.py: Add "tempdir" variable to gPodderLibClass
that specifies a temporary directory to be used (currently only for
converting files for bluetooth transfer, might be migrated to a
configuration option later)
* src/gpodder/util.py: Add discover_bluetooth_devices() function that
searches for nearby devices (either via python-bluez or via hcitool,
so one of them has to be installed as a dependency); add
bluetooth_send_file() convenience function that starts a file transfer
via Bluetooth OBEX - uses gnome-obex-send, which has to be installed
for Bluetooth file transfer to work
Mon, 21 Jan 2008 09:52:09 +0100 <thp@perli.net>
Support for different audio/video player selection

View File

@ -3839,6 +3839,403 @@ Filesystem-based MP3 player</property>
</packing>
</child>
<child>
<widget class="GtkTable" id="table13">
<property name="border_width">10</property>
<property name="visible">True</property>
<property name="n_rows">8</property>
<property name="n_columns">4</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<widget class="GtkImage" id="image2929">
<property name="visible">True</property>
<property name="icon_size">6</property>
<property name="icon_name">stock_bluetooth</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">4</property>
<property name="x_padding">6</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label115">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Bluetooth Support&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">4</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="bluetooth_enabled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Enable Bluetooth support</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkRadioButton" id="bluetooth_ask_always">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Ask for device when sending</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator17">
<property name="visible">True</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_padding">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkRadioButton" id="bluetooth_ask_never">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Always send to</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
<property name="group">bluetooth_ask_always</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="bluetooth_device_name">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Nokia N800&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkButton" id="bluetooth_select_device">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_bluetooth_select_device_clicked" last_modification_time="Wed, 16 Jan 2008 12:24:17 GMT"/>
<child>
<widget class="GtkAlignment" id="alignment23">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">0</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkHBox" id="hbox36">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">2</property>
<child>
<widget class="GtkImage" id="image2930">
<property name="visible">True</property>
<property name="stock">gtk-find</property>
<property name="icon_size">4</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label118">
<property name="visible">True</property>
<property name="label" translatable="yes">Select device</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label116">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;File Conversion Script&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">4</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="bluetooth_use_converter">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Use converter:</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkFileChooserButton" id="bluetooth_converter">
<property name="visible">True</property>
<property name="title" translatable="yes">Select A Converter Script</property>
<property name="action">GTK_FILE_CHOOSER_ACTION_OPEN</property>
<property name="local_only">True</property>
<property name="show_hidden">False</property>
<property name="do_overwrite_confirmation">False</property>
<property name="width_chars">-1</property>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">4</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkImage" id="image2931">
<property name="visible">True</property>
<property name="icon_size">6</property>
<property name="icon_name">text-x-script</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox12">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkImage" id="image2928">
<property name="visible">True</property>
<property name="icon_size">3</property>
<property name="icon_name">stock_bluetooth</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label114">
<property name="visible">True</property>
<property name="label" translatable="yes">Bluetooth</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkTable" id="table6">
<property name="border_width">10</property>

View File

@ -72,6 +72,15 @@ gPodderSettings = {
'disable_notifications': (bool, False),
'on_quit_ask': (bool, True),
# Bluetooth-related settings
'bluetooth_enabled': (bool, False),
'bluetooth_ask_always': (bool, True),
'bluetooth_ask_never': (bool, False),
'bluetooth_device_name': (str, 'No device'),
'bluetooth_device_address': (str, '00:00:00:00:00:00'),
'bluetooth_use_converter': (bool, False),
'bluetooth_converter': (str, ''),
# Settings that are updated directly in code
'ipod_mount': ( str, '/media/ipod' ),
'mp3_player_folder': ( str, '/media/usbdisk' ),
@ -142,12 +151,17 @@ class Config(dict):
else:
raise ValueError( '%s is not a setting' % name)
def connect_gtk_filechooser( self, name, filechooser):
def connect_gtk_filechooser(self, name, filechooser, is_for_files=False):
if name in self.Settings:
filechooser.set_current_folder(getattr( self, name))
filechooser.connect( 'selection-changed', lambda filechooser: setattr( self, name, filechooser.get_filename()))
if is_for_files:
# A FileChooser for a single file
filechooser.set_filename(getattr(self, name))
else:
# A FileChooser for a folder
filechooser.set_current_folder(getattr(self, name))
filechooser.connect('selection-changed', lambda filechooser: setattr(self, name, filechooser.get_filename()))
else:
raise ValueError( '%s is not a setting' % name)
raise ValueError('%s is not a setting'%name)
def receive_configure_event( self, widget, event, config_prefix):
( x, y, width, height ) = map( lambda x: config_prefix + '_' + x, [ 'x', 'y', 'width', 'height' ])

View File

@ -25,6 +25,7 @@ import pango
import sys
import shutil
import webbrowser
import subprocess
from xml.sax import saxutils
@ -396,6 +397,43 @@ class gPodder(GladeWidget):
self.show_copy_dialog( src_filename = episode.local_filename(), dst_filename = episode.sync_filename())
def copy_episode_bluetooth(self, url, *args):
episode = self.active_channel.find_episode(url)
filename = episode.local_filename()
gl = gPodderLib()
if gl.config.bluetooth_ask_always:
device = None
else:
device = gl.config.bluetooth_device_address
if gl.config.bluetooth_use_converter:
destfile = os.path.join(gl.tempdir, episode.sync_filename())
(base, ext) = os.path.splitext(filename)
if not destfile.endswith(ext):
destfile += ext
title = _('Converting file')
message = _('Please wait while gPodder converts your media file for bluetooth file transfer.')
dlg = gtk.MessageDialog(self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE)
dlg.set_title(title)
dlg.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s'%(title, message))
dlg.show_all()
def convert_and_send_thread(filename, destfile, device, dialog, notify):
p = subprocess.Popen([gl.config.bluetooth_converter, filename, destfile], stdout=sys.stdout, stderr=sys.stderr)
result = p.wait()
dialog.destroy()
if result == 0 or not os.path.exists(destfile):
util.bluetooth_send_file(destfile, device)
else:
notify(_('Error converting file.'), _('Bluetooth file transfer'))
util.delete_file(destfile)
Thread(target=convert_and_send_thread, args=[filename, destfile, device, dlg, self.notification]).start()
else:
Thread(target=util.bluetooth_send_file, args=[filename, device]).start()
def treeview_button_pressed( self, treeview, event):
if event.button == 3:
( x, y ) = ( int(event.x), int(event.y) )
@ -429,6 +467,7 @@ class gPodder(GladeWidget):
first_url = model.get_value( model.get_iter( paths[0]), 0)
menu = gtk.Menu()
gl = gPodderLib()
( can_play, can_download, can_transfer, can_cancel ) = self.play_or_download()
@ -449,6 +488,15 @@ class gPodder(GladeWidget):
item.set_image( gtk.image_new_from_stock( gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_MENU))
item.connect( 'activate', lambda w: self.save_episode_as_file( episode_url))
menu.append( item)
if gl.config.bluetooth_enabled:
if gl.config.bluetooth_ask_always:
bt_device_name = _('bluetooth device')
else:
bt_device_name = _('%s via bluetooth')%gl.config.bluetooth_device_name
item = gtk.ImageMenuItem(_('Send to %s') % bt_device_name)
item.set_image(gtk.image_new_from_icon_name('stock_bluetooth', gtk.ICON_SIZE_MENU))
item.connect('activate', lambda w: self.copy_episode_bluetooth(episode_url))
menu.append( item)
menu.append( gtk.SeparatorMenuItem())
else:
episode_title = _('%d selected episodes') % len(paths)
@ -1688,12 +1736,18 @@ class gPodderProperties(GladeWidget):
gl.config.connect_gtk_togglebutton('disable_notifications', self.disable_notifications)
gl.config.connect_gtk_togglebutton('start_iconified', self.start_iconified)
gl.config.connect_gtk_togglebutton('on_quit_ask', self.on_quit_ask)
gl.config.connect_gtk_togglebutton('bluetooth_enabled', self.bluetooth_enabled)
gl.config.connect_gtk_togglebutton('bluetooth_ask_always', self.bluetooth_ask_always)
gl.config.connect_gtk_togglebutton('bluetooth_ask_never', self.bluetooth_ask_never)
gl.config.connect_gtk_togglebutton('bluetooth_use_converter', self.bluetooth_use_converter)
gl.config.connect_gtk_filechooser( 'bluetooth_converter', self.bluetooth_converter, is_for_files=True)
self.entryCustomSyncName.set_sensitive( self.cbCustomSyncName.get_active())
self.radio_copy_torrents.set_active( not self.radio_gnome_bittorrent.get_active())
self.iPodMountpoint.set_label( gl.config.ipod_mount)
self.filesystemMountpoint.set_label( gl.config.mp3_player_folder)
self.bluetooth_device_name.set_markup('<b>%s</b>'%gl.config.bluetooth_device_name)
self.chooserDownloadTo.set_current_folder(gl.downloaddir)
if tagging_supported():
@ -1732,6 +1786,20 @@ class gPodderProperties(GladeWidget):
self.iPodMountpoint.set_label( '')
else:
self.iPodMountpoint.set_label( ipod.mount_point)
def on_bluetooth_select_device_clicked(self, widget):
gl = gPodderLib()
self.bluetooth_select_device.set_sensitive(False)
gtk.main_iteration(False)
for name, address in util.discover_bluetooth_devices():
if self.show_confirmation('Use this device as your bluetooth device?', name):
gl.config.bluetooth_device_name = name
gl.config.bluetooth_device_address = address
self.bluetooth_device_name.set_markup('<b>%s</b>'%gl.config.bluetooth_device_name)
self.bluetooth_select_device.set_sensitive(True)
return
self.show_message('No more devices found', 'Scan finished')
self.bluetooth_select_device.set_sensitive(True)
def set_uar(self, uar):
self.comboAudioPlayerApp.set_model(uar.get_applications_as_model())

View File

@ -60,6 +60,7 @@ class gPodderLibClass( object):
gpodder_dir = os.path.expanduser( '~/.config/gpodder/')
util.make_directory( gpodder_dir)
self.tempdir = gpodder_dir
self.feed_cache_file = os.path.join( gpodder_dir, 'feedcache.db')
self.channel_settings_file = os.path.join( gpodder_dir, 'channelsettings.db')

View File

@ -41,6 +41,7 @@ import glob
import stat
import re
import subprocess
import htmlentitydefs
import time
import locale
@ -605,3 +606,62 @@ def idle_add(func, *args):
else:
func(*args)
def discover_bluetooth_devices():
"""
This is a generator function that returns
(address, name) tuples of all nearby bluetooth
devices found.
If the user has python-bluez installed, it will
be used. If not, we're trying to use "hcitool".
If neither python-bluez or hcitool are available,
this function is the empty generator.
"""
try:
# If the user has python-bluez installed
import bluetooth
log('Using python-bluez to find nearby bluetooth devices')
for name, addr in bluetooth.discover_devices(lookup_names=True):
yield (name, addr)
except:
if find_command('hcitool') is not None:
log('Using hcitool to find nearby bluetooth devices')
# If the user has "hcitool" installed
p = subprocess.Popen(['hcitool', 'scan'], stdout=subprocess.PIPE)
for line in p.stdout:
match = re.match('^\t([^\t]+)\t([^\t]+)\n$', line)
if match is not None:
(addr, name) = match.groups()
yield (name, addr)
else:
log('Cannot find either python-bluez or hcitool - no bluetooth?')
return # <= empty generator
def bluetooth_send_file(filename, device=None, callback_finished=None):
"""
Sends a file via bluetooth using gnome-obex send.
Optional parameter device is the bluetooth address
of the device; optional parameter callback_finished
is a callback function that will be called when the
sending process has finished - it gets one parameter
that is either True (when sending succeeded) or False
when there was some error.
"""
if find_command('gnome-obex-send'):
command_line = ['gnome-obex-send']
if device is not None:
command_line += ['--dest', device]
command_line.append(filename)
result = (subprocess.Popen(command_line).wait() == 0)
if callback_finished is not None:
callback_finished(result)
return result
else:
log('Cannot send file. Please install "gnome-obex-send".')
if callback_finished is not None:
callback_finished(False)
return False