Disable overview on alt-tab by default and update test expectation to match.
[chromium-blink-merge.git] / remoting / host / curtain_mode_mac.cc
blobaa851563bdcf37c8ade81c95b7147bba655cf58b
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_(NULL) {
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 NULL 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 != NULL);
149 const void* on_console = CFDictionaryGetValue(session,
150 kCGSessionOnConsoleKey);
151 const void* logged_in = CFDictionaryGetValue(session, kCGSessionLoginDoneKey);
152 if (logged_in == kCFBooleanTrue && on_console == kCFBooleanTrue) {
153 pid_t child = fork();
154 if (child == 0) {
155 execl(kCGSessionPath, kCGSessionPath, "-suspend", NULL);
156 _exit(1);
157 } else if (child > 0) {
158 int status = 0;
159 waitpid(child, &status, 0);
160 if (status != 0) {
161 LOG(ERROR) << kCGSessionPath << " failed.";
162 DisconnectSession();
163 return;
165 } else {
166 LOG(ERROR) << "fork() failed.";
167 DisconnectSession();
168 return;
173 bool SessionWatcher::InstallEventHandler() {
174 DCHECK(ui_task_runner_->BelongsToCurrentThread());
175 DCHECK(!event_handler_);
177 EventTypeSpec event;
178 event.eventClass = kEventClassSystem;
179 event.eventKind = kEventSystemUserSessionActivated;
180 OSStatus result = ::InstallApplicationEventHandler(
181 NewEventHandlerUPP(SessionActivateHandler), 1, &event, this,
182 &event_handler_);
183 if (result != noErr) {
184 event_handler_ = NULL;
185 DisconnectSession();
186 return false;
189 return true;
192 void SessionWatcher::RemoveEventHandler() {
193 DCHECK(ui_task_runner_->BelongsToCurrentThread());
195 if (event_handler_) {
196 ::RemoveEventHandler(event_handler_);
197 event_handler_ = NULL;
201 void SessionWatcher::DisconnectSession() {
202 if (!caller_task_runner_->BelongsToCurrentThread()) {
203 caller_task_runner_->PostTask(
204 FROM_HERE, base::Bind(&SessionWatcher::DisconnectSession, this));
205 return;
208 if (client_session_control_)
209 client_session_control_->DisconnectSession();
212 OSStatus SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler,
213 EventRef event,
214 void* user_data) {
215 static_cast<SessionWatcher*>(user_data)->DisconnectSession();
216 return noErr;
219 } // namespace
221 namespace remoting {
223 class CurtainModeMac : public CurtainMode {
224 public:
225 CurtainModeMac(
226 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
227 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
228 base::WeakPtr<ClientSessionControl> client_session_control);
229 virtual ~CurtainModeMac();
231 // Overriden from CurtainMode.
232 virtual bool Activate() OVERRIDE;
234 private:
235 scoped_refptr<SessionWatcher> session_watcher_;
237 DISALLOW_COPY_AND_ASSIGN(CurtainModeMac);
240 CurtainModeMac::CurtainModeMac(
241 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
242 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
243 base::WeakPtr<ClientSessionControl> client_session_control)
244 : session_watcher_(new SessionWatcher(caller_task_runner,
245 ui_task_runner,
246 client_session_control)) {
249 CurtainModeMac::~CurtainModeMac() {
250 session_watcher_->Stop();
253 bool CurtainModeMac::Activate() {
254 session_watcher_->Start();
255 return true;
258 // static
259 scoped_ptr<CurtainMode> CurtainMode::Create(
260 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
261 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
262 base::WeakPtr<ClientSessionControl> client_session_control) {
263 return scoped_ptr<CurtainMode>(new CurtainModeMac(caller_task_runner,
264 ui_task_runner,
265 client_session_control));
268 } // namespace remoting