1 /*
2  * DesktopWin32ApplicationLaunch.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 #include "DesktopApplicationLaunch.hpp"
16 
17 #include <windows.h>
18 
19 #include <QWidget>
20 
21 #include <core/system/Process.hpp>
22 #include <core/system/Environment.hpp>
23 #include <core/r_util/RUserData.hpp>
24 
25 #include "DesktopOptions.hpp"
26 
27 /*
28  This class is implemented using a message-only Win32 window with
29  a well-known window title. The message-only window can respond
30  to a "get main window handle" message, which will return the HWND
31  of the application's main window. It also responds to WM_COPYDATA
32  with OPENFILE as dwData, which causes openFileRequest(QString) to
33  be signaled with the copied data.
34 
35  It also uses a lockfile (%TEMP%\rstudio.lock) to try to ensure two
36  copies of the app don't run concurrently even if something goes
37  wrong with the message-only window.
38  */
39 
40 namespace rstudio {
41 namespace desktop {
42 
43 namespace {
44 
45 const ULONG_PTR OPENFILE = 1;
46 const char* WINDOW_TITLE = "RStudio_LaunchWindow_6dd82276-ccc3-4324-839e-4e6bcd5145bd";
47 
wmGetMainWindowHandle()48 UINT wmGetMainWindowHandle()
49 {
50    static UINT msg = 0;
51    if (!msg)
52       msg = ::RegisterWindowMessage("3dd567f8-7c07-4d9d-934b-dcb922cd737e");
53    return msg;
54 }
55 
activate(HWND hWnd)56 void activate(HWND hWnd)
57 {
58    HWND hwndPopup = ::GetLastActivePopup(hWnd);
59    if (::IsWindow(hwndPopup))
60       hWnd = hwndPopup;
61    ::SetForegroundWindow(hWnd);
62    if (::IsIconic(hWnd))
63       ::ShowWindow(hWnd, SW_RESTORE);
64 }
65 
66 } // anonymous namespace
67 
ApplicationLaunch()68 ApplicationLaunch::ApplicationLaunch() :
69     QWidget(nullptr),
70     pMainWindow_(nullptr)
71 {
72    setAttribute(Qt::WA_NativeWindow);
73    setWindowTitle(QString::fromUtf8(WINDOW_TITLE));
74    ::SetParent((HWND)winId(), HWND_MESSAGE);
75 }
76 
init(QString,int & argc,char * argv[],boost::scoped_ptr<QApplication> * ppApp,boost::scoped_ptr<ApplicationLaunch> * ppAppLaunch)77 void ApplicationLaunch::init(QString,
78                              int& argc,
79                              char* argv[],
80                              boost::scoped_ptr<QApplication>* ppApp,
81                              boost::scoped_ptr<ApplicationLaunch>* ppAppLaunch)
82 {
83    ppApp->reset(new QApplication(argc, argv));
84    ppAppLaunch->reset(new ApplicationLaunch());
85 }
86 
setActivationWindow(QWidget * pWindow)87 void ApplicationLaunch::setActivationWindow(QWidget* pWindow)
88 {
89    pMainWindow_ = pWindow;
90 }
91 
activateWindow()92 void ApplicationLaunch::activateWindow()
93 {
94    if (pMainWindow_)
95       activate((HWND) pMainWindow_->winId());
96    else
97       activate((HWND) winId());
98 }
99 
startupOpenFileRequest() const100 QString ApplicationLaunch::startupOpenFileRequest() const
101 {
102    return QString();
103 }
104 
105 namespace {
106 
acquireLock()107 bool acquireLock()
108 {
109    // The file is implicitly released/deleted when the process exits
110 
111    QString lockFilePath = QDir::temp().absoluteFilePath(QString::fromUtf8("rstudio.lock"));
112    HANDLE hFile = ::CreateFileW(lockFilePath.toStdWString().c_str(),
113                                 GENERIC_WRITE,
114                                 0, // exclusive access
115                                 nullptr,
116                                 OPEN_ALWAYS,
117                                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
118                                 nullptr);
119 
120    if (hFile == INVALID_HANDLE_VALUE)
121    {
122       if (::GetLastError() == ERROR_SHARING_VIOLATION)
123          return false;
124    }
125 
126    return true;
127 }
128 
129 } // anonymous namespace
130 
131 
attemptToRegisterPeer()132 void ApplicationLaunch::attemptToRegisterPeer()
133 {
134    acquireLock();
135 }
136 
137 
sendMessage(QString filename)138 bool ApplicationLaunch::sendMessage(QString filename)
139 {
140    if (acquireLock())
141       return false;
142 
143    HWND hwndAppLaunch = nullptr;
144    do
145    {
146       hwndAppLaunch = ::FindWindowEx(HWND_MESSAGE, hwndAppLaunch, nullptr, WINDOW_TITLE);
147    } while (hwndAppLaunch == (HWND)winId()); // Ignore ourselves
148 
149    if (::IsWindow(hwndAppLaunch))
150    {
151       HWND hwnd = reinterpret_cast<HWND>(::SendMessage(hwndAppLaunch,
152                                                        wmGetMainWindowHandle(),
153                                                        0,
154                                                        0));
155       if (::IsWindow(hwnd))
156       {
157          activate(hwnd);
158 
159          if (!filename.isEmpty())
160          {
161             QByteArray data = filename.toUtf8();
162 
163             COPYDATASTRUCT copydata;
164             copydata.dwData = OPENFILE;
165             copydata.lpData = data.data();
166             copydata.cbData = data.size();
167 
168             HWND sender = (HWND)winId();
169 
170             ::SendMessage(hwndAppLaunch,
171                           WM_COPYDATA,
172                           reinterpret_cast<WPARAM>(sender),
173                           reinterpret_cast<LPARAM>(&copydata));
174          }
175       }
176    }
177 
178    return true;
179 }
180 
nativeEvent(const QByteArray & eventType,void * msg,long * result)181 bool ApplicationLaunch::nativeEvent(const QByteArray & eventType,
182                                     void * msg,
183                                     long * result)
184 {
185    MSG* message = reinterpret_cast<MSG*>(msg);
186 
187    if (message->message == WM_COPYDATA)
188    {
189       COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(message->lParam);
190       if (cds->dwData == OPENFILE)
191       {
192          QString fileName = QString::fromUtf8(
193                reinterpret_cast<char*>(cds->lpData),
194                cds->cbData);
195          openFileRequest(fileName);
196          *result = 1;
197          return true;
198       }
199    }
200    else if (message->message == wmGetMainWindowHandle())
201    {
202       if (pMainWindow_)
203          *result = reinterpret_cast<LRESULT>((HWND)(pMainWindow_->winId()));
204       else
205          *result = 0;
206       return true;
207    }
208    return QWidget::nativeEvent(eventType, message, result);
209 }
210 
launchRStudio(const std::vector<std::string> & args,const std::string & initialDir)211 void ApplicationLaunch::launchRStudio(const std::vector<std::string>& args,
212                                       const std::string& initialDir)
213 {
214    core::system::ProcessOptions options;
215    options.breakawayFromJob = true;
216    options.detachProcess = true;
217 
218    // supply initial dir to child process if specified
219    core::system::Options childEnv;
220    core::system::environment(&childEnv);
221    if (!initialDir.empty())
222    {
223       core::system::setenv(&childEnv, kRStudioInitialWorkingDir, initialDir);
224       options.environment = childEnv;
225    }
226 
227    core::Error error = core::system::runProgram(
228       desktop::options().executablePath().getAbsolutePath(),
229       args,
230       "",
231       options,
232       nullptr);
233    if (error)
234       LOG_ERROR(error);
235 }
236 
237 } // namespace desktop
238 } // namespace rstudio
239