Linux Lite 7.8 RC1 has been released - Click here



Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Linux Lite 7.8 RC1 Released
#11
Je l'ai testé rapidement, et tout est vraiment fluide. J'ai hâte de voir ce que les autres pensent des nouvelles applications ! Idea Rolleyes
Reply
#12
Wow, that was fast!  Thanks Jerry!

Could you supply the file name and line # with the code change and I'll test again (in dark mode etc.)

Cheers!
Reply
#13
Just replace the existing code with this:

Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Lite Welcome - GTK4 Native Version
# Copyright 2012-2013 "Korora Project" <[email protected]>
# Copyright 2013-2015 "Manjaro Linux" <[email protected]>
# Copyright 2014-2025 "Jerry Bezencon" <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import signal
import subprocess
from webbrowser import open_new_tab

# Force CPU/Cairo rendering for VM compatibility (fixes white window issues)
os.environ['GSK_RENDERER'] = 'cairo'

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, GLib, Gio, Adw, GdkPixbuf

# Try to import GdkX11 for window centering on X11 systems
try:
    gi.require_version('GdkX11', '4.0')
    from gi.repository import GdkX11
    HAS_X11 = True
except (ValueError, ImportError):
    HAS_X11 = False

# Application constants
APP_ID = "com.linuxlite.welcome"
APP_NAME = "Lite Welcome"

ICON_NAME = "litewelcome"
# Detect Live mode by checking for ubiquity installer on desktop
IS_LIVE_MODE = os.path.exists(os.path.expanduser("~/Desktop/ubiquity.desktop"))


def load_image_from_file(filepath, width=None, height=None):
    """
    Load an image from file using GdkPixbuf (CPU-based, reliable).
    """
    if not os.path.exists(filepath):
        print(f"File not found: {filepath}")
        return None
    try:
        # Use GdkPixbuf for reliable CPU-based rendering
        if width and height:
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filepath, width, height, True)
        else:
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath)
       
        # Create Gtk.Image directly from pixbuf for GTK4
        texture = Gdk.Texture.new_for_pixbuf(pixbuf)
        image = Gtk.Image.new_from_paintable(texture)
        return image
    except Exception as e:
        print(f"Error loading image {filepath}: {e}")
        return None


class WelcomeConfig:
    def __init__(self):
        self._arch = '64-bit' if os.uname()[4] == 'x86_64' else '32-bit'
        self._config_dir = os.path.expanduser('~/.config/lite/welcome/')
        self._autostart_path = os.path.expanduser('~/.config/autostart/lite-welcome.desktop')
        os.makedirs(self._config_dir, exist_ok=True)
        self._autostart = os.path.exists(self._autostart_path)
   
    @property
    def autostart(self):
        return self._autostart
   
    def toggle_autostart(self):
        try:
            if not self._autostart:
                os.makedirs(os.path.dirname(self._autostart_path), exist_ok=True)
                os.symlink('/usr/share/applications/lite-welcome.desktop', self._autostart_path)
            else:
                if os.path.exists(self._autostart_path):
                    os.unlink(self._autostart_path)
        except OSError as e:
            print(f"Error toggling autostart: {e}")
        self._autostart = os.path.exists(self._autostart_path)
        return self._autostart


class CommandHandler:
    def __init__(self, app=None):
        self.app = app
   
    @staticmethod
    def spawn(args):
        try:
            subprocess.Popen(args, start_new_session=True)
        except Exception as e:
            print(f"Error spawning process: {e}")
   
    @staticmethod
    def open_url(url):
        try:
            open_new_tab(url)
        except Exception as e:
            print(f"Error opening URL: {e}")
   
    def show_feedback(self, message):
        """Show toast feedback if app reference is available."""
        if self.app and hasattr(self.app, 'show_toast'):
            self.app.show_toast(message)
   
    def execute(self, command):
        if command == 'start_updates':
            if os.path.exists("/usr/bin/lite-updates"):
                self.show_feedback("Launching Install Updates...")
                self.spawn(['pkexec', '/usr/bin/lite-updates'])
        elif command == 'install_drivers':
            if os.path.exists("/usr/bin/software-properties-gtk"):
                self.show_feedback("Launching Driver Manager...")
                self.spawn(['/usr/bin/software-properties-gtk', '--open-tab=4'])
        elif command == 'timeshift':
            if os.path.exists("/usr/bin/timeshift-launcher"):
                self.show_feedback("Launching Timeshift...")
                self.spawn(['/usr/bin/timeshift-launcher'])
        elif command == 'start_software':
            if os.path.exists("/usr/bin/lite-software"):
                self.show_feedback("Launching Install Software...")
                self.spawn(['pkexec', 'lite-software'])
        elif command == 'upgrade':
            if os.path.exists("/usr/bin/lite-upgrade-series7"):
                self.show_feedback("Launching Upgrade Tool...")
                self.spawn(['/usr/bin/lite-upgrade-series7'])
        elif command == 'lite-manual':
            self.show_feedback("Opening Help Manual...")
            self.open_url("https://wiki.linuxliteos.com/en/home")
        elif command == 'installlang':
            self.show_feedback("Opening Language Settings...")
            self.open_url("https://wiki.linuxliteos.com/en/install.html#setlang")
            self.spawn(['/usr/bin/gnome-language-selector'])
        elif command == 'lighttheme':
            self.show_feedback("Applying Light Theme...")
            self.spawn(["xfconf-query", "-c", "xsettings", "-p", "/Net/ThemeName", "-s", "Materia"])
            self.spawn(["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName", "-s", "Papirus-Adapta"])
            self.spawn(["zenity", "--title=Lite Welcome", "--info", "--width=320", "--text=Light Theme applied"])
        elif command == 'darktheme':
            self.show_feedback("Applying Dark Theme...")
            self.spawn(["xfconf-query", "-c", "xsettings", "-p", "/Net/ThemeName", "-s", "Materia-dark"])
            self.spawn(["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName", "-s", "Papirus-Adapta-Nokto"])
            self.spawn(["zenity", "--title=Lite Welcome", "--info", "--width=320", "--text=Dark Theme applied"])
        elif command == 'install-now':
            self.spawn(["sudo", "--preserve-env=DBUS_SESSION_BUS_ADDRESS,XDG_RUNTIME_DIR", "pkexec", "ubiquity", "gtk_ui"])
            self.spawn(["zenity", "--timeout", "5", "--title=Lite Welcome", "--info", "--width=200", "--text=Loading please wait..."])
            self.spawn(["killall", "lite-welcome"])
        elif command.startswith("link:"):
            self.show_feedback("Opening in browser...")
            self.open_url(command[5:])


class NavigationPage(Gtk.Box):
    def __init__(self, app, title=""):
        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        self.app = app
        self.title = title
        self.cmd_handler = app.cmd_handler  # Use app's command handler for toast support
   
    def create_header(self, title):
        header_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        header_box.set_margin_top(16)
        header_box.set_margin_bottom(16)
        title_label = Gtk.Label(label=title)
        title_label.add_css_class("title-1")
        title_label.set_halign(Gtk.Align.CENTER)
        header_box.append(title_label)
        return header_box
   
    def create_button(self, label, icon_name, command=None, style_class="suggested-action"):
        button = Gtk.Button()
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        box.set_halign(Gtk.Align.CENTER)
        if icon_name:
            icon = Gtk.Image.new_from_icon_name(icon_name)
            box.append(icon)
        lbl = Gtk.Label(label=label)
        box.append(lbl)
        button.set_child(box)
        if style_class:
            button.add_css_class(style_class)
        if command:
            button.connect("clicked", lambda b: self.cmd_handler.execute(command))
        return button


class HomePage(NavigationPage):
    def __init__(self, app, data_path):
        super().__init__(app, "Home")
        self.data_path = data_path
        self.build_ui()
   
    def build_ui(self):
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scrolled.set_vexpand(True)
       
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16)
        content.set_margin_top(16)
        content.set_margin_bottom(16)
        content.set_margin_start(24)
        content.set_margin_end(24)
       
        # Logo - use PNG for maximum compatibility (SVG has rendering issues in some VMs)
        logo_path = os.path.join(self.data_path, "img", "lite-welcome.png")
        if os.path.exists(logo_path):
            # Use Gtk.Picture for proper image display at desired size
            logo = Gtk.Picture.new_for_filename(logo_path)
            logo.set_content_fit(Gtk.ContentFit.CONTAIN)
            logo.set_can_shrink(True)
            logo.set_size_request(291, 160)  # Explicit minimum size
           
            logo_button = Gtk.Button()
            logo_button.set_child(logo)
            logo_button.add_css_class("flat")
            logo_button.set_halign(Gtk.Align.CENTER)
            logo_button.connect("clicked", lambda b: self.cmd_handler.open_url("https://www.linuxliteos.com/"))
            content.append(logo_button)
       
        # Summary
        summary_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        summary_box.set_margin_top(16)
        summary_box.set_margin_bottom(16)
       
        summary1 = Gtk.Label()
        summary1.set_wrap(True)
        summary1.set_justify(Gtk.Justification.CENTER)
        summary1.set_markup("Linux Lite is free for everyone to use and share, and suitable for people who are new to Linux or for people who want a lightweight environment that is also fully functional.")
        summary_box.append(summary1)
       
        summary2 = Gtk.Label()
        summary2.set_wrap(True)
        summary2.set_justify(Gtk.Justification.CENTER)
        summary2.set_markup("Linux Lite provides a basic collection of everyday tools: a web browser, an email client, a media player, an office suite, an image editor, and so on.")
        summary_box.append(summary2)
        content.append(summary_box)
       
        # Columns
        columns_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=24)
        columns_box.set_halign(Gtk.Align.CENTER)
        columns_box.set_homogeneous(True)
       
        start_column = self.create_button_column("Start Here", [
            ("Install Updates", "software-update-available-symbolic", "page:start#updates"),
            ("Install Drivers", "preferences-system-symbolic", "page:start#drivers"),
            ("Set a Restore Point", "computer-symbolic", "page:start#restore"),
            ("Install Language Support", "preferences-desktop-locale-symbolic", "page:start#language"),
            ("Select Dark or Light Theme", "preferences-desktop-display-symbolic", "page:start#theme"),
        ], "suggested-action")
        columns_box.append(start_column)
       
        support_column = self.create_button_column("Support", [
            ("Online Support", "help-about-symbolic", "page:support#online"),
            ("Linux Lite Wiki", "accessories-dictionary-symbolic", "page:support#wiki"),
            ("Forums", "system-users-symbolic", "page:support#forums"),
            ("Hardware Database", "printer-symbolic", "page:support#hardware"),
            ("UEFI & Secure Boot", "security-high-symbolic", "page:start#uefi"),
        ], "")
       
        if IS_LIVE_MODE:
            install_btn = Gtk.Button()
            install_btn.set_size_request(-1, 48)
            install_btn.set_margin_top(8)
            install_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
            install_box.set_halign(Gtk.Align.CENTER)
            install_icon = Gtk.Image.new_from_icon_name("system-software-install-symbolic")
            install_box.append(install_icon)
            install_lbl = Gtk.Label(label="INSTALL NOW")
            install_box.append(install_lbl)
            install_btn.set_child(install_box)
            install_btn.add_css_class("destructive-action")
            install_btn.connect("clicked", lambda b: self.cmd_handler.execute("install-now"))
            support_column.append(install_btn)
       
        columns_box.append(support_column)
       
        contribute_column = self.create_button_column("Contribute", [
            ("Code", "utilities-terminal-symbolic", "page:contribute#code"),
            ("Donate", "emblem-favorite-symbolic", "page:contribute#donate"),
            ("Host a Mirror", "folder-download-symbolic", "page:contribute#mirror"),
            ("Social Media", "emblem-shared-symbolic", "page:contribute#social"),
            ("Feedback", "document-edit-symbolic", "link:https://www.linuxliteos.com/feedback.html"),
        ], "")
        columns_box.append(contribute_column)
       
        content.append(columns_box)
        scrolled.set_child(content)
        self.append(scrolled)
   
    def create_button_column(self, title, buttons, style_class):
        column = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        column.set_size_request(200, -1)
       
        title_label = Gtk.Label(label=title)
        title_label.add_css_class("heading")
        title_label.set_margin_bottom(8)
        column.append(title_label)
       
        for label, icon, cmd in buttons:
            button = Gtk.Button()
            button.set_size_request(-1, 40)
            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
            box.set_halign(Gtk.Align.CENTER)
            if icon:
                icon_widget = Gtk.Image.new_from_icon_name(icon)
                box.append(icon_widget)
            lbl = Gtk.Label(label=label)
            box.append(lbl)
            button.set_child(box)
            if style_class:
                button.add_css_class(style_class)
            if cmd.startswith("page:"):
                page_cmd = cmd[5:]
                button.connect("clicked", lambda b, p=page_cmd: self.app.navigate_to(p))
            elif cmd.startswith("link:"):
                url = cmd[5:]
                button.connect("clicked", lambda b, u=url: self.cmd_handler.open_url(u))
            column.append(button)
        return column


class StartPage(NavigationPage):
    def __init__(self, app, data_path):
        super().__init__(app, "Starting with Linux Lite")
        self.data_path = data_path
        self.sections = {}  # Store section references for scrolling
        self.scrolled = None
        self.build_ui()
   
    def build_ui(self):
        self.scrolled = Gtk.ScrolledWindow()
        self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        self.scrolled.set_vexpand(True)
       
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        content.set_margin_top(24)
        content.set_margin_bottom(24)
        content.set_margin_start(32)
        content.set_margin_end(32)
       
        header = self.create_header("Starting with Linux Lite")
        content.append(header)
       
        # Intro text
        intro = Gtk.Label()
        intro.set_markup("Once you have installed Linux Lite and before you can begin using it, it is strongly recommended that you first complete the 4 steps listed below.\n\n<b>If you are running the Live version of Linux Lite, do not attempt these steps below.</b>\n\nIf you have just finished installing Linux Lite in a language other than English, please restart your computer after these steps to activate all supported Menu translations.")
        intro.set_wrap(True)
        intro.set_justify(Gtk.Justification.CENTER)
        content.append(intro)
       
        # Step 1: Install Updates
        step1_desc = "First you need to update your system. Click on the button below to Install Updates now.\n\nOn the window that pops up, enter the password of the user you created during the installation.\n\nYou can also Install Updates via the menu. Click on Menu, Favorites, Install Updates."
        self.sections["updates"] = self.create_step_section("✓ Step 1: Install Updates", step1_desc, "Install Updates", "software-update-available-symbolic", "start_updates")
        content.append(self.sections["updates"])
       
        # Step 2: Install Drivers
        step2_desc = "Now, let's see if you need any drivers installed. Click on the button below to check.\n\nYou can also Install Drivers via the menu. Click on Menu, Settings, Install Drivers."
        self.sections["drivers"] = self.create_step_section("✓ Step 2: Install Drivers", step2_desc, "Install Drivers", "preferences-system-symbolic", "install_drivers")
        content.append(self.sections["drivers"])
       
        # Step 3: Restore Point
        step3_desc = "Last step is to create, just like on Windows, a restore point that you can restore from in case something goes wrong. When you are ready, click on the button below.\n\nYou can also access Timeshift via the menu. Click on Menu, System, Timeshift."
        self.sections["restore"] = self.create_step_section("✓ Step 3: Setting a Restore Point", step3_desc, "Set a Restore Point", "computer-symbolic", "timeshift")
        content.append(self.sections["restore"])
       
        # Step 4: Language Support
        step4_desc = "Click on the button below to install Language Support for Linux Lite.\n\nYou can also install Language Support via the menu. Click on Menu, Settings, Language Support.\n\n<b>NOTE: Don't forget to restart your computer after you have finished installing additional Language Support.</b>"
        self.sections["language"] = self.create_step_section("✓ Step 4: Installing Language Support", step4_desc, "Install Language Support", "preferences-desktop-locale-symbolic", "installlang")
        content.append(self.sections["language"])
       
        # UEFI and Secure Boot section
        self.sections["uefi"] = self.create_uefi_section()
        content.append(self.sections["uefi"])
       
        # Theme section
        self.sections["theme"] = self.create_theme_section()
        content.append(self.sections["theme"])
       
        # Keyboard and Numlock section
        self.sections["keyboard"] = self.create_keyboard_section()
        content.append(self.sections["keyboard"])
       
        # Upgrading section
        self.sections["upgrade"] = self.create_upgrade_section()
        content.append(self.sections["upgrade"])
       
        # Lite Software section
        self.sections["software"] = self.create_software_section()
        content.append(self.sections["software"])
       
        # Hardware Recommendations section
        self.sections["hardware"] = self.create_hardware_section()
        content.append(self.sections["hardware"])
       
        self.scrolled.set_child(content)
        self.append(self.scrolled)
   
    def scroll_to_section(self, section_name):
        if section_name not in self.sections:
            return
        self.pending_scroll = section_name
        GLib.idle_add(self._perform_scroll)
   
    def _perform_scroll(self):
        if not hasattr(self, 'pending_scroll') or not self.pending_scroll:
            return False
        section_name = self.pending_scroll
        self.pending_scroll = None
       
        if section_name not in self.sections:
            return False
           
        target = self.sections[section_name]
        content = self.scrolled.get_child()
        if hasattr(content, 'get_child'):
            content = content.get_child()
       
        y_pos = 0
        child = content.get_first_child() if content else None
        while child:
            if child == target:
                break
            h = child.get_height()
            if h <= 0:
                h = child.get_preferred_size().minimum_size.height
            y_pos += h + 24
            child = child.get_next_sibling()
       
        adj = self.scrolled.get_vadjustment()
        adj.set_value(y_pos)
        return False
   
    def create_step_section(self, title, description, button_label, icon_name, command):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup(f"<span size='large' weight='bold'>{title}</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        desc_label = Gtk.Label()
        desc_label.set_markup(description)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        button = self.create_button(button_label, icon_name, command, "suggested-action")
        button.set_halign(Gtk.Align.CENTER)
        box.append(button)
       
        frame.set_child(box)
        return frame
   
    def create_uefi_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>? UEFI and Secure Boot</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        # UEFI image
        uefi_img_path = os.path.join(self.data_path, "img", "uefisb.png")
        if os.path.exists(uefi_img_path):
            uefi_img = load_image_from_file(uefi_img_path, width=500, height=300)
            if uefi_img:
                uefi_img.set_halign(Gtk.Align.CENTER)
                uefi_img.set_valign(Gtk.Align.CENTER)
                uefi_img.set_size_request(500, 300)
                box.append(uefi_img)
       
        uefi_text = """<b>How do I know if my computer has UEFI?</b>

In Windows Search, type <b>msinfo</b> or <b>msinfo32</b> and launch the desktop app named System Information. Look for the BIOS Mode item, and if the value for it is <b>UEFI</b>, then you have the UEFI firmware. If it says BIOS Mode Legacy, then that's the firmware you're running.

If you bought the computer/motherboard after 2010, chances are you have a UEFI system. If you are still unsure, download the UEFI version as it will also detect and run on a BIOS-Legacy computer.

<b>Secure Boot</b>

Linux Lite recommends that you disable Secure Boot in your BIOS. This will save you potentially a lot of headaches during the use of your system. Linux Lite will run with Secure Boot enabled, but we highly recommend that you don't."""
       
        desc_label = Gtk.Label()
        desc_label.set_markup(uefi_text)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        frame.set_child(box)
        return frame
   
    def create_theme_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>? Select a Light or Dark Theme</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        desc_label = Gtk.Label(label="Click on a button below to select either a Light Theme or a Dark Theme. The Light Theme is already the default theme.")
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        # Light theme with preview
        light_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        light_box.add_css_class("section")
        light_box.set_halign(Gtk.Align.CENTER)
        light_btn = self.create_button("Light Theme", "weather-clear-symbolic", "lighttheme", "suggested-action")
        light_box.append(light_btn)
        light_img_path = os.path.join(self.data_path, "img", "lightth.png")
        if os.path.exists(light_img_path):
            light_img = load_image_from_file(light_img_path, width=380, height=192)
            if light_img:
                light_img.set_halign(Gtk.Align.CENTER)
                light_img.set_valign(Gtk.Align.CENTER)
                light_img.set_size_request(380, 192)
                light_box.append(light_img)
        box.append(light_box)

        # Dark theme with preview
        dark_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        dark_box.add_css_class("section")
        dark_box.set_halign(Gtk.Align.CENTER)
        dark_btn = self.create_button("Dark Theme", "weather-clear-night-symbolic", "darktheme", "")
        dark_box.append(dark_btn)
        dark_img_path = os.path.join(self.data_path, "img", "darkth.png")
        if os.path.exists(dark_img_path):
            dark_img = load_image_from_file(dark_img_path, width=380, height=192)
            if dark_img:
                dark_img.set_halign(Gtk.Align.CENTER)
                dark_img.set_valign(Gtk.Align.CENTER)
                dark_img.set_size_request(380, 192)
                dark_box.append(dark_img)
        box.append(dark_box)
       
        frame.set_child(box)
        return frame
   
    def create_keyboard_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>⌨️ Keyboard and Numlock</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        kb_text = """There are thousands of computing configurations in existence. As a result, different manufacturers have different ways of implementing their Keyboard and Numlock settings. When you boot Linux Lite for the first time and are having issues with your Keyboard and or Numlock, try each of the following solutions:

• Check your BIOS/UEFI configuration
• Menu, Settings, Lite Tweaks, Numlock
• FN (Function) + NUM LOCK
• FN + F11 (Acer, Toshiba, Samsung)
• Shift + NUM LOCK
• FN + NUM LOCK (Sony, Gateway, Lenovo, ASUS)
• FN + F8 (HP)
• Ctrl + F11
• FN + Shift + NUM LOCK
• FN + F4 (Dell)

<b>Keep Numlock working between boots</b> - Menu, Settings, Keyboard, Behavior tab > Enable or Disable - <b>Restore num lock state on startup</b>"""
       
        desc_label = Gtk.Label()
        desc_label.set_markup(kb_text)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        frame.set_child(box)
        return frame
   
    def create_upgrade_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>? Upgrading</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        upgrade_text = """Each <b>Series</b> of Linux Lite lasts 2 years and is based off LTS (Long Term Support) which continues to provide updates for 5 years. eg. Linux Lite <b>4.0</b> - Linux Lite <b>4.8</b> is <b>Series 4</b>, Linux Lite <b>5.0</b> is the start of <b>Series 5</b>, and so on.

Upgrading within <b>Series 7</b> is simple. Click on <b>Menu, Settings, Lite Upgrade</b> and follow the prompts to get the latest version of Linux Lite.

Upgrading can only occur from within a <b>Series</b>. For example we will upgrade you from Linux Lite <b>7.0</b> to Linux Lite <b>7.8</b>, but not from Linux Lite <b>6.0</b> to Linux Lite <b>7.8</b>."""
       
        desc_label = Gtk.Label()
        desc_label.set_markup(upgrade_text)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        button = self.create_button("Upgrade to 7.8", "system-upgrade-symbolic", "upgrade", "suggested-action")
        button.set_halign(Gtk.Align.CENTER)
        box.append(button)
       
        frame.set_child(box)
        return frame
   
    def create_software_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>? Lite Software</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        software_text = """We've made it as simple as just a few clicks to install many of your favorite programs.

On Linux Lite you can install:
• Dropbox
• Firefox Web Browser
• Kodi
• Spotify
• Steam
• Teamviewer
• Tor Web Browser
• VirtualBox
• Zoom

Click on <b>Menu, Settings, Lite Software</b> and follow the onscreen prompts to install popular software or click on the button below."""
       
        desc_label = Gtk.Label()
        desc_label.set_markup(software_text)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        button = self.create_button("Lite Software", "system-software-install-symbolic", "start_software", "")
        button.set_halign(Gtk.Align.CENTER)
        box.append(button)
       
        frame.set_child(box)
        return frame
   
    def create_hardware_section(self):
        frame = Gtk.Frame()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.add_css_class("section")
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)
       
        title_label = Gtk.Label()
        title_label.set_markup("<span size='large' weight='bold'>? Hardware Recommendations</span>")
        title_label.set_halign(Gtk.Align.CENTER)
        box.append(title_label)
       
        hw_text = """Linux Lite can run on a wide range of hardware. Our online Hardware Database contains a growing list of computers that can run Linux Lite.

<b>Recommended Computer Requirements:</b>
• 1.5 Ghz Dual Core Processor
• 4 GB Memory
• 40 GB HDD/SSD/NVME
• VGA, DVI, DP or HDMI screen capable of 1366x768 resolution
• DVD drive or USB port for the ISO image
• Disable Secure Boot

<b>TIP:</b> Check out the Linux Lite Hardware Database for a list of over 97,000 computers that can run Linux Lite."""
       
        desc_label = Gtk.Label()
        desc_label.set_markup(hw_text)
        desc_label.set_wrap(True)
        desc_label.set_halign(Gtk.Align.CENTER)
        desc_label.set_justify(Gtk.Justification.CENTER)
        box.append(desc_label)
       
        button = self.create_button("Hardware Database", "computer-symbolic", "link:https://www.linuxliteos.com/hardware.php", "")
        button.set_halign(Gtk.Align.CENTER)
        box.append(button)
       
        frame.set_child(box)
        return frame


