RJVB
RJVB

Reputation: 776

Detect if a Tcl script is run in a background process

I'm looking for a preferably cross-platform way to detect from within a Tcl script if the interpreter is running in a foreground or in a background process.

I've seen how to do it via ps (or /proc/$$/stat on Linux); is there a better way or do I have to hack something around that approach? I already have a utility library written in C so exposing the lowlevel API that ps also uses so I don't have to parse process output (or special file content) would be fine.

Upvotes: 1

Views: 431

Answers (2)

RJVB
RJVB

Reputation: 776

Thanks to Donal I came up with the implementation below that should work on all POSIX Unix variants:

/*
    processIsForeground

    synopsis: processIsForeground

    Returns true if the process is running in the foreground or false
    if in the background.
*/
int IsProcessForegroundCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    /* Check the arg count */
    if (objc != 1) {
        Tcl_WrongNumArgs(interp, 1, objv, NULL);
        return TCL_ERROR;
    }

    int fd;
    errno = 0;
    if ((fd = open("/dev/tty", O_RDONLY)) != -1) {
        const pid_t pgrp = getpgrp();
        const pid_t tcpgrp = tcgetpgrp(fd);
        if (pgrp != -1 && tcpgrp != -1) {
            Tcl_SetObjResult(interp, Tcl_NewBooleanObj(pgrp == tcpgrp));
            close(fd);
            return TCL_OK;
        }
        close(fd);
    }
    Tcl_SetErrno(errno);
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "processIsForeground: ", (char *)Tcl_PosixError(interp), NULL);
    return TCL_ERROR;
}

int Pextlib_Init(Tcl_Interp *interp)
{
    if (Tcl_InitStubs(interp, "8.4", 0) == NULL)
        return TCL_ERROR;
// SNIP
    Tcl_CreateObjCommand(interp, "processIsForeground", IsProcessForegroundCmd, NULL, NULL);

    if (Tcl_PkgProvide(interp, "Pextlib", "1.0") != TCL_OK)
        return TCL_ERROR;

    return TCL_OK;
}

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137567

There's no truly cross-platform notion of foreground, but the main platforms do have ways of doing it according to the notion they have of foreground.

Linux, macOS, and other Unix:

For determining if a process is foreground or not, you need to check if its process group ID is the terminal's controlling process group ID. For Tcl, you'd be looking to surface the getpgrp() and tcgetpgrp() system calls (both POSIX). Tcl has no built-in exposure of either, so you're talking either a compiled extension (may I recommend Critcl for this?) or calling an external program like ps. Fortunately, if you use the latter (a reasonable option if this is just an occasional operation) you can typically condition the output so that you get just the information you want and need to do next to no parsing.

# Tested on macOS, but may work on other platforms
proc isForeground {{pid 0}} {
    try {
        lassign [exec ps -p [expr {$pid ? $pid : [pid]}] -o "pgid=,tpgid="] pgid tpgid
    } on error {} {
        return -code error "no such process"
    }
    # If tpgid is zero, the process is a daemon of some kind
    expr {$pgid == $tpgid  &&  $tpgid != 0}
}

Windows

There's code to do it, and the required calls are supported by the TWAPI extension so you don't need to make your own. (WARNING! I've not tested this!)

package require twapi_ui

proc isForeground {{pid 0}} {
    set forground_pid [get_window_thread [get_foreground_window]]
    return [expr {($pid ? $pid : [pid]) == $foreground_pid}]
}

Upvotes: 1

Related Questions