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?

share|improve this question
    
What is the command to start Telegram and what is the window name just after the application started? – Jacob Vlijm Aug 19 '15 at 6:54
    
The command I used is just the path of the app and the window name is Telegram Desktop – Hossein Soltanloo 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! – Hossein Soltanloo Aug 20 '15 at 5:54
1  
@SumeetDeshmukh you're an incredibly nice and generous person. Really! – Jacob Vlijm Jul 24 '17 at 18:15
up vote 26 down vote accepted
+50

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:

  1. Copy the script into an empty file, save it as startup_minimizd.py
  2. Test- run the script with (e.g.) gedit the command:

    python3 /path/to/startup_minimizd.py gedit gedit
    
  3. 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
    
share|improve this answer
    
wow, 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
1  
@fedorqui Good one, two exceptions can likely occur: subprocess.CalledProcesError (as a result of a buggy wmctrl) and IndexError (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. – Hossein Soltanloo Aug 19 '15 at 12:53
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

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.

share|improve this answer

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>

share|improve this answer
1  
You might want to note that xdotool wont work properly on installations with Wayland. – Videonauth Dec 3 '17 at 9:21

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)
share|improve this answer
    
I tried your first script. It didn't work or didn't minimize fast enough. I saved it as startminimized. Then I ran startminimized gnome-calendar. Calendar open and keep running? – Khurshid Alam Apr 1 '17 at 7:19
    
You 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 2 '17 at 10:08

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.