Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / remoting / host / curtain_mode_mac.cc
blob0ac4aa5c2bdda50a61d5fde793c6de12c9cc2bf1
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/curtain_mode.h"
7 #include <ApplicationServices/ApplicationServices.h>
8 #include <Carbon/Carbon.h>
9 #include <Security/Security.h>
10 #include <unistd.h>
12 #include "base/bind.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/mac/mac_util.h"
16 #include "base/mac/scoped_cftyperef.h"
17 #include "base/single_thread_task_runner.h"
18 #include "remoting/host/client_session_control.h"
20 namespace {
22 using remoting::ClientSessionControl;
24 const char* kCGSessionPath =
25 "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/"
26 "CGSession";
28 // Used to detach the current session from the local console and disconnect
29 // the connnection if it gets re-attached.
31 // Because the switch-in handler can only called on the main (UI) thread, this
32 // class installs the handler and detaches the current session from the console
33 // on the UI thread as well.
34 class SessionWatcher : public base::RefCountedThreadSafe<SessionWatcher> {
35 public:
36 SessionWatcher(
37 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
38 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
39 base::WeakPtr<ClientSessionControl> client_session_control);
41 void Start();
42 void Stop();
44 private:
45 friend class base::RefCountedThreadSafe<SessionWatcher>;
46 virtual ~SessionWatcher();
48 // Detaches the session from the console and install the switch-in handler to
49 // detect when the session re-attaches back.
50 void ActivateCurtain();
52 // Installs the switch-in handler.
53 bool InstallEventHandler();
55 // Removes the switch-in handler.
56 void RemoveEventHandler();
58 // Disconnects the client session.
59 void DisconnectSession();
61 // Handlers for the switch-in event.
62 static OSStatus SessionActivateHandler(EventHandlerCallRef handler,
63 EventRef event,
64 void* user_data);
66 // Task runner on which public methods of this class must be called.
67 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
69 // Task runner representing the thread receiving Carbon events.
70 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
72 // Used to disconnect the client session.
73 base::WeakPtr<ClientSessionControl> client_session_control_;
75 EventHandlerRef event_handler_;
77 DISALLOW_COPY_AND_ASSIGN(SessionWatcher);
80 SessionWatcher::SessionWatcher(
81 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
82 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
83 base::WeakPtr<ClientSessionControl> client_session_control)
84 : caller_task_runner_(caller_task_runner),
85 ui_task_runner_(ui_task_runner),
86 client_session_control_(client_session_control),
87 event_handler_(nullptr) {
90 void SessionWatcher::Start() {
91 DCHECK(caller_task_runner_->BelongsToCurrentThread());
93 // Activate curtain asynchronously since it has to be done on the UI thread.
94 // Because the curtain activation is asynchronous, it is possible that
95 // the connection will not be curtained for a brief moment. This seems to be
96 // unaviodable as long as the curtain enforcement depends on processing of
97 // the switch-in notifications.
98 ui_task_runner_->PostTask(
99 FROM_HERE, base::Bind(&SessionWatcher::ActivateCurtain, this));
102 void SessionWatcher::Stop() {
103 DCHECK(caller_task_runner_->BelongsToCurrentThread());
105 client_session_control_.reset();
106 ui_task_runner_->PostTask(
107 FROM_HERE, base::Bind(&SessionWatcher::RemoveEventHandler, this));
110 SessionWatcher::~SessionWatcher() {
111 DCHECK(!event_handler_);
114 void SessionWatcher::ActivateCurtain() {
115 // Curtain mode causes problems with the login screen on Lion only (starting
116 // with 10.7.3), so disable it on that platform. There is a work-around, but
117 // it involves modifying a system Plist pertaining to power-management, so
118 // it's not something that should be done automatically. For more details,
119 // see https://discussions.apple.com/thread/3209415?start=690&tstart=0
121 // TODO(jamiewalch): If the underlying OS bug is ever fixed, we should support
122 // curtain mode on suitable versions of Lion.
123 if (base::mac::IsOSLion()) {
124 LOG(ERROR) << "Host curtaining is not supported on Mac OS X 10.7.";
125 DisconnectSession();
126 return;
129 // Try to install the switch-in handler. Do this before switching out the
130 // current session so that the console session is not affected if it fails.
131 if (!InstallEventHandler()) {
132 LOG(ERROR) << "Failed to install the switch-in handler.";
133 DisconnectSession();
134 return;
137 base::ScopedCFTypeRef<CFDictionaryRef> session(
138 CGSessionCopyCurrentDictionary());
140 // CGSessionCopyCurrentDictionary has been observed to return nullptr in some
141 // cases. Once the system is in this state, curtain mode will fail as the
142 // CGSession command thinks the session is not attached to the console. The
143 // only known remedy is logout or reboot. Since we're not sure what causes
144 // this, or how common it is, a crash report is useful in this case (note
145 // that the connection would have to be refused in any case, so this is no
146 // loss of functionality).
147 CHECK(session != nullptr)
148 << "Error activating curtain-mode: "
149 << "CGSessionCopyCurrentDictionary() returned NULL. "
150 << "Logging out and back in should resolve this error.";
152 const void* on_console = CFDictionaryGetValue(session,
153 kCGSessionOnConsoleKey);
154 const void* logged_in = CFDictionaryGetValue(session, kCGSessionLoginDoneKey);
155 if (logged_in == kCFBooleanTrue && on_console == kCFBooleanTrue) {
156 pid_t child = fork();
157 if (child == 0) {
158 execl(kCGSessionPath, kCGSessionPath, "-suspend", nullptr);
159 _exit(1);
160 } else if (child > 0) {
161 int status = 0;
162 waitpid(child, &status, 0);
163 if (status != 0) {
164 LOG(ERROR) << kCGSessionPath << " failed.";
165 DisconnectSession();
166 return;
168 } else {
169 LOG(ERROR) << "fork() failed.";
170 DisconnectSession();
171 return;
176 bool SessionWatcher::InstallEventHandler() {
177 DCHECK(ui_task_runner_->BelongsToCurrentThread());
178 DCHECK(!event_handler_);
180 EventTypeSpec event;
181 event.eventClass = kEventClassSystem;
182 event.eventKind = kEventSystemUserSessionActivated;
183 OSStatus result = ::InstallApplicationEventHandler(
184 NewEventHandlerUPP(SessionActivateHandler), 1, &event, this,
185 &event_handler_);
186 if (result != noErr) {
187 event_handler_ = nullptr;
188 DisconnectSession();
189 return false;
192 return true;
195 void SessionWatcher::RemoveEventHandler() {
196 DCHECK(ui_task_runner_->BelongsToCurrentThread());
198 if (event_handler_) {
199 ::RemoveEventHandler(event_handler_);
200 event_handler_ = nullptr;
204 void SessionWatcher::DisconnectSession() {
205 if (!caller_task_runner_->BelongsToCurrentThread()) {
206 caller_task_runner_->PostTask(
207 FROM_HERE, base::Bind(&SessionWatcher::DisconnectSession, this));
208 return;
211 if (client_session_control_)
212 client_session_control_->DisconnectSession();
215 OSStatus SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler,
216 EventRef event,
217 void* user_data) {
218 static_cast<SessionWatcher*>(user_data)->DisconnectSession();
219 return noErr;
222 } // namespace
224 namespace remoting {
226 class CurtainModeMac : public CurtainMode {
227 public:
228 CurtainModeMac(
229 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
230 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
231 base::WeakPtr<ClientSessionControl> client_session_control);
232 ~CurtainModeMac() override;
234 // Overriden from CurtainMode.
235 bool Activate() override;
237 private:
238 scoped_refptr<SessionWatcher> session_watcher_;
240 DISALLOW_COPY_AND_ASSIGN(CurtainModeMac);
243 CurtainModeMac::CurtainModeMac(
244 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
245 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
246 base::WeakPtr<ClientSessionControl> client_session_control)
247 : session_watcher_(new SessionWatcher(caller_task_runner,
248 ui_task_runner,
249 client_session_control)) {
252 CurtainModeMac::~CurtainModeMac() {
253 session_watcher_->Stop();
256 bool CurtainModeMac::Activate() {
257 session_watcher_->Start();
258 return true;
261 // static
262 scoped_ptr<CurtainMode> CurtainMode::Create(
263 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
264 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
265 base::WeakPtr<ClientSessionControl> client_session_control) {
266 return make_scoped_ptr(new CurtainModeMac(
267 caller_task_runner, ui_task_runner, client_session_control));
270 } // namespace remoting