|
#!/usr/bin/env python3
|
|
# File: autologin-lego-universe-menu.py
|
|
# Location: blog exclusive
|
|
# Author: bgstack15
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
# Startdate: 2024-11-29-6 13:24
|
|
# Title: Menu for Autologin for Lego Universe
|
|
# Purpose: provide a menu of which account to log in with to DLU
|
|
# History:
|
|
# Usage:
|
|
# no parameters or environment flags. Just run the graphical program.
|
|
# Reference:
|
|
# fprintd_tk
|
|
# https://coderslegacy.com/python/list-of-tkinter-widgets/
|
|
# https://stackoverflow.com/questions/3352918/how-to-center-a-window-on-the-screen-in-tkinter
|
|
# Improve:
|
|
# big idea: icon per player/account entry?
|
|
# Dependencies:
|
|
# dep-devuan: python3-tkstackrpms, python3-screeninfo
|
|
import tkinter as tk, tkinter.simpledialog, os, configparser, json, sys
|
|
from screeninfo import get_monitors
|
|
import tkstackrpms as stk
|
|
|
|
CONF_FILE = os.path.join(os.getenv("HOME"),".config","autologin-lego-universe-menu.conf")
|
|
|
|
ABOUT_TEXT = """
|
|
autologin-lego-universe-menu
|
|
(C) 2024 bgstack15
|
|
SPDX-License-Identifier: GPL-3.0-only
|
|
"""
|
|
|
|
# from so 3352918
|
|
def place_popup(popup: tk.Toplevel, root: tk.Tk, width: int, height: int) -> None:
|
|
"""Places a new window in the middle of the selected screen"""
|
|
monitor = get_monitor_from_coord(root.winfo_x(), root.winfo_y())
|
|
print(f"DEBUG: chose monitor {monitor}, from x {root.winfo_x()}, y {root.winfo_y()}")
|
|
popup.geometry(
|
|
f"{width}x{height}+{(monitor.width - width) // 2 + monitor.x}+{(monitor.height - height) // 2+ monitor.y}")
|
|
def get_monitor_from_coord(x, y):
|
|
"""Find the active monitor from tkinter geometry coords"""
|
|
monitors = get_monitors()
|
|
for m in reversed(monitors):
|
|
print(f"DEBUG: checking monitor {m}")
|
|
if m.x <= x <= m.width + m.x and m.y <= y <= m.height + m.y:
|
|
return m
|
|
return monitors[0]
|
|
|
|
def ferror(*args, **kwargs):
|
|
print(*args, file=sys.stderr, **kwargs)
|
|
|
|
#class Entry():
|
|
# def __init__(self, username = None, password = None):
|
|
# self.username = username
|
|
# self.password = password
|
|
# def d(self):
|
|
# """ to dict """
|
|
# return {"username": self.username, "password": self.password}
|
|
# def __repr__(self):
|
|
# return str(self.d())
|
|
# def __lt__(self,other):
|
|
# return self.username < other.username
|
|
#
|
|
#class EntryEncoder(json.JSONEncoder):
|
|
# def default(self, o):
|
|
# return o.d()
|
|
|
|
def AddEditWindow(title,uservar, pwvar, ok_button_func, cancel_button_func, isEdit = False):
|
|
window = tk.Toplevel()
|
|
window.minsize(100,50)
|
|
window.title(title)
|
|
window.grid()
|
|
tk.Label(window, text="Username",underline=0).grid(row=0,column=0)
|
|
tk.Label(window, text="Password",underline=0).grid(row=1,column=0)
|
|
window.ent_username = stk.Entry(window,textvariable = uservar)
|
|
window.ent_username.grid(row=0,column=1)
|
|
window.ent_password = stk.Entry(window,textvariable = pwvar)
|
|
window.ent_password.grid(row=1,column=1)
|
|
btn_ok = tk.Button(window, text="OK",underline=0,command=ok_button_func).grid(row=0,column=2)
|
|
btn_cancel = tk.Button(window, text="Cancel",underline=0,command=cancel_button_func).grid(row=1,column=2)
|
|
window.bind("<Alt-u>",lambda a=1,b=window.ent_username.focus_set: accelerator_helper_func(a,b))
|
|
window.bind("<Alt-p>",lambda a=1,b=window.ent_password.focus_set: accelerator_helper_func(a,b))
|
|
window.bind("<Alt-o>",lambda a=1,b=ok_button_func: accelerator_helper_func(a,b))
|
|
window.bind("<Alt-c>",lambda a=1,b=cancel_button_func: accelerator_helper_func(a,b))
|
|
window.bind("<Escape>",lambda a=1,b=cancel_button_func: accelerator_helper_func(a,b))
|
|
window.ent_username.bind("<Return>",lambda a=1,b=window.ent_password.focus_set: accelerator_helper_func(a,b))
|
|
window.ent_password.bind("<Return>",lambda a=1,b=ok_button_func: accelerator_helper_func(a,b))
|
|
if isEdit:
|
|
window.ent_username.config(state="disabled")
|
|
window.ent_password.focus_set()
|
|
else:
|
|
window.ent_username.focus_set()
|
|
return window
|
|
|
|
def accelerator_helper_func(o1 = None, button_func = None, o3 = None):
|
|
"""
|
|
This belongs in tkstackrpms, to facilitate a window.bind("<Alt-u>",button_func) to call button_func.
|
|
There is a limitation where what I wanted to call was a built-in and didn't accept more than one parameter.
|
|
Parameter 1 always seems to be discarded or otherwise useless.
|
|
"""
|
|
print(f"DEBUG: got accelerator o1 = {o1}, button_func = {button_func}, o3 = {o3}")
|
|
# o2 will be the function to call
|
|
try:
|
|
button_func()
|
|
except Exception as e:
|
|
ferror(f"ERROR: while using accelerator helper func, got error {e}")
|
|
|
|
class App(tk.Frame):
|
|
def __init__(self, master):
|
|
super().__init__(master)
|
|
self.master.title("Autologin for Lego Universe")
|
|
imgicon = stk.get_scaled_icon("/mnt/public/Games/lego-universe/lego-universe-16x16.png",16,"default","","apps")
|
|
self.master.tk.call("wm","iconphoto",self.master._w,imgicon)
|
|
#place_popup(self.master,root,300,300)
|
|
self.grid()
|
|
|
|
# variables
|
|
self.entries = []
|
|
self.addedit_username = tk.StringVar()
|
|
self.addedit_password = tk.StringVar()
|
|
|
|
self.listbox = tk.Listbox(master)
|
|
#listbox.insert(0,"hello world")
|
|
#listbox.insert(0,"charlie")
|
|
self.listbox.grid(column=0,row=0,columnspan=4,rowspan=6)
|
|
self.listbox.bind('<Double-1>', self.func_login)
|
|
tk.Button(self.master,text="Login",underline=0,command=self.func_login).grid(column=0,row=7)
|
|
tk.Button(self.master,text="Add",underline=0,command=self.func_add).grid(column=1,row=7)
|
|
tk.Button(self.master,text="Edit",underline=0,command=self.func_edit).grid(column=2,row=7)
|
|
tk.Button(self.master,text="Remove",underline=0,command=self.func_remove).grid(column=3,row=7)
|
|
tk.Button(self.master,text="About",underline=1,command=self.func_about).grid(column=0,row=8)
|
|
self.master.bind("<Alt-l>",self.func_login)
|
|
self.master.bind("<Alt-a>",self.func_add)
|
|
self.master.bind("<Alt-e>",self.func_edit)
|
|
self.master.bind("<Alt-r>",self.func_remove)
|
|
self.master.bind("<Alt-b>",self.func_about)
|
|
# initial load
|
|
self.refresh_form()
|
|
self.listbox.select_set(0)
|
|
|
|
# functions
|
|
def func_about(self, o1 = None):
|
|
""" Display about dialog. """
|
|
tk.messagebox.Message(title="About",message=ABOUT_TEXT,icon="info").show()
|
|
|
|
def func_login(self, o1 = None):
|
|
""" Get currently-selected entry in listbox, and enter that info to autologin-lego-universe.sh """
|
|
print(f"DEBUG: login stub, got o1 {o1}")
|
|
entry = self.get_selected_user()
|
|
command = f""". /mnt/public/Games/lego-universe/autologin-lego-universe.sh ; USERNAME="{entry['username']}" PASSWORD="{entry['password']}" autologin_lego_universe"""
|
|
print(f"Running command {command}")
|
|
self.master.wm_state("iconic")
|
|
os.system(command)
|
|
sys.exit(0)
|
|
|
|
def func_add(self, o1 = None):
|
|
""" Ask for a new login to save to list. """
|
|
print(f"DEBUG: stub func_add, got o1 {o1}")
|
|
# try closing any open add/edit window
|
|
self.close_addwindow()
|
|
self.addedit_username.set("")
|
|
self.addedit_password.set("")
|
|
self.addwindow = AddEditWindow("Add entry",self.addedit_username, self.addedit_password, self.update_entry, self.close_addwindow, False)
|
|
|
|
def func_edit(self, o1 = None):
|
|
""" Ask for a new login to save to list. """
|
|
print(f"DEBUG: stub func_edit, got o1 {o1}")
|
|
print(f"DEBUG: need to get currently selected entry")
|
|
# try closing any open add/edit window
|
|
self.close_addwindow()
|
|
entry = self.get_selected_user()
|
|
if entry["username"] is None or entry["username"] == "None":
|
|
return False
|
|
self.addedit_username.set(entry["username"])
|
|
self.addedit_password.set(entry["password"])
|
|
self.addwindow = AddEditWindow("Edit entry",self.addedit_username, self.addedit_password, self.update_entry, self.close_addwindow, True)
|
|
|
|
def get_selected_user(self):
|
|
""" Return the dict of the currently-selected user, else return empty dict """
|
|
output = {}
|
|
# a single-selection listbox
|
|
username = None
|
|
password = None
|
|
try:
|
|
username = self.listbox.get(self.listbox.curselection())
|
|
try:
|
|
password = [i["password"] for i in self.entries if i["username"] == username][0]
|
|
except Exception as e:
|
|
print(f"FAILURE when looking for {username} in entries, got {e}")
|
|
except tkinter.TclError as e:
|
|
# no entry selected
|
|
pass
|
|
return {"username":username,"password":password}
|
|
|
|
def close_addwindow(self, o1 = None):
|
|
try:
|
|
self.addwindow.destroy()
|
|
except Exception as e:
|
|
ferror(f"DEBUG: when trying to hide add/edit window, got {e}")
|
|
|
|
def func_remove(self, o1 = None):
|
|
""" Ask for a new login to save to list. """
|
|
print(f"DEBUG: stub func_remove, got o1 {o1}")
|
|
entry = self.get_selected_user()
|
|
print(f"DEBUG: got entry {entry}")
|
|
try:
|
|
u = entry["username"]
|
|
if u:
|
|
try:
|
|
self.entries.remove(entry)
|
|
except ValueError as e:
|
|
# the dict item was not in the list
|
|
ferror(f"Probably just not in the list, but got error {e}. This is probably not a big deal.")
|
|
pass
|
|
except Exception as e:
|
|
ferror(f"When trying to remove, got error {e}")
|
|
self.save_config()
|
|
|
|
def refresh_form(self):
|
|
""" Load all data """
|
|
self.entries = self.load_config()
|
|
self.listbox.delete(0,tk.END)
|
|
x = 0
|
|
print(f"DEBUG: refreshing form, from entries {self.entries}")
|
|
for e in self.entries:
|
|
print(f"Looping over {e}")
|
|
self.listbox.insert(x, e["username"])
|
|
x = x + 1
|
|
|
|
def load_config(self):
|
|
""" Read ~/.config/autologin-lego-universe-menu.conf and get all username/pw listed there """
|
|
print(f"DEBUG: loading config")
|
|
try:
|
|
with open(CONF_FILE,"r") as o:
|
|
entries = json.load(o)
|
|
except FileNotFoundError:
|
|
print(f"DEBUG: need to generate new config file")
|
|
with open(CONF_FILE,"a") as o:
|
|
o.write("[]")
|
|
entries = []
|
|
#print(f"DEBUG, got entries in {type(entries)} below:")
|
|
#print(entries)
|
|
return entries
|
|
|
|
def update_entry(self, o1 = None):
|
|
""" Take add/edit window information and find the corresponding entry and update it, or else add a new one. """
|
|
#print(f"DEBUG: will look for entry {self.addedit_username.get()}, and assign pw {self.addedit_password.get()}")
|
|
new_u = self.addedit_username.get()
|
|
new_p = self.addedit_password.get()
|
|
updated = False
|
|
for e in self.entries:
|
|
if e["username"] == new_u:
|
|
print(f"DEBUG: found entry {e}, updating pw for it.")
|
|
e["password"] = new_p
|
|
updated = True
|
|
break
|
|
if not updated:
|
|
print(f"DEBUG: adding new entry, u={new_u},p={new_p}")
|
|
self.entries.append(
|
|
{
|
|
"username": new_u,
|
|
"password": new_p,
|
|
}
|
|
)
|
|
self.save_config()
|
|
|
|
def save_config(self, o1 = None):
|
|
""" Save config, including the latest entry from add/edit window """
|
|
self.close_addwindow()
|
|
print(f"DEBUG: please save entries. Currently:")
|
|
print(self.entries)
|
|
#print(f"And also {self.addedit_username.get()}, {self.addedit_password.get()}")
|
|
with open(CONF_FILE,"w") as o:
|
|
o.write(json.dumps(self.entries))
|
|
self.refresh_form()
|
|
|
|
# main
|
|
if "__main__" == __name__:
|
|
root = tk.Tk()
|
|
# naive, because it combines all monitors into one giant display for calculating.
|
|
# FUTUREIMPROVMENT: get placement working
|
|
root.eval('tk::PlaceWindow . center')
|
|
alu = App(root)
|
|
# Interesting, but not correct. It always chooses monitor 0 but the window would popup on monitor 1 if we do nothing special about geometry
|
|
#print(f"DEBUG: screen width is {root.winfo_screenwidth()}")
|
|
#print(f"DEBUG: screen height is {root.winfo_screenheight()}")
|
|
#place_popup(alu.master,root,300,300)
|
|
alu.mainloop()
|