2

I've created a Firefox profile called "app" with a couple extensions used for hiding the address and tab bars. My goal is to get .desktop files for Google Inbox, Calendar, etc. which mimic Chrome's "Add to desktop" functionality. At the moment, I have .desktop files something like

#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Google Inbox (Firefox)
Exec=firefox -P app -new-window --class googleinbox inbox.google.com
Icon=email
StartupWMClass=googleinbox

However, when I have more than one of these, all windows get grouped launcher in the under the icon for the first one. When I use xprop WM_CLASS on each window, I find that they all indeed have the WM_CLASS of whichever I opened first.

How can I get Firefox to handle the -class flag per-window rather than per-profile?

Edit: I'm now using /usr/bin/firefox -P "PROFILE NAME" --class=WMCLASS instead of the janky script I posted below, and just dealing with needing to create new profiles for each webapp. In addition, I'm using this userChrome.css to remove the address and tab bar chrome.

3
  • for what it is worth you can now use --kiosk to hide all controls, i however seems to have issues getting --class to work from the commandline, guess i need a desktop file
    – Fuseteam
    Jul 28, 2021 at 13:36
  • @Fuseteam That sounds helpful. Actually, I've since curbed my neuroticism, and decided that having some window chrome is not necessarily a bad thing, for instance to deal with the new tabs that get created when I open a link in gmail. But, if you can't get --class to work at the command line, I wouldn't expect it to work in a .desktop file.
    – tsbertalan
    Aug 3, 2021 at 21:07
  • you're right it didn't, i've since looked further into it and appearently i just needed to create a new profile. it appears to work now. tho it pops up the profile manager when i use -P
    – Fuseteam
    Aug 4, 2021 at 13:35

2 Answers 2

3

Since no one seems to have a Firefox-specific answer (probably because that -class flag, not being documented in the man page, is not properly functional), I'll just post my ugly hack as a solution. If someone could improve it substantially, I'll give them the answer credit instead.

I wrote the following script, called it firefoxApp.sh, and put it in ~/bin/. It's a terrible hack, and I'm ashamed of it. And also proud. I ditched the separate Firefox profile, and instead just used wmctrl, grep, and xprop to change the WM_CLASS of the newly-created window, after a delay (since windows sometimes take a substantial amount of time to appear and be titled). If someone could tell me a more precise and reliable way to find the windows to reclass, that's not plagued by timing/race conditions, this script would be greatly improved. I tried and failed to do it with the PID of the launched process (presumably because the whole Firefox profile has one root PID). I won't post here the code for that attempt, since I no longer have it.

#!/bin/sh
targetclass=$1
url=$2
titlegrep=$3
if [ "$#" -ne 3 ]
then
    echo "USAGE: $0 TARGETCLASS URL TITLEGREP" 1>&2
    exit 1
fi

firefox -P default -new-window "$url" &

sleep 10

# Ensure only newlines split items in the upcoming for loop:
IFS='
'

for wid in `wmctrl -l -x | grep $titlegrep | awk '{ print \$1 }'`
do
    xprop -id $wid -f WM_CLASS 8s -set WM_CLASS $targetclass
done

This script I call using .desktop files in ~/.local/share/applications which look like the following.

#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Calendar (Firefox)
Exec=firefoxApp.sh googlecalendar calendar.google.com Calendar
Icon=calendar
StartupWMClass=googlecalendar

If I choose, I can use a more specific icon name like google-calendar-firefox-app, and then drop a file named google-calendar-firefox-app.svg in ~/.local/share/icons.

Update: I've new settled on using actual separate profiles for each such "app". It's a little more work to set them up, but this can be largely automated with another script which I'll just leave as a Gist here, and this works with Firefox rather than around it, eliminating the race-conditions of the window-renaming approach.

Of course, a significant downside feature of this approach is that these apps are sandboxed from the main Firefox profile, and won't share any plugins or extensions with it. So, e.g., you might need to copy passwords from LastPass manually.

