Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Appimage fails quitely and doesnt throw any errors #40

Open
silverhadch opened this issue Jan 11, 2025 · 2 comments
Open

[BUG] Appimage fails quitely and doesnt throw any errors #40

silverhadch opened this issue Jan 11, 2025 · 2 comments
Assignees
Labels
bug Something isn't working

Comments

@silverhadch
Copy link

Describe the bug
The ML4W_Hyprland_Settings-x86_64.AppImage fails silently when executed on Arch Linux with Hyprland. There are no meaningful error messages provided in the terminal or logs. Running the AppImage results in a crash, and the application does not launch.

To Reproduce
Steps to reproduce the behavior:

Install Hyprland and ml4w-hyprland-starter.
Run the AppImage (./ML4W_Hyprland_Settings-x86_64.AppImage).
Observe the silent failure (no UI, no output, application exits).

Expected Behavior
The application should launch successfully, displaying its UI and functionality.

Logs and Debug Information

Attempt to run with debug options (--debug, G_MESSAGES_DEBUG=all):
    Output:

(process:4117): Gtk-WARNING **: Unknown key gtk-modules in settings.ini
(process:4117): Adwaita-WARNING **: Using GtkSettings:gtk-application-prefer-dark-theme with libadwaita is unsupported.

No further logs or errors are displayed.

System logs:

journalctl -f

    No additional error messages related to the application.

Screenshots
N/A (application fails to launch).

Distribution Information

OS: Arch Linux (Hyprland)
Kernel: 6.12.8-zen1-1-zen
Hyprland Version:  Hyprland 0.46.2 built from branch  at commit 0bd541f2fd902dbfa04c3ea2ccf679395e316887  (version: bump to 0.46.2).

Date: Thu Dec 19 19:26:47 2024
Tag: v0.46.2, commits: 5566
built against:
aquamarine 0.7.0
hyprlang 0.6.0
hyprutils 0.3.3
hyprcursor 0.1.11
hyprgraphics 0.1.1

flags set:
debug

Steps Taken

Verified that dependencies (e.g., GTK4, Wayland, libadwaita) are installed.
Tested with different environment variables (GDK_BACKEND=x11, WAYLAND_DISPLAY=wayland-0).
Attempted running the AppImage with extracted contents using --appimage-extract.
Reinstalled Hyprland and ml4w-hyprland-starter.

Additional Context
The application appears to have compatibility issues with the current Arch setup or Hyprland environment. Similar applications (if any) work without issues.

@silverhadch silverhadch added the bug Something isn't working label Jan 11, 2025
@MH0386
Copy link

MH0386 commented Jan 30, 2025

same here

  • OS: Fedora Linux 41 (KDE Plasma) x86_64
  • Host: HP Spectre x360 Convertible 15-eb0xxx
  • Kernel: Linux 6.12.10-200.fc41.x86_64
  • Display (eDP-1): 2560x1440 @ 60 Hz in 15" [Built-in]
  • DE: Hyprland
  • WM: Hyprland :D (X11)
  • Theme: Breeze [GTK3]
  • Icons: WhiteSur-grey-dark [GTK3/4]
  • Font: Noto Sans (10pt) [GTK3/4]
  • Cursor: BreezeX-Black (24px)
  • Terminal: warp v0.2025.01.22.08.02.stable_05
  • Terminal Font: Hack (18.0pt)
  • CPU: Intel(R) Core(TM) i7-10750H (12) @ 5.00 GHz
  • GPU 1: NVIDIA GeForce GTX 1650 Ti Mobile [Discrete]
  • GPU 2: Intel UHD Graphics @ 1.15 GHz [Integrated]
  • Memory: 4.21 GiB / 15.23 GiB (28%)
  • Swap: 0 B / 15.23 GiB (0%)
  • Disk (/): 27.33 GiB / 951.87 GiB (3%) - btrfs
  • Local IP (wlo1): 192.168.1.7/24
  • Battery (Primary): 100% [AC Connected]
  • Locale: en_US.UTF-8

@razzervision
Copy link

Fix to The ML4W Hyprland Starter Setting App

