如何在启动时运行程序,将其最小化?


19

我只想运行Telegram,并将其添加到启动应用程序中。关键是我需要将其最小化。有命令吗?


启动应用程序后,启动Telegram的命令是什么?窗口名称是什么?
Jacob Vlijm

我使用的命令只是应用程序的路径,窗口名称是Telegram Desktop
Hossein Soltanloo 2015年

嗨,Hossien,为防万一您可能更喜欢使用pid而不是窗口标题,请编辑我的答案。
Jacob Vlijm

@JacobVlijm谢谢!这是非常有效和有用的!但是,第一种方法可以在可变窗口名称的情况下无缝运行。做得好!
Hossein Soltanloo 2015年

1
@SumeetDeshmukh你是一个非常好和慷慨的人。真!
雅各布·弗利姆

Answers:


29

最小化启动应用程序

以最小化的方式启动应用程序需要两个命令:

  • 启动应用程序
  • 最小化其窗口

因此,命令或脚本必须是“智能”的。第二个命令应等待应用程序窗口实际出现。

最小化启动应用程序的一般解决方案

下面的脚本可以执行此操作,并且可以用作以最小化方式启动应用程序的常规解决方案。只需使用以下语法运行它:

<script> <command_to_run_the_application> <window_name>

剧本

#!/usr/bin/env python3
import subprocess
import sys
import time

subprocess.Popen(["/bin/bash", "-c", sys.argv[1]])
windowname = sys.argv[2]

def read_wlist(w_name):
    try:
        l = subprocess.check_output(["wmctrl", "-l"]).decode("utf-8").splitlines()
        return [w.split()[0] for w in l if w_name in w][0]
    except (IndexError, subprocess.CalledProcessError):
        return None

t = 0
while t < 30:
    window = read_wlist(windowname)
    time.sleep(0.1)
    if window != None:
        subprocess.Popen(["xdotool", "windowminimize", window])
        break
    time.sleep(1)
    t += 1

如何使用

该脚本同时需要wmctrlxdotool

sudo apt-get install wmctrl xdotool

然后:

  1. 将脚本复制到一个空文件中,另存为 startup_minimizd.py
  2. 使用gedit以下命令测试脚本:

    python3 /path/to/startup_minimizd.py gedit gedit
    
  3. 如果一切正常,将命令(针对您的应用程序)添加到 Startup Applications

说明

  • 该脚本启动应用程序,运行您作为第一个参数给出的命令
  • 然后,脚本检查Windows的窗口列表(在的帮助下wmctrl),该窗口以第二个参数命名。
  • 如果出现该窗口,则在的帮助下将其最小化。xdotool 为了防止由于某种原因而可能不出现该窗口的无限循环,该脚本将窗口的显示时间限制为30秒。

注意

无需提及您可以一次将脚本用于多个应用程序,因为使用脚本外部的参数运行脚本即可。


编辑

通过pid识别窗口

如果窗口标题不确定或可变,或者窗口名称中存在名称冲突的风险,则使用pid可以更可靠地使用。

下面的脚本是基于使用的应用程序的PID,如在两者的输出wmctrl -lpps -ef

设置几乎相同,但是此版本不需要窗口标题,因此运行它的命令是:

python3 /path/to/startup_minimizd.py <command_to_run_application>

就像第一个脚本,它需要双方wmctrlxdotool

剧本

#!/usr/bin/env python3
import subprocess
import sys
import time

command = sys.argv[1]
command_check = command.split("/")[-1]

subprocess.Popen(["/bin/bash", "-c", command])

t = 1
while t < 30:
    try:
        w_list = [l.split() for l in subprocess.check_output(["wmctrl", "-lp"]).decode("utf-8").splitlines()]
        proc = subprocess.check_output(["pgrep", "-f", command_check]).decode("utf-8").strip().split()
        match = sum([[l[0] for l in w_list if p in l] for p in proc], [])
        subprocess.Popen(["xdotool", "windowminimize", match[0]])
        break
    except (IndexError, subprocess.CalledProcessError):
        pass
    t += 1
    time.sleep(1)

注意第二个脚本

尽管通常第二个版本应该更可靠,但是如果应用程序是由包装脚本启动的,则命令的pid将不同于最终调用的应用程序。