As a bonus, the script also populates a userChrome.css file to hide the window chrome in the new profile, which aids in creating the illusion of web "apps". It can be invoked with --help to get

usage: create_firefox_app.py [-h] [--app_name APP_NAME]
                             [--hide_user_chrome HIDE_USER_CHROME]
                             [--run_after_creating RUN_AFTER_CREATING]
                             URL icon_name

positional arguments:
  URL                   Homepage to be used when opening new windows in the
                        profile via the .desktop file.
  icon_name             Icon name to use in .desktop file. An explicit path
                        can be given, or something that resolves using the
                        regular icon search path (however that works; e.g.,
                        for ~/.local/share/icons/gmail.svg, you could enter
                        just gmail.svg).

optional arguments:
  -h, --help            show this help message and exit
  --app_name APP_NAME   Name for the generated "app" to use in the .desktop
                        file. If not given, a santized version of the URL will
                        be used instead. (default: None)
  --hide_user_chrome HIDE_USER_CHROME
                        Whether to generate userChrome.css file that will hide
                        window chrome in all windows created in the new
                        profile. Useful to make web-apps seem more app-y.
                        (default: True)
  --run_after_creating RUN_AFTER_CREATING
                        Whether we should start the new app after creating it.
                        (default: True)

If you enable the hide_user_chrome option, and need to get it back for whatever reason, say to install an extension, just trash the generated ~/.mozilla/firefox/PROFILE/chrome/userChrome.css file and restart the profile.

2
  • I'm noting that this will quite working on pure Wayland, as wmctrl and xprop are specific to X11. However, as long as you are still running XWayland, I believe this method should still work. Nov 17, 2017 at 13:59
  • @MarkStosberg That could be true, but here I am responding to you seven years later, and still I'm forced to use x11 because key programs I have don't work on wayland (also, somehow this hack is still relevant--though you can get --class to work alongside --noremote, that also has downsides).
    – tsbertalan
    Feb 26 at 16:01
1