properly not the best fix for the problem, and there is properly more bugs in the code.

  1. Navigate to the location of the ML4W_Hyprland_Settings-x86_64.AppImage file.

  2. Extract the AppImage using the following command:

    ML4W_Hyprland_Settings-x86_64.AppImage --appimage-extract
  3. Go to the squashfs-root/usr/bin directory.

  4. Modify the ml4w-hyprland-settings.py file with the provided code.

#  _   _                  _                 _
# | | | |_   _ _ __  _ __| | __ _ _ __   __| |
# | |_| | | | | '_ \| '__| |/ _` | '_ \ / _` |
# |  _  | |_| | |_) | |  | | (_| | | | | (_| |
# |_| |_|\__, | .__/|_|  |_|\__,_|_| |_|\__,_|
#        |___/|_|
#

import json
import os
import pathlib
import shutil
import subprocess
import sys
import threading
import time

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")

from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gtk

# Get script path
pathname = os.path.dirname(sys.argv[0])


# -----------------------------------------
# Define UI template
# -----------------------------------------
@Gtk.Template(filename=pathname + "/src/settings.ui")

# -----------------------------------------
# Main Window
# -----------------------------------------
class MainWindow(Adw.PreferencesWindow):
    __gtype_name__ = "Ml4wHyprlandSettingsWindow"

    settings_page = Gtk.Template.Child()
    options_page = Gtk.Template.Child()
    keywords_group = Gtk.Template.Child()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


