Message not displaying with PySimpleGUI - python

I have a project where I'm trying to build a simple time-clock using Python 3 on a Raspberry Pi. The Pi is running 64-bit Bullseye.
In my script, I create a window with two columns, one for entry and one for display. The entry side is working (as far as I have gone). The display side sorta works, and this is the issue.
The user will enter their code, press "Enter" to see their information, and then press "In" or "Out" for clocking in or out. When the user presses "In", I want to display a message that says either "Clocked In" or "Already Clocked in".
The issue is that the clocked-in message does not display. The statement that fails is the msg_elem.Update( .... If I run the Python debugger, the message is displayed, but not in "normal" running.
My question is What am I doing wrong?
This is a working example...
import sys, os, platform
import PySimpleGUI as sg
from PIL import Image, ImageTk
from time import sleep
import io
#
# Elements
# create the Elements we want to control outside the form
out_elem = sg.Text('', size=(55, 1), font=('Helvetica', 18), text_color='black',justification='center')
in_elem = sg.Input(size=(10,1), do_not_clear=True)
img_elem = sg.Image(size=(240,240),key="-IMAGE-")
msg_elem = sg.Text('', size=(65, 1), font=('Helvetica', 18), text_color='black',justification='center',key='-MSG-')
#
# Columns
button_column = [
[sg.Text("User Input"),in_elem],
[sg.ReadFormButton('1', size=(3,3)),
sg.ReadFormButton('2', size=(3,3)),
sg.ReadFormButton('3', size=(3,3)),
sg.ReadFormButton('Clear', size=(6,3))],
[sg.ReadFormButton('4', size=(3,3)),
sg.ReadFormButton('5', size=(3,3)),
sg.ReadFormButton('6', size=(3,3)),
sg.ReadFormButton('Enter', size=(6,3))],
[sg.ReadFormButton('7', size=(3,3)),
sg.ReadFormButton('8', size=(3,3)),
sg.ReadFormButton('9', size=(3,3)),
sg.ReadFormButton('Quit', size=(6,3))],
[sg.T(''), sg.T(' ' * 8),
sg.ReadFormButton('0', size=(3,3))],
[sg.T('')],
[sg.ReadFormButton('In', size=(13,3)),
sg.ReadFormButton('Out', size=(13,3)),]
]
display_column = [
[sg.Text("User Details")],
[out_elem],
[sg.T(' ' * 30), img_elem],
[msg_elem],
]
#
layout = [
[sg.Column(button_column),
sg.VSeperator(),
sg.Column(display_column,justification='center',vertical_alignment='top')]
]
form = sg.Window('Time Clock', layout, auto_size_buttons=False, size=(800,480))
keys_entered = ''
while True:
button, values = form.Read()
if button is None:
form["-IMAGE-"].update()
out_elem.Update( " " )
break
elif button == 'Clear':
keys_entered = ''
pcpid = ''
empid = ''
form["-IMAGE-"].update()
in_elem.Update(keys_entered)
out_elem.Update( " " )
msg_elem.Update( " " )
elif button in '1234567890':
keys_entered = in_elem.Get()
keys_entered += button
elif button == 'Enter':
keys_entered = '123'
first_name = 'Mike'
last_name = 'Retiredguy'
empid = 12345
im1 = Image.open( 'mike.png' )
im1.thumbnail((240,240))
bio = io.BytesIO()
im1.save( bio, format="PNG")
empimage = bio.getvalue()
form["-IMAGE-"].update( empimage )
dsplAns = f"{empid} - {last_name}, {first_name}"
out_elem.Update( dsplAns )
elif button == 'In':
# import pdb; pdb.set_trace()
sqlAns = 1 # User already clocked in
if sqlAns > 0:
msg_elem.Update( "...is already clocked in! (A)" ) # <=== THIS IS WHAT FAILS
else:
msg_elem.Update( "...is clocked in! (B)" ) # <=== THIS IS WHAT FAILS
sleep(10)
# Clear input
keys_entered = ''
pcpid = ''
empid = ''
form["-IMAGE-"].update()
in_elem.Update(keys_entered)
out_elem.Update( " " )
msg_elem.Update( " " )
elif button == 'Quit':
sys.exit(0)
in_elem.Update(keys_entered)
#
# ###EOF###
I have tried this on the RPi, and on a Virtual system with Debian (not Pi) linux. Both give me the same result. I've searched here on Stack Overflow, and Google in general, and I think I'm doing it correctly, but failing.

The method msg_elem.Update just update the architecture of PySimpleGUI, not the GUI. Need to call window.refresh() before sleep(10) if you want the GUI updated immediately, not until back to window.read(). sleep(10) take long time for GUI to wait, so it will show "Not Responding".
Demo Code
from time import sleep
import threading
import PySimpleGUI as sg
def func(window, value):
global running
message = f'You clicked the button "{value}", this message will be cleared after 3s !'
window.write_event_value('Update', message)
sleep(3)
window.write_event_value('Update', '')
running = False
sg.set_options(font=('Courier New', 12))
layout = [
[sg.Button('Hello'), sg.Button('World')],
[sg.Text('', size=80, key='State')],
]
window = sg.Window('Title', layout)
running = False
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event in ('Hello', 'World') and not running:
running = True
threading.Thread(target=func, args=(window, event), daemon=True).start()
elif event == 'Update':
message = values[event]
window['State'].update(message)
window.close()

Related

Python search engine GUI(PySimpleGui) - Listbox with right_click_menu

I have OPEN and SAVE options on the right click menu which enables users to open or save selected multiple files in the ListBox output using PySimpleGui.. I am having errors to trigger those events.. Can you please help? Please refer to the code below I scripted but having errors.
import PySimpleGUI as sg
from tkinter.filedialog import asksaveasfile
from tkinter import *
import os
import threading
from time import sleep
from random import randint
def search(values, window):
"""Perform a search based on term and type"""
os.chdir('G:\MOTOR\DataExtracts/')
global results
# reset the results list
results.clear()
results = []
matches = 0 # count of records matched
records = 0 # count of records searched
window['-RESULTS-'].update(values=results)
window['-INFO-'].update(value='Searching for matches...')
# search for term and save new results
for root, _, files in os.walk(values['-PATH-']):
for file in files:
records +=1
if values['-ENDSWITH-'] and file.lower().endswith(values['-TERM-'].lower()):
results.append(f'{file}')
window['-RESULTS-'].update(results)
if values['-STARTSWITH-'] and file.lower().startswith(values['-TERM-'].lower()):
results.append(f'{file}')
window['-RESULTS-'].update(results)
if values['-CONTAINS-'] and values['-TERM-'].lower() in file.lower():
results.append(f'{file}')
window['-RESULTS-'].update(results)
matches += 1
window['-INFO-'].update('Enter a search term and press `Search`')
sg.PopupOK('Finished!') # print count of records of matched and searched in the popup(*)
def open_file(file_name):
# probably should add error handling here for when a default program cannot be found.(*)
# open selected files with read-only mode (check box or right click option)(*)
pass
def save_file(file_name):
# download selected files - one file or multiple files download at the same time (*)
# print downloading time or progress bar with downloaded files(*)
# print downloaded files logging info(*)
# create the main file search window
results = []
sg.change_look_and_feel('Black')
command = ['Open', 'Save']
cmd_layout = [[sg.Button(cmd, size=(10, 1))] for cmd in command]
layout = [
[sg.Text('Search Term', size=(11, 1)), sg.Input('', size=(40, 1), key='-TERM-'),
sg.Radio('Contains', group_id='search_type', size=(10, 1), default=True, key='-CONTAINS-'),
sg.Radio('StartsWith', group_id='search_type', size=(10, 1), key='-STARTSWITH-'),
sg.Radio('EndsWith', group_id='search_type', size=(10, 1), key='-ENDSWITH-')],
[sg.Text('Search Path', size=(11, 1)), sg.Combo(['ShdwProd/','Other/'],readonly=True, size=(38, 1), key='-PATH-', ), #display only folder name where files are located such as 'ShadowProd' and 'Ohter' in Combo
sg.Button('Search', size=(20, 1), key='-SEARCH-'),
sg.Button('Download', size=(20, 1), key='-DOWNLOAD-')],
[sg.Text('Enter a search term and press `Search`', key='-INFO-')],
[sg.Listbox(values=results, size=(100, 28), bind_return_key = True, enable_events=True, key='-RESULTS-', right_click_menu=['&Right', command])],
[sg.Help(key='-HELP-')]]
window = sg.Window('File Search Engine', layout=layout, finalize=True, return_keyboard_events=True)
window['-RESULTS-'].Widget.config(selectmode = sg.LISTBOX_SELECT_MODE_EXTENDED)
window['-RESULTS-'].expand(expand_x=True, expand_y=True)
# main event loop
while True:
event, values = window.read()
if event is None:
break
if event == '-SEARCH-':
search(values, window)
if event == '-RESULTS-' and len(values['-RESULTS-']):
if event == '-OPEN-': # need to code(*)
pass
if event == '-DOWNLOAD-': # need to code(*)
pass
if event == '-HELP-':
sg.Popup("My help message")
the (*) marks are what I am not sure about and having errors from..
Code revised as following
import os
import threading
from time import sleep
from random import randint
import PySimpleGUI as sg
def search(values, window):
"""Perform a search based on term and type"""
os.chdir("G:/MOTOR/DataExtracts/")
global results
# reset the results list
results = []
matches = 0 # count of records matched
records = 0 # count of records searched
window['-RESULTS-'].update(values=results)
window['-INFO-'].update(value='Searching for matches...')
window.refresh()
# search for term and save new results
for root, _, files in os.walk(values['-PATH-']): # path information of file missed here
for file in files:
records +=1
if any([values['-ENDSWITH-'] and file.lower().endswith(values['-TERM-'].lower()),
values['-STARTSWITH-'] and file.lower().startswith(values['-TERM-'].lower()),
values['-CONTAINS-'] and values['-TERM-'].lower() in file.lower()]):
results.append(f'{file}')
matches += 1
window['-RESULTS-'].update(results)
window['-INFO-'].update('Enter a search term and press `Search`')
window.refresh()
sg.PopupOK('Finished!') # print count of records of matched and searched in the popup(*)
def open_files(filenames):
"""
probably should add error handling here for when a default program cannot be found.(*)
open selected files with read-only mode (check box or right click option)(*)
"""
def save_files(filenames):
"""
download selected files - one file or multiple files download at the same time (*)
print downloading time or progress bar with downloaded files(*)
print downloaded files logging info(*)
"""
# create the main file search window
results = []
sg.theme('Black')
command = ['Open', 'Save']
cmd_layout = [[sg.Button(cmd, size=(10, 1))] for cmd in command]
layout = [
[sg.Text('Search Term', size=(11, 1)),
sg.Input('', size=(40, 1), key='-TERM-'),
sg.Radio('Contains', group_id='search_type', size=(10, 1), key='-CONTAINS-', default=True),
sg.Radio('StartsWith', group_id='search_type', size=(10, 1), key='-STARTSWITH-'),
sg.Radio('EndsWith', group_id='search_type', size=(10, 1), key='-ENDSWITH-')],
[sg.Text('Search Path', size=(11, 1)),
sg.Combo(['ShdwProd/','Other/'], default_value='ShdwProd/', readonly=True, size=(38, 1), key='-PATH-', ), #display only folder name where files are located such as 'ShadowProd' and 'Ohter' in Combo
sg.Button('Search', size=(20, 1), key='-SEARCH-'),
sg.Button('Download', size=(20, 1), key='-DOWNLOAD-')],
[sg.Text('Enter a search term and press `Search`', key='-INFO-')],
[sg.Listbox(values=results, size=(100, 28), select_mode=sg.LISTBOX_SELECT_MODE_EXTENDED, bind_return_key = True, enable_events=True, key='-RESULTS-', right_click_menu=['&Right', command], expand_x=True, expand_y=True)],
[sg.Help(key='-HELP-')]]
window = sg.Window('File Search Engine', layout=layout, finalize=True, return_keyboard_events=True)
# main event loop
while True:
event, values = window.read()
print(event, values)
if event == sg.WINDOW_CLOSED:
break
elif event == '-SEARCH-':
search(values, window)
elif event == '-RESULTS-' and len(values['-RESULTS-']):
pass
elif event == 'Open': # need to code(*)
open(values['-RESULTS-'])
elif event == 'Save': # need to code(*)
save(values['-RESULTS-'])
elif event == '-DOWNLOAD-': # need to code(*)
print(values['-RESULTS-'])
elif event == '-HELP-':
sg.Popup("My help message")
window.close()

I'm trying to use pysimplegui, but i cant re-run the window

import sys
import PySimpleGUI as sg
import os
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(), sg.Cancel()],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
event, values = window.read()
window.close()
def loggin():
global success
success = False
file = open('psw.txt','r')
for i in file:
a,b = i.split(",")
b = b.strip()
if (a == values[0] and b == values[1]):
success = True
else:
print("wrong")
loggin()
if success == False:
pass
print("next stage")
I'm trying to use pysimplegui, but I can't re-run the window. I'm trying to figure out how to call window(), so I can try logging in if you mess up. But whenever I try to run window(), it gives me an error that I can't open a window that's already been open. Any workarounds or ways to restart the scripts completely?
You cannot re-use the layout by instance of element, call class of element required.
To recall a window, by using function call will be better.
import PySimpleGUI as sg
def get_codes(filename):
with open(filename, 'rt') as f:
lines = f.readlines()
codes = []
for line in lines:
a, b = line.split(',')
codes.append((a.strip(), b.strip()))
return codes
def login(codes):
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(), sg.Cancel()],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Cancel'):
print('Login Cancelled')
success = None
elif event == 'Submit':
if (values[0], values[1]) in codes:
success = True
print("Login Successful")
else:
success = False
print("Login Failed")
break
window.close()
return success
password_file = 'psw.txt'
codes = get_codes(password_file)
success = login(codes)
Here is different way. I added the os module so that you get a relative path, which will then work on any computer.
import PySimpleGUI as sg
import os
def login():
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(key='Submit'), sg.Cancel(key='Cancel')],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
while True:
event, values = window.read()
if event == 'Cancel' or event == sg.WIN_CLOSED:
break
else:
filename = os.path.join(os.path.dirname(__file__), 'psw.txt') # <-- Use this so that you have a relative path.
# This way it works on any machine
with open(filename, 'r') as file:
file = open(filename, 'r')
for i in file:
a,b = i.split(",")
b = b.strip()
if (a == values[0] and b == values[1]):
sg.popup_ok("Welcome to your account!")
window.close()
break
else:
sg.popup_error("Incorrect credentials. Try again.")
continue
login()