I think this fits here as well: I came up with this abomination (personal opinion... I'd just like for it to work with sh :) ) It has a few tricks, but is pretty robust for two Windows.. You could specify further windows too, but they have to be manually added by adding more "specificprofile1" along with the corresponding function.. Maybe someone could make it have more.. logic!

#!/bin/bash
# chromium-start.sh $1 
# e.g. put: 
# chrome-start.sh "Profile 1" to .desktop Exec=
# wmctrl -o 1366,0 ; chromium-browser %U --profile-directory=Profile\ 2 & sleep 3; wmctrl -o 0,0

# $1 = Profile folder name

profilename=$1

#2nd Chromium profile
specificprofile1="Profile 1"

echo "starting Chromium"
echo "args: " $1
echo "Profile name: " $profilename
echo "Specific profile: " $specificprofile1

# Just setting Chromium scaling variable, because of course Google Devs don't care about no fractional scaling on linux
scale_var=0.8

# Check if Chromium window with the specified class already exists
# Also allows using icons as "taskbar" switches (clicking icon takes to corresponding Chromium Window)
if wmctrl -l -x | grep "chromium-$profilename"
then
echo "Chromium Window exists, moving focus to it"
wmctrl -x -R chromium-"$profilename"
echo "true"

# Check if 2nd profile $specifiedprofile1 has been started yet or not. The WMCLASS(es) has to have been set correctly...
elif [[ "$specificprofile1" == "$profilename" ]] && [[ ! "`wmctrl -l -x | grep chromium-"$specificprofile1"`" ]]
then
    # TODO: Nesting
    if [ "$specificprofile1" == "$profilename" ]
    then
    echo $specificprofile1 "equals" $profilename
    fi
echo "#2 Chromium Window for $specificprofile1 does not exist"
# wmctrl moves to specific position of desktop (1366 means moving to the following workspace since my resolution is 1366x768)
# Be careful if using sleep timing, since the command needs to have enough time to execute to have the window in the correct workspace
wmctrl -o 1366,0
chromium-browser  --profile-directory="$profilename" --force-device-scale-factor=$scale_var %U &
# https://askubuntu.com/a/626524/654028
# Set's the chromium window which was opened latest to have a custom class, since Chromium doesn't care about the --class= flag...
# It has it's limitations, but should be robust enough for most use... Has not been tested long term.. Something probably could reset the WM_CLASS again
# xprop -id "$(wmctrl -l -x| grep "chromium-browser" | tail -n 1 |awk '{ print $1 }')" -f WM_CLASS 8s -set WM_CLASS "chromium-browser.chromium-$specificprofile1"

# Alternative method for checking if a window with specified class exists
# xprop -id "$(wmctrl -l -x| grep "chromium-$profilename" | tail -n 1 |awk '{ print $1 }')" | grep -o "WM_CLASS(STRING) = ".*"" | grep -o '".*"' | tr -d '"'

# https://stackoverflow.com/a/19441380/5776626
winrep=""
while [[ ! "`echo $winrep | grep -l "Map State: IsViewable"`" ]]
do
    winid="$(wmctrl -l -x| grep "chromium-$profilename" | tail -n 1 |awk '{ print $1 }')"
    # print $winid
    winrep="$(xwininfo -id $winid | grep -o 'Map State: IsViewable')"
    # print $winrep
    sleep 0.75
    xprop -id "$(wmctrl -l -x| grep "chromium-browser" | tail -n 1 |awk '{ print $1 }')" -f WM_CLASS 8s -set WM_CLASS "chromium-browser.chromium-$specificprofile1"
done
# sleep 3

# Move Window directly to workspace (#2 with 1366x768 resolution x = 1366), optionally comment out wmctrl -o 1366,0
# wmctrl -v -i -r $winid -e 0,1366,0,-1,-1

# sleep 5
# Move back to workspace #1
wmctrl -o 0,0


elif ! wmctrl -l -x | grep chromium-"$profilename"
then
echo "#3 Chromium Window $profilename does not exist"
wmctrl -o 0,0
chromium-browser  --profile-directory="$profilename" --force-device-scale-factor=$scale_var %U &


# https://askubuntu.com/a/626524/654028
# ....
# sleep 3

winrep=""
while [[ ! "`echo $winrep | grep -l "Map State: IsViewable"`" ]]
do
    winid="$(wmctrl -l -x| grep "chromium-$profilename" | tail -n 1 |awk '{ print $1 }')"
    # print $winid
    winrep="$(xwininfo -id $winid | grep -o 'Map State: IsViewable')"
    # print $winrep
    sleep 0.75
    xprop -id "$(wmctrl -l -x| grep "chromium-browser" | tail -n 1 |awk '{ print $1 }')" -f WM_CLASS 8s -set WM_CLASS "chromium-browser.chromium-$profilename"
done
wmctrl -o 0,0
# xprop -id "$(wmctrl -l -x| grep "chromium-browser" | tail -n 1 |awk '{ print $1 }')" -f WM_CLASS 8s -set WM_CLASS "chromium-browser.chromium-$profilename"
fi

Issues:

Printing gives errors (deprecation warnings..):

Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/%{ <-- HERE (.*?)}/ at /usr/bin/print line 528.

For debugging you can use the following to print out the errors when using actual icons: https://askubuntu.com/a/664272/654028 (# The Manual alternative)

awk '/^Exec=/ {sub("^Exec=", ""); gsub(" ?%[cDdFfikmNnUuv]", ""); exit system($0)}' chrome-ws2.desktop

while loop bugs out, probably because of looping interval

Error: no such file "at while function"
xwininfo: error: -id requires argument
xprop: error: Invalid window id format: .
xwininfo: error: -id requires argument
xprop: error: Invalid window id format: .
xwininfo: error: -id requires argument
xprop: error: Invalid window id format: .

Also when clicking a corresponding .desktop icon too fast (before the custom class has been set ?), a new window will be opened..

(quite) Sometimes when starting from both Chromium too fast (~<3s), the class of the previously opened window gets reset to chromium-browser.chromium-browser.. Then you can expect the icons to have swapped around or other unexpected behavior.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .