Firefox Background Process – How to Run Like Chromium

background-processchromiumfirefoxminimizewindow-manager

A known feature in Chromium is the option to make it run in the background, which makes it easier to open .

Is it possible to do the same with firefox (and other applications)?

Best Answer

Running an application in the background

The solution below will allow you to run firefox (or any other application) in the background, meaning: without a visible window. Nor will the application show in Dash as a running application:

enter image description here

enter image description here

If you pick Toggle Firefox however, the application will pop up immediately:

enter image description here

How it works

  1. If the panel icon (indicator) starts up, it launches a new firefox window, but immediately hides it (including possible existing firefox windows) from the face of the earth, using xdotool:

    xdotool windowunmap <window_id>
    

    This will not only hide the window, but also will hide the fact that firefox is running at all, since the unity launcher acts on visibly existing windows.

  2. The indicator stores the id of all unmapped windows in ~/.config/hidden_windows, to be mapped on the next time you choose Toggle Firefox from the menu.

The script

#!/usr/bin/env python3
import subprocess
import os
import signal
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, AppIndicator3
import time

app = "firefox"
winsdir = os.path.join(os.environ["HOME"], ".config/hidden_windows")

try:
    os.mkdir(winsdir)
except FileExistsError:
    pass

def checkruns(app):
    try:
        return subprocess.check_output(["pgrep", app]).decode("utf-8").strip()
    except subprocess.CalledProcessError:
        for w in os.listdir(winsdir):
            if w.startswith(app):
                os.remove(os.path.join(winsdir, w))

def get_currentwins(pid):
    wins = subprocess.check_output(["wmctrl", "-lp"]).decode("utf-8").splitlines()
    return [l.split()[0] for l in wins if pid in l]

def hide_currentwins(matches):
    # open(os.path.join(winsdir, "windowlist"), "wt").write("\n".join(matches))
    for w in matches:
        open(os.path.join(winsdir, app+"_"+w), "wt")
        subprocess.Popen(["xdotool", "windowunmap", w])

def run_app(app):
    subprocess.Popen(app)
    while True:
        time.sleep(1)
        pid = checkruns(app)
        matches = get_currentwins(pid) if pid else None
        if matches:
            hide_currentwins(matches)
            break

def restore_wins():
    for w in [w for w in os.listdir(winsdir) if w.startswith(app)]:
        wid = w.split("_")[-1]
        subprocess.Popen(["xdotool", "windowmap", wid])
        os.remove(os.path.join(winsdir, w))

def toggle_app(*args):
    pid = checkruns(app)
    if pid:
        matches = get_currentwins(pid)
        if matches:
            hide_currentwins(matches)
        else:
            restore_wins()
    else:
        subprocess.Popen(app)

run_app(app)

class Indicator():
    def __init__(self):
        self.app = 'toggle_app'
        self.indicator = AppIndicator3.Indicator.new(
            self.app, app,
            AppIndicator3.IndicatorCategory.OTHER)
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)       
        self.indicator.set_menu(self.create_menu())

    def create_menu(self):
        self.menu = Gtk.Menu()
        item_toggle = Gtk.MenuItem('Toggle '+app)
        item_toggle.connect("activate", toggle_app)
        self.menu.append(item_toggle)
        sep1 = Gtk.SeparatorMenuItem()
        self.menu.append(sep1)
        item_quit = Gtk.MenuItem('Quit')
        item_quit.connect('activate', self.stop)
        self.menu.append(item_quit)
        self.menu.show_all()
        return self.menu

    def stop(self, source):
        Gtk.main_quit()

Indicator()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()

How to use

  1. The script needs both wmctrl and xdotool

    sudo apt-get install wmctrl xdotool
    
  2. Copy the script into an empty file, save it as firefox_bg.py

  3. Test_run the script by the command:

    python3 /path/to/firefox_bg.py
    
  4. If all works fine, add it to Startup Applications: Dash > Startup Applications > Add. Add the command:

    /bin/bash -c "sleep 10 && python3 /path/to/firefox_bg.py"
    

    Alternatively, copy the code below into an empty file, save it as firefox_bgrunner.desktop in ~/usr/share/applications, log out and back in.

    [Desktop Entry]
    Type=Application
    Exec=python3 /path/to/firefox_bg.py
    Name=Firefox Webbrowser Background Runner
    Icon=firefox
    StartupWMClasss=nonsense
    

    *The last line, StartupWMClasss=nonsense is to make sure Firefox windows will appear under their own icon, not the one of the indicator.

    No need to mention that you have to edit the Exec= line to reflect the real (absolute) path to where you stored firefox_bg.py

    Then you will have the panel runner available from Dash:

    enter image description here

Other applications?

I tested the same procedure with gnome-terminal andThunderbird (the latter usually not the quickest to startup), and it works perfectly:

enter image description here

To use with other applications, simply edit the line:

app = "firefox"

Note however that some applications seem to check if their attempt to create a window succeeded, and create a second one if the first one is unmapped. This happened to me with Inkscape.

The script can even then perfectly be used though, but would need a small edit. If anyone ever might need to use it with Inkscape, please leave a comment.