Show reconnecting notification when screensharing
This commit is contained in:
parent
de2def7119
commit
1280afe619
10 changed files with 227 additions and 35 deletions
|
@ -1813,10 +1813,22 @@
|
|||
"messageformat": "Click here to return to the call when you're ready to stop presenting.",
|
||||
"description": "Body text for the share screen notification"
|
||||
},
|
||||
"icu:calling__presenting--reconnecting--notification-title": {
|
||||
"messageformat": "Reconnecting...",
|
||||
"description": "Title for the share screen reconnecting notification"
|
||||
},
|
||||
"icu:calling__presenting--reconnecting--notification-body": {
|
||||
"messageformat": "Your connection was lost. Signal is reconnecting.",
|
||||
"description": "Body text for the share screen reconnecting notification"
|
||||
},
|
||||
"icu:calling__presenting--info": {
|
||||
"messageformat": "Signal is sharing {window}.",
|
||||
"description": "Text that appears in the screen sharing controller to inform person that they are presenting"
|
||||
},
|
||||
"icu:calling__presenting--reconnecting": {
|
||||
"messageformat": "Reconnecting...",
|
||||
"description": "Text that appears in the screen sharing controller to inform person that the call is in reconnecting state"
|
||||
},
|
||||
"icu:calling__presenting--stop": {
|
||||
"messageformat": "Stop sharing",
|
||||
"description": "Button for stopping screen sharing"
|
||||
|
|
18
app/main.ts
18
app/main.ts
|
@ -113,6 +113,7 @@ import { load as loadLocale } from './locale';
|
|||
|
||||
import type { LoggerType } from '../ts/types/Logging';
|
||||
import { HourCyclePreference } from '../ts/types/I18N';
|
||||
import { ScreenShareStatus } from '../ts/types/Calling';
|
||||
import { DBVersionFromFutureError } from '../ts/sql/migrations';
|
||||
import type { ParsedSignalRoute } from '../ts/util/signalRoutes';
|
||||
import { parseSignalRoute } from '../ts/util/signalRoutes';
|
||||
|
@ -2332,11 +2333,20 @@ ipc.on(
|
|||
}
|
||||
);
|
||||
|
||||
ipc.on('close-screen-share-controller', () => {
|
||||
if (screenShareWindow) {
|
||||
screenShareWindow.close();
|
||||
ipc.on(
|
||||
'screen-share:status-change',
|
||||
(_event: Electron.Event, status: ScreenShareStatus) => {
|
||||
if (!screenShareWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === ScreenShareStatus.Disconnected) {
|
||||
screenShareWindow.close();
|
||||
} else {
|
||||
screenShareWindow.webContents.send('status-change', status);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
ipc.on('stop-screen-share', () => {
|
||||
if (mainWindow) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { PropsType } from './CallingScreenSharingController';
|
|||
import { CallingScreenSharingController } from './CallingScreenSharingController';
|
||||
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { ScreenShareStatus } from '../types/Calling';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
@ -18,6 +19,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
onCloseController: action('on-close-controller'),
|
||||
onStopSharing: action('on-stop-sharing'),
|
||||
presentedSourceName: overrideProps.presentedSourceName || 'Application',
|
||||
status: overrideProps.status || ScreenShareStatus.Connected,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
@ -38,3 +40,13 @@ export function ReallyLongAppName(): JSX.Element {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Reconnecting(): JSX.Element {
|
||||
return (
|
||||
<CallingScreenSharingController
|
||||
{...createProps({
|
||||
status: ScreenShareStatus.Reconnecting,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
import React from 'react';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { ScreenShareStatus } from '../types/Calling';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
onCloseController: () => unknown;
|
||||
onStopSharing: () => unknown;
|
||||
status: ScreenShareStatus;
|
||||
presentedSourceName: string;
|
||||
};
|
||||
|
||||
|
@ -16,15 +18,22 @@ export function CallingScreenSharingController({
|
|||
i18n,
|
||||
onCloseController,
|
||||
onStopSharing,
|
||||
status,
|
||||
presentedSourceName,
|
||||
}: PropsType): JSX.Element {
|
||||
let text: string;
|
||||
|
||||
if (status === ScreenShareStatus.Reconnecting) {
|
||||
text = i18n('icu:calling__presenting--reconnecting');
|
||||
} else {
|
||||
text = i18n('icu:calling__presenting--info', {
|
||||
window: presentedSourceName,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-CallingScreenSharingController">
|
||||
<div className="module-CallingScreenSharingController__text">
|
||||
{i18n('icu:calling__presenting--info', {
|
||||
window: presentedSourceName,
|
||||
})}
|
||||
</div>
|
||||
<div className="module-CallingScreenSharingController__text">{text}</div>
|
||||
<div className="module-CallingScreenSharingController__buttons">
|
||||
<Button
|
||||
className="module-CallingScreenSharingController__button"
|
||||
|
|
|
@ -64,6 +64,7 @@ import {
|
|||
CallMode,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
ScreenShareStatus,
|
||||
} from '../types/Calling';
|
||||
import {
|
||||
findBestMatchingAudioDeviceIndex,
|
||||
|
@ -310,6 +311,22 @@ function protoToCallingMessage({
|
|||
};
|
||||
}
|
||||
|
||||
export type NotifyScreenShareStatusOptionsType = Readonly<
|
||||
{
|
||||
conversationId?: string;
|
||||
isPresenting: boolean;
|
||||
} & (
|
||||
| {
|
||||
callMode: CallMode.Direct;
|
||||
callState: CallState;
|
||||
}
|
||||
| {
|
||||
callMode: CallMode.Group | CallMode.Adhoc;
|
||||
connectionState: GroupCallConnectionState;
|
||||
}
|
||||
)
|
||||
>;
|
||||
|
||||
export class CallingClass {
|
||||
readonly videoCapturer: GumVideoCapturer;
|
||||
|
||||
|
@ -1610,7 +1627,10 @@ export class CallingClass {
|
|||
);
|
||||
}
|
||||
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
ipcRenderer.send(
|
||||
'screen-share:status-change',
|
||||
ScreenShareStatus.Disconnected
|
||||
);
|
||||
|
||||
const entries = Object.entries(this.callsLookup);
|
||||
log.info(`hangup: ${entries.length} call(s) to hang up...`);
|
||||
|
@ -1785,10 +1805,84 @@ export class CallingClass {
|
|||
title: window.i18n('icu:calling__presenting--notification-title'),
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
ipcRenderer.send(
|
||||
'screen-share:status-change',
|
||||
ScreenShareStatus.Disconnected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async notifyScreenShareStatus(
|
||||
options: NotifyScreenShareStatusOptionsType
|
||||
): Promise<void> {
|
||||
let newStatus: ScreenShareStatus;
|
||||
if (options.callMode === CallMode.Direct) {
|
||||
switch (options.callState) {
|
||||
case CallState.Prering:
|
||||
case CallState.Ringing:
|
||||
case CallState.Accepted:
|
||||
newStatus = ScreenShareStatus.Connected;
|
||||
break;
|
||||
case CallState.Reconnecting:
|
||||
newStatus = ScreenShareStatus.Reconnecting;
|
||||
break;
|
||||
case CallState.Ended:
|
||||
newStatus = ScreenShareStatus.Disconnected;
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(options.callState);
|
||||
}
|
||||
} else {
|
||||
switch (options.connectionState) {
|
||||
case GroupCallConnectionState.NotConnected:
|
||||
newStatus = ScreenShareStatus.Disconnected;
|
||||
break;
|
||||
case GroupCallConnectionState.Connecting:
|
||||
case GroupCallConnectionState.Connected:
|
||||
newStatus = ScreenShareStatus.Connected;
|
||||
break;
|
||||
case GroupCallConnectionState.Reconnecting:
|
||||
newStatus = ScreenShareStatus.Reconnecting;
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(options.connectionState);
|
||||
}
|
||||
}
|
||||
|
||||
const { conversationId, isPresenting } = options;
|
||||
|
||||
if (
|
||||
options.callMode !== CallMode.Adhoc &&
|
||||
isPresenting &&
|
||||
conversationId &&
|
||||
newStatus === ScreenShareStatus.Reconnecting
|
||||
) {
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
strictAssert(
|
||||
conversation,
|
||||
'showPresentingReconnectingNotification: conversation not found'
|
||||
);
|
||||
|
||||
const { url, absolutePath } = await conversation.getAvatarOrIdenticon();
|
||||
|
||||
notificationService.notify({
|
||||
conversationId,
|
||||
iconPath: absolutePath,
|
||||
iconUrl: url,
|
||||
message: window.i18n(
|
||||
'icu:calling__presenting--reconnecting--notification-body'
|
||||
),
|
||||
type: NotificationType.IsPresenting,
|
||||
sentAt: 0,
|
||||
silent: true,
|
||||
title: window.i18n(
|
||||
'icu:calling__presenting--reconnecting--notification-title'
|
||||
),
|
||||
});
|
||||
}
|
||||
ipcRenderer.send('screen-share:status-change', newStatus);
|
||||
}
|
||||
|
||||
private async startDeviceReselectionTimer(): Promise<void> {
|
||||
// Poll once
|
||||
await this.pollForMediaDevices();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
hasScreenCapturePermission,
|
||||
|
@ -1013,10 +1012,6 @@ function callStateChange(
|
|||
return async dispatch => {
|
||||
const { callState, acceptedTime, callEndedReason } = payload;
|
||||
|
||||
if (callState === CallState.Ended) {
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
}
|
||||
|
||||
const wasAccepted = acceptedTime != null;
|
||||
const isEnded = callState === CallState.Ended && callEndedReason != null;
|
||||
|
||||
|
@ -1304,10 +1299,6 @@ function groupCallStateChange(
|
|||
if (didSomeoneStartPresenting) {
|
||||
void callingTones.someonePresenting();
|
||||
}
|
||||
|
||||
if (payload.connectionState === GroupCallConnectionState.NotConnected) {
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2630,6 +2621,25 @@ export function reducer(
|
|||
}
|
||||
|
||||
if (action.type === CALL_STATE_CHANGE_FULFILLED) {
|
||||
const call = getOwn(
|
||||
state.callsByConversation,
|
||||
action.payload.conversationId
|
||||
);
|
||||
|
||||
if (
|
||||
call?.callMode === CallMode.Direct &&
|
||||
call?.callState !== action.payload.callState
|
||||
) {
|
||||
drop(
|
||||
calling.notifyScreenShareStatus({
|
||||
callMode: CallMode.Direct,
|
||||
callState: action.payload.callState,
|
||||
isPresenting: state.activeCallState?.presentingSource != null,
|
||||
conversationId: state.activeCallState?.conversationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// We want to keep the state around for ended calls if they resulted in a message
|
||||
// request so we can show the "needs permission" screen.
|
||||
if (
|
||||
|
@ -2640,10 +2650,6 @@ export function reducer(
|
|||
return removeConversationFromState(state, action.payload.conversationId);
|
||||
}
|
||||
|
||||
const call = getOwn(
|
||||
state.callsByConversation,
|
||||
action.payload.conversationId
|
||||
);
|
||||
if (call?.callMode !== CallMode.Direct) {
|
||||
log.warn('Cannot update state for a non-direct call');
|
||||
return state;
|
||||
|
@ -2830,6 +2836,17 @@ export function reducer(
|
|||
...newRingState,
|
||||
};
|
||||
|
||||
if (existingCall?.connectionState !== connectionState) {
|
||||
drop(
|
||||
calling.notifyScreenShareStatus({
|
||||
callMode,
|
||||
connectionState,
|
||||
isPresenting: state.activeCallState?.presentingSource != null,
|
||||
conversationId: state.activeCallState?.conversationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...mergeCallWithGroupCallLookups({
|
||||
|
|
|
@ -205,3 +205,9 @@ export type ChangeIODevicePayloadType =
|
|||
export type CallingConversationType =
|
||||
| ConversationType
|
||||
| CallLinkConversationType;
|
||||
|
||||
export enum ScreenShareStatus {
|
||||
Connected = 'Connected',
|
||||
Reconnecting = 'Reconnecting',
|
||||
Disconnected = 'Disconnected',
|
||||
}
|
||||
|
|
3
ts/window.d.ts
vendored
3
ts/window.d.ts
vendored
|
@ -39,6 +39,7 @@ import type { BatcherType } from './util/batcher';
|
|||
import type { ConfirmationDialog } from './components/ConfirmationDialog';
|
||||
import type { SignalProtocolStore } from './SignalProtocolStore';
|
||||
import type { SocketStatus } from './types/SocketStatus';
|
||||
import type { ScreenShareStatus } from './types/Calling';
|
||||
import type SyncRequest from './textsecure/SyncRequest';
|
||||
import type { MessageCache } from './services/MessageCache';
|
||||
import type { StateType } from './state/reducer';
|
||||
|
@ -122,6 +123,8 @@ type PermissionsWindowPropsType = {
|
|||
type ScreenShareWindowPropsType = {
|
||||
onStopSharing: () => void;
|
||||
presentedSourceName: string;
|
||||
getStatus: () => ScreenShareStatus;
|
||||
setRenderCallback: (cb: () => void) => void;
|
||||
};
|
||||
|
||||
type SettingsOnRenderCallbackType = (props: PreferencesPropsType) => void;
|
||||
|
|
|
@ -7,6 +7,7 @@ import ReactDOM from 'react-dom';
|
|||
import { CallingScreenSharingController } from '../../components/CallingScreenSharingController';
|
||||
import { i18n } from '../sandboxedInit';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { drop } from '../../util/drop';
|
||||
import { parseEnvironment, setEnvironment } from '../../environment';
|
||||
|
||||
const { ScreenShareWindowProps } = window.Signal;
|
||||
|
@ -18,15 +19,27 @@ setEnvironment(
|
|||
window.SignalContext.isTestOrMockEnvironment()
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<div className="App dark-theme">
|
||||
<CallingScreenSharingController
|
||||
i18n={i18n}
|
||||
onCloseController={() => window.SignalContext.executeMenuRole('close')}
|
||||
onStopSharing={ScreenShareWindowProps.onStopSharing}
|
||||
presentedSourceName={ScreenShareWindowProps.presentedSourceName}
|
||||
/>
|
||||
</div>,
|
||||
function onCloseController(): void {
|
||||
drop(window.SignalContext.executeMenuRole('close'));
|
||||
}
|
||||
|
||||
document.getElementById('app')
|
||||
);
|
||||
function render() {
|
||||
// Pacify typescript
|
||||
strictAssert(ScreenShareWindowProps, 'window values not provided');
|
||||
|
||||
ReactDOM.render(
|
||||
<div className="App dark-theme">
|
||||
<CallingScreenSharingController
|
||||
i18n={i18n}
|
||||
onCloseController={onCloseController}
|
||||
onStopSharing={ScreenShareWindowProps.onStopSharing}
|
||||
status={ScreenShareWindowProps.getStatus()}
|
||||
presentedSourceName={ScreenShareWindowProps.presentedSourceName}
|
||||
/>
|
||||
</div>,
|
||||
|
||||
document.getElementById('app')
|
||||
);
|
||||
}
|
||||
render();
|
||||
ScreenShareWindowProps.setRenderCallback(render);
|
||||
|
|
|
@ -2,17 +2,33 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import { ScreenShareStatus } from '../../types/Calling';
|
||||
import { MinimalSignalContext } from '../minimalContext';
|
||||
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
|
||||
let renderCallback: undefined | (() => undefined);
|
||||
|
||||
let status = ScreenShareStatus.Connected;
|
||||
|
||||
const Signal = {
|
||||
ScreenShareWindowProps: {
|
||||
onStopSharing: () => {
|
||||
ipcRenderer.send('stop-screen-share');
|
||||
},
|
||||
presentedSourceName: params.get('sourceName'),
|
||||
getStatus() {
|
||||
return status;
|
||||
},
|
||||
setRenderCallback(callback: () => undefined) {
|
||||
renderCallback = callback;
|
||||
},
|
||||
},
|
||||
};
|
||||
contextBridge.exposeInMainWorld('Signal', Signal);
|
||||
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);
|
||||
|
||||
ipcRenderer.on('status-change', (_, newStatus: ScreenShareStatus) => {
|
||||
status = newStatus;
|
||||
renderCallback?.();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue