mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-11 05:45:26 -06:00
client: add support for persistant system tray icon
This commit is contained in:
parent
bbcc670655
commit
35e11807e5
@ -62,6 +62,8 @@ function initOnElectron() {
|
|||||||
if (options.get("nativeTitleBarVisible") !== "true") {
|
if (options.get("nativeTitleBarVisible") !== "true") {
|
||||||
initTitleBarButtons(style, currentWindow);
|
initTitleBarButtons(style, currentWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
electron.ipcRenderer.send("ipcReady");
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTitleBarButtons(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
|
function initTitleBarButtons(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
|
||||||
|
|||||||
@ -1377,7 +1377,8 @@
|
|||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"title": "System Tray",
|
"title": "System Tray",
|
||||||
"enable_tray": "Enable tray (Trilium needs to be restarted for this change to take effect)"
|
"enable_tray": "Enable tray (Trilium needs to be restarted for this change to take effect)",
|
||||||
|
"persistant_tray": "Always show the tray icon, even if no windows are currently open"
|
||||||
},
|
},
|
||||||
"heading_style": {
|
"heading_style": {
|
||||||
"title": "Heading Style",
|
"title": "Heading Style",
|
||||||
|
|||||||
@ -1291,6 +1291,7 @@
|
|||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
|
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
|
||||||
|
"persistant-tray": "Afișează întotdeauna iconița, chiar dacă nu este deschisă nicio fereastră.",
|
||||||
"title": "Tray-ul de sistem"
|
"title": "Tray-ul de sistem"
|
||||||
},
|
},
|
||||||
"update_available": {
|
"update_available": {
|
||||||
|
|||||||
@ -87,6 +87,8 @@ function SearchEngineSettings() {
|
|||||||
|
|
||||||
function TrayOptionsSettings() {
|
function TrayOptionsSettings() {
|
||||||
const [ disableTray, setDisableTray ] = useTriliumOptionBool("disableTray");
|
const [ disableTray, setDisableTray ] = useTriliumOptionBool("disableTray");
|
||||||
|
const [ persistantTray, setPersistantTray ] = useTriliumOptionBool("persistantTray");
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsSection title={t("tray.title")}>
|
<OptionsSection title={t("tray.title")}>
|
||||||
@ -96,6 +98,12 @@ function TrayOptionsSettings() {
|
|||||||
currentValue={!disableTray}
|
currentValue={!disableTray}
|
||||||
onChange={trayEnabled => setDisableTray(!trayEnabled)}
|
onChange={trayEnabled => setDisableTray(!trayEnabled)}
|
||||||
/>
|
/>
|
||||||
|
<FormCheckbox
|
||||||
|
name="persistant-tray"
|
||||||
|
label={t("tray.persistant_tray")}
|
||||||
|
currentValue={persistantTray}
|
||||||
|
onChange={enabled => setPersistantTray(enabled)}
|
||||||
|
/>
|
||||||
</OptionsSection>
|
</OptionsSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,8 @@ async function main() {
|
|||||||
// for applications and their menu bar to stay active until the user quits
|
// for applications and their menu bar to stay active until the user quits
|
||||||
// explicitly with Cmd + Q.
|
// explicitly with Cmd + Q.
|
||||||
app.on("window-all-closed", () => {
|
app.on("window-all-closed", () => {
|
||||||
if (process.platform !== "darwin") {
|
const persistantTrayEnabled = options.getOptionBool("persistantTray");
|
||||||
|
if (!persistantTrayEnabled && process.platform !== "darwin") {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -75,9 +75,9 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
|
|||||||
"highlightsList",
|
"highlightsList",
|
||||||
"checkForUpdates",
|
"checkForUpdates",
|
||||||
"disableTray",
|
"disableTray",
|
||||||
|
"persistantTray",
|
||||||
"eraseUnusedAttachmentsAfterSeconds",
|
"eraseUnusedAttachmentsAfterSeconds",
|
||||||
"eraseUnusedAttachmentsAfterTimeScale",
|
"eraseUnusedAttachmentsAfterTimeScale",
|
||||||
"disableTray",
|
|
||||||
"customSearchEngineName",
|
"customSearchEngineName",
|
||||||
"customSearchEngineUrl",
|
"customSearchEngineUrl",
|
||||||
"promotedAttributesOpenInRibbon",
|
"promotedAttributesOpenInRibbon",
|
||||||
|
|||||||
@ -124,6 +124,7 @@ const defaultOptions: DefaultOption[] = [
|
|||||||
{ name: "highlightsList", value: '["underline","color","bgColor"]', isSynced: true },
|
{ name: "highlightsList", value: '["underline","color","bgColor"]', isSynced: true },
|
||||||
{ name: "checkForUpdates", value: "true", isSynced: true },
|
{ name: "checkForUpdates", value: "true", isSynced: true },
|
||||||
{ name: "disableTray", value: "false", isSynced: false },
|
{ name: "disableTray", value: "false", isSynced: false },
|
||||||
|
{ name: "persistantTray", value: "false", isSynced: false },
|
||||||
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
|
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
|
||||||
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
|
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
|
||||||
{ name: "logRetentionDays", value: "90", isSynced: false }, // default 90 days
|
{ name: "logRetentionDays", value: "90", isSynced: false }, // default 90 days
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import electron from "electron";
|
import electron, { app } from "electron";
|
||||||
import type { BrowserWindow, Tray } from "electron";
|
import type { BrowserWindow, Tray } from "electron";
|
||||||
import { default as i18next, t } from "i18next";
|
import { default as i18next, t } from "i18next";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@ -17,6 +17,7 @@ import windowService from "./window.js";
|
|||||||
let tray: Tray;
|
let tray: Tray;
|
||||||
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
||||||
// is minimized
|
// is minimized
|
||||||
|
|
||||||
const windowVisibilityMap: Record<number, boolean> = {};; // Dictionary for storing window ID and its visibility status
|
const windowVisibilityMap: Record<number, boolean> = {};; // Dictionary for storing window ID and its visibility status
|
||||||
|
|
||||||
function getTrayIconPath() {
|
function getTrayIconPath() {
|
||||||
@ -107,7 +108,6 @@ function updateTrayMenu() {
|
|||||||
if (!tray) {
|
if (!tray) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
|
||||||
const allWindows = windowService.getAllWindows();
|
const allWindows = windowService.getAllWindows();
|
||||||
updateWindowVisibilityMap(allWindows);
|
updateWindowVisibilityMap(allWindows);
|
||||||
|
|
||||||
@ -119,19 +119,22 @@ function updateTrayMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openNewWindow() {
|
function openNewWindow() {
|
||||||
|
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
||||||
if (lastFocusedWindow){
|
if (lastFocusedWindow){
|
||||||
lastFocusedWindow.webContents.send("globalShortcut", "openNewWindow");
|
lastFocusedWindow.webContents.send("globalShortcut", "openNewWindow");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerKeyboardAction(actionName: KeyboardActionNames) {
|
async function triggerKeyboardAction(actionName: KeyboardActionNames) {
|
||||||
if (lastFocusedWindow){
|
const lastFocusedWindow = await getCurrentWindow();
|
||||||
|
if (lastFocusedWindow) {
|
||||||
lastFocusedWindow.webContents.send("globalShortcut", actionName);
|
lastFocusedWindow.webContents.send("globalShortcut", actionName);
|
||||||
ensureVisible(lastFocusedWindow);
|
ensureVisible(lastFocusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openInSameTab(note: BNote | BRecentNote) {
|
async function openInSameTab(note: BNote | BRecentNote) {
|
||||||
|
const lastFocusedWindow = await getCurrentWindow();
|
||||||
if (lastFocusedWindow){
|
if (lastFocusedWindow){
|
||||||
lastFocusedWindow.webContents.send("openInSameTab", note.noteId);
|
lastFocusedWindow.webContents.send("openInSameTab", note.noteId);
|
||||||
ensureVisible(lastFocusedWindow);
|
ensureVisible(lastFocusedWindow);
|
||||||
@ -310,6 +313,16 @@ function createTray() {
|
|||||||
i18next.on("languageChanged", updateTrayMenu);
|
i18next.on("languageChanged", updateTrayMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCurrentWindow(): Promise<BrowserWindow | null> {
|
||||||
|
if (!windowService.getMainWindow()) {
|
||||||
|
// If no windows are open, create a new main window
|
||||||
|
await windowService.createMainWindow(app);
|
||||||
|
return windowService.getMainWindow();
|
||||||
|
} else {
|
||||||
|
return windowService.getLastFocusedWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createTray
|
createTray
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import sqlInit from "./sql_init.js";
|
|||||||
import cls from "./cls.js";
|
import cls from "./cls.js";
|
||||||
import keyboardActionsService from "./keyboard_actions.js";
|
import keyboardActionsService from "./keyboard_actions.js";
|
||||||
import electron from "electron";
|
import electron from "electron";
|
||||||
import type { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents, IpcMainEvent } from "electron";
|
import { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents, ipcMain, IpcMainEvent } from "electron";
|
||||||
import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
|
import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { RESOURCE_DIR } from "./resource_dir.js";
|
import { RESOURCE_DIR } from "./resource_dir.js";
|
||||||
@ -21,6 +21,7 @@ let setupWindow: BrowserWindow | null;
|
|||||||
let allWindows: BrowserWindow[] = []; // // Used to store all windows, sorted by the order of focus.
|
let allWindows: BrowserWindow[] = []; // // Used to store all windows, sorted by the order of focus.
|
||||||
|
|
||||||
function trackWindowFocus(win: BrowserWindow) {
|
function trackWindowFocus(win: BrowserWindow) {
|
||||||
|
|
||||||
// We need to get the last focused window from allWindows. If the last window is closed, we return the previous window.
|
// We need to get the last focused window from allWindows. If the last window is closed, we return the previous window.
|
||||||
// Therefore, we need to push the window into the allWindows array every time it gets focused.
|
// Therefore, we need to push the window into the allWindows array every time it gets focused.
|
||||||
win.on("focus", () => {
|
win.on("focus", () => {
|
||||||
@ -212,11 +213,14 @@ async function createMainWindow(app: App) {
|
|||||||
mainWindowState.manage(mainWindow);
|
mainWindowState.manage(mainWindow);
|
||||||
|
|
||||||
mainWindow.setMenuBarVisibility(false);
|
mainWindow.setMenuBarVisibility(false);
|
||||||
mainWindow.loadURL(`http://127.0.0.1:${port}`);
|
|
||||||
mainWindow.on("closed", () => (mainWindow = null));
|
mainWindow.on("closed", () => (mainWindow = null));
|
||||||
|
|
||||||
configureWebContents(mainWindow.webContents, spellcheckEnabled);
|
await mainWindow.loadURL(`http://127.0.0.1:${port}`);
|
||||||
|
await configureWebContents(mainWindow.webContents, spellcheckEnabled);
|
||||||
trackWindowFocus(mainWindow);
|
trackWindowFocus(mainWindow);
|
||||||
|
|
||||||
|
await waitForIpc(mainWindow);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowExtraOpts() {
|
function getWindowExtraOpts() {
|
||||||
@ -385,6 +389,21 @@ function getAllWindows() {
|
|||||||
return allWindows;
|
return allWindows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForIpc(win: BrowserWindow) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const handler = (ev: IpcMainEvent, ) => {
|
||||||
|
const senderWindow = BrowserWindow.fromWebContents(ev.sender);
|
||||||
|
|
||||||
|
if (senderWindow === win) {
|
||||||
|
ipcMain.off("ipcReady", handler);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ipcMain.on("ipcReady", handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createMainWindow,
|
createMainWindow,
|
||||||
createExtraWindow,
|
createExtraWindow,
|
||||||
|
|||||||
@ -121,6 +121,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
|||||||
downloadImagesAutomatically: boolean;
|
downloadImagesAutomatically: boolean;
|
||||||
checkForUpdates: boolean;
|
checkForUpdates: boolean;
|
||||||
disableTray: boolean;
|
disableTray: boolean;
|
||||||
|
persistantTray: boolean;
|
||||||
promotedAttributesOpenInRibbon: boolean;
|
promotedAttributesOpenInRibbon: boolean;
|
||||||
editedNotesOpenInRibbon: boolean;
|
editedNotesOpenInRibbon: boolean;
|
||||||
codeBlockWordWrap: boolean;
|
codeBlockWordWrap: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user