Check if program is running in lua config

Okay, so I was trying to run the foot terminal as a server (you can do foot --server, then spawn the actual windows with footclient).

My worry is that it’ll crash and then I won’t even be able to open a terminal. I could always fix it from tty, but I’d rather not.

Thus, I am looking for a way to check if the server is running before choosing which command to execute, rather than check if footclient crashed with code 220 (from the manpages), or using the current workaround, which is simply uwsm app -- footclient || uwsm app -- foot.

I don’t really like it that way, as it will keep a shell instance occupied, and WILL open a new terminal if the first window closes with any other return code than 0 (again, the foot manpages say it will return the same code as whatever program it is running).

Is there any better way to do this? Also, in my case it’s foot, but I’m guessing there’s also other apps that would benefit from this.

EDIT: I have found some form of workaround, that still seems better than the previous one. Simply put, at startup I run foot -s || hyprctl eval ' to unbind the keybinding for footclient and rebind it to foot whenever the server crashes.

Hi @Armory0678 ,
Bear in mind this code is not tested, as i use alacritty, and couldnt be bothered installing foot just to answer a forum post, but i would go about it like this:

-- if set to true, and the foot daemon is not running, should launch the daemon, before
-- launching the `footclient` process.
-- If set to false, and the foot daemon is not running, will just launch the `foot` process
local prefer_client_server = true

-- name of the terminal process
local terminal_app = 'foot'

-- actual commands to execute for each use case
local terminal_daemon = 'uwsm app -- ' .. terminal_app .. ' --server'
local terminal_client = 'uwsm app -- footclient'
local terminal_standalone = 'uwsm app -- ' .. terminal_app

-- launch the terminal daemon on initial load (this can be removed
-- if you prefer to only launch the daemon on first launch of a terminal [assuming
-- you set the `prefer_client_server` variable above to `true`])
hl.dispatch(hl.dsp.exec_cmd(terminal_daemon))

---Find the pid/pids of a process by its name.
---@param name string The name of the application to find.
---@return table|nil matches A table of matching pids, or nil if no matches are found.
local function find_process_by_name(name)
  local cmd = 'ps -C ' .. name .. ' --format pid='
  local handle = io.popen(cmd)
  if not handle then
    return nil
  end
  local result = handle:read('*a')
  handle:close()
  local matching_pids = {}
  for line in result:gmatch("[^\r\n]+") do
    local pid = line:match("^%s*(%d+)%s*$")
    if pid then
      table.insert(matching_pids, tonumber(pid))
    end
  end
  return matching_pids
end

---Checks if a process with the given PID has the specified argument in its command line.
---@param pid number The process ID to check.
---@param arg string The argument to look for in the process's command line.
---@return boolean has_arg True if the argument is found in the process's command line, false otherwise.
local function pid_has_arg(pid, arg)
  local cmdline_file = io.open('/proc/' .. pid .. '/cmdline', 'r')
  if not cmdline_file then
    return false
  end
  local cmdline = cmdline_file:read('*a')
  cmdline_file:close()
  return cmdline:find(arg, 1, true) ~= nil
end

-- Function to bind to evaluate whether the daemon is running,
-- which action to use in the event it isnt (based on the `prefer_client_server` variable above
local function launch_terminal()
  local pids = find_process_by_name(terminal_app)
  local daemon_running = false
  if pids then
    for _, pid in ipairs(pids) do
      if pid_has_arg(pid, '--server') then
        daemon_running = true
        break
      end
    end
  end
  if daemon_running then
    hl.dispatch(hl.dsp.exec_cmd(terminal_client))
  else
    if prefer_client_server then
      hl.dispatch(hl.dsp.exec_cmd(terminal_daemon))
      -- we need a timer here to give the daemon time to start.
      hl.timer(function()
        hl.dispatch(hl.dsp.exec_cmd(terminal_client))
      end, { timeout = 100, type = 'oneshot' })
    else
      hl.dispatch(hl.dsp.exec_cmd(terminal_standalone))
    end
  end
end

hl.bind('SUPER+Return', launch_terminal, { description = 'Open a terminal' })

Remember, i have not thoroughly tested this, but i did have it outputting notifications to inform me what it would do, based on evaluating the same thing with alacritty as the process to be tracked.
I think it should work, but you may want to tweak it somewhat for your use case.

Thank you, just what I was looking for! I went scouring all the way into hyprland’s config, but did not even think of using Lua’s native functions. Now I just have to figure out how to force a reload when I change configs/theme …

Glad I could help mate :)

As far as the reload, mine automatically reloads any time my config or one of the modules I require changes.
I thought that was just default behaviour in Hyprland.

It is for the compositor, but the foot server also has to reload its config, or new footclient instances will not have the new theme.

Ohhh I see. You meant to reload the foot config, not the Hyprland one.
If foot has a command to reload the config, you could just dispatch an exe_cmd for it in the branch that runs the footclient binary.
I would imagine that the branch that runs the foot server would not need it, as it should read its config on launch?

But that would only help you when launching new terminals.
If foot doesn’t have native config monitoring for live reload, you could look into watching the config file with something like inotify and issue whatever command will reload the config on file changes that way?

Edit: Looks like you’re out of luck @Armory0678
There are a few closed issues requesting the ability to relooad config, or at least re-read themes from config without restarting the foot server process, or the foot client / standalone process..

On each of them, Dnkl, the repo owner, has said a firm no

So i dont think theres anything you can do short of killing all ur terminals and reloading them. (Or perhaps forking the project and adding support for this yourself? Yay for open source! :stuck_out_tongue:)

Yeah, there’s really no way to it, I think. Especially because one of the features of the foot server is preloading and then sharing the config with all clients. I might want to test out some -USR{1,2} kill signals, but there’s nothing about them on the foot manpages, so I doubt it’ll do anything. Anyways, that probably belongs on some other forum (I think there’s a foot IRC, maybe?).