xdg-open 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. #!/bin/sh
  2. #---------------------------------------------
  3. # xdg-open
  4. #
  5. # Utility script to open a URL in the registered default application.
  6. #
  7. # Refer to the usage() function below for usage.
  8. #
  9. # Copyright 2009-2010, Fathi Boudra <fabo@freedesktop.org>
  10. # Copyright 2009-2016, Rex Dieter <rdieter@fedoraproject.org>
  11. # Copyright 2006, Kevin Krammer <kevin.krammer@gmx.at>
  12. # Copyright 2006, Jeremy White <jwhite@codeweavers.com>
  13. #
  14. # LICENSE:
  15. #
  16. # Permission is hereby granted, free of charge, to any person obtaining a
  17. # copy of this software and associated documentation files (the "Software"),
  18. # to deal in the Software without restriction, including without limitation
  19. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  20. # and/or sell copies of the Software, and to permit persons to whom the
  21. # Software is furnished to do so, subject to the following conditions:
  22. #
  23. # The above copyright notice and this permission notice shall be included
  24. # in all copies or substantial portions of the Software.
  25. #
  26. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  27. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  28. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  29. # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  30. # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  31. # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  32. # OTHER DEALINGS IN THE SOFTWARE.
  33. #
  34. #---------------------------------------------
  35. manualpage()
  36. {
  37. cat << '_MANUALPAGE'
  38. Name
  39. xdg-open -- opens a file or URL in the user's preferred
  40. application
  41. Synopsis
  42. xdg-open { file | URL }
  43. xdg-open { --help | --manual | --version }
  44. Description
  45. xdg-open opens a file or URL in the user's preferred
  46. application. If a URL is provided the URL will be opened in the
  47. user's preferred web browser. If a file is provided the file
  48. will be opened in the preferred application for files of that
  49. type. xdg-open supports file, ftp, http and https URLs.
  50. xdg-open is for use inside a desktop session only. It is not
  51. recommended to use xdg-open as root.
  52. As xdg-open can not handle arguments that begin with a "-" it
  53. is recommended to pass filepaths in one of the following ways:
  54. * Pass absolute paths, i.e. by using realpath as a
  55. preprocessor.
  56. * Prefix known relative filepaths with a "./". For example
  57. using sed -E 's|^[^/]|./\0|'.
  58. * Pass a file URL.
  59. Options
  60. --help
  61. Show command synopsis.
  62. --manual
  63. Show this manual page.
  64. --version
  65. Show the xdg-utils version information.
  66. Exit Codes
  67. An exit code of 0 indicates success while a non-zero exit code
  68. indicates failure. The following failure codes can be returned:
  69. 1
  70. Error in command line syntax.
  71. 2
  72. One of the files passed on the command line did not
  73. exist.
  74. 3
  75. A required tool could not be found.
  76. 4
  77. The action failed.
  78. In case of success the process launched from the .desktop file
  79. will not be forked off and therefore may result in xdg-open
  80. running for a very long time. This behaviour intentionally
  81. differs from most desktop specific openers to allow terminal
  82. based applications to run using the same terminal xdg-open was
  83. called from.
  84. Reporting Issues
  85. Please keep in mind xdg-open inherits most of the flaws of its
  86. configuration and the underlying opener.
  87. In case the command xdg-mime query default "$(xdg-mime query
  88. filetype path/to/troublesome_file)" names the program
  89. responsible for any unexpected behaviour you can fix that by
  90. setting a different handler. (If the program is broken let the
  91. developers know)
  92. Also see the security note on xdg-mime(1) for the default
  93. subcommand.
  94. If a flaw is reproducible using the desktop specific opener
  95. (and isn't a configuration issue): Please report to whoever is
  96. responsible for that first (reporting to xdg-utils is better
  97. than not reporting at all, but since the xdg-utils are
  98. maintained in very little spare time a fix will take much
  99. longer)
  100. In case an issue specific to xdg-open please report it to
  101. https://gitlab.freedesktop.org/xdg/xdg-utils/-/issues .
  102. See Also
  103. xdg-mime(1), xdg-settings(1), MIME applications associations
  104. specification
  105. Examples
  106. xdg-open 'http://www.freedesktop.org/'
  107. Opens the freedesktop.org website in the user's default
  108. browser.
  109. xdg-open /tmp/foobar.png
  110. Opens the PNG image file /tmp/foobar.png in the user's default
  111. image viewing application.
  112. _MANUALPAGE
  113. }
  114. usage()
  115. {
  116. cat << '_USAGE'
  117. xdg-open -- opens a file or URL in the user's preferred
  118. application
  119. Synopsis
  120. xdg-open { file | URL }
  121. xdg-open { --help | --manual | --version }
  122. _USAGE
  123. }
  124. #@xdg-utils-common@
  125. #----------------------------------------------------------------------------
  126. # Common utility functions included in all XDG wrapper scripts
  127. #----------------------------------------------------------------------------
  128. #shellcheck shell=sh
  129. DEBUG()
  130. {
  131. [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0;
  132. [ "${XDG_UTILS_DEBUG_LEVEL}" -lt "$1" ] && return 0;
  133. shift
  134. echo "$@" >&2
  135. }
  136. # This handles backslashes but not quote marks.
  137. first_word()
  138. {
  139. # shellcheck disable=SC2162 # No -r is intended here
  140. read first rest
  141. echo "$first"
  142. }
  143. #-------------------------------------------------------------
  144. # map a binary to a .desktop file
  145. binary_to_desktop_file()
  146. {
  147. search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
  148. binary="$(command -v "$1")"
  149. binary="$(xdg_realpath "$binary")"
  150. base="$(basename "$binary")"
  151. IFS=:
  152. for dir in $search; do
  153. unset IFS
  154. [ "$dir" ] || continue
  155. [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue
  156. for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do
  157. [ -r "$file" ] || continue
  158. # Check to make sure it's worth the processing.
  159. grep -q "^Exec.*$base" "$file" || continue
  160. # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop").
  161. grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue
  162. command="$(grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word)"
  163. command="$(command -v "$command")"
  164. if [ x"$(xdg_realpath "$command")" = x"$binary" ]; then
  165. # Fix any double slashes that got added path composition
  166. echo "$file" | tr -s /
  167. return
  168. fi
  169. done
  170. done
  171. }
  172. #-------------------------------------------------------------
  173. # map a .desktop file to a binary
  174. desktop_file_to_binary()
  175. {
  176. search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
  177. desktop="$(basename "$1")"
  178. IFS=:
  179. for dir in $search; do
  180. unset IFS
  181. [ "$dir" ] && [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue
  182. # Check if desktop file contains -
  183. if [ "${desktop#*-}" != "$desktop" ]; then
  184. vendor=${desktop%-*}
  185. app=${desktop#*-}
  186. if [ -r "$dir/applications/$vendor/$app" ]; then
  187. file_path="$dir/applications/$vendor/$app"
  188. elif [ -r "$dir/applnk/$vendor/$app" ]; then
  189. file_path="$dir/applnk/$vendor/$app"
  190. fi
  191. fi
  192. if test -z "$file_path" ; then
  193. for indir in "$dir"/applications/ "$dir"/applications/*/ "$dir"/applnk/ "$dir"/applnk/*/; do
  194. file="$indir/$desktop"
  195. if [ -r "$file" ]; then
  196. file_path=$file
  197. break
  198. fi
  199. done
  200. fi
  201. if [ -r "$file_path" ]; then
  202. # Remove any arguments (%F, %f, %U, %u, etc.).
  203. command="$(grep -E "^Exec(\[[^]=]*])?=" "$file_path" | cut -d= -f 2- | first_word)"
  204. command="$(command -v "$command")"
  205. xdg_realpath "$command"
  206. return
  207. fi
  208. done
  209. }
  210. #-------------------------------------------------------------
  211. # Exit script on successfully completing the desired operation
  212. # shellcheck disable=SC2120 # It is okay to call this without arguments
  213. exit_success()
  214. {
  215. if [ $# -gt 0 ]; then
  216. echo "$*"
  217. echo
  218. fi
  219. exit 0
  220. }
  221. #-----------------------------------------
  222. # Exit script on malformed arguments, not enough arguments
  223. # or missing required option.
  224. # prints usage information
  225. exit_failure_syntax()
  226. {
  227. if [ $# -gt 0 ]; then
  228. echo "xdg-open: $*" >&2
  229. echo "Try 'xdg-open --help' for more information." >&2
  230. else
  231. usage
  232. echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
  233. fi
  234. exit 1
  235. }
  236. #-------------------------------------------------------------
  237. # Exit script on missing file specified on command line
  238. exit_failure_file_missing()
  239. {
  240. if [ $# -gt 0 ]; then
  241. echo "xdg-open: $*" >&2
  242. fi
  243. exit 2
  244. }
  245. #-------------------------------------------------------------
  246. # Exit script on failure to locate necessary tool applications
  247. exit_failure_operation_impossible()
  248. {
  249. if [ $# -gt 0 ]; then
  250. echo "xdg-open: $*" >&2
  251. fi
  252. exit 3
  253. }
  254. #-------------------------------------------------------------
  255. # Exit script on failure returned by a tool application
  256. exit_failure_operation_failed()
  257. {
  258. if [ $# -gt 0 ]; then
  259. echo "xdg-open: $*" >&2
  260. fi
  261. exit 4
  262. }
  263. #------------------------------------------------------------
  264. # Exit script on insufficient permission to read a specified file
  265. exit_failure_file_permission_read()
  266. {
  267. if [ $# -gt 0 ]; then
  268. echo "xdg-open: $*" >&2
  269. fi
  270. exit 5
  271. }
  272. #------------------------------------------------------------
  273. # Exit script on insufficient permission to write a specified file
  274. exit_failure_file_permission_write()
  275. {
  276. if [ $# -gt 0 ]; then
  277. echo "xdg-open: $*" >&2
  278. fi
  279. exit 6
  280. }
  281. check_input_file()
  282. {
  283. if [ ! -e "$1" ]; then
  284. exit_failure_file_missing "file '$1' does not exist"
  285. fi
  286. if [ ! -r "$1" ]; then
  287. exit_failure_file_permission_read "no permission to read file '$1'"
  288. fi
  289. }
  290. check_vendor_prefix()
  291. {
  292. file_label="$2"
  293. [ -n "$file_label" ] || file_label="filename"
  294. file="$(basename "$1")"
  295. case "$file" in
  296. [[:alpha:]]*-*)
  297. return
  298. ;;
  299. esac
  300. echo "xdg-open: $file_label '$file' does not have a proper vendor prefix" >&2
  301. echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2
  302. echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2
  303. echo "Use --novendor to override or 'xdg-open --manual' for additional info." >&2
  304. exit 1
  305. }
  306. check_output_file()
  307. {
  308. # if the file exists, check if it is writeable
  309. # if it does not exists, check if we are allowed to write on the directory
  310. if [ -e "$1" ]; then
  311. if [ ! -w "$1" ]; then
  312. exit_failure_file_permission_write "no permission to write to file '$1'"
  313. fi
  314. else
  315. DIR="$(dirname "$1")"
  316. if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then
  317. exit_failure_file_permission_write "no permission to create file '$1'"
  318. fi
  319. fi
  320. }
  321. #----------------------------------------
  322. # Checks for shared commands, e.g. --help
  323. check_common_commands()
  324. {
  325. while [ $# -gt 0 ] ; do
  326. parm="$1"
  327. shift
  328. case "$parm" in
  329. --help)
  330. usage
  331. echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
  332. exit_success
  333. ;;
  334. --manual)
  335. manualpage
  336. exit_success
  337. ;;
  338. --version)
  339. echo "xdg-open 1.2.1"
  340. exit_success
  341. ;;
  342. --)
  343. [ -z "$XDG_UTILS_ENABLE_DOUBLE_HYPEN" ] || break
  344. ;;
  345. esac
  346. done
  347. }
  348. check_common_commands "$@"
  349. [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL;
  350. # shellcheck disable=SC2034
  351. if [ "${XDG_UTILS_DEBUG_LEVEL-0}" -lt 1 ]; then
  352. # Be silent
  353. xdg_redirect_output=" > /dev/null 2> /dev/null"
  354. else
  355. # All output to stderr
  356. xdg_redirect_output=" >&2"
  357. fi
  358. #--------------------------------------
  359. # Checks for known desktop environments
  360. # set variable DE to the desktop environments name, lowercase
  361. detectDE()
  362. {
  363. # see https://bugs.freedesktop.org/show_bug.cgi?id=34164
  364. unset GREP_OPTIONS
  365. if [ -n "${XDG_CURRENT_DESKTOP}" ]; then
  366. case "${XDG_CURRENT_DESKTOP}" in
  367. # only recently added to menu-spec, pre-spec X- still in use
  368. Cinnamon|X-Cinnamon)
  369. DE=cinnamon;
  370. ;;
  371. ENLIGHTENMENT)
  372. DE=enlightenment;
  373. ;;
  374. # GNOME, GNOME-Classic:GNOME, or GNOME-Flashback:GNOME
  375. GNOME*)
  376. DE=gnome;
  377. ;;
  378. KDE)
  379. DE=kde;
  380. ;;
  381. DEEPIN|Deepin|deepin)
  382. DE=deepin;
  383. ;;
  384. LXDE)
  385. DE=lxde;
  386. ;;
  387. LXQt)
  388. DE=lxqt;
  389. ;;
  390. MATE)
  391. DE=mate;
  392. ;;
  393. XFCE)
  394. DE=xfce
  395. ;;
  396. X-Generic)
  397. DE=generic
  398. ;;
  399. esac
  400. fi
  401. # shellcheck disable=SC2153
  402. if [ -z "$DE" ]; then
  403. # classic fallbacks
  404. if [ -n "$KDE_FULL_SESSION" ]; then DE=kde;
  405. elif [ -n "$GNOME_DESKTOP_SESSION_ID" ]; then DE=gnome;
  406. elif [ -n "$MATE_DESKTOP_SESSION_ID" ]; then DE=mate;
  407. elif dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.gnome.SessionManager > /dev/null 2>&1 ; then DE=gnome;
  408. elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce;
  409. elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce
  410. elif echo "$DESKTOP" | grep -q '^Enlightenment'; then DE=enlightenment;
  411. elif [ -n "$LXQT_SESSION_CONFIG" ]; then DE=lxqt;
  412. fi
  413. fi
  414. if [ -z "$DE" ]; then
  415. # fallback to checking $DESKTOP_SESSION
  416. case "$DESKTOP_SESSION" in
  417. gnome)
  418. DE=gnome;
  419. ;;
  420. LXDE|Lubuntu)
  421. DE=lxde;
  422. ;;
  423. MATE)
  424. DE=mate;
  425. ;;
  426. xfce|xfce4|'Xfce Session')
  427. DE=xfce;
  428. ;;
  429. esac
  430. fi
  431. if [ -z "$DE" ]; then
  432. # fallback to uname output for other platforms
  433. case "$(uname 2>/dev/null)" in
  434. CYGWIN*)
  435. DE=cygwin;
  436. ;;
  437. Darwin)
  438. DE=darwin;
  439. ;;
  440. Linux)
  441. grep -q microsoft /proc/version > /dev/null 2>&1 && \
  442. command -v explorer.exe > /dev/null 2>&1 && \
  443. DE=wsl;
  444. ;;
  445. esac
  446. fi
  447. if [ x"$DE" = x"gnome" ]; then
  448. # gnome-default-applications-properties is only available in GNOME 2.x
  449. # but not in GNOME 3.x
  450. command -v gnome-default-applications-properties > /dev/null || DE="gnome3"
  451. fi
  452. if [ -f "$XDG_RUNTIME_DIR/flatpak-info" ]; then
  453. DE="flatpak"
  454. fi
  455. }
  456. #----------------------------------------------------------------------------
  457. # kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4
  458. # It also always returns 1 in KDE 3.4 and earlier
  459. # Simply return 0 in such case
  460. kfmclient_fix_exit_code()
  461. {
  462. version="$(LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE')"
  463. major="$(echo "$version" | sed 's/KDE.*: \([0-9]\).*/\1/')"
  464. minor="$(echo "$version" | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/')"
  465. release="$(echo "$version" | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/')"
  466. test "$major" -gt 3 && return "$1"
  467. test "$minor" -gt 5 && return "$1"
  468. test "$release" -gt 4 && return "$1"
  469. return 0
  470. }
  471. #----------------------------------------------------------------------------
  472. # Returns true if there is a graphical display attached.
  473. has_display()
  474. {
  475. if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; then
  476. return 0
  477. else
  478. return 1
  479. fi
  480. }
  481. #----------------------------------------------------------------------------
  482. # Prefixes a path with a "./" if it starts with a "-".
  483. # This is useful for programs to not confuse paths with options.
  484. unoption_path()
  485. {
  486. case "$1" in
  487. -*)
  488. printf "./%s" "$1" ;;
  489. *)
  490. printf "%s" "$1" ;;
  491. esac
  492. }
  493. #----------------------------------------------------------------------------
  494. # Performs a symlink and relative path resolving for a single argument.
  495. # This will always fail if the given file does not exist!
  496. xdg_realpath()
  497. {
  498. # allow caching and external configuration
  499. if [ -z "$XDG_UTILS_REALPATH_BACKEND" ] ; then
  500. if command -v realpath >/dev/null 2>/dev/null ; then
  501. lines="$(realpath -- / 2>&1)"
  502. if [ $? = 0 ] && [ "$lines" = "/" ] ; then
  503. XDG_UTILS_REALPATH_BACKEND="realpath"
  504. else
  505. # The realpath took the -- literally, probably the busybox implementation
  506. XDG_UTILS_REALPATH_BACKEND="busybox-realpath"
  507. fi
  508. unset lines
  509. elif command -v readlink >/dev/null 2>/dev/null ; then
  510. XDG_UTILS_REALPATH_BACKEND="readlink"
  511. else
  512. exit_failure_operation_failed "No usable realpath backend found. Have a realpath binary or a readlink -f that canonicalizes paths."
  513. fi
  514. fi
  515. # Always fail if the file doesn't exist (busybox realpath does that for example)
  516. [ -e "$1" ] || return 1
  517. case "$XDG_UTILS_REALPATH_BACKEND" in
  518. realpath)
  519. realpath -- "$1"
  520. ;;
  521. busybox-realpath)
  522. # busybox style realpath implementations have options too
  523. realpath "$(unoption_path "$1")"
  524. ;;
  525. readlink)
  526. readlink -f "$(unoption_path "$1")"
  527. ;;
  528. *)
  529. exit_failure_operation_impossible "Realpath backend '$XDG_UTILS_REALPATH_BACKEND' not recognized."
  530. ;;
  531. esac
  532. }
  533. # This handles backslashes but not quote marks.
  534. last_word()
  535. {
  536. # Backslash handling is intended, not using `first` too
  537. # shellcheck disable=SC2162,SC2034
  538. read first rest
  539. echo "$rest"
  540. }
  541. # Get the value of a key in a desktop file's Desktop Entry group.
  542. # Example: Use get_key foo.desktop Exec
  543. # to get the values of the Exec= key for the Desktop Entry group.
  544. get_key()
  545. {
  546. local file="${1}"
  547. local key="${2}"
  548. local desktop_entry=""
  549. IFS_="${IFS}"
  550. IFS=""
  551. # No backslash handling here, first_word and last_word do that
  552. while read -r line
  553. do
  554. case "$line" in
  555. "[Desktop Entry]")
  556. desktop_entry="y"
  557. ;;
  558. # Reset match flag for other groups
  559. "["*)
  560. desktop_entry=""
  561. ;;
  562. "${key}="*)
  563. # Only match Desktop Entry group
  564. if [ -n "${desktop_entry}" ]
  565. then
  566. echo "${line}" | cut -d= -f 2-
  567. fi
  568. esac
  569. done < "${file}"
  570. IFS="${IFS_}"
  571. }
  572. has_url_scheme()
  573. {
  574. echo "$1" | LC_ALL=C grep -Eq '^[[:alpha:]][[:alpha:][:digit:]+\.\-]*:'
  575. }
  576. # Returns true if argument is a file:// URL or path
  577. is_file_url_or_path()
  578. {
  579. if echo "$1" | grep -q '^file://' || ! has_url_scheme "$1" ; then
  580. return 0
  581. else
  582. return 1
  583. fi
  584. }
  585. get_hostname() {
  586. if [ -z "$HOSTNAME" ]; then
  587. if command -v hostname > /dev/null; then
  588. HOSTNAME=$(hostname)
  589. else
  590. HOSTNAME=$(uname -n)
  591. fi
  592. fi
  593. }
  594. # If argument is a file URL, convert it to a (percent-decoded) path.
  595. # If not, leave it as it is.
  596. file_url_to_path()
  597. {
  598. local file="$1"
  599. get_hostname
  600. if echo "$file" | grep -q '^file://'; then
  601. file=${file#file://localhost}
  602. file=${file#file://"$HOSTNAME"}
  603. file=${file#file://}
  604. if ! echo "$file" | grep -q '^/'; then
  605. echo "$file"
  606. return
  607. fi
  608. file=${file%%#*}
  609. file=${file%%\?*}
  610. local printf=printf
  611. if [ -x /usr/bin/printf ]; then
  612. printf=/usr/bin/printf
  613. fi
  614. file=$($printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")
  615. fi
  616. echo "$file"
  617. }
  618. open_cygwin()
  619. {
  620. cygstart "$1"
  621. if [ $? -eq 0 ]; then
  622. exit_success
  623. else
  624. exit_failure_operation_failed
  625. fi
  626. }
  627. open_darwin()
  628. {
  629. open "$1"
  630. if [ $? -eq 0 ]; then
  631. exit_success
  632. else
  633. exit_failure_operation_failed
  634. fi
  635. }
  636. open_kde()
  637. {
  638. if [ -n "${KDE_SESSION_VERSION}" ]; then
  639. case "${KDE_SESSION_VERSION}" in
  640. 4)
  641. kde-open "$1"
  642. ;;
  643. 5)
  644. "kde-open${KDE_SESSION_VERSION}" "$1"
  645. ;;
  646. 6)
  647. kde-open "$1"
  648. ;;
  649. esac
  650. else
  651. kfmclient exec "$1"
  652. kfmclient_fix_exit_code $?
  653. fi
  654. if [ $? -eq 0 ]; then
  655. exit_success
  656. else
  657. exit_failure_operation_failed
  658. fi
  659. }
  660. open_deepin()
  661. {
  662. if dde-open -version >/dev/null 2>&1; then
  663. dde-open "$1"
  664. else
  665. open_generic "$1"
  666. fi
  667. if [ $? -eq 0 ]; then
  668. exit_success
  669. else
  670. exit_failure_operation_failed
  671. fi
  672. }
  673. open_gnome3()
  674. {
  675. if gio help open 2>/dev/null 1>&2; then
  676. gio open "$1"
  677. elif gvfs-open --help 2>/dev/null 1>&2; then
  678. gvfs-open "$1"
  679. else
  680. open_generic "$1"
  681. fi
  682. if [ $? -eq 0 ]; then
  683. exit_success
  684. else
  685. exit_failure_operation_failed
  686. fi
  687. }
  688. open_gnome()
  689. {
  690. if gio help open 2>/dev/null 1>&2; then
  691. gio open "$1"
  692. elif gvfs-open --help 2>/dev/null 1>&2; then
  693. gvfs-open "$1"
  694. elif gnome-open --help 2>/dev/null 1>&2; then
  695. gnome-open "$1"
  696. else
  697. open_generic "$1"
  698. fi
  699. if [ $? -eq 0 ]; then
  700. exit_success
  701. else
  702. exit_failure_operation_failed
  703. fi
  704. }
  705. open_mate()
  706. {
  707. if gio help open 2>/dev/null 1>&2; then
  708. gio open "$1"
  709. elif gvfs-open --help 2>/dev/null 1>&2; then
  710. gvfs-open "$1"
  711. elif mate-open --help 2>/dev/null 1>&2; then
  712. mate-open "$1"
  713. else
  714. open_generic "$1"
  715. fi
  716. if [ $? -eq 0 ]; then
  717. exit_success
  718. else
  719. exit_failure_operation_failed
  720. fi
  721. }
  722. open_xfce()
  723. {
  724. if exo-open --help 2>/dev/null 1>&2; then
  725. exo-open "$1"
  726. elif gio help open 2>/dev/null 1>&2; then
  727. gio open "$1"
  728. elif gvfs-open --help 2>/dev/null 1>&2; then
  729. gvfs-open "$1"
  730. else
  731. open_generic "$1"
  732. fi
  733. if [ $? -eq 0 ]; then
  734. exit_success
  735. else
  736. exit_failure_operation_failed
  737. fi
  738. }
  739. open_enlightenment()
  740. {
  741. if enlightenment_open --help 2>/dev/null 1>&2; then
  742. enlightenment_open "$1"
  743. else
  744. open_generic "$1"
  745. fi
  746. if [ $? -eq 0 ]; then
  747. exit_success
  748. else
  749. exit_failure_operation_failed
  750. fi
  751. }
  752. open_flatpak()
  753. {
  754. if is_file_url_or_path "$1"; then
  755. local file
  756. file="$(file_url_to_path "$1")"
  757. check_input_file "$file"
  758. gdbus call --session \
  759. --dest org.freedesktop.portal.Desktop \
  760. --object-path /org/freedesktop/portal/desktop \
  761. --method org.freedesktop.portal.OpenURI.OpenFile \
  762. --timeout 5 \
  763. "" "3" {} 3< "$file"
  764. else
  765. # $1 contains an URI
  766. gdbus call --session \
  767. --dest org.freedesktop.portal.Desktop \
  768. --object-path /org/freedesktop/portal/desktop \
  769. --method org.freedesktop.portal.OpenURI.OpenURI \
  770. --timeout 5 \
  771. "" "$1" {}
  772. fi
  773. if [ $? -eq 0 ]; then
  774. exit_success
  775. else
  776. exit_failure_operation_failed
  777. fi
  778. }
  779. #-----------------------------------------
  780. # Recursively search .desktop file
  781. #(application, directory, target file, target_url)
  782. search_desktop_file()
  783. {
  784. local default="$1"
  785. local dir="$2"
  786. local target="$3"
  787. local target_uri="$4"
  788. local file=""
  789. # look for both vendor-app.desktop, vendor/app.desktop
  790. if [ -r "$dir/$default" ]; then
  791. file="$dir/$default"
  792. elif [ -r "$dir/$(echo "$default" | sed -e 's|-|/|')" ]; then
  793. file="$dir/$(echo "$default" | sed -e 's|-|/|')"
  794. fi
  795. if [ -r "$file" ] ; then
  796. command="$(get_key "${file}" "Exec" | first_word)"
  797. if command -v "$command" >/dev/null; then
  798. icon="$(get_key "${file}" "Icon")"
  799. # FIXME: Actually LC_MESSAGES should be used as described in
  800. # http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html
  801. localised_name="$(get_key "${file}" "Name")"
  802. #shellcheck disable=SC2046 # Splitting is intentional here
  803. set -- $(get_key "${file}" "Exec" | last_word)
  804. # We need to replace any occurrence of "%f", "%F" and
  805. # the like by the target file. We examine each
  806. # argument and append the modified argument to the
  807. # end then shift.
  808. local args=$#
  809. local replaced=0
  810. while [ $args -gt 0 ]; do
  811. case $1 in
  812. %[c])
  813. replaced=1
  814. arg="${localised_name}"
  815. shift
  816. set -- "$@" "$arg"
  817. ;;
  818. %[fF])
  819. # if there is only a target_url return,
  820. # this application can't handle it.
  821. [ -n "$target" ] || return
  822. replaced=1
  823. arg="$target"
  824. shift
  825. set -- "$@" "$arg"
  826. ;;
  827. %[uU])
  828. replaced=1
  829. # When an URI is requested use it,
  830. # otherwise fall back to the filepath.
  831. arg="${target_uri:-$target}"
  832. shift
  833. set -- "$@" "$arg"
  834. ;;
  835. %[i])
  836. replaced=1
  837. shift
  838. set -- "$@" "--icon" "$icon"
  839. ;;
  840. *)
  841. arg="$1"
  842. shift
  843. set -- "$@" "$arg"
  844. ;;
  845. esac
  846. args=$(( args - 1 ))
  847. done
  848. [ $replaced -eq 1 ] || set -- "$@" "${target:-$target_uri}"
  849. env "$command" "$@"
  850. exit_success
  851. fi
  852. fi
  853. for d in "$dir/"*/; do
  854. [ -d "$d" ] && search_desktop_file "$default" "$d" "$target" "$target_uri"
  855. done
  856. }
  857. # (file (or empty), mimetype, optional url)
  858. open_generic_xdg_mime()
  859. {
  860. filetype="$2"
  861. default="$(xdg-mime query default "$filetype")"
  862. if [ -n "$default" ] ; then
  863. xdg_user_dir="$XDG_DATA_HOME"
  864. [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share"
  865. xdg_system_dirs="$XDG_DATA_DIRS"
  866. [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/
  867. search_dirs="$xdg_user_dir:$xdg_system_dirs"
  868. DEBUG 3 "$search_dirs"
  869. old_ifs="$IFS"
  870. IFS=:
  871. for x in $search_dirs ; do
  872. IFS="$old_ifs"
  873. search_desktop_file "$default" "$x/applications/" "$1" "$3"
  874. done
  875. fi
  876. }
  877. open_generic_xdg_x_scheme_handler()
  878. {
  879. scheme="$(echo "$1" | LC_ALL=C sed -n 's/\(^[[:alpha:]][[:alnum:]+\.-]*\):.*$/\1/p')"
  880. if [ -n "$scheme" ]; then
  881. filetype="x-scheme-handler/$scheme"
  882. open_generic_xdg_mime "" "$filetype" "$1"
  883. fi
  884. }
  885. has_single_argument()
  886. {
  887. test $# = 1
  888. }
  889. open_envvar()
  890. {
  891. local oldifs="$IFS"
  892. local browser
  893. IFS=":"
  894. for browser in $BROWSER; do
  895. IFS="$oldifs"
  896. if [ -z "$browser" ]; then
  897. continue
  898. fi
  899. if echo "$browser" | grep -q %s; then
  900. # Avoid argument injection.
  901. # See https://bugs.freedesktop.org/show_bug.cgi?id=103807
  902. # URIs don't have IFS characters spaces anyway.
  903. # shellcheck disable=SC2086,SC2091,SC2059
  904. # All the scary things here are intentional
  905. has_single_argument $1 && $(printf "$browser" "$1")
  906. else
  907. $browser "$1"
  908. fi
  909. if [ $? -eq 0 ]; then
  910. exit_success
  911. fi
  912. done
  913. }
  914. open_wsl()
  915. {
  916. local win_path
  917. if is_file_url_or_path "$1" ; then
  918. win_path="$(file_url_to_path "$1")"
  919. win_path="$(wslpath -aw "$win_path")"
  920. [ $? -eq 0 ] || exit_failure_operation_failed
  921. explorer.exe "${win_path}"
  922. else
  923. rundll32.exe url.dll,FileProtocolHandler "$1"
  924. fi
  925. if [ $? -eq 0 ]; then
  926. exit_success
  927. else
  928. exit_failure_operation_failed
  929. fi
  930. }
  931. open_generic()
  932. {
  933. if is_file_url_or_path "$1"; then
  934. local file
  935. file="$(file_url_to_path "$1")"
  936. check_input_file "$file"
  937. if has_display; then
  938. filetype="$(xdg-mime query filetype "$file" | sed "s/;.*//")"
  939. # passing a path a url is okay too,
  940. # see desktop file specification for '%u'
  941. open_generic_xdg_mime "$file" "$filetype" "$1"
  942. fi
  943. if command -v run-mailcap >/dev/null; then
  944. run-mailcap --action=view "$file"
  945. if [ $? -eq 0 ]; then
  946. exit_success
  947. fi
  948. fi
  949. if has_display && mimeopen -v 2>/dev/null 1>&2; then
  950. mimeopen -L -n "$file"
  951. if [ $? -eq 0 ]; then
  952. exit_success
  953. fi
  954. fi
  955. fi
  956. if has_display; then
  957. open_generic_xdg_x_scheme_handler "$1"
  958. fi
  959. if [ -n "$BROWSER" ]; then
  960. open_envvar "$1"
  961. fi
  962. # if BROWSER variable is not set, check some well known browsers instead
  963. if [ x"$BROWSER" = x"" ]; then
  964. BROWSER=www-browser:links2:elinks:links:lynx:w3m
  965. if has_display; then
  966. BROWSER=x-www-browser:firefox:iceweasel:seamonkey:mozilla:epiphany:konqueror:chromium:chromium-browser:google-chrome:$BROWSER
  967. fi
  968. fi
  969. open_envvar "$1"
  970. exit_failure_operation_impossible "no method available for opening '$1'"
  971. }
  972. open_lxde()
  973. {
  974. # pcmanfm only knows how to handle file:// urls and filepaths, it seems.
  975. if pcmanfm --help >/dev/null 2>&1 && is_file_url_or_path "$1"; then
  976. local file
  977. file="$(file_url_to_path "$1")"
  978. # handle relative paths
  979. if ! echo "$file" | grep -q ^/; then
  980. file="$(pwd)/$file"
  981. fi
  982. pcmanfm "$file"
  983. else
  984. open_generic "$1"
  985. fi
  986. if [ $? -eq 0 ]; then
  987. exit_success
  988. else
  989. exit_failure_operation_failed
  990. fi
  991. }
  992. open_lxqt()
  993. {
  994. if qtxdg-mat open --help 2>/dev/null 1>&2; then
  995. qtxdg-mat open "$1"
  996. else
  997. exit_failure_operation_impossible "no method available for opening '$1'"
  998. fi
  999. if [ $? -eq 0 ]; then
  1000. exit_success
  1001. else
  1002. exit_failure_operation_failed
  1003. fi
  1004. }
  1005. [ x"$1" != x"" ] || exit_failure_syntax
  1006. url=
  1007. while [ $# -gt 0 ] ; do
  1008. parm="$1"
  1009. shift
  1010. case "$parm" in
  1011. -*)
  1012. exit_failure_syntax "unexpected option '$parm'"
  1013. ;;
  1014. *)
  1015. if [ -n "$url" ] ; then
  1016. exit_failure_syntax "unexpected argument '$parm'"
  1017. fi
  1018. url="$parm"
  1019. ;;
  1020. esac
  1021. done
  1022. if [ -z "${url}" ] ; then
  1023. exit_failure_syntax "file or URL argument missing"
  1024. fi
  1025. detectDE
  1026. if [ x"$DE" = x"" ]; then
  1027. DE=generic
  1028. fi
  1029. DEBUG 2 "Selected DE $DE"
  1030. # sanitize BROWSER (avoid calling ourselves in particular)
  1031. case "${BROWSER}" in
  1032. *:"xdg-open"|"xdg-open":*)
  1033. BROWSER="$(echo "$BROWSER" | sed -e 's|:xdg-open||g' -e 's|xdg-open:||g')"
  1034. ;;
  1035. "xdg-open")
  1036. BROWSER=
  1037. ;;
  1038. esac
  1039. case "$DE" in
  1040. kde)
  1041. open_kde "$url"
  1042. ;;
  1043. deepin)
  1044. open_deepin "$url"
  1045. ;;
  1046. gnome3|cinnamon)
  1047. open_gnome3 "$url"
  1048. ;;
  1049. gnome)
  1050. open_gnome "$url"
  1051. ;;
  1052. mate)
  1053. open_mate "$url"
  1054. ;;
  1055. xfce)
  1056. open_xfce "$url"
  1057. ;;
  1058. lxde)
  1059. open_lxde "$url"
  1060. ;;
  1061. lxqt)
  1062. open_lxqt "$url"
  1063. ;;
  1064. enlightenment)
  1065. open_enlightenment "$url"
  1066. ;;
  1067. cygwin)
  1068. open_cygwin "$url"
  1069. ;;
  1070. darwin)
  1071. open_darwin "$url"
  1072. ;;
  1073. flatpak)
  1074. open_flatpak "$url"
  1075. ;;
  1076. wsl)
  1077. open_wsl "$url"
  1078. ;;
  1079. generic)
  1080. open_generic "$url"
  1081. ;;
  1082. *)
  1083. exit_failure_operation_impossible "no method available for opening '$url'"
  1084. ;;
  1085. esac