class SupportPage(NavigationPage):
    def __init__(self, app, data_path):
        super().__init__(app, "Get Support")
        self.data_path = data_path
        self.sections = {}
        self.scrolled = None
        self.build_ui()
   
    def build_ui(self):
        self.scrolled = Gtk.ScrolledWindow()
        self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        self.scrolled.set_vexpand(True)
       
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        content.set_margin_top(24)
        content.set_margin_bottom(24)
        content.set_margin_start(32)
        content.set_margin_end(32)
       
        header = self.create_header("Get Support")
        content.append(header)
       
        # Online Support intro
        intro_frame = Gtk.Frame()
        intro_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        intro_box.add_css_class("section")
        intro_box.set_margin_top(16)
        intro_box.set_margin_bottom(16)
        intro_box.set_margin_start(16)
        intro_box.set_margin_end(16)
       
        intro_title = Gtk.Label()
        intro_title.set_markup("<span size='large' weight='bold'>? Online Support</span>")
        intro_title.set_halign(Gtk.Align.CENTER)
        intro_box.append(intro_title)
       
        intro_desc = Gtk.Label(label="Linux Lite aims to provide you with a variety of Support options.")
        intro_desc.set_wrap(True)
        intro_desc.set_halign(Gtk.Align.CENTER)
        intro_desc.set_justify(Gtk.Justification.CENTER)
        intro_box.append(intro_desc)
       
        intro_frame.set_child(intro_box)
        self.sections["online"] = intro_frame
        content.append(intro_frame)
       
        # Linux Lite Wiki section
        wiki_frame = Gtk.Frame()
        wiki_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        wiki_box.add_css_class("section")
        wiki_box.set_margin_top(16)
        wiki_box.set_margin_bottom(16)
        wiki_box.set_margin_start(16)
        wiki_box.set_margin_end(16)
       
        wiki_title = Gtk.Label()
        wiki_title.set_markup("<span size='large' weight='bold'>? Linux Lite Wiki</span>")
        wiki_title.set_halign(Gtk.Align.CENTER)
        wiki_box.append(wiki_title)
       
        wiki_desc = Gtk.Label()
        wiki_desc.set_markup("Click on <b>Menu, Favorites, Linux Lite Wiki</b>. The Wiki is divided into clear categories - <b>Install Guide, Network, Software and Hardware</b>. Each tutorial has step by step instructions, with accompanying pictures. You can also access our Wiki online by clicking below:")
        wiki_desc.set_wrap(True)
        wiki_desc.set_halign(Gtk.Align.CENTER)
        wiki_desc.set_justify(Gtk.Justification.CENTER)
        wiki_box.append(wiki_desc)
       
        wiki_btn = self.create_button("Online Wiki", "accessories-dictionary-symbolic", "link:https://wiki.linuxliteos.com/en/home", "")
        wiki_btn.set_halign(Gtk.Align.CENTER)
        wiki_box.append(wiki_btn)
       
        wiki_frame.set_child(wiki_box)
        self.sections["wiki"] = wiki_frame
        content.append(wiki_frame)
       
        # Forums section
        forums_frame = Gtk.Frame()
        forums_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        forums_box.add_css_class("section")
        forums_box.set_margin_top(16)
        forums_box.set_margin_bottom(16)
        forums_box.set_margin_start(16)
        forums_box.set_margin_end(16)
       
        forums_title = Gtk.Label()
        forums_title.set_markup("<span size='large' weight='bold'>? Forums</span>")
        forums_title.set_halign(Gtk.Align.CENTER)
        forums_box.append(forums_title)
       
        forums_desc = Gtk.Label()
        forums_desc.set_markup("Forums are a great resource for information. Begin by searching for your problem:\n\nIf no results turn up, by all means please post a new thread in the correct section clearly describing your situation. Once you have activated your account, you'll need to login before you can post.")
        forums_desc.set_wrap(True)
        forums_desc.set_halign(Gtk.Align.CENTER)
        forums_desc.set_justify(Gtk.Justification.CENTER)
        forums_box.append(forums_desc)
       
        forums_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        forums_buttons.set_halign(Gtk.Align.CENTER)
       
        search_btn = self.create_button("Search Forums", "system-search-symbolic", "link:https://www.linuxliteos.com/forums/search.php", "")
        forums_buttons.append(search_btn)
       
        register_btn = self.create_button("Register", "contact-new-symbolic", "link:https://www.linuxliteos.com/forums/member.php?action=register", "")
        forums_buttons.append(register_btn)
       
        login_btn = self.create_button("Login", "system-users-symbolic", "link:https://www.linuxliteos.com/forums/", "")
        forums_buttons.append(login_btn)
       
        forums_box.append(forums_buttons)
        forums_frame.set_child(forums_box)
        self.sections["forums"] = forums_frame
        content.append(forums_frame)
       
        # Hardware Database section
        hw_frame = Gtk.Frame()
        hw_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        hw_box.add_css_class("section")
        hw_box.set_margin_top(16)
        hw_box.set_margin_bottom(16)
        hw_box.set_margin_start(16)
        hw_box.set_margin_end(16)
       
        hw_title = Gtk.Label()
        hw_title.set_markup("<span size='large' weight='bold'>? Hardware Database</span>")
        hw_title.set_halign(Gtk.Align.CENTER)
        hw_box.append(hw_title)
       
        hw_desc = Gtk.Label()
        hw_desc.set_markup("The purpose of the <b>Linux Lite Hardware Database</b> is to give people an idea of different computer configurations from within a Linux Lite Series including, <b>Make and Model, CPU, Graphics, Audio, Network and Storage</b> technical specifications.\n\nThese hardware combinations provide a snapshot of the kind of computers people are able to use with Linux Lite. This makes it a great resource for people either thinking of buying new hardware, or seeing if their existing machine is capable of running Linux Lite.")
        hw_desc.set_wrap(True)
        hw_desc.set_halign(Gtk.Align.CENTER)
        hw_desc.set_justify(Gtk.Justification.CENTER)
        hw_box.append(hw_desc)
       
        hw_btn = self.create_button("Hardware Database", "computer-symbolic", "link:https://www.linuxliteos.com/hardware.php", "")
        hw_btn.set_halign(Gtk.Align.CENTER)
        hw_box.append(hw_btn)
       
        hw_frame.set_child(hw_box)
        self.sections["hardware"] = hw_frame
        content.append(hw_frame)
       
        # Tip box
        tip_frame = Gtk.Frame()
        tip_frame.add_css_class("success")
        tip_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        tip_box.set_margin_top(12)
        tip_box.set_margin_bottom(12)
        tip_box.set_margin_start(12)
        tip_box.set_margin_end(12)
       
        tip_label = Gtk.Label()
        tip_label.set_markup("<b>TIP:</b> The Forums are the best avenue for seeking answers. Our members are very generous with their time and there are always people willing to help you. Get help today.")
        tip_label.set_wrap(True)
        tip_label.set_halign(Gtk.Align.CENTER)
        tip_label.set_justify(Gtk.Justification.CENTER)
        tip_box.append(tip_label)
       
        tip_frame.set_child(tip_box)
        content.append(tip_frame)
       
        self.scrolled.set_child(content)
        self.append(self.scrolled)
   
    def scroll_to_section(self, section_name):
        if section_name not in self.sections:
            return
        self.pending_scroll = section_name
        GLib.idle_add(self._perform_scroll)
   
    def _perform_scroll(self):
        if not hasattr(self, 'pending_scroll') or not self.pending_scroll:
            return False
        section_name = self.pending_scroll
        self.pending_scroll = None
       
        if section_name not in self.sections:
            return False
           
        target = self.sections[section_name]
        content = self.scrolled.get_child()
        if hasattr(content, 'get_child'):
            content = content.get_child()
       
        y_pos = 0
        child = content.get_first_child() if content else None
        while child:
            if child == target:
                break
            h = child.get_height()
            if h <= 0:
                h = child.get_preferred_size().minimum_size.height
            y_pos += h + 24
            child = child.get_next_sibling()
       
        adj = self.scrolled.get_vadjustment()
        adj.set_value(y_pos)
        return False


