View source with raw comments or as raw
    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]).

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.

 desktop_open(+Document) is det
 desktop_open(+Document, +Options) is det
Open Document using the application the desktop associates with it, e.g., a PDF viewer for a `.pdf` file or a file browser if Document is a directory. Document is one of

Options processed:

opener(+Command)
Use Command instead of the platform default. Command is a specification for process_which/2, a term Command-Args to pass the arguments Args before Document or win_shell to use the Windows ShellExecute() API.
wait(+Boolean)
If 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:

  1. The option opener(Command)
  2. The Prolog flag desktop_opener if it is not default. It uses the same syntax as the opener(Command) option.
  3. The Windows ShellExecute() API (see win_shell/2)
  4. open on MacOS
  5. cygstart (Cygwin), termux-open (Android/Termux) or wslview (WSL without a Linux desktop)
  6. xdg-open (freedesktop.org), followed by the desktop specific gio, gnome-open, kde-open, kde-open5 and exo-open
  7. wslview, handlr, mimeopen or run-mailcap
  8. open, unless we are on Linux, where open is an alias for openvt(1) rather than a document opener.
Errors
- existence_error(source_sink, Document) if Document does not exist.
- existence_error(config, desktop_opener) if no command to open documents is available.
- process_error(Command, exit(Status)) if wait(true) is used and Command does not exit successfully.
  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).
 document_arguments(+Document, -Arguments) is det
Arguments is the list of URLs and 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)).
 document_files(+Document, -Files) is det
Files is the list of existing files or directories denoted by Document. Compound terms are handed to absolute_file_name/3, which deals with both aliases and Dir/File terms.
  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    ).
 opener(-Command, +Options) is det
Find the command to open a document. Command is either 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.
 opener_executable(+Command, -Path) is semidet
Path is the absolute file name of Command. A plain name is searched for on $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    ).
 default_opener(-Command) is nondet
Enumerate commands that may be able to open a document in the order we prefer them. Note that we must not use 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    !.
 open_argument(+Command, +Options, +Argument) is det
Run Command on a single URL or file name.
  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.