Text not updating in PySimpleGui

In my code, Im trying to make a calculator. So there is a 1 button which when pressed, updates the Question: text by adding 1 to it's text. So when I press 1, the text will convert from Question: to Question: 1. But its not updating. I have faced this problem before too. I think when I do the .update, it will only update the value till its the same number of letters as the text already has. If it has 2 letters and I try to .update('123'), it will only update to 12. Is there any way to get around this???
import PySimpleGUI as sg
layout = [
[sg.Text('Question: ', key='-IN-')],
[sg.Text('Answer will be shown here', key='-OUT-')],
[sg.Button('1'), sg.Button('2'), sg.Button('3')],
[sg.Button('4'), sg.Button('5'), sg.Button('6')],
[sg.Button('7'), sg.Button('8'), sg.Button('9')],
[sg.Button('Enter'), sg.Button('Exit')]
]
window = sg.Window('calculator', layout)
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
elif event == '1':
bleh = window['-IN-'].get()
teh = f'{bleh}1'
window['-IN-'].update(value=teh)
window.close()
As above comment, Example like this,
import PySimpleGUI as sg
layout = [
[sg.InputText('Question: ', readonly=True, key='-IN-')],
[sg.Text('Answer will be shown here', key='-OUT-')],
[sg.Button('1'), sg.Button('2'), sg.Button('3')],
[sg.Button('4'), sg.Button('5'), sg.Button('6')],
[sg.Button('7'), sg.Button('8'), sg.Button('9')],
[sg.Button('Enter'), sg.Button('Exit')]
]
window = sg.Window('calculator', layout)
input = window['-IN-']
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
elif event in '1234567890':
bleh = window['-IN-'].get()
teh = f'{bleh}{event}'
input.update(value=teh)
input.Widget.xview("end") # view end if text is too long to fit element
window.close()

