Skip to content

Commit 2c6f4fe

Browse files
IsmaelMartinezclaudeCopilot
authored
Adding screensharing pop-out window, system-wide configuration and enabling cache cleanup by default (#1781)
* feat: Implement Separate Call Window (Pop-out) - Implements session sharing for pop-out window. - Adds pop-out button to Teams toolbar. - Hides main meeting content when popped out. - Adds always-on-top and auto-pop when sharing features. Related to task 2.0 in PRD * refactor: Split PRD tasks into separate files - Separates pop-out UI, configuration manager, and knowledge base tasks into dedicated markdown files. - Updates original PRD task file to reflect the split. Related to task splitting request * pushing wip work on the sharing screenshared information * fixing error message * feat: Implement screen sharing display in pop-out window * fix: Ensure single instance of Call Pop-out Window * fix: Reliably close pop-out window on screen sharing stop * feat: Improve screen sharing thumbnail window - Rename config from autoPopWhenSharing to screenSharingThumbnail for clarity - Make pop-out window thumbnail-sized (320x180 default, max 400x300) - Implement dynamic window sizing based on actual video dimensions - Remove fixed video constraints to allow native resolution streaming - Add window resizing IPC handler for responsive thumbnail scaling - Update window title dynamically instead of showing overlay text - Create comprehensive CLAUDE.md documentation for future development 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * chore: Bump version to 2.2.0 and update release notes This commit bumps the application version to 2.2.0 and updates the release notes. - Updated package.json and package-lock.json to version 2.2.0. - Added a new release entry in com.github.IsmaelMartinez.teams_for_linux.appdata.xml summarizing new features and internal changes. - Added CLAUDE.md for Claude-specific instructions. - Added new task list files for project management. * feat: Add system-wide config support and update dependencies - Implement system-wide configuration file support at /etc/teams-for-linux/config.json - Add proper config precedence (system config as base, user config overrides) - Update dependencies: [email protected], @eslint/[email protected], [email protected], @electron/[email protected] - Fix GNOME window decorations dark mode issue (Electron 37.2.6 includes upstream fix) - Update documentation with system-wide config examples and precedence info - Update appdata with release notes for v2.2.1 Resolves #1773 - System-wide configuration support Resolves #1755 - GNOME dark mode window decorations fix 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Update app/index.js Co-authored-by: Copilot <[email protected]> * add lang to callPopOut html * hidding the menu option for inApp UI if the flag is disabled * fixes from code review * changes from code review * cleaning the appdata and package.json files to prepare the release * changing the release date to be today * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * removing duplicated inAppUi * removing the extra arguments for screen-sharing-stopped --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 0d5286f commit 2c6f4fe

21 files changed

+1112
-266
lines changed

CLAUDE.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Essential Commands
6+
7+
**Development:**
8+
- `npm start` - Run application in development mode with trace warnings
9+
- `npm run lint` - Run ESLint validation (mandatory before commits)
10+
11+
**Building:**
12+
- `npm run pack` - Development build without packaging
13+
- `npm run dist:linux` - Build Linux packages (AppImage, deb, rpm, snap)
14+
- `npm run dist` - Build all platforms using electron-builder
15+
16+
**Utility:**
17+
- `npm run generate-release-info` - Generate release information file
18+
19+
## Project Architecture
20+
21+
Teams for Linux is an Electron-based desktop application that wraps the Microsoft Teams web app. The architecture follows a modular pattern with the main process coordinating various specialized modules.
22+
23+
### Core Structure
24+
25+
- **Entry Point:** `app/index.js` - Main Electron process (currently being refactored into smaller modules)
26+
- **Configuration:** `app/appConfiguration/` - Centralized configuration management using AppConfiguration class
27+
- **Main Window:** `app/mainAppWindow/` - Manages the primary BrowserWindow and Teams web wrapper
28+
- **Browser Tools:** `app/browser/tools/` - Client-side scripts injected into Teams interface
29+
30+
### Key Modules
31+
32+
- **IPC Communication:** Extensive IPC system for main-renderer communication (see `docs/ipc-api.md`)
33+
- **Notifications:** System notification integration with custom sounds
34+
- **Screen Sharing:** Desktop capture and stream selector functionality
35+
- **Custom Backgrounds:** Custom background image management
36+
- **Tray Integration:** System tray with status indicators
37+
- **Cache Management:** Application cache handling
38+
- **Authentication:** SSO login support
39+
40+
### State Management
41+
42+
Global state is managed through specific modules:
43+
- User status tracking (`userStatus`, `idleTimeUserStatus`)
44+
- Screen sharing state (`screenSharingActive`, `currentScreenShareSourceId`)
45+
- Configuration managed via immutable AppConfiguration class
46+
47+
## Development Patterns
48+
49+
### Code Style Requirements
50+
- **NO `var`** - Use `const` by default, `let` for reassignment
51+
- **async/await** - Use instead of promise chains
52+
- **Private fields** - Use JavaScript `#property` syntax for class private members
53+
- **Arrow functions** - For concise callbacks
54+
55+
### Configuration Management
56+
- All configuration handled through `AppConfiguration` class
57+
- Treat config as immutable after startup
58+
- Changes via AppConfiguration methods only
59+
60+
### IPC Communication
61+
- Use `ipcMain.handle` for request-response patterns
62+
- Use `ipcMain.on` for fire-and-forget notifications
63+
- Document all new IPC channels in `docs/ipc-api.md`
64+
65+
### Error Handling
66+
- Robust error handling with try-catch in async functions
67+
- Graceful degradation with clear user feedback
68+
- Use `electron-log` for structured logging
69+
70+
## Testing and Quality
71+
72+
The project currently lacks comprehensive test coverage. When contributing:
73+
- Run `npm run lint` before commits (ESLint with custom config)
74+
- Consider adding tests using a framework like Jest
75+
- Ensure cross-platform compatibility (Linux primary, Windows/macOS supported)
76+
77+
## Important Notes
78+
79+
- The project is undergoing active refactoring to improve modularity
80+
- New functionality should be placed in separate modules rather than `app/index.js`
81+
- Browser scripts must be defensive as Teams DOM can change without notice
82+
- Follow single responsibility principle for new modules
83+
- Update module-specific README.md files when making changes

app/browser/tools/chromeApi.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,30 @@ function startStreaming(properties) {
5656
})
5757
.then((stream) => {
5858
properties.resolve(stream);
59+
const sourceIdToSend = properties.source.id; // Use the sourceId that was used to create the stream
60+
console.debug(
61+
"Sending screen-sharing-started with sourceId:",
62+
sourceIdToSend
63+
);
64+
ipcRenderer.send("screen-sharing-started", sourceIdToSend);
65+
const videoTrack = stream.getVideoTracks()[0];
66+
console.debug(
67+
"Starting polling for video track readyState:",
68+
videoTrack
69+
);
70+
const checkInterval = setInterval(() => {
71+
if (videoTrack.readyState === "ended") {
72+
console.debug(
73+
'Video track readyState is "ended". Sending screen-sharing-stopped.'
74+
);
75+
ipcRenderer.send("screen-sharing-stopped");
76+
clearInterval(checkInterval);
77+
}
78+
}, 500); // Check every 500ms
5979
})
6080
.catch((e) => {
6181
console.error(e.message);
82+
ipcRenderer.send("screen-sharing-stopped");
6283
properties.reject(e.message);
6384
});
6485
} else {

app/browser/tools/popOutCall.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
exports.injectPopOutScript = function (webFrame) {
2+
const script = `
3+
(function() {
4+
const POPOUT_BUTTON_ID = 'teams-for-linux-popout-button';
5+
6+
function injectPopOutButton() {
7+
const callControls = document.querySelector('[data-tid="call-controls-container"]');
8+
9+
if (callControls && !document.getElementById(POPOUT_BUTTON_ID)) {
10+
const popOutButton = document.createElement('button');
11+
popOutButton.id = POPOUT_BUTTON_ID;
12+
popOutButton.className = 'ts-btn';
13+
popOutButton.innerHTML = '<span class="ts-icon ts-icon-popout"></span> Pop Out';
14+
popOutButton.title = 'Pop out this call';
15+
16+
popOutButton.addEventListener('click', () => {
17+
if (window.electronAPI && typeof window.electronAPI.createCallPopOutWindow === 'function') {
18+
window.electronAPI.createCallPopOutWindow();
19+
} else {
20+
console.warn('Teams for Linux: electronAPI.createCallPopOutWindow is not available.');
21+
}
22+
// Hide the main Teams meeting content
23+
const meetingContent = document.querySelector('[data-tid="call-screen"]');
24+
if (meetingContent) {
25+
meetingContent.style.display = 'none';
26+
}
27+
console.log('Teams for Linux: Main meeting content hidden.');
28+
});
29+
30+
callControls.appendChild(popOutButton);
31+
console.log('Teams for Linux: Pop out button injected.');
32+
} else if (!callControls) {
33+
console.log('Teams for Linux: Call controls container not found, retrying...');
34+
}
35+
}
36+
37+
const observer = new MutationObserver((mutationsList, observer) => {
38+
if (document.querySelector('[data-tid="call-controls-container"]')) {
39+
injectPopOutButton();
40+
}
41+
});
42+
43+
observer.observe(document.body, { childList: true, subtree: true });
44+
injectPopOutButton();
45+
})();
46+
`;
47+
webFrame.executeJavaScript(script);
48+
};

app/config/index.js

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,79 @@ function getConfigFilePath(configPath) {
88
return path.join(configPath, "config.json");
99
}
1010

11+
function getSystemConfigFilePath() {
12+
return "/etc/teams-for-linux/config.json";
13+
}
14+
1115
function checkConfigFileExistence(configPath) {
1216
return fs.existsSync(getConfigFilePath(configPath));
1317
}
1418

19+
function checkSystemConfigFileExistence() {
20+
return fs.existsSync(getSystemConfigFilePath());
21+
}
22+
1523
function getConfigFile(configPath) {
1624
return require(getConfigFilePath(configPath));
1725
}
1826

27+
function getSystemConfigFile() {
28+
return require(getSystemConfigFilePath());
29+
}
30+
1931
function populateConfigObjectFromFile(configObject, configPath) {
32+
let systemConfig = {};
33+
let userConfig = {};
34+
let hasUserConfig = false;
35+
let hasSystemConfig = false;
36+
37+
// First, try to load system-wide config
38+
if (checkSystemConfigFileExistence()) {
39+
try {
40+
systemConfig = getSystemConfigFile();
41+
hasSystemConfig = true;
42+
console.info(
43+
"System-wide config loaded from /etc/teams-for-linux/config.json"
44+
);
45+
} catch (e) {
46+
console.warn(
47+
"Error loading system-wide config file, ignoring:\n" + e.message
48+
);
49+
}
50+
}
51+
52+
// Then, try to load user config (this takes precedence)
2053
if (checkConfigFileExistence(configPath)) {
2154
try {
22-
configObject.configFile = getConfigFile(configPath);
23-
configObject.isConfigFile = true;
55+
userConfig = getConfigFile(configPath);
56+
hasUserConfig = true;
2457
} catch (e) {
2558
configObject.configError = e.message;
2659
console.warn(
27-
"Error in config file, using default values:\n" +
60+
"Error in user config file, using system config or defaults:\n" +
2861
configObject.configError
2962
);
3063
}
64+
}
65+
66+
// Merge configs with user config taking precedence over system config
67+
if (hasUserConfig || hasSystemConfig) {
68+
configObject.configFile = { ...systemConfig, ...userConfig };
69+
configObject.isConfigFile = true;
70+
71+
if (hasUserConfig && hasSystemConfig) {
72+
console.info(
73+
"Using merged configuration: system-wide config overridden by user config"
74+
);
75+
} else if (hasUserConfig) {
76+
console.info("Using user configuration");
77+
} else {
78+
console.info("Using system-wide configuration (no user config found)");
79+
}
3180
} else {
32-
console.warn("No config file found, using default values");
81+
console.warn(
82+
"No config file found (user or system-wide), using default values"
83+
);
3384
}
3485
}
3586

@@ -45,6 +96,14 @@ function extractYargConfig(configObject, appVersion) {
4596
"A numeric value in seconds as poll interval to check if the system is active from being idle",
4697
type: "number",
4798
},
99+
screenSharingThumbnail: {
100+
default: {
101+
enabled: true,
102+
},
103+
describe:
104+
"Automatically show a thumbnail window when screen sharing is active.",
105+
type: "object",
106+
},
48107
appIcon: {
49108
default: "",
50109
describe: "Teams app icon to show in the tray",
@@ -73,6 +132,11 @@ function extractYargConfig(configObject, appVersion) {
73132
describe: "A text to be suffixed with page title",
74133
type: "string",
75134
},
135+
alwaysOnTop: {
136+
default: true,
137+
describe: "Keep the pop-out window always on top of other windows.",
138+
type: "boolean",
139+
},
76140
authServerWhitelist: {
77141
default: "*",
78142
describe: "Set auth-server-whitelist value",
@@ -136,7 +200,7 @@ function extractYargConfig(configObject, appVersion) {
136200
},
137201
cacheManagement: {
138202
default: {
139-
enabled: false,
203+
enabled: true,
140204
maxCacheSizeMB: 300,
141205
cacheCheckIntervalMs: 3600000,
142206
},
@@ -227,6 +291,11 @@ function extractYargConfig(configObject, appVersion) {
227291
describe:
228292
"Use windows platform information in chromium. This is helpful if MFA app does not support Linux.",
229293
},
294+
enableInAppUI: {
295+
default: false,
296+
describe: "Enable the in-app UI window (WIP)",
297+
type: "boolean",
298+
},
230299
enableIncomingCallToast: {
231300
default: false,
232301
describe: "Enable incoming call toast",

0 commit comments

Comments
 (0)