在这种情况下,我建议使用第一个脚本。



EDIT2 Steam脚本的特定版本

根据评论中的要求,在专门用于启动STEAM的版本下面将其最小化。

为什么要使用Steam的特定版本?

事实证明,Steam其行为与“常规”应用程序完全不同:

  • 事实证明,Steam它不会运行一个 pid,但至少不超过(在我的测试中)八个!
  • Steam在启动时至少有两个窗口(一个类似飞溅的窗口)运行,但有时会出现一个附加的消息窗口。
  • Windows的Steam具有pid 0,这在脚本中是一个问题。
  • 创建主窗口后,大约一秒钟后第二次提升窗口,因此单次最小化将不起作用。

这种特殊的行为Steam要求提供特殊版本的脚本,下面将添加该版本。脚本启动Steam,并在12秒钟内监视相应窗口的所有新窗口WM_CLASS,检查它们是否已最小化。如果没有,脚本将确保它们将是。

像原来的剧本,这个需求wmctrlxdotool安装。

剧本

#!/usr/bin/env python3
import subprocess
import time

command = "steam"
subprocess.Popen(["/bin/bash", "-c", command])

def get(cmd):
    return subprocess.check_output(cmd).decode("utf-8").strip()

t = 0

while t < 12:
    try:
        w_list = [l.split()[0] for l in get(["wmctrl", "-l"]).splitlines()]
        for w in w_list:
            data = get(["xprop", "-id", w])
            if all(["Steam" in data, not "_NET_WM_STATE_HIDDEN" in data]):
                subprocess.Popen(["xdotool", "windowminimize", w])
    except (IndexError, subprocess.CalledProcessError):
        pass

    t += 1
    time.sleep(1)

使用它

  • 只需将其复制到一个空文件中,另存为 runsteam_minimized.py
  • 通过以下命令运行它:

    python3 /path/to/runsteam_minimized.py
    

哇,太棒了!我不会抓到except:只返回None。最好让它失败,以便您查看失败的原因;否则,它可能会因各种不同的原因而中断,并且会在没有广告的情况下通过。
fedorqui

1
@fedorqui好一个,可能会发生两个异常:(subprocess.CalledProcesError 由于错误wmctrl)和IndexError(正常异常)将在一分钟内编辑:)。感谢您的提及
Jacob Vlijm 2015年

@HosseinSoltanloo运行脚本的确切命令是什么?
Jacob Vlijm

@JacobVlijm脚本运行良好,但是您可能会解决另一个问题。每当我有未读消息并打开应用程序时,窗口名称就会更改为“ Telegram(2)”之类的名称,因为有两条未读消息,因此脚本将无法更改名称。
侯赛因·索尔坦鲁

2
@JDHolland我确定它可以解决。会在未来几天的某个地方对其进行调查:)
Jacob Vlijm

3

最好将user72216和Sergey给出的脚本作为该问题的常规解决方案,但是有时您希望最小化启动的应用程序已经具有可以执行您想要的操作的开关。

以下是一些带有相应启动程序命令字符串的示例:

  • 电报(版本0.7.10起)具有以下-startintray选项:<path-to-Telegram>/Telegram -startintray
  • Steam具有以下-silent选项:/usr/bin/steam %U -silent
  • 传输--minimized选项:/usr/bin/transmission-gtk --minimized

在Unity中,这些应用程序开始时会以顶部菜单栏中的图标(而不是启动器上的图标)最小化,尽管在您开始使用该应用程序后仍会出现正常的启动图标。其他应用程序的行为可能有所不同。


1

我采用了Jacob的脚本,并对其进行了一些修改,以使其更具通用性。

#!/usr/bin/python

import os
import subprocess
import sys
import time
import signal

WAIT_TIME = 10


def check_exist(name):
    return subprocess.Popen("which "+name,
                            shell=True,
                            stdout=subprocess.PIPE
                            ).stdout.read().rstrip("-n")


def killpid(pidlist):
    for pid in pidlist:
        args = ["xdotool",
                "search",
                "--any",
                "--pid",
                pid,
                "--name",
                "notarealprogramname",
                "windowunmap",
                "--sync",
                "%@"]
        subprocess.Popen(args)


def killname(name):
    args = ["xdotool",
            "search",
            "--any",
            "--name",
            "--class",
            "--classname",
            name,
            "windowunmap",
            "--sync",
            "%@"]
    subprocess.Popen(args)


sys.argv.pop(0)

if check_exist(sys.argv[0]) == "":
    sys.exit(1)
if check_exist("xdotool") == "":
    sys.stderr.write("xdotool is not installed\n")
    sys.exit(1)
if check_exist("wmctrl") == "":
    sys.stderr.write("wmctrl is not installed\n")
    sys.exit(1)

try:
    prog = subprocess.Popen(sys.argv, preexec_fn=os.setsid)
except OSError, e:
    sys.exit(1)

time.sleep(WAIT_TIME)
idlist = subprocess.Popen("pgrep -g " + str(prog.pid),
                          shell=True,
                          stdout=subprocess.PIPE
                          ).stdout.read().splitlines()

ps1 = os.fork()
if ps1 > 0:
    ps2 = os.fork()

if ps1 == 0:  # Child 1
    os.setpgid(os.getpid(), os.getpid())
    killpid(idlist)
    sys.exit(0)
elif ps2 == 0:  # Child 2
    killname(os.path.basename(sys.argv[0]))
    sys.exit(0)
elif ps1 > 0 and ps2 > 0:  # Parent
    time.sleep(WAIT_TIME)
    os.killpg(os.getpgid(int(ps1)), signal.SIGTERM)
    os.kill(ps2, signal.SIGTERM)
    os.waitpid(ps1, 0)
    os.waitpid(ps2, 0)
    sys.exit(0)
else:
    exit(1)

主要区别在于:

  • 程序设置过程的组ID(GID)。因此,所有子进程及其窗口都可以轻松找到
  • 使用xdotool --sync选项代替while循环
  • 脚本允许将参数传递给程序

WAIT_TIME应该设置得足够大,以允许程序派生其子进程。在我的计算机上,足以应付Steam之类的大型程序。如果需要,增加它。

加成

xdotool的选项windowunmap在某些应用程序和任务栏程序(例如,Linux mint的任务栏)上可能很时髦,因此这是这些异常的脚本的替代版本。

#!/usr/bin/python

import os
import subprocess
import sys
import time
import signal

WAIT_TIME = 10


def check_exist(name):
    return subprocess.Popen("which "+name,
                            shell=True,
                            stdout=subprocess.PIPE
                            ).stdout.read().rstrip("-n")


def killpid(pidlist):
    for pid in pidlist:
        args = ["xdotool",
                "search",
                "--sync",
                "--pid",
                pid]
        for i in subprocess.Popen(args,
                                  stdout=subprocess.PIPE).\
                stdout.read().splitlines():
            if i != "":
                subprocess.Popen("wmctrl -i -c " +
                                 hex(int(i)), shell=True)


def killname(name):
    args = ["xdotool",
            "search",
            "--sync",
            "--any",
            "--name",
            "--class",
            "--classname",
            name]
    for i in subprocess.Popen(args,
                              preexec_fn=os.setsid,
                              stdout=subprocess.PIPE)\
            .stdout.read().splitlines():
        if i != "":
            subprocess.Popen("wmctrl -i -c " + hex(int(i)),
                             shell=True)


sys.argv.pop(0)

if check_exist(sys.argv[0]) == "":
    sys.exit(1)
if check_exist("xdotool") == "":
    sys.stderr.write("xdotool is not installed\n")
    sys.exit(1)
if check_exist("wmctrl") == "":
    sys.stderr.write("wmctrl is not installed\n")
    sys.exit(1)


try:
    prog = subprocess.Popen(sys.argv, preexec_fn=os.setsid)
except OSError, e:
    sys.exit(1)

time.sleep(WAIT_TIME)
idlist = subprocess.Popen("pgrep -g " + str(prog.pid),
                          shell=True,
                          stdout=subprocess.PIPE
                          ).stdout.read().splitlines()

ps1 = os.fork()
if ps1 > 0:
    ps2 = os.fork()

if ps1 == 0:  # Child 1
    os.setpgid(os.getpid(), os.getpid())
    killpid(idlist)
    sys.exit(0)
elif ps2 == 0:  # Child 2
    killname(os.path.basename(sys.argv[0]))
    sys.exit(0)
