chatview.cpp 19.4 KB
Newer Older
1
/*
Guillaume Roguez's avatar
Guillaume Roguez committed
2
 *  Copyright (C) 2016-2018 Savoir-faire Linux Inc.
3
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
aviau's avatar
aviau committed
4
 *  Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
5
6
 *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
 *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
7
 *  Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#include "chatview.h"

26
27
// std
#include <algorithm>
28

29
30
31
// GTK
#include <glib/gi18n.h>

32
33
34
35
// LRC
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
36

37
38
// Client
#include "utils/files.h"
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct _ChatView
{
    GtkBox parent;
};

struct _ChatViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _ChatViewPrivate ChatViewPrivate;

struct _ChatViewPrivate
{
aviau's avatar
aviau committed
54
55
    GtkWidget *box_webkit_chat_container;
    GtkWidget *webkit_chat_container;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
56

57
58
    GSettings *settings;

59
    lrc::api::conversation::Info* conversation_;
60
    AccountInfoPointer const * accountInfo_;
61

62
63
64
    QMetaObject::Connection new_interaction_connection;
    QMetaObject::Connection update_interaction_connection;
    QMetaObject::Connection update_add_to_conversations;
65
66

    gulong webkit_ready;
67
    gulong webkit_send_text;
68
69
70
71
72
73
74
75
};

G_DEFINE_TYPE_WITH_PRIVATE(ChatView, chat_view, GTK_TYPE_BOX);

#define CHAT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CHAT_VIEW_TYPE, ChatViewPrivate))

enum {
    NEW_MESSAGES_DISPLAYED,
76
    HIDE_VIEW_CLICKED,
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    LAST_SIGNAL
};

static guint chat_view_signals[LAST_SIGNAL] = { 0 };

static void
chat_view_dispose(GObject *object)
{
    ChatView *view;
    ChatViewPrivate *priv;

    view = CHAT_VIEW(object);
    priv = CHAT_VIEW_GET_PRIVATE(view);

91
92
93
    QObject::disconnect(priv->new_interaction_connection);
    QObject::disconnect(priv->update_interaction_connection);
    QObject::disconnect(priv->update_add_to_conversations);
aviau's avatar
aviau committed
94
95
96
97

    /* Destroying the box will also destroy its children, and we wouldn't
     * want that. So we remove the webkit_chat_container from the box. */
    if (priv->webkit_chat_container) {
98
99
100
        /* disconnect for webkit signals */
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
        priv->webkit_ready = 0;
101
102
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
        priv->webkit_send_text = 0;
103

aviau's avatar
aviau committed
104
105
106
107
108
109
        gtk_container_remove(
            GTK_CONTAINER(priv->box_webkit_chat_container),
            GTK_WIDGET(priv->webkit_chat_container)
        );
        priv->webkit_chat_container = nullptr;
    }
110
111
112
113

    G_OBJECT_CLASS(chat_view_parent_class)->dispose(object);
}

114
115
116
117
118
119
static void
hide_chat_view(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
{
    g_signal_emit(G_OBJECT(self), chat_view_signals[HIDE_VIEW_CLICKED], 0);
}

120
121
122
123
124
125
126
127
128
129
130
131
static void
display_links_toggled(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    if (priv->webkit_chat_container) {
        webkit_chat_container_set_display_links(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
            g_settings_get_boolean(priv->settings, "enable-display-links")
        );
    }
}

132
133
134
135
static void
placecall_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
136
    if (!priv->conversation_) return;
137
    (*priv->accountInfo_)->conversationModel->placeCall(priv->conversation_->uid);
138
139
}

140
141
142
143
144
145
146
147
static void
place_audio_call_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

    if (!priv->conversation_)
        return;

148
    (*priv->accountInfo_)->conversationModel->placeAudioOnlyCall(priv->conversation_->uid);
149
150
}

151
static void
152
button_add_to_conversations_clicked(ChatView *self)
153
154
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
155
    if (!priv->conversation_) return;
156
    (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
157
158
}

159
static gchar*
160
file_to_manipulate(GtkWindow* top_window, bool send)
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
{
    GtkWidget* dialog;
    GtkFileChooserAction action = send? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE;
    gint res;
    gchar* filename = nullptr;

    dialog = gtk_file_chooser_dialog_new(send? _("Send File") : _("Save File"),
                                         top_window,
                                         action,
                                         _("_Cancel"),
                                         GTK_RESPONSE_CANCEL,
                                         send? _("_Open"): _("_Save"),
                                         GTK_RESPONSE_ACCEPT,
                                         nullptr);

    res = gtk_dialog_run (GTK_DIALOG(dialog));

    if (res == GTK_RESPONSE_ACCEPT) {
        auto chooser = GTK_FILE_CHOOSER(dialog);
        filename = gtk_file_chooser_get_filename(chooser);
    }
    gtk_widget_destroy (dialog);

    return filename;
}

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
187
188
static void update_chatview_frame(ChatView *self);

