Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions usr/lib/webapp-manager/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,18 @@ def __init__(self, path, codename):
self.navbar = False
self.privatewindow = False

is_webapp = False
has_startup_wmclass = False
has_webapp_metadata = False
has_legacy_webapp_wmclass = False
with open(path) as desktop_file:
for line in desktop_file:
line = line.strip()

# Identify if the app is a webapp
if "StartupWMClass=WebApp" in line or "StartupWMClass=Chromium" in line or "StartupWMClass=ICE-SSB" in line:
is_webapp = True
if line.startswith("StartupWMClass="):
has_startup_wmclass = True
if "StartupWMClass=WebApp" in line or "StartupWMClass=Chromium" in line or "StartupWMClass=ICE-SSB" in line:
has_legacy_webapp_wmclass = True
continue

if "Name=" in line:
Expand All @@ -125,29 +129,36 @@ def __init__(self, path, codename):
continue

if "X-WebApp-Browser=" in line:
has_webapp_metadata = True
self.web_browser = line.replace("X-WebApp-Browser=", "")
continue

if "X-WebApp-URL=" in line:
has_webapp_metadata = True
self.url = line.replace("X-WebApp-URL=", "")
continue

if "X-WebApp-CustomParameters" in line:
has_webapp_metadata = True
self.custom_parameters = line.replace("X-WebApp-CustomParameters=", "")
continue

if "X-WebApp-Isolated" in line:
has_webapp_metadata = True
self.isolate_profile = line.replace("X-WebApp-Isolated=", "").lower() == "true"
continue

if "X-WebApp-Navbar" in line:
has_webapp_metadata = True
self.navbar = line.replace("X-WebApp-Navbar=", "").lower() == "true"
continue

if "X-WebApp-PrivateWindow" in line:
has_webapp_metadata = True
self.privatewindow = line.replace("X-WebApp-PrivateWindow=", "").lower() == "true"
continue

is_webapp = has_legacy_webapp_wmclass or (has_startup_wmclass and has_webapp_metadata)
if is_webapp and self.name is not None and self.icon is not None:
self.is_valid = True

