@commute0498, Here is my attempt at a clean-ish method for this…
- Create file
~/.config/hypr/hlx.lua with the following content:
local M = {}
M.dsp = {}
--- @class exec_with_callback_opts
--- @field timeout number? Timeout in milliseconds to wait for window to open before giving up. Default is 5000ms.
--- @field tag string? Tag to use for identifying the window. Default is `layout_<random>`.
--- @class dispatch_return
--- @field ok boolean Whether the command was successfully dispatched
--- @field pass_event boolean Whether the event should be passed to other handlers
--- @alias exec_callback fun(window: table): nil
--- Execute a command with optional callback when window opens
--- Supports multiple signatures:
--- - exec_cmd(cmd, rules, callback, opts)
--- - exec_cmd(cmd, rules, callback)
--- - exec_cmd(cmd, callback, opts)
--- - exec_cmd(cmd, callback)
--- @param cmd string
--- @param rules table|function?
--- @param callback exec_callback|table?
--- @param opts exec_with_callback_opts?
--- @return HL.Dispatcher
function M.dsp.exec_cmd(cmd, rules, callback, opts)
if type(rules) == 'function' and type(callback) == 'table' then -- Called as exec_cmd(cmd, callback, opts)
opts = callback
callback = rules
rules = nil
elseif type(callback) == 'table' and callback ~= nil then -- Called as exec_cmd(cmd, rules, opts) with no callback
opts = callback
callback = nil
end
opts = opts or {}
rules = rules or {}
-- If no callback, just dispatch normally
if not callback then
return hl.dsp.exec_cmd(cmd, rules)
end
-- With callback: use tags to id window
local timeout_ms = opts.timeout or 5000
local tag = opts.tag or ('launch_' .. tostring(math.random(1000000)))
local found = false
local subscription
subscription = hl.on('window.open', function(window)
if found or not subscription:is_active() then
return
end
local tags = window.tags
for _, window_tag in ipairs(tags) do
if window_tag == tag or window_tag == tag .. '*' then
found = true
subscription:remove()
callback(window)
return
end
end
end)
hl.timer(function()
if not found and subscription:is_active() then
subscription:remove()
end
end, {
timeout = timeout_ms,
type = 'oneshot',
})
-- Merge tag into rules
rules.tag = tag
return hl.dsp.exec_cmd(cmd, rules)
end
--- Convenience function like the `hl.exec_cmd` to allow for direct calling without having to use hl.dispatch
--- @param cmd string
--- @param rules table|function?
--- @param callback exec_callback|table?
--- @param opts exec_with_callback_opts?
--- @return dispatch_return
function M.exec_cmd(cmd, rules, callback, opts)
return hl.dispatch(M.dsp.exec_cmd(cmd, rules, callback, opts))
end
return M
- use like this:
local hlx = require('hlx')
local function kitty_window_layout(kitty_window)
hl.dispatch(hl.dsp.focus({ window = kitty_window }))
hl.dispatch(hl.dsp.window.swap({ window = kitty_window, direction = 'right' }))
hl.dispatch(hl.dsp.layout('splitratio 0.5'))
end
local function launch_kitty()
hlx.exec_cmd('kitty --directory ~/Sync/Obsidian -- ~/.local/share/bob/nvim-bin/nvim', { workspace = 1 }, kitty_window_layout)
end
hl.on('hyprland.start', function()
hlx.exec_cmd('obsidian', { workspace = 1 }, launch_kitty)
end)
The extended version of exec_cmd will cleanup its event listener for you.
Read the typedef comments for usage if its not quite what your after.
Hope this is helpful. It has earned a place in my utils folder for my hl config 
Edit: changed to OP’s workspace setting of 1. (I was testing on ws 5)
Another edit: Fixed the neovim path back to OP’s stated one in the kitty cmd