My cross-platform Home Brewed System, a tutorial for GNU/Linux users (tested in Garuda Linux)

When I began to Vibe Code my Show-It.py Python3 script, I intended it to work on both Windows 11 Pro 25H2 and Garuda Mokka Linux, and my efforts were successful on Windows, but when I copied the script file to Garuda, while I could run it from the command line, it turned out that systemd is much fussier than the Windows Task Manager, so I had to embark on a bit of a journey to make my script work corectly with systemd. As I embark on writing this Linux-based tutorial, my latest journey's complete after using Googles Gemini to help me merge my two versions of my script into one, so it truly does execute equally well on both Windows 11 Pro 25H2 and Garuda Mokka Linux, my two favorite environments to work and play on. First things first! Check that you have Python installed on your system, using this command in your terminal: python --version # If Python's installed this command should return the installed version (We want Python3 for my Python script) If for some reason, this command fails, or returns nothing, try the following: python3 # Enters the Python3 shell. Enter exit to return to your regular system shell Python3 should be installed on the vast majority of distributions. If it's not, install it to be able to use my script. The command required to install Python will differ from one distribution to the next, so I won't try to provide that information here. You can easilly perform an Internet search on How to install Python3 on your distribution in your web browser. For the purposes of this tutorial, I'll provide the content of the files we'll need to create a Test reminder. Then I'll provide directions to get that content from this post into files on your computer. Since you'll have the files needed for the Test task by the time we get to enabling it in systemd, I'll examine both files needed for the test in great detail, so hopefully you'll learn what each file does, and why two are needed, so without further ado, we'll get started with the content of the files you'll be getting, Show-It.py, Show-It.service, Test.timer, and my Reminder Crestion Guide:
  
My Show-It.py Python3 script:

#!/usr/bin/python

# Show-It.py Displays a small window on your desktop that can be used for scheduled reminders
# Copyright (C) 2026  Ernest N. Wilcox Jr.
# Contact: ewilcox@gmail.com
# Leave a comment at https://ewilcox.blogspot.com/2026/05/my-home-brewed-reminder-sysyem.html

# This program is free software: you can redistribute it and/or modify it under the terms of the
# NU 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 .

import tkinter as tk
import platform
import subprocess
import argparse
import sys
import os

def play_beep():
    """Plays a system-appropriate notification sound."""
    os_type = platform.system()

    if os_type == "Windows":
    try:
        import winsound
        # This plays the standard 'Asterisk' notification sound
        winsound.MessageBeep(winsound.MB_ICONASTERISK)
    except Exception:
        # Fallback to the classic beep if system sounds are muted/unavailable
        print('\a', end='', flush=True)
    else:
        # Linux / Garuda logic
        try:
            subprocess.run([
                "canberra-gtk-play",
                "--id", "message-new-instant-message"
            ], check=False)
        except FileNotFoundError:
            try:
                sound_file = "/usr/share/sounds/freedesktop/stereo/message-new-instant-message.oga"
                subprocess.run(["paplay", sound_file], check=False)
            except Exception:
                print('\a', end='', flush=True)

def main():
    # 1. Argument Handling
    # Logic to handle both single-string (Title-Message) and two-string (Title Message) inputs
    if len(sys.argv) == 2 and ("-" in sys.argv[1] or "/" in sys.argv[1]):
        raw_input = sys.argv[1]
        separator = "/" if "/" in raw_input else "-"
        parts = raw_input.split(separator, 1)

        raw_title = parts[0]
        raw_message = parts[1] if len(parts) > 1 else ""
    else:
        # Standard CLI behavior: script.py "Title" "Message"
        # Fixed 'nargs' typo here
        parser = argparse.ArgumentParser(description="Cross-platform Notification Tool")
        parser.add_argument("title", help="Window title")
        parser.add_argument("message", help="Message body", nargs='?', default="")
        args = parser.parse_args()
        raw_title = args.title
        raw_message = args.message

    # 2. String Cleaning
    display_title = raw_title.replace("_", " ").strip()
    display_message = raw_message.replace("_", " ").strip()

    # 3. Sound
    play_beep()

    # 4. GUI Setup
    root = tk.Tk()
    root.title(display_title)
    root.attributes('-topmost', True)
    root.minsize(380, 150)

    # UI Elements
    tk.Label(
        root,
        text=display_title,
        font=("Arial", 12, "bold")
    ).pack(pady=(20, 5))

    msg_label = tk.Label(
        root,
        text=display_message,
        font=("Arial", 11),
        wraplength=340,
        justify="center"
    )
    msg_label.pack(pady=15, padx=20)

    tk.Button(
        root,
        text="Dismiss",
        command=root.destroy,
        width=12
    ).pack(pady=(5, 20))

    # 5. Dynamic Sizing
    root.update_idletasks()
    width = max(380, root.winfo_reqwidth())
    height = max(150, root.winfo_reqheight())
    root.geometry(f"{width}x{height}")

    root.mainloop()

if __name__ == "__main__":
    main()
  
 

The Show-It@.service file you'll want for anything you run with systemd using my Show-It.py Python3 script:

    
[Unit]
Description=Show-It Notification Instance: %i

[Service]
Type=simple
ExecStart=/usr/bin/env python3 %h/Show-It/Show-It.py "%i"

# Wayland-specific Environment Variables
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=WAYLAND_DISPLAY=wayland-0
Environment=QT_QPA_PLATFORM=wayland
Environment=GDK_BACKEND=wayland

[Install]
WantedBy=graphical-session.target


The Test.timer file we'll use to test the reminder system later:

[Unit]
Description=Test Reminder at 4:30 PM daily

[Timer]
# Generic format: OnCalendar=DayOfWeek Year-Month-Day Hour:Minute:Second
# Annuals: OnCalendar=*-MM-DD The asterisk (*) acts as a wildcard for the year.
# Monthly: OnCalendar=*-*-DD
# Schedile every day at 4:30 PM
OnCalendar=*-*-* 16:30:00
# You can pass specific arguments by creating a specific service
# or just keep it simple with the defaults.
Unit=Show-It@Test-This_is_a_simple_test.service

[Install]
WantedBy=timers.target
  
 

My Reminder_Creation_Guide, which contains helpful information about creating your own reminders:

  
Activation Commands

Unlike system services, user services do not require sudo. Run these commands in your terminal:

    Reload the daemon to recognize new files:
    systemctl --user daemon-reload

    Enable and start the timer:
    systemctl --user enable --now FileName.timer

    Check the status:
    systemctl --user list-timers

    Test the new task:
    systemctl --user start Show-It@Title_Here-Message_Here.service

    Remove a task:
    Stop the timer:
        systemctl --user stop mytimer.timer
    Disable the timer:
        systemctl --user disable mytimer.timer
    Delete the unit file(s):
        rm ~/.config/systemd/user/mytimer.timer
    Reload systemd manager configuration:
        systemctl --user daemon-reload
    List timers:
        systemctl --user list-timers
    Reset failed state (if necessary):
        systemctl --user reset-failed


Formatting Rules

    Wildcards: Use * to represent "every." For example, *-*-* would technically trigger every day at midnight.

    Ranges: Use .. to specify a range, such as *-*-01..07 to cover the first week of a month.

    Repeating Intervals: Use / for intervals, like *-*-01/2 to run every two months.

    Testing Your OnCalendar String: You can verify how systemd interprets your string by using the systemd-analyze command in your terminal:

        systemd-analyze calendar "*-MM-DD"

This will show you the exact date and time of the next few times the timer will trigger, ensuring your logic is sound before you enable the service.
  
 
Now, with everything needed to create the Test reminder added to this tutorial, we'll begin by creating a directory for the Show-It.py Python3 script and check that the directory path systemd expects for the storage of files used for local services exists, and if not, create it. Open your terminal window, if it's not already running: On most distributions, press the CTRL+ALT+T keyboard key combination In the terminal, create the Show-It directory where we'll store the Show-It.py Python3 script file: mkdir ~/Show-It Check whether the path systemd expects for local services exists on your computer: cd ~/.config/systemd/user If your terminal changes to that directory, it exists. If not, create it: mkdir ~/.config/systemd/user Next, create the empty files needed to store the content provided above. Create the empty Show-It.py Python3 script file: touch ~/Showit/Show-It.py The empty Show-It@.service file: touch ~/.config/systemd/user/Show-It@.service And the empty Test.timer file: touch ~/.config/systemd/user/Test.timer Optionally, create an empty file in the Documents directory for the Reminder Creation Guide provided above: touch ~/Documents/Reminder_Creation_ Guide.txt If you've followed this tutorial so far, and completed all steps as instructed, you now have the directories and empty files needed to store my Show-It.py Python3 script, the files that will be needed for the Test reminder, Show-It@.service and Test.timer, in place on your computer, and (if you created it) the empty Reminder_Creation_ Guide.txt file. Now I describe how to get the text you want from any online resource, such as this blog post, or from any forum page, onto your computer's clipboard. I'll use my Show-It.py Python script for the step by step procedure that follows, and you'll be using this procedure for the other tow or three files: Highlight the desired text: Place your mouse cursor in front of the first charecter of my Show-It.py script above, or any contiguous text. While pressing and holding your mouse's primary button, drag your mouse's cursor over all the text you want to highlight, to and including the last character of my Show-It.py script, or any other content, then release your primary mouse button. Copy the highlighted text to your computer's clipboard: With the desired text highlighted, Press the keyboard key combination: CTRL+C or ALT+Click anywhere within the highlighted text and in the resulting context menu, click the Copy option. With the content of the Show-It.py script, or what ever other text you've copied to your computer's clipboard, from your terminal, open your text editor with the empty Show-It.py file we created earlier, or the empty file you've created for the purpose. I'll use Kate here, but you should use the text editor that's provided by your distribution: kate ~/Show-It/Show-It.py # (replace the name of the editor and the path to the file you created as appropriate) Your text editor will open with the empty Show-It.py file (or the empty file you created) opened within it. Paste the Show-It.py script (or other content) from your computer's clipboard into the editor's window: ALT+Click anywhere within the body of the editor's window, and in the resulting context menu, click Paste. The content of my Show-It.py script (or other content) from your computer's clipboard will appear in the text editor's window. Save your work to disk: Open the text editor's File menu and click the Save option or Press the CTRL+S keyboard key combination. Follow the above procedure to highlight, copy, paste, and save to disk, the content of the Show-It@.service, Test.timer, and optionally (if you created it) the Reminder Creation Guide.txt files (one at a time). There is one more step to complete so the Show-It.py Python3 script can be directly executed. We must enable it's execution permission: chmod +x ~/Show-It/Show-It.py Systemd will use two files to produce our Reminders: You may want to copy these two file descriptions into a systemd notes file for future reference.

