Compare commits

...

34 Commits
0.2.1 ... main

Author SHA1 Message Date
Tom
f4e827253d
merge #51: expose windowing to other plugins
Expose Windowing APIs to Other Plugins
2025-03-15 22:06:39 +11:00
Blue Falcon
62fcf88fe6
feat: expose getCurrentWindow 2024-06-23 02:13:35 -07:00
Blue Falcon
09fe3c2d03
feat: expose windowing APIs to other plugins
exposes:
- getWindows()
- showWindows()
- hideWindows()
- toggleWindows()
2024-06-23 01:57:53 -07:00
cc0baa3226
fix: #29 #30 focus maximised windows on trigger after alt-tab 2023-09-04 21:26:01 +10:00
6a76f575ed
revert hotkey changes as per #19 2023-08-24 18:40:50 +10:00
b60e453b68
fix #28: restore dock on close, hide dock on focus if req 2023-08-24 18:39:19 +10:00
999213801f
set public manifest version to 0.3.3 2023-08-24 18:25:29 +10:00
77ce4bd54a
partial fix #28: will quit obsidian if single vault is closed
however, causes dock to vanish if multiple vaults are open?
2023-08-23 23:17:51 +10:00
096bb34d54
fix #19: use super for default hotkeys to avoid conflicts
e.g. CmdOrCtrl+Shift+Tab is bound by default to switch tabs in obsidian and chrome

note that this will not affect macos users, as super still binds to cmd
2023-08-23 22:37:51 +10:00
fb15c25171
fix #16: disable toggle on macos menubar icon click 2023-08-23 22:06:27 +10:00
b62d8862ab
theoretically fix #28: quit app on macos if no windows remain
fix: keep track of current window even after 'close' for toggling back to focus with hotkey if run in background enabled (bug introduced in 5cef2b0ed7)
2023-08-23 21:58:06 +10:00
4c0c3da898
theoretically fix #27: call app.dock.hide() on mac
+ restore hidden taskbar icons on plugin cleanup/disable
2023-08-23 21:38:04 +10:00
b8326526ef
fix #21: always create quick notes relative to root 2023-08-23 21:25:41 +10:00
5cef2b0ed7
fix #22: preserve maximised windows after minimisation 2023-08-23 20:40:47 +10:00
Tom
78e819cc45
merge pull request #24 from Zarpyk/main
semi-addresses #22: will re-maximise windows automatically when minimised

however, only works when run in background is enabled and causes weird window flickering on minimisation
2023-08-23 20:37:49 +10:00
Zarpyk
71ba850a15
Fix #22 2023-07-27 13:58:32 +02:00
Tom
a5abaf32e7
chore: remove "coming soon" from readme
plugin has been merged into official repo: https://github.com/obsidianmd/obsidian-releases/pull/1365
2023-04-21 15:30:17 +10:00
d49c49fe83
chore: update plugin description 2023-04-11 16:49:38 +10:00
6f81acb0ad
chore: update tray.png 2023-04-11 16:44:11 +10:00
aac711518b
chore: bump version to 0.3.2 2023-04-11 16:35:34 +10:00
aa4e824723
feat: register close vault to command palette 2023-04-11 16:32:23 +10:00
d37152dd4e
chore: update terminology to different between vault-level and app-level actions 2023-04-11 16:31:36 +10:00
fcce4935a3
chore: add <kbd> formatting to quick note hotkey in readme 2023-04-11 16:23:56 +10:00
b8834b1286
feat: make tray icon tooltip configurable 2023-04-11 16:21:26 +10:00
Tom
16d577d67e
feat: #13 use the vault name as tray icon tooltip (@hedonihilist) 2023-04-11 15:53:16 +10:00
Hedonihilist
f0c6a5d49f use the vault name as tray icon tooltip 2023-04-11 00:18:00 +08:00
4e097cca14
chore: change plugin id from obsidian-tray -> tray 2023-04-10 23:53:07 +10:00
5cf55e3b7c
chore: bump version to 0.3.0 2023-04-10 23:46:37 +10:00
44518be5a5
feat: edit tray icon w/out app restart 2023-04-10 23:44:25 +10:00
3270ed5d82
feat: #7 multi-vault support (open, hide & quit actions target vault windows only) 2023-04-10 23:35:39 +10:00
8cebeed926
chore: upload obsidian.png for embedding in the readme 2023-04-10 22:01:00 +10:00
1937bdb561
chore: add trayIconImage opt to readme 2023-04-10 21:49:58 +10:00
db671961c5
feat: #2 customisable tray icon image 2023-04-10 21:24:13 +10:00
84a02f157a
fix: prevent type-specific format descs from duplicating on menu reopen 2023-04-10 21:14:27 +10:00
5 changed files with 262 additions and 147 deletions

