Windows desktop integration fixes. (#9628)

* Windows desktop integration fixes: icons, notification messages, paths, jump list

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>

* Fixed typos and formatting

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>

---------

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>
This commit is contained in:
Denis 'GeneralGDA' Gladkiy
2025-08-01 09:36:42 +06:00
committed by GitHub
parent 3fe325bce0
commit e6fc4666c3
13 changed files with 192 additions and 38 deletions
+3
View File
@@ -0,0 +1,3 @@
!macro customInit
StrCpy $INSTDIR "$LOCALAPPDATA\Programs\HulyDesktop"
!macroend
+16 -2
View File
@@ -64,12 +64,26 @@
],
"artifactName": "Huly-windows-${version}.${ext}",
"verifyUpdateCodeSignature": false,
"icon": "./src/AppIcon.png",
"icon": "./src/AppIcon.ico",
"publish": {
"provider": "generic",
"url": "https://dist.huly.io",
"channel": "latest"
}
},
"extraResources": [
{
"from": "./dist/ui/public/icons/",
"to": "./icons/",
"filter": [
"**/*.ico"
]
}
]
},
"nsis": {
"include": "installer.nsh",
"installerIcon": "./src/AppIcon.ico",
"uninstallerIcon": "./src/AppIcon.ico"
},
"linux": {
"artifactName": "Huly-linux-${version}.${ext}",
Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+4 -4
View File
@@ -14,7 +14,7 @@
//
import { app, BrowserWindow } from 'electron'
import { MenuBarAction, StandardMenuCommandLogout, StandardMenuCommandSelectWorkspace, StandardMenuCommandOpenSettings } from '../ui/types'
import { MenuBarAction, CommandLogout, CommandSelectWorkspace, CommandOpenSettings } from '../ui/types'
export function dipatchMenuBarAction(mainWindow: BrowserWindow | undefined, action: MenuBarAction) {
if (mainWindow == null) {
@@ -33,13 +33,13 @@ export function dipatchMenuBarAction(mainWindow: BrowserWindow | undefined, acti
switch (action) {
case 'settings':
mainWindow.webContents.send(StandardMenuCommandOpenSettings)
mainWindow.webContents.send(CommandOpenSettings)
break;
case 'select-workspace':
mainWindow.webContents.send(StandardMenuCommandSelectWorkspace)
mainWindow.webContents.send(CommandSelectWorkspace)
break;
case 'logout':
mainWindow.webContents.send(StandardMenuCommandLogout)
mainWindow.webContents.send(CommandLogout)
break;
case 'exit':
app.quit();
+5 -5
View File
@@ -14,12 +14,12 @@
//
import { Menu, MenuItemConstructorOptions } from 'electron'
import { StandardMenuCommand, StandardMenuCommandOpenSettings, StandardMenuCommandSelectWorkspace, StandardMenuCommandLogout, } from '../ui/types'
import { Command, CommandOpenSettings, CommandSelectWorkspace, CommandLogout, } from '../ui/types'
const isMac = process.platform === 'darwin'
const isLinux = process.platform === 'linux'
export const addMenus = (sendCommand: (cmd: StandardMenuCommand, ...args: any[]) => void): void => {
export const addMenus = (sendCommand: (cmd: Command, ...args: any[]) => void): void => {
const template: MenuItemConstructorOptions[] = [
{
label: 'File',
@@ -27,15 +27,15 @@ export const addMenus = (sendCommand: (cmd: StandardMenuCommand, ...args: any[])
{
label: 'Settings',
accelerator: isLinux ? 'Ctrl+,' : 'Meta+,',
click: () => { sendCommand(StandardMenuCommandOpenSettings) }
click: () => { sendCommand(CommandOpenSettings) }
},
{
label: 'Select workspace',
click: () => { sendCommand(StandardMenuCommandSelectWorkspace) }
click: () => { sendCommand(CommandSelectWorkspace) }
},
{
label: 'Logout',
click: () => { sendCommand(StandardMenuCommandLogout) }
click: () => { sendCommand(CommandLogout) }
},
{ role: isMac ? 'close' : 'quit' }
]
+24 -18
View File
@@ -22,7 +22,7 @@ import { ProgressInfo, UpdateInfo } from 'electron-updater'
import WinBadge from 'electron-windows-badge'
import * as path from 'path'
import { Config, MenuBarAction, NotificationParams, StandardMenuCommandLogout, StandardMenuCommandSelectWorkspace, StandardMenuCommandOpenSettings } from '../ui/types'
import { Config, MenuBarAction, NotificationParams } from '../ui/types'
import { getOptions } from './args'
import { addMenus } from './standardMenu'
import { dipatchMenuBarAction } from './customMenu'
@@ -30,6 +30,8 @@ import { addPermissionHandlers } from './permissions'
import autoUpdater from './updater'
import { generateId } from '@hcengineering/core'
import { DownloadItem } from '@hcengineering/desktop-downloads'
import { setupWindowsSpecific } from './windowsSpecificSetup'
let mainWindow: BrowserWindow | undefined
let winBadge: any
@@ -39,7 +41,7 @@ const isWindows = process.platform === 'win32'
const isDev = process.env.NODE_ENV === 'development'
const sessionPartition = !isDev ? 'persist:huly' : 'persist:huly_dev'
const iconKey = path.join(app.getAppPath(), 'dist', 'ui', 'public', 'AppIcon.png')
const iconKey = path.join(app.getAppPath(), 'dist', 'ui', 'public', isWindows ? 'AppIcon.ico' : 'AppIcon.png')
const preloadScriptPath = path.join(app.getAppPath(), 'dist', 'main', 'preload.js')
const defaultWidth = 1440
@@ -200,7 +202,7 @@ function handleAuthRedirects (window: BrowserWindow): void {
}
function handleWillDownload (window: BrowserWindow): void {
window.webContents.session.on('will-download', (event, item) => {
window.webContents.session.on('will-download', (_event, item) => {
const key = generateId()
const notifyDownloadUpdated = (): void => {
@@ -303,10 +305,14 @@ const createWindow = async (): Promise<void> => {
}
}
if (false == isWindows) {
addMenus((cmd: string, ...args: any[]) => {
mainWindow?.webContents.send(cmd, ...args)
})
function sendCommand(cmd: string, ...args: any[]): void {
mainWindow?.webContents.send(cmd, ...args)
}
if (isWindows) {
setupWindowsSpecific(sendCommand)
} else {
addMenus(sendCommand)
}
contextMenu({
@@ -315,7 +321,7 @@ contextMenu({
showSelectAll: false
})
ipcMain.on('set-badge', (event, badge: number) => {
ipcMain.on('set-badge', (_event: any, badge: number) => {
app.dock?.setBadge(badge > 0 ? `${badge}` : '')
app.badgeCount = badge
@@ -324,11 +330,11 @@ ipcMain.on('set-badge', (event, badge: number) => {
}
})
ipcMain.on('dock-bounce', (event) => {
ipcMain.on('dock-bounce', (_event: any) => {
app.dock?.bounce('informational')
})
ipcMain.on('send-notification', (event, notificationParams: NotificationParams) => {
ipcMain.on('send-notification', (_event: any, notificationParams: NotificationParams) => {
if (Notification.isSupported()) {
const notification = new Notification(notificationParams)
@@ -341,13 +347,13 @@ ipcMain.on('send-notification', (event, notificationParams: NotificationParams)
}
})
ipcMain.on('set-title', (event, title) => {
ipcMain.on('set-title', (event: any, title: string) => {
const webContents = event.sender
const window = BrowserWindow.fromWebContents(webContents)
window?.setTitle(title)
})
ipcMain.on('set-combined-config', (event, config: Config) => {
ipcMain.on('set-combined-config', (_event: any, config: Config) => {
log.info('Config set: ', config)
setupCookieHandler(config)
@@ -363,7 +369,7 @@ ipcMain.on('set-combined-config', (event, config: Config) => {
void autoUpdater.checkForUpdatesAndNotify()
})
ipcMain.handle('get-main-config', (event, path) => {
ipcMain.handle('get-main-config', (_event: any, _path: any) => {
const cfg = {
CONFIG_URL: process.env.CONFIG_URL ?? '',
FRONT_URL,
@@ -373,11 +379,11 @@ ipcMain.handle('get-main-config', (event, path) => {
}
return cfg
})
ipcMain.handle('get-host', (event, path) => {
ipcMain.handle('get-host', (_event: any, _path: any) => {
return new URL(FRONT_URL).host
})
ipcMain.on('set-front-cookie', function (event, host: string, name: string, value: string) {
ipcMain.on('set-front-cookie', function (event: any, host: string, name: string, value: string) {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
const cv: CookiesSetDetails = {
@@ -428,7 +434,7 @@ if (!gotTheLock) {
ipcMain.handle('get-screen-access', () => systemPreferences.getMediaAccessStatus('screen') === 'granted')
ipcMain.handle('get-screen-sources', () => {
return desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true, thumbnailSize: { width: 225, height: 135 } }).then(async sources => {
return sources.map(source => {
return sources.map((source: any) => {
return {
...source,
appIconURL: source.appIcon?.toDataURL(),
@@ -490,7 +496,7 @@ autoUpdater.on('update-available', (info: UpdateInfo) => {
defaultId: 0,
message: `A new version ${info.version} is available and it is required to continue. It will be downloaded and installed automatically.`
})
.then(({ response }) => {
.then(({ response }: any) => {
log.info(`Update dialog exit code: ${response}`) // eslint-disable-line no-console
if (response !== 0) {
@@ -513,7 +519,7 @@ function setDownloadProgress (percent: number): void {
mainWindow.webContents.send('handle-update-download-progress', percent)
}
autoUpdater.on('update-downloaded', (info) => {
autoUpdater.on('update-downloaded', (_info: any) => {
// We have listeners that prevents the app from being exited on mac
app.removeAllListeners('window-all-closed')
mainWindow?.removeAllListeners('close')
+102
View File
@@ -0,0 +1,102 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { app } from 'electron'
import { Command, CommandOpenSettings, CommandOpenInbox, CommandOpenOffice, CommandOpenPlanner } from '../ui/types'
import * as path from 'path'
const JUMP_COMMANDS = {
INBOX: '--jump-to-inbox',
SETTINGS: '--jump-to-settings',
PLANNER: '--jump-to-planner',
OFFICE: '--jump-to-office'
} as const
type JumpCommand = typeof JUMP_COMMANDS[keyof typeof JUMP_COMMANDS];
export function setupWindowsSpecific(sendCommand: (cmd: Command, ...args: any[]) => void): void {
app.setAppUserModelId(app.getName());
app.on('second-instance', (_event: any, commandLine: any, _workingDirectory: any) => {
const jumpCommand = commandLine[1] as JumpCommand;
let command: Command | undefined;
switch (jumpCommand) {
case JUMP_COMMANDS.INBOX:
command = CommandOpenInbox;
break;
case JUMP_COMMANDS.OFFICE:
command = CommandOpenOffice;
break;
case JUMP_COMMANDS.PLANNER:
command = CommandOpenPlanner;
break;
case JUMP_COMMANDS.SETTINGS:
command = CommandOpenSettings;
break;
default: {
// compile-time check: if jumpCommand is not a known value, this line errors
const _exhaustive: never = jumpCommand;
return; // or handle error
}
}
if (command) {
sendCommand(command);
}
})
function getIconPath(iconName: string): string {
return path.join(process.resourcesPath, 'icons', iconName);
}
app.setUserTasks
([
{
program: process.execPath,
arguments: JUMP_COMMANDS.SETTINGS,
iconPath: getIconPath('SettingsIcon.ico'),
iconIndex: 0,
title: 'Settings',
description: 'Open Settings Screen'
},
{
program: process.execPath,
arguments: JUMP_COMMANDS.INBOX,
iconPath: getIconPath('InboxIcon.ico'),
iconIndex: 0,
title: 'Inbox',
description: 'Open Inbox'
},
{
program: process.execPath,
arguments: JUMP_COMMANDS.PLANNER,
iconPath: getIconPath('PlannerIcon.ico'),
iconIndex: 0,
title: 'Planner',
description: 'Open Planner'
},
{
program: process.execPath,
arguments: JUMP_COMMANDS.OFFICE,
iconPath: getIconPath('OfficeIcon.ico'),
iconIndex: 0,
title: 'Office',
description: 'Open Office'
}
])
}
+24 -5
View File
@@ -1,4 +1,7 @@
import { loginId } from '@hcengineering/login'
import { loveId } from '@hcengineering/love'
import { timeId } from '@hcengineering/time'
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
import presentation, { MessageBox, setDownloadProgress } from '@hcengineering/presentation'
import settings, { settingId } from '@hcengineering/setting'
@@ -21,7 +24,7 @@ import { isOwnerOrMaintainer } from '@hcengineering/core'
import { configurePlatform } from './platform'
import { setupTitleBarMenu } from './titleBarMenu'
import { defineScreenShare, defineGetDisplayMedia } from './screenShare'
import { StandardMenuCommandLogout, StandardMenuCommandSelectWorkspace, StandardMenuCommandOpenSettings } from './types'
import { CommandLogout, CommandSelectWorkspace, CommandOpenSettings, CommandOpenInbox, CommandOpenPlanner, CommandOpenOffice } from './types'
import { ipcMainExposed } from './typesUtils'
import { themeStore } from '@hcengineering/theme'
@@ -61,18 +64,34 @@ window.addEventListener('DOMContentLoaded', () => {
createApp(document.body)
})
ipcMain.on(StandardMenuCommandOpenSettings, () => {
function openScreen(screenId: any): void {
closePopup()
closePanel()
const loc = getCurrentResolvedLocation()
loc.fragment = undefined
loc.query = undefined
loc.path[2] = settingId
loc.path[2] = screenId
loc.path.length = 3
navigate(loc)
}
ipcMain.on(CommandOpenSettings, () => {
openScreen(settingId)
})
ipcMain.on(StandardMenuCommandSelectWorkspace, () => {
ipcMain.on(CommandOpenInbox, () => {
openScreen(notificationId)
})
ipcMain.on(CommandOpenOffice, () => {
openScreen(loveId)
})
ipcMain.on(CommandOpenPlanner, () => {
openScreen(timeId)
})
ipcMain.on(CommandSelectWorkspace, () => {
closePopup()
closePanel()
const loc = getCurrentResolvedLocation()
@@ -84,7 +103,7 @@ window.addEventListener('DOMContentLoaded', () => {
navigate(loc)
})
ipcMain.on(StandardMenuCommandLogout, () => {
ipcMain.on(CommandLogout, () => {
void logOut().then(() => {
navigate({ path: [loginId] })
})
+14 -4
View File
@@ -73,10 +73,20 @@ export interface Branding {
export type BrandingMap = Record<string, Branding>
export const StandardMenuCommandOpenSettings = 'open-settings' as const
export const StandardMenuCommandSelectWorkspace = 'select-workspace' as const
export const StandardMenuCommandLogout = 'logout' as const
export type StandardMenuCommand = typeof StandardMenuCommandOpenSettings | typeof StandardMenuCommandSelectWorkspace | typeof StandardMenuCommandLogout
export const CommandOpenSettings = 'open-settings' as const
export const CommandOpenInbox = 'open-inbox' as const
export const CommandOpenOffice = 'open-office' as const
export const CommandOpenPlanner = 'open-planner' as const
export const CommandSelectWorkspace = 'select-workspace' as const
export const CommandLogout = 'logout' as const
export type Command =
typeof CommandOpenSettings |
typeof CommandOpenInbox |
typeof CommandOpenOffice |
typeof CommandOpenPlanner |
typeof CommandSelectWorkspace |
typeof CommandLogout
export interface NotificationParams {
title: string