The Show-It@.service file:

  
    [Unit] contains one line:
        Description=Show-It Notification Instance: %i # a brief explanation of what the service does/is for
    [Service] # contains three or more lines:
        Type=simple # the type of the service
        # In our ExecStart:
        #     %h does for systemd what ~ does for BASH (expands to the current users home directory)
        #     %i specifies the instance name (in the Unit line of the TIMER file, the string between @ and .service)
        #     /usr/bin/env python3, an explicit path to Python3
        #     %h/Show-It/Show-It.py "%i", the path to the Show-It.py file on disk
        # Wayland-specific Environment Variables
        Environment=XDG_RUNTIME_DIR=/run/user/1000 # where to store temp files - a dierctory in RAM
        Environment=WAYLAND_DISPLAY=wayland-0 # Tells systemd where to connect to the display
        Environment=QT_QPA_PLATFORM=wayland # Tells systemd we're working in a Wayland session
        Environment=GDK_BACKEND=wayland # tells systemd and Python3 to use the Wayland display protocol
    [Install] # contains one line:
        WantedBy=graphical-session.target # tells systemd to execute the service only if in a graphical session
  
 

The Test.timer file:

  
    [Unit] contains one line:
        Description=Test Reminder at 4:30 PM daily
    [Timer] contains two lines
        # Format: DayOfWeek Year-Month-Day Hour:Minute:Second in the form of a 24-hour clock (00:00:00-23:59:29)
        # Wildcards: Use * for "any" (e.g., *-*-* means every day/month/year).
        # Lists/Ranges: Use commas for lists (Mon,Wed,Fri) and .. for ranges (Mon..Fri).
        # Steps: Use / for intervals (e.g., *:0/15 for every 15 minutes).
        # Keywords: Simple scheduling with terms like minutely, hourly, daily, monthly, or weekly.
        # Multiple Entries: You can use multiple OnCalendar= lines in one file to trigger at different times.
        # 16:30:00=16 hours after midnight:30 minutes after the hour: 00 seconds after the minute= 4:30 PM (16 - 12)
        OnCalendar=*-*-* 16:30:00, scheduled date & time

        # Show-It: Base name of the service template file (Show-It@.service)
        # @: indicates a template Unit
        # Test-This_is_a_simple_test: instance (parameter) name; %I in the service unit
        # .service: he type of unit being activated.
        Unit=Show-It@Test-This_is_a_simple_test.service
  
 
To describe these two files in very broad strokes, we could say that Show-It.service tells systemd about how to launch the Show-It.py script, and that each *.timer file tells systemd about it's specific task, and how/when to launch it. Since I created and gave you both of the files (Show-It@.service and Test.timer) needed for the Test Reminder, I thoroughly described both files, so now, all that remains is to enable and test our test reminder: Reload the systemd daemon to recognize new files: systemctl --user daemon-reload Enable and start the timer: systemctl --user enable --now Test.timer Check the timer's status: systemctl --user list-timers # Press q to exit Test the new task: systemctl --user start Show-It@Test-This_is_a_simple_test.service I have a few closing comments. First, I'm surprised at what I've learned about systemd as I wrote this. Additionaally, as I worked my way through the process of writing this, I found a few issues/better ways to do a few things not only in my Show-It.py script, but in my Show-It@.service and Test.timer files, from which I found a few improvements for all my existing timer filess, so this has been as educational for me as I hope it will be for you. Ernie

Comments

Popular posts from this blog

How to - Enable/Set-up secure boot on Arch-based distributions

Common Debian App Commands With Descriptions

Completely remove OneDrive from your Windows computer/installation