blob: 9ff78435decd00a03e7283c507d9913ee1e82ca8 [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lisionc5148052015-03-04 15:10:35 -05003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lisionc5148052015-03-04 15:10:35 -050018 */
19#import "CurrentCallVC.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040020extern "C" {
21#import "libavutil/frame.h"
22#import "libavutil/display.h"
23}
Alexandre Lisionc5148052015-03-04 15:10:35 -050024
25#import <QuartzCore/QuartzCore.h>
26
Alexandre Lision210fe212016-01-27 11:15:13 -050027///Qt
28#import <QMimeData>
29#import <QtMacExtras/qmacfunctions.h>
30#import <QtCore/qabstractitemmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050031#import <QPixmap>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040032#import <QUrl>
Alexandre Lision210fe212016-01-27 11:15:13 -050033
34///LRC
Alexandre Lisionc5148052015-03-04 15:10:35 -050035#import <video/renderer.h>
Olivier Soldano994971f2017-12-05 16:30:12 -050036#import <api/newcallmodel.h>
37#import <api/call.h>
38#import <api/conversationmodel.h>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040039#import <api/avmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050040#import <globalinstances.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050041
Alexandre Lision62005312016-01-28 15:55:16 -050042#import "AppDelegate.h"
Alexandre Lisionf47a2562015-06-15 15:48:29 -040043#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040044#import "views/CallView.h"
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040045#import "views/NSColor+RingTheme.h"
Alexandre Lision210fe212016-01-27 11:15:13 -050046#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040047#import "ChatVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050048#import "views/IconButton.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050049#import "utils.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040050#import "views/CallMTKView.h"
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -040051#import "VideoCommon.h"
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040052#import "views/GradientView.h"
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -040053#import "views/MovableView.h"
Kateryna Kostiuk06681682020-05-07 20:50:56 -040054#import "views/RenderingView.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040055
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050056@interface CurrentCallVC () <NSPopoverDelegate> {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040057 QString convUid_;
58 QString callUid_;
59 QString confUid_;
Olivier Soldano994971f2017-12-05 16:30:12 -050060 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050061 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050062}
Alexandre Lisionc5148052015-03-04 15:10:35 -050063
Alexandre Lision210fe212016-01-27 11:15:13 -050064// Main container
65@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050066@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040067@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050068
Alexandre Lision89edc6a2015-11-09 11:30:47 -050069// Header info
70@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -040071@property (unsafe_unretained) IBOutlet NSView* headerGradientView;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050072@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
73
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -040074// info
75@property (unsafe_unretained) IBOutlet NSStackView* infoContainer;
76@property (unsafe_unretained) IBOutlet NSImageView* contactPhoto;
77@property (unsafe_unretained) IBOutlet NSTextField* contactNameLabel;
78@property (unsafe_unretained) IBOutlet NSTextField* callStateLabel;
79@property (unsafe_unretained) IBOutlet NSTextField* contactIdLabel;
80@property (unsafe_unretained) IBOutlet IconButton* cancelCallButton;
81@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
82@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Andreas Traczyk77a50d52018-05-08 17:47:31 -040083
Alexandre Lision89edc6a2015-11-09 11:30:47 -050084// Call Controls
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040085@property (unsafe_unretained) IBOutlet GradientView* controlsPanel;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -040086@property (unsafe_unretained) IBOutlet NSStackView* controlsStackView;
Alexandre Lision62005312016-01-28 15:55:16 -050087
Alexandre Lision4baba4c2016-02-11 13:00:57 -050088@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
89@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
90@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050091@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
92@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050093@property (unsafe_unretained) IBOutlet IconButton* transferButton;
94@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
95@property (unsafe_unretained) IBOutlet IconButton* chatButton;
Andreas Traczyk77a50d52018-05-08 17:47:31 -040096@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040097
Alexandre Lisionc5148052015-03-04 15:10:35 -050098// Video
Alexandre Lision58cab672015-06-09 15:25:40 -040099@property (unsafe_unretained) IBOutlet CallView *videoView;
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400100@property (unsafe_unretained) IBOutlet RenderingView *previewView;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400101@property (unsafe_unretained) IBOutlet MovableView *movableBaseForView;
102@property (unsafe_unretained) IBOutlet NSView* hidePreviewBackground;
103@property (unsafe_unretained) IBOutlet NSButton* hidePreviewButton;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400104
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400105@property (unsafe_unretained) IBOutlet RenderingView *distantView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500106
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400107@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400108@property QMetaObject::Connection videoStarted;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400109@property QMetaObject::Connection callStateChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400110@property QMetaObject::Connection messageConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400111@property QMetaObject::Connection profileUpdatedConnection;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400112@property QMetaObject::Connection participantsChangedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500113
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400114//conference
115@property (unsafe_unretained) IBOutlet NSStackView *callingWidgetsContainer;
116
117@property (strong) NSPopover* brokerPopoverVC;
118
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400119@property (strong) IBOutlet ChatVC* chatVC;
120
Alexandre Lisionc5148052015-03-04 15:10:35 -0500121@end
122
123@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400124lrc::api::AVModel* mediaModel;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400125NSMutableDictionary *connectingCalls;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400126NSMutableDictionary *participantsOverlays;
127NSSize framesize;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400128
129NSInteger const PREVIEW_WIDTH = 185;
130NSInteger const PREVIEW_HEIGHT = 130;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400131NSInteger const HIDE_PREVIEW_BUTTON_SIZE = 25;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400132NSInteger const PREVIEW_MARGIN = 20;
133
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400134@synthesize holdOnOffButton, hangUpButton, recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel, muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView, previewView, splitView, loadingIndicator, backgroundImage, bluerBackgroundEffect, hidePreviewButton, hidePreviewBackground, movableBaseForView, infoContainer, contactPhoto, contactNameLabel, callStateLabel, contactIdLabel, cancelCallButton, headerGradientView, controlsStackView, callingWidgetsContainer, brokerPopoverVC;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500135
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400136@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400137CVPixelBufferPoolRef pixelBufferPoolDistantView;
138CVPixelBufferRef pixelBufferDistantView;
139CVPixelBufferPoolRef pixelBufferPoolPreview;
140CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500141
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400142/* update call and conversation info
143 * set info for chat view
144 * connect signals
145 */
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400146-(void) setCurrentCall:(const QString&)callUid
147 conversation:(const QString&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400148 account:(const lrc::api::account::Info*)account
149 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500150{
Olivier Soldano994971f2017-12-05 16:30:12 -0500151 if(account == nil)
152 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400153
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400154 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500155 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500156
Olivier Soldano994971f2017-12-05 16:30:12 -0500157 if (not callModel->hasCall(callUid)){
158 callUid_.clear();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400159 confUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400160 return;
161 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500162 callUid_ = callUid;
163 convUid_ = convUid;
164 accountInfo_ = account;
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400165 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
166 if (conv.uid.isEmpty()) {
167 return;
168 }
169 confUid_ = conv.confId;
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500170 [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400171 [self connectSignals];
172}
173
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400174-(void)updateConferenceOverlays:(QString)callID {
175 auto* callModel = accountInfo_->callModel.get();
176 auto call = callModel->getCall(callID);
177 using Status = lrc::api::call::Status;
178 switch (call.status) {
179 case Status::ENDED:
180 case Status::TERMINATING:
181 case Status::INVALID:
182 case Status::PEER_BUSY:
183 case Status::TIMEOUT:
184 if (participantsOverlays[call.peerUri.toNSString()] != nil) {
185 [participantsOverlays[call.peerUri.toNSString()] removeFromSuperview];
186 participantsOverlays[call.peerUri.toNSString()] = nil;
187 if (![self isCurrentCall: callID]) {
188 [self updateCall];
189 [self setCurrentCall: callUid_ conversation:convUid_ account:accountInfo_ avModel:mediaModel];
190 }
191 }
192 break;
193 }
194}
195
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400196-(void)switchToNextConferenceCall:(QString)confId {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400197 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400198 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
199 auto activeCalls = [appDelegate getActiveCalls];
200 if (activeCalls.isEmpty()) {
201 return;
202 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400203 auto subcalls = [appDelegate getConferenceSubcalls: confId];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400204 QString callId;
205 if (subcalls.isEmpty()) {
206 for(auto subcall: activeCalls) {
207 if(subcall != callUid_) {
208 callId = subcall;
209 }
210 }
211 } else {
212 for(auto subcall: subcalls) {
213 if(subcall != callUid_) {
214 callId = subcall;
215 }
216 }
217 }
218 if (!callModel->hasCall(callId)) {
219 return;
220 }
221 auto callToSwitch = callModel->getCall(callId);
222 auto convIt = getConversationFromURI(callToSwitch.peerUri, *accountInfo_->conversationModel);
223 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
224 return;
225 }
226 [self.delegate chooseConversation: *convIt model:accountInfo_->conversationModel.get()];
227}
228
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400229-(void) connectSignals {
230 auto* callModel = accountInfo_->callModel.get();
231 auto* convModel = accountInfo_->conversationModel.get();
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400232 QObject::disconnect(self.participantsChangedConnection);
233 self.participantsChangedConnection = QObject::connect(callModel,
234 &lrc::api::NewCallModel::onParticipantsChanged,
235 [self](const QString& confId) {
236 auto* callModel = accountInfo_->callModel.get();
237 if (!callModel->hasCall(confId)) {
238 return;
239 }
240 if ([self isCurrentCall: confId]) {
241 [self updateConference: confId];
242 }
243 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400244 //monitor for updated call state
245 QObject::disconnect(self.callStateChanged);
246 self.callStateChanged = QObject::connect(callModel,
247 &lrc::api::NewCallModel::callStatusChanged,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400248 [self](const QString& callId) {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400249 [self updateConferenceOverlays: callId];
250 if ([self isCurrentCall: callId]) {
251 [self updateCall];
252 }
253 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400254 /* monitor media for messaging text messaging */
255 QObject::disconnect(self.messageConnection);
256 self.messageConnection = QObject::connect(convModel,
257 &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400258 [self] (const QString& convUid,
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400259 uint64_t msgId,
260 lrc::api::interaction::Info msg) {
261 if (msg.type == lrc::api::interaction::Type::TEXT) {
262 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
263 return;
264 }
265 [self uncollapseRightView];
266 }
267 });
268 //monitor for updated profile
269 QObject::disconnect(self.profileUpdatedConnection);
270 self.profileUpdatedConnection =
271 QObject::connect(accountInfo_->contactModel.get(),
272 &lrc::api::ContactModel::contactAdded,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400273 [self](const QString &contactUri) {
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400274 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
275 if (conv.uid.isEmpty() || conv.participants.empty()) {
276 return;
277 }
278 auto& contact = accountInfo_->contactModel->getContact(conv.participants[0]);
279 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
280 accountInfo_->conversationModel->makePermanent(convUid_);
281 [contactPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
282 [self.delegate conversationInfoUpdatedFor:convUid_];
283 [self setBackground];
284 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400285 [self connectVideoSignals];
286}
287
288-(void) updatePendingCalls {
289 for (NSView *view in callingWidgetsContainer.subviews) {
290 view.removeFromSuperview;
291 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400292 NSDictionary * calls = connectingCalls[callUid_.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400293 for (NSViewController * callView in calls.allValues) {
294 [self.callingWidgetsContainer addView: callView.view inGravity:NSStackViewGravityBottom];
295 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500296}
297
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400298-(void) setUpButtons:(lrc::api::call::Info&)callInfo isRecording:(BOOL) isRecording {
299 muteAudioButton.image = callInfo.audioMuted ? [NSImage imageNamed:@"ic_action_mute_audio.png"] :
300 [NSImage imageNamed:@"ic_action_audio.png"];
301 muteVideoButton.image = callInfo.videoMuted ? [NSImage imageNamed:@"ic_action_mute_video.png"] :
302 [NSImage imageNamed:@"ic_action_video.png"];
303 [muteVideoButton setHidden: callInfo.isAudioOnly ? YES: NO];
304 if (isRecording) {
305 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
306 } else {
307 [recordOnOffButton stopBlinkAnimation];
308 }
309}
310
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400311- (void) setUpPreviewFrame {
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400312 CGPoint previewOrigin = CGPointMake(self.videoView.frame.size.width - PREVIEW_WIDTH - PREVIEW_MARGIN, PREVIEW_MARGIN);
313 movableBaseForView.frame = CGRectMake(previewOrigin.x, previewOrigin.y, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400314 self.movableBaseForView.movable = true;
315 previewView.frame = movableBaseForView.bounds;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400316 hidePreviewBackground.frame = [self frameForExpendPreviewButton];
Kateryna Kostiuke9eb38b2020-03-23 14:33:08 -0400317 if ([hidePreviewButton respondsToSelector:@selector(contentTintColor)]) {
318 hidePreviewButton.contentTintColor = [NSColor blackColor];
319 }
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400320}
321
Alexandre Lisionc5148052015-03-04 15:10:35 -0500322- (void)awakeFromNib
323{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400324 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500325
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400326 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400327
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400328 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400329 [loadingIndicator setNumberOfLines:200];
330 [loadingIndicator setWidthOfLine:4];
331 [loadingIndicator setLengthOfLine:4];
332 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400333
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400334 [self.videoView setCallDelegate:self];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400335 [bluerBackgroundEffect setWantsLayer:YES];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500336 [backgroundImage setWantsLayer: YES];
337 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400338 movableBaseForView.wantsLayer = YES;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400339 movableBaseForView.shadow = [[NSShadow alloc] init];
340 movableBaseForView.layer.shadowOpacity = 0.6;
341 movableBaseForView.layer.shadowColor = [[NSColor blackColor] CGColor];
342 movableBaseForView.layer.shadowOffset = NSMakeSize(0, -3);
343 movableBaseForView.layer.shadowRadius = 10;
344 previewView.wantsLayer = YES;
345 previewView.layer.cornerRadius = 5;
346 previewView.layer.masksToBounds = true;
347 hidePreviewBackground.wantsLayer = YES;
348 hidePreviewBackground.layer.cornerRadius = 5;
349 hidePreviewBackground.layer.maskedCorners = kCALayerMinXMinYCorner;
350 hidePreviewBackground.layer.masksToBounds = true;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400351 movableBaseForView.hostingView = self.videoView;
352 [movableBaseForView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
353 [previewView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400354 connectingCalls = [[NSMutableDictionary alloc] init];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400355 participantsOverlays = [[NSMutableDictionary alloc] init];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500356}
357
Anthony Léonard66a050c2018-01-03 16:32:34 -0500358-(void) updateDurationLabel
359{
360 if (accountInfo_ != nil) {
361 auto* callModel = accountInfo_->callModel.get();
362 if (callModel->hasCall(callUid_)) {
363 auto& callStatus = callModel->getCall(callUid_).status;
364 if (callStatus != lrc::api::call::Status::ENDED &&
365 callStatus != lrc::api::call::Status::TERMINATING &&
366 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400367 [timeSpentLabel setStringValue:callModel->getFormattedCallDuration(callUid_).toNSString()];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500368 return;
369 }
370 }
371 }
372
373 // If call is not running anymore or accountInfo_ is not set for any reason
374 // we stop the refresh loop
375 [refreshDurationTimer invalidate];
376 refreshDurationTimer = nil;
Anthony Léonard66a050c2018-01-03 16:32:34 -0500377}
378
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400379-(void)removeConferenceLayout {
380 for (ConferenceOverlayView* overlay: [participantsOverlays allValues]) {
381 [overlay removeFromSuperview];
382 }
383 participantsOverlays = [[NSMutableDictionary alloc] init];
384}
385
386-(void)updateConference:(QString)confId
387{
388 if (confId.isEmpty()) {
389 [self removeConferenceLayout];
390 return;
391 }
392 auto* callModel = accountInfo_->callModel.get();
393 auto& call = callModel->getCall(confId);
394 using Status = lrc::api::call::Status;
395 auto participants = call.participantsInfos;
396 if (participants.size() == 0 || call.status == Status::ENDED ||
397 call.status == Status::TERMINATING || call.status == Status::INVALID) {
398 [self removeConferenceLayout];
399 return;
400 }
401 for (auto participant: participants) {
402 ConferenceParticipant conferenceParticipant;
403 conferenceParticipant.x = participant["x"].toFloat();
404 conferenceParticipant.y = participant["y"].toFloat();
405 conferenceParticipant.width = participant["w"].toFloat();
406 conferenceParticipant.hight = participant["h"].toFloat();
407 conferenceParticipant.uri = participant["uri"].toNSString();
408 conferenceParticipant.active = participant["active"] == "true";
409 conferenceParticipant.isLocal = false;
410 conferenceParticipant.bestName = participant["uri"].toNSString();
411 if (accountInfo_->profileInfo.uri == participant["uri"]) {
412 conferenceParticipant.isLocal = true;
413 conferenceParticipant.bestName = NSLocalizedString(@"Me", @"Conference name");
414 } else {
415 try {
416 auto contact = accountInfo_->contactModel->getContact(participant["uri"]);
417 conferenceParticipant.bestName = bestNameForContact(contact);
418 } catch (...) {}
419 }
420 if (participantsOverlays[conferenceParticipant.uri] != nil) {
421 ConferenceOverlayView* overlay = participantsOverlays[conferenceParticipant.uri];
422 overlay.framesize = framesize;
423 [overlay updateViewWithParticipant: conferenceParticipant];
424 } else {
425 ConferenceOverlayView* overlay = [[ConferenceOverlayView alloc] init];
426 overlay.framesize = framesize;
427 overlay.delegate = self;
428 [overlay configureView];
429 [self.distantView addSubview: overlay];
430 participantsOverlays[conferenceParticipant.uri] = overlay;
431 [overlay updateViewWithParticipant: conferenceParticipant];
432 }
433 }
434}
435
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400436-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500437{
Olivier Soldano994971f2017-12-05 16:30:12 -0500438 if (accountInfo_ == nil)
439 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400440
Olivier Soldano994971f2017-12-05 16:30:12 -0500441 auto* callModel = accountInfo_->callModel.get();
442 if (not callModel->hasCall(callUid_)) {
443 return;
444 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500445
Olivier Soldano994971f2017-12-05 16:30:12 -0500446 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400447 auto convIt = accountInfo_->conversationModel->getConversationForUID(convUid_);
448 if (!convIt.uid.isEmpty()) {
449 NSString* bestName = bestNameForConversation(convIt, *accountInfo_->conversationModel);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400450 [contactNameLabel setStringValue:bestName];
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400451 NSString* ringID = bestIDForConversation(convIt, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400452 if([bestName isEqualToString:ringID]) {
453 ringID = @"";
454 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400455 [contactIdLabel setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400456 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400457 [self setupContactInfo:contactPhoto];
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400458 confUid_ = convIt.confId;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400459 [muteAudioButton setHidden:!confUid_.isEmpty()];
460 [muteVideoButton setHidden:!confUid_.isEmpty()];
461 [recordOnOffButton setHidden:!confUid_.isEmpty()];
462 [holdOnOffButton setHidden:!confUid_.isEmpty()];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400463 [movableBaseForView setHidden:!confUid_.isEmpty()];
464 [self updateConference: confUid_];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400465
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400466 [timeSpentLabel setStringValue:callModel->getFormattedCallDuration(callUid_).toNSString()];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500467 if (refreshDurationTimer == nil)
468 refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
469 target:self
470 selector:@selector(updateDurationLabel)
471 userInfo:nil
472 repeats:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400473 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500474
Olivier Soldano994971f2017-12-05 16:30:12 -0500475 using Status = lrc::api::call::Status;
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400476 if (currentCall.status == Status::PAUSED) {
477 [holdOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1.0 duration: 1.5];
478 } else {
479 [holdOnOffButton stopBlinkAnimation];
480 }
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400481
Kateryna Kostiuka7404812019-10-28 12:24:46 -0400482 [self setUpButtons: currentCall isRecording: (callModel->isRecording([self getcallID]) || mediaModel->getAlwaysRecord())];
483
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400484 [videoView setShouldAcceptInteractions: currentCall.status == Status::IN_PROGRESS];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400485 callStateLabel.stringValue = currentCall.status == Status::INCOMING_RINGING ? @"wants to talk to you" : to_string(currentCall.status).toNSString();
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400486 loadingIndicator.hidden = (currentCall.status == Status::SEARCHING ||
487 currentCall.status == Status::CONNECTING ||
488 currentCall.status == Status::OUTGOING_RINGING) ? NO : YES;
489 pickUpButton.hidden = (currentCall.status == Status::INCOMING_RINGING) ? NO : YES;
490 callStateLabel.hidden = (currentCall.status == Status::IN_PROGRESS) ? YES : NO;
491 cancelCallButton.hidden = (currentCall.status == Status::IN_PROGRESS ||
492 currentCall.status == Status::PAUSED) ? YES : NO;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400493 callingWidgetsContainer.hidden = (currentCall.status == Status::IN_PROGRESS) ? NO : YES;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400494
Olivier Soldano994971f2017-12-05 16:30:12 -0500495 switch (currentCall.status) {
496 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500497 case Status::CONNECTING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500498 case Status::OUTGOING_RINGING:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400499 case Status::INCOMING_RINGING:
500 [infoContainer setHidden: NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400501 [headerContainer setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400502 [headerGradientView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400503 [controlsPanel setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400504 [controlsStackView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400505 [self.distantView fillWithBlack];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400506 [self.previewView fillWithBlack];
507 [hidePreviewBackground setHidden:YES];
508 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400509 [self.distantView setHidden: YES];
510 self.previewView.videoRunning = NO;
511 self.distantView.videoRunning = NO;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400512 [backgroundImage setHidden:NO];
513 [bluerBackgroundEffect setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500514 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500515 case Status::PAUSED:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400516 [infoContainer setHidden: NO];
517 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400518 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400519 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400520 [controlsStackView setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400521 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400522 [bluerBackgroundEffect setHidden:NO];
523 if(!currentCall.isAudioOnly) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400524 [self.distantView fillWithBlack];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400525 [self.previewView fillWithBlack];
526 [hidePreviewBackground setHidden:YES];
527 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400528 [self.distantView setHidden: YES];
529 self.previewView.videoRunning = NO;
530 self.distantView.videoRunning = NO;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400531 }
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400532 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500533 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400534 if(currentCall.isAudioOnly) {
535 [self setUpAudioOnlyView];
536 } else {
537 [self setUpVideoCallView];
538 }
539 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500540 case Status::IN_PROGRESS:
“Kateryna”414738e2020-04-07 11:44:35 -0400541 callModel->setCurrentCall([self getcallID]);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400542 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400543 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400544 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400545 [controlsStackView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400546 if(currentCall.isAudioOnly) {
547 [self setUpAudioOnlyView];
548 } else {
549 [self setUpVideoCallView];
550 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400551 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500552 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500553 break;
554 case Status::ENDED:
555 case Status::TERMINATING:
556 case Status::INVALID:
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400557 case Status::PEER_BUSY:
558 case Status::TIMEOUT:
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400559 connectingCalls[callUid_.toNSString()] = nil;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400560 [self removeConferenceLayout ];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400561 [self.delegate callFinished];
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400562 [self switchToNextConferenceCall: confUid_];
Olivier Soldano994971f2017-12-05 16:30:12 -0500563 break;
564 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500565}
566
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400567-(void) setUpVideoCallView {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400568 [previewView fillWithBlack];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400569 [self.distantView fillWithBlack];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400570 [previewView setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400571 [self.distantView setHidden:NO];
572 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400573 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400574 [backgroundImage setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400575 self.previewView.videoRunning = true;
576 self.distantView.videoRunning = true;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400577}
578
579-(void) setUpAudioOnlyView {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400580 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400581 [self.distantView setHidden: YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400582 [hidePreviewBackground setHidden: YES];
583 [bluerBackgroundEffect setHidden:NO];
584 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400585}
586
587-(void) setBackground {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400588 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
589 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500590 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
591 CIContext *context = [[CIContext alloc] init];
592 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
593 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
594 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400595 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
596 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500597 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
598 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
599 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
600 CGRect extent = [result extent];
601 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
602 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
603 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400604 [backgroundImage setHidden:NO];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400605 [bluerBackgroundEffect setFillColor:[NSColor darkGrayColor]];
606 [bluerBackgroundEffect setAlphaValue:0.6];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400607 } else {
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400608 contactNameLabel.textColor = [NSColor textColor];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400609 contactIdLabel.textColor = [NSColor textColor];
610 callStateLabel.textColor = [NSColor textColor];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400611 backgroundImage.layer.contents = nil;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400612 [bluerBackgroundEffect setFillColor:[NSColor windowBackgroundColor]];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400613 [bluerBackgroundEffect setAlphaValue:1];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400614 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400615 }
616}
617
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400618-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400619 auto* convModel = accountInfo_->conversationModel.get();
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400620 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
621 if (conv.uid.isEmpty() || conv.participants.empty()) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400622 return nil;
623 }
624 if(shouldDrawDefault) {
625 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400626 QVariant photo = imgManip.conversationPhoto(conv, *accountInfo_, QSize(size, size), NO);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400627 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
628 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400629 auto contact = accountInfo_->contactModel->getContact(conv.participants[0]);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400630 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:contact.profileInfo.avatar.toNSString() options:NSDataBase64DecodingIgnoreUnknownCharacters];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400631 return [[NSImage alloc] initWithData:imageData];
632}
633
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400634-(void) setupContactInfo:(NSImageView*)imageView
635{
636 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
637}
638
Olivier Soldano994971f2017-12-05 16:30:12 -0500639-(void)collapseRightView
640{
641 NSView *right = [[splitView subviews] objectAtIndex:1];
642 NSView *left = [[splitView subviews] objectAtIndex:0];
643 NSRect leftFrame = [left frame];
644 [right setHidden:YES];
645 [splitView display];
646}
647
Alexandre Lisionc5148052015-03-04 15:10:35 -0500648-(void) connectVideoSignals
649{
Olivier Soldano994971f2017-12-05 16:30:12 -0500650 if (accountInfo_ == nil)
651 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400652 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500653}
654
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400655-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500656{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400657 QObject::disconnect(renderConnections.frameUpdated);
658 QObject::disconnect(renderConnections.stopped);
659 QObject::disconnect(renderConnections.started);
660 renderConnections.started =
661 QObject::connect(mediaModel,
662 &lrc::api::AVModel::rendererStarted,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400663 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400664 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
665 [self.previewView setHidden:NO];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400666 [hidePreviewBackground setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400667 self.previewView.videoRunning = true;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400668 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400669 [self mouseIsMoving: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400670 self.distantView.videoRunning = true;
671 [self.distantView setHidden:NO];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400672 [bluerBackgroundEffect setHidden:YES];
673 [backgroundImage setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400674 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500675 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400676 renderConnections.stopped =
677 QObject::connect(mediaModel,
678 &lrc::api::AVModel::rendererStopped,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400679 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400680 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
681 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400682 self.previewView.videoRunning = false;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400683 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400684 [self mouseIsMoving: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400685 self.distantView.videoRunning = false;
686 [self.distantView setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400687 [bluerBackgroundEffect setHidden:NO];
688 [backgroundImage setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400689 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400690 });
691 renderConnections.frameUpdated =
692 QObject::connect(mediaModel,
693 &lrc::api::AVModel::frameUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400694 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400695 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
696 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
697 if(!renderer->isRendering()) {
698 return;
699 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400700 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
701 [self rendererPreview: renderer];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400702 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400703 auto renderer = &mediaModel->getRenderer(id);
704 if(!renderer->isRendering()) {
705 return;
706 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400707 [self rendererDistantView: renderer];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500708 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500709 });
710}
711
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400712-(void) rendererDistantView: (const lrc::api::video::Renderer*)renderer {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400713 @autoreleasepool {
714 auto framePtr = renderer->currentAVFrame();
715 auto frame = framePtr.get();
716 if(!frame || !frame->width || !frame->height) {
717 return;
718 }
719 auto frameSize = CGSizeMake(frame->width, frame->height);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400720 framesize = frameSize;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400721 auto rotation = 0;
722 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
723 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
724 rotation = av_display_rotation_get(data);
725 }
726 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400727 [self.distantView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400728 size: frameSize
729 rotation: rotation
730 fillFrame: false];
731 }
732 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400733 [self.distantView renderWithPixelBuffer: pixelBuffer
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400734 size: frameSize
735 rotation: rotation
736 fillFrame: false];
737 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500738 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400739}
740
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400741-(void) rendererPreview: (const lrc::api::video::Renderer*)renderer {
742 @autoreleasepool {
743 auto framePtr = renderer->currentAVFrame();
744 auto frame = framePtr.get();
745 if(!frame || !frame->width || !frame->height) {
746 return;
747 }
748 auto frameSize = CGSizeMake(frame->width, frame->height);
749 auto rotation = 0;
750 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
751 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
752 rotation = av_display_rotation_get(data);
753 }
754 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
755 [self.previewView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
756 size: frameSize
757 rotation: rotation
758 fillFrame: false];
759 }
760 if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
761 [self.previewView renderWithPixelBuffer: pixelBuffer
762 size: frameSize
763 rotation: rotation
764 fillFrame: true];
765 }
766 }
767}
768
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400769-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400770 [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
771 CVPixelBufferRef buffer = pixelBufferPreview;
772 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400773}
774
775-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400776 [VideoCommon fillPixelBuffr:&pixelBufferDistantView fromFrame:frame bufferPool:&pixelBufferPoolDistantView];
777 CVPixelBufferRef buffer = pixelBufferDistantView;
778 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400779}
780
Alexandre Lisionc5148052015-03-04 15:10:35 -0500781- (void) initFrame
782{
783 [self.view setFrame:self.view.superview.bounds];
784 [self.view setHidden:YES];
785 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400786 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500787}
788
789# pragma private IN/OUT animations
790
Olivier Soldano994971f2017-12-05 16:30:12 -0500791-(void)uncollapseRightView
792{
793 NSView *left = [[splitView subviews] objectAtIndex:0];
794 NSView *right = [[splitView subviews] objectAtIndex:1];
795 [right setHidden:NO];
796
797 CGFloat dividerThickness = [splitView dividerThickness];
798
799 // get the different frames
800 NSRect leftFrame = [left frame];
801 NSRect rightFrame = [right frame];
802
803 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
804 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
805 [left setFrameSize:leftFrame.size];
806 [right setFrame:rightFrame];
807 [splitView display];
808
809 [self.chatVC takeFocus];
810}
811
Alexandre Lisionc5148052015-03-04 15:10:35 -0500812-(void) cleanUp
813{
Anthony Léonard5c287642018-02-21 15:54:19 -0500814 if(self.splitView.isInFullScreenMode)
815 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400816 QObject::disconnect(renderConnections.frameUpdated);
817 QObject::disconnect(renderConnections.started);
818 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500819 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400820
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500821 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400822 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500823
Alexandre Lision57227772016-01-15 17:19:36 -0500824 [timeSpentLabel setStringValue:@""];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400825 [contactIdLabel setStringValue:@""];
826 [contactNameLabel setStringValue:@""];
827 [contactPhoto setImage:nil];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400828 //background view
829 [bluerBackgroundEffect setHidden:NO];
830 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400831 backgroundImage.layer.contents = nil;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400832 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400833 [self.distantView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400834
835 contactNameLabel.textColor = [NSColor highlightColor];
836 contactNameLabel.textColor = [NSColor highlightColor];
837 contactIdLabel.textColor = [NSColor highlightColor];
838 callStateLabel.textColor = [NSColor highlightColor];
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400839 [self.chatVC clearData];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500840}
841
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400842/*
843 * update ui
844*/
845
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400846-(void) setupCallView
847{
848 if (accountInfo_ == nil)
849 return;
850
851 auto* callModel = accountInfo_->callModel.get();
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400852
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400853 /* check if text media is already present */
854 if(not callModel->hasCall(callUid_))
855 return;
856
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400857 // when call comes in we want to show the controls/header
858 [self mouseIsMoving: YES];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400859 [loadingIndicator setAnimates:YES];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400860 auto currentCall = callModel->getCall([self getcallID]);
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400861 [previewView setHidden: YES];
862 [self setUpPreviewFrame];
863 [self updatePendingCalls];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400864 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400865}
866
867-(void) showWithAnimation:(BOOL)animate
868{
869 if (!animate) {
870 [self.view setHidden:NO];
871 [self setupCallView];
872 return;
873 }
874
875 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
876 [self.view setHidden:NO];
877
878 [CATransaction begin];
879 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
880 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
881 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
882 [animation setDuration:0.2f];
883 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
884 [CATransaction setCompletionBlock:^{
885 [self setupCallView];
886 }];
887
888 [self.view.layer addAnimation:animation forKey:animation.keyPath];
889 [CATransaction commit];
890}
891
892-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500893{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500894 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500895 return;
896 }
897
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400898 if (!animate) {
899 [self.view setHidden:YES];
900 return;
901 }
902
Alexandre Lisionc5148052015-03-04 15:10:35 -0500903 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
904 [CATransaction begin];
905 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
906 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
907 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
908 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500909 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500910
911 [CATransaction setCompletionBlock:^{
912 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400913 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500914 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500915// if (currentCall_) {
916// [self animateIn];
917// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500918 }];
919 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400920
921 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500922 [CATransaction commit];
923}
924
925/**
926 * Debug purpose
927 */
928-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
929{
930 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
931}
932
Alexandre Lision58cab672015-06-09 15:25:40 -0400933#pragma mark - Button methods
934
Alexandre Lisionc5148052015-03-04 15:10:35 -0500935- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500936 if (accountInfo_ == nil)
937 return;
938
939 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -0500940 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500941}
942
943- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500944 if (accountInfo_ == nil)
945 return;
946
Anthony Léonardf584cc52018-01-19 12:00:19 -0500947 // If we accept a conversation with a non trusted contact, we first accept it
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400948 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
949 if (conv.uid.isEmpty() || conv.participants.empty()) {
950 return;
951 }
952 auto& contact = accountInfo_->contactModel->getContact(conv.participants[0]);
953 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING) {
954 accountInfo_->conversationModel->makePermanent(convUid_);
Anthony Léonardf584cc52018-01-19 12:00:19 -0500955 }
956
Olivier Soldano994971f2017-12-05 16:30:12 -0500957 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500958 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500959}
960
961- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500962 if (accountInfo_ == nil)
963 return;
964
965 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500966 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400967 if (callModel->isRecording(callUid_)) {
968 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
969 } else {
970 [recordOnOffButton stopBlinkAnimation];
971 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500972}
973
974- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500975 if (accountInfo_ == nil)
976 return;
977
978 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400979 callModel->togglePause([self getcallID]);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500980}
981
Alexandre Lision62005312016-01-28 15:55:16 -0500982- (IBAction)showDialpad:(id)sender {
983 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
984 [appDelegate showDialpad];
985}
986
Alexandre Lision58cab672015-06-09 15:25:40 -0400987-(IBAction)toggleChat:(id)sender;
988{
989 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
990 if (rightViewCollapsed) {
991 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -0400992 } else {
993 [self collapseRightView];
994 }
Alexandre Lision62005312016-01-28 15:55:16 -0500995 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -0400996}
997
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400998- (IBAction)muteAudio:(id)sender
999{
Olivier Soldano994971f2017-12-05 16:30:12 -05001000 if (accountInfo_ == nil)
1001 return;
1002
1003 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001004 auto currentCall = callModel->getCall(callUid_);
1005 if (currentCall.audioMuted) {
1006 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
1007 } else {
1008 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
1009 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001010 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001011}
1012
1013- (IBAction)muteVideo:(id)sender
1014{
Olivier Soldano994971f2017-12-05 16:30:12 -05001015 if (accountInfo_ == nil)
1016 return;
Olivier Soldano994971f2017-12-05 16:30:12 -05001017 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001018 auto currentCall = callModel->getCall(callUid_);
1019 if(!currentCall.isAudioOnly) {
1020 if (currentCall.videoMuted) {
1021 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
1022 } else {
1023 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
1024 }
1025 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001026 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001027}
Alexandre Lision883719f2015-10-22 17:37:45 -04001028
1029- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001030
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -04001031}
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001032
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001033- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001034 if (brokerPopoverVC != nullptr) {
1035 [brokerPopoverVC performClose:self];
1036 brokerPopoverVC = NULL;
1037 } else {
1038 auto* contactSelectorVC = [[ChooseContactVC alloc] initWithNibName:@"ChooseContactVC" bundle:nil];
1039 auto* convModel = accountInfo_->conversationModel.get();
1040 [contactSelectorVC setConversationModel:convModel andCurrentConversation: convUid_];
1041 contactSelectorVC.delegate = self;
1042 brokerPopoverVC = [[NSPopover alloc] init];
1043 [brokerPopoverVC setContentSize:contactSelectorVC.view.frame.size];
1044 [brokerPopoverVC setContentViewController:contactSelectorVC];
1045 [brokerPopoverVC setAnimates:YES];
1046 [brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
1047 [brokerPopoverVC setDelegate:self];
1048 [brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
1049 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001050}
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001051
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001052- (IBAction)hidePreview:(id)sender {
1053 CGRect previewFrame = previewView.frame;
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -05001054 CGRect newPreviewFrame;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001055 if (previewFrame.size.width > HIDE_PREVIEW_BUTTON_SIZE) {
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001056 self.movableBaseForView.movable = false;
1057 newPreviewFrame = self.getVideoPreviewCollapsedSize;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001058 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarEnterFullScreenTemplate];
1059 } else {
1060 self.movableBaseForView.movable = true;
1061 newPreviewFrame = CGRectMake(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001062 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarExitFullScreenTemplate];
1063 }
1064 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
1065 context.duration = 0.2f;
1066 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
1067 previewView.animator.frame = newPreviewFrame;
1068 } completionHandler: nil];
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001069}
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001070
Alexandre Lision58cab672015-06-09 15:25:40 -04001071#pragma mark - NSSplitViewDelegate
1072
1073/* Return YES if the subview should be collapsed because the user has double-clicked on an adjacent divider. If a split view has a delegate, and the delegate responds to this message, it will be sent once for the subview before a divider when the user double-clicks on that divider, and again for the subview after the divider, but only if the delegate returned YES when sent -splitView:canCollapseSubview: for the subview in question. When the delegate indicates that both subviews should be collapsed NSSplitView's behavior is undefined.
1074 */
1075- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1076{
1077 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1078 return ([subview isEqual:rightView]);
1079}
1080
1081
1082- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1083{
1084 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1085 return ([subview isEqual:rightView]);
1086}
1087
1088
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001089# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001090
1091- (void) callShouldToggleFullScreen
1092{
1093 if(self.splitView.isInFullScreenMode)
1094 [self.splitView exitFullScreenModeWithOptions:nil];
1095 else {
1096 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1097 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1098 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1099 NSFullScreenModeApplicationPresentationOptions, nil];
1100
1101 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1102 }
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001103 for (ConferenceOverlayView* participant: [participantsOverlays allValues]) {
1104 [participant sizeChanged];
1105 }
Alexandre Lision58cab672015-06-09 15:25:40 -04001106}
1107
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001108-(void) mouseIsMoving:(BOOL) move
1109{
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001110 [[controlsPanel animator] setAlphaValue:move];// fade out
1111 [[controlsStackView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001112 [[headerContainer animator] setAlphaValue:move];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001113 [[headerGradientView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001114}
1115
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001116- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1117{
1118 return YES;
1119}
1120
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001121-(void) screenShare {
1122 NSScreen *mainScreen = [NSScreen mainScreen];
1123 NSRect screenFrame = mainScreen.frame;
1124 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1125 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1126}
1127-(void) switchToDevice:(int)deviceID {
1128 auto devices = mediaModel->getDevices();
1129 auto device = devices[deviceID];
1130 mediaModel->switchInputTo(device);
1131}
1132
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001133-(QVector<QString>) getDeviceList {
1134 QVector<QString> devicesVector;
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001135 for (auto device : mediaModel->getDevices()) {
1136 try {
1137 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001138 devicesVector.append(settings.name);
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001139 } catch (...) {}
1140 }
1141 return devicesVector;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001142}
1143
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001144-(NSString*) getDefaultDeviceName {
1145 auto type = mediaModel->getCurrentRenderedDevice(callUid_).type;
1146 switch (type) {
1147 case lrc::api::video::DeviceType::CAMERA:
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001148 try {
1149 auto device = mediaModel->getCurrentRenderedDevice(callUid_).name;
1150 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001151 return settings.name.toNSString();
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001152 } catch (...) {}
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001153 case lrc::api::video::DeviceType::DISPLAY:
1154 return NSLocalizedString(@"Share screen", @"Contextual menu entry");
1155 default:
1156 return @"";
1157 }
1158}
1159
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001160-(void) switchToFile:(QString)uri {
1161 mediaModel->setInputFile(uri);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001162}
1163
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001164-(CGRect) getVideoPreviewCollapsedSize {
1165 CGPoint origin;
1166 switch (movableBaseForView.closestCorner) {
1167 case TOP_LEFT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001168 origin = CGPointMake(0, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001169 break;
1170 case BOTTOM_LEFT:
1171 origin = CGPointMake(0, 0);
1172 break;
1173 case TOP_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001174 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001175 break;
1176 case BOTTOM_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001177 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE, 0);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001178 break;
1179 }
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001180 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001181}
1182
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001183-(CGRect) frameForExpendPreviewButton {
1184 CGPoint origin = CGPointMake(self.previewView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE,
1185 self.previewView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
1186 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001187}
1188
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001189# pragma mark -ChooseContactVCDelegate
1190
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001191-(void)callToContact:(const QString&)contactUri convUID:(const QString&)convID {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001192 if (brokerPopoverVC != nullptr) {
1193 [brokerPopoverVC performClose:self];
1194 brokerPopoverVC = NULL;
1195 }
1196 auto* callModel = accountInfo_->callModel.get();
1197 auto currentCall = callModel->getCall([self getcallID]);
1198 auto* convModel = accountInfo_->conversationModel.get();
1199 auto newCall = callModel->callAndAddParticipant(contactUri,
1200 [self getcallID],
1201 currentCall.isAudioOnly);
1202 [self addPreviewForContactUri:contactUri call: newCall];
1203}
1204
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001205-(void)joinCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001206 if (brokerPopoverVC != nullptr) {
1207 [brokerPopoverVC performClose:self];
1208 brokerPopoverVC = NULL;
1209 }
1210 auto* callModel = accountInfo_->callModel.get();
1211 callModel->joinCalls([self getcallID], callId);
1212}
1213
1214# pragma mark -CallInConferenceVCDelegate
1215
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001216-(void)removePreviewForContactUri:(const QString&)uri forCall:(const QString&) callId {
1217 NSMutableDictionary * calls = connectingCalls[callId.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001218 if (!calls) {
1219 return;
1220 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001221 NSViewController *callView = calls[uri.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001222 if (!callView) {
1223 return;
1224 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001225 calls[uri.toNSString()] = nil;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001226 [self.callingWidgetsContainer removeView:callView.view];
1227}
1228
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001229-(void)addPreviewForContactUri:(const QString&)uri call:(const QString&)callId {
1230 NSMutableDictionary *calls = connectingCalls[callUid_.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001231 if (!calls) {
1232 calls = [[NSMutableDictionary alloc] init];
1233 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001234 if (calls[uri.toNSString()]) {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001235 return;
1236 }
1237 CallInConferenceVC *callingView = [self callingViewForCallId: callId];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001238 calls[uri.toNSString()] = callingView;
1239 connectingCalls[callUid_.toNSString()] = calls;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001240 [self.callingWidgetsContainer addView:callingView.view inGravity:NSStackViewGravityBottom];
1241 [self.callingWidgetsContainer updateConstraints];
1242 [self.callingWidgetsContainer updateLayer];
1243 [self.callingWidgetsContainer setNeedsDisplay:YES];
1244}
1245
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001246-(CallInConferenceVC*) callingViewForCallId:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001247 CallInConferenceVC *callView = [[CallInConferenceVC alloc]
1248 initWithNibName:@"CallInConferenceVC"
1249 bundle:nil
1250 callId:callId
1251 accountInfo:accountInfo_];
1252 callView.delegate = self;
1253 callView.initiatorCallId = callUid_;
1254 return callView;
1255}
1256
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001257-(const QString&)getcallID {
1258 return confUid_.isEmpty() ? callUid_ : confUid_;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001259}
1260
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001261-(bool)isCurrentCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001262 return (callId == confUid_ || callId == callUid_);
1263}
1264
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001265#pragma mark ConferenceLayoutDelegate
1266
1267-(void)hangUpParticipant:(NSString*)uri {
1268 if (accountInfo_ == nil)
1269 return;
1270 auto* callModel = accountInfo_->callModel.get();
1271
1272 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1273 auto callId = convIt->callId;
1274 if (not callModel->hasCall(callId)){
1275 return;
1276 }
1277 callModel->hangUp(callId);
1278}
1279
1280-(void)minimizeParticipant {
1281 if (accountInfo_ == nil)
1282 return;
1283 auto* callModel = accountInfo_->callModel.get();
1284 if (not callModel->hasCall(confUid_)) {
1285 return;
1286 }
1287 try {
1288 auto call = callModel->getCall(confUid_);
1289 switch (call.layout) {
1290 case lrc::api::call::Layout::GRID:
1291 break;
1292 case lrc::api::call::Layout::ONE_WITH_SMALL:
1293 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::GRID);
1294 break;
1295 case lrc::api::call::Layout::ONE:
1296 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::ONE_WITH_SMALL);
1297 break;
1298 };
1299 } catch (...) {}
1300}
1301
1302-(int)getCurrentLayout {
1303 auto* callModel = accountInfo_->callModel.get();
1304 if (not callModel->hasCall(confUid_)){
1305 return -1;
1306 }
1307 return static_cast<int>(callModel->getCall(confUid_).layout);
1308}
1309
1310-(BOOL)isMasterCall {
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001311 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
1312 if (conv.uid.isEmpty()) {
1313 return false;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001314 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001315
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001316 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001317 if (!conv.confId.isEmpty() && callModel->hasCall(conv.confId)) {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001318 return true;
1319 } else {
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001320 auto call = callModel->getCall(conv.callId);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001321 return call.participantsInfos.size() == 0;
1322 }
1323}
1324
1325-(void)maximizeParticipant:(NSString*)uri active:(BOOL)isActive {
1326 if (accountInfo_ == nil)
1327 return;
1328 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1329 QString callId;
1330 BOOL localVideo = accountInfo_->profileInfo.uri == QString::fromNSString(uri);
1331 if (!localVideo) {
1332 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1333 callId = convIt->callId;
1334 }
1335 auto* callModel = accountInfo_->callModel.get();
1336 if ((not callModel->hasCall(callId) || not callModel->hasCall(confUid_)) && !localVideo){
1337 return;
1338 }
1339 try {
1340 auto call = callModel->getCall(confUid_);
1341 switch (call.layout) {
1342 case lrc::api::call::Layout::GRID:
1343 callModel->setActiveParticipant(confUid_, callId);
1344 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::ONE_WITH_SMALL);
1345 break;
1346 case lrc::api::call::Layout::ONE_WITH_SMALL:
1347 callModel->setActiveParticipant(confUid_, callId);
1348 callModel->setConferenceLayout(confUid_,
1349 isActive ? lrc::api::call::Layout::ONE : lrc::api::call::Layout::ONE_WITH_SMALL);
1350 break;
1351 case lrc::api::call::Layout::ONE:
1352 callModel->setActiveParticipant(confUid_, callId);
1353 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::GRID);
1354 break;
1355 };
1356 } catch (...) {}
1357}
1358
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001359#pragma mark Popover delegate
1360
1361- (void)popoverWillClose:(NSNotification *)notification
1362{
1363 if (brokerPopoverVC != nullptr) {
1364 [brokerPopoverVC performClose:self];
1365 brokerPopoverVC = NULL;
1366 }
1367}
Alexandre Lisionc5148052015-03-04 15:10:35 -05001368@end