Expand Down Expand Up @@ -469,6 +480,19 @@ def edit_webapp(self, path, name, desc, browser, url, icon, category, custom_par
with open(path, 'w') as configfile:
config.write(configfile, space_around_delimiters=False)

def update_startup_wmclass(self, path, startup_wmclass):
config = configparser.RawConfigParser()
config.optionxform = str
config.read(path)

if not config.has_section("Desktop Entry"):
return

config.set("Desktop Entry", "StartupWMClass", startup_wmclass)

with open(path, 'w') as configfile:
config.write(configfile, space_around_delimiters=False)

def bool_to_string(boolean):
if boolean:
return "true"
Expand Down
111 changes: 111 additions & 0 deletions usr/lib/webapp-manager/webapp-manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import gettext
import locale
import os
import re
import shutil
import subprocess
import warnings
Expand Down Expand Up @@ -64,6 +65,7 @@ def __init__(self, application):
self.settings = Gio.Settings(schema_id="org.x.webapp-manager")
self.manager = WebAppManager()
self.selected_webapp = None
self.detecting_dialog = None
self.icon_theme = Gtk.IconTheme.get_default()

# Set the Glade file
Expand All @@ -87,6 +89,7 @@ def __init__(self, application):
self.remove_button = self.builder.get_object("remove_button")
self.edit_button = self.builder.get_object("edit_button")
self.run_button = self.builder.get_object("run_button")
self.run_detect_button = self.builder.get_object("run_detect_button")
self.ok_button = self.builder.get_object("ok_button")
self.name_entry = self.builder.get_object("name_entry")
self.desc_entry = self.builder.get_object("desc_entry")
Expand Down Expand Up @@ -114,6 +117,7 @@ def __init__(self, application):
self.remove_button.connect("clicked", self.on_remove_button)
self.edit_button.connect("clicked", self.on_edit_button)
self.run_button.connect("clicked", self.on_run_button)
self.run_detect_button.connect("clicked", self.on_run_detect_button)
self.ok_button.connect("clicked", self.on_ok_button)
self.favicon_button.connect("clicked", self.on_favicon_button)
self.name_entry.connect("changed", self.on_name_entry)
Expand Down Expand Up @@ -267,6 +271,7 @@ def on_webapp_selected(self, selection):
self.remove_button.set_sensitive(True)
self.edit_button.set_sensitive(True)
self.run_button.set_sensitive(True)
self.run_detect_button.set_sensitive(True)

def on_webapp_activated(self, treeview, path, column):
self.run_webapp(self.selected_webapp)
Expand Down Expand Up @@ -306,6 +311,111 @@ def run_webapp(self, webapp):
def on_run_button(self, widget):
self.run_webapp(self.selected_webapp)

def on_run_detect_button(self, widget):
if self.selected_webapp is None:
return

self.run_webapp(self.selected_webapp)
self.detect_startup_wmclass(self.selected_webapp)

@_async
def detect_startup_wmclass(self, webapp):
if webapp is None:
return

self.show_detecting_popup()

if shutil.which("xprop") is None:
self.close_detecting_popup()
self.show_info_message(
_("xprop Not Found"),
_("The 'xprop' command is not installed."),
_("Install xprop (x11-utils) to use WM_CLASS detection."))
return

try:
result = subprocess.run(["xprop", "WM_CLASS"], capture_output=True, text=True)
except Exception as e:
self.close_detecting_popup()
self.show_info_message(
_("WM_CLASS Detection Failed"),
str(e),
None)
return

if result.returncode != 0:
message = result.stderr.strip() if result.stderr else _("xprop returned an error.")
self.close_detecting_popup()
self.show_info_message(
_("WM_CLASS Detection Failed"),
message,
None)
return

wmclass_match = re.search(r'WM_CLASS\(STRING\)\s*=\s*"([^"]+)"', result.stdout)
if wmclass_match is None:
self.close_detecting_popup()
self.show_info_message(
_("WM_CLASS Detection Failed"),
_("Could not parse WM_CLASS output."),
result.stdout.strip())
return

startup_wmclass = wmclass_match.group(1)
self.manager.update_startup_wmclass(webapp.path, startup_wmclass)
self.on_startup_wmclass_updated(startup_wmclass)

@idle
def show_detecting_popup(self):
if self.detecting_dialog is not None:
return

dialog = Gtk.MessageDialog(
transient_for=self.window,
flags=0,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.NONE,
text=_("Detecting StartupWMClass"))
dialog.format_secondary_text(
_("Click the launched web app window to capture WM_CLASS.\n\nIf you click another window, the wrong class may be saved."))
dialog.set_modal(False)
dialog.show_all()
self.detecting_dialog = dialog

@idle
def close_detecting_popup(self):
if self.detecting_dialog is not None:
self.detecting_dialog.destroy()
self.detecting_dialog = None

@idle
def show_info_message(self, title, text, secondary_text=None):
dialog = Gtk.MessageDialog(
transient_for=self.window,
flags=0,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
text=title)
dialog.format_secondary_text(text)
if secondary_text:
dialog.format_secondary_text("%s\n\n%s" % (text, secondary_text))
dialog.run()
dialog.destroy()

@idle
def on_startup_wmclass_updated(self, startup_wmclass):
self.load_webapps()
dialog = Gtk.MessageDialog(
transient_for=self.window,
flags=0,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
text=_("StartupWMClass Updated"))
dialog.format_secondary_text(_("StartupWMClass was set to '%s'.") % startup_wmclass)
dialog.run()
dialog.destroy()
self.close_detecting_popup()

def on_ok_button(self, widget):
category = self.category_combo.get_model()[self.category_combo.get_active()][CATEGORY_ID]
browser = self.browser_combo.get_model()[self.browser_combo.get_active()][BROWSER_OBJ]
Expand Down Expand Up @@ -514,6 +624,7 @@ def load_webapps(self):
self.remove_button.set_sensitive(False)
self.edit_button.set_sensitive(False)
self.run_button.set_sensitive(False)
self.run_detect_button.set_sensitive(False)

webapps = self.manager.get_webapps()
for webapp in webapps:
Expand Down
21 changes: 21 additions & 0 deletions usr/share/webapp-manager/webapp-manager.ui
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="run_detect_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can-focus">False</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Run and detect StartupWMClass (click the launched app window)</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">applications-engineering-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
Expand Down