static bool wxLaunchDefaultBrowserBaseImpl(const wxString& url, int flags) { wxUnusedVar(flags); #if defined(__WXMSW__) #if wxUSE_IPC if ( flags & wxBROWSER_NEW_WINDOW ) { // ShellExecuteEx() opens the URL in an existing window by default so // we can't use it if we need a new window wxURI uri(url); wxRegKey key(wxRegKey::HKCR, uri.GetScheme() + _T("\\shell\\open")); if ( !key.Exists() ) { // try default browser, it must be registered at least for http URLs key.SetName(wxRegKey::HKCR, _T("http\\shell\\open")); } if ( key.Exists() ) { wxRegKey keyDDE(key, wxT("DDEExec")); if ( keyDDE.Exists() ) { // we only know the syntax of WWW_OpenURL DDE request for IE, // optimistically assume that all other browsers are compatible // with it static const wxString TOPIC_OPEN_URL = wxT("WWW_OpenURL"); wxString ddeCmd; wxRegKey keyTopic(keyDDE, wxT("topic")); bool ok = keyTopic.Exists() && (keyTopic.QueryDefaultValue() = TOPIC_OPEN_URL); if ( ok ) { ddeCmd = keyDDE.QueryDefaultValue(); ok = !ddeCmd.empty(); } if ( ok ) { // for WWW_OpenURL, the index of the window to open the URL // in is -1 (meaning "current") by default, replace it with // 0 which means "new" (see KB article 160957) ok = ddeCmd.Replace(wxT("-1"), wxT("0"), false /* only first occurence */) == 1; } if ( ok ) { // and also replace the parameters: the topic should // contain a placeholder for the URL ok = ddeCmd.Replace(wxT("%1"), url, false) == 1; } if ( ok ) { // try to send it the DDE request now but ignore the errors wxLogNull noLog; const wxString ddeServer = wxRegKey(keyDDE, wxT("application")).QueryDefaultValue(); if ( wxExecuteDDE(ddeServer, TOPIC_OPEN_URL, ddeCmd) ) return true; // this is not necessarily an error: maybe browser is // simply not running, but no matter, in any case we're // going to launch it using ShellExecuteEx() below now and // we shouldn't try to open a new window if we open a new // browser anyhow } } } } #endif // wxUSE_IPC WinStruct<SHELLEXECUTEINFO> sei; sei.lpFile = url.c_str(); sei.lpVerb = _T("open"); sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_FLAG_NO_UI; // we give error message ourselves BOOL nExecResult = ::ShellExecuteEx(&sei); //From MSDN for wince //hInstApp member is only valid if the function fails, in which case it //receives one of the following error values, which are less than or //equal to 32. const int nResult = (int) sei.hInstApp; // Firefox returns file not found for some reason, so make an exception // for it if ( nResult > 32 || nResult == SE_ERR_FNF || nExecResult == TRUE ) { #ifdef __WXDEBUG__ // Log something if SE_ERR_FNF happens if ( nResult == SE_ERR_FNF || nExecResult == FALSE ) wxLogDebug(wxT("SE_ERR_FNF from ShellExecute -- maybe FireFox?")); #endif // __WXDEBUG__ return true; } #elif defined(__WXMAC__) OSStatus err; ICInstance inst; long int startSel; long int endSel; err = ICStart(&inst, 'STKA'); // put your app creator code here if (err == noErr) { #if !TARGET_CARBON err = ICFindConfigFile(inst, 0, NULL); #endif if (err == noErr) { ConstStr255Param hint = 0; startSel = 0; endSel = url.length(); err = ICLaunchURL(inst, hint, url.fn_str(), endSel, &startSel, &endSel); if (err != noErr) wxLogDebug(wxT("ICLaunchURL error %d"), (int) err); } ICStop(inst); return true; } else { wxLogDebug(wxT("ICStart error %d"), (int) err); return false; } #else // (non-Mac, non-MSW) #ifdef __UNIX__ // Our best best is to use xdg-open from freedesktop.org cross-desktop // compatibility suite xdg-utils // (see http://portland.freedesktop.org/wiki/) -- this is installed on // most modern distributions and may be tweaked by them to handle // distribution specifics. Only if that fails, try to find the right // browser ourselves. wxString path, xdg_open; if ( wxGetEnv(_T("PATH"), &path) && wxFindFileInPath(&xdg_open, path, _T("xdg-open")) ) { if ( wxExecute(xdg_open + _T(" ") + url) ) return true; } wxString desktop = wxTheApp->GetTraits()->GetDesktopEnvironment(); // GNOME and KDE desktops have some applications which should be always installed // together with their main parts, which give us the if (desktop == wxT("GNOME")) { wxArrayString errors; wxArrayString output; // gconf will tell us the path of the application to use as browser long res = wxExecute( wxT("gconftool-2 --get /desktop/gnome/applications/browser/exec"), output, errors, wxEXEC_NODISABLE ); if (res >= 0 && errors.GetCount() == 0) { wxString cmd = output[0]; cmd << _T(' ') << url; if (wxExecute(cmd)) return true; } } else if (desktop == wxT("KDE")) { // kfmclient directly opens the given URL if (wxExecute(wxT("kfmclient openURL ") + url)) return true; } #endif bool ok = false; wxString cmd; #if wxUSE_MIMETYPE wxFileType *ft = wxTheMimeTypesManager->GetFileTypeFromExtension(_T("html")); if ( ft ) { wxString mt; ft->GetMimeType(&mt); ok = ft->GetOpenCommand(&cmd, wxFileType::MessageParameters(url)); delete ft; } #endif // wxUSE_MIMETYPE if ( !ok || cmd.empty() ) { // fallback to checking for the BROWSER environment variable cmd = wxGetenv(wxT("BROWSER")); if ( !cmd.empty() ) cmd << _T(' ') << url; } ok = ( !cmd.empty() && wxExecute(cmd) ); if (ok) return ok; // no file type for HTML extension wxLogError(_T("No default application configured for HTML files.")); #endif // !wxUSE_MIMETYPE && !__WXMSW__ return false; }
long wxExecute(const wxString& cmd, int flags, wxProcess *handler, const wxExecuteEnv *env) { wxCHECK_MSG( !cmd.empty(), 0, wxT("empty command in wxExecute") ); #if wxUSE_THREADS // for many reasons, the code below breaks down if it's called from another // thread -- this could be fixed, but as Unix versions don't support this // neither I don't want to waste time on this now wxASSERT_MSG( wxThread::IsMain(), wxT("wxExecute() can be called only from the main thread") ); #endif // wxUSE_THREADS wxString command; #if wxUSE_IPC // DDE hack: this is really not pretty, but we need to allow this for // transparent handling of DDE servers in wxMimeTypesManager. Usually it // returns the command which should be run to view/open/... a file of the // given type. Sometimes, however, this command just launches the server // and an additional DDE request must be made to really open the file. To // keep all this well hidden from the application, we allow a special form // of command: WX_DDE#<command>#DDE_SERVER#DDE_TOPIC#DDE_COMMAND in which // case we execute just <command> and process the rest below wxString ddeServer, ddeTopic, ddeCommand; static const size_t lenDdePrefix = 7; // strlen("WX_DDE:") if ( cmd.Left(lenDdePrefix) == wxT("WX_DDE#") ) { // speed up the concatenations below ddeServer.reserve(256); ddeTopic.reserve(256); ddeCommand.reserve(256); const wxChar *p = cmd.c_str() + 7; #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ while ( *p && *p != wxT('#') ) { command += *p++; } if ( *p ) { // skip '#' p++; } else { wxFAIL_MSG(wxT("invalid WX_DDE command in wxExecute")); } #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ while ( *p && *p != wxT('#') ) { ddeServer += *p++; } if ( *p ) { // skip '#' p++; } else { wxFAIL_MSG(wxT("invalid WX_DDE command in wxExecute")); } #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ while ( *p && *p != wxT('#') ) { ddeTopic += *p++; } if ( *p ) { // skip '#' p++; } else { wxFAIL_MSG(wxT("invalid WX_DDE command in wxExecute")); } #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ while ( *p ) { ddeCommand += *p++; } // if we want to just launch the program and not wait for its // termination, try to execute DDE command right now, it can succeed if // the process is already running - but as it fails if it's not // running, suppress any errors it might generate if ( !(flags & wxEXEC_SYNC) ) { wxLogNull noErrors; if ( wxExecuteDDE(ddeServer, ddeTopic, ddeCommand) ) { // a dummy PID - this is a hack, of course, but it's well worth // it as we don't open a new server each time we're called // which would be quite bad return -1; } } } else #endif // wxUSE_IPC { // no DDE command = cmd; } // the IO redirection is only supported with wxUSE_STREAMS BOOL redirect = FALSE; #if wxUSE_STREAMS wxPipe pipeIn, pipeOut, pipeErr; // open the pipes to which child process IO will be redirected if needed if ( handler && handler->IsRedirected() ) { // create pipes for redirecting stdin, stdout and stderr if ( !pipeIn.Create() || !pipeOut.Create() || !pipeErr.Create() ) { wxLogSysError(_("Failed to redirect the child process IO")); // indicate failure: we need to return different error code // depending on the sync flag return flags & wxEXEC_SYNC ? -1 : 0; } redirect = TRUE; } #endif // wxUSE_STREAMS // create the process STARTUPINFO si; wxZeroMemory(si); si.cb = sizeof(si); #if wxUSE_STREAMS if ( redirect ) { si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = pipeIn[wxPipe::Read]; si.hStdOutput = pipeOut[wxPipe::Write]; si.hStdError = pipeErr[wxPipe::Write]; // We must set the handles to those sides of std* pipes that we won't // in the child to be non-inheritable. We must do this before launching // the child process as otherwise these handles will be inherited by // the child which will never close them and so the pipe will not // return ERROR_BROKEN_PIPE if the parent or child exits unexpectedly // causing the remaining process to potentially become deadlocked in // ReadFile() or WriteFile(). if ( !::SetHandleInformation(pipeIn[wxPipe::Write], HANDLE_FLAG_INHERIT, 0) ) wxLogLastError(wxT("SetHandleInformation(pipeIn)")); if ( !::SetHandleInformation(pipeOut[wxPipe::Read], HANDLE_FLAG_INHERIT, 0) ) wxLogLastError(wxT("SetHandleInformation(pipeOut)")); if ( !::SetHandleInformation(pipeErr[wxPipe::Read], HANDLE_FLAG_INHERIT, 0) ) wxLogLastError(wxT("SetHandleInformation(pipeErr)")); } #endif // wxUSE_STREAMS // The default logic for showing the console is to show it only if the IO // is not redirected however wxEXEC_{SHOW,HIDE}_CONSOLE flags can be // explicitly specified to change it. if ( (flags & wxEXEC_HIDE_CONSOLE) || (redirect && !(flags & wxEXEC_SHOW_CONSOLE)) ) { si.dwFlags |= STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; } PROCESS_INFORMATION pi; DWORD dwFlags = CREATE_SUSPENDED; if ( (flags & wxEXEC_MAKE_GROUP_LEADER) ) dwFlags |= CREATE_NEW_PROCESS_GROUP; dwFlags |= CREATE_DEFAULT_ERROR_MODE ; wxWxCharBuffer envBuffer; bool useCwd = false; if ( env ) { useCwd = !env->cwd.empty(); // Translate environment variable map into NUL-terminated list of // NUL-terminated strings. if ( !env->env.empty() ) { #if wxUSE_UNICODE // Environment variables can contain non-ASCII characters. We could // check for it and not use this flag if everything is really ASCII // only but there doesn't seem to be any reason to do it so just // assume Unicode by default. dwFlags |= CREATE_UNICODE_ENVIRONMENT; #endif // wxUSE_UNICODE wxEnvVariableHashMap::const_iterator it; size_t envSz = 1; // ending '\0' #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ for ( it = env->env.begin(); it != env->env.end(); ++it ) { // Add size of env variable name and value, and '=' char and // ending '\0' envSz += it->first.length() + it->second.length() + 2; } envBuffer.extend(envSz); wxChar *p = envBuffer.data(); #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ for ( it = env->env.begin(); it != env->env.end(); ++it ) { const wxString line = it->first + wxS("=") + it->second; // Include the trailing NUL which will always terminate the // buffer returned by t_str(). const size_t len = line.length() + 1; wxTmemcpy(p, line.t_str(), len); p += len; } // And another NUL to terminate the list of NUL-terminated strings. *p = 0; } } // Translate wxWidgets priority to Windows conventions. if ( handler ) { unsigned prio = handler->GetPriority(); if ( prio <= 20 ) dwFlags |= IDLE_PRIORITY_CLASS; else if ( prio <= 40 ) dwFlags |= BELOW_NORMAL_PRIORITY_CLASS; else if ( prio <= 60 ) dwFlags |= NORMAL_PRIORITY_CLASS; else if ( prio <= 80 ) dwFlags |= ABOVE_NORMAL_PRIORITY_CLASS; else if ( prio <= 99 ) dwFlags |= HIGH_PRIORITY_CLASS; else if ( prio <= 100 ) dwFlags |= REALTIME_PRIORITY_CLASS; else { wxFAIL_MSG(wxT("invalid value of thread priority parameter")); dwFlags |= NORMAL_PRIORITY_CLASS; } } bool ok = ::CreateProcess ( NULL, // application name (use only cmd line) wxMSW_CONV_LPTSTR(command), // full command line NULL, // security attributes: defaults for both NULL, // the process and its main thread redirect, // inherit handles if we use pipes dwFlags, // process creation flags envBuffer.data(), // environment (may be NULL which is fine) useCwd // initial working directory ? wxMSW_CONV_LPTSTR(env->cwd) : NULL, // (or use the same) &si, // startup info (unused here) &pi // process info ) != 0; #if wxUSE_STREAMS // we can close the pipe ends used by child anyhow if ( redirect ) { ::CloseHandle(pipeIn.Detach(wxPipe::Read)); ::CloseHandle(pipeOut.Detach(wxPipe::Write)); ::CloseHandle(pipeErr.Detach(wxPipe::Write)); } #endif // wxUSE_STREAMS if ( !ok ) { #if wxUSE_STREAMS // close the other handles too if ( redirect ) { ::CloseHandle(pipeIn.Detach(wxPipe::Write)); ::CloseHandle(pipeOut.Detach(wxPipe::Read)); ::CloseHandle(pipeErr.Detach(wxPipe::Read)); } #endif // wxUSE_STREAMS wxLogSysError(_("Execution of command '%s' failed"), command.c_str()); return flags & wxEXEC_SYNC ? -1 : 0; } #if wxUSE_STREAMS // the input buffer bufOut is connected to stdout, this is why it is // called bufOut and not bufIn wxStreamTempInputBuffer bufOut, bufErr; if ( redirect ) { // We can now initialize the wxStreams wxPipeInputStream * outStream = new wxPipeInputStream(pipeOut.Detach(wxPipe::Read)); wxPipeInputStream * errStream = new wxPipeInputStream(pipeErr.Detach(wxPipe::Read)); wxPipeOutputStream * inStream = new wxPipeOutputStream(pipeIn.Detach(wxPipe::Write)); handler->SetPipeStreams(outStream, inStream, errStream); bufOut.Init(outStream); bufErr.Init(errStream); } #endif // wxUSE_STREAMS // create a hidden window to receive notification about process // termination HWND hwnd = wxCreateHiddenWindow ( &gs_classForHiddenWindow, wxMSWEXEC_WNDCLASSNAME, (WNDPROC)wxExecuteWindowCbk ); wxASSERT_MSG( hwnd, wxT("can't create a hidden window for wxExecute") ); // Alloc data wxExecuteData *data = new wxExecuteData; data->hProcess = pi.hProcess; data->dwProcessId = pi.dwProcessId; data->hWnd = hwnd; data->state = (flags & wxEXEC_SYNC) != 0; if ( flags & wxEXEC_SYNC ) { // handler may be !NULL for capturing program output, but we don't use // it wxExecuteData struct in this case data->handler = NULL; } else { // may be NULL or not data->handler = handler; if (handler) handler->SetPid(pi.dwProcessId); } DWORD tid; HANDLE hThread = ::CreateThread(NULL, 0, wxExecuteThread, (void *)data, 0, &tid); // resume process we created now - whether the thread creation succeeded or // not if ( ::ResumeThread(pi.hThread) == (DWORD)-1 ) { // ignore it - what can we do? wxLogLastError(wxT("ResumeThread in wxExecute")); } // close unneeded handle if ( !::CloseHandle(pi.hThread) ) { wxLogLastError(wxT("CloseHandle(hThread)")); } if ( !hThread ) { wxLogLastError(wxT("CreateThread in wxExecute")); DestroyWindow(hwnd); delete data; // the process still started up successfully... return pi.dwProcessId; } gs_asyncThreads.push_back(hThread); data->hThread = hThread; #if wxUSE_IPC // second part of DDE hack: now establish the DDE conversation with the // just launched process if ( !ddeServer.empty() ) { bool ddeOK; // give the process the time to init itself // // we use a very big timeout hoping that WaitForInputIdle() will return // much sooner, but not INFINITE just in case the process hangs // completely - like this we will regain control sooner or later switch ( ::WaitForInputIdle(pi.hProcess, 10000 /* 10 seconds */) ) { default: wxFAIL_MSG( wxT("unexpected WaitForInputIdle() return code") ); // fall through case WAIT_FAILED: wxLogLastError(wxT("WaitForInputIdle() in wxExecute")); case WAIT_TIMEOUT: wxLogDebug(wxT("Timeout too small in WaitForInputIdle")); ddeOK = false; break; case 0: // ok, process ready to accept DDE requests ddeOK = wxExecuteDDE(ddeServer, ddeTopic, ddeCommand); } if ( !ddeOK ) { wxLogDebug(wxT("Failed to send DDE request to the process \"%s\"."), cmd.c_str()); } } #endif // wxUSE_IPC if ( !(flags & wxEXEC_SYNC) ) { // clean up will be done when the process terminates // return the pid return pi.dwProcessId; } wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL; wxCHECK_MSG( traits, -1, wxT("no wxAppTraits in wxExecute()?") ); void *cookie = NULL; if ( !(flags & wxEXEC_NODISABLE) ) { // disable all app windows while waiting for the child process to finish cookie = traits->BeforeChildWaitLoop(); } // wait until the child process terminates #if defined(__INTEL_COMPILER) && 1 /* VDM auto patch */ # pragma ivdep # pragma swp # pragma unroll # pragma prefetch # if 0 # pragma simd noassert # endif #endif /* VDM auto patch */ while ( data->state ) { #if wxUSE_STREAMS if ( !bufOut.Update() && !bufErr.Update() ) #endif // wxUSE_STREAMS { // don't eat 100% of the CPU -- ugly but anything else requires // real async IO which we don't have for the moment ::Sleep(50); } // we must always process messages for our hidden window or we'd never // get wxWM_PROC_TERMINATED and so this loop would never terminate MSG msg; ::PeekMessage(&msg, data->hWnd, 0, 0, PM_REMOVE); // we may also need to process messages for all the other application // windows if ( !(flags & wxEXEC_NOEVENTS) ) { wxEventLoopBase * const loop = wxEventLoopBase::GetActive(); if ( loop ) loop->Yield(); } } if ( !(flags & wxEXEC_NODISABLE) ) { // reenable disabled windows back traits->AfterChildWaitLoop(cookie); } DWORD dwExitCode = data->dwExitCode; delete data; // return the exit code return dwExitCode; }
bool wxDoLaunchDefaultBrowser(const wxString& url, const wxString& scheme, int flags) { wxUnusedVar(flags); #if wxUSE_IPC if ( flags & wxBROWSER_NEW_WINDOW ) { // ShellExecuteEx() opens the URL in an existing window by default so // we can't use it if we need a new window wxRegKey key(wxRegKey::HKCR, scheme + wxT("\\shell\\open")); if ( !key.Exists() ) { // try the default browser, it must be registered at least for http URLs key.SetName(wxRegKey::HKCR, wxT("http\\shell\\open")); } if ( key.Exists() ) { wxRegKey keyDDE(key, wxT("DDEExec")); if ( keyDDE.Exists() ) { // we only know the syntax of WWW_OpenURL DDE request for IE, // optimistically assume that all other browsers are compatible // with it static const wxChar *TOPIC_OPEN_URL = wxT("WWW_OpenURL"); wxString ddeCmd; wxRegKey keyTopic(keyDDE, wxT("topic")); bool ok = keyTopic.Exists() && keyTopic.QueryDefaultValue() == TOPIC_OPEN_URL; if ( ok ) { ddeCmd = keyDDE.QueryDefaultValue(); ok = !ddeCmd.empty(); } if ( ok ) { // for WWW_OpenURL, the index of the window to open the URL // in is -1 (meaning "current") by default, replace it with // 0 which means "new" (see KB article 160957) ok = ddeCmd.Replace(wxT("-1"), wxT("0"), false /* only first occurrence */) == 1; } if ( ok ) { // and also replace the parameters: the topic should // contain a placeholder for the URL ok = ddeCmd.Replace(wxT("%1"), url, false) == 1; } if ( ok ) { // try to send it the DDE request now but ignore the errors wxLogNull noLog; const wxString ddeServer = wxRegKey(keyDDE, wxT("application")); if ( wxExecuteDDE(ddeServer, TOPIC_OPEN_URL, ddeCmd) ) return true; // this is not necessarily an error: maybe browser is // simply not running, but no matter, in any case we're // going to launch it using ShellExecuteEx() below now and // we shouldn't try to open a new window if we open a new // browser anyhow } } } } #endif // wxUSE_IPC WinStruct<SHELLEXECUTEINFO> sei; sei.lpFile = url.c_str(); sei.lpVerb = wxT("open"); sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_FLAG_NO_UI; // we give error message ourselves if ( ::ShellExecuteEx(&sei) ) return true; return false; }