- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / host / curtain_mode_mac.cc
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.
4
5 #include "remoting/host/curtain_mode.h"
6
7 #include <ApplicationServices/ApplicationServices.h>
8 #include <Carbon/Carbon.h>
9 #include <Security/Security.h>
10 #include <unistd.h>
11
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"
19
20 namespace {
21
22 using remoting::ClientSessionControl;
23
24 const char* kCGSessionPath =
25     "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/"
26     "CGSession";
27
28 // Used to detach the current session from the local console and disconnect
29 // the connnection if it gets re-attached.
30 //
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);
40
41   void Start();
42   void Stop();
43
44  private:
45   friend class base::RefCountedThreadSafe<SessionWatcher>;
46   virtual ~SessionWatcher();
47
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();
51
52   // Installs the switch-in handler.
53   bool InstallEventHandler();
54
55   // Removes the switch-in handler.
56   void RemoveEventHandler();
57
58   // Disconnects the client session.
59   void DisconnectSession();
60
61   // Handlers for the switch-in event.
62   static OSStatus SessionActivateHandler(EventHandlerCallRef handler,
63                                          EventRef event,
64                                          void* user_data);
65
66   // Task runner on which public methods of this class must be called.
67   scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
68
69   // Task runner representing the thread receiving Carbon events.
70   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
71
72   // Used to disconnect the client session.
73   base::WeakPtr<ClientSessionControl> client_session_control_;
74
75   EventHandlerRef event_handler_;
76
77   DISALLOW_COPY_AND_ASSIGN(SessionWatcher);
78 };
79
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) {
88 }
89
90 void SessionWatcher::Start() {
91   DCHECK(caller_task_runner_->BelongsToCurrentThread());
92
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));
100 }
101
102 void SessionWatcher::Stop() {
103   DCHECK(caller_task_runner_->BelongsToCurrentThread());
104
105   client_session_control_.reset();
106   ui_task_runner_->PostTask(
107       FROM_HERE, base::Bind(&SessionWatcher::RemoveEventHandler, this));
108 }
109
110 SessionWatcher::~SessionWatcher() {
111   DCHECK(!event_handler_);
112 }
113
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
120   //
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;
127   }
128
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;
135   }
136
137   base::ScopedCFTypeRef<CFDictionaryRef> session(
138       CGSessionCopyCurrentDictionary());
139
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);
148
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;
164       }
165     } else {
166       LOG(ERROR) << "fork() failed.";
167       DisconnectSession();
168       return;
169     }
170   }
171 }
172
173 bool SessionWatcher::InstallEventHandler() {
174   DCHECK(ui_task_runner_->BelongsToCurrentThread());
175   DCHECK(!event_handler_);
176
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;
187   }
188
189   return true;
190 }
191
192 void SessionWatcher::RemoveEventHandler() {
193   DCHECK(ui_task_runner_->BelongsToCurrentThread());
194
195   if (event_handler_) {
196     ::RemoveEventHandler(event_handler_);
197     event_handler_ = NULL;
198   }
199 }
200
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;
206   }
207
208   if (client_session_control_)
209     client_session_control_->DisconnectSession();
210 }
211
212 OSStatus SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler,
213                                                 EventRef event,
214                                                 void* user_data) {
215   static_cast<SessionWatcher*>(user_data)->DisconnectSession();
216   return noErr;
217 }
218
219 }  // namespace
220
221 namespace remoting {
222
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();
230
231   // Overriden from CurtainMode.
232   virtual bool Activate() OVERRIDE;
233
234  private:
235   scoped_refptr<SessionWatcher> session_watcher_;
236
237   DISALLOW_COPY_AND_ASSIGN(CurtainModeMac);
238 };
239
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)) {
247 }
248
249 CurtainModeMac::~CurtainModeMac() {
250   session_watcher_->Stop();
251 }
252
253 bool CurtainModeMac::Activate() {
254   session_watcher_->Start();
255   return true;
256 }
257
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));
266 }
267
268 }  // namespace remoting