summaryrefslogtreecommitdiff
path: root/zen/shell_execute.h
blob: 628e957a6605c07b0ca59239f3603d44814d4cba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// **************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0        *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#ifndef EXECUTE_HEADER_23482134578134134
#define EXECUTE_HEADER_23482134578134134

#include "file_error.h"

#ifdef ZEN_WIN
    #include "scope_guard.h"
    #include "win.h" //includes "windows.h"

#elif defined ZEN_LINUX || defined ZEN_MAC
    #include "thread.h"
    #include <stdlib.h> //::system()
#endif


namespace zen
{
//launch commandline and report errors via popup dialog
//windows: COM needs to be initialized before calling this function!
enum ExecutionType
{
    EXEC_TYPE_SYNC,
    EXEC_TYPE_ASYNC
};

namespace
{
#ifdef ZEN_WIN
template <class Function>
bool shellExecuteImpl(Function fillExecInfo, ExecutionType type)
{
    SHELLEXECUTEINFO execInfo = {};
    execInfo.cbSize = sizeof(execInfo);
    execInfo.lpVerb = nullptr;
    execInfo.nShow  = SW_SHOWNORMAL;
    execInfo.fMask  = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC) : 0;
    //don't use SEE_MASK_ASYNCOK -> different async mode than the default which returns successful despite errors!
    execInfo.fMask |= SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one
    //for the record, SEE_MASK_UNICODE does nothing: http://blogs.msdn.com/b/oldnewthing/archive/2014/02/27/10503519.aspx

    fillExecInfo(execInfo);

    if (!::ShellExecuteEx(&execInfo)) //__inout  LPSHELLEXECUTEINFO lpExecInfo
        return false;

    if (execInfo.hProcess)
    {
        ZEN_ON_SCOPE_EXIT(::CloseHandle(execInfo.hProcess));

        if (type == EXEC_TYPE_SYNC)
            ::WaitForSingleObject(execInfo.hProcess, INFINITE);
    }
    return true;
}


void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const Zstring& displayPath, ExecutionType type) //throw FileError
{
    if (!shellExecuteImpl([&](SHELLEXECUTEINFO& execInfo)
{
    execInfo.fMask |= SEE_MASK_IDLIST;
    execInfo.lpIDList = const_cast<void*>(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE!
    }, type)) //throw FileError
    throwFileError(_("Incorrect command line:") + L"\n" + fmtFileName(displayPath), L"ShellExecuteEx", ::GetLastError());
}
#endif


void shellExecute(const Zstring& command, ExecutionType type) //throw FileError
{
#ifdef ZEN_WIN
    //parse commandline
    Zstring commandTmp = command;
    trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces

    std::vector<Zstring> argv;
    int argc = 0;
    if (LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc))
    {
        ZEN_ON_SCOPE_EXIT(::LocalFree(tmp));
        std::copy(tmp, tmp + argc, std::back_inserter(argv));
    }

    Zstring filepath;
    Zstring arguments;
    if (!argv.empty())
    {
        filepath = argv[0];
        for (auto iter = argv.begin() + 1; iter != argv.end(); ++iter)
            arguments += (iter != argv.begin() ? L" " : L"") +
                         (iter->empty() || std::any_of(iter->begin(), iter->end(), &isWhiteSpace<wchar_t>) ? L"\"" + *iter + L"\"" : *iter);
    }

    if (!shellExecuteImpl([&](SHELLEXECUTEINFO& execInfo)
{
    execInfo.lpFile       = filepath.c_str();
        execInfo.lpParameters = arguments.c_str();
    }, type))
    throwFileError(_("Incorrect command line:") + L"\nFile: " + fmtFileName(filepath) + L"\nArg: " + arguments, L"ShellExecuteEx", ::GetLastError());

#elif defined ZEN_LINUX || defined ZEN_MAC
    /*
    we cannot use wxExecute due to various issues:
    - screws up encoding on OS X for non-ASCII characters
    - does not provide any reasonable error information
    - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows
    */

    if (type == EXEC_TYPE_SYNC)
    {
        //Posix::system - execute a shell command
        int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
        if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system    "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
            throw FileError(_("Incorrect command line:") + L"\n" + command);
    }
    else
        async([=] { int rv = ::system(command.c_str()); (void)rv; });
#endif
}
}
}

#endif //EXECUTE_HEADER_23482134578134134
bgstack15