class ContributePage(NavigationPage):
    def __init__(self, app, data_path):
        super().__init__(app, "Contribute")
        self.data_path = data_path
        self.sections = {}
        self.scrolled = None
        self.build_ui()
   
    def build_ui(self):
        self.scrolled = Gtk.ScrolledWindow()
        self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        self.scrolled.set_vexpand(True)
       
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        content.set_margin_top(24)
        content.set_margin_bottom(24)
        content.set_margin_start(32)
        content.set_margin_end(32)
       
        header = self.create_header("Contribute")
        content.append(header)
       
        # Code section
        code_frame = Gtk.Frame()
        code_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        code_box.add_css_class("section")
        code_box.set_margin_top(16)
        code_box.set_margin_bottom(16)
        code_box.set_margin_start(16)
        code_box.set_margin_end(16)
       
        code_title = Gtk.Label()
        code_title.set_markup("<span size='large' weight='bold'>? Code</span>")
        code_title.set_halign(Gtk.Align.CENTER)
        code_box.append(code_title)
       
        code_desc = Gtk.Label()
        code_desc.set_markup("This is a very exciting time in Linux Lite development. We are in the process of producing custom, free software for Linux Lite. Our philosophy is basic code, simple design and minimal dependency. Our applications should be clean, fast and simple. Intelligent, well thought out design with great attention to detail.\n\nWe use the GPL v2. If you're a developer and would like to help with Linux Lite, click below. We also pay developers to help improve our software.")
        code_desc.set_wrap(True)
        code_desc.set_halign(Gtk.Align.CENTER)
        code_desc.set_justify(Gtk.Justification.CENTER)
        code_box.append(code_desc)
       
        code_btn = self.create_button("Learn More", "utilities-terminal-symbolic", "link:https://www.linuxliteos.com/contribute.html", "")
        code_btn.set_halign(Gtk.Align.CENTER)
        code_box.append(code_btn)
       
        code_frame.set_child(code_box)
        self.sections["code"] = code_frame
        content.append(code_frame)
       
        # Donate section
        donate_frame = Gtk.Frame()
        donate_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        donate_box.add_css_class("section")
        donate_box.set_margin_top(16)
        donate_box.set_margin_bottom(16)
        donate_box.set_margin_start(16)
        donate_box.set_margin_end(16)
       
        donate_title = Gtk.Label()
        donate_title.set_markup("<span size='large' weight='bold'>️ Donate</span>")
        donate_title.set_halign(Gtk.Align.CENTER)
        donate_box.append(donate_title)
       
        donate_desc = Gtk.Label()
        donate_desc.set_markup("Linux Lite is free and can best be described as a labour of love.\n\nThe goal of this project is to work on it full time so that we can deliver to you a better operating system with each release. More time, more features, greater options. If you use Linux Lite and want to contribute towards its success, consider donating.\n\nDonations go towards development and online services such as websites and repositories, as well as hardware purchases. Every donor is listed on our donate page, regardless of their contribution. Thanks for making a difference and for supporting free software.\n\nLinux Lite pays developers to help improve our custom software through developer services such as UpWork.")
        donate_desc.set_wrap(True)
        donate_desc.set_halign(Gtk.Align.CENTER)
        donate_desc.set_justify(Gtk.Justification.CENTER)
        donate_box.append(donate_desc)
       
        donate_btn = self.create_button("Donate", "emblem-favorite-symbolic", "link:https://www.linuxliteos.com/donate.html", "suggested-action")
        donate_btn.set_halign(Gtk.Align.CENTER)
        donate_box.append(donate_btn)
       
        donate_frame.set_child(donate_box)
        self.sections["donate"] = donate_frame
        content.append(donate_frame)
       
        # Host a Mirror section
        mirror_frame = Gtk.Frame()
        mirror_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        mirror_box.add_css_class("section")
        mirror_box.set_margin_top(16)
        mirror_box.set_margin_bottom(16)
        mirror_box.set_margin_start(16)
        mirror_box.set_margin_end(16)
       
        mirror_title = Gtk.Label()
        mirror_title.set_markup("<span size='large' weight='bold'>? Host a Mirror</span>")
        mirror_title.set_halign(Gtk.Align.CENTER)
        mirror_box.append(mirror_title)
       
        mirror_desc = Gtk.Label()
        mirror_desc.set_markup("Help support the Linux Lite community by hosting an official mirror. By providing a mirror, you'll help users worldwide enjoy faster downloads and better access to Linux Lite updates.\n\nOnce your mirror is approved and added, it will appear on our official Mirrors page. For details on how to set up and contribute a mirror, please contact us at our Feedback page.")
        mirror_desc.set_wrap(True)
        mirror_desc.set_halign(Gtk.Align.CENTER)
        mirror_desc.set_justify(Gtk.Justification.CENTER)
        mirror_box.append(mirror_desc)
       
        mirror_btn = self.create_button("Learn More", "folder-download-symbolic", "link:https://www.linuxliteos.com/feedback.html", "")
        mirror_btn.set_halign(Gtk.Align.CENTER)
        mirror_box.append(mirror_btn)
       
        mirror_frame.set_child(mirror_box)
        self.sections["mirror"] = mirror_frame
        content.append(mirror_frame)
       
        # Social Media section
        social_frame = Gtk.Frame()
        social_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        social_box.add_css_class("section")
        social_box.set_margin_top(16)
        social_box.set_margin_bottom(16)
        social_box.set_margin_start(16)
        social_box.set_margin_end(16)
       
        social_title = Gtk.Label()
        social_title.set_markup("<span size='large' weight='bold'>? Social Media</span>")
        social_title.set_halign(Gtk.Align.CENTER)
        social_box.append(social_title)
       
        social_desc = Gtk.Label()
        social_desc.set_markup("Social Media has become a very powerful and effective way of proliferating knowledge throughout the world. We encourage you to spread the word about free operating systems such as Linux Lite.")
        social_desc.set_wrap(True)
        social_desc.set_halign(Gtk.Align.CENTER)
        social_desc.set_justify(Gtk.Justification.CENTER)
        social_box.append(social_desc)
       
        # Social media buttons
        social_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        social_buttons.set_halign(Gtk.Align.CENTER)
       
        social_links = [
            ("social-facebook.png", "https://www.facebook.com/linuxliteos", "Facebook"),
            ("social-twitter.png", "https://twitter.com/LinuxLite", "Twitter"),
            ("social-discord.png", "https://discord.gg/bQSFaFAUkm", "Discord"),
            ("social-instagram.png", "https://www.instagram.com/linuxliteos", "Instagram"),
            ("social-linkedin.png", "https://www.linkedin.com/in/jerrybezencon", "LinkedIn"),
            ("youtube.svg", "https://www.youtube.com/c/linuxliteos", "YouTube"),
            ("social-reddit.svg", "https://www.reddit.com/r/LinuxLite/", "Reddit"),
        ]
       
        for img_file, url, tooltip in social_links:
            btn = Gtk.Button()
            btn.add_css_class("flat")
            btn.set_tooltip_text(tooltip)
           
            img_path = os.path.join(self.data_path, "img", img_file)
            icon = load_image_from_file(img_path, width=32, height=32)
            if icon:
                btn.set_child(icon)
            else:
                btn.set_label(tooltip[0])
           
            btn.connect("clicked", lambda b, u=url: self.cmd_handler.open_url(u))
            social_buttons.append(btn)
       
        social_box.append(social_buttons)
        social_frame.set_child(social_box)
        self.sections["social"] = social_frame
        content.append(social_frame)
       
        # Tip box
        tip_frame = Gtk.Frame()
        tip_frame.add_css_class("success")
        tip_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        tip_box.set_margin_top(12)
        tip_box.set_margin_bottom(12)
        tip_box.set_margin_start(12)
        tip_box.set_margin_end(12)
       
        tip_label = Gtk.Label()
        tip_label.set_markup("<b>TIP:</b> Social Media is a great way to spread the word about free operating systems such as Linux Lite. If you've come to Linux for the first time, share your experiences with the people you know online via Facebook, Twitter, Discord, Instagram, LinkedIn, YouTube, Reddit, and other Social Networking sites.")
        tip_label.set_wrap(True)
        tip_label.set_halign(Gtk.Align.CENTER)
        tip_label.set_justify(Gtk.Justification.CENTER)
        tip_box.append(tip_label)
       
        tip_frame.set_child(tip_box)
        content.append(tip_frame)
       
        self.scrolled.set_child(content)
        self.append(self.scrolled)
   
    def scroll_to_section(self, section_name):
        if section_name not in self.sections:
            return
        self.pending_scroll = section_name
        GLib.idle_add(self._perform_scroll)
   
    def _perform_scroll(self):
        if not hasattr(self, 'pending_scroll') or not self.pending_scroll:
            return False
        section_name = self.pending_scroll
        self.pending_scroll = None
       
        if section_name not in self.sections:
            return False
           
        target = self.sections[section_name]
        content = self.scrolled.get_child()
        if hasattr(content, 'get_child'):
            content = content.get_child()
       
        y_pos = 0
        child = content.get_first_child() if content else None
        while child:
            if child == target:
                break
            h = child.get_height()
            if h <= 0:
                h = child.get_preferred_size().minimum_size.height
            y_pos += h + 24
            child = child.get_next_sibling()
       
        adj = self.scrolled.get_vadjustment()
        adj.set_value(y_pos)
        return False