189
static void
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
190
webkit_chat_container_script_dialog(GtkWidget* webview, gchar *interaction, ChatView* self)
191
{
192
193
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
194
    if (!priv->conversation_) return;
195
    if (order == "ACCEPT") {
196
        (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
197
    } else if (order == "REFUSE") {
198
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid);
199
    } else if (order == "BLOCK") {
200
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid, true);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    } else if (order == "UNBLOCK") {
        try {
            auto contactUri = priv->conversation_->participants.front();
            auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
            (*priv->accountInfo_)->contactModel->addContact(contact);
        } catch (std::out_of_range) {
            g_debug("webkit_chat_container_script_dialog: oor while retrieving invalid contact info. Chatview bug ?");
        }
    } else if (order.find("PLACE_CALL") == 0) {
        placecall_clicked(self);
    } else if (order.find("PLACE_AUDIO_CALL") == 0) {
        place_audio_call_clicked(self);
    } else if (order.find("CLOSE_CHATVIEW") == 0) {
        hide_chat_view(webview, self);
215
216
217
    } else if (order.find("SEND:") == 0) {
        // Get text body
        auto toSend = order.substr(std::string("SEND:").size());
218
        (*priv->accountInfo_)->conversationModel->sendMessage(priv->conversation_->uid, toSend);
219
    } else if (order.find("SEND_FILE") == 0) {
220
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
221
222
223
224
            if (auto filename = file_to_manipulate(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))), true))
                model->sendFile(priv->conversation_->uid, filename, g_path_get_basename(filename));
        }
    } else if (order.find("ACCEPT_FILE:") == 0) {
225
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
226
227
            try {
                auto interactionId = std::stoull(order.substr(std::string("ACCEPT_FILE:").size()));
228
229

                lrc::api::datatransfer::Info info = {};
230
                (*priv->accountInfo_)->conversationModel->getTransferInfo(interactionId, info);
231

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
                // get prefered directory destination.
                auto* download_directory_variant = g_settings_get_value(priv->settings, "download-folder");
                char* download_directory_value;
                g_variant_get(download_directory_variant, "&s", &download_directory_value);
                std::string default_download_dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
                auto current_value = std::string(download_directory_value);
                if (current_value.empty()) {
                    g_settings_set_value(priv->settings, "download-folder", g_variant_new("s", default_download_dir.c_str()));
                }
                // get full path
                std::string filename = current_value.empty()? default_download_dir.c_str() : download_directory_value;
                if (!filename.empty() && filename.back() != '/') filename += "/";
                filename += info.displayName;

                model->acceptTransfer(priv->conversation_->uid, interactionId, filename);
247
248
249
250
251
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("REFUSE_FILE:") == 0) {
252
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
            try {
                auto interactionId = std::stoull(order.substr(std::string("REFUSE_FILE:").size()));
                model->cancelTransfer(priv->conversation_->uid, interactionId);
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("OPEN_FILE:") == 0) {
        // Get text body
        auto filename {"file://" + order.substr(std::string("OPEN_FILE:").size())};
        filename.erase(std::find_if(filename.rbegin(), filename.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), filename.end());
        GError* error = nullptr;
        if (!gtk_show_uri(nullptr, filename.c_str(), GDK_CURRENT_TIME, &error)) {
            g_debug("Could not open file: %s", error->message);
            g_error_free(error);
        }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
270
271
    } else if (order.find("ADD_TO_CONVERSATIONS") == 0) {
        button_add_to_conversations_clicked(self);
272
273
274
    }
}

275
276
277
278
279
280
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
281
    priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
282
283
284
285
286
287
288
289
290
291
}

static void
chat_view_class_init(ChatViewClass *klass)
{
    G_OBJECT_CLASS(klass)->dispose = chat_view_dispose;

    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
                                                "/cx/ring/RingGnome/chatview.ui");

aviau's avatar
aviau committed
292
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
293
294

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
295
        "new-interactions-displayed",
296
297
298
299
300
301
302
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
303
304
305
306
307
308
309
310
311
312

    chat_view_signals[HIDE_VIEW_CLICKED] = g_signal_new (
        "hide-view-clicked",
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
313
314
}

aviau's avatar
aviau committed
315
static void
316
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
317
318
319
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

320
    if (!priv->conversation_) return;
321
    if (interaction.status == lrc::api::interaction::Status::UNREAD)
322
        (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, interactionId);
323
324

    webkit_chat_container_print_new_interaction(
aviau's avatar
aviau committed
325
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
326
        *(*priv->accountInfo_)->conversationModel,
327
328
        interactionId,
        interaction
aviau's avatar
aviau committed
329
    );
330
331
}

332
static void
333
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
334
335
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
336
337
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
338
        *(*priv->accountInfo_)->conversationModel,
339
340
        interactionId,
        interaction
341
    );
342
}
343

344
345
346
347
348
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
349

350
    // Contact
351
    if (!priv->conversation_) return;
352
    auto contactUri = priv->conversation_->participants.front();
353
    try{
354
        auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
355
356
357
        if (!contact.profileInfo.avatar.empty()) {
            webkit_chat_container_set_sender_image(
                WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
358
                (*priv->accountInfo_)->contactModel->getContactProfileId(contactUri),
359
360
361
362
363
                contact.profileInfo.avatar
                );
        }
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
364
365
    }

