Files
q-tools/shell/tk-zenity
2025-01-06 14:30:23 +02:00

190 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import sys
import time
import tkinter as tk
import tkinter.messagebox
import tkinter.simpledialog
import tkinter.ttk
from threading import Thread
class StdinBox(tk.Toplevel):
def __init__(self, title="", message="", geometry="800x80"):
tk.Toplevel.__init__(self)
self.geometry(geometry)
self.title(title)
self.exit = False
self.my_message = message
self.my_title = title
self.messageLabel = tk.Label(self, text=message, bg="#222", fg="#fff", font="TkFixedFont")
self.messageLabel.pack(expand=True, fill=tk.BOTH)
daemon = Thread(target=self.stream_stdin)
daemon.start()
self.update_timer()
self.mainloop()
def update_message(self, message):
self.messageLabel.configure(text=message)
def update_title(self, title):
self.title(title)
def update_timer(self):
if self.exit:
self.quit()
return
self.messageLabel.configure(text=self.my_message)
self.update()
self.after(333, self.update_timer)
def stream_stdin(self):
with open(sys.stdin.fileno(), mode="rt", encoding="utf-8", newline=None, buffering=1, closefd=True) as fp:
for line in fp:
self.my_message = line.rstrip("\r\n")
self.exit = True
class ListBox(tk.Toplevel):
def __init__(self, title="", message="", geometry="800x800"):
tk.Toplevel.__init__(self)
self.geometry(geometry) # Must change these accordingly
self.configure(background="#222")
self.title(title)
self.messageLabel = tk.Label(self, text=message, bg="#222", fg="#fff", font="TkFixedFont")
self.choose = tk.Listbox(self, bg="#222", fg="#fff", font="TkFixedFont", selectmode="browse")
with open(sys.stdin.fileno(), mode="rt", encoding="utf-8", newline=None, buffering=1, closefd=True) as fp:
for i, line in enumerate(fp):
self.choose.insert(i, line.rstrip("\r\n"))
self.last_element = i
self.choose.select_set(0)
self.labelframe = tk.Frame(self, bg="#222")
self.cancel_b = tk.Button(self.labelframe, text="Cancel", command=self.quit)
self.ok_b = tk.Button(self.labelframe, text="OK", command=self.selected)
self.messageLabel.pack() # expand=True, fill=tk.BOTH)
self.choose.pack(expand=True, fill=tk.BOTH)
self.cancel_b.pack(side="left")
self.ok_b.pack(side="right")
self.labelframe.pack()
self.choose.focus_set()
self.choose.bind("<Return>", self.selected)
self.choose.bind("<Double-Button-1>", self.selected)
self.choose.bind("<Key>", self.key_handler)
self.ok_b.bind("<Return>", self.selected)
self.cancel_b.bind("<Return>", self.handle_exit)
self.bind_all("<Control-c>", self.handle_exit)
self.bind_all("<Escape>", self.handle_exit)
self.mainloop()
def set_position(self, index):
index = max(0, min(self.last_element, index))
self.choose.selection_clear(0, tk.END)
self.choose.activate(index)
self.choose.selection_set(index)
def key_handler(self, event):
if len(event.char) == 1 and event.char.isalnum():
# ~ print(event.char, event.keysym, event.keycode)
for i in list(range(self.choose.curselection()[0] + 1, self.last_element + 1)) + list(
range(0, self.choose.curselection()[0] + 1)
):
if self.choose.get(i).lower().startswith(event.char):
self.set_position(i)
return
if event.keysym == "Up":
self.set_position(self.choose.curselection()[0])
if event.keysym == "Down":
self.set_position(self.choose.curselection()[0])
if event.keysym == "Next":
self.set_position(self.choose.curselection()[0] + 10)
if event.keysym == "Prior":
self.set_position(self.choose.curselection()[0] - 10)
if event.keysym == "Home":
self.set_position(0)
if event.keysym == "End":
self.set_position(self.last_element)
def selected(self, *args):
for i in self.choose.curselection():
print(self.choose.get(i), end="")
self.quit()
def handle_exit(self, *args):
self.quit()
def get_opts():
parser = argparse.ArgumentParser()
parser.add_argument(
"--title",
action="store",
default=None,
help="Window title",
)
parser.add_argument("--text", action="store", default=None, help="Description text", nargs="?")
parser.add_argument(
"--geometry",
action="store",
default=None,
help="Geometry (not valid for all commands), ex. 800x200+50+50",
nargs="?",
)
parser.add_argument(
"mode",
action="store",
default="info",
const="info",
nargs="?",
choices=["info", "password", "entry", "stream", "list", "question"],
help="operation mode. Note: stream mode expects stdin to update text and line starting with #.",
)
parsed = parser.parse_args()
return parsed
def set_defaults(opts, title, text, geometry=None):
if opts.text is None:
opts.text = text
if opts.title is None:
opts.title = title
if opts.geometry is None:
opts.geometry = geometry
return opts
def main():
opts = get_opts()
tk.Tk().withdraw()
if opts.mode == "password":
opts = set_defaults(opts, "Password", "Type password:")
pw = tkinter.simpledialog.askstring(opts.title, opts.text, show="*")
print(pw, end="")
if opts.mode == "info":
opts = set_defaults(opts, "Info", "Message")
tkinter.messagebox.showinfo(title=opts.title, message=opts.text)
if opts.mode == "entry":
opts = set_defaults(opts, "Entry", "Type message:")
msg = tkinter.simpledialog.askstring(title=opts.title, prompt=opts.text)
print(msg, end="")
if opts.mode == "stream":
opts = set_defaults(opts, "Info", "", "800x80-50-50")
StdinBox(title=opts.title, message=opts.text, geometry=opts.geometry)
if opts.mode == "list":
opts = set_defaults(opts, "Select", "Select from list:")
ListBox(title=opts.title, message=opts.text)
if opts.mode == "question":
opts = set_defaults(opts, "Entry", "Question")
msg = tkinter.messagebox.askokcancel(title=opts.title, message=opts.text)
print(msg, end="")
if __name__ == "__main__":
main()