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

前端 未结 6 1985
借酒劲吻你
借酒劲吻你 2020-12-22 23:41

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

相关标签:
6条回答
  • 2020-12-23 00:16

    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.

    0 讨论(0)
  • 2020-12-23 00:16

    you can look up PIDs with wmctrl too, as a matter of fact, and I think that's a better way to do it. xwininfo will return all sorts of entities which appear to be windows, but you won't really find them on your desktop.

    If you do man wmctrl , you'll find that wmctrl -l lists all windows that are actually visible on your desktop with (most importantly) their window ids and titles. -p adds PIDs and -x will add window classes.

    As the manual says ( RTFM, right? :D), wmctrl can also search through some of these and activate a window that matches the search. However, I have no idea what determines which one of all possible matches will be returned. On the other hand, you can use the provided listing function to write a wrapper that does the searching better and possibly based on some other properties (such as the timestamp of the last access to the window) which you can get by querying the provided win id to xprop, for example.

    These lines of code below return the most recent instance a mate-terminal class window:

    XTIME="_NET_WM_USER_TIME" #a shorter name for xprop query that shoul return timestamps
    export TMPDIR=/dev/shm    #save tmp files to memory to make it faster
    LST=`mktemp`              #tmp file to store our listing 
    wmctrl -lx |  awk -F' ' '{printf("%s\t%s    \t",$1,$3); for(i=5;i<=NF;i++) printf("%s",$i); printf("\n")  }'  > $LST #pretty-print our listing of windows into the tmp file
     #To each line of listing, prepend a timestamp acquired via an xprop call
     #Use awk to find a line whose 3rd column (winclass) matches the window class "mate-terminal.Mate-terminal" and among those that do, find the one whose timestamp is the largest
    while read LINE; do ID=`echo "$LINE"|cut -f 1`; TIME=`xprop -id $ID $XTIME`;  TIME="${TIME/* = /}"; echo -e "$TIME\t$LINE" ; done <$LST ) | awk -v s="mate-terminal.Mate-terminal" '$3 == s {if($1>max){max=$1;line=$0};};END{print line}'
    rm $LST  #delete tmp file
    

    Anyhow, for the thing you describe you are building—if I were you, I would find out what class of windows your desired command generates and then base my search on that, rather than on PIDs. Alternatively, you could presume that command CMD will possibly generate windows with a class name that includes CMD.

    After you have found your line, you should use the window id
    to activate the window via wmctrl.

    Hope this helps.

    A side note: I've found that xdotool can do searches based on class names and window titles too, but it is extremely slow. On my computer, this bash script (that calls quite a couple of external utilites) is 10 times as fast as the compiled alternative that is xdotool :P.

    0 讨论(0)
  • 2020-12-23 00:18

    Here is a list of all the X11 window management commands you will ever need.

    You can get the window ID associated with a process ID using wmctrl in a script like this:

    #!/usr/bin/env bash
    # 
    # File:
    #   getwindidbypid
    # 
    # Description:
    #   Get the ID of a window by PID (if the process has a window).
    # 
    # Usage:
    #   getwindidbypid <PID>
    # 
    
    while IFS= read line; do
      if [[ "${line}" =~ (0x)([0-9a-z]+)([ ][- ][0-9]+[ ])([0-9]*) ]]; then
        winId="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
        pid="${BASH_REMATCH[4]}"
        if [[ "${pid}" -eq "${1}" ]]; then
          WIND_IDS+=("${winId}")
        fi
      fi
    done < <(wmctrl -lp)
    
    if [ "${#WIND_IDS[@]}" -gt 0 ]; then
      echo "${WIND_IDS[@]}"
    fi
    

    Example:

    user ~ $  getwindidbypid 37248
    0x05a00012
    
    0 讨论(0)
  • 2020-12-23 00:20

    You can use:

    xdotool getwindowfocus getwindowname
    

    (As is: you don't need to replace those nice-sounding names with anything.)

    0 讨论(0)
  • 2020-12-23 00:20

    xterm $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]
    
    0 讨论(0)
  • 2020-12-23 00:36

    Note: The first answer I provided used cmctrl to associate pids with windows to perform the task of focusing upon an existing process. Sometimes wmctrl would not act properly and desire reboot. Using process names proved itself to be much more useful, simplistic, and reliable. Therefore, this version uses process names and does not consider pids.

    I use openbox and a keyboard. I use the following bash which works in Debian to return to, launch, and rotate among existing windows based upon process name:

    #!/usr/bin/bash
    #
    # focus
    #   [-c|--processCount COUNT] # maximum number of processes to automatically start at once,
    #   [-m|--mainTitle MAIN_TITLE] # extended regular expression to match a main window in search so that associated windows will not be counted as a separate instance of the same process. Example: The window in which thunderbird creates a new email is not the main thunderbird window which can be separately closed.
    #   [-p|--processName PROCESS_NAME] # extended regular expression to match the process name which is need COMMAND spawns a process of a different name. Example: flashpeak-slimjet launches a program called slimjet.
    #   [-w|--wantedTitle WANTED_TITLE] # further reduces matches according to WANTED_TITLE. This is needed when EVAL opens a specific file.
    #   [-v|--verbose] # use to see window information which must be matched, what has been matched etcn.
    #   COMMAND # used as the PROCESS_NAME if not explicitly stated.
    #   [COMMAND_ARGUMENTS] # EVAL (with COMMAND) which is assumed to create a newly focused window.
    #
    # Application switcher which moves to next window applicable to COMMAND or executes COMMAND.
    # Rotate to next window belonging to PROCESS_NAME.
    # Another instance via EVAL if at last matched window and COUNT allows.
    #
    # If no window declarations contain regular expressions PROCESS_NAME or COMMAND, then COMMAND
    # If the active window does not match PROCESS_NAME or COMMAND, then activate the first match.
    # If the active window matches PROCESS_NAME or COMMAND, then goto the next match.
    # If at list end, and COUNT allows, then execute COMMAND again, otherwise wrap back to the first matching window.
    #
    
    # bug: COUNT may not distinguish between a 'main' window and its spawns which may inhibit expected COMMAND after wrapping only spawned windows. This bug is to be resolved via the introduction of -m which identifies a 'main' window.
    
    
    gBase=$(basename "$0")
    gDir=$(dirname "$0")
    
    unset aWantsMainWindow
    . "$gDir"/argumentDeclare. # declare ARGUMENT
    while :; do
      . "$gDir"/argumentNextOrBreak. # ready ARGUMENT
      if OptionDidReplyIntegerOrIs '-c' '--processCount'; then
         [[ -z "${REPLY+x}" ]] && REPLY="$1" && shift
         COUNT=$REPLY
      elif OptionDidReplyOrIs '-p' '--processName'; then
         [[ -z "${REPLY+x}" ]] && REPLY="$1" && shift
         PROCESS_NAME=$REPLY
      elif OptionDidReplyOrIs '-m' '--mainTitle'; then
         [[ -z "${REPLY+x}" ]] && REPLY="$1" && shift
         MAIN_TITLE=$REPLY
      elif OptionDidReplyOrIs '-w' '--wantedTitle'; then
         [[ -z "${REPLY+x}" ]] && REPLY="$1" && shift
         WANTED_TITLE=$REPLY
      elif OptionIsFlag '-v' '--verbose'; then
         aVerbose=-
      else
         >&2 "!!! $gBase: Unknown option ${ARGUMENT} !!!"
         exit 1
      fi
    done
    
    : ${COMMAND:="$1"} # used for description matching
    EVAL="$@" # used for evaluation
    
    # previous wmctrl/pid usage falls into problems which clear after reboot
    
    # xwininfo entire tree which contains many undesired windows
    #  | grep grandchild depth (or deeper) assumed to be children of the window manager
    #  | grep exclude label lines
    #  | grep exclude no name -- leaving -- 0xid (main_name): ("name" "name") dimensions...
    #  | sed -- leaving -- 0xid "main_name": ("name" "name")
    zInfos=$(xwininfo -tree -root \
                         | grep -E '^        ' \
                         | grep -E '^[[:blank:]]+0x[[:xdigit:]]+' \
                         | grep -v '(has no name): ()' \
                       | sed -E 's/^[[:blank:]]*(0x[[:xdigit:]]+ ".*": \(".*" ".*"\)).*/\1/')
    [[ -n "$aVerbose" ]] \
      && printf "%d Available windows:\n%s${zInfos:+\n}" \
                    "$(wc -l < <(printf "%s${zInfos:+\n}" "$zInfos"))" \
                    "$zInfos"
    zInfosMatch=$(grep -E "0x[[:xdigit:]]+ \".*\": \((\".*\" )?\"${PROCESS_NAME:-$COMMAND}\"( \".*\")?\)" <<< "$zInfos")
    [[ -n "$WANTED_TITLE" ]] \
      && zInfosMatch=$(grep -E '0x[[:xdigit:]]+ \"$WANTED_TITLE\"): \(".*" ".*"\)' <<< "$zInfosMatch")
    [[ -n "$aVerbose" ]] \
      && printf "%d Matching windows:\n%s${zInfosMatch:+\n}" \
                    "$(wc -l < <(printf "%s${zInfosMatch:+\n}" "$zInfosMatch"))" \
                    "$zInfosMatch"
    zIdFocus=$(printf '0x%x\n' "$(xdotool getwindowfocus)")
    [[ -n "$aVerbose" ]] \
      && printf "Currently focused id: %s\n" "$zIdFocus"
    
    zIdsMatch=$(cut -d' ' -f1  <<< "$zInfosMatch")
    
    if [[ -z "$aWantsMainWindow" ]]; then
      # attempt to roll to next process window
      zFirstIndexMatch=$(grep "$zIdFocus" -Fnxm1 <<< "$zIdsMatch" | cut -d: -f1)
      if [[ -n "$zFirstIndexMatch" ]]; then
         [[ "$zFirstIndexMatch" -le "$(wc -l <<< "$zIdsMatch")" ]] \
            && zNextIndexMatch=$(( zFirstIndexMatch + 1 )) \
              || unset zNextIndex
      else
         zIds=$(cut -d' ' -f1  <<< "$zInfos")
         zNextIndex=$(( 1 + $(grep "$zIdFocus" -Fnxm1 <<< "$zIds" | cut -d: -f1) ))
         while IFS= read -r zNextId || [[ -n "$zNextId" ]]; do
            zNextIndexMatch=$(grep "$zNextId" -Fnxm1 <<< "$zIdsMatch" | cut -d: -f1)
            [[ -n "$zNextIndexMatch" ]] && break
            zNextIndex=$(( zNextIndex + 1 ))
         done < <(tail -n+"$zNextIndex" <<< "$zIds")
      fi
      # find next matching window (if previous match and available)
      [[ -n "$zNextIndexMatch" ]] \
         && zNextIdMatch=$(sed "$zNextIndexMatch!d" <<< "$zIdsMatch")
      # raise first matching window (if no previous match and available) 
      [[ -z "$zNextIndexMatch$zFirstIndexMatch" ]] \
         && zNextIdMatch=$(head -n1 <<< "$zIdsMatch")
      if [[ -n "$zNextIdMatch" ]]; then
         [[ -n "$aVerbose" ]] \
            && printf 'Next matching id: %s\n' "$zNextIdMatch"
         wmctrl -iR "$zNextIdMatch"
         exit
      fi
    fi
    
    # consider executing EVAL
    [[ -z "$MAIN_TITLE" ]] \
      && zInfosMain="$zInfosMatch" \
         || zInfosMain=$(grep -E '0x[[:xdigit:]]+ \"$MAIN_TITLE\"): \(".*" ".*"\)' <<< "$zInfosMatch")
    zInfosMainCount=$(wc -l < <(printf "%s${zInfosMain:+\n}" "$zInfosMain"))
    [[ -n "$aVerbose" ]] \
      && printf "%d Main Windows: %s${zInfosMain:+\n}" \
                    "$zInfosMainCount" \
                    "$zInfosMain"
    if [[ "${COUNT:-1}" -gt "$zInfosMainCount" ]]; then
      [[ -n "$aVerbose" ]] \
         && printf 'Opening new main window via: %s\n' "$EVAL"
      eval $EVAL & # ampersand to deal with processes that don't just end
      disown -a # disown to deal with processes that don't let me end
      exit
    fi
    # raise first matching window
    zNextIdMatch=$(head -n1 <<< "$zIdsMatch")
    if [[ -z "$zNextIdMatch" ]]; then
      >&2 printf "!!! $aBase: Did not locate first matching window or attempt to execute: %s !!!\n" "$EVAL"
      exit 1
    else
      [[ -n "$aVerbose" ]] \
         && printf 'Wrapping to first matching id: %s\n' "$zNextIdMatch"
      wmctrl -iR "$zNextIdMatch"
      exit
    fi
    

    Note: This code requires package wmctrl.

    Example usages:

    focus qpaeq focus -c2 tilda focus -c2 -m '.* - Slimjet' -p slimjet flashpeak-slimjet focus -m ".* - Mozilla Thunderbird" thunderbird focus -m '.* - Mozilla Firefox' -p Firefox-esr firefox-esr

    0 讨论(0)
提交回复
热议问题