class LiteWelcomeApp(Adw.Application):
    def __init__(self):
        super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.window = None
        self.config = WelcomeConfig()
        self.cmd_handler = CommandHandler(self)  # Pass app reference for toast notifications
        self.stack = None
        self.toast_overlay = None  # For progress feedback
        self.css_provider = None   # For theme switching
       
        here = os.path.dirname(os.path.abspath(__file__))
        if os.path.exists('/usr/share/litewelcome'):
            self.data_path = '/usr/share/litewelcome'
        else:
            self.data_path = here
   
    def load_css(self):
        """Load custom CSS for the application based on system theme."""
        # Get the style manager to detect system color scheme
        style_manager = Adw.StyleManager.get_default()
        is_dark = style_manager.get_dark()
       
        # Connect to color scheme changes
        style_manager.connect("notify::dark", self._on_color_scheme_changed)
       
        self._apply_css(is_dark)
   
    def _on_color_scheme_changed(self, style_manager, param):
        """Called when system color scheme changes."""
        is_dark = style_manager.get_dark()
        self._apply_css(is_dark)
   
    def _apply_css(self, is_dark):
        """Apply CSS based on whether dark mode is active."""
        # Remove old CSS provider if exists
        if self.css_provider:
            Gtk.StyleContext.remove_provider_for_display(
                Gdk.Display.get_default(),
                self.css_provider
            )
       
        if is_dark:
            css = b"""
            /* Dark mode - dark backgrounds */
            window, .background {
                background-color: #2d2d2d;
            }
           
            /* Content area */
            scrolledwindow, viewport {
                background-color: #2d2d2d;
            }
           
            /* Frames for sections */
            frame {
                background-color: #3d3d3d;
                border-radius: 8px;
                border: 1px solid #505050;
            }
           
            /* Header bar styling */
            headerbar {
                background-color: #383838;
            }
           
            /* Footer/toolbar area */
            .toolbar {
                background-color: #383838;
                border-top: 1px solid #505050;
            }
           
            /* Tip boxes with green tint (darker) */
            frame.success {
                background-color: #2d4a32;
                border: 1px solid #4a7c54;
            }
           
            /* Base button styling with hover effects for all non-flat buttons */
            button:not(.flat) {
                transition: background-color 150ms ease-in-out;
            }
           
            /* Suggested action buttons (blue) */
            button.suggested-action {
                background-color: @accent_bg_color;
                color: @accent_fg_color;
            }
            button.suggested-action:hover {
                background-color: shade(@accent_bg_color, 1.15);
            }
            button.suggested-action:active {
                background-color: shade(@accent_bg_color, 1.25);
            }
           
            /* Destructive action buttons (red) */
            button.destructive-action {
                background-color: @destructive_bg_color;
                color: @destructive_fg_color;
            }
            button.destructive-action:hover {
                background-color: shade(@destructive_bg_color, 1.15);
            }
            button.destructive-action:active {
                background-color: shade(@destructive_bg_color, 1.25);
            }
           
            /* Regular buttons (no specific class) */
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button) {
                background-color: #505050;
            }
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button):hover {
                background-color: #606060;
            }
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button):active {
                background-color: #707070;
            }
           
            /* Flat buttons (social icons) - subtle hover */
            button.flat:hover {
                background-color: rgba(255, 255, 255, 0.1);
            }
            button.flat:active {
                background-color: rgba(255, 255, 255, 0.2);
            }
           
            /* Social button size */
            .social-button {
                min-width: 28px;
                min-height: 28px;
                padding: 4px;
            }
           
            /* Tooltip styling - ensure readable text */
            tooltip {
                background-color: #4a4a4a;
                color: #ffffff;
            }
            tooltip label {
                color: #ffffff;
            }
            """
        else:
            css = b"""
            /* Light mode - light grey for good contrast */
            window, .background {
                background-color: #f0f0f0;
            }
           
            /* Content area */
            scrolledwindow, viewport {
                background-color: #f0f0f0;
            }
           
            /* Frames for sections */
            frame {
                background-color: #ffffff;
                border-radius: 8px;
                border: 1px solid #d0d0d0;
            }
           
            /* Header bar styling */
            headerbar {
                background-color: #e8e8e8;
            }
           
            /* Footer/toolbar area */
            .toolbar {
                background-color: #e8e8e8;
                border-top: 1px solid #d0d0d0;
            }
           
            /* Tip boxes with green tint */
            frame.success {
                background-color: #e8f5e9;
                border: 1px solid #a5d6a7;
            }

            /* Base button styling with hover effects for all non-flat buttons */
            button:not(.flat) {
                transition: background-color 150ms ease-in-out;
            }
           
            /* Suggested action buttons (blue) */
            button.suggested-action {
                background-color: @accent_bg_color;
                color: @accent_fg_color;
            }
            button.suggested-action:hover {
                background-color: shade(@accent_bg_color, 0.85);
            }
            button.suggested-action:active {
                background-color: shade(@accent_bg_color, 0.75);
            }
           
            /* Destructive action buttons (red) */
            button.destructive-action {
                background-color: @destructive_bg_color;
                color: @destructive_fg_color;
            }
            button.destructive-action:hover {
                background-color: shade(@destructive_bg_color, 0.85);
            }
            button.destructive-action:active {
                background-color: shade(@destructive_bg_color, 0.75);
            }
           
            /* Regular buttons (no specific class) */
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button) {
                background-color: #e0e0e0;
            }
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button):hover {
                background-color: #c8c8c8;
            }
            button:not(.flat):not(.suggested-action):not(.destructive-action):not(.image-button):active {
                background-color: #b0b0b0;
            }
           
            /* Flat buttons (social icons) - subtle hover */
            button.flat:hover {
                background-color: rgba(0, 0, 0, 0.08);
            }
            button.flat:active {
                background-color: rgba(0, 0, 0, 0.15);
            }
           
            /* Social button size */
            .social-button {
                min-width: 28px;
                min-height: 28px;
                padding: 4px;
            }
           
            /* Tooltip styling - ensure black text on light background */
            tooltip {
                background-color: #f5f5f5;
                color: #000000;
            }
            tooltip label {
                color: #000000;
            }
            """
       
        self.css_provider = Gtk.CssProvider()
        self.css_provider.load_from_data(css)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            self.css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
   
    def do_activate(self):
        if not self.window:
            self.load_css()
            self.window = Adw.ApplicationWindow(application=self)
            self.window.set_title(APP_NAME)
            self.window.set_default_size(850, 768)
           
            # Set taskbar icon by adding search path and using icon name
            icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
            icon_theme.add_search_path("/usr/share/litewelcome/img")
            self.window.set_icon_name("litewelcome")
           
            self.build_ui()
           
            # Center window on screen after it's realized
            self.window.connect("realize", self._on_window_realize)
        self.window.present()
   
    def _on_window_realize(self, window):
        """Called when window is realized - schedule centering."""
        # Use idle_add to ensure the window is fully mapped before centering
        GLib.idle_add(self._center_window_on_screen)
   
    def _center_window_on_screen(self):
        """Center the window on the primary monitor."""
        try:
            display = Gdk.Display.get_default()
            if not display:
                return False
           
            # Get list of monitors
            monitors = display.get_monitors()
            if monitors.get_n_items() == 0:
                return False
           
            # Get the primary/first monitor
            monitor = monitors.get_item(0)
            geometry = monitor.get_geometry()
           
            # Window dimensions (use default size)
            win_width = 850
            win_height = 768
           
            # Calculate center position
            x = geometry.x + (geometry.width - win_width) // 2
            y = geometry.y + (geometry.height - win_height) // 2
           
            # Try X11-specific positioning (Linux Lite uses X11/Xfce)
            if HAS_X11:
                surface = self.window.get_surface()
                if surface and isinstance(surface, GdkX11.X11Surface):
                    xid = surface.get_xid()
                    x11_display = display.get_xdisplay()
                    if x11_display and xid:
                        # Use ctypes to call XMoveWindow directly
                        import ctypes
                        try:
                            xlib = ctypes.CDLL('libX11.so.6')
                            xlib.XMoveWindow(x11_display, xid, x, y)
                            xlib.XFlush(x11_display)
                        except (OSError, AttributeError):
                            pass
        except Exception as e:
            print(f"Could not center window: {e}")
       
        return False  # Don't repeat the idle callback
   
    def build_ui(self):
        # Create toast overlay for progress feedback
        self.toast_overlay = Adw.ToastOverlay()
       
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
       
        header = Adw.HeaderBar()
        header.set_show_end_title_buttons(True)
       
        self.back_button = Gtk.Button.new_from_icon_name("go-previous-symbolic")
        self.back_button.set_tooltip_text("Back to Home")
        self.back_button.connect("clicked", self.on_back_clicked)
        self.back_button.set_visible(False)
        header.pack_start(self.back_button)
       
        title = Adw.WindowTitle.new(APP_NAME, "")
        header.set_title_widget(title)
        main_box.append(header)
       
        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        self.stack.set_transition_duration(250)
        self.stack.set_vexpand(True)
       
        self.stack.add_named(HomePage(self, self.data_path), "home")
        self.stack.add_named(StartPage(self, self.data_path), "start")
        self.stack.add_named(SupportPage(self, self.data_path), "support")
        self.stack.add_named(ContributePage(self, self.data_path), "contribute")
       
        main_box.append(self.stack)
        main_box.append(self.create_footer())
       
        # Wrap main content in toast overlay
        self.toast_overlay.set_child(main_box)
        self.window.set_content(self.toast_overlay)
   
    def show_toast(self, message, timeout=2):
        """Show a toast notification with the given message."""
        if self.toast_overlay:
            toast = Adw.Toast.new(message)
            toast.set_timeout(timeout)
            self.toast_overlay.add_toast(toast)
   
    def create_footer(self):
        footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        footer.add_css_class("toolbar")
        footer.set_margin_start(16)
        footer.set_margin_end(16)
        footer.set_margin_top(8)
        footer.set_margin_bottom(8)
       
        social_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
       
        # Social media buttons with images - larger for better click targets
        social_links = [
            ("social-facebook.png", "https://www.facebook.com/linuxliteos", "Facebook"),
            ("gitlab.svg", "https://gitlab.com/linuxlite", "GitLab"),
            ("social-twitter.png", "https://twitter.com/LinuxLite", "Twitter"),
            ("youtube.svg", "https://www.youtube.com/c/linuxliteos", "YouTube"),
            ("social-reddit.svg", "https://www.reddit.com/r/LinuxLite/", "Reddit"),
            ("social-discord.png", "https://discord.gg/bQSFaFAUkm", "Discord"),
        ]
       
        for img_file, url, tooltip in social_links:
            btn = Gtk.Button()
            btn.add_css_class("flat")
            btn.add_css_class("social-button")  # Custom class for larger click target
            btn.set_tooltip_text(tooltip)
           
            # Load the social media icon - larger size (28x28)
            img_path = os.path.join(self.data_path, "img", img_file)
            icon = load_image_from_file(img_path, width=28, height=28)
            if icon:
                btn.set_child(icon)
            else:
                # Fallback to first letter if image fails
                btn.set_label(tooltip[0])
           
            btn.connect("clicked", lambda b, u=url: self.cmd_handler.open_url(u))
            social_box.append(btn)
       
        footer.append(social_box)
       
        spacer = Gtk.Box()
        spacer.set_hexpand(True)
        footer.append(spacer)
       
        autostart_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        self.autostart_check = Gtk.CheckButton()
        self.autostart_check.set_active(self.config.autostart)
        self.autostart_check.connect("toggled", self.on_autostart_toggled)
        autostart_box.append(self.autostart_check)
        autostart_box.append(Gtk.Label(label="Show this dialog on startup"))
        footer.append(autostart_box)
       
        close_btn = Gtk.Button(label="Close")
        close_btn.set_margin_start(16)
        close_btn.connect("clicked", lambda b: self.quit())
        footer.append(close_btn)
       
        return footer
   
    def navigate_to(self, page_spec):
        if '#' in page_spec:
            page, section = page_spec.split('#', 1)
        else:
            page = page_spec
            section = None
       
        self.stack.set_visible_child_name(page)
        self.back_button.set_visible(page != "home")
       
        # If a section was specified, scroll to it
        if section:
            page_widget = self.stack.get_child_by_name(page)
            if hasattr(page_widget, 'scroll_to_section'):
                page_widget.scroll_to_section(section)
   
    def on_back_clicked(self, button):
        self.stack.set_visible_child_name("home")
        self.back_button.set_visible(False)
   
    def on_autostart_toggled(self, check):
        self.config.toggle_autostart()
        check.set_active(self.config.autostart)


