gtk-slapt-mod: Module Maker

New features which should be implemented in Porteus; suggestions are welcome. All questions or problems with testing releases (alpha, beta, or rc) should go in their relevant thread here, rather than the Bug Reports section.
pterid
Contributor
Contributor
Posts: 97
Joined: 01 Feb 2025, 20:13
Distribution: Porteus 5.01 Xfce on ext4 USB

gtk-slapt-mod: Module Maker

Post#1 by pterid » 04 Mar 2026, 20:45

Image

I had some fun writing this Gtk Python wrapper for slapt-mod. Search your Slackware packages and combine them together in a module, quickly with your mouse.

Once again, no slop, just 100% organic bad code. It's just a draft, another couple of nights of hobby coding, but I hope it might be useful to someone. Please try it and report any bugs you find.

Gtk Python is quite easy to write once you get used to it and have the Gtk3 docs handy. Thanks to Porteus development team members who already wrote neat distro apps like lsmodules and porteus-settings-centre, establishing the patterns that I just tried to follow.

Only tested on 5.01 Xfce, but hopefully will work on other DEs.

How to use
  • Run as root. (If you get the module, it'll enforce that.)
  • It might take a few seconds to start up while it loads packages.
  • Type a search term in the search bar and press Enter or click Go. Matching packages will appear. You can match the name or the description.
  • use package1 OR package2 OR package3... to match multiple packages. This is handy if you want to combine packages manually into one module.
  • Or view all packages (might take a sec if you have slackdce).
  • The package list widget has its own little search bar if you press Ctrl+F.
  • Select one or more packages (Ctrl+click) and either double-click the final one or just click Install selected.
  • The little popup has the same settings you will be used to seeing in slapt-mod. All your selected packages are in the text box, separate by spaces. (The text box isn't very wide, but you can move the cursor to check.) You can install with automatic dependencies where the repo supports it.
  • Click Install. A terminal window appears, makes the module and saves it in /tmp like usual.
  • Move the .xzm to your module folder, or activate it straight away.
Module download (Mediafire)

Contains the executable, plus menu item (in System folder) and desktop shortcut. Click on the desktop and press F5 to see the desktop shortcut. Run xfce4-panel -r or your DE's equivalent refresh command to ensure the menu item is visible immediately after you activate the module.

https://www.mediafire.com/file/lnzz0q4c ... 1.xzm/file

Sorry, I didn't make a github repo yet for this.

Full source code:

Code: Select all

#!/usr/bin/python3

## Porteus Module Maker
## Author: jssouza, lcpterid

import re
import sys
import os
import glob
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Vte', '2.91')
from gi.repository import Gtk, Vte, GdkPixbuf, Gio, GLib
from subprocess import run

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

class TermWindow(Gtk.Window):

    def __init__(self, command_args):
        Gtk.Window.__init__(self, title = "Porteus Installer", border_width = 5, height_request = 500, width_request = 550, icon_name = "system-settings")

        self.vb = Gtk.Box(spacing = 5, orientation = Gtk.Orientation.VERTICAL)
        self.vb.set_homogeneous(False)

        terminal = Vte.Terminal()
        pty = Vte.Pty.new_sync(Vte.PtyFlags.DEFAULT)
        terminal.set_pty(pty)
        pty.spawn_async(None, command_args, None, GLib.SpawnFlags.DO_NOT_REAP_CHILD, None, None, -1, None, self.ready)

        self.connect("delete-event", self.on_window_close)

        scrolledwindow = Gtk.ScrolledWindow()
        scrolledwindow.add(terminal)

        self.vb.pack_start(scrolledwindow, True, True, 0)

        self.hb_bottom = Gtk.Box(spacing = 5)
        self.hb_bottom.set_homogeneous(False)
        self.ok_button = Gtk.Button.new_with_label("Quit")
        self.ok_button.connect("clicked", self.on_quit_button_clicked)
        self.hb_bottom.pack_end(self.ok_button, False, False, 6)

        self.vb.pack_start(self.hb_bottom, False, False, 6)
        self.add(self.vb)

    def ready(self, pty, task):
        # print('ready')
        None

    def on_window_close(self):
        self.destroy()

    def on_quit_button_clicked(self, button):
        self.destroy()


class InstallWindow(Gtk.Window):

    include_deps = False
    compression = "zstd"

    def __init__(self, pkgname):

        Gtk.Window.__init__(self, title = "Install Module", border_width = 10, height_request = 240, width_request = 250, icon_name = "system-software-update")

        self.connect("delete-event", self.on_window_close)

        self.vb = Gtk.Box(spacing = 5, orientation = Gtk.Orientation.VERTICAL)
        self.vb.set_homogeneous(False)

        self.hb_row1 = Gtk.Box(spacing = 2, orientation = Gtk.Orientation.VERTICAL)
        self.hb_row1.set_homogeneous(False)

        self.top_mod_name = Gtk.Label()
        self.top_mod_name.set_markup(f"<b>Module(s) to install:</b>")
        self.top_mod_name.set_justify(Gtk.Justification.RIGHT)
        self.hb_row1.pack_start(self.top_mod_name, False, False, 0)

        self.mod_name_box = Gtk.Entry()
        self.mod_name_box.set_max_width_chars(25)
        self.mod_name_box.set_text(pkgname)
        self.hb_row1.pack_start(self.mod_name_box, False, False, 0)

        self.hb_row3 = Gtk.Box(spacing = 3, orientation = Gtk.Orientation.VERTICAL)
        self.hb_row3.set_homogeneous(False)

        self.compression_label = Gtk.Label()
        self.compression_label.set_markup(f"Compression type:")
        self.hb_row3.pack_start(self.compression_label, False, False, 0)

        self.hb_row3b = Gtk.Box(spacing = 5)
        self.hb_row3b.set_homogeneous(False)
        self.hb_row3b.set_margin_start(35)
        self.hb_row3b.set_margin_end(35)

        self.compression_button_1 = Gtk.RadioButton.new_with_label_from_widget(None, "zstd")
        self.compression_button_2 = Gtk.RadioButton.new_from_widget(self.compression_button_1)
        self.compression_button_2.set_label("xz")
        self.hb_row3b.pack_start(self.compression_button_1, True, False, 0)
        self.hb_row3b.pack_start(self.compression_button_2, True, False, 0)
        self.compression_button_1.connect("toggled", self.on_compression_toggled, "1")
        self.compression_button_2.connect("toggled", self.on_compression_toggled, "2")

        self.hb_row3.pack_start(self.hb_row3b, False, False, 0)

        self.hb_row4 = Gtk.Box(spacing = 5)
        self.hb_row4.set_homogeneous(True)

        self.deps_checkbox = Gtk.CheckButton(label="Include dependencies")
        self.deps_checkbox.connect("toggled", self.on_deps_toggled)
        self.deps_checkbox.set_active(True)
        self.hb_row4.pack_start(self.deps_checkbox, False, False, 0)

        self.hb_row5 = Gtk.Box(spacing = 10)
        self.hb_row5.set_homogeneous(False)

        self.cancel_button = Gtk.Button.new_with_label("Cancel")
        self.cancel_button.connect("clicked", self.on_window_close)
        self.hb_row5.pack_start(self.cancel_button, True, False, 0)
        self.install_button = Gtk.Button.new_with_label("Install")
        self.install_button.connect("clicked", self.on_install_button_clicked)
        self.hb_row5.pack_start(self.install_button, True, False, 0)

        self.vb.pack_start(self.hb_row1, False, False, 5)
        self.vb.pack_start(self.hb_row3, False, False, 5)
        self.vb.pack_start(self.hb_row4, False, False, 5)
        self.vb.pack_end(self.hb_row5, False, False, 5)

        self.add(self.vb)

    def on_window_close(self, _arg):
        self.destroy()

    def on_deps_toggled(self, button):
        self.include_deps = button.get_active()

    def on_compression_toggled(self, button, name):
        if button.get_active():
            self.compression = name

    def on_install_button_clicked(self, button):
        if self.include_deps:
            slapt_mod_flag = "m"
        else:
            slapt_mod_flag = "n"

        if self.compression == "2":
            slapt_mod_flag = slapt_mod_flag.upper()

        slapt_mod_flag = f"-{slapt_mod_flag}"
        module_names = self.mod_name_box.get_text()

        term_window = TermWindow(["/usr/bin/bash", "-c", f"yes | slapt-mod {slapt_mod_flag} {module_names}"])
        term_window.show_all()
        self.destroy()


    def msg_dialog(self, msg):
        dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
        Gtk.ButtonsType.OK, msg)
        dialog.run()
        dialog.destroy()

class SlaptModPackages:
    '''slapt-mod package list'''

    package_list = []
    slapt_mod_error_state = 0

    def __init__(self):
        self.fetch_packages()

    def fetch_packages(self):
        package_list = []
        slapt_mod_process = run(['slapt-mod','-s','-'], capture_output=True)
        self.slapt_mod_error_state = slapt_mod_process.returncode
        slapt_mod_raw_output = slapt_mod_process.stdout.decode('utf-8')
        for this_line in slapt_mod_raw_output.split('\n'):
            this_name = this_line.split(' ')[0]
            this_desc = this_line.replace(this_name,'').strip()
            if this_desc[0:1] == '(' and this_desc[-1:] == ')':
                this_desc = this_desc[1:-1]
            this_package = {'name': this_name, 'desc': this_desc}
            self.package_list.append(this_package)

    def get_package_list(self):
        return self.package_list


class PackagesWindow(Gtk.Window):

    slapt_mod_packages = SlaptModPackages()
    
    column_pkg_name = None
    column_pkg_desc = None

    needs_refresh = False

    def __init__(self):

        Gtk.Window.__init__(self, title = "Module Maker", border_width = 10, height_request = 500, width_request = 800, icon_name = "go-down")

        self.vb = Gtk.Box(spacing = 5, orientation = Gtk.Orientation.VERTICAL)
        self.vb.set_homogeneous(False)

        self.hb_top = Gtk.Box(spacing = 5)
        self.hb_top.set_homogeneous(False)

        self.update_packages_button = Gtk.Button.new_with_label("Update package lists")
        self.update_packages_button.connect("clicked", self.on_update_packages_button_clicked)
        self.hb_top.pack_start(self.update_packages_button, False, False, 6)

        self.search_label = Gtk.Label()
        self.search_label.set_markup(f"<b>Search:</b>")
        self.search_label.set_justify(Gtk.Justification.RIGHT)
        self.hb_top.pack_start(self.search_label, False, False, 0)

        self.search_box = Gtk.Entry()
        self.search_box.set_max_width_chars(20)
        self.search_box.connect("activate", self.on_go_button_clicked)
        self.hb_top.pack_start(self.search_box, False, False, 0)

        self.go_button = Gtk.Button.new_with_label("Go")
        self.go_button.connect("clicked", self.on_go_button_clicked)
        self.hb_top.pack_start(self.go_button, False, False, 6)

        self.list_all_button = Gtk.Button.new_with_label("List all")
        self.list_all_button.connect("clicked", self.on_list_all_button_clicked)
        self.hb_top.pack_end(self.list_all_button, False, False, 6)

        self.clear_all_button = Gtk.Button.new_with_label("Clear all")
        self.clear_all_button.connect("clicked", self.on_clear_all_button_clicked)
        self.hb_top.pack_end(self.clear_all_button, False, False, 6)

        self.vb.pack_start(self.hb_top, False, False, 6)

        # Columns in order:
        # 0 - str - package name
        # 1 - str - package description

        self.slapt_mod_packages_model = Gtk.TreeStore(str, str)

        self.tree_view = Gtk.TreeView(model = self.slapt_mod_packages_model)
        self.tree_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)

        renderer_pkg_name = Gtk.CellRendererText()
        self.column_pkg_name = Gtk.TreeViewColumn("Package Name", renderer_pkg_name, text=0)
        self.column_pkg_name.set_sort_column_id(0)
        self.column_pkg_name.set_fixed_width(350)
        self.column_pkg_name.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        self.column_pkg_name.set_resizable(True)
        self.tree_view.append_column(self.column_pkg_name)

        renderer_pkg_desc = Gtk.CellRendererText()
        self.column_pkg_desc = Gtk.TreeViewColumn("Description", renderer_pkg_desc, text=1)
        self.column_pkg_desc.set_sort_column_id(1)
        self.column_pkg_desc.set_fixed_width(200)
        self.column_pkg_desc.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        self.column_pkg_desc.set_resizable(True)
        self.tree_view.append_column(self.column_pkg_desc)

        self.tree_view.connect('row-activated', self.on_row_double_clicked)

        self.scrolled_win = Gtk.ScrolledWindow()
        self.scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        self.scrolled_win.add(self.tree_view)
        self.vb.pack_start(self.scrolled_win, True, True, 0)

        self.hb_bottom = Gtk.Box(spacing = 5)
        self.hb_bottom.set_homogeneous(False)

        self.quit_button = Gtk.Button.new_with_label("Quit")
        self.quit_button.connect("clicked", self.on_quit_button_clicked)
        self.hb_bottom.pack_end(self.quit_button, False, False, 6)

        self.install_button = Gtk.Button.new_with_label("Install selected")
        self.install_button.connect("clicked", self.on_install_button_clicked)
        self.hb_bottom.pack_end(self.install_button, False, False, 6)

        self.vb.pack_end(self.hb_bottom, False, False, 6)

        self.add(self.vb)
        self.search_box.grab_focus()

    def on_update_packages_button_clicked(self, _button):
        self.needs_refresh = True
        term_window = TermWindow(["/usr/local/bin/slapt-mod", "-u"])
        term_window.show_all()

    def on_clear_all_button_clicked(self, _button):
        self.slapt_mod_packages_model.clear()

    def on_list_all_button_clicked(self, _button):
        self.package_search('')

    def on_go_button_clicked(self,_button):
        if self.needs_refresh:
            self.needs_refresh = False
            self.slapt_mod_packages.fetch_packages()
        search_term = self.search_box.get_text()
        self.package_search(search_term)

    def on_quit_button_clicked(self, _button):
        Gtk.main_quit()

    def on_row_double_clicked(self, _tree_view, _path, _column):
            self.launch_install()

    def on_install_button_clicked(self, _button):
            self.launch_install()

    def launch_install(self):
        selected_package_paths = self.tree_view.get_selection().get_selected_rows()[1]
        selected_package_list = []
        for pkgpath in selected_package_paths:
            selected_package_list.append(self.slapt_mod_packages_model[pkgpath][0])
        selected_package_string = ' '.join(selected_package_list)
        install_window = InstallWindow(selected_package_string)
        install_window.set_modal(True)
        install_window.show_all()

    def package_search(self, search_term):
        self.slapt_mod_packages_model.clear()
        the_package_list = self.slapt_mod_packages.get_package_list()
        search_array = search_term.split(' OR ')
        package_match = False
        for pkg in the_package_list:
            for sterm in search_array:
                if sterm in pkg['name'] or sterm in pkg['desc']:
                    package_match = True
                    self.slapt_mod_packages_model.append(None, [pkg['name'], pkg['desc']])

