diff --git a/lib/open.js b/lib/open.js index ce4e9aa..5b6dcf2 100644 --- a/lib/open.js +++ b/lib/open.js @@ -1,63 +1,66 @@ -var exec = require('child_process').exec - , path = require('path') - ; - - -/** - * open a file or uri using the default application for the file type. - * - * @return {ChildProcess} - the child process object. - * @param {string} target - the file/uri to open. - * @param {string} appName - (optional) the application to be used to open the - * file (for example, "chrome", "firefox") - * @param {function(Error)} callback - called with null on success, or - * an error object that contains a property 'code' with the exit - * code of the process. - */ - -module.exports = open; - -function open(target, appName, callback) { - var opener; - - if (typeof(appName) === 'function') { - callback = appName; - appName = null; - } - - switch (process.platform) { - case 'darwin': - if (appName) { - opener = 'open -a "' + escape(appName) + '"'; - } else { - opener = 'open'; - } - break; - case 'win32': - // if the first parameter to start is quoted, it uses that as the title - // so we pass a blank title so we can quote the file we are opening - if (appName) { - opener = 'start "" "' + escape(appName) + '"'; - } else { - opener = 'start ""'; - } - break; - default: - if (appName) { - opener = escape(appName); - } else { - // use Portlands xdg-open everywhere else - opener = path.join(__dirname, '../vendor/xdg-open'); - } - break; - } - - if (process.env.SUDO_USER) { - opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener; - } - return exec(opener + ' "' + escape(target) + '"', callback); -} - -function escape(s) { - return s.replace(/"/g, '\\\"'); -} +var exec = require('child_process').exec + , path = require('path') + ; + + +/** + * open a file or uri using the default application for the file type. + * + * @return {ChildProcess} - the child process object. + * @param {string} target - the file/uri to open. + * @param {string} appName - (optional) the application to be used to open the + * file (for example, "chrome", "firefox") + * @param {function(Error)} callback - called with null on success, or + * an error object that contains a property 'code' with the exit + * code of the process. + */ + +module.exports = open; + +function open(target, appName, callback) { + var opener; + var env_vars = ''; + + if (typeof(appName) === 'function') { + callback = appName; + appName = null; + } + + switch (process.platform) { + case 'darwin': + if (appName) { + opener = 'open -a "' + escape(appName) + '"'; + } else { + opener = 'open'; + } + break; + case 'win32': + // if the first parameter to start is quoted, it uses that as the title + // so we pass a blank title so we can quote the file we are opening + if (appName) { + opener = 'start "" "' + escape(appName) + '"'; + } else { + opener = 'start ""'; + } + break; + default: + if (appName) { + opener = escape(appName); + } else { + // use Portlands xdg-open everywhere else + script_directory = path.join(__dirname, '../vendor/'); + opener = path.join(script_directory, 'xdg-open'); + env_vars = "SCRIPT_DIRECTORY=" + script_directory + ' '; + } + break; + } + + if (process.env.SUDO_USER) { + opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener; + } + return exec(env_vars + opener + ' "' + escape(target) + '"', callback); +} + +function escape(s) { + return s.replace(/"/g, '\\\"'); +} diff --git a/vendor/xdg-mime b/vendor/xdg-mime new file mode 100755 index 0000000..0657917 --- /dev/null +++ b/vendor/xdg-mime @@ -0,0 +1,919 @@ +#!/bin/sh +. $SCRIPT_DIRECTORY/xdg-utils-common.in + +#--------------------------------------------- +# xdg-mime +# +# Utility script to manipulate MIME related information +# on XDG compliant systems. +# +# Refer to the usage() function below for usage. +# +# Copyright 2009-2010, Fathi Boudra +# Copyright 2009-2010, Rex Dieter +# Copyright 2006, Kevin Krammer +# Copyright 2006, Jeremy White +# +# LICENSE: +# +#--------------------------------------------- + +manualpage() +{ +cat << _MANUALPAGE +_MANUALPAGE +} + +usage() +{ +cat << _USAGE +_USAGE +} + +#@xdg-utils-common@ + +update_mime_database() +{ + if [ x"$mode" = x"user" -a -n "$DISPLAY" ] ; then + detectDE + if [ x"$DE" = x"kde" ] ; then + DEBUG 1 "Running kbuildsycoca" + if [ x"$KDE_SESSION_VERSION" = x"4" ]; then + eval 'kbuildsycoca4'$xdg_redirect_output + else + eval 'kbuildsycoca'$xdg_redirect_output + fi + fi + fi + for x in `echo "$PATH:/opt/gnome/bin" | sed 's/:/ /g'`; do + if [ -x $x/update-mime-database ] ; then + DEBUG 1 "Running $x/update-mime-database $1" + eval '$x/update-mime-database $1'$xdg_redirect_output + return + fi + done +} + +info_kde() +{ + if [ x"$KDE_SESSION_VERSION" = x"4" ]; then + DEBUG 1 "Running kmimetypefinder \"$1\"" + kmimetypefinder "$1" 2>/dev/null | head -n 1 + else + DEBUG 1 "Running kfile \"$1\"" + kfile "$1" 2> /dev/null | head -n 1 | cut -d "(" -f 2 | cut -d ")" -f 1 + fi + + if [ $? -eq 0 ]; then + exit_success + else + exit_failure_operation_failed + fi +} + +info_gnome() +{ + if gvfs-info --help 2>/dev/null 1>&2; then + DEBUG 1 "Running gvfs-info \"$1\"" + gvfs-info "$1" 2> /dev/null | grep standard::content-type | cut -d' ' -f4 + elif gnomevfs-info --help 2>/dev/null 1>&2; then + DEBUG 1 "Running gnomevfs-info \"$1\"" + gnomevfs-info --slow-mime "$1" 2> /dev/null | grep "^MIME" | cut -d ":" -f 2 | sed s/"^ "// + else + # according to https://bugs.freedesktop.org/show_bug.cgi?id=33094#c5 + # neither gvfs-info or gnomevfs-info are present in a default Ubuntu Natty + # install, so fallback to info_generic + info_generic "$1" + fi + + if [ $? -eq 0 ]; then + exit_success + else + exit_failure_operation_failed + fi +} + +info_generic() +{ + if mimetype --version >/dev/null 2>&1; then + DEBUG 1 "Running mimetype -b \"$1\"" + mimetype -b "$1" + else + DEBUG 1 "Running file --mime-type \"$1\"" + /usr/bin/file -b --mime-type "$1" 2> /dev/null + fi + + if [ $? -eq 0 ]; then + exit_success + else + exit_failure_operation_failed + fi +} + +make_default_kde() +{ + # $1 is vendor-name.desktop + # $2 is mime/type + # + # On KDE 3, add to $KDE_CONFIG_PATH/profilerc: + # [$2 - 1] + # Application=$1 + # + # Remove all [$2 - *] sections, or even better, + # renumber [$2 - *] sections and remove duplicate + # + # On KDE 4, add $2=$1 to $XDG_DATA_APPS/mimeapps.list + # + # Example file: + # + # [Added Associations] + # text/plain=kde4-kate.desktop;kde4-kwrite.desktop; + # + # [Removed Associations] + # text/plain=gnome-gedit.desktop;gnu-emacs.desktop; + vendor="$1" + mimetype="$2" + if [ x"$KDE_SESSION_VERSION" = x"4" ]; then + default_dir=`kde4-config --path xdgdata-apps 2> /dev/null | cut -d ':' -f 1` + default_file="$default_dir/mimeapps.list" + else + default_dir=`kde-config --path config 2> /dev/null | cut -d ':' -f 1` + default_file="$default_dir/profilerc" + fi + if [ -z "$default_dir" ]; then + DEBUG 2 "make_default_kde: No kde runtime detected" + return + fi + DEBUG 2 "make_default_kde $vendor $mimetype" + DEBUG 1 "Updating $default_file" + mkdir -p "$default_dir" + [ -f "$default_file" ] || touch "$default_file" + if [ x"$KDE_SESSION_VERSION" = x"4" ]; then + [ -f "$default_file" ] || touch "$default_file" + awk -v application="$vendor" -v mimetype="$mimetype" ' + BEGIN { + prefix=mimetype "=" + associations=0 + found=0 + blanks=0 + } + { + suppress=0 + if (index($0, "[Added Associations]") == 1) { + associations=1 + } else if (index($0, "[") == 1) { + if (associations && !found) { + print prefix application + found=1 + } + associations=0 + } else if ($0 == "") { + blanks++ + suppress=1 + } else if (associations && index($0, prefix) == 1) { + value=substr($0, length(prefix) + 1, length) + split(value, apps, ";") + value=application ";" + count=0 + for (i in apps) { + count++ + } + for (i=0; i < count; i++) { + if (apps[i] != application && apps[i] != "") { + value=value apps[i] ";" + } + } + $0=prefix value + found=1 + } + if (!suppress) { + while (blanks > 0) { + print "" + blanks-- + } + print $0 + } + } + END { + if (!found) { + if (!associations) { + print "[Added Associations]" + } + print prefix application + } + while (blanks > 0) { + print "" + blanks-- + } + } +' "$default_file" > "${default_file}.new" && mv "${default_file}.new" "$default_file" + eval 'kbuildsycoca4'$xdg_redirect_output + else + awk -v application="$vendor" -v mimetype="$mimetype" ' + BEGIN { + header_start="[" mimetype " - " + suppress=0 + } + { + if (index($0, header_start) == 1 ) + suppress=1 + else + if (/^\[/) { suppress=0 } + + if (!suppress) { + print $0 + } + } + END { + print "" + print "[" mimetype " - 1]" + print "Application=" application + print "AllowAsDefault=true" + print "GenericServiceType=Application" + print "Preference=1" + print "ServiceType=" mimetype + } +' "$default_file" > "${default_file}.new" && mv "${default_file}.new" "$default_file" + fi +} + +make_default_generic() +{ + # $1 is vendor-name.desktop + # $2 is mime/type + # Add $2=$1 to XDG_DATA_HOME/applications/mimeapps.list + xdg_user_dir="$XDG_DATA_HOME" + [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" + default_file="$xdg_user_dir/applications/mimeapps.list" + DEBUG 2 "make_default_generic $1 $2" + DEBUG 1 "Updating $default_file" + [ -f "$default_file" ] || touch "$default_file" + awk -v mimetype="$2" -v application="$1" ' + BEGIN { + prefix=mimetype "=" + indefault=0 + added=0 + blanks=0 + found=0 + } + { + suppress=0 + if (index($0, "[Default Applications]") == 1) { + indefault=1 + found=1 + } else if (index($0, "[") == 1) { + if (!added && indefault) { + print prefix application + added=1 + } + indefault=0 + } else if ($0 == "") { + suppress=1 + blanks++ + } else if (indefault && !added && index($0, prefix) == 1) { + $0=prefix application + added=1 + } + if (!suppress) { + while (blanks > 0) { + print "" + blanks-- + } + print $0 + } + } + END { + if (!added) { + if (!found) { + print "" + print "[Default Applications]" + } + print prefix application + } + while (blanks > 0) { + print "" + blanks-- + } + } +' "$default_file" > "${default_file}.new" && mv "${default_file}.new" "$default_file" +} + +search_desktop_file() +{ + local MIME="$1" + local dir="$2" + + grep -l "$MIME;" "$dir/"*.desktop 2>/dev/null + + for f in $dir/*/; do + [ -d "$f" ] && search_desktop_file "$MIME" "$f" + done +} + +defapp_fallback() +{ + MIME="$1" + + xdg_user_dir="$XDG_DATA_HOME" + [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" + + xdg_system_dirs="$XDG_DATA_DIRS" + [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ + + preference=-1 + desktop_file="" + for d in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do + for x in `search_desktop_file "$MIME" "$d/applications"`; do + pref=0`awk -F"=" '/InitialPreference=/ {print($2)}' "$x"` + DEBUG 2 " Checking $x" + + if [ $pref -gt $preference ]; then + DEBUG 2 " Select $x [ $preference => $pref ]" + preference=$pref + desktop_file=$x + fi + done + done + + if [ -n "$desktop_file" ] ; then + echo `basename $desktop_file` + exit_success + fi +} + +defapp_generic() +{ + MIME="$1" + xdg_user_dir="$XDG_DATA_HOME" + [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" + xdg_user_dir="$xdg_user_dir/$xdg_dir_name" + xdg_system_dirs="$XDG_DATA_DIRS" + [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ + + for x in `echo "$xdg_user_dir" | sed 's/:/ /g'`; do + mimeapps_list="$x/applications/mimeapps.list" + if [ -f "$mimeapps_list" ] ; then + DEBUG 2 "Checking $mimeapps_list" + trader_result=`awk -v mimetype="$MIME" ' + BEGIN { + prefix=mimetype "=" + indefault=0 + found=0 + } + { + if (index($0, "[Default Applications]") == 1) { + indefault=1 + } else if (index($0, "[") == 1) { + indefault=0 + } else if (!found && indefault && index($0, prefix) == 1) { + print substr($0, length(prefix) +1, length) + found=1 + } + } +' $mimeapps_list` + if [ -n "$trader_result" ] ; then + echo $trader_result + exit_success + fi + fi + done + + for x in `echo "$xdg_system_dirs" | sed 's/:/ /g'`; do + for prefix in "$XDG_MENU_PREFIX" ""; do + DEBUG 2 "Checking $x/applications/${prefix}defaults.list and $x/applications/${prefix}mimeinfo.cache" + trader_result=`grep "$MIME=" $x/applications/${prefix}defaults.list $x/applications/${prefix}mimeinfo.cache 2> /dev/null | cut -d '=' -f 2 | cut -d ';' -f 1` + if [ -n "$trader_result" ] ; then + echo $trader_result + exit_success + fi + done + done + + defapp_fallback $MIME + exit_success +} + +defapp_kde() +{ + MIME="$1" + if [ x"$KDE_SESSION_VERSION" = x"4" ]; then + KTRADER=`which ktraderclient 2> /dev/null` + MIMETYPE="--mimetype" + SERVICETYPE="--servicetype" + else + KTRADER=`which ktradertest 2> /dev/null` + fi + if [ -n "$KTRADER" ] ; then + DEBUG 1 "Running KDE trader query \"$MIME\" mimetype and \"Application\" servicetype" + trader_result=`$KTRADER $MIMETYPE "$MIME" $SERVICETYPE Application 2>/dev/null \ + | grep DesktopEntryPath | head -n 1 | cut -d ':' -f 2 | cut -d \' -f 2` + if [ -n "$trader_result" ] ; then + basename "$trader_result" + exit_success + else + exit_failure_operation_failed + fi + else + defapp_generic "$1" + fi +} + +[ x"$1" != x"" ] || exit_failure_syntax + +mode= +action= +filename= +mimetype= + +case $1 in + install) + action=install + ;; + + uninstall) + action=uninstall + ;; + + query) + shift + + if [ -z "$1" ] ; then + exit_failure_syntax "query type argument missing" + fi + + case $1 in + filetype) + action=info + + filename="$2" + if [ -z "$filename" ] ; then + exit_failure_syntax "FILE argument missing" + fi + case $filename in + -*) + exit_failure_syntax "unexpected option '$filename'" + ;; + esac + check_input_file "$filename" + filename=`readlink -f -- "$filename"` + ;; + + default) + action=defapp + mimetype="$2" + if [ -z "$mimetype" ] ; then + exit_failure_syntax "mimetype argument missing" + fi + case $mimetype in + -*) + exit_failure_syntax "unexpected option '$mimetype'" + ;; + + */*) + # Ok + ;; + + *) + exit_failure_syntax "mimetype '$mimetype' is not in the form 'minor/major'" + ;; + esac + ;; + + *) + exit_failure_syntax "unknown query type '$1'" + ;; + esac + ;; + + default) + action=makedefault + shift + + if [ -z "$1" ] ; then + exit_failure_syntax "application argument missing" + fi + case $1 in + -*) + exit_failure_syntax "unexpected option '$1'" + ;; + + *.desktop) + filename="$1" + ;; + + *) + exit_failure_syntax "malformed argument '$1', expected *.desktop" + ;; + esac + ;; + + *) + exit_failure_syntax "unknown command '$1'" + ;; +esac + +shift + + +if [ "$action" = "makedefault" ]; then + if [ -z "$1" ] ; then + exit_failure_syntax "mimetype argument missing" + fi + + while [ $# -gt 0 ] ; do + case $1 in + -*) + exit_failure_syntax "unexpected option '$1'" + ;; + esac + mimetype="$1" + shift + + make_default_kde "$filename" "$mimetype" + make_default_generic "$filename" "$mimetype" + done + exit_success +fi + +if [ "$action" = "info" ]; then + detectDE + + if [ x"$DE" = x"" ]; then + if [ -x /usr/bin/file ]; then + DE=generic + fi + fi + + case "$DE" in + kde) + info_kde "$filename" + ;; + + gnome*) + info_gnome "$filename" + ;; + + *) + info_generic "$filename" + ;; + esac + exit_failure_operation_impossible "no method available for quering MIME type of '$filename'" +fi + +if [ "$action" = "defapp" ]; then + detectDE + + case "$DE" in + kde) + defapp_kde "$mimetype" + ;; + + *) + defapp_generic "$mimetype" + ;; + esac + exit_failure_operation_impossible "no method available for quering default application for '$mimetype'" +fi + +vendor=true +while [ $# -gt 0 ] ; do + parm="$1" + shift + + case $parm in + --mode) + if [ -z "$1" ] ; then + exit_failure_syntax "mode argument missing for --mode" + fi + case "$1" in + user) + mode="user" + ;; + + system) + mode="system" + ;; + + *) + exit_failure_syntax "unknown mode '$1'" + ;; + esac + shift + ;; + + --novendor) + vendor=false + ;; + + -*) + exit_failure_syntax "unexpected option '$parm'" + ;; + + *) + if [ -n "$filename" ] ; then + exit_failure_syntax "unexpected argument '$parm'" + fi + + filename="$parm" + check_input_file "$filename" + ;; + esac +done + +if [ -z "$action" ] ; then + exit_failure_syntax "command argument missing" +fi + +if [ -n "$XDG_UTILS_INSTALL_MODE" ] ; then + if [ "$XDG_UTILS_INSTALL_MODE" = "system" ] ; then + mode="system" + elif [ "$XDG_UTILS_INSTALL_MODE" = "user" ] ; then + mode="user" + fi +fi + +if [ -z "$mode" ] ; then + if [ `whoami` = "root" ] ; then + mode="system" + else + mode="user" + fi +fi + +if [ -z "$filename" ] ; then + exit_failure_syntax "mimetypes-file argument missing" +fi + +if [ "$vendor" = "true" -a "$action" = "install" ] ; then + check_vendor_prefix "$filename" +fi + +xdg_base_dir= +xdg_dir_name=mime/packages/ + +xdg_user_dir="$XDG_DATA_HOME" +[ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" +[ x"$mode" = x"user" ] && xdg_base_dir="$xdg_user_dir/mime" +xdg_user_dir="$xdg_user_dir/$xdg_dir_name" + +xdg_system_dirs="$XDG_DATA_DIRS" +[ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ +for x in `echo $xdg_system_dirs | sed 's/:/ /g'`; do + if [ -w $x/$xdg_dir_name ] ; then + [ x"$mode" = x"system" ] && xdg_base_dir="$x/mime" + xdg_global_dir="$x/$xdg_dir_name" + break + fi +done +[ -w $xdg_global_dir ] || xdg_global_dir= +DEBUG 3 "xdg_user_dir: $xdg_user_dir" +DEBUG 3 "xdg_global_dir: $xdg_global_dir" + +# Find KDE3 mimelnk directory +kde_user_dir= +kde_global_dir= +kde_global_dirs=`kde${KDE_SESSION_VERSION}-config --path mime 2> /dev/null` +DEBUG 3 "kde_global_dirs: $kde_global_dirs" +first= +for x in `echo $kde_global_dirs | sed 's/:/ /g'` ; do + if [ -z "$first" ] ; then + first=false + kde_user_dir="$x" + elif [ -w $x ] ; then + kde_global_dir="$x" + fi +done +DEBUG 3 "kde_user_dir: $kde_user_dir" +DEBUG 3 "kde_global_dir: $kde_global_dir" + +# TODO: Gnome legacy support +# See http://forums.fedoraforum.org/showthread.php?t=26875 +gnome_user_dir="$HOME/.gnome/apps" +gnome_global_dir=/usr/share/gnome/apps +[ -w $gnome_global_dir ] || gnome_global_dir= +DEBUG 3 "gnome_user_dir: $gnome_user_dir" +DEBUG 3 "gnome_global_dir: $gnome_global_dir" + +if [ x"$mode" = x"user" ] ; then + xdg_dir="$xdg_user_dir" + kde_dir="$kde_user_dir" + gnome_dir="$gnome_user_dir" + my_umask=077 +else + xdg_dir="$xdg_global_dir" + kde_dir="$kde_global_dir" + gnome_dir="$gnome_global_dir" + my_umask=022 + if [ -z "${xdg_dir}${kde_dir}${gnome_dir}" ] ; then + exit_failure_operation_impossible "No writable system mimetype directory found." + fi +fi + +# echo "[xdg|$xdg_user_dir|$xdg_global_dir]" +# echo "[kde|$kde_user_dir|$kde_global_dir]" +# echo "[gnome|$gnome_user_dir|$gnome_global_dir]" +# echo "[using|$xdg_dir|$kde_dir|$gnome_dir]" + +basefile=`basename "$filename"` +#[ -z $vendor ] || basefile="$vendor-$basefile" + +mimetypes= +if [ -n "$kde_dir" ] ; then + DEBUG 2 "KDE3 mimelnk directory found, extracting mimetypes from XML file" + + mimetypes=`awk < "$filename" ' +# Strip XML comments +BEGIN { + suppress=0 +} +{ + do + if (suppress) { + if (match($0,/-->/)) { + $0=substr($0,RSTART+RLENGTH) + suppress=0 + } + else { + break + } + } + else { + if (match($0,//)) { + $0=substr($0,RSTART+RLENGTH) + suppress=0 + } + else { + break + } + } + else { + if (match($0,//)) { + $0=substr($0,RSTART+RLENGTH) + suppress=0 + } + else { + break + } + } + else { + if (match($0,//)) { + $0=substr($0,RSTART+RLENGTH) + suppress=0 + } + else { + break + } + } + else { + if (match($0,/