diff --git a/apps/webapp/src/script/Config.ts b/apps/webapp/src/script/Config.ts index 3fe38d6ffbf..6ba66b8b71b 100644 --- a/apps/webapp/src/script/Config.ts +++ b/apps/webapp/src/script/Config.ts @@ -119,6 +119,13 @@ const Config = { return window.desktopAppConfig; }, + getDesktopSettings: () => { + if (!Runtime.isDesktopApp) { + return undefined; + } + + return window.desktopAppSettings; + }, }; export {Config}; diff --git a/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/CallOptions.tsx b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/CallOptions.tsx index fe7ece960ef..4c28368a9cf 100644 --- a/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/CallOptions.tsx +++ b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/CallOptions.tsx @@ -22,6 +22,7 @@ import {ChangeEvent, useCallback, useEffect, useRef, useState} from 'react'; import type {WebappProperties} from '@wireapp/api-client/lib/user/data/'; import {amplify} from 'amplify'; +import {Runtime} from '@wireapp/commons'; import {Checkbox, CheckboxLabel} from '@wireapp/react-ui-kit'; import {WebAppEvents} from '@wireapp/webapp-events'; @@ -30,6 +31,8 @@ import type {PropertiesRepository} from 'Repositories/properties/PropertiesRepos import {PROPERTIES_TYPE} from 'Repositories/properties/PropertiesType'; import {t} from 'Util/LocalizerUtil'; +import {HardwareAccelerationRestartModal} from './HardwareAccelerationRestartModal'; + import {Config} from '../../../../../Config'; import {PreferencesSection} from '../components/PreferencesSection'; @@ -54,6 +57,21 @@ const CallOptions = ({constraintsHandler, propertiesRepository}: CallOptionsProp !!propertiesRepository.properties.settings.call.enable_press_space_to_unmute, ); + const desktopSettings = Config.getDesktopSettings(); + + const isHardwareAccelerationChangeable = Runtime.isDesktopApp() && !!desktopSettings; + + const [showHwRestartModal, setShowHwRestartModal] = useState(false); + const [pendingHwValue, setPendingHwValue] = useState(null); + + const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(() => { + if (!isHardwareAccelerationChangeable) { + return true; // default in browser (but not changeable) + } + + return desktopSettings.isHardwareAccelerationEnabled(); + }); + useEffect(() => { const updateProperties = ({settings}: WebappProperties) => { setVbrEncoding(!isCbrEncodingEnforced && settings.call.enable_vbr_encoding); @@ -102,6 +120,32 @@ const CallOptions = ({constraintsHandler, propertiesRepository}: CallOptionsProp [propertiesRepository], ); + const handleHardwareAccelerationChange = useCallback((event: ChangeEvent) => { + const isChecked = event.target.checked; + + setPendingHwValue(isChecked); + setShowHwRestartModal(true); + }, []); + + const confirmHardwareAccelerationChange = () => { + if (!desktopSettings || pendingHwValue === null) { + setShowHwRestartModal(false); + return; + } + + desktopSettings.setHardwareAccelerationEnabled(pendingHwValue); + setHardwareAccelerationEnabled(pendingHwValue); + + setShowHwRestartModal(false); + + amplify.publish(WebAppEvents.LIFECYCLE.RESTART); + }; + + const cancelHardwareAccelerationChange = () => { + setShowHwRestartModal(false); + setPendingHwValue(null); + }; + return (
@@ -153,6 +197,29 @@ const CallOptions = ({constraintsHandler, propertiesRepository}: CallOptionsProp

)} + + {isHardwareAccelerationChangeable && ( +
+ + + {t('preferencesOptionsEnableHardwareAcceleration')} + + +

+ {t('preferencesOptionsEnableHardwareAccelerationDetails')} +

+
+ )} + +
); }; diff --git a/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/HardwareAccelerationRestartModal.tsx b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/HardwareAccelerationRestartModal.tsx new file mode 100644 index 00000000000..74a6f0b28b7 --- /dev/null +++ b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/HardwareAccelerationRestartModal.tsx @@ -0,0 +1,47 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {ModalComponent} from 'Components/Modals/ModalComponent'; +import {t} from 'Util/LocalizerUtil'; + +interface HardwareAccelerationRestartModalProps { + isShown: boolean; + onCancel: () => void; + onConfirm: () => void; +} + +const HardwareAccelerationRestartModal = ({isShown, onCancel, onConfirm}: HardwareAccelerationRestartModalProps) => { + return ( + +
+

{t('preferencesOptionsEnableHardwareAccelerationModalTitle')}

+ +

{t('preferencesOptionsEnableHardwareAccelerationModalMessage')}

+ +
+ + + +
+
+
+ ); +}; + +export {HardwareAccelerationRestartModal}; diff --git a/apps/webapp/src/script/repositories/conversation/linkPreviews/index.ts b/apps/webapp/src/script/repositories/conversation/linkPreviews/index.ts index 0bd47a65e2d..55c64849c95 100644 --- a/apps/webapp/src/script/repositories/conversation/linkPreviews/index.ts +++ b/apps/webapp/src/script/repositories/conversation/linkPreviews/index.ts @@ -52,6 +52,10 @@ declare global { interface Window { openGraphAsync?: (url: string) => Promise; desktopAppConfig?: {version: string; supportsCallingPopoutWindow?: boolean}; + desktopAppSettings?: { + setHardwareAccelerationEnabled: (enabled: boolean) => void; + isHardwareAccelerationEnabled: () => boolean; + }; } } const logger = getLogger('LinkPreviewRepository'); diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index 25a858c12dd..c445c73e62d 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -717,13 +717,6 @@ declare module 'I18n/en-US.json' { 'conversationFileUploadOverlayDescription': `Drag & drop to add files`; 'conversationFileUploadOverlayTitle': `Upload files`; 'conversationFileVideoPreviewLabel': `Video file preview for: {src}`; - 'conversationFilterDrafts': `Drafts`; - 'conversationFilterMentions': `Mentions`; - 'conversationFilterNone': `No filter`; - 'conversationFilterPings': `Pings`; - 'conversationFilterReplies': `Replies`; - 'conversationFilterTooltip': `Filter conversations`; - 'conversationFilterUnread': `Unread`; 'conversationFoldersEmptyText': `Add your conversations to folders to stay organized.`; 'conversationFoldersEmptyTextLearnMore': `Learn more`; 'conversationFooterArchive': `Archive`; @@ -1692,6 +1685,12 @@ declare module 'I18n/en-US.json' { 'preferencesOptionsEmojiReplaceDetail': `:-) → [icon]`; 'preferencesOptionsEnableAgcCheckbox': `Automatic gain control (AGC)`; 'preferencesOptionsEnableAgcDetails': `Enable to allow your microphone volume to be adjusted automatically to ensure all participants in a call are heard with similar and comfortable loudness.`; + 'preferencesOptionsEnableHardwareAcceleration': `Enable Hardware Acceleration (Recommended)`; + 'preferencesOptionsEnableHardwareAccelerationDetails': `When enabled, hardware acceleration allows the webcam to use your device’s GPU to improve video performance and reduce CPU usage. This can result in smoother video playback, better responsiveness, and improved overall stability.\nThis option is enabled by default and is recommended for most systems. Disable it only if you experience compatibility or display issues.\nA restart of the application is required for changes to take effect.`; + 'preferencesOptionsEnableHardwareAccelerationModalTitle': `Restart required`; + 'preferencesOptionsEnableHardwareAccelerationModalMessage': `Changing the hardware acceleration setting requires a restart of the application. The app will close and reopen automatically. Do you want to restart now?`; + 'preferencesOptionsEnableHardwareAccelerationModalCancel': `Cancel`; + 'preferencesOptionsEnableHardwareAccelerationModalOk': `Restart now`; 'preferencesOptionsEnablePressSpaceToUnmute': `Unmute with space bar`; 'preferencesOptionsEnablePressSpaceToUnmuteDetails': `Enable to unmute your microphone by pressing and holding the space bar as long as you want to speak. You can use this option in full view.`; 'preferencesOptionsEnableSoundlessIncomingCalls': `Silence other calls`; @@ -1798,6 +1797,7 @@ declare module 'I18n/en-US.json' { 'searchCreateGroup': `Create group`; 'searchCreateGuestRoom': `Create guest room`; 'searchDirectConversations': `Search 1:1 conversations`; + 'searchDraftsConversations': `Search in drafts`; 'searchFavoriteConversations': `Search favorites`; 'searchFederatedDomainNotAvailable': `The federated domain is currently not available.`; 'searchFederatedDomainNotAvailableLearnMore': `Learn more`; @@ -1816,6 +1816,7 @@ declare module 'I18n/en-US.json' { 'searchManageServices': `Manage Apps`; 'searchManageServicesNoResults': `Manage apps`; 'searchMemberInvite': `Invite people to join the team`; + 'searchMentionsConversations': `Search in mentions`; 'searchNoContactsOnWire': `You have no contacts on {brandName}.\nTry finding people by\nname or username.`; 'searchNoMatchesPartner': `No results`; 'searchNoServicesManager': `Apps are helpers that can improve your workflow.`; @@ -1826,6 +1827,8 @@ declare module 'I18n/en-US.json' { 'searchPeople': `People`; 'searchPeopleOnlyPlaceholder': `Search people`; 'searchPeoplePlaceholder': `Search for people and conversations`; + 'searchPingsConversations': `Search in pings`; + 'searchRepliesConversations': `Search in replies`; 'searchServiceConfirmButton': `Open Conversation`; 'searchServicePlaceholder': `Search by name`; 'searchServices': `Apps`; @@ -1835,6 +1838,7 @@ declare module 'I18n/en-US.json' { 'searchTrySearch': `Find people by\nname or username`; 'searchTrySearchFederation': `Find people in Wire by name or\n@username\n\nFind people from another domain\nby @username@domainname`; 'searchTrySearchLearnMore': `Learn more`; + 'searchUnreadConversations': `Search in unread`; 'selectAccountTypeHeading': `How will you use Wire?`; 'selectPersonalAccountTypeOptionButtonText': `Create Personal Account`; 'selectPersonalAccountTypeOptionDescription': `Chat with friends and family.`; @@ -1885,6 +1889,8 @@ declare module 'I18n/en-US.json' { 'success.openWebAppText': `Open Wire for web`; 'success.subheader': `What do you want to do next?`; 'systemMessageLearnMore': `Learn more`; + 'tabsFilterHeader': `Show filters`; + 'tabsFilterTooltip': `Customize visible tabs`; 'takeoverButtonChoose': `Choose your own`; 'takeoverButtonKeep': `Keep this one`; 'takeoverLink': `Learn more`;