是否有linux命令来确定与给定进程ID关联的窗口ID?

时间:2010-02-12 09:28:10

标签: linux x11

给定XX的进程iD,我想要列出任何窗口ID,其中_NET_WM_PID = XX。如果可能的话,更好的是最老的仍然活跃的窗口ID。

我对linux很新,但我要做的是创建一个脚本,该脚本将采用命令行,并查看是否已经打开一个属于使用同一命令行调用的进程的窗口。如果是这样,只需将焦点设置到该窗口,否则执行命令行以获得新进程。我的目的是在我的ubuntu桌面中使用它,我将把这个脚本挂钩到我的easystroke鼠标手势命令中,这样,例如,每次我为gmail做手势我都没有得到一个全新的gmail会话,我只是被带到我现有的gmail chrome app窗口。也许有一个更简单的方法可以解决所有这些,但我还没有找到它的方式。

在帮助下,我已经弄清楚如何使用pgrep找到命令行的PID以及如何使用wmctrl将焦点设置为窗口句柄,但我仍然坚持从PID到窗口ID。

6 个答案:

答案 0 :(得分:36)

xwininfo和xprop允许检索你想要的东西,但这有点棘手。

xwininfo允许检索所有已知窗口,xprop查询X关于_NET_WM_PID参数的单个窗口ID。

到目前为止,一个hacky方法是:

#!/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

结果:

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

正如您将看到的,即使您在屏幕上只看到一个窗口,单个进程也可能有一定数量的已知窗口。

也许你应该获得这些工具来源,以便制作你想要的东西。

答案 1 :(得分:24)

你也可以用 wmctrl 查找PID,事实上,我认为这是一种更好的方法。 xwininfo 会返回各种看似是Windows的实体,但您无法在桌面上找到它们。

如果你做 man wmctrl ,你会发现 wmctrl -l <​​/ strong>列出了桌面上实际可见的所有窗口(最重要的是) window ids titles -p 添加 PID -x 将添加窗口类

正如手册所说(RTFM,对吗?:D),wmctrl也可以搜索其中的一些并激活与搜索匹配的窗口。但是,我不知道是什么决定了所有可能的匹配中的哪一个将被返回。另一方面,您可以使用提供的列表函数编写一个包装器,该包装器可以更好地进行搜索,并且可能基于一些其他属性(例如上次访问窗口的时间戳),您可以通过查询提供的<例如,em> win id 到xprop。

下面这些代码行返回最近的一个mate-terminal类窗口实例:

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

无论如何,对于你所描述的你正在构建的东西 - 如果我是你,我会找出你想要的命令生成什么类型​​的窗口,然后根据它进行搜索,而不是基于PID。或者,您可以假设命令CMD可能生成具有包含CMD的类名的窗口。

找到你的线后,你应该使用窗口id
通过wmctrl激活窗口。

希望这有帮助。

旁注:我发现xdotool也可以根据类名和窗口标题进行搜索,但非常慢。在我的计算机上,这个bash脚本(调用相当多的外部实用程序)的速度是xdotool:P的编译替代品的10倍。

答案 2 :(得分:3)

Here是几个X11窗口管理解决方案(包括此问题之一)。

Xwininfo和xprop是获取所有窗口ID的好工具,但不是用于获取与PID关联的主窗口的ID的最简单工具(如果可以使用它们)。要获取主窗口的ID,请按以下方式使用wmctrl:

#!/usr/bin/env bash
# getwindidbypid
# 
# 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

示例:

user ~ $  getwindidbypid 37248
0x05a00012

如果wmctrl找到多个主窗口,此解决方案将打印多个窗口ID。要仅返回第一个,只需将[@]更改为[0]中的echo "${WIND_IDS[@]}"

答案 3 :(得分:2)

您可以使用:

xdotool getwindowfocus getwindowname

(原样:你不需要用任何东西替换那些听起来不错的名字。)

答案 4 :(得分:2)

$WINDOWID功能

xterm 环境下,您可以找到以下变量:

echo $WINDOWID
58720292

因此对于任何 other pid:

使用sed很快:

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

...

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

或在纯bash 函数中:

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
}

然后

getWinFromPid 123456 myWinId
xdotool windowactivate $myWinId

对于其他术语,例如gnome-terminal

这很强大,因为我们不希望terminal进程的pid,而需要使用终端的shell的pid。例如:

wmctrl -lp

不显示通缉的小伙子们!

所以我们必须在流程的层次结构中导航

1。从进程ID获取窗口ID

1a。当前活动的终端窗口

本人在活动会话中

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

在任何 active 终端控制台中键入此命令时,便会起作用。

然后使用xdotool

sleep 12; xdotool windowactivate $SHWINID

您现在可以切换到另一个窗口,它将在12秒后返回。

1b。来自任何外壳程序或子进程ID的窗口ID

我写了这个小函数:

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')
}

然后

getWinFromPid <process id> [<variable name>]

如果未提交变量名,则将使用pid id作为索引号填充全局数组$winIDfromPid

getWinFromPid 1234 winId
echo $winId
0x0100012345

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

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

Nota:已通过xtermmate-terminalkonsolegnome-terminal进行过测试。

Nota2:如果已经安装了wmctrl,则可以替换最后两行功能:

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

作者:

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

功能将变为大约2倍。

2。从窗口ID获取运行PID的进程

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

或没有 ID 的情况下,您将必须用鼠标单击:

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

进入功能

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

然后:

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。带有进程的Shell窗口列表

最后,有一个小功能可以显示带有进程的窗口列表。

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
}

然后

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]

答案 5 :(得分:0)

注意:我提供的第一个答案使用cmctrl将pids与Windows关联,以执行着眼于现有进程的任务。有时wmctrl无法正常运行,并希望重新启动。事实证明,使用进程名称本身更加有用,简化和可靠。因此,此版本使用进程名称,而不考虑pid。

我使用openbox和键盘。我使用以下在Debian中可用的bash来根据进程名称返回,启动和在现有窗口之间旋转:

#!/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

注意:此代码需要软件包wmctrl。

示例用法:

关注qpaeq 焦点-c2 tilda 焦点-c2 -m'。*-Slimjet'-p slimjet flashpeak-slimjet focus -m“。*-Mozilla Thunderbird”雷鸟 focus -m'。*-Mozilla Firefox'-p Firefox-esr firefox-esr