Running a function when a widget window is brought to the front?

I'm building a gui front-end for a project I've been working on, and I'm having trouble figuring out this specific aspect of PyQT. Long ago there was a post that I had that I think would've solved the problem but I can't quite find it.
Regardless, I have the following files, main.py and playlistcreator.py respectively:
(main.py)
import getpass
import playlistcreator
import os
import sys
import sip
sip.setapi('QString', 2)
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
# Window class
class Window(QWidget):
def __init__(self): # constructor for Window (passes "self" -aka Window- to __init__ to initialize it)
super(Window, self).__init__() # inherits from QMainWindow
self.setGeometry(50, 50, 300, 150) # Set window dimensions
self.setWindowTitle("Google Music Playlist Transfer") # Set window title
self.setWindowIcon(QIcon('gmusic.png')) # Set window icon
self.login = QWidget() # login widget ("front page")
self.processing = QWidget() # on successful login - being processing data
self.file_inputs = QWidget() # after processing, ask user where they would like to store data
# call functions
self.loginUI()
self.processingUI()
self.file_inputsUI()
# create stacked widget and add layouts to stack
self.Stack = QStackedWidget(self)
self.Stack.addWidget(self.login)
self.Stack.addWidget(self.processing)
self.Stack.addWidget(self.file_inputs)
main_box = QHBoxLayout(self)
main_box.addWidget(self.Stack)
self.setLayout(main_box)
self.show()
def loginUI(self):
# Set email field
self.email = QLineEdit()
self.email.setMaxLength(110)
self.email.setAlignment(Qt.AlignLeft)
# Set password field
self.pwd = QLineEdit()
self.pwd.setAlignment(Qt.AlignLeft)
self.pwd.setEchoMode(QLineEdit.Password)
# Form layout
form_layout = QFormLayout()
form_layout.addRow("Email: ", self.email)
form_layout.addRow("Password: ", self.pwd)
# Login button
self.login_btn = QPushButton("Login", self) # login button
self.login_btn.clicked.connect(self.process_login) # tell button what to do
self.login_btn.resize(self.login_btn.sizeHint())
# Quit button
self.quit_btn = QPushButton("Exit", self) # exit button
self.quit_btn.clicked.connect(self.close_application) # tell button what to do
# Error label layout
self.error_layout = QHBoxLayout()
# Button box layout
button_box = QHBoxLayout()
button_box.addStretch(1)
button_box.addWidget(self.login_btn)
button_box.addWidget(self.quit_btn)
# input layout (main layout for "home")
input_box = QVBoxLayout()
input_box.addLayout(self.error_layout)
input_box.addLayout(form_layout)
input_box.addLayout(button_box)
self.login.setLayout(input_box)
def processingUI(self):
# setup layout
layout = QHBoxLayout()
# alert user that we're grabbing track data
self.progress_label = QLabel("Grabbing tracks from Google Music. . .")
self.progress_label.setStyleSheet("color: rgb(0, 100, 0);")
layout.addWidget(self.progress_label)
# Get users list of "thumbs up" tracks from Google Music
# set layout
favorite_tracks = self.get_tracks
self.processing.setLayout(layout)
def file_inputsUI(self):
layout = QFormLayout()
# Set text field for directory
self.dir_textbox = QTextEdit(self)
# Add textbox to layout
layout.addRow("Music Directory: ", self.dir_textbox)
self.file_inputs.setLayout(layout)
def close_application(self):
confirm = QMessageBox.question(self, 'Exit Confirmation',
"Are you sure you want to exit?",
QMessageBox.Yes | QMessageBox.No)
if confirm == QMessageBox.Yes:
sys.exit()
else:
pass
def process_login(self):
email_input = str(self.email.text())
pwd_input = str(self.pwd.text())
login_successful = playlistcreator.login(email_input, pwd_input)
if login_successful:
self.Stack.setCurrentIndex(1)
else:
self.error_label = QLabel("Please check your email/password!")
self.error_label.setStyleSheet("color: rgb(255, 0, 0);")
self.error_layout.addWidget(self.error_label)
def get_tracks(self):
tracks = playlistcreator.get_favorite_tracks()
print("You have ", len(tracks), " favorite tracks!")
return tracks
# def open_dir_dialog(self):
# directory = QFileDialog.getExistingDirectory(self, 'Select USB Drive Location')
# self.myTextBox.setText(fileName)
def main():
# Create an PyQT5 application object.
app = QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
# # Input Music Directory
# music_dir = input("Please put the path to your music folder: ")
# list_of_paths = playlistcreator.findmp3(music_dir, favorite_tracks)
# # Input playlist file save location
# playlist_path = input("Where would you like to save the playlist?: ")
# playlist_name = input("What would you like to name the playlist? ")
# playlist_name += ".m3u8"
# # Testing appending file extension to string
# playlistcreator.addtoplaylist(list_of_paths, playlist_path, playlist_name)
# playlistcreator.logout()
main()
(playlistcreator.py)
import os
from gmusicapi import Mobileclient
from mutagen.easyid3 import EasyID3
api = Mobileclient()
songCount = 0
favorite_files_path = []
logged_in = False
def login(email, password):
library = []
if "#" not in email:
email += "#gmail.com"
logged_in = api.login(email, password, Mobileclient.FROM_MAC_ADDRESS)
return logged_in
def get_favorite_tracks():
print("Getting songs from Google Music. . .")
library = api.get_all_songs()
print("There are", len(library), "items in your music library")
good_songs = [song for song in library if song['rating'] == '5']
print("You have", len(good_songs), "favorite tracks!")
return good_songs
def logout():
api.logout()
def findmp3(rootFolder, favoriteTracks):
print("Searching for favorite tracks. . .")
for directory, subdirectory, files in os.walk(rootFolder, topdown=False): # for all files in directory
global songCount
global favorite_files_path
for mp3 in files: # for files in
if mp3.endswith(".mp3"): # if file ends with .mp3
file_path = os.path.join(directory, mp3) # concatenate to create full path
try:
file_tags = EasyID3(file_path) # grab file tags from mp3 file
except:
file_tags = EasyID3() # create tags if none are found
file_tags.save(file_path) # save file to have new tags
if "title" in file_tags:
fileTitle = file_tags["title"][0] # TIT2 = corresponding tag in mutagen for song title
else:
fileTitle = "None"
if "artist" in file_tags:
fileArtist = file_tags["artist"][0] # TPE = corresponding tag in mutagen for artist
else:
fileArtist = "None"
if "album" in file_tags:
fileAlbum = file_tags["album"][0] # TALB = corresponding tag in mutagen for album
else:
fileAlbum = "None"
for track in favoriteTracks:
gMusicTitle = track["title"]
gMusicArtist = track["artist"]
gMusicAlbum = track["album"]
if fileTitle in gMusicTitle and fileArtist in gMusicArtist and fileAlbum in gMusicAlbum: # if file tags (artist, album, title) match gMusic tags
songCount += 1
if songCount == 1:
print("Found 1 song")
else:
print("Found", songCount, "songs") # print updated count of found tracks
favorite_files_path.append(file_path) # add all found files to a list (via their paths)
break # break out of "for track in favorite tracks"
return favorite_files_path
def addtoplaylist(paths, playlist_path, playlist_name):
# Open file (or create if it does not exist)
# change to given directory
try:
os.chdir(playlist_path)
except Exception as err:
print(err)
# open file - if it does not exist, create it
with open(playlist_name, 'a+', encoding="UTF-8") as playlistFile:
print("Adding tracks...", end="")
for track in paths:
print(".", end="")
playlistFile.write(track + '\n')
playlistFile.close()
The problem I've been running into is when the user logs in they hit the processingUI window (which states that the program is grabbing all their tracks from Google) but it doesn't actually seem to be running the get_tracks() method (which is what actually gets the user's tracks from Google), as the program hangs there and doesn't do anything.
I've tested the program without a GUI using straight command line, and it works flawlessly, but I'm not sure what the issue is with the GUI - do I need to thread the execution to start when the proper "window" is brought to the front?

How to “correctly” detect application name when changing focus event occurs with python xlib

I want to detect applications window name when changing focus event occurs with python xlib, so in the first step I use this code:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import Xlib.display
import time
display = Xlib.display.Display()
while True:
window = display.get_input_focus().focus
wmname = window.get_wm_name()
wmclass = window.get_wm_class()
if wmclass is None and wmname is None:
window = window.query_tree().parent
wmname = window.get_wm_name()
print "WM Name: %s" % ( wmname, )
time.sleep(3)
But I want a correct way, then I research about xlib events and find Input Focus Events and write this code:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import Xlib.display
from Xlib import X
def main():
display = Xlib.display.Display(':0')
root = display.screen().root
root.change_attributes(event_mask=Xlib.X.FocusChangeMask)
while True:
event = root.display.next_event()
#if event.type == X.FocusIn or event.type == X.FocusOut:
if event.type == X.FocusOut :
window = display.get_input_focus().focus
wmname = window.get_wm_name()
wmclass = window.get_wm_class()
if wmclass is None and wmname is None:
window = window.query_tree().parent
wmname = window.get_wm_name()
print "WM Name: %s" % ( wmname, )
if __name__ == "__main__":
main()
Sadly it's not work correctly especially in tabbed browsing on google chrome and firefox, so Is there a correct way for this situation?
Your code is almost right, but it misses two things:
rather than listening only to focus changes, it should also listen to window property events which include changes of WM_NAME property, that also happen when you cycle tabs in your browser.
rather than listening only in root window, it should listen to every window (that gets focused). You can attach the event handler the same way as you do with the root window.
That being said, here is a working sample:
#!/usr/bin/python3
import Xlib
import Xlib.display
disp = Xlib.display.Display()
root = disp.screen().root
NET_WM_NAME = disp.intern_atom('_NET_WM_NAME')
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
root.change_attributes(event_mask=Xlib.X.FocusChangeMask)
while True:
try:
window_id = root.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
window = disp.create_resource_object('window', window_id)
window.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
window_name = window.get_full_property(NET_WM_NAME, 0).value
except Xlib.error.XError: #simplify dealing with BadWindow
window_name = None
print(window_name)
event = disp.next_event()
#rr- As I just corrected elsewhere, you'll want to query both the current _NET_WM_NAME (UTF-8) and the legacy WM_NAME (non-UTF8) properties or the default xterm configuration will return no title.
I just posted a complete working example over on your Unix & Linux StackExchange question.
To avoid sending people on a cross-reference hunt, here's a copy of the code I posted there:
#!/usr/bin/python
from contextlib import contextmanager
import Xlib
import Xlib.display
disp = Xlib.display.Display()
root = disp.screen().root
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
NET_WM_NAME = disp.intern_atom('_NET_WM_NAME') # UTF-8
WM_NAME = disp.intern_atom('WM_NAME') # Legacy encoding
last_seen = { 'xid': None, 'title': None }
#contextmanager
def window_obj(win_id):
"""Simplify dealing with BadWindow (make it either valid or None)"""
window_obj = None
if win_id:
try:
window_obj = disp.create_resource_object('window', win_id)
except Xlib.error.XError:
pass
yield window_obj
def get_active_window():
win_id = root.get_full_property(NET_ACTIVE_WINDOW,
Xlib.X.AnyPropertyType).value[0]
focus_changed = (win_id != last_seen['xid'])
if focus_changed:
with window_obj(last_seen['xid']) as old_win:
if old_win:
old_win.change_attributes(event_mask=Xlib.X.NoEventMask)
last_seen['xid'] = win_id
with window_obj(win_id) as new_win:
if new_win:
new_win.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
return win_id, focus_changed
def _get_window_name_inner(win_obj):
"""Simplify dealing with _NET_WM_NAME (UTF-8) vs. WM_NAME (legacy)"""
for atom in (NET_WM_NAME, WM_NAME):
try:
window_name = win_obj.get_full_property(atom, 0)
except UnicodeDecodeError: # Apparently a Debian distro package bug
title = "<could not decode characters>"
else:
if window_name:
win_name = window_name.value
if isinstance(win_name, bytes):
# Apparently COMPOUND_TEXT is so arcane that this is how
# tools like xprop deal with receiving it these days
win_name = win_name.decode('latin1', 'replace')
return win_name
else:
title = "<unnamed window>"
return "{} (XID: {})".format(title, win_obj.id)
def get_window_name(win_id):
if not win_id:
last_seen['title'] = "<no window id>"
return last_seen['title']
title_changed = False
with window_obj(win_id) as wobj:
if wobj:
win_title = _get_window_name_inner(wobj)
title_changed = (win_title != last_seen['title'])
last_seen['title'] = win_title
return last_seen['title'], title_changed
def handle_xevent(event):
if event.type != Xlib.X.PropertyNotify:
return
changed = False
if event.atom == NET_ACTIVE_WINDOW:
if get_active_window()[1]:
changed = changed or get_window_name(last_seen['xid'])[1]
elif event.atom in (NET_WM_NAME, WM_NAME):
changed = changed or get_window_name(last_seen['xid'])[1]
if changed:
handle_change(last_seen)
def handle_change(new_state):
"""Replace this with whatever you want to actually do"""
print(new_state)
if __name__ == '__main__':
root.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
get_window_name(get_active_window()[0])
handle_change(last_seen)
while True: # next_event() sleeps until we get an event
handle_xevent(disp.next_event())
There's also a more heavily commented version in this GitHub Gist.

Categories

Resources