Skip to content

Instantly share code, notes, and snippets.

@gelin
Last active April 21, 2026 19:35
Show Gist options
  • Select an option

  • Save gelin/27178bf2a54ba1f416211cef83fcb74a to your computer and use it in GitHub Desktop.

Select an option

Save gelin/27178bf2a54ba1f416211cef83fcb74a to your computer and use it in GitHub Desktop.
Adds any URL as the Chrome application to the main menu
#!/usr/bin/env python3
import argparse
import re
import tempfile
import os.path
import subprocess
import urllib.request
import urllib.parse
from PIL import Image
VENDOR = 'chrome'
CHROME_DEFAULT = '/opt/google/chrome/google-chrome'
PROFILE_DEFAULT = 'Default'
def main():
parser = argparse.ArgumentParser(description='Adds some site as the application to the main menu.')
parser.add_argument('url', help='url of the site to be used as the app')
parser.add_argument('--title', default='Chrome App', help='menu item title (default: Chrome App)')
parser.add_argument('--icon', default=None, help='url to the PNG icon for the app (default: None)')
parser.add_argument('--wmclass', default=None,
help='WM_CLASS for the app (default: based on URL and profile)')
parser.add_argument('--chrome', default=CHROME_DEFAULT,
help=f'path to Chrome binary (default: {CHROME_DEFAULT})')
parser.add_argument('--profile', default=PROFILE_DEFAULT,
help=f'Chrome profile (default: {PROFILE_DEFAULT})')
args = parser.parse_args()
with tempfile.TemporaryDirectory() as tempdir:
wmclass = args.wmclass if args.wmclass else find_class(args.url, args.profile)
icon = download_icon(tempdir, args.icon, wmclass)
icon_name = install_icon(icon, wmclass)
desktop_file = create_desktop_file(tempdir, args.chrome, args.profile, args.url, args.title, icon_name, wmclass)
install_desktop_file(desktop_file)
def find_class(url, profile=PROFILE_DEFAULT):
'''Returns window class from the site URL and Chrome profile.
It has "chrome-" prefix, followed by the URL domain, followed by "__",
followed by the URL path where each "/" is replaced with "_",
followed by "-" and the profile name.'''
parts = urllib.parse.urlsplit(url)
wmclass = VENDOR
wmclass += '-'
wmclass += parts.hostname
wmclass += re.sub(r'^/', '__', parts.path).replace('/', '_')
wmclass += '-'
wmclass += profile.replace(' ', '_')
return wmclass
def download_icon(tempdir, icon_url, wmclass):
# TODO: find icon from site URL
if icon_url is None:
return None
if os.path.exists(icon_url):
print('Using', icon_url, 'as icon')
return icon_url # assuming the icon is a local file
try:
print('Retrieving', icon_url)
filename = os.path.join(tempdir, f'{wmclass}.png')
filename, _ = urllib.request.urlretrieve(icon_url, filename=filename)
return filename
except Exception as e:
print('Failed:', e)
return None
def install_icon(icon_file, wmclass):
if icon_file is None:
return None
try:
icon_name = wmclass
size = resize_icon(icon_file)
print('Installing', icon_file, 'as', icon_name)
subprocess.run(
['xdg-icon-resource', 'install', '--size', str(size), icon_file, icon_name],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
)
return icon_name
except Exception as e:
print('Failed:', e)
return None
def resize_icon(icon_file):
image = Image.open(icon_file)
actual_size, _ = image.size
best_size = find_icon_size(actual_size)
if best_size != actual_size:
print('Resizing', icon_file, 'to', best_size)
resized = image.resize((best_size, best_size), Image.LANCZOS)
resized.save(icon_file)
return best_size
def find_icon_size(actual_size):
if actual_size >= 256:
return 256
elif actual_size >= 128:
return 128
elif actual_size >= 64:
return 64
elif actual_size >= 32:
return 32
else:
return 16
def create_desktop_file(tempdir, chrome, profile, url, title, icon_name, wmclass):
desktop_file = os.path.join(tempdir, f'{wmclass}.desktop')
try:
print('Creating', desktop_file)
with open(desktop_file, 'wt') as f:
print('[Desktop Entry]', file=f)
print('Type=Application', file=f)
print(f'Name={title}', file=f)
if icon_name:
print(f'Icon={icon_name}', file=f)
print(f"Exec={chrome} '--profile-directory={profile}' --app={url}", file=f)
print(f'StartupWMClass={wmclass}', file=f)
print('Terminal=false', file=f)
print('Categories=Network', file=f)
return desktop_file
except Exception as e:
print('Failed:', e)
return None
def install_desktop_file(desktop_file):
if desktop_file is None:
return
try:
print('Installing', desktop_file)
subprocess.run(
['xdg-desktop-menu', 'install', desktop_file],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
)
except Exception as e:
print('Failed:', e)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment