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>(©data));
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