OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/client/plugin/chromoting_instance.h" | 5 #include "remoting/client/plugin/chromoting_instance.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/message_loop.h" | 12 #include "base/message_loop.h" |
13 #include "base/stringprintf.h" | 13 #include "base/stringprintf.h" |
14 #include "base/synchronization/waitable_event.h" | 14 #include "base/synchronization/waitable_event.h" |
15 #include "base/task.h" | 15 #include "base/task.h" |
16 #include "base/threading/thread.h" | 16 #include "base/threading/thread.h" |
17 // TODO(sergeyu): We should not depend on renderer here. Instead P2P | 17 // TODO(sergeyu): We should not depend on renderer here. Instead P2P |
18 // Pepper API should be used. Remove this dependency. | 18 // Pepper API should be used. Remove this dependency. |
19 // crbug.com/74951 | 19 // crbug.com/74951 |
20 #include "content/renderer/p2p/ipc_network_manager.h" | 20 #include "content/renderer/p2p/ipc_network_manager.h" |
21 #include "content/renderer/p2p/ipc_socket_factory.h" | 21 #include "content/renderer/p2p/ipc_socket_factory.h" |
22 #include "ppapi/cpp/completion_callback.h" | 22 #include "ppapi/cpp/completion_callback.h" |
23 #include "ppapi/cpp/input_event.h" | 23 #include "ppapi/cpp/input_event.h" |
24 #include "ppapi/cpp/rect.h" | 24 #include "ppapi/cpp/rect.h" |
25 // TODO(wez): Remove this when crbug.com/86353 is complete. | 25 // TODO(wez): Remove this when crbug.com/86353 is complete. |
26 #include "ppapi/cpp/private/var_private.h" | 26 #include "ppapi/cpp/private/var_private.h" |
27 #include "remoting/base/util.h" | |
27 #include "remoting/client/client_config.h" | 28 #include "remoting/client/client_config.h" |
28 #include "remoting/client/chromoting_client.h" | 29 #include "remoting/client/chromoting_client.h" |
29 #include "remoting/client/rectangle_update_decoder.h" | 30 #include "remoting/client/rectangle_update_decoder.h" |
30 #include "remoting/client/plugin/chromoting_scriptable_object.h" | 31 #include "remoting/client/plugin/chromoting_scriptable_object.h" |
31 #include "remoting/client/plugin/pepper_client_logger.h" | |
32 #include "remoting/client/plugin/pepper_input_handler.h" | 32 #include "remoting/client/plugin/pepper_input_handler.h" |
33 #include "remoting/client/plugin/pepper_port_allocator_session.h" | 33 #include "remoting/client/plugin/pepper_port_allocator_session.h" |
34 #include "remoting/client/plugin/pepper_view.h" | 34 #include "remoting/client/plugin/pepper_view.h" |
35 #include "remoting/client/plugin/pepper_view_proxy.h" | 35 #include "remoting/client/plugin/pepper_view_proxy.h" |
36 #include "remoting/client/plugin/pepper_util.h" | 36 #include "remoting/client/plugin/pepper_util.h" |
37 #include "remoting/client/plugin/pepper_xmpp_proxy.h" | 37 #include "remoting/client/plugin/pepper_xmpp_proxy.h" |
38 #include "remoting/jingle_glue/jingle_thread.h" | 38 #include "remoting/jingle_glue/jingle_thread.h" |
39 #include "remoting/proto/auth.pb.h" | 39 #include "remoting/proto/auth.pb.h" |
40 #include "remoting/protocol/connection_to_host.h" | 40 #include "remoting/protocol/connection_to_host.h" |
41 #include "remoting/protocol/host_stub.h" | 41 #include "remoting/protocol/host_stub.h" |
42 // TODO(sergeyu): This is a hack: plugin should not depend on webkit | 42 // TODO(sergeyu): This is a hack: plugin should not depend on webkit |
43 // glue. It is used here to get P2PPacketDispatcher corresponding to | 43 // glue. It is used here to get P2PPacketDispatcher corresponding to |
44 // the current RenderView. Use P2P Pepper API for connection and | 44 // the current RenderView. Use P2P Pepper API for connection and |
45 // remove these includes. | 45 // remove these includes. |
46 // crbug.com/74951 | 46 // crbug.com/74951 |
47 #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" | 47 #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" |
48 #include "webkit/plugins/ppapi/resource_tracker.h" | 48 #include "webkit/plugins/ppapi/resource_tracker.h" |
49 | 49 |
50 namespace remoting { | 50 namespace remoting { |
51 | 51 |
52 // This flag blocks LOGs to the UI if we're already in the middle of logging | |
53 // to the UI. This prevents a potential infinite loop if we encounter an error | |
54 // while sending the log message to the UI. | |
55 static bool logging_to_plugin_ = false; | |
dmac
2011/07/21 23:37:07
should these be marked with a g in front of them?
garykac
2011/08/02 00:15:37
Done.
| |
56 static ChromotingInstance* logging_instance_ = NULL; | |
57 static MessageLoop* logging_message_loop_ = NULL; | |
58 static logging::LogMessageHandlerFunction logging_old_handler_ = NULL; | |
59 | |
52 const char* ChromotingInstance::kMimeType = "pepper-application/x-chromoting"; | 60 const char* ChromotingInstance::kMimeType = "pepper-application/x-chromoting"; |
53 | 61 |
54 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) | 62 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) |
55 : pp::InstancePrivate(pp_instance), | 63 : pp::InstancePrivate(pp_instance), |
56 initialized_(false), | 64 initialized_(false) { |
57 logger_(this) { | |
58 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); | 65 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); |
59 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); | 66 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); |
67 logging_old_handler_ = logging::GetLogMessageHandler(); | |
68 logging::SetLogMessageHandler(&LogToUI); | |
69 logging_instance_ = this; | |
70 logging_message_loop_ = MessageLoop::current(); | |
60 } | 71 } |
61 | 72 |
62 ChromotingInstance::~ChromotingInstance() { | 73 ChromotingInstance::~ChromotingInstance() { |
63 DCHECK(CurrentlyOnPluginThread()); | 74 DCHECK(CurrentlyOnPluginThread()); |
64 | 75 |
76 logging::SetLogMessageHandler(logging_old_handler_); | |
77 logging_old_handler_ = NULL; | |
78 logging_instance_ = NULL; | |
79 logging_message_loop_ = NULL; | |
80 | |
65 if (client_.get()) { | 81 if (client_.get()) { |
66 base::WaitableEvent done_event(true, false); | 82 base::WaitableEvent done_event(true, false); |
67 client_->Stop(base::Bind(&base::WaitableEvent::Signal, | 83 client_->Stop(base::Bind(&base::WaitableEvent::Signal, |
68 base::Unretained(&done_event))); | 84 base::Unretained(&done_event))); |
69 done_event.Wait(); | 85 done_event.Wait(); |
70 } | 86 } |
71 | 87 |
72 // Stopping the context shutdown all chromoting threads. This is a requirement | 88 // Stopping the context shutdown all chromoting threads. This is a requirement |
73 // before we can call Detach() on |view_proxy_|. | 89 // before we can call Detach() on |view_proxy_|. |
74 context_.Stop(); | 90 context_.Stop(); |
75 | 91 |
76 view_proxy_->Detach(); | 92 view_proxy_->Detach(); |
77 } | 93 } |
78 | 94 |
79 bool ChromotingInstance::Init(uint32_t argc, | 95 bool ChromotingInstance::Init(uint32_t argc, |
80 const char* argn[], | 96 const char* argn[], |
81 const char* argv[]) { | 97 const char* argv[]) { |
82 CHECK(!initialized_); | 98 CHECK(!initialized_); |
83 initialized_ = true; | 99 initialized_ = true; |
84 | 100 |
85 logger_.VLog(1, "Started ChromotingInstance::Init"); | 101 VLOG(1) << "Started ChromotingInstance::Init"; |
86 | 102 |
87 // Start all the threads. | 103 // Start all the threads. |
88 context_.Start(); | 104 context_.Start(); |
89 | 105 |
90 webkit::ppapi::PluginInstance* plugin_instance = | 106 webkit::ppapi::PluginInstance* plugin_instance = |
91 webkit::ppapi::ResourceTracker::Get()->GetInstance(pp_instance()); | 107 webkit::ppapi::ResourceTracker::Get()->GetInstance(pp_instance()); |
92 | 108 |
93 P2PSocketDispatcher* socket_dispatcher = | 109 P2PSocketDispatcher* socket_dispatcher = |
94 plugin_instance->delegate()->GetP2PSocketDispatcher(); | 110 plugin_instance->delegate()->GetP2PSocketDispatcher(); |
95 IpcNetworkManager* network_manager = NULL; | 111 IpcNetworkManager* network_manager = NULL; |
96 IpcPacketSocketFactory* socket_factory = NULL; | 112 IpcPacketSocketFactory* socket_factory = NULL; |
97 PortAllocatorSessionFactory* session_factory = | 113 PortAllocatorSessionFactory* session_factory = |
98 CreatePepperPortAllocatorSessionFactory(this); | 114 CreatePepperPortAllocatorSessionFactory(this); |
99 | 115 |
100 // If we don't have socket dispatcher for IPC (e.g. P2P API is | 116 // If we don't have socket dispatcher for IPC (e.g. P2P API is |
101 // disabled), then JingleSessionManager will try to use physical sockets. | 117 // disabled), then JingleSessionManager will try to use physical sockets. |
102 if (socket_dispatcher) { | 118 if (socket_dispatcher) { |
103 logger_.VLog(1, "Creating IpcNetworkManager and IpcPacketSocketFactory."); | 119 VLOG(1) << "Creating IpcNetworkManager and IpcPacketSocketFactory."; |
104 network_manager = new IpcNetworkManager(socket_dispatcher); | 120 network_manager = new IpcNetworkManager(socket_dispatcher); |
105 socket_factory = new IpcPacketSocketFactory(socket_dispatcher); | 121 socket_factory = new IpcPacketSocketFactory(socket_dispatcher); |
106 } | 122 } |
107 | 123 |
108 // Create the chromoting objects. | 124 // Create the chromoting objects. |
109 host_connection_.reset(new protocol::ConnectionToHost( | 125 host_connection_.reset(new protocol::ConnectionToHost( |
110 context_.network_message_loop(), network_manager, socket_factory, | 126 context_.network_message_loop(), network_manager, socket_factory, |
111 session_factory)); | 127 session_factory)); |
112 view_.reset(new PepperView(this, &context_)); | 128 view_.reset(new PepperView(this, &context_)); |
113 view_proxy_ = new PepperViewProxy(this, view_.get()); | 129 view_proxy_ = new PepperViewProxy(this, view_.get()); |
114 rectangle_decoder_ = new RectangleUpdateDecoder( | 130 rectangle_decoder_ = new RectangleUpdateDecoder( |
115 context_.decode_message_loop(), view_proxy_); | 131 context_.decode_message_loop(), view_proxy_); |
116 input_handler_.reset(new PepperInputHandler(&context_, | 132 input_handler_.reset(new PepperInputHandler(&context_, |
117 host_connection_.get(), | 133 host_connection_.get(), |
118 view_proxy_)); | 134 view_proxy_)); |
119 | 135 |
120 // Default to a medium grey. | 136 // Default to a medium grey. |
121 view_->SetSolidFill(0xFFCDCDCD); | 137 view_->SetSolidFill(0xFFCDCDCD); |
122 | 138 |
123 return true; | 139 return true; |
124 } | 140 } |
125 | 141 |
126 void ChromotingInstance::Connect(const ClientConfig& config) { | 142 void ChromotingInstance::Connect(const ClientConfig& config) { |
127 DCHECK(CurrentlyOnPluginThread()); | 143 DCHECK(CurrentlyOnPluginThread()); |
128 | 144 |
129 client_.reset(new ChromotingClient(config, &context_, host_connection_.get(), | 145 client_.reset(new ChromotingClient(config, &context_, host_connection_.get(), |
130 view_proxy_, rectangle_decoder_.get(), | 146 view_proxy_, rectangle_decoder_.get(), |
131 input_handler_.get(), &logger_, NULL)); | 147 input_handler_.get(), NULL)); |
132 | 148 |
133 logger_.Log(logging::LOG_INFO, "Connecting."); | 149 LOG(INFO) << "Connecting."; |
134 | 150 |
135 // Setup the XMPP Proxy. | 151 // Setup the XMPP Proxy. |
136 ChromotingScriptableObject* scriptable_object = GetScriptableObject(); | 152 ChromotingScriptableObject* scriptable_object = GetScriptableObject(); |
137 scoped_refptr<PepperXmppProxy> xmpp_proxy = | 153 scoped_refptr<PepperXmppProxy> xmpp_proxy = |
138 new PepperXmppProxy(scriptable_object->AsWeakPtr(), | 154 new PepperXmppProxy(scriptable_object->AsWeakPtr(), |
139 context_.jingle_thread()->message_loop()); | 155 context_.jingle_thread()->message_loop()); |
140 scriptable_object->AttachXmppProxy(xmpp_proxy); | 156 scriptable_object->AttachXmppProxy(xmpp_proxy); |
141 | 157 |
142 // Kick off the connection. | 158 // Kick off the connection. |
143 client_->Start(xmpp_proxy); | 159 client_->Start(xmpp_proxy); |
144 | 160 |
145 logger_.Log(logging::LOG_INFO, "Connection status: Initializing"); | 161 LOG(INFO) << "Connection status: Initializing"; |
146 GetScriptableObject()->SetConnectionInfo(STATUS_INITIALIZING, | 162 GetScriptableObject()->SetConnectionInfo(STATUS_INITIALIZING, |
147 QUALITY_UNKNOWN); | 163 QUALITY_UNKNOWN); |
148 } | 164 } |
149 | 165 |
150 void ChromotingInstance::Disconnect() { | 166 void ChromotingInstance::Disconnect() { |
151 DCHECK(CurrentlyOnPluginThread()); | 167 DCHECK(CurrentlyOnPluginThread()); |
152 | 168 |
153 logger_.Log(logging::LOG_INFO, "Disconnecting from host."); | 169 LOG(INFO) << "Disconnecting from host."; |
154 if (client_.get()) { | 170 if (client_.get()) { |
155 // TODO(sergeyu): Should we disconnect asynchronously? | 171 // TODO(sergeyu): Should we disconnect asynchronously? |
156 base::WaitableEvent done_event(true, false); | 172 base::WaitableEvent done_event(true, false); |
157 client_->Stop(base::Bind(&base::WaitableEvent::Signal, | 173 client_->Stop(base::Bind(&base::WaitableEvent::Signal, |
158 base::Unretained(&done_event))); | 174 base::Unretained(&done_event))); |
159 done_event.Wait(); | 175 done_event.Wait(); |
160 } | 176 } |
161 | 177 |
162 GetScriptableObject()->SetConnectionInfo(STATUS_CLOSED, QUALITY_UNKNOWN); | 178 GetScriptableObject()->SetConnectionInfo(STATUS_CLOSED, QUALITY_UNKNOWN); |
163 } | 179 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
196 } | 212 } |
197 | 213 |
198 case PP_INPUTEVENT_TYPE_CONTEXTMENU: { | 214 case PP_INPUTEVENT_TYPE_CONTEXTMENU: { |
199 // We need to return true here or else we'll get a local (plugin) context | 215 // We need to return true here or else we'll get a local (plugin) context |
200 // menu instead of the mouseup event for the right click. | 216 // menu instead of the mouseup event for the right click. |
201 return true; | 217 return true; |
202 } | 218 } |
203 | 219 |
204 case PP_INPUTEVENT_TYPE_KEYDOWN: { | 220 case PP_INPUTEVENT_TYPE_KEYDOWN: { |
205 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); | 221 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); |
206 logger_.VLog(3, "PP_INPUTEVENT_TYPE_KEYDOWN key=%d", key.GetKeyCode()); | 222 VLOG(3) << "PP_INPUTEVENT_TYPE_KEYDOWN" << " key=" << key.GetKeyCode(); |
207 pih->HandleKeyEvent(true, key); | 223 pih->HandleKeyEvent(true, key); |
208 return true; | 224 return true; |
209 } | 225 } |
210 | 226 |
211 case PP_INPUTEVENT_TYPE_KEYUP: { | 227 case PP_INPUTEVENT_TYPE_KEYUP: { |
212 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); | 228 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); |
213 logger_.VLog(3, "PP_INPUTEVENT_TYPE_KEYUP key=%d", key.GetKeyCode()); | 229 VLOG(3) << "PP_INPUTEVENT_TYPE_KEYUP" << " key=" << key.GetKeyCode(); |
214 pih->HandleKeyEvent(false, key); | 230 pih->HandleKeyEvent(false, key); |
215 return true; | 231 return true; |
216 } | 232 } |
217 | 233 |
218 case PP_INPUTEVENT_TYPE_CHAR: { | 234 case PP_INPUTEVENT_TYPE_CHAR: { |
219 pih->HandleCharacterEvent(pp::KeyboardInputEvent(event)); | 235 pih->HandleCharacterEvent(pp::KeyboardInputEvent(event)); |
220 return true; | 236 return true; |
221 } | 237 } |
222 | 238 |
223 default: { | 239 default: { |
224 LOG(INFO) << "Unhandled input event: " << event.GetType(); | 240 LOG(INFO) << "Unhandled input event: " << event.GetType(); |
225 break; | 241 break; |
226 } | 242 } |
227 } | 243 } |
228 | 244 |
229 return false; | 245 return false; |
230 } | 246 } |
231 | 247 |
232 ChromotingScriptableObject* ChromotingInstance::GetScriptableObject() { | 248 ChromotingScriptableObject* ChromotingInstance::GetScriptableObject() { |
233 pp::VarPrivate object = GetInstanceObject(); | 249 pp::VarPrivate object = GetInstanceObject(); |
234 if (!object.is_undefined()) { | 250 if (!object.is_undefined()) { |
235 pp::deprecated::ScriptableObject* so = object.AsScriptableObject(); | 251 pp::deprecated::ScriptableObject* so = object.AsScriptableObject(); |
236 DCHECK(so != NULL); | 252 DCHECK(so != NULL); |
237 return static_cast<ChromotingScriptableObject*>(so); | 253 return static_cast<ChromotingScriptableObject*>(so); |
238 } | 254 } |
239 logger_.Log(logging::LOG_ERROR, | 255 LOG(ERROR) << "Unable to get ScriptableObject for Chromoting plugin."; |
240 "Unable to get ScriptableObject for Chromoting plugin."); | |
241 return NULL; | 256 return NULL; |
242 } | 257 } |
243 | 258 |
244 void ChromotingInstance::SubmitLoginInfo(const std::string& username, | 259 void ChromotingInstance::SubmitLoginInfo(const std::string& username, |
245 const std::string& password) { | 260 const std::string& password) { |
246 if (host_connection_->state() != | 261 if (host_connection_->state() != |
247 protocol::ConnectionToHost::STATE_CONNECTED) { | 262 protocol::ConnectionToHost::STATE_CONNECTED) { |
248 logger_.Log(logging::LOG_INFO, | 263 LOG(INFO) << "Client not connected or already authenticated."; |
249 "Client not connected or already authenticated."); | |
250 return; | 264 return; |
251 } | 265 } |
252 | 266 |
253 protocol::LocalLoginCredentials* credentials = | 267 protocol::LocalLoginCredentials* credentials = |
254 new protocol::LocalLoginCredentials(); | 268 new protocol::LocalLoginCredentials(); |
255 credentials->set_type(protocol::PASSWORD); | 269 credentials->set_type(protocol::PASSWORD); |
256 credentials->set_username(username); | 270 credentials->set_username(username); |
257 credentials->set_credential(password.data(), password.length()); | 271 credentials->set_credential(password.data(), password.length()); |
258 | 272 |
259 host_connection_->host_stub()->BeginSessionRequest( | 273 host_connection_->host_stub()->BeginSessionRequest( |
260 credentials, | 274 credentials, |
261 new DeleteTask<protocol::LocalLoginCredentials>(credentials)); | 275 new DeleteTask<protocol::LocalLoginCredentials>(credentials)); |
262 } | 276 } |
263 | 277 |
264 void ChromotingInstance::SetScaleToFit(bool scale_to_fit) { | 278 void ChromotingInstance::SetScaleToFit(bool scale_to_fit) { |
265 view_proxy_->SetScaleToFit(scale_to_fit); | 279 view_proxy_->SetScaleToFit(scale_to_fit); |
266 } | 280 } |
267 | 281 |
268 void ChromotingInstance::Log(int severity, const char* format, ...) { | 282 // static |
269 va_list ap; | 283 bool ChromotingInstance::LogToUI(int severity, const char* file, int line, |
270 va_start(ap, format); | 284 size_t message_start, |
271 logger_.va_Log(severity, format, ap); | 285 const std::string& str) { |
272 va_end(ap); | 286 if (logging_instance_ && !logging_to_plugin_) { |
287 logging_to_plugin_ = true; | |
dmac
2011/07/21 23:37:07
logging_to_plugin_ race conditions about the globa
garykac
2011/08/02 00:15:37
True. I pushed the check down into the "CorrectThr
| |
288 std::string message = remoting::GetTimestampString(); | |
289 message += (str.c_str() + message_start); | |
290 logging_instance_->LogToUI_CorrectThread(message); | |
291 logging_to_plugin_ = false; | |
292 } | |
293 | |
294 if (logging_old_handler_) | |
295 return (logging_old_handler_)(severity, file, line, message_start, str); | |
296 return false; | |
273 } | 297 } |
274 | 298 |
275 void ChromotingInstance::VLog(int verboselevel, const char* format, ...) { | 299 void ChromotingInstance::LogToUI_CorrectThread(const std::string& message) { |
276 va_list ap; | 300 if (!logging_message_loop_) |
277 va_start(ap, format); | 301 return; |
278 logger_.va_VLog(verboselevel, format, ap); | 302 |
279 va_end(ap); | 303 if (logging_message_loop_ != MessageLoop::current()) { |
304 logging_message_loop_->PostTask( | |
305 FROM_HERE, | |
306 NewRunnableMethod(this, | |
307 &ChromotingInstance::LogToUI_CorrectThread, message)); | |
308 return; | |
309 } | |
310 | |
311 ChromotingScriptableObject* cso = GetScriptableObject(); | |
312 if (cso) { | |
313 cso->LogDebugInfo(message); | |
314 } | |
280 } | 315 } |
281 | 316 |
282 pp::Var ChromotingInstance::GetInstanceObject() { | 317 pp::Var ChromotingInstance::GetInstanceObject() { |
283 if (instance_object_.is_undefined()) { | 318 if (instance_object_.is_undefined()) { |
284 ChromotingScriptableObject* object = new ChromotingScriptableObject(this); | 319 ChromotingScriptableObject* object = new ChromotingScriptableObject(this); |
285 object->Init(); | 320 object->Init(); |
286 | 321 |
287 // The pp::Var takes ownership of object here. | 322 // The pp::Var takes ownership of object here. |
288 instance_object_ = pp::VarPrivate(this, object); | 323 instance_object_ = pp::VarPrivate(this, object); |
289 } | 324 } |
290 | 325 |
291 return instance_object_; | 326 return instance_object_; |
292 } | 327 } |
293 | 328 |
294 ChromotingStats* ChromotingInstance::GetStats() { | 329 ChromotingStats* ChromotingInstance::GetStats() { |
295 if (!client_.get()) | 330 if (!client_.get()) |
296 return NULL; | 331 return NULL; |
297 return client_->GetStats(); | 332 return client_->GetStats(); |
298 } | 333 } |
299 | 334 |
300 void ChromotingInstance::ReleaseAllKeys() { | 335 void ChromotingInstance::ReleaseAllKeys() { |
301 input_handler_->ReleaseAllKeys(); | 336 input_handler_->ReleaseAllKeys(); |
302 } | 337 } |
303 | 338 |
304 } // namespace remoting | 339 } // namespace remoting |
OLD | NEW |