diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..5bf41995 Binary files /dev/null and b/.DS_Store differ diff --git a/app/browser/index.js b/app/browser/index.js index 6b01d90f..757de85f 100644 --- a/app/browser/index.js +++ b/app/browser/index.js @@ -57,6 +57,8 @@ function initializeModules(config, ipcRenderer) { require('./tools/zoom').init(config); require('./tools/shortcuts').init(config); require('./tools/chromeApi')(config); + require('./tools/mutationTitle').init(config); + require('./tools/trayIconRenderer').init(config, ipcRenderer); require('./tools/settings').init(config, ipcRenderer); require('./tools/customBackgrounds')(config, ipcRenderer); require('./tools/theme').init(config, ipcRenderer); diff --git a/app/browser/notifications/activityManager.js b/app/browser/notifications/activityManager.js index 384c6710..10d42690 100644 --- a/app/browser/notifications/activityManager.js +++ b/app/browser/notifications/activityManager.js @@ -1,4 +1,3 @@ -const TrayIconRenderer = require('../tools/trayIconRenderer'); const activityHub = require('../tools/activityHub'); const wakeLock = require('../tools/wakeLock'); @@ -9,21 +8,10 @@ class ActivityManager { */ constructor(ipcRenderer, config) { this.ipcRenderer = ipcRenderer; - this.iconRenderer = new TrayIconRenderer(config); this.config = config; this.myStatus = -1; } - updateActivityCount(count) { - this.iconRenderer.render(count).then(icon => { - this.ipcRenderer.send('tray-update', { - icon: icon, - flash: (count > 0 && !this.config.disableNotificationWindowFlash) - }); - }); - this.ipcRenderer.invoke('set-badge-count', count); - } - start() { setActivityHandlers(this); setEventHandlers(this); @@ -51,9 +39,6 @@ class ActivityManager { } } -/** - * @param {ActivityManager} self - */ function setActivityHandlers(self) { activityHub.on('activities-count-updated', updateActivityCountHandler(self)); activityHub.on('incoming-call-created', incomingCallCreatedHandler(self)); @@ -65,71 +50,48 @@ function setActivityHandlers(self) { activityHub.on('my-status-changed', myStatusChangedHandler(self)); } -/** - * @param {ActivityManager} self - */ function setEventHandlers(self) { self.ipcRenderer.on('enable-wakelock', () => wakeLock.enable()); self.ipcRenderer.on('disable-wakelock', () => wakeLock.disable()); } -/** - * @param {ActivityManager} self - */ -function updateActivityCountHandler(self) { +function updateActivityCountHandler() { return async (data) => { - self.updateActivityCount(data.count); + const event = new CustomEvent('unread-count', { detail: { number: data.count } }); + window.dispatchEvent(event); }; } -/** - * @param {ActivityManager} self - */ function incomingCallCreatedHandler(self) { return async (data) => { self.ipcRenderer.invoke('incoming-call-created', data); }; } -/** - * @param {ActivityManager} self - */ function incomingCallConnectingHandler(self) { return async () => { self.ipcRenderer.invoke('incoming-call-connecting'); }; } -/** - * @param {ActivityManager} self - */ function incomingCallDisconnectingHandler(self) { return async () => { self.ipcRenderer.invoke('incoming-call-disconnecting'); }; } -/** - * @param {ActivityManager} self - */ function callConnectedHandler(self) { return async () => { self.ipcRenderer.invoke('call-connected'); }; } -/** - * @param {ActivityManager} self - */ function callDisconnectedHandler(self) { return async () => { self.ipcRenderer.invoke('call-disconnected'); }; } -/** - * @param {ActivityManager} self - */ // eslint-disable-next-line no-unused-vars function meetingStartNotifyHandler(self) { if (!self.config.disableMeetingNotifications) { @@ -142,9 +104,6 @@ function meetingStartNotifyHandler(self) { return null; } -/** - * @param {ActivityManager} self - */ // eslint-disable-next-line no-unused-vars function myStatusChangedHandler(self) { // eslint-disable-next-line no-unused-vars diff --git a/app/browser/tools/mutationTitle.js b/app/browser/tools/mutationTitle.js new file mode 100644 index 00000000..e36c580e --- /dev/null +++ b/app/browser/tools/mutationTitle.js @@ -0,0 +1,32 @@ +class MutationObserverTitle { + + init(config) { + if (config.useMutationTitleLogic) { + console.log('MutationObserverTitle enabled'); + window.addEventListener('DOMContentLoaded', this._applyMutationToTitleLogic); + } + } + + _applyMutationToTitleLogic() { + console.log('Appliying MutationObserverTitle logic'); + const observer = new window.MutationObserver( + () => { + console.log('title changed'); + console.log(window.document.title); + const regex = /^\((\d+)\)/; + const match = window.document.title.match(regex); + if (match) { + const number = match[1]; + console.log(number); + const event = new CustomEvent('unread-count', { detail: { number: number } }); + window.dispatchEvent(event); + } + } + ); + observer.observe(window.document.querySelector('title'),{ childList: true }); + console.log('MutationObserverTitle logic applied'); + } + +} + +exports = module.exports = new MutationObserverTitle(); diff --git a/app/browser/tools/reactHandler.js b/app/browser/tools/reactHandler.js index 3ebc6ea2..c9b6d26d 100644 --- a/app/browser/tools/reactHandler.js +++ b/app/browser/tools/reactHandler.js @@ -1,19 +1,21 @@ class ReactHandler { - - _getTeams2ReactElement() { - return document.getElementById('app'); + getTeams2IdleTracker() { + const teams2CoreServices = this._getTeams2CoreServices(); + return teams2CoreServices?.clientState?._idleTracker; } - _getTeams2CoreServices() { - return this._getTeams2ReactElement()?._reactRootContainer?._internalRoot?.current?.updateQueue?.baseState?.element?.props?.coreServices; + getTeams2ClientPreferences() { + const teams2CoreServices = this._getTeams2CoreServices(); + return teams2CoreServices?.clientPreferences?.clientPreferences; } - getTeams2IdleTracker() { - return this._getTeams2CoreServices()?.clientState?._idleTracker; + _getTeams2ReactElement() { + return document.getElementById('app'); } - getTeams2ClientPreferences() { - return this._getTeams2CoreServices()?.clientPreferences?.clientPreferences; + _getTeams2CoreServices() { + const reactElement = this._getTeams2ReactElement(); + return reactElement?._reactRootContainer?._internalRoot?.current?.updateQueue?.baseState?.element?.props?.coreServices; } } diff --git a/app/browser/tools/theme.js b/app/browser/tools/theme.js index 96a0b60d..f9e76780 100644 --- a/app/browser/tools/theme.js +++ b/app/browser/tools/theme.js @@ -2,41 +2,37 @@ const ReactHandler = require('./reactHandler'); const instance = require('./instance'); class ThemeManager { - /** - * @param {object} config - * @param {Electron.IpcRenderer} ipcRenderer - */ - init(config, ipcRenderer) { - this.ipcRenderer = ipcRenderer; - this.config = config; + init(config, ipcRenderer) { + this.ipcRenderer = ipcRenderer; + this.config = config; - const clientPreferences = ReactHandler.getTeams2ClientPreferences(); - if (clientPreferences) { - console.log('Using react to set the follow system theme'); - ReactHandler.getTeams2ClientPreferences().theme.followOsTheme = config.followSystemTheme; - } + const clientPreferences = ReactHandler.getTeams2ClientPreferences(); + if (clientPreferences) { + console.log('Using react to set the follow system theme'); + ReactHandler.getTeams2ClientPreferences().theme.followOsTheme = config.followSystemTheme; + } - if (config.followSystemTheme) { - console.log('followSystemTheme', config.followSystemTheme); - this.ipcRenderer.on('system-theme-changed', this.applyTheme); - } - } + if (config.followSystemTheme) { + console.log('followSystemTheme', config.followSystemTheme); + this.ipcRenderer.on('system-theme-changed', this.applyTheme); + } + } - applyTheme = async (event, ...args) => { - const theme = args[0] ? 'dark' : 'default'; - const clientPreferences = ReactHandler.getTeams2ClientPreferences(); - if (clientPreferences) { - console.log('Using react to set the theme'); - clientPreferences.theme.userTheme = theme; - console.log('Theme changed to', theme); - } else { - console.log('Using angular to set the theme'); - const inst = await instance.whenReady().catch(() => { - console.error('Failed to apply Theme'); - }); - inst.controller.layoutService.setTheme(theme); - } - } + async applyTheme (event, ...args) { + const theme = args[0] ? 'dark' : 'default'; + const clientPreferences = ReactHandler.getTeams2ClientPreferences(); + if (clientPreferences) { + console.log('Using react to set the theme'); + clientPreferences.theme.userTheme = theme; + console.log('Theme changed to', theme); + } else { + console.log('Using angular to set the theme'); + const inst = await instance.whenReady().catch(() => { + console.error('Failed to apply Theme'); + }); + inst.controller.layoutService.setTheme(theme); + } + } } module.exports = new ThemeManager(); diff --git a/app/browser/tools/trayIconRenderer.js b/app/browser/tools/trayIconRenderer.js index 447d1198..085bfaba 100644 --- a/app/browser/tools/trayIconRenderer.js +++ b/app/browser/tools/trayIconRenderer.js @@ -1,10 +1,25 @@ const { nativeImage } = require('electron'); const TrayIconChooser = require('./trayIconChooser'); class TrayIconRenderer { - constructor(config) { + init(config, ipcRenderer) { + this.ipcRenderer = ipcRenderer; + this.config = config; const iconChooser = new TrayIconChooser(config); this.baseIcon = nativeImage.createFromPath(iconChooser.getFile()); this.iconSize = this.baseIcon.getSize(); + window.addEventListener('unread-count', this.updateActivityCount.bind(this)); + } + + updateActivityCount(event) { + const count = event.detail.number; + this.render(count).then(icon => { + console.log('sending tray-update'); + this.ipcRenderer.send('tray-update', { + icon: icon, + flash: (count > 0 && !this.config.disableNotificationWindowFlash) + }); + }); + this.ipcRenderer.invoke('set-badge-count', count); } render(newActivityCount) { @@ -57,4 +72,4 @@ class TrayIconRenderer { } } -module.exports = exports = TrayIconRenderer; \ No newline at end of file +module.exports = exports = new TrayIconRenderer(); \ No newline at end of file diff --git a/app/config/README.md b/app/config/README.md index 543c6c3b..2364479c 100644 --- a/app/config/README.md +++ b/app/config/README.md @@ -8,51 +8,53 @@ The application uses [yargs](https://www.npmjs.com/package/yargs) to allow comma Here is the list of available arguments and its usage: -| Option | Usage | Default Value | -|:--------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------:| -| appActiveCheckInterval | A numeric value in seconds as poll interval to check if the system is active from being idle | 2 | -| appIcon | Teams app icon to show in the tray | | -| appIconType | Type of tray icon to be used default/light/dark | default | -| appIdleTimeout | A numeric value in seconds as duration before app considers the system as idle | 300 | -| appIdleTimeoutCheckInterval | A numeric value in seconds as poll interval to check if the appIdleTimeout is reached | 10 | -| appLogLevels | Comma separated list of log levels (error,warn,info,debug) | error,warn | -| appTitle | A text to be suffixed with page title | Microsoft Teams | -| authServerWhitelist | set auth-server-whitelist value | * | -| chromeUserAgent | user agent string for chrome | Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3831.6 Safari/537.36 | -| customBGServiceBaseUrl | Base URL of the server which provides custom background images | http://localhost | -| customBGServiceConfigFetchInterval | A numeric value in seconds as poll interval to download custom background service configuration. If 0, it will be downloaded only at application start | 0 | -| customBGServiceIgnoreMSDefaults | A flag indicates whether to ignore Microsoft provided images or not | false | -| customCACertsFingerprints | custom CA Certs Fingerprints to allow SSL unrecognized signer or self signed certificate (see below) | [] | -| customCSSName | Custom CSS name for the packaged available css files. Currently those are: "compactDark", "compactLight", "tweaks", "condensedDark" and "condensedLight" | | -| customCSSLocation | Location for custom CSS styles | | -| customUserDir | Custom User Directory so that you can have multiple profiles | | -| clearStorage | Whether to clear the storage before creating the window or not | false | -| clientCertPath clientCertPassword | custom Client Certs for corporate authentication (certificate must be in pkcs12 format) | [] | -| closeAppOnCross | Close the app when clicking the close (X) cross | false | -| config | config file location | ~/.config/teams-for-linux/config.json | -| defaultURLHandler | Default application to be used to open the HTTP URLs | | -| disableAutogain | A flag indicates whether to disable mic auto gain or not | false | -| disableGpu | A flag to disable GPU and hardware acceleration (can be useful if the window remains blank) | false | -| disableMeetingNotifications | Whether to disable meeting notifications or not | false | -| disableNotifications | A flag to disable all notifications | false | -| disableNotificationSound | Disable chat/meeting start notification sound | false | -| disableNotificationSoundIfNotAvailable | Disable chat/meeting start notification sound if status is not Available (e.g. busy, in a call) | true | -| disableNotificationWindowFlash | A flag indicates whether to disable window flashing when there is a notification | false | -| followSystemTheme | A flag to enable automatic app theme switching (Default vs Dark) based on the current system theme | false | -| help | show the available commands | false | -| optInTeamsV2 | Set this option to use MS Teams V2 | false | -| onlineCheckMethod | Type of network test for checking online status, can be: https, dns, native, none | https | -| partition | [BrowserWindow](https://electronjs.org/docs/api/browser-window) webpreferences partition | persist:teams-4-linux | -| proxyServer | Proxy Server with format address:port | None | -| menubar | A value controls the menu bar behaviour (auto/visible/hidden) | auto | -| minimized | Start the application minimized | false | -| ntlmV2enabled | set enable-ntlm-v2 value | true | -| screenLockInhibitionMethod | Screen lock inhibition method to be used (Electron/WakeLockSentinel) | Electron | -| spellCheckerLanguages | Language codes to use with Electron\'s spell checker (experimental) | [] | -| url | url to open (will be overridden, if opted in to use Teams V2) | [https://teams.microsoft.com/](https://teams.microsoft.com/) | -| version | show the version number | false | -| webDebug | Start with the browser developer tools open | false | -| incomingCallCommand | Command to execute on an incoming call | | +| Option | Usage | Default Value | +|---------------------------------|--------------------------------------------------------------------------------------------|-----------------------| +| appActiveCheckInterval | A numeric value in seconds as poll interval to check if the system is active from being idle | 2 | +| appIcon | Teams app icon to show in the tray | | +| appIconType | Type of tray icon to be used default/light/dark | default | +| appIdleTimeout | A numeric value in seconds as duration before app considers the system as idle | 300 | +| appIdleTimeoutCheckInterval | A numeric value in seconds as poll interval to check if the appIdleTimeout is reached | 10 | +| appLogLevels | Comma separated list of log levels (error,warn,info,debug) | error,warn | +| appTitle | A text to be suffixed with page title | Microsoft Teams | +| authServerWhitelist | Set auth-server-whitelist value | string | +| awayOnSystemIdle | Sets the user status as away when system goes idle | false | +| chromeUserAgent | Google Chrome User Agent | string | +| customBGServiceBaseUrl | Base URL of the server which provides custom background images | string | +| customBGServiceIgnoreMSDefaults | A flag indicates whether to ignore Microsoft provided images or not | false | +| customBGServiceConfigFetchInterval | A numeric value in seconds as poll interval to download background service config download | number | +| customCACertsFingerprints | Array of custom CA Certs Fingerprints to allow SSL unrecognized signer or self signed certificate | array | +| customCSSName | custom CSS name for the packaged available css files | string | +| customCSSLocation | custom CSS styles file location | string | +| followSystemTheme | Follow system theme | false | +| customUserDir | Custom User Directory so that you can have multiple profiles | string | +| clearStorage | Whether to clear the storage before creating the window or not | false | +| clientCertPath | Custom Client Certs for corporate authentication (certificate must be in pkcs12 format) | string | +| clientCertPassword | Custom Client Certs password for corporate authentication (certificate must be in pkcs12 format) | string | +| closeAppOnCross | Close the app when clicking the close (X) cross | false | +| defaultURLHandler | Default application to be used to open the HTTP URLs | string | +| disableAutogain | A flag indicates whether to disable mic auto gain or not | false | +| disableGpu | A flag to disable GPU and hardware acceleration (can be useful if the window remains blank) | false | +| disableMeetingNotifications | Whether to disable meeting notifications or not | false | +| disableNotifications | A flag to disable all notifications | false | +| disableNotificationSound | Disable chat/meeting start notification sound | false | +| disableNotificationSoundIfNotAvailable | Disables notification sound unless status is Available (e.g. while in a call, busy, etc.) | false | +| disableNotificationWindowFlash | A flag indicates whether to disable window flashing when there is a notification | false | +| incomingCallCommand | Command to execute on an incoming call. | | +| incomingCallCommandArgs | Arguments for the incomming call command. | | +| menubar | A value controls the menu bar behaviour | string | +| minimized | Start the application minimized | false | +| ntlmV2enabled | Set enable-ntlm-v2 value | string | +| onlineCheckMethod | Type of network test for checking online status. | string | +| optInTeamsV2 | Opt in to use Teams V2 | false | +| partition | BrowserWindow webpreferences partition | string | +| proxyServer | Proxy Server with format address:port | string | +| screenLockInhibitionMethod | Screen lock inhibition method to be used (Electron/WakeLockSentinel) | string | +| spellCheckerLanguages | Array of languages to use with Electron's spell checker (experimental) | array | +| url | Microsoft Teams URL | string | +| useMutationTitleLogic | Use MutationObserver to update counter from title | false | +| version | Show the version number | false | +| webDebug | Enable web debugging | false | As an example, to disable the persistence, you can run the following command: diff --git a/app/config/index.js b/app/config/index.js index 90033557..967f34bf 100644 --- a/app/config/index.js +++ b/app/config/index.js @@ -62,6 +62,11 @@ function argv(configPath, appVersion) { describe: 'Set auth-server-whitelist value', type: 'string' }, + awayOnSystemIdle: { + default: false, + describe: 'Sets the user status as away when system goes idle', + type: 'boolean' + }, chromeUserAgent: { default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`, describe: 'Google Chrome User Agent', @@ -167,20 +172,13 @@ function argv(configPath, appVersion) { describe: 'A flag indicates whether to disable window flashing when there is a notification', type: 'boolean' }, - partition: { - default: 'persist:teams-4-linux', - describe: 'BrowserWindow webpreferences partition', - type: 'string' - }, - optInTeamsV2: { - default: false, - describe: 'Opt in to use Teams V2', - type: 'boolean' - }, - proxyServer: { + incomingCallCommand: { default: null, - describe: 'Proxy Server with format address:port', - type: 'string' + describe: 'Command to execute on an incoming call.' + }, + incomingCallCommandArgs: { + default: [], + describe: 'Arguments for the incomming call command.' }, menubar: { default: 'auto', @@ -198,6 +196,27 @@ function argv(configPath, appVersion) { describe: 'Set enable-ntlm-v2 value', type: 'string' }, + onlineCheckMethod: { + default: 'https', + describe: 'Type of network test for checking online status.', + type: 'string', + choices: ['https', 'dns', 'native', 'none'] + }, + optInTeamsV2: { + default: false, + describe: 'Opt in to use Teams V2', + type: 'boolean' + }, + partition: { + default: 'persist:teams-4-linux', + describe: 'BrowserWindow webpreferences partition', + type: 'string' + }, + proxyServer: { + default: null, + describe: 'Proxy Server with format address:port', + type: 'string' + }, screenLockInhibitionMethod: { default: 'Electron', describe: 'Screen lock inhibition method to be used (Electron/WakeLockSentinel)', @@ -214,28 +233,14 @@ function argv(configPath, appVersion) { describe: 'Microsoft Teams URL', type: 'string' }, - webDebug: { + useMutationTitleLogic: { default: false, - describe: 'Enable debug at start', + describe: 'Use MutationObserver to update counter from title', type: 'boolean' }, - onlineCheckMethod: { - default: 'https', - describe: 'Type of network test for checking online status.', - type: 'string', - choices: ['https', 'dns', 'native', 'none'] - }, - incomingCallCommand: { - default: null, - describe: 'Command to execute on an incoming call.' - }, - incomingCallCommandArgs: { - default: [], - describe: 'Arguments for the incomming call command.' - }, - awayOnSystemIdle: { + webDebug: { default: false, - describe: 'Sets the user status as away when system goes idle', + describe: 'Enable debug at start', type: 'boolean' } }) diff --git a/app/index.js b/app/index.js index c1370eef..9d4b94f6 100644 --- a/app/index.js +++ b/app/index.js @@ -3,7 +3,6 @@ const path = require('path'); const fs = require('fs'); const { LucidLog } = require('lucid-log'); const { httpHelper } = require('./helpers'); - const isDev = require('electron-is-dev'); const os = require('os'); const isMac = os.platform() === 'darwin'; @@ -257,23 +256,11 @@ async function requestMediaAccess() { }); } -/** - * Handle user-status-changed message - * - * @param {*} event - * @param {*} options - */ async function userStatusChangedHandler(event, options) { userStatus = options.data.status; logger.debug(`User status changed to '${userStatus}'`); } -/** - * Handle user-status-changed message - * - * @param {*} event - * @param {*} count - */ async function setBadgeCountHandler(event, count) { logger.debug(`Badge count set to '${count}'`); app.setBadgeCount(count); diff --git a/app/mainAppWindow/index.js b/app/mainAppWindow/index.js index 929997cd..736861e9 100644 --- a/app/mainAppWindow/index.js +++ b/app/mainAppWindow/index.js @@ -150,8 +150,6 @@ function handleTeamsV2OptIn(config) { }); return; } - window.webContents.executeJavaScript('localStorage.removeItem("tmp.isOptedIntoT2Web");', true) - .then(window.reload()); } /** diff --git a/com.github.IsmaelMartinez.teams_for_linux.appdata.xml b/com.github.IsmaelMartinez.teams_for_linux.appdata.xml index 1e5b67b7..8a140d7e 100644 --- a/com.github.IsmaelMartinez.teams_for_linux.appdata.xml +++ b/com.github.IsmaelMartinez.teams_for_linux.appdata.xml @@ -14,6 +14,48 @@ https://github.com/IsmaelMartinez/teams-for-linux/issues com.github.IsmaelMartinez.teams_for_linux.desktop + + +
    +
  • updating tray icon with the number in the document.title. This is hidden behind a feature flag/config option called useMutationTitleLogic
  • +
  • moved trayIconRenderer out of the activityHub, and to react to events instead.
  • +
  • re-order config readme to show in the right order.
  • +
  • removing the 'removal' of the opt in for teamsv2. Now if you use that feature flag and want to revert you will need to do it manually.
  • +
+
+
+ + +
    +
  • adding how to debug in bug report, moving react logic into its own ha… by @IsmaelMartinez in #1176
  • +
  • Feature/teams v2 changes theme by @IsmaelMartinez in #1177
  • +
+
+
+ + +
    +
  • feat: add opt in handling for teams v2 by @mhenselin in #1160
  • +
+
+
+ + +
    +
  • Removing the angular recurring call to disable some promotions as I s… by @IsmaelMartinez in #1152
  • +
  • Hotfix/remove recurring angular call by @IsmaelMartinez in #1153
  • +
  • Hotfix/remove recurring angular call by @IsmaelMartinez in #1154
  • +
  • add support for changing idle (away) state in Teams2 by @bastidest in #1157
  • +
+
+
+ + +
    +
  • Added bail out logic to the angular detection as this is creating some memory issues in some users
  • +
+
+
    diff --git a/package.json b/package.json index 5e65a4b5..95800a1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "teams-for-linux", - "version": "1.4.18", + "version": "1.4.19", "main": "app/index.js", "description": "Unofficial client for Microsoft Teams for Linux", "homepage": "https://github.com/IsmaelMartinez/teams-for-linux",