win = PackagesWindow()
win.connect("delete-event", Gtk.main_quit)
win.connect("destroy", Gtk.main_quit) 
win.show_all()
Gtk.main()
Last edited by pterid on 05 Mar 2026, 11:12, edited 1 time in total.

User avatar
ncmprhnsbl
DEV Team
DEV Team
Posts: 4547
Joined: 20 Mar 2012, 03:42
Distribution: v5.1-alpha*-64bit
Location: australia
Contact:

gtk-slapt-mod: Module Maker

Post#2 by ncmprhnsbl » 05 Mar 2026, 01:05

sweet, beat me to it :)
tbh, though it was one of those ideas that keeps getting pushed further down the list..
a quick test here, seems to operate as intended :good:
Forum Rules : https://forum.porteus.org/viewtopic.php?f=35&t=44

vinnie
Shogun
Shogun
Posts: 327
Joined: 13 Jun 2024, 08:25
Distribution: gnemesis!

gtk-slapt-mod: Module Maker

Post#3 by vinnie » 08 Mar 2026, 00:33

seems nice!
unfortunately I use nemesis but meaby it's possible to adapt your gui also with nemesis

pterid
Contributor
Contributor
Posts: 97
Joined: 01 Feb 2025, 20:13
Distribution: Porteus 5.01 Xfce on ext4 USB

