├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── capture.css ├── capture.html ├── capture.js ├── index.js ├── render.css ├── render.html └── render.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mPyKen] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build_on_linux: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@master 16 | with: 17 | node-version: 22 18 | - name: install dependencies 19 | run: npm ci 20 | - name: build 21 | run: npm run make 22 | 23 | build_on_mac: 24 | runs-on: macos-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@master 28 | with: 29 | node-version: 22 30 | # install python 3.11 as in https://github.com/electron/forge/issues/3371 31 | - uses: actions/setup-python@v5 32 | with: 33 | python-version: "3.11" 34 | - name: install dependencies 35 | run: npm ci 36 | - name: build 37 | run: npm run make -- --arch=universal 38 | 39 | build_on_win: 40 | runs-on: windows-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions/setup-node@master 44 | with: 45 | node-version: 22 46 | - name: install dependencies 47 | run: npm ci 48 | - name: build 49 | run: npm run make 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | publish_on_linux: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@master 13 | with: 14 | node-version: 22 15 | - name: install dependencies 16 | run: npm ci 17 | - name: publish 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | run: npm run publish 21 | 22 | publish_on_mac: 23 | runs-on: macos-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@master 27 | with: 28 | node-version: 22 29 | # install python 3.11 as in https://github.com/electron/forge/issues/3371 30 | - uses: actions/setup-python@v5 31 | with: 32 | python-version: "3.11" 33 | - name: install dependencies 34 | run: npm ci 35 | - name: publish 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | run: npm run publish -- --arch=universal 39 | 40 | publish_on_win: 41 | runs-on: windows-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/setup-node@master 45 | with: 46 | node-version: 22 47 | - name: install dependencies 48 | run: npm ci 49 | - name: publish 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | run: npm run publish 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Electron-Forge 89 | out/ 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mPyKen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github All Releases](https://img.shields.io/github/license/mPyKen/ScreenAreaShare)](https://github.com/mPyKen/ScreenAreaShare) 2 | [![Build](https://github.com/mPyKen/ScreenAreaShare/actions/workflows/build.yml/badge.svg)](https://github.com/mPyKen/ScreenAreaShare/actions/workflows/build.yml) 3 | [![Github All Releases](https://img.shields.io/github/v/release/mPyKen/ScreenAreaShare)](https://github.com/mPyKen/ScreenAreaShare/releases) 4 | [![Github All Releases](https://img.shields.io/github/downloads/mPyKen/ScreenAreaShare/total.svg)](https://github.com/mPyKen/ScreenAreaShare/releases) 5 | 6 | # ScreenAreaShare 7 | 8 | ScreenAreaShare allows sharing selected area of the screen in applications that do not natively support this feature such as Teams. 9 | 10 | ## How It Works 11 | ScreenAreaShare creates 2 windows. The capture window is a click-through, transparent window indicating the recording area via a red border. 12 | The rendering window displays the content of that area. In applications such as Teams, you can then share the rendering window. 13 | 14 | ## How To Run 15 | ### Via Release 16 | You can download the executable for windows from the [Releases page](https://github.com/mPyKen/ScreenAreaShare/releases). 17 | ### Via Source Code 18 | 1. Clone or download the source code. 19 | 2. Run `npm install` 20 | 3. Either run directly or build an executable. 21 | 1. Source: run `npm start`. 22 | 2. Build executable: run `npm run make`. 23 | Execute the built .exe file in the `out/` folder. 24 | 25 | ## Usage 26 | 1. Two windows should open: One border-window and one rendering window. These two windows can be moved via a drag-drop. The red borders can be resized. 27 | 2. With both windows open, start the sharing function of the application of your choice (e.g. Teams) by selecting the option to share a window. Select this application. 28 | 29 | ## Command Line Parameters 30 | If you run from source: `npm start -- -- --cx=0 --cy=0 ...` (note the additional `--` required by electron-forge) 31 | If you run a prebuilt executable: `path/to/screen-area-share.exe --cx=0 --cy=0 ...` 32 | 33 | |Parameter|Description| 34 | |-|-| 35 | |`--freeze`|Hide capturing window| 36 | |`--consider-scale`|Considers scale settings of the screen| 37 | |`--maxfps=`|Set maximum frames per second during capture| 38 | |`--cx=`, `--cy=`|Initial top left coordinate of the capturing window| 39 | |`--cw=`, `--ch=`|Initial width and height of the capturing window| 40 | |`--rx=`, `--ry=`|Initial top left coordinate of the rendering window| 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screen-area-share", 3 | "productName": "screen-area-share", 4 | "version": "1.4.2", 5 | "description": "Share part of your screen on e.g. Teams", 6 | "author": { 7 | "name": "github.com/mPyKen" 8 | }, 9 | "main": "src/index.js", 10 | "scripts": { 11 | "start": "electron-forge start", 12 | "package": "electron-forge package", 13 | "make": "electron-forge make", 14 | "publish": "electron-forge publish", 15 | "pack": "electron-builder --dir", 16 | "dist": "electron-builder" 17 | }, 18 | "keywords": [], 19 | "license": "MIT", 20 | "dependencies": { 21 | "electron-squirrel-startup": "^1.0.0" 22 | }, 23 | "devDependencies": { 24 | "@electron-forge/cli": "^7.7.0", 25 | "@electron-forge/maker-deb": "^7.7.0", 26 | "@electron-forge/maker-dmg": "^7.7.0", 27 | "@electron-forge/maker-rpm": "^7.7.0", 28 | "@electron-forge/maker-squirrel": "^7.7.0", 29 | "@electron-forge/maker-zip": "^7.7.0", 30 | "@electron-forge/publisher-github": "^7.7.0", 31 | "electron": "^34.2.0", 32 | "electron-builder": "^25.1.8" 33 | }, 34 | "config": { 35 | "forge": { 36 | "packagerConfig": {}, 37 | "makers": [ 38 | { 39 | "name": "@electron-forge/maker-squirrel" 40 | }, 41 | { 42 | "name": "@electron-forge/maker-zip", 43 | "platforms": [ 44 | "darwin" 45 | ] 46 | }, 47 | { 48 | "name": "@electron-forge/maker-dmg" 49 | }, 50 | { 51 | "name": "@electron-forge/maker-deb" 52 | }, 53 | { 54 | "name": "@electron-forge/maker-rpm" 55 | } 56 | ], 57 | "publishers": [ 58 | { 59 | "name": "@electron-forge/publisher-github", 60 | "config": { 61 | "repository": { 62 | "owner": "mPyKen", 63 | "name": "ScreenAreaShare", 64 | "draft": true 65 | } 66 | } 67 | } 68 | ] 69 | } 70 | }, 71 | "build": { 72 | "productName": "ScreenAreaShare", 73 | "mac": { 74 | "target": "dmg" 75 | }, 76 | "win": { 77 | "target": [ 78 | "portable" 79 | ] 80 | }, 81 | "portable": { 82 | "artifactName": "ScreenAreaShare.exe" 83 | }, 84 | "directories": { 85 | "output": "out" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/capture.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 3 | margin: auto; 4 | /* max-width: 38rem; */ 5 | /* padding: 2rem; */ 6 | } 7 | 8 | .draggable {-webkit-app-region: drag;} 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | 14 | .border { 15 | position: absolute; 16 | top: 2px; 17 | left: 2px; 18 | right: 2px; 19 | bottom: 2px; 20 | } 21 | 22 | .outer { 23 | position: absolute; 24 | border: solid 10px; 25 | top: 0px; 26 | left: 0px; 27 | width: 100%; 28 | height: 100%; 29 | border-color: red; 30 | } 31 | 32 | .inner { 33 | position: absolute; 34 | top: 0px; 35 | left: 0px; 36 | width: 100%; 37 | height: 100%; 38 | /* margin: 20px; */ 39 | /* border: solid 20px; */ 40 | /* background-color: aqua; */ 41 | /* border-color: red; */ 42 | } 43 | -------------------------------------------------------------------------------- /src/capture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Capture Window 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /src/capture.js: -------------------------------------------------------------------------------- 1 | // prepare stdout 2 | var nodeConsole = require("console"); 3 | let cons = new nodeConsole.Console(process.stdout, process.stderr); 4 | 5 | // get main window 6 | const { ipcRenderer } = require("electron"); 7 | 8 | // https://www.electronjs.org/docs/latest/api/frameless-window#click-through-window 9 | const inner = document.getElementsByClassName("inner")[0]; 10 | inner.addEventListener("mouseenter", () => { 11 | ipcRenderer.send("set-ignore-mouse-events", true, { forward: true }); 12 | // cons.log(`mouseenter, ignore: true`); 13 | }); 14 | inner.addEventListener("mouseleave", () => { 15 | ipcRenderer.send("set-ignore-mouse-events", false); 16 | // cons.log(`mouseleave, ignore: false`); 17 | }); 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var nodeConsole = require("console"); 2 | let cons = new nodeConsole.Console(process.stdout, process.stderr); 3 | 4 | const { 5 | app, 6 | BrowserWindow, 7 | screen, 8 | ipcMain, 9 | desktopCapturer, 10 | } = require("electron"); 11 | const path = require("path"); 12 | 13 | if (require("electron-squirrel-startup")) return app.quit(); 14 | 15 | // ignore dpi scaling as per https://stackoverflow.com/a/57924406 16 | const considerScale = app.commandLine.hasSwitch("consider-scale"); 17 | if (!considerScale) { 18 | app.commandLine.appendSwitch("high-dpi-support", 1); 19 | app.commandLine.appendSwitch("force-device-scale-factor", 1); 20 | } 21 | 22 | ipcMain.on("set-ignore-mouse-events", (event, ...args) => { 23 | BrowserWindow.fromWebContents(event.sender).setIgnoreMouseEvents(...args); 24 | }); 25 | 26 | const maxFrameRate = app.commandLine.hasSwitch("maxfps") 27 | ? parseInt(app.commandLine.getSwitchValue("maxfps")) 28 | : 60; 29 | const freeze = app.commandLine.hasSwitch("freeze"); 30 | const initCapRect = { 31 | x: app.commandLine.hasSwitch("cx") 32 | ? parseInt(app.commandLine.getSwitchValue("cx")) 33 | : null, 34 | y: app.commandLine.hasSwitch("cy") 35 | ? parseInt(app.commandLine.getSwitchValue("cy")) 36 | : null, 37 | width: app.commandLine.hasSwitch("cw") 38 | ? parseInt(app.commandLine.getSwitchValue("cw")) 39 | : 1280, 40 | height: app.commandLine.hasSwitch("ch") 41 | ? parseInt(app.commandLine.getSwitchValue("ch")) 42 | : 720, 43 | }; 44 | const initRenderRect = { 45 | x: app.commandLine.hasSwitch("rx") 46 | ? parseInt(app.commandLine.getSwitchValue("rx")) 47 | : null, 48 | y: app.commandLine.hasSwitch("ry") 49 | ? parseInt(app.commandLine.getSwitchValue("ry")) 50 | : null, 51 | width: app.commandLine.hasSwitch("rw") 52 | ? parseInt(app.commandLine.getSwitchValue("rw")) 53 | : 1280, 54 | height: app.commandLine.hasSwitch("rh") 55 | ? parseInt(app.commandLine.getSwitchValue("rh")) 56 | : 720, 57 | }; 58 | 59 | function checkWindowBounds(win) { 60 | const rect = win.getBounds(); 61 | const dbounds = screen.getDisplayMatching(rect).bounds; 62 | rect.x = Math.max(rect.x, dbounds.x); 63 | rect.y = Math.max(rect.y, dbounds.y); 64 | rect.x = Math.min(rect.x, dbounds.x + dbounds.width - rect.width); 65 | rect.y = Math.min(rect.y, dbounds.y + dbounds.height - rect.height); 66 | win.setBounds(rect); 67 | } 68 | 69 | const createWindows = () => { 70 | // 71 | // create and setup render window 72 | // 73 | 74 | const mainWindow = new BrowserWindow({ 75 | webPreferences: { 76 | nodeIntegration: true, 77 | contextIsolation: false, 78 | enableRemoteModule: true, 79 | backgroundThrottling: false, // continue playing