How to create a PySimpleGUI program
I wrote a program a few weeks ago that moves playlists from Linux to iPhone. It works but I wanted something a little nicer. I was going to write it in GTK but I always write GTK. Then I remember writing some simple program in a library called EasyGUI years ago. Anyone remember AnyGUI? I was surprised to find EasyGUI still out here. I started writing code and thought it seemed a bit too simple. Then I remembered another one, PySimpleGUI. It’s still out here too. Well, it won mainly because it can be themed easily.
Base setup and libraries
I would suggest creating a virtual environment if you want to keep this project separate from your system Python but it is up to you. This code is for Linux running Gnome. I am running Pop! OS 22.04 running Gnome 42.3 and will write this in Python 3.x.
First thing you need are the packages. You will need PyGObject and PySimpleGUI. Everything else is included in Python.
Install both with PIP:
$ pip install pysimplegui
$ pip install pygobject
I ran into problems with PyGObject, installing these two packages should get you running.
$ sudo apt install libcairo2-dev
$ sudo apt install libgirepository1.0-dev
Try to pip install pygobject again. If it fails just look at the messages as you will need to identify what library you are missing and install it.
The program
Here is the full program. I have documented it throughout to make it clear what is going on. There Cookbook for PySimpleGUI… Now to convert to classes and make it really nice.
#!/usr/bin/env python3 from gi.repository import Gio # https://pypi.org/project/PyGObject/ import PySimpleGUI as sg # https://pypi.org/project/PySimpleGUI/ import urllib.parse import shutil import os import sqlite3 import getpass import threading ''' PySimpleGUI example that pushes Lollipop playlists to FlacBox on iPhone. Author: C. Nichols, Oct. 2022 What you will learn: How to create a simple PySImpleGUI application Basic layout. Basic app theming. How to update lists within the application. How to thread a process and pump message back to the user. How to create a popup. How to get the path to the iPhone document folder via Gio. SQLite access. ''' def notify(appname, message): cmd = 'zenity --name="{0}" --notification --text="{1}" "$THREAT\n$STRING"'.format(appname, message) os.system(cmd) def get_flacbox_path(): '''Uses GTK Gio to access Gnome virtual filesystem and determine the path to iPhone Documments. Return a path we can use to push files over.''' flac_path = 'iPhone not connected, most likely.' try: vm = Gio.VolumeMonitor.get() for volume in vm.get_volumes(): activation_root = volume.get_activation_root() if activation_root: if activation_root.get_uri_scheme() == 'afc': gvfs_path = "/run/user/1000/gvfs/afc:host={0},port=3".format(volume.get_uuid()) flac_path = os.path.join(gvfs_path,"com.leshko.flap","_downloads") except: # iPhone not connected. pass if os.path.exists(flac_path): return flac_path def get_playlists(user): '''Collects all Lollipop playlists from the database, if any.''' playlist_data = {} # Connect to the Lollipop database and fetch the tracks for each playlist. lollipop_db = '/home/{0}/.local/share/lollypop/playlists.db'.format(user) #if not os.path.exists(lollipop_db): return 0 try: conn = sqlite3.connect(lollipop_db) cursor = conn.execute("SELECT * from playlists p JOIN tracks t ON p.id = t.playlist_id") for row in cursor: playlist_name = row[1].strip().upper() if playlist_name not in playlist_data: playlist_data[playlist_name]=[] if "popular tracks" in playlist_name: continue # This is a default Lollipop playlist and cannot be removed. song_path = urllib.parse.unquote(row[8].strip()).replace('file://','').replace('\n','') playlist_data[playlist_name].append(song_path) conn.close() except: return 0 return playlist_data def push_to_iphone(window, pl_songs, selected_lists, base_flacbox_path): '''Copies music to iPhone.''' messages = [] try: for playlist_name in selected_lists: for song_path in pl_songs[playlist_name.strip()]: song_only_path = os.path.split(song_path)[0] song_name = os.path.split(song_path)[-1] phone_playlist_path = os.path.join(base_flacbox_path, playlist_name) phone_playlist_file = os.path.join(base_flacbox_path, playlist_name, song_name) if not os.path.exists(phone_playlist_path): os.mkdir(phone_playlist_path) if os.path.exists(song_path): try: shutil.copyfile(os.path.realpath(song_path), os.path.realpath(phone_playlist_file)) msg = 'Copied: {0} - {1}'.format(playlist_name,song_name) except Exception as err: msg = 'Error: "{0} - {1} -> {2}'.format(playlist_name,song_name,err) else: msg = 'Not Found: {0} - {1}'.format(playlist_name,song_name) # Update app message window. print(msg) # Gets piped to output in app. #window.write_event_value('-THREAD PROGRESS-', msg) except Exception as err: msg = err # Update app message window. print(msg) # Let our app know we are done and notify user. window.write_event_value('-UPLOAD_COMPLETED-', '') def threaded_copy(window, pdict, upload_list, doc_path): '''Called from upload button to process push_to_iphone in thread.''' threading.Thread(target=push_to_iphone, args=(window, pdict, upload_list, doc_path,), daemon=True).start() # =========================================== # MAIN # =========================================== if __name__ == "__main__": # Set theme, more at PySimpleGui web-site. sg.theme('Default1') # Get the current logged on user's name. my_name = getpass.getuser() # Check for iPhone doc_path = '' iphone_available = False doc_path = get_flacbox_path() if doc_path: iphone_available = True conn = sg.Text('iPhone Connected', text_color='#037100', enable_events=True, key="-CONNECT-") else: conn = sg.Text('Connect iPhone', text_color='#ff0000', enable_events=True, key="-CONNECT-") # Grab the playlists from Lollipop. pdict = get_playlists(my_name) pnames = list(pdict.keys()) pnames.sort() upload_list = [] # Define the upload list. # Define layout into columns and add the form components. col_right = [[sg.Button('Upload')]] col_left = [[sg.Button('Quit')]] col_center = [ [sg.Text('Select Playlist(s)')], [sg.Listbox(values=pnames, size=(70, 4), enable_events=True, key='-ADD-')], [sg.Text('Playlist(s) to upload:')], [sg.Listbox(values=upload_list, size=(70, 4), enable_events=True, key='-REMOVE-')], [sg.Text('Messages:')], [sg.Output(size=(70,10))] ] gui_rows = [ [sg.Column(col_center, element_justification='center', expand_x=True)], [sg.Column(col_left, element_justification='left', expand_x=True), conn, sg.Column(col_right, element_justification='right', expand_x=True)] ] # Create window. window = sg.Window('Lollipop2iPhone', gui_rows, element_justification='center') # *** Start the application loop *** while (True): # This is the code that reads and updates your window event, values = window.read() # [TEXT ACTION GET IPHONE PATH] if '-CONNECT-' in event: if not iphone_available: doc_path = get_flacbox_path() if doc_path: iphone_available = True window['-CONNECT-'].update('iPhone Connected', text_color = '#037100') else: iphone_available = False window['-CONNECT-'].update('Connect iPhone', text_color = '#ff0000') sg.popup('iPhone not found.') # [LIST ADD TO UPLOADs] if '-ADD-' in event: if values['-ADD-']: upload_list.append(values['-ADD-'][0]) pnames.remove(values['-ADD-'][0]) # Update physical lists. window['-ADD-'].update(pnames) window['-REMOVE-'].update(upload_list) # [LIST REMOVE FROM UPLOADS] if '-REMOVE-' in event: if values['-REMOVE-']: #INDEX = int(''.join(map(str, window["remove"].get_indexes()))) upload_list.remove(values['-REMOVE-'][0]) #pnames.insert(INDEX, values['remove'][0]) pnames.append(values['-REMOVE-'][0]) pnames.sort() window['-REMOVE-'].update(upload_list) window['-ADD-'].update(pnames) # [BUTTON UPLOAD] if 'Upload' in event: #window.perform_long_operation(lambda: push_to_iphone(pdict, upload_list, doc_path), 'UPLOAD_COMPLETED') threaded_copy(window, pdict, upload_list, doc_path) # [UPLOAD FINISHED POPUP] if '-UPLOAD_COMPLETED-' in event: # Notify the uploads are finished. sg.popup('Uploads Finished.') # Reset the list boxes... for i in upload_list: pnames.append(i) upload_list = [] pnames.sort() window['-ADD-'].update(pnames) window['-REMOVE-'].update(upload_list) # Send notification to Gnome. notify("PySimpleGUI", "Playlists uploaded to Flacbox.") # [BUTTON QUIT] if event in ('Quit', sg.WIN_CLOSED): break window.close()