├── .gitignore ├── assets └── icon │ ├── mono │ ├── badge-64.png │ ├── normal-64.png │ └── offline-64.png │ ├── overlay-new-xs.png │ ├── colored │ ├── badge-64.png │ ├── normal-64.png │ └── offline-64.png │ └── default │ ├── badge-64.png │ ├── normal-64.png │ └── offline-64.png ├── google-chat-linux-nvm-launcher.sh ├── src ├── contextmenu.js ├── paths.js ├── faviconChanged.js ├── keyboard.js ├── index.js ├── configs.js ├── tray.js └── window.js ├── google-chat-linux.sh ├── google-chat-linux-localdev.sh ├── custom-css-examples └── new-chat-button-smaller │ └── custom.css ├── google-chat.desktop.template ├── google-chat-linux-unpacked-install.sh ├── containerbuild └── Containerfile ├── scripts └── in-container.sh ├── package.json ├── .github └── workflows │ └── release-desktop-package.yaml ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | *.xz 3 | google-chat-linux 4 | updateSRCINFO.sh 5 | dist 6 | node_modules 7 | -------------------------------------------------------------------------------- /assets/icon/mono/badge-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/mono/badge-64.png -------------------------------------------------------------------------------- /assets/icon/mono/normal-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/mono/normal-64.png -------------------------------------------------------------------------------- /assets/icon/mono/offline-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/mono/offline-64.png -------------------------------------------------------------------------------- /assets/icon/overlay-new-xs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/overlay-new-xs.png -------------------------------------------------------------------------------- /assets/icon/colored/badge-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/colored/badge-64.png -------------------------------------------------------------------------------- /assets/icon/colored/normal-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/colored/normal-64.png -------------------------------------------------------------------------------- /assets/icon/default/badge-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/default/badge-64.png -------------------------------------------------------------------------------- /assets/icon/default/normal-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/default/normal-64.png -------------------------------------------------------------------------------- /assets/icon/colored/offline-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/colored/offline-64.png -------------------------------------------------------------------------------- /assets/icon/default/offline-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squalou/google-chat-linux/HEAD/assets/icon/default/offline-64.png -------------------------------------------------------------------------------- /google-chat-linux-nvm-launcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NVM_DIR="$HOME/.nvm" 3 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 4 | nvm use --lts 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | $SCRIPT_DIR/google-chat-linux.sh "$@" # --enable-features=UseOzonePlatform --ozone-platform=wayland 7 | 8 | -------------------------------------------------------------------------------- /src/contextmenu.js: -------------------------------------------------------------------------------- 1 | const contextMenu = require('electron-context-menu'); 2 | 3 | const initializeContextMenu = () => { 4 | contextMenu({ 5 | showInspectElement: false, 6 | showCopyImageAddress: true, 7 | showSaveImageAs: true, 8 | }); 9 | } 10 | 11 | module.exports = { 12 | initializeContextMenu: initializeContextMenu, 13 | } -------------------------------------------------------------------------------- /google-chat-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 3 | echo $SCRIPTPATH 4 | export NODE_PATH="$SCRIPTPATH/node_modules/.bin" 5 | export PATH=$NODE_PATH:$PATH 6 | # GTK_USE_PORTAL=1 is set from index.js 7 | # for wayland : --ozone-platform=wayland 8 | ${NODE_PATH}/electron "${SCRIPTPATH}/src/index.js" --trace-warnings --ozone-platform=wayland "$@" 9 | 10 | -------------------------------------------------------------------------------- /google-chat-linux-localdev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 3 | cp -rf assets/icon node_modules/electron/dist/resources/ 4 | echo $SCRIPTPATH 5 | export NODE_PATH="$SCRIPTPATH/node_modules/.bin" 6 | export PATH=$NODE_PATH:$PATH 7 | # GTK_USE_PORTAL=1 is set from index.js 8 | # for wayland : --ozone-platform=wayland 9 | ${NODE_PATH}/electron "${SCRIPTPATH}/src/index.js" --trace-warnings "$@" 10 | 11 | -------------------------------------------------------------------------------- /custom-css-examples/new-chat-button-smaller/custom.css: -------------------------------------------------------------------------------- 1 | /* Custom CSS for Google Chat */ 2 | /* 'New Chat' huge icon -> not displayed*/ 3 | .bGH { 4 | display: none !important; 5 | } 6 | /* 'New Chat' area : make it small and move it out of the way */ 7 | .bGG { 8 | /* display: none !important; */ 9 | position: absolute; 10 | height: 20px !important; 11 | left: 155px; 12 | top: -40px; 13 | z-index: 1000; 14 | } 15 | .L3 { 16 | } 17 | -------------------------------------------------------------------------------- /google-chat.desktop.template: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Exec={{CURRENT_DIR}}/dist/linux-unpacked/google-chat-linux %u 5 | Terminal=false 6 | Icon={{CURRENT_DIR}}/build/icons/icon.png 7 | StartupWMClass=google-chat-linux 8 | Categories=GNOME;GTK;Network;InstantMessaging; 9 | Name=Google Chat 10 | Comment=Google Chat 11 | GenericName=Web Chat 12 | Keywords=InstantMessaging;Chat; 13 | X-GNOME-FullName=Google Chat Linux 14 | -------------------------------------------------------------------------------- /google-chat-linux-unpacked-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 3 | 4 | DATA_HOME=${XDG_DATA_HOME:-"~/.local/share/applications"} 5 | 6 | npm install && npm run dist 7 | rm -f $DATA_HOME/google-chat.desktop 8 | rm -f ./google-chat.desktop 9 | cp ./google-chat.desktop.template $DATA_HOME/google-chat.desktop 10 | sed -i "s|{{CURRENT_DIR}}|$SCRIPT_DIR|g" $DATA_HOME/google-chat.desktop 11 | update-desktop-database $DATA_HOME/ 12 | -------------------------------------------------------------------------------- /containerbuild/Containerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/buildpack-deps:focal-curl 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN \ 5 | # https://github.com/nodesource/distributions#ubuntu-versions 6 | mkdir -p /etc/apt/keyrings && \ 7 | curl -sL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ 8 | echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list && \ 9 | apt-get -qq update && \ 10 | # binutils provides ar - required to build deb \ 11 | # rpm provides rpmbuild - required to build rpm \ 12 | apt-get -qq install --no-install-recommends -y nodejs binutils rpm && \ 13 | apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* 14 | 15 | WORKDIR /project 16 | 17 | ENV LANG C.UTF-8 18 | ENV LANGUAGE C.UTF-8 19 | ENV LC_ALL C.UTF-8 20 | 21 | ENV DEBUG_COLORS true 22 | ENV FORCE_COLOR true 23 | -------------------------------------------------------------------------------- /src/paths.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { app } = require("electron"); 3 | 4 | let theme = 'default'; 5 | const iconPathTemplate = `\`icon/\${theme}/\${iconName}\``; 6 | const evalIconPath = (theme, iconName) => { 7 | return eval(iconPathTemplate); 8 | } 9 | 10 | const setIconTheme = (t) => { 11 | theme = t; 12 | } 13 | 14 | 15 | const normal = () => { 16 | return path.resolve(process.resourcesPath, evalIconPath(theme, "normal-64.png")) 17 | } 18 | const badge = () => { 19 | return path.resolve(process.resourcesPath, evalIconPath(theme, "badge-64.png")) 20 | } 21 | const offline = () => { 22 | return path.resolve(process.resourcesPath, evalIconPath(theme, "offline-64.png")) 23 | } 24 | 25 | module.exports = { 26 | "configsPath": path.resolve(app.getPath("appData"), "google-hangouts-chat-linux.json"), 27 | "OVERLAY_NEW_NOTIF": path.resolve(process.resourcesPath, "icon/overlay-new-xs.png"), 28 | normal: normal, 29 | badge: badge, 30 | offline: offline, 31 | setIconTheme: setIconTheme 32 | } 33 | -------------------------------------------------------------------------------- /src/faviconChanged.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron'); 2 | // Google chat initially loads favicon with rel="icon", 3 | // but replace it with rel="shortcut icon" when a new message appears. 4 | // We need to query for both elements 5 | const targetSelectors = [ 6 | 'link#favicon256', 7 | 'link[rel="shortcut icon"]', 8 | 'link[rel="icon"]' 9 | ]; 10 | 11 | let previousHref; 12 | const emitFaviconChanged = (favicon) => { 13 | const href = favicon?.href || ''; 14 | 15 | if (previousHref === href) { 16 | return; 17 | } 18 | previousHref = href; 19 | 20 | ipcRenderer.send('favicon-changed', href); 21 | } 22 | 23 | const initObserver = () => { 24 | // convert NodeList to array so we can use 'some' iteration on it 25 | let favicons = [].slice.call(document.head.querySelectorAll(targetSelectors.join(','))); 26 | let fi = favicons[0]; 27 | favicons.some(function (d) { 28 | // compat with old chat : selectAll returns too many things 29 | if (d.id === "favicon256") { 30 | fi = d 31 | return true; 32 | } 33 | }) 34 | emitFaviconChanged(fi); 35 | } 36 | 37 | let interval; 38 | window.addEventListener('DOMContentLoaded', () => { 39 | clearInterval(interval); 40 | interval = setInterval(initObserver, 1500) 41 | }); 42 | -------------------------------------------------------------------------------- /scripts/in-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | image=${CONTAINER_IMAGE_NAME:-"google-chat-linux-containerbuild"} 6 | 7 | # Initialize CONTAINER_ENGINE with a default value if not already set 8 | CONTAINER_ENGINE=${CONTAINER_ENGINE:-} 9 | 10 | # Check if CONTAINER_ENGINE is set and valid 11 | if [ -n "$CONTAINER_ENGINE" ]; then 12 | if ! type "$CONTAINER_ENGINE" &> /dev/null; then 13 | >&2 echo "The specified CONTAINER_ENGINE '$CONTAINER_ENGINE' does not exist or is not executable." 14 | exit 1 15 | fi 16 | else 17 | # If CONTAINER_ENGINE is empty, try setting it to podman or docker 18 | if type podman &> /dev/null; then 19 | CONTAINER_ENGINE="podman" 20 | elif type docker &> /dev/null; then 21 | CONTAINER_ENGINE="docker" 22 | else 23 | >&2 echo "Neither podman nor docker container engines found." 24 | exit 1 25 | fi 26 | fi 27 | 28 | if ! "${CONTAINER_ENGINE}" inspect "${image}" >/dev/null; then 29 | echo "Container image ${image} not found. Have you run npm run container:setup?" 30 | exit 1 31 | fi 32 | 33 | # Adapted https://www.electron.build/multi-platform-build#docker to podman 34 | "${CONTAINER_ENGINE}" run --rm -ti \ 35 | --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS_TAG|TRAVIS|TRAVIS_REPO_|TRAVIS_BUILD_|TRAVIS_BRANCH|TRAVIS_PULL_REQUEST_|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \ 36 | --env ELECTRON_CACHE="/root/.cache/electron" \ 37 | --env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \ 38 | -v "${PWD}:/project:Z" \ 39 | -v "${PWD##*/}-node-modules:/project/node_modules:Z" \ 40 | -v ~/.cache/electron:/root/.cache/electron:Z \ 41 | -v ~/.cache/electron-builder:/root/.cache/electron-builder:Z \ 42 | "${image}" "$@" 43 | -------------------------------------------------------------------------------- /src/keyboard.js: -------------------------------------------------------------------------------- 1 | const { globalShortcut } = require("electron"); 2 | let mainWindow; 3 | 4 | const goBack = () => { 5 | if (mainWindow.webContents.canGoBack()) { 6 | mainWindow.webContents.goBack(); 7 | } 8 | } 9 | 10 | const goForward = () => { 11 | if (mainWindow.webContents.canGoForward()) { 12 | mainWindow.webContents.goForward(); 13 | } 14 | } 15 | 16 | const zoomIn = () => { 17 | const currentZoom = mainWindow.webContents.getZoomFactor(); 18 | mainWindow.webContents.setZoomFactor(currentZoom + 0.1); 19 | }; 20 | 21 | const zoomOut = () => { 22 | const currentZoom = mainWindow.webContents.getZoomFactor(); 23 | mainWindow.webContents.setZoomFactor(currentZoom - 0.1); 24 | }; 25 | 26 | const registerKeyboardShortcuts = (windowObj) => { 27 | mainWindow = windowObj; 28 | 29 | globalShortcut.register("Alt+Right", () => { 30 | goForward(); 31 | }); 32 | 33 | globalShortcut.register("Alt+Left", () => { 34 | goBack(); 35 | }); 36 | 37 | // Handle different keyboard layouts for zoom-in and zoom-out 38 | // for example: on some layouts, 39 | // the "+" key may be represented as "Shift+=" and in some other just as "+" 40 | mainWindow.webContents.on("before-input-event", (event, input) => { 41 | const isMac = process.platform === "darwin"; 42 | const commandOrControl = isMac ? input.meta : input.control; 43 | const isZoomInShortcut = (commandOrControl && input.key === "+"); 44 | const isZoomOutShortcut = (commandOrControl && input.key === "-"); 45 | 46 | if (isZoomInShortcut) { 47 | zoomIn(); 48 | event.preventDefault(); 49 | return 50 | } 51 | if (isZoomOutShortcut) { 52 | zoomOut(); 53 | event.preventDefault(); 54 | return 55 | } 56 | }); 57 | }; 58 | 59 | module.exports = { 60 | "registerKeyboardShortcuts": registerKeyboardShortcuts 61 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-chat-linux", 3 | "version": "5.29.23-1", 4 | "description": "Unofficial alternative Google Chat desktop app", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "pack": "electron-builder --dir", 9 | "dist": "electron-builder", 10 | "dist:deb": "electron-builder --linux=deb", 11 | "dist:rpm": "electron-builder --linux=rpm", 12 | "container:setup": "container_engine=${CONTAINER_ENGINE:-$(type -P podman || type -P docker)} && ${container_engine} build -t google-chat-linux-containerbuild containerbuild || >&2 echo 'Install podman or docker container engine to run this command.' && exit 1", 13 | "container:build": "scripts/in-container.sh npm install && scripts/in-container.sh npm run dist", 14 | "container:build:deb": "scripts/in-container.sh npm install && scripts/in-container.sh npm run dist:deb", 15 | "container:build:rpm": "scripts/in-container.sh npm install && scripts/in-container.sh npm run dist:rpm" 16 | }, 17 | "repository": "github.com:squalou/google-chat-linux.git", 18 | "homepage": "https://github.com/squalou/google-chat-linux", 19 | "author": "Roberto Fasciolo (https://www.robyf.net/)", 20 | "license": "WTFPL", 21 | "build": { 22 | "appId": "Google Chat Linux", 23 | "linux": { 24 | "desktop": { 25 | "Name": "Google Chat Alt", 26 | "MimeType": "x-scheme-handler/gchat;" 27 | }, 28 | "category": "Network;InstantMessaging", 29 | "target": "deb" 30 | }, 31 | "deb": { 32 | "depends": [ 33 | "xdg-desktop-portal" 34 | ] 35 | }, 36 | "rpm": { 37 | "fpm": [ 38 | "--rpm-rpmbuild-define=_build_id_links none" 39 | ] 40 | }, 41 | "win": { 42 | "target": [ 43 | "nsis" 44 | ] 45 | }, 46 | "extraResources": [ 47 | { 48 | "filter": [ 49 | "**/*" 50 | ], 51 | "from": "assets" 52 | } 53 | ] 54 | }, 55 | "engines": { 56 | "node": "18.14.0", 57 | "npm": ">=8.15.0" 58 | }, 59 | "dependencies": { 60 | "electron-context-menu": "^3.5.0" 61 | }, 62 | "devDependencies": { 63 | "electron": "29.0.0", 64 | "electron-builder": "^24.4.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { app, Tray } = require('electron'); 2 | const WindowManager = require('./window'); 3 | const TrayManager = require('./tray'); 4 | const KeyboardManager = require('./keyboard'); 5 | const ConfigManager = require('./configs'); 6 | const ContextMenu = require('./contextmenu'); 7 | const applicationVersion = require('./../package.json').version; 8 | let mainWindow, systemTrayIcon, config, contextMenu; 9 | 10 | process.env.NODE_OPTIONS = "--no-force-async-hooks-checks"; 11 | process.env.ELECTRON_DISABLE_SANDBOX = true; 12 | process.env.GTK_USE_PORTAL = 1; 13 | 14 | process.title = 'Google Chat Linux'; 15 | console.log(process.title + ' - v' + applicationVersion); 16 | console.log('Node.js runtime version:', process.version); 17 | console.log('runtime platform : ', process.platform); 18 | 19 | const initialize = () => { 20 | app.allowRendererProcessReuse = true; 21 | config = ConfigManager.loadConfigs(); 22 | 23 | if (!mainWindow) { 24 | mainWindow = WindowManager.initializeWindow(config); 25 | } 26 | 27 | if (!contextMenu) { 28 | contextMenu = ContextMenu.initializeContextMenu(mainWindow); 29 | } 30 | 31 | if (!systemTrayIcon) { 32 | systemTrayIcon = TrayManager.initializeTray(mainWindow); 33 | } 34 | 35 | if (WindowManager.getEnableKeyboardShortcuts()) { 36 | KeyboardManager.registerKeyboardShortcuts(mainWindow); 37 | } 38 | 39 | }; 40 | 41 | if (process.platform === 'win32') { 42 | // Force single window, add Quit on taskbar for windows 43 | const gotTheLock = app.requestSingleInstanceLock(); 44 | if (!gotTheLock) { 45 | app.quit(); 46 | } else { 47 | app.on('second-instance', (event, argv) => { 48 | if (process.platform === 'win32' && argv.includes('--quit')) { 49 | // Needs to be delayed to not interfere with mainWindow.restore(); 50 | setTimeout(() => { 51 | console.log('Quitting via Task'); 52 | WindowManager.onQuitEntryClicked() 53 | app.quit(); 54 | }, 10); 55 | } 56 | }); 57 | } 58 | 59 | app.setUserTasks([ 60 | { 61 | program: process.execPath, 62 | arguments: '--quit', 63 | iconIndex: 0, 64 | title: "Quit" 65 | } 66 | ]); 67 | } 68 | 69 | app.on("ready", initialize); 70 | app.on("activate", initialize); 71 | -------------------------------------------------------------------------------- /src/configs.js: -------------------------------------------------------------------------------- 1 | const { app } = require("electron"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const pathsManifest = require("./paths"); 5 | const process = require("process"); 6 | 7 | const setConfigDefaults = (configuration) => { 8 | configuration.keepMinimized = configuration.keepMinimized === undefined ? true : configuration.keepMinimized; 9 | configuration.startHidden = configuration.startHidden === undefined ? false : configuration.startHidden; 10 | configuration.enableKeyboardShortcuts = configuration.enableKeyboardShortcuts === undefined ? true : configuration.enableKeyboardShortcuts; 11 | configuration.enableNodeIntegration = configuration.enableNodeIntegration === undefined ? true : configuration.enableNodeIntegration; 12 | configuration.openUrlInside = configuration.openUrlInside === undefined ? false : configuration.openUrlInside; 13 | configuration.useXdgOpen = configuration.useXdgOpen === undefined ? false : configuration.useXdgOpen; 14 | configuration.thirdPartyAuthLoginMode = configuration.thirdPartyAuthLoginMode === undefined ? false : configuration.thirdPartyAuthLoginMode; 15 | configuration.useOldUrl = configuration.useOldUrl === undefined ? false : configuration.useOldUrl; 16 | configuration.languages = configuration.languages === undefined ? undefined : configuration.languages; 17 | configuration.iconTheme = configuration.iconTheme === undefined ? 'default' : configuration.iconTheme; 18 | pathsManifest.setIconTheme(configuration.iconTheme) 19 | if (process.platform === 'win32') { 20 | configuration.keepMinimized = true; 21 | } 22 | console.log(configuration) 23 | console.log("?disable-gpu:" + app.commandLine.hasSwitch('disable-gpu')); 24 | } 25 | 26 | const loadConfigs = () => { 27 | try { 28 | c = JSON.parse(fs.readFileSync(pathsManifest.configsPath, "utf8")); 29 | setConfigDefaults(c); 30 | return c; 31 | } catch (e) { 32 | console.error(e); 33 | const defconfig = '{"bounds":{"x":456,"y":229,"width":1105,"height":757},"wasMaximized":false}' 34 | fs.writeFileSync(pathsManifest.configsPath, defconfig, 'utf8'); 35 | c = JSON.parse(defconfig, "utf-8"); 36 | setConfigDefaults(c); 37 | return c; 38 | } 39 | } 40 | 41 | const loadCustomCss = () => { 42 | const userDataPath = app.getPath('userData'); 43 | const customCssFilePath = path.join(userDataPath, 'custom.css'); 44 | 45 | if (fs.existsSync(customCssFilePath)) { 46 | try { 47 | const customCss = fs.readFileSync(customCssFilePath, 'utf8'); 48 | return customCss; 49 | } catch (error) { 50 | console.error('Error reading custom.css file:', error); 51 | return ''; 52 | } 53 | } else { 54 | console.log(`No custom.css file found in ${userDataPath}`); 55 | fs.writeFileSync(customCssFilePath, '/* Custom CSS for Google Chat */', 'utf8'); 56 | return ''; 57 | } 58 | } 59 | 60 | const updateConfigs = (updateData) => { 61 | let configs = loadConfigs(); 62 | configs = Object.assign({}, configs, updateData); 63 | saveConfigs(configs); 64 | } 65 | 66 | const saveConfigs = (configData) => { 67 | try { 68 | fs.writeFileSync(pathsManifest.configsPath, JSON.stringify(configData), 'utf8'); 69 | } catch (e) { 70 | console.error(e); 71 | return; 72 | } 73 | } 74 | 75 | module.exports = { 76 | "loadConfigs": loadConfigs, 77 | "updateConfigs": updateConfigs, 78 | "saveConfigs": saveConfigs, 79 | "loadCustomCss": loadCustomCss 80 | } -------------------------------------------------------------------------------- /src/tray.js: -------------------------------------------------------------------------------- 1 | const { Tray, Menu, ipcMain } = require("electron"); 2 | //const path = require("path"); 3 | const pathsManifest = require("./paths"); 4 | const WindowManager = require('./window'); 5 | let mainWindow; 6 | let systemTrayIcon; 7 | const onShowEntryClicked = () => { 8 | (!mainWindow.isVisible() || mainWindow.isMinimized()) ? mainWindow.show() : mainWindow.hide(); 9 | } 10 | 11 | const onSystemTrayIconClicked = () => { 12 | (!mainWindow.isVisible() || mainWindow.isMinimized() || !mainWindow.isFocused()) ? mainWindow.show() : mainWindow.hide(); 13 | } 14 | 15 | const buildContextMenu = () => { 16 | const template = [ 17 | { 18 | "label": "Show/Hide", 19 | "click": () => { 20 | onShowEntryClicked(); 21 | }, 22 | }, { 23 | label: 'Force reload', click: function () { 24 | WindowManager.onForceReloadClicked(); 25 | } 26 | }, { 27 | type: 'separator' 28 | }, { 29 | type: 'separator' 30 | }, { 31 | "label": WindowManager.getThirdPartyAuthLoginMode() ? "Regular mode after auth (restart)" : "Use third party auth mode (restart)", 32 | "click": () => { 33 | WindowManager.onToggleThirdPartyAuthLoginMode(); 34 | buildContextMenu(); 35 | } 36 | }, { 37 | type: 'separator' 38 | }, { 39 | "label": "Quit", 40 | "click": () => { 41 | WindowManager.onQuitEntryClicked(); 42 | } 43 | } 44 | ] 45 | 46 | const contextMenu = Menu.buildFromTemplate(template); 47 | systemTrayIcon.setContextMenu(contextMenu); 48 | systemTrayIcon.setToolTip(process.title); 49 | systemTrayIcon.setTitle(process.title); 50 | 51 | systemTrayIcon.on("click", () => { 52 | onSystemTrayIconClicked(); 53 | }); 54 | 55 | return systemTrayIcon; 56 | } 57 | 58 | const initializeTray = (windowObj) => { 59 | // pb : favicon object changes so the observer .... cannot observe ! or even be initialized 60 | // -> moved things to ipcRenderer preload mechanism in faviconChange.js - thank you ankurk91 :-) 61 | // see https://github.com/ankurk91/google-chat-electron.git 62 | try { 63 | systemTrayIcon = new Tray(pathsManifest.offline()); 64 | } catch (e) { 65 | console.log(e) 66 | console.log("set Tray icon failed !") 67 | systemTrayIcon = new Tray(null); 68 | } 69 | mainWindow = windowObj; 70 | return buildContextMenu(); 71 | 72 | }; 73 | 74 | ipcMain.on('favicon-changed', (evt, href) => { 75 | var itype = ""; 76 | if (href.match(/favicon_chat_new_non_notif_r/) || 77 | href.match(/favicon_chat_r/)) { 78 | itype = "NORMAL"; 79 | } else if (href.match(/favicon_chat_new_notif_r/)) { 80 | itype = "ATTENTION"; 81 | } else { 82 | itype = "OFFLINE"; 83 | } 84 | setIcon(itype); 85 | }); 86 | 87 | function iconForType(iconType) { 88 | if (iconType == "NORMAL") { 89 | return pathsManifest.normal(); 90 | } else if (iconType == "ATTENTION") { 91 | return pathsManifest.badge(); 92 | } else { 93 | return pathsManifest.offline(); 94 | } 95 | } 96 | 97 | const setIcon = (iconType) => { 98 | const i = iconForType(iconType) 99 | try { 100 | systemTrayIcon.setImage(i); 101 | } catch (e) { 102 | //do nothing ... fails on some distribs / OS / window managers 103 | console.log("Failed to update window icon :-(") 104 | console.log(e) 105 | } 106 | WindowManager.updateIcon(i); 107 | 108 | if (iconType == "ATTENTION") { 109 | WindowManager.setOverlayIcon(); 110 | } else { 111 | WindowManager.cleanOverlayIcon(); 112 | } 113 | } 114 | 115 | 116 | module.exports = { 117 | initializeTray: initializeTray 118 | }; 119 | -------------------------------------------------------------------------------- /.github/workflows/release-desktop-package.yaml: -------------------------------------------------------------------------------- 1 | name: Release desktop package 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | 11 | jobs: 12 | release: 13 | name: Create release 14 | runs-on: ubuntu-latest 15 | # Note this. We are going to use that in further jobs. 16 | outputs: 17 | upload_url: ${{ steps.create_release.outputs.upload_url }} 18 | steps: 19 | - name: Get tag name 20 | id: tag 21 | run: echo ::set-output name=NAME::${GITHUB_REF/refs\/tags\//} 22 | - name: Create release 23 | id: create_release 24 | uses: actions/create-release@latest 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | with: 28 | tag_name: ${{ github.ref }} 29 | release_name: ${{ steps.tag.outputs.NAME }} 30 | 31 | release_assets: 32 | name: Release assets 33 | needs: release # we need to know the upload URL 34 | runs-on: ${{ matrix.config.os }} # we run many different builds 35 | strategy: 36 | matrix: 37 | config: 38 | - os: ubuntu-latest 39 | - os: windows-latest 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v2 43 | 44 | - name: Extract package version 45 | id: extract_package_version 46 | uses: Saionaro/extract-package-version@v1.1.1 47 | 48 | - name: Install dependencies 49 | if: runner.os != 'Windows' 50 | run: sudo apt-get install -y ruby-dev build-essential && sudo gem install fpm 51 | 52 | - name: Install npm modules 53 | run: npm install 54 | 55 | - name: Build deb desktop app 56 | run: npm run dist 57 | 58 | - name: Build rpm desktop app 59 | if: runner.os == 'Linux' 60 | run: npm run dist:rpm 61 | 62 | - name: Create check sum file for deb 63 | if: matrix.config.os == 'ubuntu-latest' 64 | run: sha512sum *.deb > google-chat-linux-SHA512.txt 65 | working-directory: dist 66 | 67 | - name: Create check sum file for rpm 68 | if: matrix.config.os == 'ubuntu-latest' 69 | run: sha512sum *.rpm > google-chat-linux-rpm-SHA512.txt 70 | working-directory: dist 71 | 72 | - name: Upload deb check sum file to release 73 | if: matrix.config.os == 'ubuntu-latest' 74 | uses: actions/upload-release-asset@v1 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | with: 78 | upload_url: ${{ needs.release.outputs.upload_url }} 79 | asset_path: dist/google-chat-linux-SHA512.txt 80 | asset_name: google-chat-linux-SHA512.txt 81 | asset_content_type: text/plain 82 | 83 | - name: Upload rpm check sum file to release 84 | if: matrix.config.os == 'ubuntu-latest' 85 | uses: actions/upload-release-asset@v1 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | with: 89 | upload_url: ${{ needs.release.outputs.upload_url }} 90 | asset_path: dist/google-chat-linux-rpm-SHA512.txt 91 | asset_name: google-chat-linux-rpm-SHA512.txt 92 | asset_content_type: text/plain 93 | 94 | - name: Upload Debian package to release 95 | if: matrix.config.os == 'ubuntu-latest' 96 | uses: actions/upload-release-asset@v1 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | with: 100 | upload_url: ${{ needs.release.outputs.upload_url }} 101 | asset_path: dist/google-chat-linux_${{ steps.extract_package_version.outputs.version }}_amd64.deb 102 | asset_name: google-chat-linux_${{ steps.extract_package_version.outputs.version }}_amd64.deb 103 | asset_content_type: application/octet-stream 104 | 105 | - name: Upload rpm package to release 106 | if: matrix.config.os == 'ubuntu-latest' 107 | uses: actions/upload-release-asset@v1 108 | env: 109 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 110 | with: 111 | upload_url: ${{ needs.release.outputs.upload_url }} 112 | asset_path: dist/google-chat-linux-${{ steps.extract_package_version.outputs.version }}.x86_64.rpm 113 | asset_name: google-chat-linux-${{ steps.extract_package_version.outputs.version }}.x86_64.rpm 114 | asset_content_type: application/octet-stream 115 | 116 | - name: Create Exe check sum file 117 | if: matrix.config.os == 'windows-latest' 118 | run: sha512sum *.exe > google-chat-win-SHA512.txt 119 | working-directory: dist 120 | 121 | - name: Upload Exe package to release 122 | if: matrix.config.os == 'windows-latest' 123 | uses: actions/upload-release-asset@v1 124 | env: 125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | with: 127 | upload_url: ${{ needs.release.outputs.upload_url }} 128 | asset_path: dist/google-chat-linux Setup ${{ steps.extract_package_version.outputs.version }}.exe 129 | asset_name: google-chat-linux_Setup_${{ steps.extract_package_version.outputs.version }}.exe 130 | asset_content_type: application/octet-stream 131 | 132 | - name: Upload Exe check sum file to release 133 | if: matrix.config.os == 'windows-latest' 134 | uses: actions/upload-release-asset@v1 135 | env: 136 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 137 | with: 138 | upload_url: ${{ needs.release.outputs.upload_url }} 139 | asset_path: dist/google-chat-win-SHA512.txt 140 | asset_name: google-chat-win-SHA512.txt 141 | asset_content_type: text/plain 142 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # google-chat-linux-git 2 | 3 | An electron-base client for Google Hangouts Chat, since Google didn't see fit to provide one. 4 | 5 | ## CHANGELOG 6 | 7 | ### 5.29.23-1 8 | 9 | bump electron to 29 with better wayland support. Define `ELECTRON_OZONE_PLATFORM_HINT=auto` to take advantage of it. Values can be `auto`, `wayland`, `x11`, with `auto` being a reasonable default. 10 | 11 | Set it in `.zshenv` for instance, and logout / login again. Must be in a placed sourced when .desktop applications launchers are used, will certainly be DE dependant. 12 | 13 | ### 5.27.23-6, -5, -4, -3 14 | 15 | empty releases for AUR packaging fix only 16 | 17 | ### 5.27.23-2 18 | 19 | Add a menu in 'View' to change tray icon theme. 20 | 21 | ### 5.27.23-1 22 | 23 | Add support for several iconThemes, to match some 'monochrome' desktop themes. 3 values are supported : 24 | 25 | * `default` : the good old green ones 26 | * `colored` : the new colored google ones 27 | * `mono` : an attempt at monochrome icon theme 28 | 29 | Edit `~/.config/google-hangouts-chat-linux.json` and set `"iconTheme":"colored"` for instance, then **restart**. 30 | 31 | No GUI setting for now. 32 | 33 | ### 5.27.22-4 34 | 35 | Fix systray notification (favicon name changed on google side) see https://github.com/squalou/google-chat-linux/issues/87 36 | 37 | ### 5.27.22-3 38 | 39 | Add a nice pseudo-protocol support : open gchat:// urls in client instead of browser. (requires configutaion in browsers) 40 | 41 | See README.md "Open Google Chat URLs from web browser in the app". 42 | 43 | 44 | ### 5.27.22-2 45 | 46 | * Desktop shortcut name changed to `Google Chat Alt` 47 | * `rpm` build available 48 | * build using containers available 49 | 50 | ### 5.27.22-1 51 | 52 | Update electron to 27.0.3. 53 | 54 | Wayland is best supported now with titlbars and all. Run with `--ozone-platform=wayland` and your're done ! 55 | 56 | ### 5.24.22-1 57 | 58 | Fix https://github.com/squalou/google-chat-linux/issues/62 : thank you https://github.com/ThatOneCalculator ! 59 | 60 | Minor electron update (24.8.5) 61 | 62 | 63 | ### 5.24.21-1 64 | 65 | Fix https://github.com/squalou/google-chat-linux/issues/69 : download of attachments. (was broken by a side effect of https://github.com/squalou/google-chat-linux/issues/67 ) 66 | 67 | ### 5.24.20-2 68 | 69 | repackage, fix vulnerabilities 70 | 71 | ### 5.24.20-1 72 | 73 | Prevent several instances of google chat linux to be launched simultaneously. (https://github.com/squalou/google-chat-linux/issues/67) 74 | 75 | * Under X.Org : Starting a new one will restore and focus the existing one. 76 | * Under Wayland : it depends, with Gnome a notification is displayed but no focus given. 77 | 78 | 79 | ### 5.24.19-4 80 | 81 | Clean MORE links (https://github.com/squalou/google-chat-linux/issues/66) 82 | ### 5.24.19-3 83 | 84 | Clean links from google decoration before opening them (https://github.com/squalou/google-chat-linux/issues/66) 85 | 86 | ### 5.24.19-2 87 | 88 | Fix open links in external browser (https://github.com/squalou/google-chat-linux/issues/65) 89 | 90 | ### 5.24.19-1 91 | 92 | - Update to electron 24 to support new functions added to google chat (quote reply among them). As a result, tray icon support gets worse. See https://github.com/squalou/google-chat-linux/issues/63 93 | - Clean some outdated icons. 94 | 95 | ### 5.21.19-1 96 | 97 | - https://github.com/squalou/google-chat-linux/issues/51 : manually set `NO_REDIRECT_URL` to solve login with custom OAuth SSO providers issues. Comma separated list of urls is accepted ! (see issue in github for more details) 98 | 99 | ### 5.21.18-3 100 | 101 | - remove `--ozone-platform-hint=auto` from default launcher 102 | - add a word in README about wayland 103 | 104 | ### 5.21.18-2 105 | 106 | - update to electron 21 107 | - set `--ozone-platform-hint=auto` to for better wayland support when available. (https://releases.electronjs.org/releases/stable?version=21&page=6&limit=2) 108 | 109 | ### 5.20.18-1 110 | 111 | - update to electron 20 112 | - Fix #54 (systray in wayland) 113 | 114 | 115 | ### 5.15.17-1 116 | 117 | Fix #40 : client breaks out to browser when login needed. 118 | 119 | ### 5.15.16-2 120 | 121 | Fix language support https://github.com/squalou/google-chat-linux/issues/42 122 | 123 | Add "languages": ["fr","en-US"] in the json to override default OS locale. 124 | 125 | ### 5.15.16-1 126 | 127 | Update to electron 15.3.2 128 | 129 | ### 5.14.16-1 130 | 131 | * Good Bye Themes ! 132 | * they never worked in new UI 133 | * they ceased to work in old UI 134 | * new UI has native dark theme support ... 135 | * ... no more theme support ! 136 | * Good bye Old UI support : it was finally completely disabled by Google. 137 | 138 | ### 5.14.15-1 139 | 140 | * electron 14 141 | * support native filechooser instead of GTK only. 142 | * make sure you install `xdg-desktop-portal-gtk` or `xdg-desktop-portal-kde` or xdg-desktop-portal-wlr ... depending on your DE and distrib. 143 | * `GTK_USE_PORTAL=1` is set at startup by the application. If it fails you may want to set it in your login script (.bashrc, /etc/profile.d/custom.sh ...) 144 | * logout / login and open google-chat-linux, whenever needing to use the filechooser it should use your DE default one. 145 | * details here https://tristan.partin.io/blog/2021/04/01/electron-linux-and-your-file-chooser 146 | 147 | ### 5.12.14-2 148 | - apply node dependencies security fixes 149 | 150 | ### 5.12.14-1 151 | - Fix #36 : google account login page stays inside client instead of opening in default browser 152 | - Improve #37 : click systray when window is visible without focus gives focus. When has focus : hides it. 153 | 154 | ### 5.12.13-2 155 | 156 | - Fix again old notifications : too many favicons were selected, the wrong ones got displayed. 157 | ### 5.12.13-1 158 | 159 | - Fix support for new chat UI notifications 160 | - Cleaner notification detection with ipcRenderer, thanks to @ankurk91 (check his fork here https://github.com/ankurk91/google-chat-electron) 161 | - Runs now in sandbox and with contextisolation 162 | 163 | ### 5.12.12-1 164 | 165 | Allow to open previous chat url to restore previous behaviour and fix notification in systray + theme support. (see #35) 166 | 167 | ### 5.12.11-2 168 | 169 | fix #34 : Additional 'Advanced' option to open urls using `xdg-open` rather than `shell.openExternal`. Works better for some users. 170 | ### 5.12.11-1 171 | 172 | Better windows support : 173 | - add taskbar menu to Quit 174 | - remove option to hide from taskbar (new windows paradigm tends to do without systray and use taskbar instead) 175 | 176 | ### 5.12.10-1 177 | 178 | Upgrade electron version to 12. 179 | 180 | With electron 12 : The default values of contextIsolation and worldSafeExecuteJavaScript are now true. #27949 #27502 181 | As a result in this app : contextIsolation is forced to `false` in order to have systray integration work as previously. 182 | 183 | ### 5.11.10-2 and -3 184 | 185 | Upgrade dependencies (vulnerability fix) 186 | 187 | Add "windows" packaging configuration : `npm run dist` produces a nice installer on windows platform now :-) 188 | 189 | ### 5.11.10-1 190 | 191 | Add a secondary "dark theme" accessible in "View" Menu. 192 | 193 | ### 5.11.9-1 194 | 195 | Move to **electron 11** that brings *Apple M1* native support. 196 | 197 | Change versioning scheme : 198 | - first number is internal architecture, won't change anytime soon 199 | - second is the electron version. 200 | - third is a 'feature' level 201 | - dash-number is a packaging number : same features, only minor bugfix and packaging changes : no news, only better things 202 | 203 | 204 | ### 0.5.8-1 205 | 206 | add FAKE `libappindicator3.so` and `libappindicator3.so1` in /opt/google-chat-linux to fix left click on TRay icon. 207 | 208 | **In case tere are side effects** ... remove the files, and open an issue, I'll see what I can do. 209 | 210 | see https://github.com/electron/electron/issues/14941 211 | 212 | ### 0.5.7 : support for external auth system at login page 213 | 214 | 0.5.7-5: add "third party auth" in systray menu, and fix a bug there by the way 215 | 216 | 0.5.7-4: on Force reload : re-apply theme if needed. Useful when Gogole forces a "REFRESH" action that tends toremove theme. 217 | 218 | 0.5.7-3: cleanup Menu. Use `Menu / Use temporary thirs party auth mode` in case your login doesn't work (see 0.5.7-2) 219 | 220 | 0.5.7-2: better support for external auth system at login page (starting with 0.5.7-1 but incomplete) 221 | 222 | - add option to disable Node Integration (from Menu). It breaks systray **but** may help with some auth redirection mechanisms (Atlassian Crowd for instance) 223 | - add option to keep all URL's inside electron client (from Menu), to help debugging some situations 224 | - always keep chat.google.com url inside client 225 | 226 | Google 'refresh' action stays inside client. 227 | 228 | The "Menu" shows up when hitting "Alt" key. 229 | 230 | ### 0.5.6 231 | 232 | electron 10 233 | 234 | ### 0.5.2 235 | 236 | customize spellcheck language (Windows + Linux) by editing `$HOME/.config/google-hangouts-chat-linux.json`, add `"languages": ["fr","en-US"]` in the json for instance to override default OS locale. 237 | 238 | ### 0.5.1 239 | 240 | Alt-Left / Alt-Right navigation shortcuts are disabled now by default. Reenable them in menu (restart required) 241 | 242 | ### 0.5.0-3 243 | 244 | Electron 9, which has reverted to 'old' systray integration. Should help someDE users to have this work. 245 | 246 | **Notes** 247 | 248 | * going back to electron 8 or previous is not as simple as changing version in package.json anymore. Look at package.json history to see the changes. 249 | * packages 0.5.0-1 and 0.5.0-2 do **not** work. Avoid tthem, use 0.5.0-3. 250 | 251 | 252 | ### 0.4.4 253 | 254 | - Secure tray icon change. 255 | - Avoid renderer processes to be restarted on every navigation. 256 | - Add 'About' menu to display version. 257 | - Restart app to 'keep minimized' configuration takes effect. 258 | 259 | ### 0.4.3 260 | 261 | - by default keep window in windows list when 'closing'. Add a 'view' menu to change back to previous behaviour : hiding on 'close'. This is done for DE that does NOT display systray of eletron 8 : *Cinnamon* for instance, any DE that doesn't handle well 'appindicator' in electron 8. 262 | - At first run, shows in windows list on close to prevent lost windows on those DE. 263 | - Change color of minimized window icon for those DE. 264 | 265 | ### 0.4.2 adds a "Menu" in app, press Alt to reveal it. It's the same as systray menu, for OS's where systray is not (yet?) properly supported 266 | 267 | from 0.4.1 : update to electron 8 for better Systray compatibility on linux when appindicator lib is used. see https://github.com/electron/electron/issues/21445. You may have to install some plugnis depending on your DE (xfce4-statusnotifier-plugin for instance) 268 | 269 | from 0.4.0 on : **systray** color works without hidden window, so everything should be ok again! 270 | 271 | from 0.3.2 on : **systray icon color change IS BACK** through the use of a hidden window ... kind of dirty but seems to work. 272 | 273 | on version 0.3.0 and 0.3.1 : **systray icon color change does not work anymore**, I volontarily removed it. Why ? It requires "nodeIntegration: true" in electron, which in turn breaks the "Search" for people (Ctrl K), which is much, much more useful than the icon color change. 274 | 275 | As a compensation, there is a custom theme ... :) (activate it from systray menu) 276 | 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tray has changed ... again 2 | https://github.com/electron/electron/pull/36472 3 | https://github.com/electron/electron/pull/36333 4 | https://github.com/electron/electron/issues/36602 5 | 6 | broken *again* on electron 22+ 7 | 8 | See below Changelog **5.24.19-1** 9 | 10 | # google-chat-linux-git 11 | 12 | An electron-base client for Google Hangouts Chat, since Google didn't see fit to provide one. 13 | 14 | upstream project : https://github.com/robyf/google-chat-linux 15 | clever other for : https://github.com/ankurk91/google-chat-electron 16 | 17 | See [Systray support](#systray-support) notes. 18 | 19 | ## AppImage support 20 | 21 | See further below if you ever need to build an AppImage for your distribution. 22 | 23 | ## Windows support 24 | 25 | Electron is cross platform. I added the minimum required tweaks to have a decently working app on Windows. You can install the _Setup_.exe from [releases](https://github.com/squalou/google-chat-linux/releases). 26 | 27 | There will be "SmartScreen" warning about how unsafe this `.exe` is, Windows pretending it has detected something nasty and is protecting you. **I Don't Care** and won't buy a certificate. 28 | 29 | If you're not happy with this, build from sources with `npm install && npm run dist` or get a proper OS with a proper distribution system. 30 | 31 | ## Linux dependencies 32 | 33 | Starting with 5.14.x, xdg-desktop-portal must be installed. It's probably already the case on most distributions. [see here](README.md/#support-native-filechooser) 34 | 35 | Dependency is taken care of in AUR Arch package and Debian package. 36 | 37 | ## Custom CSS 38 | 39 | To inject custom CSS, create a file called `custom.css` in `~/.config/google-chat-linux` for Linux or `%APPDATA%\google-chat-linux\` for Windows. 40 | 41 | To find out how Google Chat makes their themes, go to Developer Tools (Ctrl + Shift + I) > Sources > `gtn-roster-iframe-id (world)` > `(no domain)` > the large purple CSS file (begins with `/_/scs/mss-static/_/ss/k=boq-dynamite.DynamiteWebUi...`). The file is pretty scattered, but searching for terms such as 42 | 43 | - `color-scheme: light;` 44 | - `:root {` 45 | - `[data-theme=dark] {` 46 | 47 | can prove to be helpful. 48 | 49 | ## Open Google Chat URLs from web browser in the app 50 | 51 | You can configure your web browser to detect Google Chat URLs and open them in the Google Chat Alt application: 52 | 53 | Step 0: Install Google Chat Alt. 54 | 55 | Step 1: Install a user script manager. See [Step 1 on Greasy Fork](https://greasyfork.org/) for various options. 56 | 57 | Note: the script manager must be able to cope with content security policy (CSP) headers. Tampermonkey on Firefox is known to work. 58 | 59 | Step 2: Install the user script [Google Chat Alt landing page](https://greasyfork.org/en/scripts/481609-google-chat-alt-landing-page) by clicking the green install button on the user script's page, and your user script manager will ask you to confirm the install. 60 | 61 | Step 3: Try it out by navigating e.g. to https://mail.google.com/chat/ in your web browser. If the user script is working correctly instead of Google Chat web UI your browser should ask you: 62 | 63 | > Allow this site to open the gchat link with Google Chat Alt? 64 | 65 | You can check the checkmark: 66 | 67 | > Always allow [...] to open gchat links 68 | 69 | Once you press the button Open Link, Google Chat Alt will be either started or (if you have it already running) restore its window. You can then close the tab in your browser. 70 | 71 | Note: If navigating to Google Chat opens Google Chat web UI the user script manager might not be compatible with the user script, Google might have changed something on the web site or you might have failed to install the script properly. 72 | 73 | Note: If navigating to Google Chat opens landing page that says "Launching Google Chat Alt" but web browser doesn't ask to open the link in the application, verify if you have installed Google Chat Alt. Running `gio mime x-scheme-handler/gchat` should display `google-chat-linux.desktop` as a default application. 74 | 75 | On technical level this functionality works in following way: Google Chat Alt uses [XDG Desktop file to claim to support URI scheme](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#uri-schemes-handling) gchat://. No browser is able to handle this (made up) URI scheme but we use this to pass URI to the app. App looks for URI with this scheme and if found, it replaces gchat:// with https:// and navigates to that address. This should work for channel and direct message links out of box. 76 | 77 | ## Wayland support 78 | 79 | ### TL/DR; 80 | 81 | Improved with electron 29 : declare environment variable `export ELECTRON_OZONE_PLATFORM_HINT=auto` (values can be `auto`, `x11`, `wayland`), for instance in `.zshenv`, no need to use --ozone-platform flag, so no need to use custom .desktop file ! 82 | 83 | Improved with electron 27 : WaylandWindowDecorations is now enabled by default. 84 | 85 | Run with `--ozone-platform=wayland`. 86 | 87 | 88 | ### Detailed story 89 | 90 | Electron 20 introduced a command line to mimic chromium way to switch to Wayland if available. Simply run electron ap with `--ozone-platform-hint=auto` to make it use Wayland if available, Xorg else. The default value is `default` and does not try Wayland at all. 91 | 92 | This has side effects on window decoration (absent on Gnome for instance, fixed with electron 27). 93 | 94 | Another side effect is the lack of notification in systray. (Which only works already with a workaround on Gnome, see further). 95 | 96 | So you have to enable yet another feature flag (see https://github.com/electron/electron/pull/29618) `--enable-features=WaylandWindowDecorations` 97 | 98 | So, feel free to enable **both** options if you want, it will work with this lmitation. 99 | 100 | I first enforced the ozone flag in the .desktop shortcut, bad idea sorry about that ;-), forget the 5.21-18-2 version. 101 | 102 | I didn't find a way to make this a runtime option, this setting must be taken into account very early in electron startup I'm not even sure it's possible to do that. 103 | 104 | So, **to use electron's Wayland rendering** edit `/usr/share/applciations/google-chat-linux.desktop` and add `--enable-features=WaylandWindowDecorations --ozone-platform-hint=auto`. 105 | 106 | ## CHANGELOG and news 107 | 108 | See full [CHANGELOG](./CHANGELOG.md). 109 | 110 | ### 5.29.23-1 111 | 112 | bump electron to 29 with better wayland support. Define `ELECTRON_OZONE_PLATFORM_HINT=auto` to take advantage of it. Values can be `auto`, `wayland`, `x11`, with `auto` being a reasonable default. 113 | 114 | Set it in `.zshenv` for instance, and logout / login again. Must be in a placed sourced when .desktop applications launchers are used, will certainly be DE dependant. 115 | 116 | ### 5.27.23-6 5.27.23-5 5.27.23-4 and 5.27.23-3 117 | 118 | empty release - AUR only pkg release version 119 | 120 | ### 5.27.23-2 121 | 122 | Add a menu in 'View' to change tray icon theme. 123 | 124 | ### 5.27.23-1 125 | 126 | Add support for several iconThemes, to match some 'monochrome' desktop themes. 3 values are supported : 127 | 128 | * `default` : the good old green ones 129 | * `colored` : the new colored google ones 130 | * `mono` : an attempt at monochrome icon theme 131 | 132 | Edit `~/.config/google-hangouts-chat-linux.json` and set `"iconTheme":"colored"` for instance, then **restart**. 133 | 134 | No GUI setting for now. 135 | 136 | ### 5.27.22-4 137 | 138 | Fix systray notification (favicon name changed on google side) see https://github.com/squalou/google-chat-linux/issues/87 139 | 140 | ### 5.27.22-3 141 | 142 | Add a nice pseudo-protocol support : open gchat:// urls in client instead of browser. See "Open Google Chat URLs from web browser in the app" above. 143 | Thanks again https://github.com/pbabinca ! 144 | 145 | ### 5.27.22-2 146 | 147 | * Desktop shortcut name changed to `Google Chat Alt` 148 | * `rpm` build available 149 | * build using containers available 150 | 151 | And thank you https://github.com/pbabinca for all this ! 152 | 153 | ### 5.27.22-1 154 | 155 | Update electron to 27.0.3 with Wayland improvements. 156 | 157 | ### 5.24.22-1 158 | 159 | Fix https://github.com/squalou/google-chat-linux/issues/62 : thank you https://github.com/ThatOneCalculator ! 160 | 161 | Minor electron update (24.8.5) 162 | 163 | ### 5.24.21-1 164 | 165 | Fix https://github.com/squalou/google-chat-linux/issues/69 : download of attachments. (was broken by a side effect of https://github.com/squalou/google-chat-linux/issues/67 ) 166 | 167 | ### 5.24.20-2 168 | 169 | repackage, fix vulnerabilities 170 | 171 | ### 5.24.20-1 172 | 173 | Prevent several instances of google chat linux to be launched. (https://github.com/squalou/google-chat-linux/issues/67) 174 | 175 | ### 5.24.19-4 176 | 177 | Clean MORE links (https://github.com/squalou/google-chat-linux/issues/66) 178 | 179 | ### 5.24.19-3 180 | 181 | Clean links from google decoration before opening them (https://github.com/squalou/google-chat-linux/issues/66) 182 | 183 | ### 5.24.19-2 184 | 185 | Fix open links in external browser (https://github.com/squalou/google-chat-linux/issues/65) 186 | 187 | ### 5.24.19-1 188 | 189 | Update to electron 24. 190 | 191 | **Why ?** 192 | 193 | Some functionalities are blocked for older browsers, for instance "quoted-reply", which has finally arrived in google chat. (2023, hello guys, wake up) 194 | 195 | **Bad news** 196 | 197 | electron sucks with Tray ... again 198 | 199 | gnome users will want to try https://extensions.gnome.org//extension/615/appindicator-support/ 200 | 201 | instead of Ubuntu Appindicators (if in use on the distro) 202 | 203 | Note that ... looks like it's a crappy situation (again) 204 | - Ubuntu Appindicator required for electron -> 21, wont work for 22+ 205 | - Appindicator and KStatusNotifierItem required for electron 22+ 206 | 207 | AND THEN AGAIN ! Double click must be used instead of single click, 208 | 209 | and that shit is on Gnome only, and of course poor to no wayland support 210 | 211 | Tray F***ng Icons still failing in 2023, not like it's been around since 28 years. 212 | 213 | 214 | **Weird news** though : notification on application shortcut seems to work on Gnome ... only when app is launched from sources ! (`./google-chat-linux.sh`) 215 | 216 | I'm probably definitively done with this electron nightmare. 217 | 218 | Best solution is probably hte ArchLinux packagine approach : without electron embedded/packaged. go wonder. 219 | 220 | I've added `google-chat-linux-nvm-launcher.sh` that uses nvm, uses `nvm use --lts`, then starts `./google-chat-linux/google-chat-linux.sh`, 221 | it can be referenced in a local .desktop file, and it will work. That's hjow I personally launch it. 222 | 223 | 224 | ### 5.21.19-1 225 | 226 | - https://github.com/squalou/google-chat-linux/issues/51 : manually set `NO_REDIRECT_URL` to solve login with custom OAuth SSO providers issues. Comma separated list of urls is accepted ! (see issue in github for more details) 227 | 228 | 229 | ## versioning scheme 230 | 231 | Starting with 5.11.9-1 : 232 | 233 | - first number is internal architecture, won't change anytime soon 234 | - second is the electron version. 235 | - third is a 'feature' level 236 | - dash-number is a packaging number : same features, only minor bugfix and packaging changes : no news, only better things 237 | 238 | ## support native filechooser 239 | 240 | * make sure you install `xdg-desktop-portal` or `xdg-desktop-portal` or `xdg-desktop-portal-kde` or `xdg-desktop-portal-wlr` ... depending on your DE and distrib. 241 | * logout / login and open google-chat-linux, whenever needing to use the filechooser it should use your DE default one. 242 | 243 | Troubleshooting 244 | 245 | * in case nothing happens when needing to upload / download a file 246 | * launch from console, and check for `Can't open portal file chooser: GDBus.Error`. If 247 | it is displayed, then your `xdg-desktop-portal` is not installed. 248 | 249 | * if the wrong filechooser is displayed (gtk on kde), make sure `GTK_USE_PORTAL=1` is set. It should be set by the app itself, you may want to set it yourself and check if it works better. `export GTK_USE_PORTAL=1; /opt/google-chat-linux/google-chat-linux` for instance 250 | 251 | * if necessary set `GTK_USE_PORTAL=1` in your login script (`/etc/profile.d/custom.sh`, or `$HOME/.bashrc`, whatever). 252 | 253 | ## configure spellcheck language 254 | 255 | After first run, quit, then edit $HOME/.config/google-hangouts-chat-linux.json, add "languages": ["fr","en-US"] in the json to override default OS locale. 256 | 257 | ## auth with third party provider 258 | 259 | If your login redirects to some OAuth provider (other than Google), login may fail. 260 | 261 | In Menu (Alt, or systray right click), choose `use third party auth mode`. Login should work but you loose some features (systray related). Use the same menu after login to restore normal mode. Repeat anytime login is required. 262 | 263 | **NEW** Since 5.21.19-1 you can also set `NO_REDIRECT_URL` environment variable to the url (or a comma separated list of urls) of the OAuth provider. 264 | 265 | ## Freeze ? 266 | 267 | If sometimes the app looks like beeing frozen, and comes back to life after a few seconds, you may want to try `--disable-gpu` flag when starting the app from a terminal. It is a known issue with electron, especially with intel video drivers (you may want to try modesetting driver instead by the way). 268 | 269 | You may want to /usr/share/applciations/google-chat-linux.desktop and add the flag on the `Exec` line. (do it at each new version or copy the .desktop file to `$HOME/.local/share/applications/`) 270 | 271 | ## build and run 272 | 273 | ```sh 274 | npm install 275 | ./google-chat-linux.sh 276 | ``` 277 | 278 | ## make it work manually 279 | 280 | ```sh 281 | npm install electron 282 | export PATH=$HOME/node_modules/.bin:$PATH 283 | ``` 284 | 285 | fix the rights on sandbox executable as the error message will suggest: 286 | 287 | ```sh 288 | sudo chown root:root $HOME/node_modules/electron/dist/chrome-sandbox && sudo chown 4755 $HOME/node_modules/electron/dist/chrome-sandbox 289 | electron . 290 | ``` 291 | 292 | OR if you're in a hurry : 293 | 294 | ```sh 295 | export ELECTRON_DISABLE_SANDBOX=true; export NODE_OPTIONS="--no-force-async-hooks-checks"; electron . 296 | ``` 297 | 298 | ## Linux packages 299 | 300 | ### Arch (Manjaro, Anarchy) 301 | 302 | a package 'google-chat-linux-bin' is availabe on AUR for Arch Linux and derivatives. 303 | 304 | ### Debian based (Ubuntu, Mint ...) 305 | 306 | [Have a look in tags](https://github.com/squalou/google-chat-linux/tags) section, download the relevant .deb file and install with `sudo dpkg -i ` command. (Thank you CYOSP ;-) ) 307 | 308 | **Tested on** Ubuntu 18.04, 20.04, 21.04, Mint 20.1 309 | 310 | **Note** some environment variables are set in index.js : GTK_USE_PORTAL, ELECTRON_DISABLE_SANDBOX and NODE_OPTIONS="--no-force-async-hooks-checks". This *should* work. Else, set them manually. 311 | 312 | ### rpm based (Fedora) 313 | 314 | [Have a look in tags](https://github.com/squalou/google-chat-linux/tags) section, download the relevant .rpm file and install with `sudo dnf install ` command. 315 | 316 | ### AppImage (useful for arm64 and other distributions) 317 | 318 | edit `package.json`, replace target `deb` by `AppImage`. 319 | 320 | before 321 | 322 | ```json 323 | "build": { 324 | "appId": "Google Chat Linux", 325 | "linux": { 326 | "desktop": { 327 | "Name": "Google Chat Alt", 328 | "MimeType": "x-scheme-handler/gchat;" 329 | }, 330 | "category": "Network;InstantMessaging", 331 | "target": "deb" // <--here remove deb and put AppImage 332 | }, 333 | } 334 | ``` 335 | 336 | then `npm run dist` 337 | 338 | The package will be built in `dist` subfolder. AppImage is fine for instance on Asahi Linux on Apple silicium. 339 | 340 | 341 | ### manually build a deb package 342 | 343 | You have two options - either install all build dependencies and then run : 344 | 345 | ```sh 346 | npm run dist 347 | ``` 348 | 349 | Or install docker (or podman) container engine and then create a local container with all build dependencies : 350 | 351 | ```sh 352 | npm run container:setup 353 | ``` 354 | 355 | and then create the package by running: 356 | 357 | ```sh 358 | npm run container:build:deb 359 | ``` 360 | 361 | In the end you'll end up with .deb file in `dist/`. Run for instance `sudo dkpg -i dist/google-chat-linux*.deb`. 362 | 363 | Installation of the .deb file is tested under Ubuntu, and works fine. Under Mint it installs well but react with emotes crashes the app. Go wonder. 364 | 365 | NOTE : to run from a terminal you'll have to : 366 | 367 | - either `sudo chown root:root /opt/google-chat-linux/chrome-sandbox && sudo chown 4755 /opt/google-chat-linux/chrome-sandbox` after the .deb is inYYstalled 368 | - or run `export ELECTRON_DISABLE_SANDBOX=true; export NODE_OPTIONS="--no-force-async-hooks-checks"` before the launch of `/opt/google-chat-linux/google-chat-linux` 369 | 370 | The provided .desktop file takes care of it, so running from your desktop launcher will work. 371 | 372 | ## Windows package 373 | 374 | A package is available in [releases](https://github.com/squalou/google-chat-linux/releases). Or else build it yourself : 375 | 376 | ```sh 377 | npm run dist 378 | ``` 379 | A _Setup_.exe will be built under `\dist\` directory. 380 | 381 | 382 | ### rpm based distributions (Fedora) 383 | 384 | Install podman (or docker) container engine and then create a local container with all build dependencies : 385 | 386 | ```sh 387 | npm run container:setup 388 | ``` 389 | 390 | and then create the package by running: 391 | 392 | ```sh 393 | npm run container:build:rpm 394 | ``` 395 | 396 | In the end you'll end up with .rpm file in `dist/`. Run for instance `sudo dnf install dist/google-chat-linux*.rpm`. 397 | 398 | ## Systray Support 399 | 400 | **Note** : from 0.5 on, electron 9 bring back Tray integration BUT "click" events are ignored. 401 | 402 | ### Workaround for the tray 'click' event 403 | 404 | 405 | ```bash 406 | sudo touch /opt/google-chat-linux/libappindicator3.so 407 | sudo touch /opt/google-chat-linux/libappindicator3.so.1 408 | ``` 409 | 410 | this way : left click raises the window again ! 411 | 412 | These files are added in distributed packages... in hope there are no side effects. 413 | 414 | 415 | https://github.com/electron/electron/issues/14941 416 | 417 | ### More precisely: 418 | 419 | * On Linux the app indicator will be used if it is supported, otherwise GtkStatusIcon will be used instead. 420 | * When app indicator is used on Linux, the click event is ignored. 421 | * https://www.electronjs.org/docs/api/tray 422 | * There is sadly nothing I can do about it. (except cry a bit as such nonsenses and wonder how other apps (slack, telegram) deal with all this) 423 | 424 | -------------------------------------------------------------------------------- /src/window.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain, shell, Menu } = require("electron"); 2 | const path = require("path"); 3 | const pathsManifest = require("./paths"); 4 | const { platform } = require("process"); 5 | const ConfigManager = require("./configs"); 6 | 7 | const DEFAULT_ICONS = 'default' 8 | const COLORED_ICONS = 'colored' 9 | const MONO_ICONS = 'mono' 10 | 11 | let mainWindow; 12 | let isQuitting = false; 13 | let keepMinimized = true; 14 | let startHidden = true; 15 | let enableKeyboardShortcuts = true; 16 | let enableNodeIntegration = true; 17 | let openUrlInside = false; 18 | let useXdgOpen = false; 19 | let thirdPartyAuthLoginMode = false; 20 | 21 | const noRedirectUrlArrayHardcoded = ["accounts/SetOSID?authuser=0&continue=https%3A%2F%2Fchat.google.com" 22 | , "accounts.google.com" 23 | , "accounts.youtube.com" 24 | , "mail.google.com/ServiceLogin" 25 | , "mail.google.com/chat" 26 | , "https://chat.google.com/" 27 | ]; 28 | 29 | const dirty_start_redirect_url = 'https://www.google.com/url?'; 30 | const dirty_end_redirect_url_array = ['&source=chat&', '&uct=', '&usg=']; 31 | 32 | let urlNotRedirectedTmp; 33 | if (process.env.NO_REDIRECT_URL) { 34 | urlNotRedirectedTmp = noRedirectUrlArrayHardcoded.concat(process.env.NO_REDIRECT_URL.toString().split(",")); 35 | } else { 36 | urlNotRedirectedTmp = noRedirectUrlArrayHardcoded; 37 | } 38 | const urlNotRedirected = urlNotRedirectedTmp; 39 | console.log("not redirected urls:"); 40 | console.log(urlNotRedirected); 41 | 42 | const clean_url = (url) => { 43 | //console.log("cleaning url "+url); 44 | init_url = url; 45 | try { 46 | if (url.startsWith(dirty_start_redirect_url)) { 47 | url = url.substr(dirty_start_redirect_url.length); 48 | // now that initial dirt is away, there are still chars to remove. Find first http string and remove what's before 49 | ht = url.indexOf('http'); 50 | url = url.substr(ht); 51 | } 52 | //console.log("cleaning url step 1 "+url); 53 | dirty_end_redirect_url_array.forEach(d => { 54 | e = url.indexOf(d); 55 | if (e > 0) { 56 | url = url.substr(0, e); 57 | } 58 | }) 59 | url = decodeURIComponent(url); 60 | //console.log("cleaning url step 2 "+url); 61 | console.log("cleaning url from " + init_url + " to " + url); 62 | return url; 63 | } 64 | catch (e) { 65 | console.log("error while cleaning url " + url + "\n" + e); 66 | return init_url; 67 | } 68 | } 69 | 70 | ipcMain.on('open-link', (evt, href) => { 71 | href = clean_url(href); 72 | shell.openExternal(href); 73 | }); 74 | 75 | const setIsQuitting = (b) => { 76 | isQuitting = b; 77 | }; 78 | 79 | const getOpenUrlInside = () => { 80 | return openUrlInside; 81 | }; 82 | 83 | const setOpenUrlInside = (b) => { 84 | openUrlInside = b; 85 | }; 86 | 87 | const getUseXdgOpen = () => { 88 | return useXdgOpen; 89 | }; 90 | 91 | const setUseXdgOpen = (b) => { 92 | useXdgOpen = b; 93 | }; 94 | 95 | const getEnableKeyboardShortcuts = () => { 96 | return enableKeyboardShortcuts; 97 | }; 98 | 99 | const setEnableKeyboardShortcuts = (b) => { 100 | enableKeyboardShortcuts = b; 101 | }; 102 | 103 | const getEnableNodeIntegration = () => { 104 | return enableNodeIntegration; 105 | }; 106 | 107 | const setEnableNodeIntegration = (b) => { 108 | enableNodeIntegration = b; 109 | }; 110 | 111 | const getThirdPartyAuthLoginMode = () => { 112 | return thirdPartyAuthLoginMode; 113 | }; 114 | 115 | const setThirdPartyAuthLoginMode = (b) => { 116 | thirdPartyAuthLoginMode = b; 117 | }; 118 | 119 | const onKeepMinimizedClicked = (keep) => { 120 | if (keep !== keepMinimized) { 121 | keepMinimized = keep; 122 | app.relaunch(); 123 | onQuitEntryClicked(); 124 | } 125 | } 126 | 127 | const onStartHiddenClicked = () => { 128 | startHidden = !startHidden; 129 | app.relaunch(); 130 | onQuitEntryClicked(); 131 | } 132 | 133 | const onQuitEntryClicked = () => { 134 | setIsQuitting(true); 135 | app.quit(); 136 | } 137 | 138 | const onSetIconThemeClicked = (theme) => { 139 | iconTheme = theme; 140 | app.relaunch(); 141 | onQuitEntryClicked(); 142 | } 143 | 144 | const onToggleThirdPartyAuthLoginMode = () => { 145 | setThirdPartyAuthLoginMode(!getThirdPartyAuthLoginMode()); 146 | if (getThirdPartyAuthLoginMode()) { 147 | setOpenUrlInside(true); 148 | setEnableNodeIntegration(false); 149 | } else { 150 | setOpenUrlInside(false); 151 | setEnableNodeIntegration(true); 152 | } 153 | app.relaunch(); 154 | onQuitEntryClicked(); 155 | } 156 | 157 | const onToggleOpenUrlInside = () => { 158 | setOpenUrlInside(!getOpenUrlInside()); 159 | buildMenu(); 160 | } 161 | 162 | const onToggleUseXdgOpen = () => { 163 | setUseXdgOpen(!getUseXdgOpen()); 164 | if (getUseXdgOpen()) { 165 | setOpenUrlInside(false); 166 | } 167 | buildMenu(); 168 | } 169 | 170 | const onToggleKeyboardShortcuts = () => { 171 | setEnableKeyboardShortcuts(!getEnableKeyboardShortcuts()); 172 | app.relaunch(); 173 | onQuitEntryClicked(); 174 | } 175 | 176 | const onToggleNodeIntegration = () => { 177 | setEnableNodeIntegration(!getEnableNodeIntegration()); 178 | app.relaunch(); 179 | onQuitEntryClicked(); 180 | } 181 | 182 | const onForceReloadClicked = () => { 183 | mainWindow.webContents.reload(); 184 | } 185 | 186 | const updateIcon = (icon) => { 187 | try { 188 | mainWindow.setIcon(icon); 189 | } catch (e) { 190 | //do nothing ... fails on some distribs / OS / window managers 191 | console.log("Failed to update window icon :-(") 192 | console.log(e) 193 | } 194 | } 195 | 196 | const setOverlayIcon = () => { 197 | try { 198 | mainWindow.setOverlayIcon(pathsManifest.OVERLAY_NEW_NOTIF, "!"); 199 | } catch (e) { 200 | //do nothing ... fails on some distribs / OS / window managers 201 | //console.log(e) 202 | } 203 | } 204 | 205 | const cleanOverlayIcon = () => { 206 | try { 207 | mainWindow.setOverlayIcon(null, ""); 208 | } catch (e) { 209 | //do nothing ... fails on some distribs / OS / window managers 210 | //console.log(e) 211 | } 212 | } 213 | 214 | const getBrowserWindowOptions = (config) => { 215 | // sandbox still required for url opens 216 | return { 217 | "title": process.title, 218 | "autoHideMenuBar": true, 219 | "webPreferences": { 220 | "nodeIntegration": config.enableNodeIntegration, 221 | "contextIsolation": true, 222 | "sandbox": false, 223 | "spellcheck": true, 224 | "preload": path.join(__dirname, 'faviconChanged.js'), 225 | }, 226 | "show": false, 227 | "backgroundColor": "#262727", 228 | "icon": pathsManifest.normal(), 229 | } 230 | } 231 | 232 | const getExtraOptions = () => { 233 | return { 234 | "name": "Google Hangouts Chat for Linux", 235 | "url": "https://mail.google.com/chat/u/0", 236 | "openLocally": true 237 | }; 238 | } 239 | 240 | const doNotRedirect = (url) => { 241 | return urlNotRedirected.some((e) => url.includes(e)); 242 | } 243 | 244 | const downloadUrl = (url) => { 245 | var isDownloadAttachment = url.startsWith("https://chat.google.com/") && url.indexOf("get_attachment_url?url_type=DOWNLOAD_URL")>-1; 246 | // console.log("is it a download url : " + isDownloadAttachment); 247 | return isDownloadAttachment; 248 | } 249 | 250 | const handleRedirect = (e, url) => { 251 | // return true if redirect was trapped here, else false, in order to continue with 'default' behaviour (useful for download attachements) 252 | // 253 | // leave redirect for double auth mechanism, trap crappy blocked url link 254 | // console.log(url) 255 | // console.log(e) 256 | var handled = false; 257 | if (e !== undefined && url.includes("about:blank")) { 258 | handled = true; 259 | e.preventDefault(); 260 | } else if (!downloadUrl(url) && !openUrlInside && !doNotRedirect(url)) { 261 | handled = true; 262 | url = clean_url(url); 263 | if (process.platform === 'linux' && getUseXdgOpen()) { 264 | require('child_process').exec('xdg-open ' + url); 265 | } else { 266 | shell.openExternal(url); 267 | } 268 | if (e !== undefined) e.preventDefault(); 269 | } 270 | return handled; 271 | }; 272 | 273 | const getURL = (commandLine) => { 274 | const foundItem = commandLine.find(item => item.startsWith('gchat://')); 275 | return foundItem ? foundItem.replace('gchat://', 'https://') : null; 276 | }; 277 | 278 | const initializeWindow = (config) => { 279 | const gotTheLock = app.requestSingleInstanceLock(undefined) 280 | if (!gotTheLock) { 281 | console.log("Another instance is already running ! aborting"); 282 | app.quit() 283 | } else { 284 | app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => { 285 | if (mainWindow) { 286 | if (mainWindow.isMinimized()) { mainWindow.restore() } 287 | mainWindow.show() 288 | mainWindow.focus() 289 | const url = getURL(commandLine) 290 | if (url) { 291 | mainWindow.loadURL(url) 292 | } 293 | } 294 | }) 295 | } 296 | const bwOptions = (config && config.bounds) ? Object.assign(getBrowserWindowOptions(config), config.bounds) : getBrowserWindowOptions() 297 | const extraOptions = getExtraOptions(); 298 | keepMinimized = (config && config.keepMinimized); 299 | startHidden = (config && config.startHidden); 300 | enableKeyboardShortcuts = (config && config.enableKeyboardShortcuts); 301 | enableNodeIntegration = (config && config.enableNodeIntegration); 302 | openUrlInside = (config && config.openUrlInside); 303 | useXdgOpen = (config && config.useXdgOpen); 304 | thirdPartyAuthLoginMode = (config && config.thirdPartyAuthLoginMode); 305 | iconTheme = (config && config.iconTheme) 306 | mainWindow = new BrowserWindow(bwOptions); 307 | 308 | let url = getURL(process.argv) 309 | if (!url) { 310 | url = extraOptions.url 311 | } 312 | mainWindow.loadURL(url); 313 | 314 | if (config.languages !== undefined) { 315 | const ses = mainWindow.webContents.session 316 | ses.setSpellCheckerLanguages(config.languages) 317 | } 318 | mainWindow.once('ready-to-show', () => { 319 | if (!startHidden) { 320 | mainWindow.show(); 321 | } 322 | 323 | const customCss = ConfigManager.loadCustomCss(); 324 | if (customCss === '') { 325 | console.log('No custom CSS found'); 326 | } 327 | else { 328 | mainWindow.webContents.insertCSS(customCss, { 329 | cssOrigin: 'author' 330 | }).then(result => { 331 | console.log('Custom CSS injected', result); 332 | }); 333 | } 334 | }); 335 | 336 | mainWindow.on('close', (e) => { 337 | if (isQuitting) { 338 | let isMaximized = mainWindow.isMaximized(); 339 | configsData = {}; 340 | configsData.bounds = mainWindow.getBounds(); 341 | configsData.wasMaximized = isMaximized; 342 | configsData.keepMinimized = keepMinimized; 343 | configsData.startHidden = startHidden; 344 | configsData.enableKeyboardShortcuts = enableKeyboardShortcuts; 345 | configsData.enableNodeIntegration = enableNodeIntegration; 346 | configsData.openUrlInside = openUrlInside; 347 | configsData.useXdgOpen = useXdgOpen; 348 | configsData.thirdPartyAuthLoginMode = thirdPartyAuthLoginMode; 349 | configsData.iconTheme = iconTheme; 350 | ConfigManager.updateConfigs(configsData); 351 | } else { 352 | e.preventDefault(); 353 | if (keepMinimized) { 354 | mainWindow.minimize() 355 | } else { 356 | mainWindow.hide(); 357 | } 358 | } 359 | }); 360 | 361 | mainWindow.webContents.on('will-navigate', handleRedirect); 362 | mainWindow.webContents.setWindowOpenHandler(({ url }) => { 363 | if (handleRedirect(undefined, url)){ 364 | return { action: 'deny' }; 365 | } else { 366 | return { action: 'allow' }; 367 | } 368 | }); 369 | 370 | buildMenu(); 371 | 372 | return mainWindow; 373 | } 374 | 375 | 376 | const getHideTick = () => { 377 | return keepMinimized ? '☐' : '☑'; 378 | } 379 | 380 | const getShowTick = () => { 381 | return keepMinimized ? '☑' : '☐'; 382 | } 383 | 384 | const getStartHiddenTick = () => { 385 | return startHidden ? '☑' : '☐'; 386 | } 387 | 388 | const getOpenUrlInsideTick = () => { 389 | return getOpenUrlInside() ? '☐' : '☑'; 390 | } 391 | 392 | const getUseXdgOpenTick = () => { 393 | return getUseXdgOpen() ? '☑' : '☐'; 394 | } 395 | 396 | const getIconThemeTick = (theme) => { 397 | return iconTheme === theme ? '☑' : '☐'; 398 | } 399 | 400 | const menuSubMenu = () => { 401 | 402 | return [ 403 | { 404 | label: 'Force reload', 405 | click: () => { 406 | onForceReloadClicked(); 407 | } 408 | }, { 409 | label: getEnableKeyboardShortcuts() ? "Disable alt left/right shortcuts (restart)" : "Enable alt left/right shortcuts (restart)", 410 | click: () => { 411 | onToggleKeyboardShortcuts(); 412 | } 413 | }, { 414 | type: 'separator' 415 | }, { 416 | label: getThirdPartyAuthLoginMode() ? "Back to regular mode after auth (restart)" : "Use third party auth mode (restart)", 417 | click: () => { 418 | onToggleThirdPartyAuthLoginMode(); 419 | } 420 | }, { 421 | type: 'separator' 422 | }, { 423 | label: "Quit", 424 | accelerator: 'CommandOrControl+Q', 425 | click: () => { 426 | onQuitEntryClicked(); 427 | } 428 | } 429 | ]; 430 | } 431 | 432 | const viewSubMenu = () => { 433 | if (platform === 'win32') { 434 | return [ 435 | { 436 | label: getStartHiddenTick() + ' Start hidden (restart)', 437 | click: () => { 438 | onStartHiddenClicked(); 439 | } 440 | } 441 | ] 442 | } else { 443 | return [ 444 | { 445 | label: getHideTick() + ' Hide from windows list when minimized (restart)', 446 | click: () => { 447 | onKeepMinimizedClicked(false); 448 | } 449 | }, { 450 | label: getShowTick() + ' Show in windows list when minimized (restart)', 451 | click: () => { 452 | onKeepMinimizedClicked(true); 453 | } 454 | }, { 455 | label: getStartHiddenTick() + ' Start hidden (restart)', 456 | click: () => { 457 | onStartHiddenClicked(); 458 | } 459 | }, { 460 | type: 'separator' 461 | }, { 462 | label: 'Tray icon theme (restart)', 463 | }, { 464 | label: getIconThemeTick(DEFAULT_ICONS) + ' ' + DEFAULT_ICONS, 465 | click: () => { 466 | onSetIconThemeClicked(DEFAULT_ICONS); 467 | } 468 | }, { 469 | label: getIconThemeTick(COLORED_ICONS) + ' ' + COLORED_ICONS, 470 | click: () => { 471 | onSetIconThemeClicked(COLORED_ICONS); 472 | } 473 | }, { 474 | label: getIconThemeTick(MONO_ICONS) + ' ' + MONO_ICONS, 475 | click: () => { 476 | onSetIconThemeClicked(MONO_ICONS); 477 | } 478 | } 479 | ] 480 | } 481 | } 482 | 483 | const advancedSubMenu = () => { 484 | let mn = [] 485 | mn.push({ 486 | label: 'You should probably not tweak things here :-)', 487 | }); 488 | mn.push({ 489 | type: 'separator' 490 | }); 491 | mn.push({ 492 | label: getOpenUrlInsideTick() + " Open URLs in external default browser", 493 | click: () => { 494 | onToggleOpenUrlInside(); 495 | } 496 | }); 497 | if (process.platform === 'linux') { 498 | mn.push({ 499 | label: getUseXdgOpenTick() + " Open URLs using xdg-open rather than default method", 500 | click: () => { 501 | onToggleUseXdgOpen(); 502 | } 503 | }); 504 | } 505 | mn.push({ 506 | label: getEnableNodeIntegration() ? "Disable Node integration (breaks icon color change) (restart)" : "Enable Node integration (enables icon color change) (restart)", 507 | click: () => { 508 | onToggleNodeIntegration(); 509 | } 510 | }); 511 | return mn; 512 | } 513 | 514 | const buildMenu = () => { 515 | const template = [ 516 | { 517 | label: 'Menu', 518 | submenu: menuSubMenu() 519 | }, { 520 | label: 'View', 521 | submenu: viewSubMenu() 522 | }, { 523 | label: 'Advanced', 524 | submenu: advancedSubMenu() 525 | }, { 526 | label: 'About', 527 | submenu: [ 528 | { 529 | label: app.name + ' ' + app.getVersion() 530 | }, { 531 | label: 'electron ' + process.versions.electron 532 | } 533 | 534 | ] 535 | }, { 536 | label: 'DevTools', 537 | accelerator: 'CommandOrControl+Shift+I', 538 | click: () => { 539 | mainWindow.webContents.openDevTools(); 540 | } 541 | 542 | } 543 | ] 544 | const menu = Menu.buildFromTemplate(template); 545 | Menu.setApplicationMenu(menu); 546 | } 547 | 548 | module.exports = { 549 | initializeWindow: initializeWindow, 550 | getEnableKeyboardShortcuts: getEnableKeyboardShortcuts, 551 | onToggleKeyboardShortcuts: onToggleKeyboardShortcuts, 552 | onForceReloadClicked: onForceReloadClicked, 553 | onQuitEntryClicked: onQuitEntryClicked, 554 | onToggleThirdPartyAuthLoginMode: onToggleThirdPartyAuthLoginMode, 555 | getThirdPartyAuthLoginMode: getThirdPartyAuthLoginMode, 556 | updateIcon: updateIcon, 557 | setOverlayIcon: setOverlayIcon, 558 | cleanOverlayIcon: cleanOverlayIcon 559 | } 560 | --------------------------------------------------------------------------------