def main():
    signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))
    return LiteWelcomeApp().run(sys.argv)

if __name__ == '__main__':
    sys.exit(main())
Download your free copy of Linux Lite today.

Jerry Bezencon
Linux Lite Creator

"Do not correct a fool, or he will hate you; correct a wise man and he will appreciate you."

[Image: X5qGkCg.png]

[Image: 0op1GNe.png] [Image: LgJ2mtP.png] [Image: vLZcFUE.png] [Image: lrUHro3.jpg]
Reply
#14
I replaced the code in the /bin/lite-welcome file with the code provided and then the app wouldn't open at all.  I noticed that the new code is around 300 lines longer (aprox 1600 lines) than the old code (aprox. 1300 lines).  Was the new code supposed to be that different than the previous?

Thanks
Reply
#15
Yes, different code. Make sure the application file is executable.
Download your free copy of Linux Lite today.

Jerry Bezencon
Linux Lite Creator

"Do not correct a fool, or he will hate you; correct a wise man and he will appreciate you."

[Image: X5qGkCg.png]

[Image: 0op1GNe.png] [Image: LgJ2mtP.png] [Image: vLZcFUE.png] [Image: lrUHro3.jpg]
Reply
#16
Ok, it's working now.  I'm not sure why it didn't work the first time because I had made the file executable.Thanks![attachment=303 Wrote:valtam pid='62774' dateline='1767585564']Yes, different code. Make sure the application file is executable.


