Hi everybody. I created a solution for this Problem. Just today I updated my App. Hier can you find the Download GitHub - MichaelLepori/ObsiWizy: Tool for Obsidian Users and hier ObsiWizy On YouTube how it works. Enjoy it and let me know, if you would like to be able to do more with it. Till then, take care. ML
Hi! Iâve written an open-source plugin for Obsidian that adds this functionality (close to tray, open on startup, global hotkey): GitHub - dragonwocky/obsidian-tray: Launch Obsidian on startup and run it in the background from the system tray
Iâve submitted it to Obsidianâs community plugins repo, so hopefully itâll be available there soon, but until then there are instructions for manual installation of the plugin at the link above.
Thanks to dragonwocky for writing the âTrayâ plugin. Hoping to see it available as an official plugin soon!
This is such a great plugin! Thanks @dragonwocky!
i looked for it in the community plugins from within Obsidian, but it was not there under âTrayâ.
This is a very significant UX addition for a lot of people, myself included. Please to the benevolent Obsidian masters, include this in the âofficialâ community plugins section
Close to tray, e.q. minimize as close opton will be great feature
Dragonwockyâs plugin doesnât work that well for some Linux users, including myself. For us, this is still an issue and I still request the feature that names this thread.
(The plugin works beautifully on Windows, though)
This would be nice!
@dragonwocky iâm using this plugin and work flawless
Thanks a lot for the effort you put in this =)
(post deleted by author)
There is an autohotkey script by @darsain in this thread. I extend it to add some value. Furthermore, I did this because the plugin by dragonwocky seems not be actively maintained (last release Sep 4, 2023).
Please keep in mind that the following autohotkey solution is only for windows.
Functionality: Bring obsidian to the foreground with the hotkey F2. The hotkey can be changed in the script (F2::). To minimize obsidian to tray, press F2 again (when obsidian is in the foreground). You can also click the minimize button on the top right of the obsidian window to minimize it to the tray.
Overall, there could be some edge cases where the script doesnât work. The same limitations as those stated by darsain apply.
; Hide and restore obsidian to tray.
; Source: https://www.autohotkey.com/boards/viewtopic.php?p=607299#p607299
#Requires AutoHotkey v2.0
hider := HideToTray()
; Hide it at startup
Run(EnvGet("LocalAppData") . "\Programs\Obsidian\Obsidian.exe")
WinWait("ahk_exe Obsidian.exe", , 7)
Sleep(500)
hider.Hide(WinExist("ahk_exe Obsidian.exe"))
hidden_obsidian_window_id := false
#HotIf (GetKeyState("CapsLock", "P"))
2::
; The most fundamentals this block are from https://forum.obsidian.md/t/close-to-tray/6781/16.
{
; MsgBox(WinExist("ahk_exe Obsidian.exe"),, 4)
global hidden_obsidian_window_id
window_id := WinExist("ahk_exe Obsidian.exe")
hider.RestoreLast()
if (window_id) {
; Check if it's focused
if (WinActive("ahk_id " . window_id)) {
; Save window id and hide
hidden_obsidian_window_id := window_id
hider.Hide(WinExist("ahk_exe Obsidian.exe"))
} else {
; Focus
WinActivate("ahk_id " . window_id)
}
} else if (hidden_obsidian_window_id)
{
; Unhide
hider.RestoreLast()
hidden_obsidian_window_id := false
} else if not WinActive("ahk_exe Obsidian.exe") {
; Launch the app
A_LocalAppData := EnvGet("LocalAppData")
Run(A_LocalAppData . "\Programs\Obsidian\Obsidian.exe")
WinWait("ahk_exe Obsidian.exe", , 7)
WinActivate("ahk_exe Obsidian.exe")
}
}
class HideToTray
{
; add classes here to exclude from hiding:
excludeClasses := ['Progman', 'WorkerW', 'Shell_TrayWnd', 'ApplicationFrameWindow']
static __New() => SingletonDecorator.ApplyTo(this)
__New() {
this.test := ''
this.exclude := Map()
for winClass in this.excludeClasses {
this.exclude[winClass] := ''
}
this.hiddenWindows := []
this.knownWindows := Map()
this.knownWindows.DefineProp('DeleteIfExists', { call: (s, p) => s.Has(p) && s.Delete(p) })
this.SetHooks()
Loop 2 {
ObjRelease(ObjPtr(this))
}
}
; public methods
Hide(hWnd) {
if !this.exclude.Has(WinGetClass(hWnd)) {
this.stopHook := true
this.hiddenWindows.Push(HideToTray.HiddenWindow(hWnd))
this.knownWindows[hWnd] := ''
this.stopHook := false
}
}
RestoreAll() => (this.stopHook := true, this.hiddenWindows := [], this.stopHook := false)
RestoreLast() => this.hiddenWindows.Has(1) && this.hiddenWindows.Pop()
; private methods
SetHooks() {
static EVENT_SYSTEM_MINIMIZESTART := 0x0016
, EVENT_SYSTEM_MINIMIZEEND := 0x0017
, EVENT_OBJECT_DESTROY := 0x8001
, OBJID_WINDOW := 0
this.HasOwnProp('stopHook') || (
this.stopHook := false,
OnMessage(0x404, this.OnNotify := ObjBindMethod(this, 'AHK_NOTIFYICON')),
this.destroyHook := WinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, HookProc),
this.minHook := WinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND, HookProc)
)
HookProc(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
if idObject = OBJID_WINDOW && !this.stopHook {
switch event {
case EVENT_OBJECT_DESTROY: this.knownWindows.DeleteIfExists(hwnd), this.UnHide(hwnd)
case EVENT_SYSTEM_MINIMIZESTART: this.knownWindows.Has(hwnd) && this.Hide(hwnd)
case EVENT_SYSTEM_MINIMIZEEND: this.UnHide(hwnd)
}
}
}
}
UnHide(hWnd) {
try for i, wnd in this.hiddenWindows {
continue
} until wnd.hwnd = hwnd && this.hiddenWindows.RemoveAt(i)
}
AHK_NOTIFYICON(wp, lp, msg, hwnd) {
static WM_LBUTTONDOWN := 0x201, WM_RBUTTONUP := 0x205
, iconID := 0x404, iconMsg := 0x404, maxLenMenuStr := 40
if !(lp = WM_LBUTTONDOWN || lp = WM_RBUTTONUP) {
return
}
for i, wnd in this.hiddenWindows {
if wnd.icon.wnd.hwnd = hwnd {
switch lp {
case WM_RBUTTONUP: ShowMenu(i)
case WM_LBUTTONDOWN: this.hiddenWindows.RemoveAt(i)
}
break
}
}
ShowMenu(idx) {
title := this.hiddenWindows[idx].title
b := StrLen(title) > maxLenMenuStr
menuText := 'Restore «' . SubStr(title, 1, maxLenMenuStr) . (b ? '...' : '') . '»'
iconMenu := Menu()
iconMenu.Add(menuText, (*) => this.AHK_NOTIFYICON(iconID, WM_LBUTTONDOWN, iconMsg, hwnd))
iconMenu.SetIcon(menuText, 'HICON:*' . this.hiddenWindows[idx].icon.hIcon)
iconMenu.Add('Restore all windows', (*) => this.RestoreAll())
iconMenu.Show()
iconMenu.Delete()
}
}
__Delete() {
Loop 2 {
ObjAddRef(ObjPtr(this))
}
this.DeleteProp('minHook')
this.DeleteProp('destroyHook')
OnMessage(0x404, this.OnNotify, 0)
this.RestoreAll()
}
class HiddenWindow {
__New(hWnd) {
this.title := WinGetTitle(hWnd)
this.icon := TrayIcon(this.GetWindowIcon(hWnd), this.title)
WinMinimize(hWnd), WinHide(hWnd)
this.hwnd := hWnd
}
__Delete() {
try WinExist(this.hwnd) && (WinShow(), WinRestore(), WinActivate())
}
GetWindowIcon(hWnd) {
static WM_GETICON := 0x007F, ICON_SMALL := 0, GCLP_HICONSM := -34
, GetClassLong := 'GetClassLong' . (A_PtrSize = 4 ? '' : 'Ptr')
((hIcon := SendMessage(WM_GETICON, ICON_SMALL, A_ScreenDPI, , hWnd))
|| (hIcon := DllCall(GetClassLong, 'Ptr', hWnd, 'Int', GCLP_HICONSM, 'Ptr'))
|| (hIcon := LoadPicture('Shell32', 'Icon3', &IMAGE_ICON := 1)))
return hIcon
}
}
}
class TrayIcon
{
NIF_MESSAGE := 1, NIF_ICON := 2, NIF_TIP := 4
NIM_ADD := 0, NIM_DELETE := 2
iconID := 0x404, iconMsg := 0x404
__New(hIcon, tip?) {
flags := this.NIF_MESSAGE | this.NIF_ICON | (IsSet(tip) ? this.NIF_TIP : 0)
this.wnd := Gui(), this.hIcon := hIcon
this.NOTIFYICONDATA := Buffer(396, 0)
NumPut(
'Ptr', this.NOTIFYICONDATA.size,
'Ptr', this.wnd.hwnd,
'UInt', this.iconID,
'UInt', flags,
'Ptr', this.iconMsg,
'Ptr', hIcon, this.NOTIFYICONDATA
)
if IsSet(tip) {
StrPut(tip, this.NOTIFYICONDATA.ptr + 4 * A_PtrSize + 8, 'CP0')
}
DllCall('Shell32\Shell_NotifyIcon', 'UInt', this.NIM_ADD, 'Ptr', this.NOTIFYICONDATA)
}
__Delete() {
DllCall('Shell32\Shell_NotifyIcon', 'UInt', this.NIM_DELETE, 'Ptr', this.NOTIFYICONDATA)
this.wnd.Destroy()
}
}
class WinEventHook
{
; Event Constants: https://is.gd/tRT5Wr
__New(eventMin, eventMax, hookProc, options := '', idProcess := 0, idThread := 0, dwFlags := 0) {
this.callback := CallbackCreate(hookProc, options, 7)
this.hHook := DllCall('SetWinEventHook', 'UInt', eventMin, 'UInt', eventMax, 'Ptr', 0, 'Ptr', this.callback
, 'UInt', idProcess, 'UInt', idThread, 'UInt', dwFlags, 'Ptr')
}
__Delete() {
DllCall('UnhookWinEvent', 'Ptr', this.hHook)
CallbackFree(this.callback)
}
}
class SingletonDecorator
{
static ApplyTo(targetClass) {
proto := targetClass.Prototype
origCall := targetClass.GetMethod('Call')
origNew := proto.HasMethod('__New') ? proto.GetMethod('__New') : ''
origDel := proto.HasMethod('__Delete') ? proto.GetMethod('__Delete') : ''
targetClass.DefineProp('Call', { Call: (cls, p*) => cls.HasProp('singleton') ? cls.singleton : origCall(cls, p*) })
proto.DefineProp('__New', { Call: (inst, p*) => (
(origNew && origNew(inst, p*)),
targetClass.singleton := inst,
ObjRelease(ObjPtr(inst))
) })
proto.DefineProp('__Delete', { Call: inst => (
ObjAddRef(ObjPtr(inst)),
(origDel && origDel(inst)),
targetClass.DeleteProp('singleton')
) })
}
}