gtk-slapt-mod: Module Maker

Post#4 by pterid » 08 Mar 2026, 10:14

ncmprhnsbl wrote:
05 Mar 2026, 01:05
sweet, beat me to it :)
tbh, though it was one of those ideas that keeps getting pushed further down the list..
a quick test here, seems to operate as intended :good:
Thank you for testing! :) Of course you are 100% free to use it in the distro in future, whether as-is or modified as you like...
vinnie wrote:
08 Mar 2026, 00:33
seems nice!
unfortunately I use nemesis but meaby it's possible to adapt your gui also with nemesis
Thanks also! I haven't used Nemesis yet, so I don't know if you have a tool comparable to slapt-mod that can download packages and assemble them into a module... but if you do, you can probably just replace the calls to slapt-mod with equivalent calls to the Nemesis tool. This Module Maker is really just a thin Gui wrapper.

Edit: the Nemesis developers are also 100% free to use it in their distro in future, whether as-is or modified as they like. ;)

User avatar
ncmprhnsbl
DEV Team
DEV Team
Posts: 4547
Joined: 20 Mar 2012, 03:42
Distribution: v5.1-alpha*-64bit
Location: australia
Contact:

gtk-slapt-mod: Module Maker

Post#5 by ncmprhnsbl » 09 Mar 2026, 00:41