elif ps1 > 0 and ps2 > 0:  # Parent
    time.sleep(WAIT_TIME)
    os.killpg(os.getpgid(int(ps1)), signal.SIGTERM)
    os.kill(ps2, signal.SIGTERM)
    os.waitpid(ps1, 0)
    os.waitpid(ps2, 0)
    sys.exit(0)
else:
    exit(1)

我尝试了您的第一个脚本。它不起作用或没有足够快地最小化。我将其另存为startminimized。然后我跑了startminimized gnome-calendar。日历打开并继续运行?
库尔希德·阿拉姆

1
您可以尝试增加variable WAIT_TIME。对于较弱的计算机,我使用40秒的延迟。您也可以尝试第二个脚本,因为它使用其他命令来最小化应用程序。
谢尔盖

1

如果程序正在关闭到托盘上,则实际上可能是要在启动时关闭程序窗口,而不是将其最小化。这种程序的一个例子是Viber。在这种情况下,可以使用以下脚本start_closed.sh

#!/bin/bash

# Check that there is only one input argument
if [[ $# -gt 1 ]]; then
echo "Usage: $0 <program-to-start>"
exit 1
fi

$1 &                               # Start program passed in first argument
pid=$!                             # Get PID of last started program
xdotool search --sync --pid $pid | # Wait for window with PID to appear...
xargs wmctrl -i -c                 # ...and close it

用法: <path-to-script> <program-to-start>


1
您可能需要注意,xdotool在Wayland上的安装中无法正常工作。
Videonauth

0

我只是在冲浪并且遇到了这个问题,所以我只是想知道您的操作系统是什么?至于我,我正在使用UBUNTU BUDGIE 18.04 LTS因此在此操作系统中,它非常简单。

只需转到菜单

从菜单转到Budgie桌面设置

从桌面设置转到自动启动

它将为您提供2个选项,从“ +”添加

1.添加应用

2.添加命令

通过选择添加应用程序,将列出所有应用程序,选择所需的任何应用程序,它将在您启动计算机时启动,并且也将其最小化。


0

我需要封闭的程序而不是最小化的程序,并且我尝试了这里发布的所有脚本,那些有效的脚本仅对某些程序有效,而对其他程序无效。因此,我编写了一个效果更好的代码(几乎看不到窗口出现,只有托盘图标看起来很自然),并且可以对我尝试过的所有程序都有效。它基于雅各的作品。使用此脚本,您可能需要根据程序添加一个参数(请参见下文),但对于许多程序而言,它始终对我有用,它也应该可以使用Steam。

用法:

  1. sudo apt-get install wmctrl xdotool
  2. 将脚本另存为,startup_closed.py授予其执行权限,然后执行python3 ./startup_closed.py -c <command to open program>
  3. 如果程序托盘图标未显示或窗口未显示,则您需要添加以下参数之一:-splash-hide,反复尝试。例如:python3 ./startup_closed.py -hide -c teamviewerpython3 ./startup_closed.py -splash -c slack
  4. 还有更多参数,但您可能不需要它们。另外,还提供了有关在帮助中何时以及为什么需要参数的确切详细信息:./startup_closed.py --help

脚本:

#!/usr/bin/env python3
import subprocess
import sys
import time
import argparse
import random

parser = argparse.ArgumentParser(description='This script executes a command you specify and closes or hides the window/s that opens from it, leaving only the tray icon. Useful to "open closed to tray" a program. If the program does not have a tray icon then it just gets closed. There is no magic solution to achieve this that works for all the programs, so you may need to tweek a couple of arguments to make it work for your program, a couple of trial and error may be required with the arguments -splash and -hide, you probably will not need the others.')

parser.add_argument("-c", type=str, help="The command to open your program. This parameter is required.", required=True)
parser.add_argument("-splash", help="Does not close the first screen detected. Closes the second window detected. Use in programs that opens an independent splash screen. Otherwise the splash screen gets closed and the program cannot start.", action='store_true', default=False)
parser.add_argument("-hide", help="Hides instead of closing, for you is the same but some programs needs this for the tray icon to appear.", action='store_true', default=False)
parser.add_argument("-skip", type=int, default=0, help='Skips the ammount of windows specified. For example if you set -skip 2 then the first 2 windows that appear from the program will not be affected, use it in programs that opens multiple screens and not all must be closed. The -splash argument just increments by 1 this argument.', required=False)
parser.add_argument("-repeat", type=int, default=1, help='The amount of times the window will be closed or hidden. Default = 1. Use it for programs that opens multiple windows to be closed or hidden.', required=False)
parser.add_argument("-delay", type=float, default=10, help="Delay in seconds to wait before running the application, useful at boot to not choke the computer. Default = 10", required=False)
parser.add_argument("-speed", type=float, default=0.02, help="Delay in seconds to wait between closing attempts, multiple frequent attempts are required because the application may be still loading Default = 0.02", required=False)

args = parser.parse_args()

if args.delay > 0:
    finalWaitTime = random.randint(args.delay, args.delay * 2);
    print(str(args.delay) + " seconds of delay configured, will wait for: " + str(finalWaitTime))
    time.sleep(finalWaitTime)
    print("waiting finished, running the application command...")

command_check = args.c.split("/")[-1]
subprocess.Popen(["/bin/bash", "-c", args.c])

hasIndependentSplashScreen = args.splash
onlyHide = args.hide
skip = args.skip
repeatAmmount = args.repeat
speed = args.speed

actionsPerformed = 0
lastWindowId = 0

if hasIndependentSplashScreen:
    skip += 1

while True:
    try:
        w_list = [l.split() for l in subprocess.check_output(["wmctrl", "-lp"]).decode("utf-8").splitlines()]
        proc = subprocess.check_output(["pgrep", "-f", command_check]).decode("utf-8").strip().split()
        match = sum([[l[0] for l in w_list if p in l] for p in proc], [])
        if len(match) > 0:
            windowId = match[0]
            if windowId != lastWindowId:
                if skip > 0:
                    skip -= 1
                    print("skipped window: " + windowId)
                    lastWindowId = windowId
                else:
                    print("new window detected: " + windowId)
                    if onlyHide:
                        subprocess.Popen(["xdotool", "windowunmap", windowId])
                        print("window was hidden: " + windowId)
                    else:
                        subprocess.Popen(["xdotool", "key", windowId, "alt+F4"])
                        print("window was closed: " + windowId)

                    actionsPerformed += 1
                    lastWindowId = windowId

            if actionsPerformed == repeatAmmount:
                break

    except (IndexError, subprocess.CalledProcessError):
        break

    time.sleep(speed)

print("finished")

0

我提供了一个非常优雅的解决方案,该解决方案仅依赖于xdotool,并且对于没有“最小化启动”参数的应用程序(例如Telegram)非常有用。

唯一的缺点是必须为每个应用程序手动设计解决方案,但是假设这不是问题(例如:如果要自动启动某个应用程序,而又不允许它在登录后污染屏幕),这将更加简单明了。 。

实际例子

## Starts Telegram and immediately closes it
xdotool search --sync --onlyvisible --name '^Telegram$' windowclose &
telegram-desktop &
## Starts WhatsApp and immediately closes it
xdotool search --sync --onlyvisible --name '(\([0-9]*\) ){0,1}(WhatsApp$|WhatsApp Web$)' windowclose &
whatsapp-nativefier &

解决方案

乍一看,您可能会认为最好使用流程的PID或类进行匹配,但这实际上适得其反,因为您经常会为同一PID获得多个结果。例如,实际上正在等待通知的0x0窗口,系统托盘图标或任何其他“隐藏”窗口。

解决方案是设计一个xdotool命令,该命令始终仅返回一个唯一窗口。在我使用的两个示例中--name,您都可以结合使用多个选择器--all (例如:匹配给定的类名+类名+名称正则表达式)通常,好的--name正则表达式可以解决问题。

制作search完条件后,只需生成一个带有参数和条件的xdotool实例(与外壳分离)--sync,然后是windowclose。之后运行您的应用程序:

xdotool search --sync [... myapp-match-conditions] windowclose &
my-app

找出xdotool search --help所有可能组合的可能性,您可以安排这些组合来定位所需的确切窗口。有时,它变得棘手,您必须结合多个条件,但是一旦完成,它就很少会失败(当然,除非有更新会更改应用程序并破坏您的实现)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.