I just want Telegram to be run and I have added it to startup apps. The point is that I need it to be minimized. Any commands?
-
What is the command to start Telegram and what is the window name just after the application started? – Jacob Vlijm Aug 19 '15 at 06:54
-
The command I used is just the path of the app and the window name is Telegram Desktop – nermitt Aug 19 '15 at 10:55
-
Hi Hossien, just in case you might prefer using the pid instead of the window title, edited my answer. – Jacob Vlijm Aug 19 '15 at 19:11
-
@JacobVlijm Thanks! It's greatly efficient and useful! However the first method works seamlessly in variable window name cases. Good Job! – nermitt Aug 20 '15 at 05:54
-
1@SumeetDeshmukh you're an incredibly nice and generous person. Really! – Jacob Vlijm Jul 24 '17 at 18:15
-
@JacobVlijm it actually helped me, and it's an incredibly nice answer that's why I rewarded it :) – Sumeet Deshmukh Jul 25 '17 at 03:39
11 Answers
Starting an application minimized
Starting up an application in a minimized way takes two commands:
- starting the application
- minimize its window
Therefore, the command or script needs to be "smart"; the second command should wait for the application window to actually appear.
General solution to startup an application minimized
The script below does that and can be used as a general solution to startup an application in a minimized way. Just run it in the syntax:
<script> <command_to_run_the_application> <window_name>
The script
#!/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
How to use
The script needs both wmctrl
and xdotool
:
sudo apt-get install wmctrl xdotool
Then:
- Copy the script into an empty file, save it as
startup_minimizd.py
Test- run the script with (e.g.)
gedit
the command:python3 /path/to/startup_minimizd.py gedit gedit
- If all works fine, add the command (for your application) to
Startup Applications
Explanation
- The script starts up the application, running the command you gave as first argument
- Then the script checks the window list (with the help of
wmctrl
) for windows, named after your second argument. - If the window appears, it is immediately minimized with the help of
xdotool
To prevent an endless loop if the window might not appear for some reason, the script practices a time limit of 30 seconds for the window to appear.
Note
No need to mention that you can use the script for multiple applications at once, since you run it with arguments outside the script.
EDIT
recognizing the window by its pid
If the window title is unsure or variable, or there is a risk of name clashes in the window's name, using the pid
is a more reliable method to use.
The script below is based on the use of the application's pid, as in the output of both wmctrl -lp
and ps -ef
.
The setup is pretty much the same, but the window title is not needed in this version, so the command to run it is:
python3 /path/to/startup_minimizd.py <command_to_run_application>
Just like the first script, it needs both wmctrl
and xdotool
The script
#!/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)
Note on the second script
Although in general the second version should be more reliable, in cases when the application is started by a wrapper script, the pid of the command will be different from the application that is finally called.
In such cases, I recommend using the first script.
EDIT2 a specific version of the script for Steam
As requested in a comment, below a version, specifically made for starting up STEAM minimized.
Why a specific version for Steam?
It turns out Steam
behaves quite different from a "normal" application:
- It turns out
Steam
does not run one pid, but no less then (in my test) eight! Steam
runs on start up with at least two windows (one splash- like window), but sometimes an additional message window appears.- Windows of Steam have
pid 0
, which is a problem in the script as it was. - After the main window is created, the window is raised a second time after a second or so, so a single minimization won't do.
This exceptional behaviour of Steam
asks for a special version of the script, which is added below. The script starts up Steam
, and during 12 seconds, it keeps an eye on all new windows of the corresponding WM_CLASS
, checking if they are minimized. If not, the script makes sure they will be.
Like the original script, this one needs wmctrl
and xdotool
to be installed.
The script
#!/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)
To use it
- Simply copy it into an empty file, save it as
runsteam_minimized.py
Run it by the command:
python3 /path/to/runsteam_minimized.py

- 83,767
-
1wow, great one! I wouldn't though catch
except:
just to return None. Probably better let it fail so that you see what failed; otherwise, it can break for any kind of different causes and will pass unadvertised. – fedorqui Aug 19 '15 at 12:01 -
2@fedorqui Good one, two exceptions can likely occur:
subprocess.CalledProcesError
(as a result of a buggywmctrl
) andIndexError
(normal exception) will edit in a minute :). Thanks for mentioning – Jacob Vlijm Aug 19 '15 at 12:03 -
@HosseinSoltanloo What is exactly the command you run the script with? – Jacob Vlijm Aug 19 '15 at 12:38
-
@JacobVlijm The script works well but there is another problem that you might fix it. Whenever I have unread messages and I open the app, the window name changes to something like "Telegram (2)" as there are two unread messages and this way the script won't work cuz of change in the name. – nermitt Aug 19 '15 at 12:53
-
@HosseinSoltanloo Can be fixed, then we have to switch to the process name. Will post an edited version... – Jacob Vlijm Aug 19 '15 at 13:05
-
@HosseinSoltanloo btw the wkndow name does not need to be the full name, Telegram will do in your situation it seems. I will post an edited version though, based on pid. – Jacob Vlijm Aug 19 '15 at 13:26
-
Any thoughts on modifying this for programs like Steam that open an initialization window before the proper application window (the latter of which is what should actually be minimized) when run?
I tried using both versions of the script and was unable to get the desired results (i.e. once Steam has actually started up, the games library window doesn't automatically minimize to the Launcher). It worked beautifully for another application I tried it with, however (thanks!).
– Baku9 Mar 27 '16 at 17:34 -
@J.D.Holland should be possible. I don't use steam, but is it like a splash window? Does it disappear after a few seconds? I can check and install it if I am at my own system again. – Jacob Vlijm Mar 27 '16 at 17:37
-
@JacobVlijm Sort of, yeah. Here's a screenshot. It shows for just a couple of seconds, disappears, then the actual application is visible (but won't minimize itself). I may just not be doing something right. – Baku9 Mar 27 '16 at 17:57
-
2@J.D.Holland I am sure it can be fixed. Will look into it in somewhere in the next few days :) – Jacob Vlijm Mar 27 '16 at 18:02
-
@J.D.Holland done, added the steam version. Steam behaves quite exceptional :) – Jacob Vlijm Mar 28 '16 at 18:05
-
@JacobVlijm Just tried it out, and it did pretty much exactly what I was hoping for. I tested it by logging out and back in a few times, and my desktop hung for a bit on one of the logout attempts (not even sure if that's necessarily related to what we're doing, just thought it may be worth mentioning). Other than that one hiccup, however, it seems to be working perfectly! Thanks for going the extra mile on this, you've more than earned an upvote and green check mark. :] – Baku9 Mar 29 '16 at 18:12
-
Great! I use it to start Spotify minimzed on my server, works awesome. The PID method, that is. – Peterdk Mar 11 '17 at 18:44
-
-
-
ubuntu 22.04. script fails; message about gpu being unusable. added ' --no-sandbox' to subprocess.Popen(["/bin/bash", "-c", command]) and it works fine. Thanks! – David Nov 13 '23 at 14:48
It's good to have the scripts given by user72216 and Sergey as general solutions to the problem, but sometimes the application you wish to startup minimized already has a switch that will do what you want.
Here are a few examples with the corresponding startup program command strings:
- Telegram (since version 0.7.10) has the
-startintray
option:<path-to-Telegram>/Telegram -startintray
- Steam has the
-silent
option:/usr/bin/steam %U -silent
- Transmission has the
--minimized
option:/usr/bin/transmission-gtk --minimized
In Unity these applications start minimized as icons in the top menu bar rather than as icons on the launcher, though the normal launch icon will still appear once you start using the application. Other applications may behave differently.