nemesis does have a similar wrapper script for pacman (pman) so porting this shouldn't be too difficult, maybe.. mainly just the search results piped to the gui could be tricky, but rest, the meaty bits are just spawned in a terminal, so that shouldn't be to hard..
at the moment, nemesis doesn't use pygobject3 for it's dialogs (of which there are are few), it's still using gtkdialog (and dialog(i think) for terminal based config tools)
not really planning to change this anytime soon..
the cinnamon flavour, does include pygobject, so it could support a port of this out the box.
of course, adding the missing deps is trivial..
unless someone wants to rewrite it in gtkdialog :O
Forum Rules : https://forum.porteus.org/viewtopic.php?f=35&t=44

User avatar
Blaze
DEV Team
DEV Team
Posts: 4068
Joined: 28 Dec 2010, 11:31
Distribution: ⟰ Porteus current ☯ all DEs ☯
Location: ☭ Russian Federation, Lipetsk region, Dankov
Contact:

gtk-slapt-mod: Module Maker

Post#6 by Blaze » 11 Mar 2026, 15:45

Hi pterid.
Module Maker freeze on the alien repo at 20%
Image
Linux 6.6.11-porteus #1 SMP PREEMPT_DYNAMIC Sun Jan 14 12:07:37 MSK 2024 x86_64 Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz GenuineIntel GNU/Linux
MS-7A12 » [AMD/ATI] Navi 23 [Radeon RX 6600] [1002:73ff] (rev c7) » Vengeance LPX 16GB DDR4 K2 3200MHz C16