View File

@ -1,37 +1,39 @@
<img alt="" src="tray.png" align="right" height="128px"> <img alt="" src="tray.png" align="right" height="128px">
**Tray** is an [Obsidian](https://obsidian.md/) plugin that can be used to launch the app **Tray** is an [Obsidian](https://obsidian.md/) plugin that can be used to launch the app
on system startup and run it in the background, adding global hotkeys and a tray menu that on system startup and run it in the background, adding global hotkeys and a tray menu to
toggle app window visibility and can create quick notes from anywhere in your operating system. toggle window visibility and create quick notes from anywhere in your operating system.
## Configuration ## Configuration
### Window management ### Window management
| Option | Description | Default | | Option | Description | Default |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| Launch on startup | Open Obsidian automatically whenever you log into your computer. | Disabled | | Launch on startup | Open Obsidian automatically whenever you log into your computer. | Disabled |
| Hide on launch | Minimises Obsidian automatically whenever the app is launched. If the "Run in background" option is enabled, windows will be hidden to the system tray/menubar instead of minimised to the taskbar/dock. | Disabled | | Hide on launch | Minimises Obsidian automatically whenever the app is launched. If the "Run in background" option is enabled, windows will be hidden to the system tray/menubar instead of minimised to the taskbar/dock. | Disabled |
| Run in background | Hide the app and continue to run it in the background instead of quitting it when pressing the window close button or toggle focus hotkey. | Disabled | | Run in background | Hide the app and continue to run it in the background instead of quitting it when pressing the window close button or toggle focus hotkey. | Disabled |
| Hide taskbar icon | Hides the window's icon from from the dock/taskbar. This may not work on all Linux-based OSes. | Disabled | | Hide taskbar icon | Hides the window's icon from from the dock/taskbar. This may not work on Linux-based OSes. | Disabled |
| Create tray icon | Add an icon to your system tray/menubar to bring hidden Obsidian windows back into focus on click or force a full quit/relaunch of the app through the right-click menu. _Changing this option requires a restart to take effect._ | Enabled | | Create tray icon | Add an icon to your system tray/menubar to bring hidden Obsidian windows back into focus on click or force a full quit/relaunch of the app through the right-click menu. | Enabled |
| Toggle window focus hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | CmdOrCtrl+Shift+Tab | | Tray icon image | Set the image used by the tray/menubar icon. Recommended size: 16x16 | ![](obsidian.png) |
| Tray icon tooltip | Set a title to identify the tray/menubar icon by. The `{{vault}}` placeholder will be replaced by the vault name. | `{{vault}} \| Obsidian` |
| Toggle window focus hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | <kbd>CmdOrCtrl+Shift+Tab</kbd> |
The `Relaunch Obsidian` action can be triggered from the tray/menubar context menu, or with the in-app The `Relaunch Obsidian` and `Close Vault` actions can be triggered from the tray/menubar context menu,
command palette (search for "Tray: Relaunch Obsidian"). Hotkeys can be assigned to the command via or with the in-app command palette (search for "Tray: Relaunch Obsidian" or "Tray: Close Vault").
Obsidian's built-in hotkey manager. Hotkeys can be assigned to the commands via Obsidian's built-in hotkey manager.
### Quick notes ### Quick notes
| Option | Description | Default | | Option | Description | Default |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| Quick note location | New quick notes will be placed in this folder. | | | Quick note location | New quick notes will be placed in this folder. | |
| Quick note date format | New quick notes will use a filename of this pattern. Format: [Moment.js format string](https://momentjs.com/docs/#/displaying/format/) | YYYY-MM-DD | | Quick note date format | New quick notes will use a filename of this pattern. Format: [Moment.js format string](https://momentjs.com/docs/#/displaying/format/) | `YYYY-MM-DD` |
| Quick note hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | CmdOrCtrl+Shift+Q | | Quick note hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | <kbd>CmdOrCtrl+Shift+Q</kbd> |
## Installation ## Installation
### Obsidian Marketplace (Coming Soon) ### Obsidian Marketplace
1. In Obsidian, navigate to **Settings****Community plugins**. 1. In Obsidian, navigate to **Settings****Community plugins**.
2. Press the **Browse** button beside the **Community plugins** option. 2. Press the **Browse** button beside the **Community plugins** option.
@ -59,3 +61,7 @@ This plugin is provided as-is and is designed for personal use. It has not
been tested on every platform and may not work as expected with all future updates. been tested on every platform and may not work as expected with all future updates.
If you notice something is not working as intended, please open a bug report or If you notice something is not working as intended, please open a bug report or
pull request so it can be fixed. pull request so it can be fixed.
The Obsidian logo is distributed with this plugin as the default image for the system
tray/menubar icon, intended to be used within Obsidian. This logo remains the property
of the Obsidian project and is not under the same license as the plugin's source code.

359
main.js
View File

@ -1,5 +1,5 @@
/** /**
* obsidian-tray v0.2.1 * obsidian-tray v0.3.5
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://github.com/dragonwocky/obsidian-tray/) under the MIT license * (https://github.com/dragonwocky/obsidian-tray/) under the MIT license
*/ */
@ -15,11 +15,11 @@ const LOG_PREFIX = "obsidian-tray",
LOG_TRAY_ICON = "creating tray icon", LOG_TRAY_ICON = "creating tray icon",
LOG_REGISTER_HOTKEY = "registering hotkey", LOG_REGISTER_HOTKEY = "registering hotkey",
LOG_UNREGISTER_HOTKEY = "unregistering hotkey", LOG_UNREGISTER_HOTKEY = "unregistering hotkey",
ACTION_QUICK_NOTE = "Add Quick Note", ACTION_QUICK_NOTE = "Quick Note",
ACTION_OPEN = "Open Obsidian", ACTION_SHOW = "Show Vault",
ACTION_HIDE = "Hide Obsidian", ACTION_HIDE = "Hide Vault",
ACTION_RELAUNCH = "Relaunch Obsidian", ACTION_RELAUNCH = "Relaunch Obsidian",
ACTION_QUIT = "Quit Obsidian", ACTION_CLOSE = "Close Vault",
DEFAULT_DATE_FORMAT = "YYYY-MM-DD", DEFAULT_DATE_FORMAT = "YYYY-MM-DD",
ACCELERATOR_FORMAT = ` ACCELERATOR_FORMAT = `
This hotkey is registered globally and will be detected even if Obsidian does This hotkey is registered globally and will be detected even if Obsidian does
@ -31,46 +31,70 @@ const LOG_PREFIX = "obsidian-tray",
Format: Format:
<a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener"> <a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener">
Moment.js format string</a> Moment.js format string</a>
<br>Preview:
`, `,
// 16x16 base64 obsidian icon: generated from obsidian.asar/icon.png // 16x16 base64 obsidian icon: generated from obsidian.asar/icon.png
OBSIDIAN_BASE64_ICON = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHZSURBVDhPlZKxTxRBFMa/XZcF7nIG7mjxjoRCwomJxgsFdhaASqzQxFDzB1AQKgstLGxIiBQGJBpiCCGx8h+wgYaGgAWNd0dyHofeEYVwt/PmOTMZV9aDIL/s5pvZvPfN9yaL/+HR3eXcypta0m4juFbP5GHuXc9IbunDFc9db/G81/ZzhDMN7g8td47mll4R5BfHwZN4LOaA+fHa259PbUmIYzWkt3e2NZNo3/V9v1vvU6kkstk+tLW3ItUVr/m+c3N8MlkwxYqmBFcbwUQQCNOcyVzDwEAWjuPi5DhAMV/tKOYPX5hCyz8Gz1zX5SmWjBvZfmTSaRBJkGAIoxJHv+pVW2yIGNxOJ8bUVNcFEWLxuG1ia6JercTbttwQTeDwPS0kCMXiXtgk/jQrFUw7ptYSMWApF40yo/ytjHq98fdk3ayVE+cn2CxMb6ruz9qAJKFUKoWza1VJSi/n0+ffgYHdWW2gHuxXymg0gjCB0sjpmiaDnkL3RzDyzLqBUKns2ztQqUR0fk2TwSrGSf1eczqF5vsPZRCQSSAFLk6gqctgQRkc6TWRQLV2YMYQki9OoNkqzFQ9r+WOGuW5CrJbOzyAlPKr6MSGLbkcDwbf35oY/jRkt6cAfgNwowruAMz9AgAAAABJRU5ErkJggg==`, OBSIDIAN_BASE64_ICON = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHZSURBVDhPlZKxTxRBFMa/XZcF7nIG7mjxjoRCwomJxgsFdhaASqzQxFDzB1AQKgstLGxIiBQGJBpiCCGx8h+wgYaGgAWNd0dyHofeEYVwt/PmOTMZV9aDIL/s5pvZvPfN9yaL/+HR3eXcypta0m4juFbP5GHuXc9IbunDFc9db/G81/ZzhDMN7g8td47mll4R5BfHwZN4LOaA+fHa259PbUmIYzWkt3e2NZNo3/V9v1vvU6kkstk+tLW3ItUVr/m+c3N8MlkwxYqmBFcbwUQQCNOcyVzDwEAWjuPi5DhAMV/tKOYPX5hCyz8Gz1zX5SmWjBvZfmTSaRBJkGAIoxJHv+pVW2yIGNxOJ8bUVNcFEWLxuG1ia6JercTbttwQTeDwPS0kCMXiXtgk/jQrFUw7ptYSMWApF40yo/ytjHq98fdk3ayVE+cn2CxMb6ruz9qAJKFUKoWza1VJSi/n0+ffgYHdWW2gHuxXymg0gjCB0sjpmiaDnkL3RzDyzLqBUKns2ztQqUR0fk2TwSrGSf1eczqF5vsPZRCQSSAFLk6gqctgQRkc6TWRQLV2YMYQki9OoNkqzFQ9r+WOGuW5CrJbOzyAlPKr6MSGLbkcDwbf35oY/jRkt6cAfgNwowruAMz9AgAAAABJRU5ErkJggg==`,
log = (message) => console.log(`${LOG_PREFIX}: ${message}`); log = (message) => console.log(`${LOG_PREFIX}: ${message}`);
let tray; let tray, plugin;
const obsidian = require("obsidian"), const obsidian = require("obsidian"),
{ { app, Tray, Menu } = require("electron").remote,
app, { nativeImage, BrowserWindow } = require("electron").remote,
BrowserWindow, { getCurrentWindow, globalShortcut } = require("electron").remote;
getCurrentWindow,
globalShortcut,
Tray,
Menu,
nativeImage,
} = require("electron").remote;
const showWindows = () => { const vaultWindows = new Set(),
log(LOG_SHOWING_WINDOWS); maximizedWindows = new Set(),
const windows = BrowserWindow.getAllWindows(); getWindows = () => [...vaultWindows],
windows.forEach((win) => win.show()); observeWindows = () => {
getCurrentWindow().focus(); const onWindowCreation = (win) => {
vaultWindows.add(win);
win.setSkipTaskbar(plugin.settings.hideTaskbarIcon);
win.on("close", () => {
if (win !== getCurrentWindow()) vaultWindows.delete(win);
});
// preserve maximised windows after minimisation
if (win.isMaximized()) maximizedWindows.add(win);
win.on("maximize", () => maximizedWindows.add(win));
win.on("unmaximize", () => maximizedWindows.delete(win));
};
onWindowCreation(getCurrentWindow());
getCurrentWindow().webContents.on("did-create-window", onWindowCreation);
if (process.platform === "darwin") {
// on macos, the "hide taskbar icon" option is implemented
// via app.dock.hide(): thus, the app as a whole will be
// hidden from the dock, including windows from other vaults.
// when a vault is closed via the "close vault" button,
// the cleanup process will call app.dock.show() to restore
// access to any other open vaults w/out the tray enabled
// => thus, this listener is required to re-hide the dock
// if switching to another vault with the option enabled
getCurrentWindow().on("focus", () => {
if (plugin.settings.hideTaskbarIcon) hideTaskbarIcons();
});
}
}, },
hideWindows = (runInBackground) => { showWindows = () => {
log(LOG_SHOWING_WINDOWS);
getWindows().forEach((win) => {
if (maximizedWindows.has(win)) {
win.maximize();
win.focus();
} else win.show();
});
},
hideWindows = () => {
log(LOG_HIDING_WINDOWS); log(LOG_HIDING_WINDOWS);
const windows = BrowserWindow.getAllWindows(); getWindows().forEach((win) => [
windows.forEach((win) => [
win.isFocused() && win.blur(), win.isFocused() && win.blur(),
runInBackground ? win.hide() : win.minimize(), plugin.settings.runInBackground ? win.hide() : win.minimize(),
]); ]);
}, },
toggleWindows = (runInBackground, checkForFocus = true) => { toggleWindows = (checkForFocus = true) => {
const windows = BrowserWindow.getAllWindows(), const openWindows = getWindows().some((win) => {
openWindows = windows.some((win) => { return (!checkForFocus || win.isFocused()) && win.isVisible();
return (!checkForFocus || win.isFocused()) && win.isVisible(); });
}); if (openWindows) hideWindows();
if (openWindows) { else showWindows();
hideWindows(runInBackground);
} else showWindows();
}; };
const onWindowClose = (event) => event.preventDefault(), const onWindowClose = (event) => event.preventDefault(),
@ -90,49 +114,94 @@ const onWindowClose = (event) => event.preventDefault(),
// the 3-second delayed window force close in obsidian.asar/main.js // the 3-second delayed window force close in obsidian.asar/main.js
getCurrentWindow().on("close", onWindowClose); getCurrentWindow().on("close", onWindowClose);
}, },
cleanupWindowClose = () => { allowWindowClose = () => {
getCurrentWindow().removeListener("close", onWindowClose); getCurrentWindow().removeListener("close", onWindowClose);
window.removeEventListener("beforeunload", onWindowUnload, true); window.removeEventListener("beforeunload", onWindowUnload, true);
}; };
const setHideTaskbarIcon = (plugin) => { const hideTaskbarIcons = () => {
getCurrentWindow().setSkipTaskbar(plugin.settings.hideTaskbarIcon); getWindows().forEach((win) => win.setSkipTaskbar(true));
if (process.platform === "darwin") app.dock.hide();
}, },
setLaunchOnStartup = (plugin) => { showTaskbarIcons = () => {
getWindows().forEach((win) => win.setSkipTaskbar(false));
if (process.platform === "darwin") app.dock.show();
},
setLaunchOnStartup = () => {
const { launchOnStartup, runInBackground, hideOnLaunch } = plugin.settings; const { launchOnStartup, runInBackground, hideOnLaunch } = plugin.settings;
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: launchOnStartup, openAtLogin: launchOnStartup,
openAsHidden: runInBackground && hideOnLaunch, openAsHidden: runInBackground && hideOnLaunch,
}); });
},
relaunchObsidian = () => {
app.relaunch();
app.exit(0);
}; };
const addQuickNote = (plugin) => { const cleanup = () => {
log(LOG_CLEANUP);
unregisterHotkeys();
showTaskbarIcons();
allowWindowClose();
destroyTray();
},
relaunchApp = () => {
app.relaunch();
app.exit(0);
},
closeVault = () => {
log(LOG_CLEANUP);
cleanup();
const vaultWindows = getWindows(),
obsidianWindows = BrowserWindow.getAllWindows();
if (obsidianWindows.length === vaultWindows.length) {
// quit app directly if only remaining windows are in the
// current vault - necessary for successful quit on macos
app.quit();
} else vaultWindows.forEach((win) => win.destroy());
};
const addQuickNote = () => {
const { quickNoteLocation, quickNoteDateFormat } = plugin.settings, const { quickNoteLocation, quickNoteDateFormat } = plugin.settings,
pattern = quickNoteDateFormat || DEFAULT_DATE_FORMAT, pattern = quickNoteDateFormat || DEFAULT_DATE_FORMAT,
date = obsidian.moment().format(pattern), date = obsidian.moment().format(pattern),
name = obsidian name = obsidian
.normalizePath(`${quickNoteLocation ?? ""}/${date}`) .normalizePath(`${quickNoteLocation ?? ""}/${date}`)
.replace(/\*|"|\\|<|>|:|\||\?/g, "-"); .replace(/\*|"|\\|<|>|:|\||\?/g, "-"),
plugin.app.fileManager.createAndOpenMarkdownFile(name); // manually create and open file instead of depending
// on createAndOpenMarkdownFile to force file creation
// relative to the root instead of the active file
// (in case user has default location for new notes
// set to "same folder as current file")
leaf = plugin.app.workspace.getLeaf(),
root = plugin.app.fileManager.getNewFileParent(""),
openMode = { active: true, state: { mode: "source" } };
plugin.app.fileManager
.createNewMarkdownFile(root, name)
.then((file) => leaf.openFile(file, openMode));
showWindows(); showWindows();
}, },
createTrayIcon = (plugin) => { replaceVaultName = (str) => {
return str.replace(/{{vault}}/g, plugin.app.vault.getName());
},
destroyTray = () => {
tray?.destroy();
tray = undefined;
},
createTrayIcon = () => {
destroyTray();
if (!plugin.settings.createTrayIcon) return;
log(LOG_TRAY_ICON); log(LOG_TRAY_ICON);
const obsidianIcon = nativeImage.createFromDataURL(OBSIDIAN_BASE64_ICON), const obsidianIcon = nativeImage.createFromDataURL(
plugin.settings.trayIconImage ?? OBSIDIAN_BASE64_ICON
),
contextMenu = Menu.buildFromTemplate([ contextMenu = Menu.buildFromTemplate([
{ {
type: "normal", type: "normal",
label: ACTION_QUICK_NOTE, label: ACTION_QUICK_NOTE,
accelerator: plugin.settings.quickNoteHotkey, accelerator: plugin.settings.quickNoteHotkey,
click: () => addQuickNote(plugin), click: addQuickNote,
}, },
{ {
type: "normal", type: "normal",
label: ACTION_OPEN, label: ACTION_SHOW,
accelerator: plugin.settings.toggleWindowFocusHotkey, accelerator: plugin.settings.toggleWindowFocusHotkey,
click: showWindows, click: showWindows,
}, },
@ -143,42 +212,38 @@ const addQuickNote = (plugin) => {
click: hideWindows, click: hideWindows,
}, },
{ type: "separator" }, { type: "separator" },
{ label: ACTION_RELAUNCH, click: relaunchObsidian }, { label: ACTION_RELAUNCH, click: relaunchApp },
{ label: ACTION_QUIT, role: "quit" }, { label: ACTION_CLOSE, click: closeVault },
]); ]);
tray = new Tray(obsidianIcon); tray = new Tray(obsidianIcon);
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
tray.setToolTip("Obsidian"); tray.setToolTip(replaceVaultName(plugin.settings.trayIconTooltip));
tray.on("click", () => tray.on("click", () => {
toggleWindows(plugin.settings.runInBackground, false) if (process.platform === "darwin") {
); // macos does not register separate left/right click actions
// for menu items, icon should open menu w/out causing toggle
tray.popUpContextMenu();
} else toggleWindows(false);
});
}; };
const registerHotkeys = (plugin) => { const registerHotkeys = () => {
log(LOG_REGISTER_HOTKEY); log(LOG_REGISTER_HOTKEY);
try { try {
const toggleAccelerator = plugin.settings.toggleWindowFocusHotkey, const { toggleWindowFocusHotkey, quickNoteHotkey } = plugin.settings;
quicknoteAccelerator = plugin.settings.quickNoteHotkey; if (toggleWindowFocusHotkey) {
if (toggleAccelerator) { globalShortcut.register(toggleWindowFocusHotkey, toggleWindows);
globalShortcut.register(toggleAccelerator, () => {
const runInBackground = plugin.settings.runInBackground;
toggleWindows(runInBackground);
});
} }
if (quicknoteAccelerator) { if (quickNoteHotkey) {
globalShortcut.register(quicknoteAccelerator, () => { globalShortcut.register(quickNoteHotkey, addQuickNote);
addQuickNote(plugin);
});
} }
} catch {} } catch {}
}, },
unregisterHotkeys = (plugin) => { unregisterHotkeys = () => {
log(LOG_UNREGISTER_HOTKEY); log(LOG_UNREGISTER_HOTKEY);
try { try {
const toggle = plugin.settings.toggleWindowFocusHotkey, globalShortcut.unregister(plugin.settings.toggleWindowFocusHotkey);
quicknote = plugin.settings.quickNoteHotkey; globalShortcut.unregister(plugin.settings.quickNoteHotkey);
globalShortcut.unregister(toggle);
globalShortcut.unregister(quicknote);
} catch {} } catch {}
}; };
@ -209,21 +274,24 @@ const OPTIONS = [
`, `,
type: "toggle", type: "toggle",
default: false, default: false,
onChange: (plugin) => { onChange() {
setLaunchOnStartup(plugin); setLaunchOnStartup();
const runInBackground = plugin.settings.runInBackground; if (plugin.settings.runInBackground) interceptWindowClose();
if (!runInBackground) showWindows(); else [allowWindowClose(), showWindows()];
}, },
}, },
{ {
key: "hideTaskbarIcon", key: "hideTaskbarIcon",
desc: ` desc: `
Hides the window's icon from from the dock/taskbar. Enabling the tray icon first Hides the window's icon from from the dock/taskbar. Enabling the tray icon first
is recommended if using this option. This may not work on all Linux-based OSes. is recommended if using this option. This may not work on Linux-based OSes.
`, `,
type: "toggle", type: "toggle",
default: false, default: false,
onChange: setHideTaskbarIcon, onChange() {
if (plugin.settings.hideTaskbarIcon) hideTaskbarIcons();
else showTaskbarIcons();
},
}, },
{ {
key: "createTrayIcon", key: "createTrayIcon",
@ -231,15 +299,40 @@ const OPTIONS = [
Adds an icon to your system tray/menubar to bring hidden Obsidian windows Adds an icon to your system tray/menubar to bring hidden Obsidian windows
back into focus on click or force a full quit/relaunch of the app through back into focus on click or force a full quit/relaunch of the app through
the right-click menu. the right-click menu.
<br><span class="mod-warning">Changing this option requires a restart to take effect.</span>
`, `,
type: "toggle", type: "toggle",
default: true, default: true,
onChange: createTrayIcon,
},
{
key: "trayIconImage",
desc: `
Set the image used by the tray/menubar icon. Recommended size: 16x16
<br>Preview: <img data-preview style="height: 16px; vertical-align: bottom;">
`,
type: "image",
default: OBSIDIAN_BASE64_ICON,
onChange: createTrayIcon,
},
{
key: "trayIconTooltip",
desc: `
Set a title to identify the tray/menubar icon by. The
<code>{{vault}}</code> placeholder will be replaced by the vault name.
<br>Preview: <b class="u-pop" data-preview></b>
`,
type: "text",
default: "{{vault}} | Obsidian",
postprocessor: replaceVaultName,
onChange: createTrayIcon,
}, },
{ {
key: "toggleWindowFocusHotkey", key: "toggleWindowFocusHotkey",
desc: ACCELERATOR_FORMAT,
type: "hotkey", type: "hotkey",
default: "CmdOrCtrl+Shift+Tab", default: "CmdOrCtrl+Shift+Tab",
onBeforeChange: unregisterHotkeys,
onChange: registerHotkeys,
}, },
"Quick notes", "Quick notes",
{ {
@ -250,14 +343,20 @@ const OPTIONS = [
}, },
{ {
key: "quickNoteDateFormat", key: "quickNoteDateFormat",
desc: "New quick notes will use a filename of this pattern.", desc: `
New quick notes will use a filename of this pattern. ${MOMENT_FORMAT}
<br>Preview: <b class="u-pop" data-preview></b>
`,
type: "moment", type: "moment",
default: DEFAULT_DATE_FORMAT, default: DEFAULT_DATE_FORMAT,
}, },
{ {
key: "quickNoteHotkey", key: "quickNoteHotkey",
desc: ACCELERATOR_FORMAT,
type: "hotkey", type: "hotkey",
default: "CmdOrCtrl+Shift+Q", default: "CmdOrCtrl+Shift+Q",
onBeforeChange: unregisterHotkeys,
onChange: registerHotkeys,
}, },
]; ];
@ -274,10 +373,6 @@ const keyToLabel = (key) =>
.createContextualFragment((html ?? "").replace(/\s+/g, " ")); .createContextualFragment((html ?? "").replace(/\s+/g, " "));
class SettingsTab extends obsidian.PluginSettingTab { class SettingsTab extends obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() { display() {
this.containerEl.empty(); this.containerEl.empty();
for (const opt of OPTIONS) { for (const opt of OPTIONS) {
@ -286,51 +381,61 @@ class SettingsTab extends obsidian.PluginSettingTab {
setting.setName(opt); setting.setName(opt);
setting.setHeading(); setting.setHeading();
} else { } else {
if (opt.default) { if (opt.default) opt.placeholder ??= `Example: ${opt.default}`;
opt.placeholder ??= `Example: ${opt.default}`;
}
if (opt.type === "hotkey") {
opt.desc ??= "";
opt.desc += ACCELERATOR_FORMAT;
opt.onBeforeChange = unregisterHotkeys;
opt.onChange = registerHotkeys;
}
if (opt.type === "moment") {
opt.desc = `${opt.desc ? `${opt.desc}<br>` : ""}${MOMENT_FORMAT}`;
}
setting.setName(keyToLabel(opt.key)); setting.setName(keyToLabel(opt.key));
setting.setDesc(htmlToFragment(opt.desc)); setting.setDesc(htmlToFragment(opt.desc));
const onChange = async (value) => { const onChange = async (value) => {
await opt.onBeforeChange?.(this.plugin); await opt.onBeforeChange?.();
this.plugin.settings[opt.key] = value; plugin.settings[opt.key] = value;
await this.plugin.saveSettings(); await plugin.saveSettings();
await opt.onChange?.(this.plugin); await opt.onChange?.();
}; };
const value = plugin.settings[opt.key] ?? opt.default ?? "";
if (opt.type === "toggle") { if (opt.type === "toggle") {
setting.addToggle((toggle) => { setting.addToggle((toggle) => {
toggle toggle.setValue(value).onChange(onChange);
.setValue(this.plugin.settings[opt.key] ?? opt.default) });
.onChange(onChange); } else if (opt.type === "image") {
const previewImg = setting.descEl.querySelector("img[data-preview");
if (previewImg) previewImg.src = value;
const fileUpload = setting.descEl.createEl("input");
fileUpload.style.visibility = "hidden";
fileUpload.type = "file";
fileUpload.onchange = (event) => {
const file = event.target.files[0],
reader = new FileReader();
reader.onloadend = () => {
onChange(reader.result);
if (previewImg) previewImg.src = reader.result;
};
reader.readAsDataURL(file);
};
setting.addButton((button) => {
button.setIcon("image").onClick(() => fileUpload.click());
}); });
} else if (opt.type === "moment") { } else if (opt.type === "moment") {
setting.addMomentFormat((moment) => { setting.addMomentFormat((moment) => {
const sampleEl = setting.descEl.createEl("b"); const previewEl = setting.descEl.querySelector("[data-preview]");
sampleEl.className = "u-pop"; if (previewEl) moment.setSampleEl(previewEl);
moment moment
.setPlaceholder(opt.placeholder) .setPlaceholder(opt.placeholder)
.setDefaultFormat(opt.default ?? "") .setDefaultFormat(opt.default ?? "")
.setValue(this.plugin.settings[opt.key] ?? opt.default ?? "") .setValue(value)
.setSampleEl(sampleEl)
.onChange(onChange); .onChange(onChange);
}); });
} else { } else {
const previewEl = setting.descEl.querySelector("[data-preview]"),
updatePreview = (value) => {
if (!previewEl) return;
previewEl.innerText = opt?.postprocessor(value) ?? value;
};
updatePreview(value);
setting.addText((text) => { setting.addText((text) => {
text text
.setPlaceholder(opt.placeholder) .setPlaceholder(opt.placeholder)
.setValue(this.plugin.settings[opt.key] ?? opt.default ?? "") .setValue(value)
.onChange(onChange); .onChange((value) => [onChange(value), updatePreview(value)]);
}); });
} }
} }
@ -345,20 +450,15 @@ class TrayPlugin extends obsidian.Plugin {
this.addSettingTab(new SettingsTab(this.app, this)); this.addSettingTab(new SettingsTab(this.app, this));
const { settings } = this; const { settings } = this;
registerHotkeys(this); plugin = this;
setHideTaskbarIcon(this); createTrayIcon();
setLaunchOnStartup(this); registerHotkeys();
if (settings.createTrayIcon) createTrayIcon(this); setLaunchOnStartup();
observeWindows();
if (settings.runInBackground) interceptWindowClose(); if (settings.runInBackground) interceptWindowClose();
if (settings.hideTaskbarIcon) hideTaskbarIcons();
if (settings.hideOnLaunch) { if (settings.hideOnLaunch) {
let _hidden; this.registerEvent(this.app.workspace.onLayoutReady(hideWindows));
this.registerEvent(
this.app.workspace.onLayoutReady(() => {
if (_hidden) return;
_hidden = true;
hideWindows(settings.runInBackground);
})
);
} }
// add as command: can be called from command palette // add as command: can be called from command palette
@ -366,15 +466,24 @@ class TrayPlugin extends obsidian.Plugin {
this.addCommand({ this.addCommand({
id: "relaunch-app", id: "relaunch-app",
name: ACTION_RELAUNCH, name: ACTION_RELAUNCH,
callback: relaunchObsidian, callback: relaunchApp,
});
this.addCommand({
id: "close-vault",
name: ACTION_CLOSE,
callback: closeVault,
}); });
} }
onunload() { onunload() {
log(LOG_CLEANUP); cleanup();
unregisterHotkeys(this);
cleanupWindowClose();
} }
getCurrentWindow = getCurrentWindow
getWindows = getWindows;
showWindows = showWindows;
hideWindows = hideWindows;
toggleWindows = toggleWindows;
async loadSettings() { async loadSettings() {
const DEFAULT_SETTINGS = OPTIONS.map((opt) => ({ [opt.key]: opt.default })); const DEFAULT_SETTINGS = OPTIONS.map((opt) => ({ [opt.key]: opt.default }));
this.settings = Object.assign(...DEFAULT_SETTINGS, await this.loadData()); this.settings = Object.assign(...DEFAULT_SETTINGS, await this.loadData());

View File

@ -1,10 +1,10 @@
{ {
"id": "obsidian-tray", "id": "tray",
"name": "Tray", "name": "Tray",
"author": "dragonwocky", "author": "dragonwocky",
"authorUrl": "https://dragonwocky.me/", "authorUrl": "https://dragonwocky.me/",
"description": "Launch Obsidian on startup and run it in the background from the system tray.", "description": "Run Obsidian from the system tray for customisable window management & global quick notes",
"version": "0.2.1", "version": "0.3.5",
"isDesktopOnly": true, "isDesktopOnly": true,
"minAppVersion": "1.0.0" "minAppVersion": "1.0.0"
} }

BIN
obsidian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

BIN
tray.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB