From 278733b2d94229656f39fef93fa4b0635edd82a0 Mon Sep 17 00:00:00 2001 From: "niklase@google.com" Date: Mon, 30 May 2011 11:39:02 +0000 Subject: [PATCH] git-svn-id: http://webrtc.googlecode.com/svn/trunk@5 4adac7df-926f-26a2-2b94-8c16560cd09d --- peerconnection/samples/client/conductor.cc | 321 ++++++++++++++ peerconnection/samples/client/conductor.h | 117 +++++ peerconnection/samples/client/defaults.cc | 48 +++ peerconnection/samples/client/defaults.h | 30 ++ peerconnection/samples/client/main.cc | 57 +++ peerconnection/samples/client/main_wnd.cc | 396 +++++++++++++++++ peerconnection/samples/client/main_wnd.h | 102 +++++ .../samples/client/peer_connection_client.cc | 406 ++++++++++++++++++ .../samples/client/peer_connection_client.h | 103 +++++ 9 files changed, 1580 insertions(+) create mode 100644 peerconnection/samples/client/conductor.cc create mode 100644 peerconnection/samples/client/conductor.h create mode 100644 peerconnection/samples/client/defaults.cc create mode 100644 peerconnection/samples/client/defaults.h create mode 100644 peerconnection/samples/client/main.cc create mode 100644 peerconnection/samples/client/main_wnd.cc create mode 100644 peerconnection/samples/client/main_wnd.h create mode 100644 peerconnection/samples/client/peer_connection_client.cc create mode 100644 peerconnection/samples/client/peer_connection_client.h diff --git a/peerconnection/samples/client/conductor.cc b/peerconnection/samples/client/conductor.cc new file mode 100644 index 000000000..698ba4822 --- /dev/null +++ b/peerconnection/samples/client/conductor.cc @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "peerconnection/samples/client/conductor.h" + +#include "peerconnection/samples/client/defaults.h" +#include "talk/base/logging.h" + +Conductor::Conductor(PeerConnectionClient* client, MainWnd* main_wnd) + : handshake_(NONE), + waiting_for_audio_(false), + waiting_for_video_(false), + peer_id_(-1), + video_channel_(-1), + audio_channel_(-1), + client_(client), + main_wnd_(main_wnd) { + // Create a window for posting notifications back to from other threads. + bool ok = Create(HWND_MESSAGE, L"Conductor", 0, 0, 0, 0, 0, 0); + ASSERT(ok); + client_->RegisterObserver(this); + main_wnd->RegisterObserver(this); +} + +Conductor::~Conductor() { + ASSERT(peer_connection_.get() == NULL); + Destroy(); + DeletePeerConnection(); +} + +bool Conductor::has_video() const { + return video_channel_ != -1; +} + +bool Conductor::has_audio() const { + return audio_channel_ != -1; +} + +bool Conductor::connection_active() const { + return peer_connection_.get() != NULL; +} + +void Conductor::Close() { + if (peer_connection_.get()) { + peer_connection_->Close(); + } else { + client_->SignOut(); + } +} + +bool Conductor::InitializePeerConnection() { + ASSERT(peer_connection_.get() == NULL); + peer_connection_.reset(new webrtc::PeerConnection(GetPeerConnectionString())); + peer_connection_->RegisterObserver(this); + if (!peer_connection_->Init()) { + DeletePeerConnection(); + } else { + bool audio = peer_connection_->SetAudioDevice("", "", 0); + LOG(INFO) << "SetAudioDevice " << (audio ? "succeeded." : "failed."); + } + return peer_connection_.get() != NULL; +} + +void Conductor::DeletePeerConnection() { + peer_connection_.reset(); + handshake_ = NONE; +} + +void Conductor::StartCaptureDevice() { + ASSERT(peer_connection_.get()); + if (main_wnd_->IsWindow()) { + main_wnd_->SwitchToStreamingUI(); + + if (peer_connection_->SetVideoCapture("")) { + peer_connection_->SetVideoRenderer(-1, main_wnd_->handle(), 0, + 0.7f, 0.7f, 0.95f, 0.95f); + } else { + ASSERT(false); + } + } +} + +// +// PeerConnectionObserver implementation. +// + +void Conductor::OnError() { + LOG(INFO) << __FUNCTION__; + ASSERT(false); +} + +void Conductor::OnSignalingMessage(const std::string& msg) { + LOG(INFO) << __FUNCTION__; + + bool shutting_down = (video_channel_ == -1 && audio_channel_ == -1); + + if (handshake_ == OFFER_RECEIVED && !shutting_down) + StartCaptureDevice(); + + // Send our answer/offer/shutting down message. + // If we're the initiator, this will be our offer. If we just received + // an offer, this will be an answer. If PeerConnection::Close has been + // called, then this is our signal to the other end that we're shutting + // down. + if (handshake_ != QUIT_SENT) { + SendMessage(handle(), SEND_MESSAGE_TO_PEER, 0, + reinterpret_cast(&msg)); + } + + if (shutting_down) { + handshake_ = QUIT_SENT; + PostMessage(handle(), PEER_CONNECTION_CLOSED, 0, 0); + } +} + +// Called when a remote stream is added +void Conductor::OnAddStream(const std::string& stream_id, int channel_id, + bool video) { + LOG(INFO) << __FUNCTION__ << " " << stream_id; + bool send_notification = (waiting_for_video_ || waiting_for_audio_); + if (video) { + ASSERT(video_channel_ == -1); + video_channel_ = channel_id; + waiting_for_video_ = false; + LOG(INFO) << "Setting video renderer for channel: " << channel_id; + bool ok = peer_connection_->SetVideoRenderer(channel_id, + main_wnd_->handle(), 1, 0.0f, 0.0f, 1.0f, 1.0f); + ASSERT(ok); + } else { + ASSERT(audio_channel_ == -1); + audio_channel_ = channel_id; + waiting_for_audio_ = false; + } + + if (send_notification && !waiting_for_audio_ && !waiting_for_video_) + PostMessage(handle(), MEDIA_CHANNELS_INITIALIZED, 0, 0); +} + +void Conductor::OnRemoveStream(const std::string& stream_id, int channel_id, + bool video) { + LOG(INFO) << __FUNCTION__; + if (video) { + ASSERT(channel_id == video_channel_); + video_channel_ = -1; + } else { + ASSERT(channel_id == audio_channel_); + audio_channel_ = -1; + } +} + +// +// PeerConnectionClientObserver implementation. +// + +void Conductor::OnSignedIn() { + LOG(INFO) << __FUNCTION__; + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnDisconnected() { + LOG(INFO) << __FUNCTION__; + if (peer_connection_.get()) { + peer_connection_->Close(); + } else if (main_wnd_->IsWindow()) { + main_wnd_->SwitchToConnectUI(); + } +} + +void Conductor::OnPeerConnected(int id, const std::string& name) { + LOG(INFO) << __FUNCTION__; + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWnd::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnPeerDisconnected(int id, const std::string& name) { + LOG(INFO) << __FUNCTION__; + if (id == peer_id_) { + LOG(INFO) << "Our peer disconnected"; + peer_id_ = -1; + if (peer_connection_.get()) + peer_connection_->Close(); + } + + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWnd::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) { + ASSERT(peer_id_ == peer_id || peer_id_ == -1); + + if (handshake_ == NONE) { + handshake_ = OFFER_RECEIVED; + peer_id_ = peer_id; + if (!peer_connection_.get()) { + // Got an offer. Give it to the PeerConnection instance. + // Once processed, we will get a callback to OnSignalingMessage with + // our 'answer' which we'll send to the peer. + LOG(INFO) << "Got an offer from our peer: " << peer_id; + if (!InitializePeerConnection()) { + LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + client_->SignOut(); + return; + } + } + } else if (handshake_ == INITIATOR) { + LOG(INFO) << "Remote peer sent us an answer"; + handshake_ = ANSWER_RECEIVED; + } else { + LOG(INFO) << "Remote peer is disconnecting"; + handshake_ = QUIT_SENT; + } + + peer_connection_->SignalingMessage(message); + + if (handshake_ == QUIT_SENT) { + DisconnectFromCurrentPeer(); + } +} + +// +// MainWndCallback implementation. +// + +void Conductor::StartLogin(const std::string& server, int port) { + ASSERT(!client_->is_connected()); + if (!client_->Connect(server, port, GetPeerName())) { + MessageBoxA(main_wnd_->handle(), + ("Failed to connect to " + server).c_str(), + "Error", MB_OK | MB_ICONERROR); + } +} + +void Conductor::DisconnectFromServer() { + if (!client_->is_connected()) + return; + client_->SignOut(); +} + +void Conductor::ConnectToPeer(int peer_id) { + ASSERT(peer_id_ == -1); + ASSERT(peer_id != -1); + ASSERT(handshake_ == NONE); + + if (handshake_ != NONE) + return; + + if (InitializePeerConnection()) { + peer_id_ = peer_id; + waiting_for_video_ = peer_connection_->AddStream(kVideoLabel, true); + waiting_for_audio_ = peer_connection_->AddStream(kAudioLabel, false); + if (waiting_for_video_ || waiting_for_audio_) + handshake_ = INITIATOR; + ASSERT(waiting_for_video_ || waiting_for_audio_); + } + + if (handshake_ == NONE) { + ::MessageBoxA(main_wnd_->handle(), "Failed to initialize PeerConnection", + "Error", MB_OK | MB_ICONERROR); + } +} + +void Conductor::DisconnectFromCurrentPeer() { + if (peer_connection_.get()) + peer_connection_->Close(); +} + +// +// Win32Window implementation. +// + +bool Conductor::OnMessage(UINT msg, WPARAM wp, LPARAM lp, + LRESULT& result) { // NOLINT + bool ret = true; + if (msg == MEDIA_CHANNELS_INITIALIZED) { + ASSERT(handshake_ == INITIATOR); + bool ok = peer_connection_->Connect(); + ASSERT(ok); + StartCaptureDevice(); + // When we get an OnSignalingMessage notification, we'll send our + // json encoded signaling message to the peer, which is the first step + // of establishing a connection. + } else if (msg == PEER_CONNECTION_CLOSED) { + LOG(INFO) << "PEER_CONNECTION_CLOSED"; + DeletePeerConnection(); + ::InvalidateRect(main_wnd_->handle(), NULL, TRUE); + waiting_for_audio_ = false; + waiting_for_video_ = false; + peer_id_ = -1; + ASSERT(video_channel_ == -1); + ASSERT(audio_channel_ == -1); + if (main_wnd_->IsWindow()) { + if (client_->is_connected()) { + main_wnd_->SwitchToPeerList(client_->peers()); + } else { + main_wnd_->SwitchToConnectUI(); + } + } else { + DisconnectFromServer(); + } + } else if (msg == SEND_MESSAGE_TO_PEER) { + bool ok = client_->SendToPeer(peer_id_, + *reinterpret_cast(lp)); + if (!ok) { + LOG(LS_ERROR) << "SendToPeer failed"; + DisconnectFromServer(); + } + } else { + ret = false; + } + + return ret; +} diff --git a/peerconnection/samples/client/conductor.h b/peerconnection/samples/client/conductor.h new file mode 100644 index 000000000..ce968d768 --- /dev/null +++ b/peerconnection/samples/client/conductor.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ +#pragma once + +#include + +#include "peerconnection/samples/client/main_wnd.h" +#include "peerconnection/samples/client/peer_connection_client.h" +#include "talk/app/peerconnection.h" +#include "talk/base/scoped_ptr.h" + + +class Conductor + : public webrtc::PeerConnectionObserver, + public PeerConnectionClientObserver, + public MainWndCallback, + public talk_base::Win32Window { + public: + enum WindowMessages { + MEDIA_CHANNELS_INITIALIZED = WM_APP + 1, + PEER_CONNECTION_CLOSED, + SEND_MESSAGE_TO_PEER, + }; + + enum HandshakeState { + NONE, + INITIATOR, + ANSWER_RECEIVED, + OFFER_RECEIVED, + QUIT_SENT, + }; + + Conductor(PeerConnectionClient* client, MainWnd* main_wnd); + ~Conductor(); + + bool has_video() const; + bool has_audio() const; + bool connection_active() const; + + void Close(); + + protected: + bool InitializePeerConnection(); + void DeletePeerConnection(); + void StartCaptureDevice(); + + // + // PeerConnectionObserver implementation. + // + + virtual void OnError(); + virtual void OnSignalingMessage(const std::string& msg); + + // Called when a remote stream is added + virtual void OnAddStream(const std::string& stream_id, int channel_id, + bool video); + + virtual void OnRemoveStream(const std::string& stream_id, + int channel_id, + bool video); + + // + // PeerConnectionClientObserver implementation. + // + + virtual void OnSignedIn(); + + virtual void OnDisconnected(); + + virtual void OnPeerConnected(int id, const std::string& name); + + virtual void OnPeerDisconnected(int id, const std::string& name); + + virtual void OnMessageFromPeer(int peer_id, const std::string& message); + + // + // MainWndCallback implementation. + // + + virtual void StartLogin(const std::string& server, int port); + + virtual void DisconnectFromServer(); + + virtual void ConnectToPeer(int peer_id); + + virtual void DisconnectFromCurrentPeer(); + + // + // Win32Window implementation. + // + + virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, + LRESULT& result); // NOLINT + + protected: + HandshakeState handshake_; + bool waiting_for_audio_; + bool waiting_for_video_; + int peer_id_; + talk_base::scoped_ptr peer_connection_; + PeerConnectionClient* client_; + MainWnd* main_wnd_; + int video_channel_; + int audio_channel_; +}; + +#endif // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ diff --git a/peerconnection/samples/client/defaults.cc b/peerconnection/samples/client/defaults.cc new file mode 100644 index 000000000..c8e852757 --- /dev/null +++ b/peerconnection/samples/client/defaults.cc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "peerconnection/samples/client/defaults.h" + +const char kAudioLabel[] = "audio_label"; +const char kVideoLabel[] = "video_label"; +const uint16 kDefaultServerPort = 8888; + +std::string GetEnvVarOrDefault(const char* env_var_name, + const char* default_value) { + std::string value; + const char* env_var = getenv(env_var_name); + if (env_var) + value = env_var; + + if (value.empty()) + value = default_value; + + return value; +} + +std::string GetPeerConnectionString() { + return GetEnvVarOrDefault("WEBRTC_CONNECT", "STUN stun.l.google.com:19302"); +} + +std::string GetDefaultServerName() { + return GetEnvVarOrDefault("WEBRTC_SERVER", "localhost"); +} + +std::string GetPeerName() { + char computer_name[MAX_PATH] = {0}, user_name[MAX_PATH] = {0}; + DWORD size = ARRAYSIZE(computer_name); + ::GetComputerNameA(computer_name, &size); + size = ARRAYSIZE(user_name); + ::GetUserNameA(user_name, &size); + std::string ret(user_name); + ret += '@'; + ret += computer_name; + return ret; +} diff --git a/peerconnection/samples/client/defaults.h b/peerconnection/samples/client/defaults.h new file mode 100644 index 000000000..466a965de --- /dev/null +++ b/peerconnection/samples/client/defaults.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ +#pragma once + +#include +#include + +#include "talk/base/basictypes.h" + +extern const char kAudioLabel[]; +extern const char kVideoLabel[]; +extern const uint16 kDefaultServerPort; + +std::string GetEnvVarOrDefault(const char* env_var_name, + const char* default_value); +std::string GetPeerConnectionString(); +std::string GetDefaultServerName(); +std::string GetPeerName(); + +#endif // PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ diff --git a/peerconnection/samples/client/main.cc b/peerconnection/samples/client/main.cc new file mode 100644 index 000000000..21054b193 --- /dev/null +++ b/peerconnection/samples/client/main.cc @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "peerconnection/samples/client/conductor.h" +#include "peerconnection/samples/client/main_wnd.h" +#include "peerconnection/samples/client/peer_connection_client.h" +#include "system_wrappers/source/trace_impl.h" +#include "talk/base/win32socketinit.h" + + +int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance, + wchar_t* cmd_line, int cmd_show) { + talk_base::EnsureWinsockInit(); + + webrtc::Trace::CreateTrace(); + webrtc::Trace::SetTraceFile("peerconnection_client.log"); + webrtc::Trace::SetLevelFilter(webrtc::kTraceWarning); + + MainWnd wnd; + if (!wnd.Create()) { + ASSERT(false); + return -1; + } + + PeerConnectionClient client; + Conductor conductor(&client, &wnd); + + // Main loop. + MSG msg; + BOOL gm; + while ((gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) { + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + + if (conductor.connection_active() || client.is_connected()) { + conductor.Close(); + while ((conductor.connection_active() || client.is_connected()) && + (gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + + return 0; +} diff --git a/peerconnection/samples/client/main_wnd.cc b/peerconnection/samples/client/main_wnd.cc new file mode 100644 index 000000000..e75133f4f --- /dev/null +++ b/peerconnection/samples/client/main_wnd.cc @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "peerconnection/samples/client/main_wnd.h" + +#include "talk/base/common.h" +#include "talk/base/logging.h" + +ATOM MainWnd::wnd_class_ = 0; +const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd"; + +// TODO(tommi): declare in header: +std::string GetDefaultServerName(); + +namespace { +void CalculateWindowSizeForText(HWND wnd, const wchar_t* text, + size_t* width, size_t* height) { + HDC dc = ::GetDC(wnd); + RECT text_rc = {0}; + ::DrawText(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE); + ::ReleaseDC(wnd, dc); + RECT client, window; + ::GetClientRect(wnd, &client); + ::GetWindowRect(wnd, &window); + + *width = text_rc.right - text_rc.left; + *width += (window.right - window.left) - + (client.right - client.left); + *height = text_rc.bottom - text_rc.top; + *height += (window.bottom - window.top) - + (client.bottom - client.top); +} + +HFONT GetDefaultFont() { + static HFONT font = reinterpret_cast(GetStockObject(DEFAULT_GUI_FONT)); + return font; +} + +std::string GetWindowText(HWND wnd) { + char text[MAX_PATH] = {0}; + ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text)); + return text; +} + +void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) { + LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0, + reinterpret_cast(str.c_str())); + ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data); +} + +} // namespace + +MainWnd::MainWnd() + : ui_(CONNECT_TO_SERVER), wnd_(NULL), edit1_(NULL), edit2_(NULL), + label1_(NULL), label2_(NULL), button_(NULL), listbox_(NULL), + destroyed_(false), callback_(NULL), nested_msg_(NULL) { +} + +MainWnd::~MainWnd() { + ASSERT(!IsWindow()); +} + +bool MainWnd::Create() { + ASSERT(wnd_ == NULL); + if (!RegisterWindowClass()) + return false; + + wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC", + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, GetModuleHandle(NULL), this); + + ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast(GetDefaultFont()), + TRUE); + + CreateChildWindows(); + SwitchToConnectUI(); + + return wnd_ != NULL; +} + +bool MainWnd::Destroy() { + BOOL ret = FALSE; + if (IsWindow()) { + ret = ::DestroyWindow(wnd_); + } + + return ret != FALSE; +} + +void MainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + +bool MainWnd::IsWindow() const { + return wnd_ && ::IsWindow(wnd_) != FALSE; +} + +bool MainWnd::PreTranslateMessage(MSG* msg) { + bool ret = false; + if (msg->message == WM_CHAR) { + if (msg->wParam == VK_TAB) { + HandleTabbing(); + ret = true; + } else if (msg->wParam == VK_RETURN) { + OnDefaultAction(); + ret = true; + } else if (msg->wParam == VK_ESCAPE) { + if (callback_) { + if (ui_ == STREAMING) { + callback_->DisconnectFromCurrentPeer(); + } else { + callback_->DisconnectFromServer(); + } + } + } + } + return ret; +} + +void MainWnd::SwitchToConnectUI() { + ASSERT(IsWindow()); + LayoutPeerListUI(false); + ui_ = CONNECT_TO_SERVER; + LayoutConnectUI(true); + ::SetFocus(edit1_); +} + +void MainWnd::SwitchToPeerList(const Peers& peers) { + LayoutConnectUI(false); + + ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0); + + AddListBoxItem(listbox_, "List of currently connected peers:", -1); + Peers::const_iterator i = peers.begin(); + for (; i != peers.end(); ++i) + AddListBoxItem(listbox_, i->second.c_str(), i->first); + + ui_ = LIST_PEERS; + LayoutPeerListUI(true); +} + +void MainWnd::SwitchToStreamingUI() { + LayoutConnectUI(false); + LayoutPeerListUI(false); + ui_ = STREAMING; +} + +void MainWnd::OnPaint() { + PAINTSTRUCT ps; + ::BeginPaint(handle(), &ps); + + RECT rc; + ::GetClientRect(handle(), &rc); + HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + + ::EndPaint(handle(), &ps); +} + +void MainWnd::OnDestroyed() { + PostQuitMessage(0); +} + +void MainWnd::OnDefaultAction() { + if (!callback_) + return; + if (ui_ == CONNECT_TO_SERVER) { + std::string server(GetWindowText(edit1_)); + std::string port_str(GetWindowText(edit2_)); + int port = port_str.length() ? atoi(port_str.c_str()) : 0; + callback_->StartLogin(server, port); + } else if (ui_ == LIST_PEERS) { + LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0); + if (sel != LB_ERR) { + LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0); + if (peer_id != -1 && callback_) { + callback_->ConnectToPeer(peer_id); + } + } + } else { + MessageBoxA(wnd_, "OK!", "Yeah", MB_OK); + } +} + +bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { + switch (msg) { + case WM_ERASEBKGND: + *result = TRUE; + return true; + case WM_PAINT: + OnPaint(); + return true; + case WM_SETFOCUS: + if (ui_ == CONNECT_TO_SERVER) { + SetFocus(edit1_); + } + return true; + case WM_SIZE: + if (ui_ == CONNECT_TO_SERVER) { + LayoutConnectUI(true); + } else if (ui_ == LIST_PEERS) { + LayoutPeerListUI(true); + } + break; + case WM_CTLCOLORSTATIC: + *result = reinterpret_cast(GetSysColorBrush(COLOR_WINDOW)); + return true; + case WM_COMMAND: + if (button_ == reinterpret_cast(lp)) { + if (BN_CLICKED == HIWORD(wp)) + OnDefaultAction(); + } else if (listbox_ == reinterpret_cast(lp)) { + if (LBN_DBLCLK == HIWORD(wp)) { + OnDefaultAction(); + } + } + return true; + } + return false; +} + +// static +LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + MainWnd* me = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWL_USERDATA)); + if (!me && WM_CREATE == msg) { + CREATESTRUCT* cs = reinterpret_cast(lp); + me = reinterpret_cast(cs->lpCreateParams); + me->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast(me)); + } + + LRESULT result = 0; + if (me) { + void* prev_nested_msg = me->nested_msg_; + me->nested_msg_ = &msg; + + bool handled = me->OnMessage(msg, wp, lp, &result); + if (WM_NCDESTROY == msg) { + me->destroyed_ = true; + } else if (!handled) { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + if (me->destroyed_ && prev_nested_msg == NULL) { + me->OnDestroyed(); + me->wnd_ = NULL; + me->destroyed_ = false; + } + + me->nested_msg_ = prev_nested_msg; + } else { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + return result; +} + +// static +bool MainWnd::RegisterWindowClass() { + if (wnd_class_) + return true; + + WNDCLASSEX wcex = { sizeof(WNDCLASSEX) }; + wcex.style = CS_DBLCLKS; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); + wcex.lpfnWndProc = &WndProc; + wcex.lpszClassName = kClassName; + wnd_class_ = ::RegisterClassEx(&wcex); + ASSERT(wnd_class_); + return wnd_class_ != 0; +} + +void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id, + const wchar_t* class_name, DWORD control_style, + DWORD ex_style) { + if (::IsWindow(*wnd)) + return; + + // Child windows are invisible at first, and shown after being resized. + DWORD style = WS_CHILD | control_style; + *wnd = ::CreateWindowEx(ex_style, class_name, L"", style, + 100, 100, 100, 100, wnd_, + reinterpret_cast(id), + GetModuleHandle(NULL), NULL); + ASSERT(::IsWindow(*wnd)); + ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast(GetDefaultFont()), + TRUE); +} + +void MainWnd::CreateChildWindows() { + // Create the child windows in tab order. + CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit1_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit2_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0); + + CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox", + LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE); + + ::SetWindowTextA(edit1_, GetDefaultServerName().c_str()); + ::SetWindowTextA(edit2_, "8888"); +} + +void MainWnd::LayoutConnectUI(bool show) { + struct Windows { + HWND wnd; + const wchar_t* text; + size_t width; + size_t height; + } windows[] = { + { label1_, L"Server" }, + { edit1_, L"XXXyyyYYYgggXXXyyyYYYggg" }, + { label2_, L":" }, + { edit2_, L"XyXyX" }, + { button_, L"Connect" }, + }; + + if (show) { + const size_t kSeparator = 5; + size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator; + + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + CalculateWindowSizeForText(windows[i].wnd, windows[i].text, + &windows[i].width, &windows[i].height); + total_width += windows[i].width; + } + + RECT rc; + ::GetClientRect(wnd_, &rc); + size_t x = (rc.right / 2) - (total_width / 2); + size_t y = rc.bottom / 2; + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + size_t top = y - (windows[i].height / 2); + ::MoveWindow(windows[i].wnd, x, top, windows[i].width, windows[i].height, + TRUE); + x += kSeparator + windows[i].width; + if (windows[i].text[0] != 'X') + ::SetWindowText(windows[i].wnd, windows[i].text); + ::ShowWindow(windows[i].wnd, SW_SHOWNA); + } + } else { + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + ::ShowWindow(windows[i].wnd, SW_HIDE); + } + } +} + +void MainWnd::LayoutPeerListUI(bool show) { + if (show) { + RECT rc; + ::GetClientRect(wnd_, &rc); + ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE); + ::ShowWindow(listbox_, SW_SHOWNA); + } else { + ::ShowWindow(listbox_, SW_HIDE); + } +} + +void MainWnd::HandleTabbing() { + bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT; + UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST; + HWND focus = GetFocus(), next; + do { + next = ::GetWindow(focus, next_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + + if (!next) { + next = ::GetWindow(focus, loop_around_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + } + focus = next; + } while (true); + ::SetFocus(next); +} diff --git a/peerconnection/samples/client/main_wnd.h b/peerconnection/samples/client/main_wnd.h new file mode 100644 index 000000000..0fadfd8fa --- /dev/null +++ b/peerconnection/samples/client/main_wnd.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_ +#pragma once + +#include +#include + +#include "peerconnection/samples/client/peer_connection_client.h" +#include "talk/base/win32.h" + +class MainWndCallback { + public: + virtual void StartLogin(const std::string& server, int port) = 0; + virtual void DisconnectFromServer() = 0; + virtual void ConnectToPeer(int peer_id) = 0; + virtual void DisconnectFromCurrentPeer() = 0; + protected: + virtual ~MainWndCallback() {} +}; + +class MainWnd { + public: + static const wchar_t kClassName[]; + + enum UI { + CONNECT_TO_SERVER, + LIST_PEERS, + STREAMING, + }; + + MainWnd(); + ~MainWnd(); + + bool Create(); + bool Destroy(); + bool IsWindow() const; + + void RegisterObserver(MainWndCallback* callback); + + bool PreTranslateMessage(MSG* msg); + + void SwitchToConnectUI(); + void SwitchToPeerList(const Peers& peers); + void SwitchToStreamingUI(); + + HWND handle() const { return wnd_; } + UI current_ui() const { return ui_; } + + protected: + enum ChildWindowID { + EDIT_ID = 1, + BUTTON_ID, + LABEL1_ID, + LABEL2_ID, + LISTBOX_ID, + }; + + void OnPaint(); + void OnDestroyed(); + + void OnDefaultAction(); + + bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result); + + static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + static bool RegisterWindowClass(); + + void CreateChildWindow(HWND* wnd, ChildWindowID id, const wchar_t* class_name, + DWORD control_style, DWORD ex_style); + void CreateChildWindows(); + + void LayoutConnectUI(bool show); + void LayoutPeerListUI(bool show); + + void HandleTabbing(); + + private: + UI ui_; + HWND wnd_; + HWND edit1_; + HWND edit2_; + HWND label1_; + HWND label2_; + HWND button_; + HWND listbox_; + bool destroyed_; + void* nested_msg_; + MainWndCallback* callback_; + static ATOM wnd_class_; +}; + +#endif // PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_ diff --git a/peerconnection/samples/client/peer_connection_client.cc b/peerconnection/samples/client/peer_connection_client.cc new file mode 100644 index 000000000..be5fd90f4 --- /dev/null +++ b/peerconnection/samples/client/peer_connection_client.cc @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "peerconnection/samples/client/peer_connection_client.h" + +#include "peerconnection/samples/client/defaults.h" +#include "talk/base/logging.h" + +PeerConnectionClient::PeerConnectionClient() + : callback_(NULL), my_id_(-1), state_(NOT_CONNECTED) { + control_socket_.SignalCloseEvent.connect(this, + &PeerConnectionClient::OnClose); + hanging_get_.SignalCloseEvent.connect(this, + &PeerConnectionClient::OnClose); + control_socket_.SignalConnectEvent.connect(this, + &PeerConnectionClient::OnConnect); + hanging_get_.SignalConnectEvent.connect(this, + &PeerConnectionClient::OnHangingGetConnect); + control_socket_.SignalReadEvent.connect(this, + &PeerConnectionClient::OnRead); + hanging_get_.SignalReadEvent.connect(this, + &PeerConnectionClient::OnHangingGetRead); +} + +PeerConnectionClient::~PeerConnectionClient() { +} + +int PeerConnectionClient::id() const { + return my_id_; +} + +bool PeerConnectionClient::is_connected() const { + return my_id_ != -1; +} + +const Peers& PeerConnectionClient::peers() const { + return peers_; +} + +void PeerConnectionClient::RegisterObserver( + PeerConnectionClientObserver* callback) { + ASSERT(!callback_); + callback_ = callback; +} + +bool PeerConnectionClient::Connect(const std::string& server, int port, + const std::string& client_name) { + ASSERT(!server.empty()); + ASSERT(!client_name.empty()); + ASSERT(state_ == NOT_CONNECTED); + + if (server.empty() || client_name.empty()) + return false; + + if (port <= 0) + port = kDefaultServerPort; + + server_address_.SetIP(server); + server_address_.SetPort(port); + + if (server_address_.IsUnresolved()) { + hostent* h = gethostbyname(server_address_.IPAsString().c_str()); + if (!h) { + LOG(LS_ERROR) << "Failed to resolve host name: " + << server_address_.IPAsString(); + return false; + } else { + server_address_.SetResolvedIP( + ntohl(*reinterpret_cast(h->h_addr_list[0]))); + } + } + + char buffer[1024]; + wsprintfA(buffer, "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name.c_str()); + onconnect_data_ = buffer; + + bool ret = ConnectControlSocket(); + if (ret) + state_ = SIGNING_IN; + + return ret; +} + +bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) { + if (state_ != CONNECTED) + return false; + + ASSERT(is_connected()); + ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED); + if (!is_connected() || peer_id == -1) + return false; + + char headers[1024]; + wsprintfA(headers, "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n" + "Content-Length: %i\r\n" + "Content-Type: text/plain\r\n" + "\r\n", + my_id_, peer_id, message.length()); + onconnect_data_ = headers; + onconnect_data_ += message; + return ConnectControlSocket(); +} + +bool PeerConnectionClient::SignOut() { + if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT) + return true; + + if (hanging_get_.GetState() != talk_base::Socket::CS_CLOSED) + hanging_get_.Close(); + + if (control_socket_.GetState() == talk_base::Socket::CS_CLOSED) { + ASSERT(my_id_ != -1); + state_ = SIGNING_OUT; + + char buffer[1024]; + wsprintfA(buffer, "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); + onconnect_data_ = buffer; + return ConnectControlSocket(); + } else { + state_ = SIGNING_OUT_WAITING; + } + + return true; +} + +void PeerConnectionClient::Close() { + control_socket_.Close(); + hanging_get_.Close(); + onconnect_data_.clear(); + peers_.clear(); + my_id_ = -1; + state_ = NOT_CONNECTED; +} + +bool PeerConnectionClient::ConnectControlSocket() { + ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED); + int err = control_socket_.Connect(server_address_); + if (err == SOCKET_ERROR) { + Close(); + return false; + } + return true; +} + +void PeerConnectionClient::OnConnect(talk_base::AsyncSocket* socket) { + ASSERT(!onconnect_data_.empty()); + int sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); + ASSERT(sent == onconnect_data_.length()); + onconnect_data_.clear(); +} + +void PeerConnectionClient::OnHangingGetConnect(talk_base::AsyncSocket* socket) { + char buffer[1024]; + wsprintfA(buffer, "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); + int len = lstrlenA(buffer); + int sent = socket->Send(buffer, len); + ASSERT(sent == len); +} + +bool PeerConnectionClient::GetHeaderValue(const std::string& data, + size_t eoh, + const char* header_pattern, + size_t* value) { + ASSERT(value); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + *value = atoi(&data[found + lstrlenA(header_pattern)]); + return true; + } + return false; +} + +bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh, + const char* header_pattern, + std::string* value) { + ASSERT(value); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + size_t begin = found + lstrlenA(header_pattern); + size_t end = data.find("\r\n", begin); + if (end == std::string::npos) + end = eoh; + value->assign(data.substr(begin, end - begin)); + return true; + } + return false; +} + +bool PeerConnectionClient::ReadIntoBuffer(talk_base::AsyncSocket* socket, + std::string* data, + size_t* content_length) { + LOG(INFO) << __FUNCTION__; + + char buffer[0xffff]; + do { + int bytes = socket->Recv(buffer, sizeof(buffer)); + if (bytes <= 0) + break; + data->append(buffer, bytes); + } while (true); + + bool ret = false; + size_t i = data->find("\r\n\r\n"); + if (i != std::string::npos) { + LOG(INFO) << "Headers received"; + const char kContentLengthHeader[] = "\r\nContent-Length: "; + if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) { + LOG(INFO) << "Expecting " << *content_length << " bytes."; + size_t total_response_size = (i + 4) + *content_length; + if (data->length() >= total_response_size) { + ret = true; + std::string should_close; + const char kConnection[] = "\r\nConnection: "; + if (GetHeaderValue(*data, i, kConnection, &should_close) && + should_close.compare("close") == 0) { + socket->Close(); + } + } else { + // We haven't received everything. Just continue to accept data. + } + } else { + LOG(LS_ERROR) << "No content length field specified by the server."; + } + } + return ret; +} + +void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) { + LOG(INFO) << __FUNCTION__; + size_t content_length = 0; + if (ReadIntoBuffer(socket, &control_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = ParseServerResponse(control_data_, content_length, &peer_id, + &eoh); + if (ok) { + if (my_id_ == -1) { + // First response. Let's store our server assigned ID. + ASSERT(state_ == SIGNING_IN); + my_id_ = peer_id; + ASSERT(my_id_ != -1); + + // The body of the response will be a list of already connected peers. + if (content_length) { + size_t pos = eoh + 4; + while (pos < control_data_.size()) { + size_t eol = control_data_.find('\n', pos); + if (eol == std::string::npos) + break; + int id = 0; + std::string name; + bool connected; + if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id, + &connected) && id != my_id_) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } + pos = eol + 1; + } + } + ASSERT(is_connected()); + callback_->OnSignedIn(); + } else if (state_ == SIGNING_OUT) { + Close(); + callback_->OnDisconnected(); + } else if (state_ == SIGNING_OUT_WAITING) { + SignOut(); + } + } + + control_data_.clear(); + + if (state_ == SIGNING_IN) { + ASSERT(hanging_get_.GetState() == talk_base::Socket::CS_CLOSED); + state_ = CONNECTED; + hanging_get_.Connect(server_address_); + } + } +} + +void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) { + LOG(INFO) << __FUNCTION__; + size_t content_length = 0; + if (ReadIntoBuffer(socket, ¬ification_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = ParseServerResponse(notification_data_, content_length, + &peer_id, &eoh); + + if (ok) { + // Store the position where the body begins. + size_t pos = eoh + 4; + + if (my_id_ == peer_id) { + // A notification about a new member or a member that just + // disconnected. + int id = 0; + std::string name; + bool connected = false; + if (ParseEntry(notification_data_.substr(pos), &name, &id, + &connected)) { + if (connected) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } else { + peers_.erase(id); + callback_->OnPeerDisconnected(id, name); + } + } + } else { + callback_->OnMessageFromPeer(peer_id, + notification_data_.substr(pos)); + } + } + + notification_data_.clear(); + } + + if (hanging_get_.GetState() == talk_base::Socket::CS_CLOSED && + state_ == CONNECTED) { + hanging_get_.Connect(server_address_); + } +} + +bool PeerConnectionClient::ParseEntry(const std::string& entry, + std::string* name, + int* id, + bool* connected) { + ASSERT(name); + ASSERT(id); + ASSERT(connected); + ASSERT(entry.length()); + + *connected = false; + size_t separator = entry.find(','); + if (separator != std::string::npos) { + *id = atoi(&entry[separator + 1]); + name->assign(entry.substr(0, separator)); + separator = entry.find(',', separator + 1); + if (separator != std::string::npos) { + *connected = atoi(&entry[separator + 1]) ? true : false; + } + } + return !name->empty(); +} + +int PeerConnectionClient::GetResponseStatus(const std::string& response) { + int status = -1; + size_t pos = response.find(' '); + if (pos != std::string::npos) + status = atoi(&response[pos + 1]); + return status; +} + +bool PeerConnectionClient::ParseServerResponse(const std::string& response, + size_t content_length, + size_t* peer_id, + size_t* eoh) { + LOG(INFO) << response; + + int status = GetResponseStatus(response.c_str()); + if (status != 200) { + LOG(LS_ERROR) << "Received error from server"; + Close(); + callback_->OnDisconnected(); + return false; + } + + *eoh = response.find("\r\n\r\n"); + ASSERT(*eoh != std::string::npos); + if (*eoh == std::string::npos) + return false; + + *peer_id = -1; + + // See comment in peer_channel.cc for why we use the Pragma header and + // not e.g. "X-Peer-Id". + GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id); + + return true; +} + +void PeerConnectionClient::OnClose(talk_base::AsyncSocket* socket, int err) { + LOG(INFO) << __FUNCTION__; + + socket->Close(); + + if (err != WSAECONNREFUSED) { + if (socket == &hanging_get_) { + if (state_ == CONNECTED) { + LOG(INFO) << "Issuing a new hanging get"; + hanging_get_.Close(); + hanging_get_.Connect(server_address_); + } + } + } else { + // Failed to connect to the server. + Close(); + callback_->OnDisconnected(); + } +} diff --git a/peerconnection/samples/client/peer_connection_client.h b/peerconnection/samples/client/peer_connection_client.h new file mode 100644 index 000000000..d40179e76 --- /dev/null +++ b/peerconnection/samples/client/peer_connection_client.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_ +#pragma once + +#include +#include + +#include "talk/base/sigslot.h" +#include "talk/base/win32socketserver.h" + +typedef std::map Peers; + +struct PeerConnectionClientObserver { + virtual void OnSignedIn() = 0; // Called when we're logged on. + virtual void OnDisconnected() = 0; + virtual void OnPeerConnected(int id, const std::string& name) = 0; + virtual void OnPeerDisconnected(int id, const std::string& name) = 0; + virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0; + protected: + virtual ~PeerConnectionClientObserver() {} +}; + +class PeerConnectionClient : public sigslot::has_slots<> { + public: + enum State { + NOT_CONNECTED, + SIGNING_IN, + CONNECTED, + SIGNING_OUT_WAITING, + SIGNING_OUT, + }; + + PeerConnectionClient(); + ~PeerConnectionClient(); + + int id() const; + bool is_connected() const; + const Peers& peers() const; + + void RegisterObserver(PeerConnectionClientObserver* callback); + + bool Connect(const std::string& server, int port, + const std::string& client_name); + + bool SendToPeer(int peer_id, const std::string& message); + + bool SignOut(); + + protected: + void Close(); + bool ConnectControlSocket(); + void OnConnect(talk_base::AsyncSocket* socket); + void OnHangingGetConnect(talk_base::AsyncSocket* socket); + + // Quick and dirty support for parsing HTTP header values. + bool GetHeaderValue(const std::string& data, size_t eoh, + const char* header_pattern, size_t* value); + + bool GetHeaderValue(const std::string& data, size_t eoh, + const char* header_pattern, std::string* value); + + // Returns true if the whole response has been read. + bool ReadIntoBuffer(talk_base::AsyncSocket* socket, std::string* data, + size_t* content_length); + + void OnRead(talk_base::AsyncSocket* socket); + + void OnHangingGetRead(talk_base::AsyncSocket* socket); + + // Parses a single line entry in the form ",," + bool ParseEntry(const std::string& entry, std::string* name, int* id, + bool* connected); + + int GetResponseStatus(const std::string& response); + + bool ParseServerResponse(const std::string& response, size_t content_length, + size_t* peer_id, size_t* eoh); + + void OnClose(talk_base::AsyncSocket* socket, int err); + + PeerConnectionClientObserver* callback_; + talk_base::SocketAddress server_address_; + talk_base::Win32Socket control_socket_; + talk_base::Win32Socket hanging_get_; + std::string onconnect_data_; + std::string control_data_; + std::string notification_data_; + Peers peers_; + State state_; + int my_id_; +}; + +#endif // PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_