1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: jan@swi-prolog.org 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2026, SWI-Prolog Solutions b.v. 7 All rights reserved. 8 9 Redistribution and use in source and binary forms, with or without 10 modification, are permitted provided that the following conditions 11 are met: 12 13 1. Redistributions of source code must retain the above copyright 14 notice, this list of conditions and the following disclaimer. 15 16 2. Redistributions in binary form must reproduce the above copyright 17 notice, this list of conditions and the following disclaimer in 18 the documentation and/or other materials provided with the 19 distribution. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 POSSIBILITY OF SUCH DAMAGE. 33*/ 34 35:- module(desktop, 36 [ desktop_open/1, % +Document 37 desktop_open/2 % +Document, +Options 38 ]). 39:- autoload(library(apply),[maplist/2,maplist/3]). 40:- autoload(library(error),[must_be/2,existence_error/2]). 41:- autoload(library(lists),[append/3]). 42:- autoload(library(option),[option/2,option/3]). 43:- autoload(library(process),[process_create/3,process_which/2]). 44:- autoload(library(uri),[uri_is_global/1]).
https://www.swi-prolog.org or mailto:bugs@example.com.
The URL is passed to the opener unmodified.~, $var and wildcards are
expanded and all matching files are opened.Options processed:
Command-Args to
pass the arguments Args before Document or win_shell to use
the Windows ShellExecute() API.true, wait for the opener to complete and raise an
exception if it fails. This does not wait for the
application: openers such as xdg-open merely hand the
document to the desktop and complete immediately, normally with
success regardless of what the desktop does with it. Default is
false, which starts the opener using the detached(true) option
of process_create/3. The option is ignored if win_shell is
used, which never waits.The command to use is taken from the first of these that yields an available command:
opener(Command)desktop_opener if it is not default. It
uses the same syntax as the opener(Command) option.open on MacOScygstart (Cygwin), termux-open (Android/Termux) or
wslview (WSL without a Linux desktop)xdg-open (freedesktop.org), followed by the desktop
specific gio, gnome-open, kde-open, kde-open5
and exo-openwslview, handlr, mimeopen or run-mailcapopen, unless we are on Linux, where open is an alias
for openvt(1) rather than a document opener.110:- predicate_options(desktop_open/2, 2, 111 [ opener(any), 112 wait(boolean) 113 ]). 114 115:- create_prolog_flag(desktop_opener, default, 116 [ type(term), 117 keep(true) 118 ]). 119 120desktop_open(Document) :- 121 desktop_open(Document, []). 122 123desktop_open(Document, Options) :- 124 document_arguments(Document, Arguments), 125 opener(Command, Options), 126 maplist(open_argument(Command, Options), Arguments).
file(File) terms to pass to the
opener, one invocation per element.133document_arguments(Document, [Document]) :- 134 atomic(Document), 135 uri_is_global(Document), 136 !. 137document_arguments(Document, Arguments) :- 138 document_files(Document, Files), 139 maplist(file_argument, Files, Arguments). 140 141file_argument(File, file(File)).
149document_files(Spec, [File]) :- 150 compound(Spec), 151 !, 152 ( absolute_file_name(Spec, File, 153 [ access(exist), 154 file_type(directory), 155 file_errors(fail) 156 ]) 157 -> true 158 ; absolute_file_name(Spec, File, 159 [ access(exist), 160 file_errors(error) 161 ]) 162 ). 163document_files(Spec, Files) :- 164 must_be(atomic, Spec), 165 atom_string(Name, Spec), 166 ( access_file(Name, exist) % also true for directories 167 -> Expanded = [Name] % do not expand ``$`` and ``*`` 168 ; catch(expand_file_name(Name, Expanded), error(_,_), 169 existence_error(source_sink, Spec)), 170 Expanded \== [] 171 -> true 172 ; existence_error(source_sink, Spec) 173 ), 174 maplist(existing_file, Expanded, Files). 175 176existing_file(File, Absolute) :- 177 ( access_file(File, exist) % also true for directories 178 -> absolute_file_name(File, Absolute) 179 ; existence_error(source_sink, File) 180 ).
win_shell
or a term Exe-Args, where Exe is the absolute path of the executable
and Args are arguments that precede the document.188opener(Command, Options) :- 189 option(opener(Spec), Options), 190 !, 191 ( opener_command(Spec, Command0), 192 resolve_command(Command0, Command) 193 -> true 194 ; existence_error(program, Spec) 195 ). 196opener(Command, _Options) :- 197 ( current_prolog_flag(desktop_opener, Spec), 198 Spec \== default, 199 opener_command(Spec, Command0), 200 resolve_command(Command0, Command) 201 -> true 202 ; default_opener(Command0), 203 resolve_command(Command0, Command) 204 -> true 205 ; existence_error(config, desktop_opener) 206 ). 207 208opener_command(win_shell, Command) => 209 Command = win_shell. 210opener_command(Exe-Args, Command) => 211 must_be(list, Args), 212 Command = Exe-Args. 213opener_command(Exe, Command) => 214 Command = Exe-[]. 215 216:- if(current_predicate(win_shell/2)). 217resolve_command(win_shell, Command) => 218 Command = win_shell. 219:- endif. 220resolve_command(Exe-Args, Command) => 221 opener_executable(Exe, Path), 222 Command = Path-Args. 223resolve_command(_, _) => 224 fail.
$PATH.
231opener_executable(Command, Path) :-
232 ( atom(Command),
233 file_base_name(Command, Command) % plain name
234 -> process_which(path(Command), Path)
235 ; process_which(Command, Path)
236 ).open on Linux, where
this is an alias for openvt(1) rather than a document opener.244:- if(current_predicate(win_shell/2)). 245default_opener(win_shell). % Windows ShellExecute() 246:- endif. 247default_opener(open-[]) :- % MacOS 248 current_prolog_flag(apple, true). 249default_opener(cygstart-[]). % Cygwin 250default_opener('termux-open'-[]). % Android (Termux) 251default_opener(wslview-[]) :- % WSL without Linux desktop 252 getenv('WSL_DISTRO_NAME', _), 253 \+ getenv('DISPLAY', _), 254 \+ getenv('WAYLAND_DISPLAY', _). 255default_opener('xdg-open'-[]). % freedesktop.org 256default_opener(gio-[open]). % Gnome (GIO) 257default_opener('gnome-open'-[]). % Gnome (deprecated) 258default_opener('kde-open'-[]). % KDE 259default_opener('kde-open5'-[]). 260default_opener('exo-open'-[]). % XFCE 261default_opener(wslview-[]). % WSL (wslu) 262default_opener(handlr-[open]). % handlr 263default_opener(mimeopen-['-n']). % Perl File::MimeInfo 264default_opener('run-mailcap'-[]). % mailcap(5) 265default_opener(open-[]) :- % Haiku and friends 266 \+ linux. 267 268linux :- 269 current_prolog_flag(arch, Arch), 270 sub_atom(Arch, _, _, _, linux), 271 !.
277:- if(current_predicate(win_shell/2)). 278open_argument(win_shell, _Options, Argument) :- 279 !, 280 win_shell_argument(Argument, OsArgument), 281 win_shell(open, OsArgument). 282:- endif. 283open_argument(Exe-Args, Options, Argument) :- 284 append(Args, [Argument], Argv), 285 ( option(wait(Wait), Options, false), 286 must_be(boolean, Wait), 287 Wait == true 288 -> process_create(Exe, Argv, 289 [ stdin(null), 290 stdout(null) 291 ]) 292 ; process_create(Exe, Argv, 293 [ detached(true), 294 stdin(null), 295 stdout(null), 296 stderr(null) 297 ]) 298 ). 299 300:- if(current_predicate(win_shell/2)). 301win_shell_argument(file(File), OsFile) :- 302 !, 303 prolog_to_os_filename(File, OsFile). 304win_shell_argument(URL, URL). 305:- endif.
Interact with the desktop environment
This library provides access to the desktop environment of the user. Currently it only provides desktop_open/1,2, which hands a document over to the application the desktop associates with it.