366
    // For this account
367
    if (!(*priv->accountInfo_)->profileInfo.avatar.empty()) {
368
369
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
370
371
            (*priv->accountInfo_)->contactModel->getContactProfileId((*priv->accountInfo_)->profileInfo.uri),
            (*priv->accountInfo_)->profileInfo.avatar
372
        );
373
374
375
    }
}

376
static void
377
print_text_recording(ChatView *self)
378
379
380
381
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

382
    // Read interactions
383
    if (!priv->conversation_) return;
384
385
    for (const auto& it: priv->conversation_->interactions) {
        if (it.second.status == lrc::api::interaction::Status::UNREAD)
386
            (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, it.first);
387
388
    }

389
390
    webkit_chat_container_print_history(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
391
        *(*priv->accountInfo_)->conversationModel,
392
393
        priv->conversation_->interactions
    );
394

395
    QObject::disconnect(priv->new_interaction_connection);
396
397
}

aviau's avatar
aviau committed
398
399
400
401
402
403
404
405
406
407
408
409
static void
webkit_chat_container_ready(ChatView* self)
{
    /* The webkit chat container has loaded the javascript libraries, we can
     * now use it. */

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

    webkit_chat_container_clear(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
    );

410
    display_links_toggled(self);
411
412
413
414
    print_text_recording(self);
    load_participants_images(self);

    priv->new_interaction_connection = QObject::connect(
415
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::newInteraction,
416
    [self, priv](const std::string& uid, uint64_t interactionId, lrc::api::interaction::Info interaction) {
417
        if (!priv->conversation_) return;
418
419
420
421
        if(uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });
422

423
    priv->update_interaction_connection = QObject::connect(
424
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
425
    [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
426
        if (!priv->conversation_) return;
427
428
429
430
431
        if(uid == priv->conversation_->uid) {
            update_interaction(self, msgId, msg);
        }
    });

432
    if (!priv->conversation_) return;
433
    auto contactUri = priv->conversation_->participants.front();
434
    try {
435
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
436
437
438
439
440
441
442
443
444
445
446
447
        auto bestName = contactInfo.profileInfo.alias;
        if (bestName.empty())
            bestName = contactInfo.registeredName;
        if (bestName.empty())
            bestName = contactInfo.profileInfo.uri;
        bestName.erase(std::remove(bestName.begin(), bestName.end(), '\r'), bestName.end());
        webkit_chat_disable_send_interaction(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                             (contactInfo.profileInfo.type == lrc::api::profile::Type::SIP)
                                             && priv->conversation_->callId.empty());
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
    }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484

    update_chatview_frame(self);
}

static void
update_chatview_frame(ChatView* self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->conversation_) return;

    auto contactUri = priv->conversation_->participants.front();

    lrc::api::contact::Info contactInfo;
    try {
        contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
    } catch (const std::out_of_range&) {
        g_debug("update_chatview_frame: failed to retrieve contactInfo");
        return;
    }

    // get alias and bestName
    auto alias = contactInfo.profileInfo.alias;
    auto bestName = contactInfo.registeredName;
    if (bestName.empty())
        bestName = contactInfo.profileInfo.uri;
    bestName.erase(std::remove(bestName.begin(), bestName.end(), '\r'), bestName.end());
    alias.erase(std::remove(alias.begin(), alias.end(), '\r'), alias.end());

    // get temporary status
    bool temp = contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY || contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING;

    webkit_chat_update_chatview_frame(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), contactInfo.isBanned, temp, alias.c_str(), bestName.c_str());

    webkit_chat_container_set_invitation(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                             (contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING),
                                             bestName);
aviau's avatar
aviau committed
485
486
487
488
489
490
491
492
493
494
}

static void
build_chat_view(ChatView* self)
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

    gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container);
    gtk_widget_show(priv->webkit_chat_container);

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
495
    update_chatview_frame(self);
aviau's avatar
aviau committed
496

497
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
498
499
500
501
502
503
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

504
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
505
506
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
507
508
        self);

aviau's avatar
aviau committed
509
510
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);
511
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
512
513

GtkWidget *
514
chat_view_new (WebKitChatContainer* webkit_chat_container,
515
               AccountInfoPointer const & accountInfo,
516
               lrc::api::conversation::Info* conversation)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
517
518
519
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
520
521
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
522
    priv->conversation_ = conversation;
523
    priv->accountInfo_ = &accountInfo;
aviau's avatar
aviau committed
524
525

    build_chat_view(self);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
526
527
528
    return (GtkWidget *)self;
}

529
void
530
chat_view_update_temporary(ChatView* self, bool showAddButton, bool showInvitation)
531
{
532
    g_return_if_fail(IS_CHAT_VIEW(self));
533
534
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
535
    update_chatview_frame(self);
536
537
}

538
539
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
540
{
541
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
542
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
543
    return *priv->conversation_;
544
}
545
546
547
548
549

void
chat_view_set_header_visible(ChatView *self, gboolean visible)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
550
    webkit_chat_set_header_visible(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), visible);
551
}