Attached Files Thumbnail(s)
   
Reply
#17
No problem Smile
Download your free copy of Linux Lite today.

Jerry Bezencon
Linux Lite Creator

"Do not correct a fool, or he will hate you; correct a wise man and he will appreciate you."

[Image: X5qGkCg.png]

[Image: 0op1GNe.png] [Image: LgJ2mtP.png] [Image: vLZcFUE.png] [Image: lrUHro3.jpg]
Reply
#18
Should the included version of GIMP be updated to 3.x series?

The current version of GIMP that is included with LL is 2.10.36.  The 3.x version has been available for quite awhile now and 3.06 is the default on the gimp.org website.  When you open in LL and go to "Help > About" it recommends updating.

https://www.gimp.org/downloads


Attached Files Thumbnail(s)
   
Reply
#19
The Flatpack works OK. There is no native complete package for Ubuntu 24. Even the PPA has limitations.
https://ubuntuhandbook.org/index.php/202...ll-ubuntu/

TC
All opinions expressed and all advice given by Trinidad Cruz on this forum are his responsibility alone and do not necessarily reflect the views or methods of the developers of Linux Lite. He is a citizen of the United States where it is acceptable to occasionally be uninformed and inept as long as you pay your taxes.
Reply
#20
I added the ubuntuhandbook1/gimp-3 PPA and it upgraded fine that way afterwards but it doesn't sound like it's as up-to-date as the flatpak or snap versions so maybe not the best way to go but it's nice that it will updates with the rest of the system via "apt update" etc.

Option 3: Ubuntu PPA (unofficial)
For those who prefer the native
Code:
.deb
package format, I’ve built GIMP 3.0 into this unofficial PPA for Ubuntu 22.04, Ubuntu 24.04, and Ubuntu 24.10 on
Code:
amd64
,
Code:
arm64
/
Code:
armhf
platforms.
NOTE: The PPA package has few downsides:
  1. It’s built with system GTK3 library, which is a bit outdated. While, GIMP 3.0 relies on the most recent GTK3 library for some fixes.
  2. Also due to outdated library (libglib2.0-dev), the Ubuntu 22.04 package reversed this commit, or it won’t build.
  3. For Ubuntu 22.04, the PPA also contains updated versions of libheif, libwebp, kvazaar HEVC encoder that might conflict to other 3rd party packages.
To add the PPA, press
Code:
Ctrl+Alt+T
to open up a terminal window, and run command:
sudo add-apt-repository ppa:ubuntuhandbook1/gimp-3
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)