- 81
I needed the programs closed to tray, not minimized, and I've tried all the scripts posted here, the ones that worked, worked only for some programs and not for others. So I've coded one that works much better (you almost don't see the window appearing, only the tray icon, it looks native) and works for all the programs I've tried. It's based on the Jacob's one. With this script you may need to add an argument depending on the program (see below) but always worked for me with lots of programs it should also work with steam.
Usage:
sudo apt-get install wmctrl xdotool
- Save the script as
startup_closed.py
give it execution permissions and then executepython3 ./startup_closed.py -c <command to open program>
- If the program tray icon does not show or the window does not show then you need to add one of these arguments:
-splash
or-hide
, by trial and error. For example:python3 ./startup_closed.py -hide -c teamviewer
orpython3 ./startup_closed.py -splash -c slack
- There are more arguments but you probably don't need them. Also there are full details of exactly when and why the arguments are needed in the help:
./startup_closed.py --help
Script:
#!/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")

- 131
-
This is pretty nice. I did however run into a case with the zoom application, where startup_closed.py tried to close the hosting terminal window, and successfully closed the vscode window that I had run startup_closed.py also. Not sure if it was due to the zoom application already running, or something else. Definitely a low delay value triggers this. Do you have a github or other public repository for this? – Jason Harrison Mar 23 '21 at 17:30
I came with a rather elegant solution that relies exclusively on xdotool
, and it's quite useful for applications that don't have a "start minimized" argument, like Telegram.
The only downside is that solution has to be manually crafted for each app, but assuming that's not a problem (e.g.: if you want to autostart a certain application without allowing it to pollute your screen after logging in), this is much simpler and straightforward.
Actual Examples
## 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 &
The Solution
At first glance, you might think it's better to use the process' PID or class to match against, however that's actually counterproductive as you frequently will get multiple results for the same PID. Examples are a 0x0 window that's actually waiting for a notification, a systray icon, or any other "hidden" window.
The solution is crafting a xdotool command that always returns only one unique window. In both my examples that was done using --name
, however you can combine multiple selectors with --all
(e.g.: match a given classname + a class name + a name regex). Usually a good --name
regex does the trick.
After crafting your search
conditions, simply spawn a instance of xdotool (detached from the shell) with the --sync
parameter and your conditions, followed by windowclose
. Run your app afterwards:
xdotool search --sync [... myapp-match-conditions] windowclose &
my-app
Check out
xdotool search --help
for all the possibilities of combinations you can arrange to be able to target the exact window you want. Sometimes it gets tricky and you have to combine several conditions, but once your finished, it'll rarely ever fail (unless an update changes the application and break your implementation, of course).

- 131
- 3
If program is being closed to tray, one might actually want to close program window on startup instead of minimizing it. One example of such program is Viber. In this case one could use following script 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
Usage: <path-to-script> <program-to-start>
-
1You might want to note that
xdotool
wont work properly on installations with Wayland. – Videonauth Dec 03 '17 at 09:21 -
I managed to do it without
wmctrl
(that didn't even work for me) using this command:
– AlonL May 27 '20 at 16:38xdotool search --onlyvisible --pid $pid --sync | xargs xdotool windowminimize
Just add -u
to the launch command.
E.g. to run Slack
minimized, use the command:
/usr/bin/slack %U -u
And autostart is in System Settings - Startup and Shutdown - Autostart
for KDE 5.

- 119
-
Looking at the other answers in this thread, do you think maybe that your answer a) is incomplete (i.e. doesn't apply to all applications) and b) lacks some explanation? – Jeremy Oct 15 '21 at 09:20
-
I took Jacob's scripts and modified them a bit to make a more universal one.
#!/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)
Main differences are:
- Program sets group ID (GID) for the process. Thus, all the child processes and their windows can be easily found
- xdotool --sync option is used instead of a while loop
- Script allows passing arguments to the program
WAIT_TIME should be set big enough to allow program to fork its child processes. On my computer it is enough for big programs like steam. Increase it, if needed.
Addition
xdotool
's option windowunmap
may work funky with some applications and tray programs (linux mint's tray, for example), so here's an alternative version of the script for those exceptions.
#!/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)

- 251
-
I tried your first script. It didn't work or didn't minimize fast enough. I saved it as
startminimized
. Then I ranstartminimized gnome-calendar
. Calendar open and keep running? – Khurshid Alam Apr 01 '17 at 07:19 -
1You can try to increase variable
WAIT_TIME
. I use 40 second delay for weak computers. Also you can try second script as it uses a different command to minimize the app. – Sergey Apr 02 '17 at 10:08
I was just surfing and came across this question, so I was just wondering what is your operating system? As for me i'm using UBUNTU BUDGIE 18.04 LTS so in this operating system it is very simple.
Simply go to menu
From Menu go to Budgie Desktop Settings
and
From Desktop Setting go to Auto Start
It will give you 2 options, from "+" add
1. Add Application
2. Add Command
By selecting Add Application all applications will be listed select any application you want and it will start when you start your computer and it will also be minimized.

- 111
Similar to another answer, but using just one line of code and startup scripts (Menu > Startup Applications). wmctrl -c <WIN>
naturally closes a window by matching a string to its title. For example, below I close the programs Autokey, Telegram, Pidgin, and Whatsapp:
/bin/bash -c "sleep 25 && wmctrl -c 'Autokey' && wmctrl -c 'Telegram' && wmctrl -c 'Buddy' && wmctrl -c 'Whatsapp'"

- 111
I landed on this page recently so though I would post an updated answer this solution works for Wayland
APP_COMMAND & sleep 2 wmctrl -ic $(wmctrl -l | grep APP_WINDOW_TITLE | awk '{print $1;}')
example
qbittorrent & sleep 2 && wmctrl -ic $(wmctrl -l | grep qBittorrent | awk '{print $1;}')
Note that this is designed for apps that would be in the system tray when closed

- 101