# -----------------------------------------
# Main App
# -----------------------------------------
class MyApp(Adw.Application):
    win = Adw.ApplicationWindow()  # Application Window
    path_name = pathname  # Path of Application
    homeFolder = os.path.expanduser("~")  # Path to home folder
    configFolderName = "ml4w-hyprland-settings"  # config folder name
    configFolder = homeFolder + "/.config/" + configFolderName  # Config folder name
    settingsFolder = ""  # Settingsfolder
    hyprctlFile = ""  # hyprctl.conf
    hyprctl = {}  # hyprctl dictionary synced with hyprctlfile
    rowtype = {}  # rowtype dictionary for keywords
    pref_rows = {}  # Dictionary for pref row objects
    action_rows = {}  # Dictionary for action row objects
    keyword_blocked = False  # Temp Status of removing a keyword

    def __init__(self, **kwargs):
        super().__init__(
            application_id="com.ml4w.hyprland.settings",
            flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
        )
        self.create_action("quit", lambda *_: self.quit(), ["<primary>q"])
        self.create_action("about", self.on_about)

    def do_activate(self):
        # Define main window
        win = self.props.active_window
        if not win:
            win = MainWindow(application=self)

        # Setup Configuration
        self.runSetup()

        # Get pages
        self.settings_page = win.settings_page
        self.options_page = win.options_page

        # get groups
        self.keywords_group = win.keywords_group

        # Initialization
        self.initUI()

        # load hyprctl.sh on startup
        self.init_hyprctl = True

        if self.init_hyprctl:
            value = "true"
            print(":: Execute: " + self.settingsFolder + "/hyprctl.sh")
            subprocess.Popen(self.settingsFolder + "/hyprctl.sh")

        # Show Application Window
        win.present()
        print(":: Welcome to ML4W Hyprland Settings App")

    def initUI(self):
        # Load hyprctl.json
        hyprctl_file = open(self.settingsFolder + "/hyprctl.json")
        hyprctl_arr = json.load(hyprctl_file)
        for row in hyprctl_arr:
            self.hyprctl[row["key"]] = row["value"]

        if os.path.exists(self.settingsFolder + "/settings.json"):
            # Load Custom configuration
            config_file = open(self.settingsFolder + "/settings.json")
            config = json.load(config_file)
            print(":: Using custom settings.json in " + self.settingsFolder)
        else:
            # Load Default configuration
            config_file = open(pathname + "/settings.json")
            config = json.load(config_file)
            print(":: Using default settings.json in " + pathname)

        # Create Groups
        for p in config["groups"]:
            prefGroup = Adw.PreferencesGroup()
            prefGroup.set_title(p["title"])
            prefGroup.name = "group_" + p["title"]
            prefGroup.set_description(p["description"])

            # Create Rows
            for i in p["rows"]:

                # Fill rowtype dictionary
                self.rowtype[i["keyword"]] = i["type"]

                # Get row values
                if i["keyword"] not in self.hyprctl:
                    value = self.getKeywordValue(i["keyword"])
                else:
                    if i["type"] == "SpinRowFloat":
                        value = self.hyprctl[i["keyword"]] * 10
                    else:
                        value = self.hyprctl[i["keyword"]]

                # Create rows
                if i["type"] == "SpinRow":
                    self.createSpinRow(prefGroup, i, value)
                elif i["type"] == "SpinRowFloat":
                    self.createSpinFloatRow(prefGroup, i, value)
                elif i["type"] == "SwitchRow":
                    self.createSwitchRow(prefGroup, i, value)
                elif i["type"] == "ColorRow":
                    self.createColorRow(prefGroup, i, value)

            self.settings_page.add(prefGroup)

        # Create keyword rows
        for keyword in self.hyprctl:
            self.createActionRow(keyword)

    # ActionRow
    def createActionRow(self, keyword):
        actionRow = Adw.ActionRow()
        actionRow.set_title(keyword)
        btn = Gtk.Button()
        btn.set_label("Remove")
        btn.set_valign(3)
        btn.connect("clicked", self.remove_keyword, keyword)
        actionRow.add_suffix(btn)
        self.keywords_group.add(actionRow)
        self.action_rows[keyword] = actionRow

    def remove_keyword(self, widget, v):
        subprocess.Popen(["hyprctl", "reload"])
        print(":: Hyprctl Reload")
        # time.sleep(1)
        self.removeHyptctl(v)
        self.keywords_group.remove(self.action_rows[v])
        self.action_rows.pop(v)
        self.keyword_blocked = True
        value = self.getKeywordValue(v)
        if self.rowtype[v] == "SpinRow":
            self.pref_rows[v].set_value(int(value))
        if self.rowtype[v] == "SpinRowFloat":
            self.pref_rows[v].set_value(int(value) * 10)
        if self.rowtype[v] == "SwitchRow":
            self.pref_rows[v].set_active(value)
        if self.rowtype[v] == "ColorRow":
            color_rgba = Gdk.RGBA()
            if "rgb" in value:
                color_rgba.parse("#" + value.split("(")[1].split(")")[0])
            else:
                color_rgba.parse("#" + value[2:])
            self.pref_rows[v].set_rgba(color_rgba)

        print(":: Execute: " + self.settingsFolder + "/hyprctl.sh")
        subprocess.Popen(self.settingsFolder + "/hyprctl.sh")

    # SpinRow
    def createSpinRow(self, pref, row, value):
        spinRow = Adw.SpinRow()
        spinRow.set_title(row["title"])
        spinRow.set_subtitle(row["subtitle"])
        adjust = Gtk.Adjustment()
        adjust.set_lower(row["lower"])
        adjust.set_upper(row["upper"])
        adjust.set_value(int(value))
        adjust.set_step_increment(row["step"])
        spinRow.set_adjustment(adjust)
        adjust.connect("value-changed", self.on_spin_change, adjust, row)
        pref.add(spinRow)
        self.pref_rows[row["keyword"]] = spinRow

    def on_spin_change(self, adjust, *data):
        if not self.keyword_blocked:
            subprocess.Popen(
                ["hyprctl", "keyword", data[1]["keyword"], str(int(adjust.get_value()))]
            )
            if data[1]["keyword"] not in self.hyprctl:
                self.createActionRow(data[1]["keyword"])
            self.updateHyprctl(data[1]["keyword"], int(adjust.get_value()))
        self.keyword_blocked = False

    # SpinRowFloat
    def createSpinFloatRow(self, pref, row, value):
        spinRow = Adw.SpinRow()
        spinRow.set_title(row["title"])
        spinRow.set_subtitle(row["subtitle"])
        adjust = Gtk.Adjustment()
        adjust.set_lower(row["lower"])
        adjust.set_upper(row["upper"])
        adjust.set_value(int(value))
        adjust.set_step_increment(row["step"])
        spinRow.set_adjustment(adjust)
        adjust.connect("value-changed", self.on_spinfloat_change, adjust, row)
        pref.add(spinRow)
        self.pref_rows[row["keyword"]] = spinRow

    def on_spinfloat_change(self, adjust, *data):
        if not self.keyword_blocked:
            value = adjust.get_value() / 10
            subprocess.Popen(["hyprctl", "keyword", data[1]["keyword"], str(value)])
            if data[1]["keyword"] not in self.hyprctl:
                self.createActionRow(data[1]["keyword"])
            self.updateHyprctl(data[1]["keyword"], value)
        self.keyword_blocked = False

    # SwitchRow
    def createSwitchRow(self, pref, row, value):
        switchRow = Adw.SwitchRow()
        switchRow.set_title(row["title"])
        switchRow.set_subtitle(row["subtitle"])
        switchRow.set_active(value)
        switchRow.connect("notify::active", self.on_switch_change, row)
        pref.add(switchRow)
        self.pref_rows[row["keyword"]] = switchRow

    def on_switch_change(self, widget, *data):
        if not self.keyword_blocked:
            if widget.get_active():
                value = "true"
            else:
                value = "false"
            subprocess.Popen(["hyprctl", "keyword", data[1]["keyword"], value])
            if data[1]["keyword"] not in self.hyprctl:
                self.createActionRow(data[1]["keyword"])
            self.updateHyprctl(data[1]["keyword"], widget.get_active())
        self.keyword_blocked = False

    # ColorRow
    def createColorRow(self, pref, row, value):
        colorRow = Adw.ActionRow()
        colorRow.set_title(row["title"])
        colorRow.set_subtitle(row["subtitle"])
        color = Gtk.ColorDialogButton()
        color.set_valign(3)

        color_rgba = Gdk.RGBA()
        if "rgb" in value:
            color_rgba.parse("#" + value.split("(")[1].split(")")[0])
        else:
            color_rgba.parse("#" + value[2:])

        color.set_rgba(color_rgba)
        color_dialog = Gtk.ColorDialog()
        color.connect("notify::rgba", self.on_color_select, row)
        color.set_dialog(color_dialog)
        colorRow.add_suffix(color)

        pref.add(colorRow)
        self.pref_rows[row["keyword"]] = color

    def rgb_to_hex(self, rgb):
        r = int(rgb[0])
        g = int(rgb[1])
        b = int(rgb[2])
        return f"{r:02x}{g:02x}{b:02x}"

    def rgba_to_hex(self, rgba):
        r = int(rgba[0])
        g = int(rgba[1])
        b = int(rgba[2])
        a = int(float(rgba[3]) * 255)
        # return f'{r:02x}{g:02x}{b:02x}{a:02x}'
        return f"{r:02x}{g:02x}{b:02x}"

    def on_color_select(self, widget, *data):
        if not self.keyword_blocked:
            rgbaStr = widget.get_rgba().to_string()

            if "rgba" in rgbaStr:
                rgbaStr = rgbaStr.replace("rgba(", "")
                rgbaStr = rgbaStr.replace(")", "")
                rgba_hex = "rgb(" + self.rgba_to_hex(rgbaStr.split(",")) + ")"
            else:
                rgbaStr = rgbaStr.replace("rgb(", "")
                rgbaStr = rgbaStr.replace(")", "")
                rgba_hex = "rgb(" + self.rgb_to_hex(rgbaStr.split(",")) + ")"

            if data[1]["keyword"] not in self.hyprctl:
                self.createActionRow(data[1]["keyword"])
            self.updateHyprctl(data[1]["keyword"], rgba_hex)
            subprocess.Popen(["hyprctl", "keyword", data[1]["keyword"], rgba_hex])
        self.keyword_blocked = False

    # Update and write hyprctl.json
    def removeHyptctl(self, keyword):
        result = []
        del_key = ""
        for k, v in self.hyprctl.items():
            if k != keyword:
                result.append({"key": k, "value": v})
            else:
                del_key = k
        self.hyprctl.pop(del_key)
        self.writeToHyprctl(result)

    def updateHyprctl(self, keyword, value):
        result = []
        self.hyprctl[keyword] = value
        for k, v in self.hyprctl.items():
            result.append({"key": k, "value": v})
        self.writeToHyprctl(result)

    def writeToHyprctl(self, result):
        with open(self.settingsFolder + "/hyprctl.json", "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=4)

    # Get current keyword value

    def getKeywordValue(self, keyword):
        result = subprocess.run(
            f"hyprctl getoption {keyword}", shell=True, capture_output=True, text=True
        )

        outcome = result.stdout.strip()
        if not outcome:
            print(
                f":: Warning: No output received for '{keyword}'. Using default value."
            )
            return 0  # Or another default based on the expected type

        int_val = float_val = custom_val = None

        for line in outcome.split("\n"):
            if "int:" in line:
                int_val = line.split("int: ")[1].strip()
            elif "float:" in line:
                float_val = line.split("float: ")[1].strip()
            elif "custom type:" in line:
                custom_val = line.split("custom type: ")[1].strip()

        row_type = self.rowtype.get(keyword, "SpinRow")  # Default type

        if row_type == "SpinRowFloat":
            return int(float(float_val) * 10) if float_val else 0
        elif row_type == "ColorRow":
            return custom_val.split(" ")[0] if custom_val else "#000000"
        elif row_type == "SwitchRow":
            return int_val == "1" if int_val else False
        else:
            return int(int_val) if int_val else 0

    # File setup
    def runSetup(self):
        # Create ml4w-hyprland-settings in .config folder
        pathlib.Path(self.configFolder).mkdir(parents=True, exist_ok=True)
        self.settingsFolder = self.configFolder
        print(":: Using configuration in: " + self.settingsFolder)

        # Update hyprctl.sh in settingsFolder
        shutil.copy(self.path_name + "/hyprctl.sh", self.settingsFolder)
        print(":: hyprctl.sh updated in " + self.settingsFolder)

        # Create empty hyprctl.json if not exists
        if not os.path.exists(self.settingsFolder + "/hyprctl.json"):
            shutil.copy(self.path_name + "/hyprctl.json", self.settingsFolder)
            print(":: hyprctl.json created in " + self.settingsFolder)

    # Add Application actions
    def create_action(self, name, callback, shortcuts=None):
        action = Gio.SimpleAction.new(name, None)
        action.connect("activate", callback)
        self.add_action(action)
        if shortcuts:
            self.set_accels_for_action(f"app.{name}", shortcuts)

    def changeTheme(self, win):
        app = win.get_application()
        sm = app.get_style_manager()
        sm.set_color_scheme(Adw.ColorScheme.PREFER_DARK)

    def on_about(self, widget, _):

        dialog = Adw.AboutWindow(
            application_icon="application-x-executable",
            application_name="ML4W Hyprland Settings App",
            developer_name="Stephan Raabe",
            version="1.1",
            website="https://gitlab.com/stephan-raabe/ml4w-hyprland-settings",
            issue_url="https://gitlab.com/stephan-raabe/ml4w-hyprland-settings/-/issues",
            support_url="https://gitlab.com/stephan-raabe/ml4w-hyprland-settings/-/issues",
            copyright="© 2024 Stephan Raabe",
            license_type=Gtk.License.GPL_3_0_ONLY,
        )
        dialog.present()


# Application Start
app = MyApp()
sm = app.get_style_manager()
sm.set_color_scheme(Adw.ColorScheme.PREFER_DARK)
app.run(sys.argv)
  1. Now, convert it back into an AppImage using a tool like appimagetool.

  2. From the squashfs-root directory, run the following command:

    ARCH=x86_64 appimagetool ./ ML4W_Hyprland_Settings-x86_64.AppImage

    Replace ARCH=x86_64 with your system's architecture if different.

  3. Finally, move the new AppImage to the location of the original ML4W_Hyprland_Settings-x86_64.AppImage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants