blob: eda66178265b72eb8bc23adebcd760b01bdf91d1 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
Donald E Curtisa8736442015-08-05 15:48:13 -07002 * Copyright 2012 The WebRTC Project Authors. All rights reserved.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00003 *
Donald E Curtisa8736442015-08-05 15:48:13 -07004 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00009 */
10
Donald E Curtisa8736442015-08-05 15:48:13 -070011#include "webrtc/examples/peerconnection/client/linux/main_wnd.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000012
13#include <gdk/gdkkeysyms.h>
14#include <gtk/gtk.h>
15#include <stddef.h>
16
nisse9f8e37b2016-09-01 01:06:23 -070017#include "libyuv/convert_from.h"
Donald E Curtisa8736442015-08-05 15:48:13 -070018#include "webrtc/examples/peerconnection/client/defaults.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000019#include "webrtc/base/common.h"
20#include "webrtc/base/logging.h"
21#include "webrtc/base/stringutils.h"
nisse7341ab82016-11-02 03:39:58 -070022#include "webrtc/media/engine/webrtcvideoframe.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000023
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000024using rtc::sprintfn;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000025
26namespace {
27
28//
29// Simple static functions that simply forward the callback to the
30// GtkMainWnd instance.
31//
32
33gboolean OnDestroyedCallback(GtkWidget* widget, GdkEvent* event,
34 gpointer data) {
35 reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event);
36 return FALSE;
37}
38
39void OnClickedCallback(GtkWidget* widget, gpointer data) {
40 reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget);
41}
42
43gboolean SimulateButtonClick(gpointer button) {
44 g_signal_emit_by_name(button, "clicked");
45 return false;
46}
47
48gboolean OnKeyPressCallback(GtkWidget* widget, GdkEventKey* key,
49 gpointer data) {
50 reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key);
51 return false;
52}
53
54void OnRowActivatedCallback(GtkTreeView* tree_view, GtkTreePath* path,
55 GtkTreeViewColumn* column, gpointer data) {
56 reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column);
57}
58
59gboolean SimulateLastRowActivated(gpointer data) {
60 GtkTreeView* tree_view = reinterpret_cast<GtkTreeView*>(data);
61 GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
62
63 // "if iter is NULL, then the number of toplevel nodes is returned."
64 int rows = gtk_tree_model_iter_n_children(model, NULL);
65 GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1);
66
67 // Select the last item in the list
68 GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view);
69 gtk_tree_selection_select_path(selection, lastpath);
70
71 // Our TreeView only has one column, so it is column 0.
72 GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0);
73
74 gtk_tree_view_row_activated(tree_view, lastpath, column);
75
76 gtk_tree_path_free(lastpath);
77 return false;
78}
79
80// Creates a tree view, that we use to display the list of peers.
81void InitializeList(GtkWidget* list) {
82 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
83 GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
84 "List Items", renderer, "text", 0, NULL);
85 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
86 GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
87 gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
88 g_object_unref(store);
89}
90
91// Adds an entry to a tree view.
92void AddToList(GtkWidget* list, const gchar* str, int value) {
93 GtkListStore* store = GTK_LIST_STORE(
94 gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
95
96 GtkTreeIter iter;
97 gtk_list_store_append(store, &iter);
98 gtk_list_store_set(store, &iter, 0, str, 1, value, -1);
99}
100
101struct UIThreadCallbackData {
102 explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d)
103 : callback(cb), msg_id(id), data(d) {}
104 MainWndCallback* callback;
105 int msg_id;
106 void* data;
107};
108
109gboolean HandleUIThreadCallback(gpointer data) {
110 UIThreadCallbackData* cb_data = reinterpret_cast<UIThreadCallbackData*>(data);
111 cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data);
112 delete cb_data;
113 return false;
114}
115
116gboolean Redraw(gpointer data) {
117 GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
118 wnd->OnRedraw();
119 return false;
120}
jbauch70625e52015-12-09 14:18:14 -0800121
122} // namespace
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000123
124//
125// GtkMainWnd implementation.
126//
127
128GtkMainWnd::GtkMainWnd(const char* server, int port, bool autoconnect,
129 bool autocall)
130 : window_(NULL), draw_area_(NULL), vbox_(NULL), server_edit_(NULL),
131 port_edit_(NULL), peer_list_(NULL), callback_(NULL),
132 server_(server), autoconnect_(autoconnect), autocall_(autocall) {
133 char buffer[10];
134 sprintfn(buffer, sizeof(buffer), "%i", port);
135 port_ = buffer;
136}
137
138GtkMainWnd::~GtkMainWnd() {
139 ASSERT(!IsWindow());
140}
141
142void GtkMainWnd::RegisterObserver(MainWndCallback* callback) {
143 callback_ = callback;
144}
145
146bool GtkMainWnd::IsWindow() {
147 return window_ != NULL && GTK_IS_WINDOW(window_);
148}
149
150void GtkMainWnd::MessageBox(const char* caption, const char* text,
151 bool is_error) {
152 GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(window_),
153 GTK_DIALOG_DESTROY_WITH_PARENT,
154 is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO,
155 GTK_BUTTONS_CLOSE, "%s", text);
156 gtk_window_set_title(GTK_WINDOW(dialog), caption);
157 gtk_dialog_run(GTK_DIALOG(dialog));
158 gtk_widget_destroy(dialog);
159}
160
161MainWindow::UI GtkMainWnd::current_ui() {
162 if (vbox_)
163 return CONNECT_TO_SERVER;
164
165 if (peer_list_)
166 return LIST_PEERS;
167
168 return STREAMING;
169}
170
171
172void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
173 local_renderer_.reset(new VideoRenderer(this, local_video));
174}
175
176void GtkMainWnd::StopLocalRenderer() {
177 local_renderer_.reset();
178}
179
jbauch70625e52015-12-09 14:18:14 -0800180void GtkMainWnd::StartRemoteRenderer(
181 webrtc::VideoTrackInterface* remote_video) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000182 remote_renderer_.reset(new VideoRenderer(this, remote_video));
183}
184
185void GtkMainWnd::StopRemoteRenderer() {
186 remote_renderer_.reset();
187}
188
189void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) {
190 g_idle_add(HandleUIThreadCallback,
191 new UIThreadCallbackData(callback_, msg_id, data));
192}
193
194bool GtkMainWnd::Create() {
195 ASSERT(window_ == NULL);
196
197 window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
198 if (window_) {
199 gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
200 gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480);
201 gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client");
202 g_signal_connect(G_OBJECT(window_), "delete-event",
203 G_CALLBACK(&OnDestroyedCallback), this);
204 g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback),
205 this);
206
207 SwitchToConnectUI();
208 }
209
210 return window_ != NULL;
211}
212
213bool GtkMainWnd::Destroy() {
214 if (!IsWindow())
215 return false;
216
217 gtk_widget_destroy(window_);
218 window_ = NULL;
219
220 return true;
221}
222
223void GtkMainWnd::SwitchToConnectUI() {
224 LOG(INFO) << __FUNCTION__;
225
226 ASSERT(IsWindow());
227 ASSERT(vbox_ == NULL);
228
229 gtk_container_set_border_width(GTK_CONTAINER(window_), 10);
230
231 if (peer_list_) {
232 gtk_widget_destroy(peer_list_);
233 peer_list_ = NULL;
234 }
235
236 vbox_ = gtk_vbox_new(FALSE, 5);
237 GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0);
238 gtk_container_add(GTK_CONTAINER(vbox_), valign);
239 gtk_container_add(GTK_CONTAINER(window_), vbox_);
240
241 GtkWidget* hbox = gtk_hbox_new(FALSE, 5);
242
243 GtkWidget* label = gtk_label_new("Server");
244 gtk_container_add(GTK_CONTAINER(hbox), label);
245
246 server_edit_ = gtk_entry_new();
247 gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str());
248 gtk_widget_set_size_request(server_edit_, 400, 30);
249 gtk_container_add(GTK_CONTAINER(hbox), server_edit_);
250
251 port_edit_ = gtk_entry_new();
252 gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str());
253 gtk_widget_set_size_request(port_edit_, 70, 30);
254 gtk_container_add(GTK_CONTAINER(hbox), port_edit_);
255
256 GtkWidget* button = gtk_button_new_with_label("Connect");
257 gtk_widget_set_size_request(button, 70, 30);
258 g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
259 gtk_container_add(GTK_CONTAINER(hbox), button);
260
261 GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0);
262 gtk_container_add(GTK_CONTAINER(halign), hbox);
263 gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0);
264
265 gtk_widget_show_all(window_);
266
267 if (autoconnect_)
268 g_idle_add(SimulateButtonClick, button);
269}
270
271void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
272 LOG(INFO) << __FUNCTION__;
273
274 if (!peer_list_) {
275 gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
276 if (vbox_) {
277 gtk_widget_destroy(vbox_);
278 vbox_ = NULL;
279 server_edit_ = NULL;
280 port_edit_ = NULL;
281 } else if (draw_area_) {
282 gtk_widget_destroy(draw_area_);
283 draw_area_ = NULL;
284 draw_buffer_.reset();
285 }
286
287 peer_list_ = gtk_tree_view_new();
288 g_signal_connect(peer_list_, "row-activated",
289 G_CALLBACK(OnRowActivatedCallback), this);
290 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE);
291 InitializeList(peer_list_);
292 gtk_container_add(GTK_CONTAINER(window_), peer_list_);
293 gtk_widget_show_all(window_);
294 } else {
295 GtkListStore* store =
296 GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_)));
297 gtk_list_store_clear(store);
298 }
299
300 AddToList(peer_list_, "List of currently connected peers:", -1);
301 for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i)
302 AddToList(peer_list_, i->second.c_str(), i->first);
303
304 if (autocall_ && peers.begin() != peers.end())
305 g_idle_add(SimulateLastRowActivated, peer_list_);
306}
307
308void GtkMainWnd::SwitchToStreamingUI() {
309 LOG(INFO) << __FUNCTION__;
310
311 ASSERT(draw_area_ == NULL);
312
313 gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
314 if (peer_list_) {
315 gtk_widget_destroy(peer_list_);
316 peer_list_ = NULL;
317 }
318
319 draw_area_ = gtk_drawing_area_new();
320 gtk_container_add(GTK_CONTAINER(window_), draw_area_);
321
322 gtk_widget_show_all(window_);
323}
324
325void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) {
326 callback_->Close();
327 window_ = NULL;
328 draw_area_ = NULL;
329 vbox_ = NULL;
330 server_edit_ = NULL;
331 port_edit_ = NULL;
332 peer_list_ = NULL;
333}
334
335void GtkMainWnd::OnClicked(GtkWidget* widget) {
336 // Make the connect button insensitive, so that it cannot be clicked more than
337 // once. Now that the connection includes auto-retry, it should not be
338 // necessary to click it more than once.
339 gtk_widget_set_sensitive(widget, false);
340 server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_));
341 port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_));
342 int port = port_.length() ? atoi(port_.c_str()) : 0;
343 callback_->StartLogin(server_, port);
344}
345
346void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) {
347 if (key->type == GDK_KEY_PRESS) {
348 switch (key->keyval) {
349 case GDK_Escape:
350 if (draw_area_) {
351 callback_->DisconnectFromCurrentPeer();
352 } else if (peer_list_) {
353 callback_->DisconnectFromServer();
354 }
355 break;
356
357 case GDK_KP_Enter:
358 case GDK_Return:
359 if (vbox_) {
360 OnClicked(NULL);
361 } else if (peer_list_) {
362 // OnRowActivated will be called automatically when the user
363 // presses enter.
364 }
365 break;
366
367 default:
368 break;
369 }
370 }
371}
372
373void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path,
374 GtkTreeViewColumn* column) {
375 ASSERT(peer_list_ != NULL);
376 GtkTreeIter iter;
377 GtkTreeModel* model;
378 GtkTreeSelection* selection =
379 gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
380 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
381 char* text;
382 int id = -1;
383 gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1);
384 if (id != -1)
385 callback_->ConnectToPeer(id);
386 g_free(text);
387 }
388}
389
390void GtkMainWnd::OnRedraw() {
391 gdk_threads_enter();
392
393 VideoRenderer* remote_renderer = remote_renderer_.get();
394 if (remote_renderer && remote_renderer->image() != NULL &&
395 draw_area_ != NULL) {
396 int width = remote_renderer->width();
397 int height = remote_renderer->height();
398
399 if (!draw_buffer_.get()) {
400 draw_buffer_size_ = (width * height * 4) * 4;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200401 draw_buffer_.reset(new uint8_t[draw_buffer_size_]);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000402 gtk_widget_set_size_request(draw_area_, width * 2, height * 2);
403 }
404
Peter Boström0c4e06b2015-10-07 12:23:21 +0200405 const uint32_t* image =
406 reinterpret_cast<const uint32_t*>(remote_renderer->image());
407 uint32_t* scaled = reinterpret_cast<uint32_t*>(draw_buffer_.get());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000408 for (int r = 0; r < height; ++r) {
409 for (int c = 0; c < width; ++c) {
410 int x = c * 2;
411 scaled[x] = scaled[x + 1] = image[c];
412 }
413
Peter Boström0c4e06b2015-10-07 12:23:21 +0200414 uint32_t* prev_line = scaled;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000415 scaled += width * 2;
416 memcpy(scaled, prev_line, (width * 2) * 4);
417
418 image += width;
419 scaled += width * 2;
420 }
421
422 VideoRenderer* local_renderer = local_renderer_.get();
423 if (local_renderer && local_renderer->image()) {
Peter Boström0c4e06b2015-10-07 12:23:21 +0200424 image = reinterpret_cast<const uint32_t*>(local_renderer->image());
425 scaled = reinterpret_cast<uint32_t*>(draw_buffer_.get());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000426 // Position the local preview on the right side.
427 scaled += (width * 2) - (local_renderer->width() / 2);
428 // right margin...
429 scaled -= 10;
430 // ... towards the bottom.
431 scaled += (height * width * 4) -
432 ((local_renderer->height() / 2) *
433 (local_renderer->width() / 2) * 4);
434 // bottom margin...
435 scaled -= (width * 2) * 5;
436 for (int r = 0; r < local_renderer->height(); r += 2) {
437 for (int c = 0; c < local_renderer->width(); c += 2) {
438 scaled[c / 2] = image[c + r * local_renderer->width()];
439 }
440 scaled += width * 2;
441 }
442 }
443
444 gdk_draw_rgb_32_image(draw_area_->window,
445 draw_area_->style->fg_gc[GTK_STATE_NORMAL],
446 0,
447 0,
448 width * 2,
449 height * 2,
450 GDK_RGB_DITHER_MAX,
451 draw_buffer_.get(),
452 (width * 2) * 4);
453 }
454
455 gdk_threads_leave();
456}
457
458GtkMainWnd::VideoRenderer::VideoRenderer(
459 GtkMainWnd* main_wnd,
460 webrtc::VideoTrackInterface* track_to_render)
461 : width_(0),
462 height_(0),
463 main_wnd_(main_wnd),
464 rendered_track_(track_to_render) {
nissedb25d2e2016-02-26 01:24:58 -0800465 rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000466}
467
468GtkMainWnd::VideoRenderer::~VideoRenderer() {
nissedb25d2e2016-02-26 01:24:58 -0800469 rendered_track_->RemoveSink(this);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000470}
471
472void GtkMainWnd::VideoRenderer::SetSize(int width, int height) {
473 gdk_threads_enter();
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000474
475 if (width_ == width && height_ == height) {
476 return;
477 }
478
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000479 width_ = width;
480 height_ = height;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200481 image_.reset(new uint8_t[width * height * 4]);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000482 gdk_threads_leave();
483}
484
nissedb25d2e2016-02-26 01:24:58 -0800485void GtkMainWnd::VideoRenderer::OnFrame(
nisse7341ab82016-11-02 03:39:58 -0700486 const cricket::VideoFrame& video_frame) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000487 gdk_threads_enter();
488
nisse7341ab82016-11-02 03:39:58 -0700489 const cricket::WebRtcVideoFrame frame(
nisse45c8b892016-11-02 03:20:19 -0700490 webrtc::I420Buffer::Rotate(video_frame.video_frame_buffer(),
nisse7341ab82016-11-02 03:39:58 -0700491 video_frame.rotation()),
492 webrtc::kVideoRotation_0, video_frame.timestamp_us());
nisse45c8b892016-11-02 03:20:19 -0700493
nisse7341ab82016-11-02 03:39:58 -0700494 SetSize(frame.width(), frame.height());
nisse45c8b892016-11-02 03:20:19 -0700495
nisse7341ab82016-11-02 03:39:58 -0700496 rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer(
497 frame.video_frame_buffer());
nisse9f8e37b2016-09-01 01:06:23 -0700498 libyuv::I420ToRGBA(buffer->DataY(), buffer->StrideY(),
499 buffer->DataU(), buffer->StrideU(),
500 buffer->DataV(), buffer->StrideV(),
501 image_.get(), width_ * 4,
502 buffer->width(), buffer->height());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000503
504 gdk_threads_leave();
505
506 g_idle_add(Redraw, main_wnd_);
507}