pterid
Contributor
Contributor
Posts: 97
Joined: 01 Feb 2025, 20:13
Distribution: Porteus 5.01 Xfce on ext4 USB

gtk-slapt-mod: Module Maker

Post#7 by pterid » 12 Mar 2026, 00:51

Blaze wrote:
11 Mar 2026, 15:45
Hi pterid.
Module Maker freeze on the alien repo at 20%
Sorry to hear that Blaze!

Unfortunately I cannot replicate this at the moment - I put the same sources (slackware.nl/alien/sbrepos/ both 15.0 and current) on Porteus 5.01 and they updated OK. Is it consistently broken for you, and does slapt-mod work on the command line?

User avatar
Blaze
DEV Team
DEV Team
Posts: 4068
Joined: 28 Dec 2010, 11:31
Distribution: ⟰ Porteus current ☯ all DEs ☯
Location: ☭ Russian Federation, Lipetsk region, Dankov
Contact:

gtk-slapt-mod: Module Maker

Post#8 by Blaze » 12 Mar 2026, 14:59

pterid, is the same story in terminal:
Image
Seems GPG-KEY issue with slackpkg coz I have this error:

Code: Select all

No key for verification
Linux 6.6.11-porteus #1 SMP PREEMPT_DYNAMIC Sun Jan 14 12:07:37 MSK 2024 x86_64 Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz GenuineIntel GNU/Linux
MS-7A12 » [AMD/ATI] Navi 23 [Radeon RX 6600] [1002:73ff] (rev c7) » Vengeance LPX 16GB DDR4 K2 3200MHz C16

pterid
Contributor
Contributor
Posts: 97
Joined: 01 Feb 2025, 20:13
Distribution: Porteus 5.01 Xfce on ext4 USB

gtk-slapt-mod: Module Maker

Post#9 by pterid » 12 Mar 2026, 17:59

Blaze wrote:
12 Mar 2026, 14:59
pterid, is the same story in terminal:
Image
Seems GPG-KEY issue with slackpkg coz I have this error:

Code: Select all

No key for verification
Ah so a deeper issue with slapt-get, not with my little gui... I am afraid I cannot help with that!

beny
Full of knowledge
Full of knowledge
Posts: 2397
Joined: 02 Jan 2011, 11:33
Location: italy

gtk-slapt-mod: Module Maker

Post#10 by beny » 12 Mar 2026, 18:25

hi blaze take a look at the adress:maybe the http trouble with alien repos, the python-script work also in porteux but do not download files only the -u option work
Retrieving package data [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...Done
Retrieving patch list [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...Done
Retrieving checksum list [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...Done
Retrieving checksum signature [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...Done
Verifying checksum signature [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...No key for verification
Retrieving ChangeLog.txt [https://slackware.nl/people/alien/sbrep ... nt/x86_64/]...Done
Reading Package Lists...Done

User avatar
ncmprhnsbl
DEV Team
DEV Team
Posts: 4547
Joined: 20 Mar 2012, 03:42
Distribution: v5.1-alpha*-64bit
Location: australia
Contact:

gtk-slapt-mod: Module Maker

Post#11 by ncmprhnsbl » 12 Mar 2026, 23:31

Blaze wrote:
12 Mar 2026, 14:59
Seems GPG-KEY issue with slackpkg coz I have this error:
try:

Code: Select all

slapt-mod -k
which should fetch the gpg keys ...if that's the problem..
Forum Rules : https://forum.porteus.org/viewtopic.php?f=35&t=44

Post Reply