tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

main_wnd.cc (15667B)


      1 /*
      2 *  Copyright 2012 The WebRTC Project Authors. All rights reserved.
      3 *
      4 *  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.
      9 */
     10 
     11 #include "examples/peerconnection/client/linux/main_wnd.h"
     12 
     13 #include <cairo.h>
     14 #include <gdk/gdk.h>
     15 #include <gdk/gdkkeysyms.h>
     16 #include <glib-object.h>
     17 #include <glib.h>
     18 #include <glibconfig.h>
     19 #include <gobject/gclosure.h>
     20 #include <gtk/gtk.h>
     21 
     22 #include <cstddef>
     23 #include <cstdio>
     24 #include <cstdlib>
     25 #include <cstring>
     26 #include <map>
     27 
     28 #include "api/media_stream_interface.h"
     29 #include "api/scoped_refptr.h"
     30 #include "api/video/i420_buffer.h"
     31 #include "api/video/video_frame.h"
     32 #include "api/video/video_frame_buffer.h"
     33 #include "api/video/video_rotation.h"
     34 #include "api/video/video_source_interface.h"
     35 #include "examples/peerconnection/client/main_wnd.h"
     36 #include "examples/peerconnection/client/peer_connection_client.h"
     37 #include "rtc_base/checks.h"
     38 #include "rtc_base/logging.h"
     39 #include "third_party/libyuv/include/libyuv/convert_from.h"
     40 
     41 namespace {
     42 
     43 //
     44 // Simple static functions that simply forward the callback to the
     45 // GtkMainWnd instance.
     46 //
     47 
     48 gboolean OnDestroyedCallback(GtkWidget* widget,
     49                             GdkEvent* event,
     50                             gpointer data) {
     51  reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event);
     52  return FALSE;
     53 }
     54 
     55 void OnClickedCallback(GtkWidget* widget, gpointer data) {
     56  reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget);
     57 }
     58 
     59 gboolean SimulateButtonClick(gpointer button) {
     60  g_signal_emit_by_name(button, "clicked");
     61  return false;
     62 }
     63 
     64 gboolean OnKeyPressCallback(GtkWidget* widget,
     65                            GdkEventKey* key,
     66                            gpointer data) {
     67  reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key);
     68  return false;
     69 }
     70 
     71 void OnRowActivatedCallback(GtkTreeView* tree_view,
     72                            GtkTreePath* path,
     73                            GtkTreeViewColumn* column,
     74                            gpointer data) {
     75  reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column);
     76 }
     77 
     78 gboolean SimulateLastRowActivated(gpointer data) {
     79  GtkTreeView* tree_view = reinterpret_cast<GtkTreeView*>(data);
     80  GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
     81 
     82  // "if iter is NULL, then the number of toplevel nodes is returned."
     83  int rows = gtk_tree_model_iter_n_children(model, nullptr);
     84  GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1);
     85 
     86  // Select the last item in the list
     87  GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view);
     88  gtk_tree_selection_select_path(selection, lastpath);
     89 
     90  // Our TreeView only has one column, so it is column 0.
     91  GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0);
     92 
     93  gtk_tree_view_row_activated(tree_view, lastpath, column);
     94 
     95  gtk_tree_path_free(lastpath);
     96  return false;
     97 }
     98 
     99 // Creates a tree view, that we use to display the list of peers.
    100 void InitializeList(GtkWidget* list) {
    101  GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
    102  GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
    103      "List Items", renderer, "text", 0, NULL);
    104  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
    105  GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
    106  gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
    107  g_object_unref(store);
    108 }
    109 
    110 // Adds an entry to a tree view.
    111 void AddToList(GtkWidget* list, const gchar* str, int value) {
    112  GtkListStore* store =
    113      GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
    114 
    115  GtkTreeIter iter;
    116  gtk_list_store_append(store, &iter);
    117  gtk_list_store_set(store, &iter, 0, str, 1, value, -1);
    118 }
    119 
    120 struct UIThreadCallbackData {
    121  explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d)
    122      : callback(cb), msg_id(id), data(d) {}
    123  MainWndCallback* callback;
    124  int msg_id;
    125  void* data;
    126 };
    127 
    128 gboolean HandleUIThreadCallback(gpointer data) {
    129  UIThreadCallbackData* cb_data = reinterpret_cast<UIThreadCallbackData*>(data);
    130  cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data);
    131  delete cb_data;
    132  return false;
    133 }
    134 
    135 gboolean Redraw(gpointer data) {
    136  GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
    137  wnd->OnRedraw();
    138  return false;
    139 }
    140 
    141 gboolean Draw(GtkWidget* widget, cairo_t* cr, gpointer data) {
    142  GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
    143  wnd->Draw(widget, cr);
    144  return false;
    145 }
    146 
    147 }  // namespace
    148 
    149 //
    150 // GtkMainWnd implementation.
    151 //
    152 
    153 GtkMainWnd::GtkMainWnd(const char* server,
    154                       int port,
    155                       bool autoconnect,
    156                       bool autocall)
    157    : window_(nullptr),
    158      draw_area_(nullptr),
    159      vbox_(nullptr),
    160      server_edit_(nullptr),
    161      port_edit_(nullptr),
    162      peer_list_(nullptr),
    163      callback_(nullptr),
    164      server_(server),
    165      autoconnect_(autoconnect),
    166      autocall_(autocall) {
    167  char buffer[10];
    168  snprintf(buffer, sizeof(buffer), "%i", port);
    169  port_ = buffer;
    170 }
    171 
    172 GtkMainWnd::~GtkMainWnd() {
    173  RTC_DCHECK(!IsWindow());
    174 }
    175 
    176 void GtkMainWnd::RegisterObserver(MainWndCallback* callback) {
    177  callback_ = callback;
    178 }
    179 
    180 bool GtkMainWnd::IsWindow() {
    181  return window_ != nullptr && GTK_IS_WINDOW(window_);
    182 }
    183 
    184 void GtkMainWnd::MessageBox(const char* caption,
    185                            const char* text,
    186                            bool is_error) {
    187  GtkWidget* dialog = gtk_message_dialog_new(
    188      GTK_WINDOW(window_), GTK_DIALOG_DESTROY_WITH_PARENT,
    189      is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s",
    190      text);
    191  gtk_window_set_title(GTK_WINDOW(dialog), caption);
    192  gtk_dialog_run(GTK_DIALOG(dialog));
    193  gtk_widget_destroy(dialog);
    194 }
    195 
    196 MainWindow::UI GtkMainWnd::current_ui() {
    197  if (vbox_)
    198    return CONNECT_TO_SERVER;
    199 
    200  if (peer_list_)
    201    return LIST_PEERS;
    202 
    203  return STREAMING;
    204 }
    205 
    206 void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
    207  local_renderer_.reset(new VideoRenderer(this, local_video));
    208 }
    209 
    210 void GtkMainWnd::StopLocalRenderer() {
    211  local_renderer_.reset();
    212 }
    213 
    214 void GtkMainWnd::StartRemoteRenderer(
    215    webrtc::VideoTrackInterface* remote_video) {
    216  remote_renderer_.reset(new VideoRenderer(this, remote_video));
    217 }
    218 
    219 void GtkMainWnd::StopRemoteRenderer() {
    220  remote_renderer_.reset();
    221 }
    222 
    223 void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) {
    224  g_idle_add(HandleUIThreadCallback,
    225             new UIThreadCallbackData(callback_, msg_id, data));
    226 }
    227 
    228 bool GtkMainWnd::Create() {
    229  RTC_DCHECK(window_ == nullptr);
    230 
    231  window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    232  if (window_) {
    233    gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
    234    gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480);
    235    gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client");
    236    g_signal_connect(G_OBJECT(window_), "delete-event",
    237                     G_CALLBACK(&OnDestroyedCallback), this);
    238    g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback),
    239                     this);
    240 
    241    SwitchToConnectUI();
    242  }
    243 
    244  return window_ != nullptr;
    245 }
    246 
    247 bool GtkMainWnd::Destroy() {
    248  if (!IsWindow())
    249    return false;
    250 
    251  gtk_widget_destroy(window_);
    252  window_ = nullptr;
    253 
    254  return true;
    255 }
    256 
    257 void GtkMainWnd::SwitchToConnectUI() {
    258  RTC_LOG(LS_INFO) << __FUNCTION__;
    259 
    260  RTC_DCHECK(IsWindow());
    261  RTC_DCHECK(vbox_ == nullptr);
    262 
    263  gtk_container_set_border_width(GTK_CONTAINER(window_), 10);
    264 
    265  if (peer_list_) {
    266    gtk_widget_destroy(peer_list_);
    267    peer_list_ = nullptr;
    268  }
    269 
    270  vbox_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    271  GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0);
    272  gtk_container_add(GTK_CONTAINER(vbox_), valign);
    273  gtk_container_add(GTK_CONTAINER(window_), vbox_);
    274 
    275  GtkWidget* hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    276 
    277  GtkWidget* label = gtk_label_new("Server");
    278  gtk_container_add(GTK_CONTAINER(hbox), label);
    279 
    280  server_edit_ = gtk_entry_new();
    281  gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str());
    282  gtk_widget_set_size_request(server_edit_, 400, 30);
    283  gtk_container_add(GTK_CONTAINER(hbox), server_edit_);
    284 
    285  port_edit_ = gtk_entry_new();
    286  gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str());
    287  gtk_widget_set_size_request(port_edit_, 70, 30);
    288  gtk_container_add(GTK_CONTAINER(hbox), port_edit_);
    289 
    290  GtkWidget* button = gtk_button_new_with_label("Connect");
    291  gtk_widget_set_size_request(button, 70, 30);
    292  g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
    293  gtk_container_add(GTK_CONTAINER(hbox), button);
    294 
    295  GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0);
    296  gtk_container_add(GTK_CONTAINER(halign), hbox);
    297  gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0);
    298 
    299  gtk_widget_show_all(window_);
    300 
    301  if (autoconnect_)
    302    g_idle_add(SimulateButtonClick, button);
    303 }
    304 
    305 void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
    306  RTC_LOG(LS_INFO) << __FUNCTION__;
    307 
    308  if (!peer_list_) {
    309    gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
    310    if (vbox_) {
    311      gtk_widget_destroy(vbox_);
    312      vbox_ = nullptr;
    313      server_edit_ = nullptr;
    314      port_edit_ = nullptr;
    315    } else if (draw_area_) {
    316      gtk_widget_destroy(draw_area_);
    317      draw_area_ = nullptr;
    318      draw_buffer_.SetSize(0);
    319    }
    320 
    321    peer_list_ = gtk_tree_view_new();
    322    g_signal_connect(peer_list_, "row-activated",
    323                     G_CALLBACK(OnRowActivatedCallback), this);
    324    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE);
    325    InitializeList(peer_list_);
    326    gtk_container_add(GTK_CONTAINER(window_), peer_list_);
    327    gtk_widget_show_all(window_);
    328  } else {
    329    GtkListStore* store =
    330        GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_)));
    331    gtk_list_store_clear(store);
    332  }
    333 
    334  AddToList(peer_list_, "List of currently connected peers:", -1);
    335  for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i)
    336    AddToList(peer_list_, i->second.c_str(), i->first);
    337 
    338  if (autocall_ && peers.begin() != peers.end())
    339    g_idle_add(SimulateLastRowActivated, peer_list_);
    340 }
    341 
    342 void GtkMainWnd::SwitchToStreamingUI() {
    343  RTC_LOG(LS_INFO) << __FUNCTION__;
    344 
    345  RTC_DCHECK(draw_area_ == nullptr);
    346 
    347  gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
    348  if (peer_list_) {
    349    gtk_widget_destroy(peer_list_);
    350    peer_list_ = nullptr;
    351  }
    352 
    353  draw_area_ = gtk_drawing_area_new();
    354  gtk_container_add(GTK_CONTAINER(window_), draw_area_);
    355  g_signal_connect(G_OBJECT(draw_area_), "draw", G_CALLBACK(&::Draw), this);
    356 
    357  gtk_widget_show_all(window_);
    358 }
    359 
    360 void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) {
    361  callback_->Close();
    362  window_ = nullptr;
    363  draw_area_ = nullptr;
    364  vbox_ = nullptr;
    365  server_edit_ = nullptr;
    366  port_edit_ = nullptr;
    367  peer_list_ = nullptr;
    368 }
    369 
    370 void GtkMainWnd::OnClicked(GtkWidget* widget) {
    371  // Make the connect button insensitive, so that it cannot be clicked more than
    372  // once.  Now that the connection includes auto-retry, it should not be
    373  // necessary to click it more than once.
    374  gtk_widget_set_sensitive(widget, false);
    375  server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_));
    376  port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_));
    377  int port = !port_.empty() ? atoi(port_.c_str()) : 0;
    378  callback_->StartLogin(server_, port);
    379 }
    380 
    381 void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) {
    382  if (key->type == GDK_KEY_PRESS) {
    383    switch (key->keyval) {
    384      case GDK_KEY_Escape:
    385        if (draw_area_) {
    386          callback_->DisconnectFromCurrentPeer();
    387        } else if (peer_list_) {
    388          callback_->DisconnectFromServer();
    389        }
    390        break;
    391 
    392      case GDK_KEY_KP_Enter:
    393      case GDK_KEY_Return:
    394        if (vbox_) {
    395          OnClicked(nullptr);
    396        } else if (peer_list_) {
    397          // OnRowActivated will be called automatically when the user
    398          // presses enter.
    399        }
    400        break;
    401 
    402      default:
    403        break;
    404    }
    405  }
    406 }
    407 
    408 void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view,
    409                                GtkTreePath* path,
    410                                GtkTreeViewColumn* column) {
    411  RTC_DCHECK(peer_list_ != nullptr);
    412  GtkTreeIter iter;
    413  GtkTreeModel* model;
    414  GtkTreeSelection* selection =
    415      gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
    416  if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
    417    char* text;
    418    int id = -1;
    419    gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1);
    420    if (id != -1)
    421      callback_->ConnectToPeer(id);
    422    g_free(text);
    423  }
    424 }
    425 
    426 void GtkMainWnd::OnRedraw() {
    427  gdk_threads_enter();
    428 
    429  VideoRenderer* remote_renderer = remote_renderer_.get();
    430  if (remote_renderer && !remote_renderer->image().empty() &&
    431      draw_area_ != nullptr) {
    432    if (width_ != remote_renderer->width() ||
    433        height_ != remote_renderer->height()) {
    434      width_ = remote_renderer->width();
    435      height_ = remote_renderer->height();
    436      gtk_widget_set_size_request(draw_area_, remote_renderer->width(),
    437                                  remote_renderer->height());
    438    }
    439    draw_buffer_.SetData(remote_renderer->image());
    440    gtk_widget_queue_draw(draw_area_);
    441  }
    442  // Here we can draw the local preview as well if we want....
    443  gdk_threads_leave();
    444 }
    445 
    446 void GtkMainWnd::Draw(GtkWidget* widget, cairo_t* cr) {
    447  cairo_format_t format = CAIRO_FORMAT_ARGB32;
    448  cairo_surface_t* surface = cairo_image_surface_create_for_data(
    449      draw_buffer_.data(), format, width_, height_,
    450      cairo_format_stride_for_width(format, width_));
    451  cairo_set_source_surface(cr, surface, 0, 0);
    452  cairo_rectangle(cr, 0, 0, width_, height_);
    453  cairo_fill(cr);
    454  cairo_surface_destroy(surface);
    455 }
    456 
    457 GtkMainWnd::VideoRenderer::VideoRenderer(
    458    GtkMainWnd* main_wnd,
    459    webrtc::VideoTrackInterface* track_to_render)
    460    : width_(0),
    461      height_(0),
    462      main_wnd_(main_wnd),
    463      rendered_track_(track_to_render) {
    464  rendered_track_->AddOrUpdateSink(this, webrtc::VideoSinkWants());
    465 }
    466 
    467 GtkMainWnd::VideoRenderer::~VideoRenderer() {
    468  rendered_track_->RemoveSink(this);
    469 }
    470 
    471 void GtkMainWnd::VideoRenderer::SetSize(int width, int height) {
    472  gdk_threads_enter();
    473 
    474  if (width_ == width && height_ == height) {
    475    return;
    476  }
    477 
    478  width_ = width;
    479  height_ = height;
    480  // ARGB
    481  image_.SetSize(width * height * 4);
    482  gdk_threads_leave();
    483 }
    484 
    485 void GtkMainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) {
    486  gdk_threads_enter();
    487 
    488  webrtc::scoped_refptr<webrtc::I420BufferInterface> buffer(
    489      video_frame.video_frame_buffer()->ToI420());
    490  if (video_frame.rotation() != webrtc::kVideoRotation_0) {
    491    buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation());
    492  }
    493  SetSize(buffer->width(), buffer->height());
    494 
    495  // TODO(bugs.webrtc.org/6857): This conversion is correct for little-endian
    496  // only. Cairo ARGB32 treats pixels as 32-bit values in *native* byte order,
    497  // with B in the least significant byte of the 32-bit value. Which on
    498  // little-endian means that memory layout is BGRA, with the B byte stored at
    499  // lowest address. Libyuv's ARGB format (surprisingly?) uses the same
    500  // little-endian format, with B in the first byte in memory, regardless of
    501  // native endianness.
    502  libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(),
    503                     buffer->StrideU(), buffer->DataV(), buffer->StrideV(),
    504                     image_.data(), width_ * 4, buffer->width(),
    505                     buffer->height());
    506 
    507  gdk_threads_leave();
    508 
    509  g_idle_add(Redraw, main_wnd_);
    510 }