Is there a linux command to determine the window IDs associated with a given process ID?

Viewed 64323

Given a process iD of XX, I'd like to have a list of any window id's where _NET_WM_PID = XX. Even better would be the oldest still active window id if possible.

I'm very new to linux, but what I'm trying to do is create a script that would take a command line, and see if there's a windows already open belonging to a process invoked with that same command line. If so, just set focus to that window, otherwise execute the command line to get a new process going. My intention is to use this in my ubuntu desktop, where I'll hook this script into my easystroke mouse gesture commands, so that, for example, every time I gesture for gmail I don't get a brand new gmail session, I just get brought to my existing gmail chrome app window. Perhaps there's a much easier way to go about all this, but I haven't found my way to it yet.

With help, I've figured out how find a PID for a command line with pgrep and how to set focus to a window handle with wmctrl, but I'm stuck on getting from PID to window ID.

6 Answers

xwininfo and xprop permits to retrieve what you want, but it is a little tricky.

xwininfo permits to retrieve all known windows, and xprop to query X about a single window ID for your _NET_WM_PID parameter.

So far, a hacky way to do it would be:

#!/bin/sh

findpid=$1

known_windows=$(xwininfo -root -children|sed -e 's/^ *//'|grep -E "^0x"|awk '{ print $1 }')

for id in ${known_windows}
do
    xp=$(xprop -id $id _NET_WM_PID)
    if test $? -eq 0; then
        pid=$(xprop -id $id _NET_WM_PID|cut -d'=' -f2|tr -d ' ')

        if test "x${pid}" = x${findpid}
        then
            echo "Windows Id: $id"
        fi
    fi
done

Result:

mycroft:~ $ ./find_windows.sh 1919
Windows Id: 0x1800748
Windows Id: 0x181b221
Windows Id: 0x1803ad5
Windows Id: 0x181f681
Windows Id: 0x181f658
Windows Id: 0x180006d
Windows Id: 0x1800003
Windows Id: 0x1800001
Windows Id: 0x180001e

As you will see, a single process may have a certain number of known windows, even if you see only one on your screen.

Maybe you should get these tools sources in order to make what you want.

$WINDOWID feature

Under xterm environment as some other implementation, you could find this variable:

echo $WINDOWID
58720292

So for any other pid:

Shortly by using sed:

targetpid=12345
sed -zne 's/WINDOWID=//p' /proc/$targetpid/environ

...

xdotool windowactivate $(sed -zne 's/WINDOWID=//p' /proc/$targetpid/environ)

Or in a pure bash function:

getWinFromPid () { 
    local pid=$1 array line
    [ -z "$pid" ] || [ ! -d /proc/$pid ] && return -1
    local -n result=${2:-winIDfromPid[$pid]}
    mapfile -d $'\0' -t array </proc/$pid/environ
    for line in "${array[@]}" ;do
        [ -z "${line%WINDOWID=*}" ] &&
            result=${line#*=} && return
    done
}

Then

getWinFromPid 123456 myWinId
xdotool windowactivate $myWinId

For other term, like gnome-terminal:

This is strong because we don't want pid of terminal process, but pid of shell using terminal. For sample:

wmctrl -lp

don't show wanted pids!

So we have to navigate in hierarchy of process

1. Get Window ID from process ID

1a. Current active terminal window

From active session himself:

SHWINID=$(xprop  -root | sed -ne 's/^_NET_ACTIVE_WINDOW.*[)].*window id # //p')

This work as you type this in any active terminal console.

Then now, with xdotool:

sleep 12; xdotool windowactivate $SHWINID

You can now switch to another window, will be back in 12 seconds.

1b. Window ID from any shell or subprocess ID

I wrote this little function:

getWinFromPid () { 
    local pid=$1 ttypid crtpid wid xprop ttycheck
    [ -z "$pid" ] || [ ! -d /proc/$pid ] && return -1
    local -n result=${2:-winIDfromPid[$pid]}
    read ttycheck < <(ps ho tty $pid)
    ttypid=$ttycheck
    while [ "$ttypid" = "$ttycheck" ]; do
        crtpid=$pid
        read pid ttypid < <(ps ho ppid,tty $pid)
    done
    result=
    while [ -z "$result" ] && read wid; do
        xprop=$(xprop -id $wid)
        [ "$xprop" ] && [ -z "${xprop//*_NET_WM_DESKTOP*}" ] &&
            [ -z "${xprop//*_NET_WM_PID(CARDINAL) = $crtpid*}" ] && result=$wid
    done < <(xwininfo -root -children -all |
       sed -e '1,/children:$/d;s/^ *\(0x[0-9a-fA-F]\+\) .*/\1/p;d')
}

Then

getWinFromPid <process id> [<variable name>]

If no variable name submited, this will populate global array $winIDfromPid with pid id as index number:

getWinFromPid 1234 winId
echo $winId
0x0100012345

getWinFromPid 1234
echo ${winIDfromPid[1234]}
0x0100012345

declare -p winIDfromPid 
declare -a winIDfromPid=([1234]="0x0100012345")

Nota: this was tested with xterm, mate-terminal, konsole and gnome-terminal.

Nota2: If you already have wmctrl installed, you could replace two last lines of function:

    done < <(xwininfo -root -children -all |
       sed -e '1,/children:$/d;s/^ *\(0x[0-9a-fA-F]\+\) .*/\1/p;d')

by:

    done < <(wmctrl -l|sed -ne 's/^ *\(0x[0-9a-fA-F]\+\) .*/\1/p');}

