blob: 913aa83c8ae41d594d674f36bc6ef3a2e1a96306 [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;
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -0400170 [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;
Olivier Soldano994971f2017-12-05 16:30:12 -0500494 switch (currentCall.status) {
495 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500496 case Status::CONNECTING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500497 case Status::OUTGOING_RINGING:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400498 case Status::INCOMING_RINGING:
499 [infoContainer setHidden: NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400500 [headerContainer setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400501 [headerGradientView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400502 [controlsPanel setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400503 [controlsStackView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400504 [self.distantView fillWithBlack];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400505 [self.previewView fillWithBlack];
506 [hidePreviewBackground setHidden:YES];
507 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400508 [self.distantView setHidden: YES];
509 self.previewView.videoRunning = NO;
510 self.distantView.videoRunning = NO;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400511 [backgroundImage setHidden:NO];
512 [bluerBackgroundEffect setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500513 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500514 case Status::PAUSED:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400515 [infoContainer setHidden: NO];
516 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400517 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400518 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400519 [controlsStackView setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400520 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400521 [bluerBackgroundEffect setHidden:NO];
522 if(!currentCall.isAudioOnly) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400523 [self.distantView fillWithBlack];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400524 [self.previewView fillWithBlack];
525 [hidePreviewBackground setHidden:YES];
526 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400527 [self.distantView setHidden: YES];
528 self.previewView.videoRunning = NO;
529 self.distantView.videoRunning = NO;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400530 }
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400531 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500532 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400533 if(currentCall.isAudioOnly) {
534 [self setUpAudioOnlyView];
535 } else {
536 [self setUpVideoCallView];
537 }
538 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500539 case Status::IN_PROGRESS:
“Kateryna”414738e2020-04-07 11:44:35 -0400540 callModel->setCurrentCall([self getcallID]);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400541 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400542 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400543 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400544 [controlsStackView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400545 if(currentCall.isAudioOnly) {
546 [self setUpAudioOnlyView];
547 } else {
548 [self setUpVideoCallView];
549 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400550 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500551 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500552 break;
553 case Status::ENDED:
554 case Status::TERMINATING:
555 case Status::INVALID:
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400556 case Status::PEER_BUSY:
557 case Status::TIMEOUT:
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400558 connectingCalls[callUid_.toNSString()] = nil;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400559 [self.delegate callFinished];
Kateryna Kostiuk66150982020-09-18 15:14:36 -0400560 [self removeConferenceLayout];
561 [self switchToNextConferenceCall: confUid_];
Kateryna Kostiukd5aded72020-11-06 17:03:42 -0500562 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
563 [appDelegate restoreScreenSleep];
Olivier Soldano994971f2017-12-05 16:30:12 -0500564 break;
565 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500566}
567
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400568-(void) setUpVideoCallView {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400569 [previewView fillWithBlack];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400570 [self.distantView fillWithBlack];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400571 [previewView setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400572 [self.distantView setHidden:NO];
573 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400574 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400575 [backgroundImage setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400576 self.previewView.videoRunning = true;
577 self.distantView.videoRunning = true;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400578}
579
580-(void) setUpAudioOnlyView {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400581 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400582 [self.distantView setHidden: YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400583 [hidePreviewBackground setHidden: YES];
584 [bluerBackgroundEffect setHidden:NO];
585 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400586}
587
588-(void) setBackground {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400589 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
590 if(image) {
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400591 @autoreleasepool {
592 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
593 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
594 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
595 [clamp setValue:ciImage forKey: kCIInputImageKey];
596 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
597 [bluerFilter setDefaults];
598 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
599 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
600 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
601 CIContext *context = [CIContext contextWithOptions:nil];
602 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
603 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
604 backgroundImage.layer.contents = bluredImage;
605 [backgroundImage setHidden:NO];
606 [bluerBackgroundEffect setFillColor:[NSColor darkGrayColor]];
607 [bluerBackgroundEffect setAlphaValue:0.6];
608 }
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400609 } else {
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400610 contactNameLabel.textColor = [NSColor textColor];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400611 contactIdLabel.textColor = [NSColor textColor];
612 callStateLabel.textColor = [NSColor textColor];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400613 backgroundImage.layer.contents = nil;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400614 [bluerBackgroundEffect setFillColor:[NSColor windowBackgroundColor]];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400615 [bluerBackgroundEffect setAlphaValue:1];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400616 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400617 }
618}
619
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400620-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400621 @autoreleasepool {
622 auto* convModel = accountInfo_->conversationModel.get();
623 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
624 if (conv.uid.isEmpty() || conv.participants.empty()) {
625 return nil;
626 }
627 if(shouldDrawDefault) {
628 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
629 QVariant photo = imgManip.conversationPhoto(conv, *accountInfo_, QSize(size, size), NO);
630 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
631 }
632 auto contact = accountInfo_->contactModel->getContact(conv.participants[0]);
633 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:contact.profileInfo.avatar.toNSString() options:NSDataBase64DecodingIgnoreUnknownCharacters];
634 return [[NSImage alloc] initWithData:imageData];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400635 }
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400636}
637
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400638-(void) setupContactInfo:(NSImageView*)imageView
639{
640 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
641}
642
Olivier Soldano994971f2017-12-05 16:30:12 -0500643-(void)collapseRightView
644{
645 NSView *right = [[splitView subviews] objectAtIndex:1];
646 NSView *left = [[splitView subviews] objectAtIndex:0];
647 NSRect leftFrame = [left frame];
648 [right setHidden:YES];
649 [splitView display];
650}
651
Alexandre Lisionc5148052015-03-04 15:10:35 -0500652-(void) connectVideoSignals
653{
Olivier Soldano994971f2017-12-05 16:30:12 -0500654 if (accountInfo_ == nil)
655 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400656 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500657}
658
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400659-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500660{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400661 QObject::disconnect(renderConnections.frameUpdated);
662 QObject::disconnect(renderConnections.stopped);
663 QObject::disconnect(renderConnections.started);
664 renderConnections.started =
665 QObject::connect(mediaModel,
666 &lrc::api::AVModel::rendererStarted,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400667 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400668 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
669 [self.previewView setHidden:NO];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400670 [hidePreviewBackground setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400671 self.previewView.videoRunning = true;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400672 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400673 [self mouseIsMoving: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400674 self.distantView.videoRunning = true;
675 [self.distantView setHidden:NO];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400676 [bluerBackgroundEffect setHidden:YES];
677 [backgroundImage setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400678 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500679 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400680 renderConnections.stopped =
681 QObject::connect(mediaModel,
682 &lrc::api::AVModel::rendererStopped,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400683 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400684 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
685 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400686 self.previewView.videoRunning = false;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400687 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400688 [self mouseIsMoving: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400689 self.distantView.videoRunning = false;
690 [self.distantView setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400691 [bluerBackgroundEffect setHidden:NO];
692 [backgroundImage setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400693 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400694 });
695 renderConnections.frameUpdated =
696 QObject::connect(mediaModel,
697 &lrc::api::AVModel::frameUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400698 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400699 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
700 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
701 if(!renderer->isRendering()) {
702 return;
703 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400704 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
705 [self rendererPreview: renderer];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400706 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400707 auto renderer = &mediaModel->getRenderer(id);
708 if(!renderer->isRendering()) {
709 return;
710 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400711 [self rendererDistantView: renderer];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500712 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500713 });
714}
715
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400716-(void) rendererDistantView: (const lrc::api::video::Renderer*)renderer {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400717 @autoreleasepool {
718 auto framePtr = renderer->currentAVFrame();
719 auto frame = framePtr.get();
720 if(!frame || !frame->width || !frame->height) {
721 return;
722 }
723 auto frameSize = CGSizeMake(frame->width, frame->height);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400724 framesize = frameSize;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400725 auto rotation = 0;
726 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
727 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
728 rotation = av_display_rotation_get(data);
729 }
730 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400731 [self.distantView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400732 size: frameSize
733 rotation: rotation
734 fillFrame: false];
735 }
736 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400737 [self.distantView renderWithPixelBuffer: pixelBuffer
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400738 size: frameSize
739 rotation: rotation
740 fillFrame: false];
741 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500742 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400743}
744
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400745-(void) rendererPreview: (const lrc::api::video::Renderer*)renderer {
746 @autoreleasepool {
747 auto framePtr = renderer->currentAVFrame();
748 auto frame = framePtr.get();
749 if(!frame || !frame->width || !frame->height) {
750 return;
751 }
752 auto frameSize = CGSizeMake(frame->width, frame->height);
753 auto rotation = 0;
754 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
755 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
756 rotation = av_display_rotation_get(data);
757 }
758 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
759 [self.previewView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
760 size: frameSize
761 rotation: rotation
762 fillFrame: false];
763 }
764 if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
765 [self.previewView renderWithPixelBuffer: pixelBuffer
766 size: frameSize
767 rotation: rotation
768 fillFrame: true];
769 }
770 }
771}
772
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400773-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400774 [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
775 CVPixelBufferRef buffer = pixelBufferPreview;
776 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400777}
778
779-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400780 [VideoCommon fillPixelBuffr:&pixelBufferDistantView fromFrame:frame bufferPool:&pixelBufferPoolDistantView];
781 CVPixelBufferRef buffer = pixelBufferDistantView;
782 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400783}
784
Alexandre Lisionc5148052015-03-04 15:10:35 -0500785- (void) initFrame
786{
787 [self.view setFrame:self.view.superview.bounds];
788 [self.view setHidden:YES];
789 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400790 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500791}
792
793# pragma private IN/OUT animations
794
Olivier Soldano994971f2017-12-05 16:30:12 -0500795-(void)uncollapseRightView
796{
797 NSView *left = [[splitView subviews] objectAtIndex:0];
798 NSView *right = [[splitView subviews] objectAtIndex:1];
799 [right setHidden:NO];
800
801 CGFloat dividerThickness = [splitView dividerThickness];
802
803 // get the different frames
804 NSRect leftFrame = [left frame];
805 NSRect rightFrame = [right frame];
806
807 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
808 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
809 [left setFrameSize:leftFrame.size];
810 [right setFrame:rightFrame];
811 [splitView display];
812
813 [self.chatVC takeFocus];
814}
815
Alexandre Lisionc5148052015-03-04 15:10:35 -0500816-(void) cleanUp
817{
Anthony Léonard5c287642018-02-21 15:54:19 -0500818 if(self.splitView.isInFullScreenMode)
819 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400820 QObject::disconnect(renderConnections.frameUpdated);
821 QObject::disconnect(renderConnections.started);
822 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500823 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400824
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500825 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400826 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500827
Alexandre Lision57227772016-01-15 17:19:36 -0500828 [timeSpentLabel setStringValue:@""];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400829 [contactIdLabel setStringValue:@""];
830 [contactNameLabel setStringValue:@""];
831 [contactPhoto setImage:nil];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400832 //background view
833 [bluerBackgroundEffect setHidden:NO];
834 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400835 backgroundImage.layer.contents = nil;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400836 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400837 [self.distantView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400838
839 contactNameLabel.textColor = [NSColor highlightColor];
840 contactNameLabel.textColor = [NSColor highlightColor];
841 contactIdLabel.textColor = [NSColor highlightColor];
842 callStateLabel.textColor = [NSColor highlightColor];
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400843 [self.chatVC clearData];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500844}
845
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400846/*
847 * update ui
848*/
849
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400850-(void) setupCallView
851{
852 if (accountInfo_ == nil)
853 return;
854
855 auto* callModel = accountInfo_->callModel.get();
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400856
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400857 /* check if text media is already present */
858 if(not callModel->hasCall(callUid_))
859 return;
860
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400861 // when call comes in we want to show the controls/header
862 [self mouseIsMoving: YES];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400863 [loadingIndicator setAnimates:YES];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400864 auto currentCall = callModel->getCall([self getcallID]);
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400865 [previewView setHidden: YES];
866 [self setUpPreviewFrame];
867 [self updatePendingCalls];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400868 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400869}
870
871-(void) showWithAnimation:(BOOL)animate
872{
873 if (!animate) {
874 [self.view setHidden:NO];
875 [self setupCallView];
876 return;
877 }
878
879 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
880 [self.view setHidden:NO];
881
882 [CATransaction begin];
883 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
884 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
885 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
886 [animation setDuration:0.2f];
887 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
888 [CATransaction setCompletionBlock:^{
889 [self setupCallView];
890 }];
891
892 [self.view.layer addAnimation:animation forKey:animation.keyPath];
893 [CATransaction commit];
894}
895
896-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500897{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500898 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500899 return;
900 }
901
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400902 if (!animate) {
903 [self.view setHidden:YES];
904 return;
905 }
906
Alexandre Lisionc5148052015-03-04 15:10:35 -0500907 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
908 [CATransaction begin];
909 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
910 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
911 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
912 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500913 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500914
915 [CATransaction setCompletionBlock:^{
916 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400917 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500918 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500919// if (currentCall_) {
920// [self animateIn];
921// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500922 }];
923 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400924
925 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500926 [CATransaction commit];
927}
928
929/**
930 * Debug purpose
931 */
932-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
933{
934 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
935}
936
Alexandre Lision58cab672015-06-09 15:25:40 -0400937#pragma mark - Button methods
938
Alexandre Lisionc5148052015-03-04 15:10:35 -0500939- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500940 if (accountInfo_ == nil)
941 return;
942
943 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -0500944 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500945}
946
947- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500948 if (accountInfo_ == nil)
949 return;
950
Anthony Léonardf584cc52018-01-19 12:00:19 -0500951 // If we accept a conversation with a non trusted contact, we first accept it
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400952 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
953 if (conv.uid.isEmpty() || conv.participants.empty()) {
954 return;
955 }
956 auto& contact = accountInfo_->contactModel->getContact(conv.participants[0]);
957 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING) {
958 accountInfo_->conversationModel->makePermanent(convUid_);
Anthony Léonardf584cc52018-01-19 12:00:19 -0500959 }
960
Olivier Soldano994971f2017-12-05 16:30:12 -0500961 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500962 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500963}
964
965- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500966 if (accountInfo_ == nil)
967 return;
968
969 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500970 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400971 if (callModel->isRecording(callUid_)) {
972 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
973 } else {
974 [recordOnOffButton stopBlinkAnimation];
975 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500976}
977
978- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500979 if (accountInfo_ == nil)
980 return;
981
982 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400983 callModel->togglePause([self getcallID]);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500984}
985
Alexandre Lision62005312016-01-28 15:55:16 -0500986- (IBAction)showDialpad:(id)sender {
987 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
988 [appDelegate showDialpad];
989}
990
Alexandre Lision58cab672015-06-09 15:25:40 -0400991-(IBAction)toggleChat:(id)sender;
992{
993 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
994 if (rightViewCollapsed) {
995 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -0400996 } else {
997 [self collapseRightView];
998 }
Alexandre Lision62005312016-01-28 15:55:16 -0500999 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -04001000}
1001
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001002- (IBAction)muteAudio:(id)sender
1003{
Olivier Soldano994971f2017-12-05 16:30:12 -05001004 if (accountInfo_ == nil)
1005 return;
1006
1007 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001008 auto currentCall = callModel->getCall(callUid_);
1009 if (currentCall.audioMuted) {
1010 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
1011 } else {
1012 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
1013 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001014 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001015}
1016
1017- (IBAction)muteVideo:(id)sender
1018{
Olivier Soldano994971f2017-12-05 16:30:12 -05001019 if (accountInfo_ == nil)
1020 return;
Olivier Soldano994971f2017-12-05 16:30:12 -05001021 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001022 auto currentCall = callModel->getCall(callUid_);
1023 if(!currentCall.isAudioOnly) {
1024 if (currentCall.videoMuted) {
1025 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
1026 } else {
1027 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
1028 }
1029 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001030 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001031}
Alexandre Lision883719f2015-10-22 17:37:45 -04001032
1033- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001034
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -04001035}
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001036
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001037- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001038 if (brokerPopoverVC != nullptr) {
1039 [brokerPopoverVC performClose:self];
1040 brokerPopoverVC = NULL;
1041 } else {
1042 auto* contactSelectorVC = [[ChooseContactVC alloc] initWithNibName:@"ChooseContactVC" bundle:nil];
1043 auto* convModel = accountInfo_->conversationModel.get();
1044 [contactSelectorVC setConversationModel:convModel andCurrentConversation: convUid_];
1045 contactSelectorVC.delegate = self;
1046 brokerPopoverVC = [[NSPopover alloc] init];
1047 [brokerPopoverVC setContentSize:contactSelectorVC.view.frame.size];
1048 [brokerPopoverVC setContentViewController:contactSelectorVC];
1049 [brokerPopoverVC setAnimates:YES];
1050 [brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
1051 [brokerPopoverVC setDelegate:self];
1052 [brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
1053 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001054}
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001055
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001056- (IBAction)hidePreview:(id)sender {
1057 CGRect previewFrame = previewView.frame;
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -05001058 CGRect newPreviewFrame;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001059 if (previewFrame.size.width > HIDE_PREVIEW_BUTTON_SIZE) {
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001060 self.movableBaseForView.movable = false;
1061 newPreviewFrame = self.getVideoPreviewCollapsedSize;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001062 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarEnterFullScreenTemplate];
1063 } else {
1064 self.movableBaseForView.movable = true;
1065 newPreviewFrame = CGRectMake(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001066 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarExitFullScreenTemplate];
1067 }
1068 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
1069 context.duration = 0.2f;
1070 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
1071 previewView.animator.frame = newPreviewFrame;
1072 } completionHandler: nil];
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001073}
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001074
Alexandre Lision58cab672015-06-09 15:25:40 -04001075#pragma mark - NSSplitViewDelegate
1076
1077/* 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.
1078 */
1079- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1080{
1081 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1082 return ([subview isEqual:rightView]);
1083}
1084
1085
1086- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1087{
1088 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1089 return ([subview isEqual:rightView]);
1090}
1091
1092
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001093# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001094
1095- (void) callShouldToggleFullScreen
1096{
1097 if(self.splitView.isInFullScreenMode)
1098 [self.splitView exitFullScreenModeWithOptions:nil];
1099 else {
1100 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1101 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1102 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1103 NSFullScreenModeApplicationPresentationOptions, nil];
1104
1105 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1106 }
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001107 for (ConferenceOverlayView* participant: [participantsOverlays allValues]) {
1108 [participant sizeChanged];
1109 }
Alexandre Lision58cab672015-06-09 15:25:40 -04001110}
1111
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001112-(void) mouseIsMoving:(BOOL) move
1113{
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001114 [[controlsPanel animator] setAlphaValue:move];// fade out
1115 [[controlsStackView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001116 [[headerContainer animator] setAlphaValue:move];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001117 [[headerGradientView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001118}
1119
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001120- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1121{
1122 return YES;
1123}
1124
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001125-(void) screenShare {
1126 NSScreen *mainScreen = [NSScreen mainScreen];
1127 NSRect screenFrame = mainScreen.frame;
1128 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
Kateryna Kostiukb05049a2020-11-02 17:33:10 -05001129 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height, [self getcallID]);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001130}
1131-(void) switchToDevice:(int)deviceID {
1132 auto devices = mediaModel->getDevices();
1133 auto device = devices[deviceID];
Kateryna Kostiukb05049a2020-11-02 17:33:10 -05001134 mediaModel->switchInputTo(device, [self getcallID]);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001135}
1136
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001137-(QVector<QString>) getDeviceList {
1138 QVector<QString> devicesVector;
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001139 for (auto device : mediaModel->getDevices()) {
1140 try {
1141 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001142 devicesVector.append(settings.name);
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001143 } catch (...) {}
1144 }
1145 return devicesVector;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001146}
1147
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001148-(NSString*) getDefaultDeviceName {
1149 auto type = mediaModel->getCurrentRenderedDevice(callUid_).type;
1150 switch (type) {
1151 case lrc::api::video::DeviceType::CAMERA:
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001152 try {
1153 auto device = mediaModel->getCurrentRenderedDevice(callUid_).name;
1154 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001155 return settings.name.toNSString();
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001156 } catch (...) {}
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001157 case lrc::api::video::DeviceType::DISPLAY:
1158 return NSLocalizedString(@"Share screen", @"Contextual menu entry");
1159 default:
1160 return @"";
1161 }
1162}
1163
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001164-(void) switchToFile:(QString)uri {
Kateryna Kostiukb05049a2020-11-02 17:33:10 -05001165 mediaModel->setInputFile(uri, [self getcallID]);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001166}
1167
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001168-(CGRect) getVideoPreviewCollapsedSize {
1169 CGPoint origin;
1170 switch (movableBaseForView.closestCorner) {
1171 case TOP_LEFT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001172 origin = CGPointMake(0, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001173 break;
1174 case BOTTOM_LEFT:
1175 origin = CGPointMake(0, 0);
1176 break;
1177 case TOP_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001178 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 -04001179 break;
1180 case BOTTOM_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001181 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE, 0);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001182 break;
1183 }
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001184 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001185}
1186
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001187-(CGRect) frameForExpendPreviewButton {
1188 CGPoint origin = CGPointMake(self.previewView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE,
1189 self.previewView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
1190 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001191}
1192
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001193# pragma mark -ChooseContactVCDelegate
1194
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001195-(void)callToContact:(const QString&)contactUri convUID:(const QString&)convID {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001196 if (brokerPopoverVC != nullptr) {
1197 [brokerPopoverVC performClose:self];
1198 brokerPopoverVC = NULL;
1199 }
1200 auto* callModel = accountInfo_->callModel.get();
1201 auto currentCall = callModel->getCall([self getcallID]);
1202 auto* convModel = accountInfo_->conversationModel.get();
1203 auto newCall = callModel->callAndAddParticipant(contactUri,
1204 [self getcallID],
1205 currentCall.isAudioOnly);
1206 [self addPreviewForContactUri:contactUri call: newCall];
1207}
1208
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001209-(void)joinCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001210 if (brokerPopoverVC != nullptr) {
1211 [brokerPopoverVC performClose:self];
1212 brokerPopoverVC = NULL;
1213 }
1214 auto* callModel = accountInfo_->callModel.get();
1215 callModel->joinCalls([self getcallID], callId);
1216}
1217
1218# pragma mark -CallInConferenceVCDelegate
1219
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001220-(void)removePreviewForContactUri:(const QString&)uri forCall:(const QString&) callId {
1221 NSMutableDictionary * calls = connectingCalls[callId.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001222 if (!calls) {
1223 return;
1224 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001225 NSViewController *callView = calls[uri.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001226 if (!callView) {
1227 return;
1228 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001229 calls[uri.toNSString()] = nil;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001230 [self.callingWidgetsContainer removeView:callView.view];
1231}
1232
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001233-(void)addPreviewForContactUri:(const QString&)uri call:(const QString&)callId {
1234 NSMutableDictionary *calls = connectingCalls[callUid_.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001235 if (!calls) {
1236 calls = [[NSMutableDictionary alloc] init];
1237 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001238 if (calls[uri.toNSString()]) {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001239 return;
1240 }
1241 CallInConferenceVC *callingView = [self callingViewForCallId: callId];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001242 calls[uri.toNSString()] = callingView;
1243 connectingCalls[callUid_.toNSString()] = calls;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001244 [self.callingWidgetsContainer addView:callingView.view inGravity:NSStackViewGravityBottom];
1245 [self.callingWidgetsContainer updateConstraints];
1246 [self.callingWidgetsContainer updateLayer];
1247 [self.callingWidgetsContainer setNeedsDisplay:YES];
1248}
1249
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001250-(CallInConferenceVC*) callingViewForCallId:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001251 CallInConferenceVC *callView = [[CallInConferenceVC alloc]
1252 initWithNibName:@"CallInConferenceVC"
1253 bundle:nil
1254 callId:callId
1255 accountInfo:accountInfo_];
1256 callView.delegate = self;
1257 callView.initiatorCallId = callUid_;
1258 return callView;
1259}
1260
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001261-(const QString&)getcallID {
1262 return confUid_.isEmpty() ? callUid_ : confUid_;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001263}
1264
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001265-(bool)isCurrentCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001266 return (callId == confUid_ || callId == callUid_);
1267}
1268
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001269#pragma mark ConferenceLayoutDelegate
1270
1271-(void)hangUpParticipant:(NSString*)uri {
1272 if (accountInfo_ == nil)
1273 return;
1274 auto* callModel = accountInfo_->callModel.get();
1275
1276 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1277 auto callId = convIt->callId;
1278 if (not callModel->hasCall(callId)){
1279 return;
1280 }
1281 callModel->hangUp(callId);
1282}
1283
1284-(void)minimizeParticipant {
1285 if (accountInfo_ == nil)
1286 return;
1287 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001288 auto confId = [self getcallID];
1289 if (not callModel->hasCall(confId)) {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001290 return;
1291 }
1292 try {
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001293 auto call = callModel->getCall(confId);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001294 switch (call.layout) {
1295 case lrc::api::call::Layout::GRID:
1296 break;
1297 case lrc::api::call::Layout::ONE_WITH_SMALL:
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001298 callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001299 break;
1300 case lrc::api::call::Layout::ONE:
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001301 callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001302 break;
1303 };
1304 } catch (...) {}
1305}
1306
1307-(int)getCurrentLayout {
1308 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001309 auto confId = [self getcallID];
1310 if (not callModel->hasCall(confId)){
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001311 return -1;
1312 }
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001313 return static_cast<int>(callModel->getCall(confId).layout);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001314}
1315
1316-(BOOL)isMasterCall {
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001317 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
1318 if (conv.uid.isEmpty()) {
1319 return false;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001320 }
1321 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001322 try {
1323 auto call = callModel->getCall(conv.callId);
1324 if (call.participantsInfos.size() == 0) {
1325 return true;
1326 }
1327 return !conv.confId.isEmpty() && callModel->hasCall(conv.confId);
1328 } catch (...) {}
1329 return true;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001330}
1331
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001332-(BOOL)isCallModerator {
1333 if (accountInfo_ == nil)
1334 return false;
1335 auto* callModel = accountInfo_->callModel.get();
1336 return callModel->isModerator([self getcallID]);
1337}
1338
1339
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001340-(void)maximizeParticipant:(NSString*)uri active:(BOOL)isActive {
1341 if (accountInfo_ == nil)
1342 return;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001343 BOOL localVideo = accountInfo_->profileInfo.uri == QString::fromNSString(uri);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001344 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001345 auto confId = [self getcallID];
1346 if (not callModel->hasCall(confId) && !localVideo)
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001347 return;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001348 try {
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001349 auto call = callModel->getCall(confId);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001350 switch (call.layout) {
1351 case lrc::api::call::Layout::GRID:
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001352 callModel->setActiveParticipant(confId, QString::fromNSString(uri));
1353 callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001354 break;
1355 case lrc::api::call::Layout::ONE_WITH_SMALL:
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001356 callModel->setActiveParticipant(confId, QString::fromNSString(uri));
1357 callModel->setConferenceLayout(confId,
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001358 isActive ? lrc::api::call::Layout::ONE : lrc::api::call::Layout::ONE_WITH_SMALL);
1359 break;
1360 case lrc::api::call::Layout::ONE:
Kateryna Kostiukead4ea12020-10-02 13:53:24 -04001361 callModel->setActiveParticipant(confId, QString::fromNSString(uri));
1362 callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001363 break;
1364 };
1365 } catch (...) {}
1366}
1367
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001368#pragma mark Popover delegate
1369
1370- (void)popoverWillClose:(NSNotification *)notification
1371{
1372 if (brokerPopoverVC != nullptr) {
1373 [brokerPopoverVC performClose:self];
1374 brokerPopoverVC = NULL;
1375 }
1376}
Alexandre Lisionc5148052015-03-04 15:10:35 -05001377@end