#!/usr/bin/bash # # Simple frontend for ObexFTP and BlueTooth using BASH # # Requirements: GTK-server 2.2.3 or higher and some BASH shell # (...and of course, a working Bluetooth connection with your phone.) # # Tested with: # Nokia N73 # Sony-Ericsson W800i # Sony-Ericsson W910i # Samsung SGH-U600 # HTC S710 # # 2008 - (c) PvE - GPL # # March 13, 2008: -Initial release # March 17, 2008: -Fixed bug detecting mousebutton # -Fixed bug with single quotes in directory- and filenames # -Set icon for each window # -Fixed UTF8 directory- and filenames # -Replace XML entity ''' for a real quote # March 18, 2008: -Fixed issue with parent folder # April 26, 2008: -Code beautification with assignments - code more than 50 lines smaller! # May 3, 2008: -More logic in appearing of dialogs # February 15, 2009:-Fixed bug with g_locale_from_utf8 # -More buttons using 'stock' now #-------------------------------------------------------------------------------------------------Setup embedded GTK # Set the extending globbing option in BASH shopt -s extglob declare PIPE="/tmp/gtk.bash.\$$" # Find GTK-server configfile first if [[ -f gtk-server.cfg ]]; then CFG=gtk-server.cfg elif [[ -f /etc/gtk-server.cfg ]]; then CFG=/etc/gtk-server.cfg elif [[ -f /usr/local/etc/gtk-server.cfg ]]; then CFG=/usr/local/etc/gtk-server.cfg else echo "No GTK-server configfile found! Please install GTK-server..." exit 1 fi # Now create global functionnames from GTK API if [[ ! -f $HOME/.gtk4bash || $CFG -nt $HOME/.gtk4bash ]] then echo "#!/usr/bin/bash" > $HOME/.gtk4bash echo "gtk-server -fifo=$PIPE &" >> $HOME/.gtk4bash echo "while [ ! -p $PIPE ]; do continue; done" >> $HOME/.gtk4bash while read LINE do if [[ $LINE = FUNCTION_NAME* && $LINE = +(*gtk_*|*gdk_*|*g_*|*glade_*) ]] then LINE=${LINE#*= } printf "\nfunction ${LINE%%,*}\n" >> $HOME/.gtk4bash printf "{\n/bin/echo ${LINE%%,*} \$@ > $PIPE" >> $HOME/.gtk4bash printf "\nread GTK < $PIPE\n}\n" >> $HOME/.gtk4bash fi done < $CFG printf "\nfunction gtk()\n{\necho \$1 > $PIPE; read GTK < $PIPE;\n}\n" >> $HOME/.gtk4bash fi # Declare global variables declare GTK NULL="NULL" # Make these available again unset CFG PIPE LINE # Disable globbing shopt -u extglob # Include the generated '.gtk4bash'-file in the shellscript to use embedded GTK functions . $HOME/.gtk4bash # Assignment function function define { $2 $3 $4 $5 $6 $7 $8 $9; eval $1="\"$GTK\""; } #------------------------------------------------------------------------------------------------- function Get_Dir { typeset LIST POS DIRS FILES SIZES UTF8 TMP FTYPE=0 DNR=0 FNR=0 TAG=0 # Cleanup the list first gtk_list_store_clear $LST # Get the current path if [[ $1 = "." ]] then CURPATH="." elif [[ $1 = ".." ]] then if [[ $CURPATH = */* ]] then CURPATH=${CURPATH%/*} else CURPATH="." fi else if [[ $CURPATH = "." ]] then CURPATH=$1 else if [[ -n $1 ]] then CURPATH=$CURPATH/$1 fi fi fi # Get the files gtk_window_set_title $PROGRESS "'Retrieving listing...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" $OBEXFTP -b $BLUE -c "$CURPATH" -l 2>/dev/null | tr "\015" "\012" > $LOGFILE & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done LIST=`cat $LOGFILE` while [[ $TAG -lt ${#LIST} ]] do if [[ ${LIST:$TAG:1} = "<" ]] then POS=$TAG fi if [[ ${LIST:$TAG:1} = ">" ]] then if [[ ${LIST:$POS:$TAG-$POS+1} = *folder?name* && ${LIST:$POS:$TAG-$POS+1} != *parent-folder?name* ]] then DIRS[$DNR]=`echo ${LIST:$POS:$TAG-$POS+1} | awk '{print substr($0,index($0, "folder name="))}' | cut -d '"' -f1,2`"\"" ((DNR+=1)) fi if [[ ${LIST:$POS:$TAG-$POS+1} = *file?name* ]] then FILES[$FNR]=`echo ${LIST:$POS:$TAG-$POS+1} | awk '{print substr($0,index($0, "file name="))}' | cut -d'"' -f1,2`"\"" SIZES[$FNR]=`echo ${LIST:$POS:$TAG-$POS+1} | awk '{print substr($0,index($0, "size="))}' | cut -d'"' -f1,2`"\"" ((FNR+=1)) fi fi ((TAG+=1)) done # Add the directory 'up' gtk_list_store_append $LST $ITER gtk_list_store_set $LST $ITER 0 ".." -1 DIR[0]=".." TYPE[0]=1 TOTAL=1 # Add the other items, first directories ITEM=0 while [[ $ITEM -lt $DNR ]] do gtk_list_store_append $LST $ITER # We have the directory name in UTF-8, replace XML entity ''' for a real single quote UTF8=`echo ${DIRS[$ITEM]#*=} | awk '{print substr($0,2,length($0)-2)}' | sed "s/'/\\\'/"` # Decode to normal string define DIR[$TOTAL] g_locale_from_utf8 "\"$UTF8\"" ${#UTF8} # Now prepare for GTK, keep UTF8 but escape single quote for GTK-server TMP=`echo $UTF8 | sed "s/'/\\\\\'/"` gtk_list_store_set $LST $ITER 0 "'[$TMP]'" -1 TYPE[$TOTAL]=1 ((TOTAL+=1)) ((ITEM+=1)) done # Now add the files ITEM=0 while [[ $ITEM -lt $FNR ]] do gtk_list_store_append $LST $ITER # We have the filename in UTF-8, replace XML entity ''' for a real single quote UTF8=`echo ${FILES[$ITEM]#*=} | awk '{print substr($0,2,length($0)-2)}' | sed "s/'/\\\'/"` # Decode to normal string define DIR[$TOTAL] g_locale_from_utf8 "\"$UTF8\"" ${#UTF8} # Now prepare for GTK, keep UTF8 but escape single quote for GTK-server TMP=`echo $UTF8 | sed "s/'/\\\\\'/"` gtk_list_store_set $LST $ITER 0 "'$TMP'" -1 TYPE[$TOTAL]=0 ((TOTAL+=1)) gtk_list_store_set $LST $ITER 1 ${SIZES[$ITEM]#*=} -1 ((ITEM+=1)) done gtk_widget_hide $PROGRESS gtk_server_callback "update" } # Check if hcitool is installled HCITOOL=`which hcitool 2>/dev/null` if [[ -z $HCITOOL ]] then echo "hcitool not found on your system!" exit 1 fi # Check if ObexFTP is installed OBEXFTP=`which obexftp 2>/dev/null` if [[ -z $OBEXFTP ]] then echo "ObexFTP not found on your system!" exit 1 fi # Define GUI gtk_init define WIN gtk_window_new 0 gtk_window_set_title $WIN "'ObexFTP Bluetooth Phone Browser'" gtk_window_set_position $WIN 1 gtk_window_set_resizable $WIN 1 gtk_widget_set_size_request $WIN 390 330 gtk_window_set_icon_name $WIN "harddrive" # Signal every 250 msecs, max time of doubleclick gtk_server_connect $WIN "show" "doubleclick" gtk_server_timeout 250 $WIN "show" # Upper frame define FRAME gtk_frame_new $NULL gtk_frame_set_label $FRAME "' Scan devices '" gtk_container_set_border_width $FRAME 3 define COMBO gtk_combo_box_new_text gtk_widget_set_size_request $COMBO 200 25 define SCAN gtk_toggle_button_new_with_label "Scan" gtk_widget_set_size_request $SCAN 70 25 define REFRESH gtk_toggle_button_new_with_label "Refresh" gtk_widget_set_size_request $REFRESH 70 25 define VSEP gtk_vseparator_new define SBOX gtk_hbox_new 0 0 gtk_container_set_border_width $SBOX 5 gtk_box_pack_start $SBOX $SCAN 0 0 1 gtk_box_pack_start $SBOX $COMBO 1 1 1 gtk_box_pack_start $SBOX $VSEP 0 0 1 gtk_box_pack_start $SBOX $REFRESH 0 0 1 gtk_container_add $FRAME $SBOX # List with files and directories define ITER gtk_server_opaque gtk_server_redefine "gtk_list_store_new NONE WIDGET 3 LONG LONG LONG" define LST gtk_list_store_new 2 64 64 define CHOICE gtk_tree_view_new_with_model $LST gtk_server_connect $CHOICE "button-press-event" $CHOICE 1 gtk_tree_view_set_headers_visible $CHOICE 1 define SEL gtk_tree_view_get_selection $CHOICE gtk_tree_selection_set_mode $SEL 3 define CELL gtk_cell_renderer_text_new define COLUMN1 gtk_tree_view_column_new_with_attributes "'[folder(s)] file(s)'" $CELL "text" 0 $NULL gtk_tree_view_append_column $CHOICE $COLUMN1 gtk_tree_view_column_set_resizable $COLUMN1 1 define COLUMN2 gtk_tree_view_column_new_with_attributes "bytesize" $CELL "text" 1 $NULL gtk_tree_view_append_column $CHOICE $COLUMN2 gtk_tree_view_column_set_resizable $COLUMN2 1 define SW gtk_scrolled_window_new $NULL $NULL gtk_scrolled_window_set_policy $SW 1 1 gtk_scrolled_window_set_shadow_type $SW 1 gtk_container_add $SW $CHOICE # Buttons define DOWNLOAD gtk_toggle_button_new_with_label "Fetch!" gtk_widget_set_size_request $DOWNLOAD 70 25 define UPLOAD gtk_toggle_button_new_with_label "Upload!" gtk_widget_set_size_request $UPLOAD 70 25 define REMOVE gtk_toggle_button_new_with_label "Remove!" gtk_widget_set_size_request $REMOVE 70 25 define MKDIR gtk_toggle_button_new_with_label "Mkdir!" gtk_widget_set_size_request $MKDIR 70 25 define EXIT gtk_button_new_from_stock "gtk-quit" # Define status bar define STATUSBAR gtk_statusbar_new define CID gtk_statusbar_get_context_id $STATUSBAR $WIN gtk_server_version gtk_statusbar_push $STATUSBAR $CID "'BASH ${BASH_VERSION} with GTK-server $GTK on `uname -sr`'" # Boxes define HBOX gtk_hbox_new 0 0 define VBOX gtk_vbox_new 0 0 # Pack with boxes gtk_box_pack_start $HBOX $DOWNLOAD 0 0 1 gtk_box_pack_start $HBOX $UPLOAD 0 0 1 gtk_box_pack_start $HBOX $MKDIR 0 0 1 gtk_box_pack_start $HBOX $REMOVE 0 0 1 gtk_box_pack_end $HBOX $EXIT 0 0 1 gtk_box_pack_start $VBOX $FRAME 0 0 1 gtk_box_pack_start $VBOX $SW 1 1 1 gtk_box_pack_start $VBOX $HBOX 0 0 1 gtk_box_pack_start $VBOX $STATUSBAR 0 0 1 gtk_container_add $WIN $VBOX gtk_widget_show_all $WIN # Create file open dialog define FILEDIALOG gtk_window_new 0 gtk_window_set_title $FILEDIALOG "'Open file'" gtk_window_set_icon_name $FILEDIALOG "harddrive" gtk_window_set_transient_for $FILEDIALOG $WIN gtk_window_set_position $FILEDIALOG 4 gtk_window_set_default_size $FILEDIALOG 600 400 define SELECTOR gtk_file_chooser_widget_new 0 define OKFILE gtk_button_new_from_stock "gtk-open" define CANFILE gtk_button_new_from_stock "gtk-cancel" # Arrange widgets on window define VBOXFILE gtk_vbox_new 0 0 define HBOXFILE gtk_hbox_new 0 0 gtk_box_pack_end $HBOXFILE $OKFILE 0 0 1 gtk_box_pack_end $HBOXFILE $CANFILE 0 0 1 gtk_box_pack_start $VBOXFILE $SELECTOR 1 1 1 gtk_box_pack_start $VBOXFILE $HBOXFILE 0 0 1 gtk_container_add $FILEDIALOG $VBOXFILE # Create select directory dialog define DIRDIALOG gtk_window_new 0 gtk_window_set_title $DIRDIALOG "'Select directory'" gtk_window_set_icon_name $DIRDIALOG "harddrive" gtk_window_set_transient_for $DIRDIALOG $WIN gtk_window_set_position $DIRDIALOG 4 gtk_window_set_default_size $DIRDIALOG 600 400 define DIRSEL gtk_file_chooser_widget_new 3 define OKDIR gtk_button_new_from_stock "gtk-open" define CANDIR gtk_button_new_from_stock "gtk-cancel" # Arrange widgets on window define VBOXDIR gtk_vbox_new 0 0 define HBOXDIR gtk_hbox_new 0 0 gtk_box_pack_end $HBOXDIR $OKDIR 0 0 1 gtk_box_pack_end $HBOXDIR $CANDIR 0 0 1 gtk_box_pack_start $VBOXDIR $DIRSEL 1 1 1 gtk_box_pack_start $VBOXDIR $HBOXDIR 0 0 1 gtk_container_add $DIRDIALOG $VBOXDIR # Create progressbar define PROGRESS gtk_window_new 0 gtk_window_set_icon_name $PROGRESS "harddrive" gtk_widget_set_size_request $PROGRESS 300 25 gtk_window_set_resizable $PROGRESS 0 gtk_window_set_position $PROGRESS 4 gtk_window_set_transient_for $PROGRESS $WIN gtk_window_set_modal $PROGRESS 1 define BAR gtk_progress_bar_new gtk_container_add $PROGRESS $BAR gtk_progress_bar_set_pulse_step $BAR 0.02 gtk_progress_bar_set_text $BAR "'Please wait...'" # Create NEW dialog define NEWWIN gtk_window_new 0 gtk_window_set_title $NEWWIN "'Enter directoryname'" gtk_window_set_icon_name $NEWWIN "harddrive" gtk_window_set_transient_for $NEWWIN $WIN gtk_window_set_position $NEWWIN 4 gtk_window_set_resizable $NEWWIN 0 define NEWENTRY gtk_entry_new gtk_widget_set_size_request $NEWENTRY 300 25 define NEWOK gtk_button_new_from_stock "gtk-ok" define NEWCANC gtk_button_new_from_stock "gtk-cancel" # Now arrange widgets on window using boxes define NEWVBOX gtk_vbox_new 0 0 define NEWHBOX gtk_hbox_new 0 0 gtk_box_pack_start $NEWVBOX $NEWENTRY 0 0 1 gtk_box_pack_start $NEWHBOX $NEWCANC 0 0 1 gtk_box_pack_end $NEWHBOX $NEWOK 0 0 1 gtk_box_pack_start $NEWVBOX $NEWHBOX 0 0 1 gtk_container_add $NEWWIN $NEWVBOX # Initialize variables DOUBLECLICK=0 EVENT=0 CURPATH="." COMBOCOUNT=0 LOGFILE=/tmp/blue.log # Mainloop while [[ $EVENT != $EXIT && $EVENT != $WIN ]] do define EVENT gtk_server_callback "wait" # Determine the current Bluetooth device gtk_combo_box_get_active_text $COMBO BLUE=${GTK%% *} case $EVENT in $SCAN) # First empty the combobox while [[ $COMBOCOUNT -gt 0 ]] do gtk_combo_box_remove_text $COMBO 0 ((COMBOCOUNT-=1)) done # Also empty the list gtk_list_store_clear $LST # Scan for devices and fill combobox again gtk_window_set_title $PROGRESS "'Scanning devices...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" $HCITOOL scan > $LOGFILE 2>&1 & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done gtk_widget_hide $PROGRESS gtk_toggle_button_set_active $SCAN 0 gtk_server_callback "update" DEVICES=`cat $LOGFILE` # Now fill combobox TMP="" for NR in $DEVICES do if [[ $NR = *:*:*:*:*:* ]] then if [[ -n $TMP && $TMP = *:*:*:*:*:* ]] then gtk_combo_box_append_text $COMBO "'$TMP'" ((COMBOCOUNT+=1)) fi TMP=$NR" - " else TMP=$TMP" "$NR fi done if [[ $COMBOCOUNT -eq 0 && $TMP = *Scanning* ]] then gtk_combo_box_append_text $COMBO "'No bluetooth devices found!'" else gtk_combo_box_append_text $COMBO "'$TMP'" fi ((COMBOCOUNT+=1)) gtk_combo_box_set_active $COMBO 0;; $REFRESH) if [[ $BLUE = *:*:*:*:*:* ]] then Get_Dir "." fi gtk_toggle_button_set_active $REFRESH 0 # Flush event gtk_server_callback "update";; $CHOICE) gtk_server_mouse 2 ((BUTTON=$GTK&1)) if [[ $BUTTON -eq 1 ]] then ((DOUBLECLICK+=1)) if [[ $DOUBLECLICK -eq 2 ]] then # Reset the doubleclick counter let DOUBLECLICK=0 # Now get the selected entry NR=0 while [[ $NR -lt $TOTAL ]] do define PTH gtk_tree_path_new_from_string $NR define ROW gtk_tree_selection_path_is_selected $SEL $PTH gtk_tree_path_free $PTH if [[ $ROW = "1" ]] then break else ((NR+=1)) fi done # Only doubleclicking directorynames is allowed if [[ ${TYPE[$NR]} -eq 1 ]] then Get_Dir "${DIR[$NR]}" fi fi fi;; "doubleclick") # Here the doubleclick counter is reset as the timeout has passed let DOUBLECLICK=0;; $DOWNLOAD) if [[ $BLUE = *:*:*:*:*:* ]] then gtk_widget_show_all $DIRDIALOG else gtk_toggle_button_set_active $DOWNLOAD 0 fi;; $CANDIR) gtk_toggle_button_set_active $DOWNLOAD 0 gtk_widget_hide $DIRDIALOG # Flush event gtk_server_callback "update";; $DIRDIALOG) gtk_toggle_button_set_active $DOWNLOAD 0 gtk_widget_hide $DIRDIALOG # Flush event gtk_server_callback "update";; $OKDIR) gtk_file_chooser_get_current_folder $DIRSEL cd "$GTK" gtk_widget_hide $DIRDIALOG gtk_server_callback "update" LIST="" NR=0 # Get selection while [[ $NR -lt $TOTAL ]] do define PTH gtk_tree_path_new_from_string $NR define ROW gtk_tree_selection_path_is_selected $SEL $PTH gtk_tree_path_free $PTH if [[ $ROW = "1" && ${TYPE[$NR]} -ne 1 ]] then LIST=$LIST$NR" " fi ((NR+=1)) done # Make a list of files FILES="" for I in $LIST do FILES="\""${DIR[$I]}"\" $FILES" done # Get the files if [[ -n $FILES ]] then gtk_window_set_title $PROGRESS "'Downloading file(s)...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" eval $OBEXFTP -b $BLUE -c "\""$CURPATH"\"" -g $FILES & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done gtk_widget_hide $PROGRESS fi gtk_toggle_button_set_active $DOWNLOAD 0 # Flush event gtk_server_callback "update";; $REMOVE) LIST="" NR=0 # Get selection while [[ $NR -lt $TOTAL ]] do define PTH gtk_tree_path_new_from_string $NR define ROW gtk_tree_selection_path_is_selected $SEL $PTH gtk_tree_path_free $PTH if [[ $ROW = "1" ]] then LIST=$LIST$NR" " fi ((NR+=1)) done # Make a list of files FILES="" for I in $LIST do FILES="\""${DIR[$I]}"\" $FILES" done # Get the files if [[ -n $FILES ]] then gtk_window_set_title $PROGRESS "'Removing file(s)...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" eval $OBEXFTP -b $BLUE -c "\""$CURPATH"\"" -k $FILES & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done Get_Dir gtk_widget_hide $PROGRESS fi gtk_toggle_button_set_active $REMOVE 0 # Flush event gtk_server_callback "update";; $UPLOAD) if [[ $BLUE = *:*:*:*:*:* ]] then gtk_widget_show_all $FILEDIALOG else gtk_toggle_button_set_active $UPLOAD 0 fi;; $CANFILE) gtk_toggle_button_set_active $UPLOAD 0 gtk_widget_hide $FILEDIALOG # Flush event gtk_server_callback "update";; $FILEDIALOG) gtk_toggle_button_set_active $UPLOAD 0 gtk_widget_hide $FILEDIALOG # Flush event gtk_server_callback "update";; $OKFILE) define FILES gtk_file_chooser_get_filename $SELECTOR gtk_widget_hide $FILEDIALOG gtk_server_callback "update" # Upload the files if [[ -n $FILES && $BLUE = *:*:*:*:*:* ]] then gtk_window_set_title $PROGRESS "'Uploading file...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" eval $OBEXFTP -b $BLUE -c "\""$CURPATH"\"" -p "\""$FILES"\"" & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done Get_Dir gtk_widget_hide $PROGRESS fi gtk_toggle_button_set_active $UPLOAD 0 # Flush event gtk_server_callback "update";; $MKDIR) if [[ $BLUE = *:*:*:*:*:* ]] then gtk_widget_show_all $NEWWIN gtk_widget_grab_focus $NEWENTRY else gtk_toggle_button_set_active $MKDIR 0 fi;; $NEWCANC) gtk_toggle_button_set_active $MKDIR 0 gtk_widget_hide $NEWWIN # Flush event gtk_server_callback "update";; $NEWWIN) gtk_toggle_button_set_active $MKDIR 0 gtk_widget_hide $NEWWIN # Flush event gtk_server_callback "update";; $NEWOK) define TXT gtk_entry_get_text $NEWENTRY gtk_widget_hide $NEWWIN gtk_server_callback "update" # Upload the files if [[ $BLUE = *:*:*:*:*:* ]] then gtk_window_set_title $PROGRESS "'Creating directory...'" gtk_widget_show_all $PROGRESS gtk_server_callback "update" eval $OBEXFTP -b $BLUE -c "\""$CURPATH"\"" -C "\""$TXT"\"" & while [[ `ps -p $!` = *$!* ]] do gtk_progress_bar_pulse $BAR gtk_server_callback "update" sleep 0.1 done Get_Dir gtk_widget_hide $PROGRESS fi gtk_toggle_button_set_active $MKDIR 0 # Flush event gtk_server_callback "update";; esac done # Exit GTK gtk_server_exit # Cleanup rm -f $LOGFILE # Exit shell exit 0