function will become approx 2time faster.

2. Get process running PID from a window ID

winID=0x123456
ps --ppid $(xprop -id $winID _NET_WM_PID|sed s/.*=//) ho sid |
    xargs -I{} -n1 ps --sid {} fw

or without ID, you will have to click with mouse:

ps --ppid $(xprop _NET_WM_PID|sed s/.*=//) ho sid|xargs -I{} -n1 ps --sid {} fw

Into a function

psWin() {
    ps --ppid $(xprop ${1+-id} $1 _NET_WM_PID|sed s/.*=//) ho sid |
        xargs -I{} -n1 ps --sid {} fw
}

Then:

psWin $SHWINID
  PID TTY      STAT   TIME COMMAND
30529 pts/2   Ss     0:00 bash
19979 pts/2   S+     0:00  \_ bash
19982 pts/2   S+     0:00      \_ xargs -I{} -n1 ps --sid {} fw
19986 pts/2   R+     0:00          \_ ps --sid 30529 fw

3. List of shell windows, with process

Finally, there is a little function for showing a list of window with process.

shWinList () {
    local pids=() wids=() wtitl=() ttys=() pid ttypid wpid crtpid line title desk ttycheck
    for pid in $(ps axho pid,tty| sed -ne 's/ pts.*//p') ;do     # list pid / tty
        wpid=$pid ttypid=
        read ttycheck < <(ps ho tty $pid)
        ttypid=$ttycheck
        while [ "$ttypid" = "$ttycheck" ]; do
            crtpid=$wpid
            read wpid ttypid < <(ps ho ppid,tty $wpid)
        done
        [ -e /proc/$pid ] && pids[crtpid]+=$pid\  ttys[crtpid]=$ttycheck
    done
    while read wid; do   title= pid= desk=                       # list wid / tty
        while read line; do
            [ "$line" ] && { 
                [ -z "${line%%_NET_WM_PID*}" ] && pid=${line##*= }
                [ -z "${line%%WM_NAME*}" ] &&
                    title=${line#*\"} title=${title%\"*}
                [ -z "${line%%_NET_WM_DESKTOP(*}" ] && desk=${line##*= } ;}
        done < <(xprop -id $wid)
        [ "${pids[pid]}" ] && [ "$title" ] && [ "$desk" ] &&
            wtitl[16#${wid#0x}]=${title} wids[16#${wid#0x}]=${pids[pid]} \
                 ttys[16#${wid#0x}]=${ttys[pid]}
    done < <(xwininfo -root -children -all |
                 sed -ne 's/^ *\(0x[0-9a-fA-F]\+\) .*/\1/p')
    for xwin in ${!wids[@]} ;do  out=                            # merge & print
        printf "  0x%x %-9s %-40s " $xwin "${ttys[$xwin]}" "${wtitl[$xwin]}"
        for pid in ${wids[$xwin]} ;do
            mapfile -d '' cmdl < /proc/$pid/cmdline
            echo -n " $pid[${cmdl[*]}]"
        done ; echo
    done
}

Then

shWinList
  0xa600024 pts/42    user@hostloc: /tmp            14817[bash]
  0x1c00024 pts/3     user@hostloc: ~               29349[watch ps --tty pts/3 fw] 31989[bash]
  0x4600024 pts/16    user@remote: ~                14441[ssh user@remote]
  0x6000024 pts/43    user@hostloc: ~               5728[bash]

Using pids requires random window-manager restarts to resume functionality. Using process names proved itself to be much more useful, simplistic, and very reliable.

I use openbox and a keyboard. I created and posted a small bash program called focus to launch and rotate among existing windows based upon process names which fully demonstrates how to successfully associate processes with windows.

This is most likely the best answer because it is a 'goto that program' via the command line in one-liner easily declarable in a window manager keyboard configuration file. But, this answer is very low on the list of answers.

Related