#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import threading from time import sleep try: # Python 2.x imports import Tkinter as tk import ttk import tkMessageBox as messagebox import tkFileDialog as filedialog import tkFont except ImportError: # Python 3.x imports import tkinter as tk from tkinter import ttk from tkinter import messagebox from tkinter import filedialog import tkinter.font as tkFont from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, ListboxOfRadiobuttons from pykms_GuiMisc import custom_background, custom_pages from pykms_Client import clt_options, clt_version, clt_config, client_thread gui_version = "py-kms_gui_v3.0" __license__ = "MIT License" __author__ = u"Matteo ℱan <SystemRage@protonmail.com>" __copyright__ = "© Copyright 2020" __url__ = "https://github.com/SystemRage/py-kms" gui_description = "A GUI for py-kms." ##--------------------------------------------------------------------------------------------------------------------------------------------------------- def get_ip_address(): if os.name == 'posix': try: # Python 2.x import import commands except ImportError: #Python 3.x import import subprocess as commands ip = commands.getoutput("hostname -I") elif os.name == 'nt': import socket ip = socket.gethostbyname(socket.gethostname()) else: ip = 'Unknown' return ip def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"): global txsrv, txclt, txcol if redirect_conditio: if stream == 'stdout': sys.stdout = redirect_to(txsrv, txclt, txcol) elif stream == 'stderr': sys.stderr = redirect_to(txsrv, txclt, txcol, stderr_side) else: stream = redirect_to(txsrv, txclt, txcol) return stream def gui_redirector_setup(): TextRedirect.Pretty.tag_num = 0 TextRedirect.Pretty.newlinecut = [-1, -2, -4, -5] def gui_redirector_clear(): global txsrv, oysrv try: if oysrv: txsrv.configure(state = 'normal') txsrv.delete('1.0', 'end') txsrv.configure(state = 'disabled') except: # self.onlysrv not defined (menu not used) pass ##----------------------------------------------------------------------------------------------------------------------------------------------------------- class KmsGui(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.wraplength = 200 serverthread.with_gui = True self.validation_int = (self.register(self.validate_int), "%S") self.validation_float = (self.register(self.validate_float), "%P") ## Define fonts and colors. self.btnwinfont = tkFont.Font(family = 'Times', size = 12, weight = 'bold') self.othfont = tkFont.Font(family = 'Times', size = 9, weight = 'bold') self.optfont = tkFont.Font(family = 'Helvetica', size = 11, weight = 'bold') self.optfontredux = tkFont.Font(family = 'Helvetica', size = 9, weight = 'bold') self.msgfont = tkFont.Font(family = 'Monospace', size = 6) # need a monospaced type (like courier, etc..). self.customcolors = { 'black' : '#000000', 'white' : '#FFFFFF', 'green' : '#90EE90', 'yellow' : '#FFFF00', 'magenta' : '#DA70D6', 'orange' : '#FFA500', 'red' : '#FF4500', 'blue' : '#1E90FF', 'cyan' : '#AFEEEE', 'lavender': '#E6E6FA', } self.option_add('*TCombobox*Listbox.font', self.optfontredux) self.gui_create() def browse(self, entrywidget, options): path = filedialog.askdirectory() if os.path.isdir(path): entrywidget.delete('0', 'end') entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def'])) def invert(self, widgets = []): for widget in widgets: if widget['state'] == 'normal': widget.configure(state = 'disabled') elif widget['state'] == 'disabled': widget.configure(state = 'normal') def gui_menu(self): self.onlysrv, self.onlyclt = (False for _ in range(2)) menubar = tk.Menu(self) prefmenu = tk.Menu(menubar, tearoff = 0, font = ("Noto Sans Regular", 10), borderwidth = 3, relief = 'ridge') menubar.add_cascade(label = 'Preferences', menu = prefmenu) prefmenu.add_command(label = 'Enable server-side mode', command = lambda: self.pref_onlysrv(prefmenu)) prefmenu.add_command(label = 'Enable client-side mode', command = lambda: self.pref_onlyclt(prefmenu)) self.config(menu = menubar) def pref_onlysrv(self, menu): global oysrv if self.onlyclt or serverthread.is_running_server: return self.onlysrv = not self.onlysrv if self.onlysrv: menu.entryconfigure(0, label = 'Disable server-side mode') self.clt_on_show(force_remove = True) else: menu.entryconfigure(0, label = 'Enable server-side mode') self.invert(widgets = [self.shbtnclt]) oysrv = self.onlysrv def pref_onlyclt(self, menu): if self.onlysrv or serverthread.is_running_server: return self.onlyclt = not self.onlyclt if self.onlyclt: menu.entryconfigure(1, label = 'Disable client-side mode') if self.shbtnclt['text'] == 'SHOW\nCLIENT': self.clt_on_show(force_view = True) self.optsrvwin.grid_remove() self.msgsrvwin.grid_remove() gui_redirector('stderr', redirect_to = TextRedirect.Stderr, stderr_side = "clt") else: menu.entryconfigure(1, label = 'Enable client-side mode') self.optsrvwin.grid() self.msgsrvwin.grid() gui_redirector('stderr', redirect_to = TextRedirect.Stderr) self.invert(widgets = [self.runbtnsrv, self.shbtnclt, self.runbtnclt]) def gui_create(self): ## Create server gui self.gui_srv() ## Create client gui + other operations. self.gui_complete() ## Create menu. self.gui_menu() ## Create globals for printing process (redirect stdout). global txsrv, txclt, txcol txsrv = self.textboxsrv.get() txclt = self.textboxclt.get() txcol = self.customcolors ## Redirect stderr. gui_redirector('stderr', redirect_to = TextRedirect.Stderr) def gui_pages_show(self, pagename, side): # https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter # https://www.reddit.com/r/learnpython/comments/7xxtsy/trying_to_understand_tkinter_and_how_to_switch/ pageside = self.pagewidgets[side] tk.Misc.lift(pageside["PageWin"][pagename], aboveThis = None) keylist = list(pageside["PageWin"].keys()) for elem in [pageside["BtnAni"], pageside["LblAni"]]: if pagename == "PageStart": elem["Left"].config(state = "disabled") if len(keylist) == 2: elem["Right"].config(state = "normal") elif pagename == "PageEnd": elem["Right"].config(state = "disabled") if len(keylist) == 2: elem["Left"].config(state = "normal") else: for where in ["Left", "Right"]: elem[where].config(state = "normal") if pagename != "PageStart": page_l = keylist[keylist.index(pagename) - 1] pageside["BtnAni"]["Left"]['command'] = lambda pag=page_l, pos=side: self.gui_pages_show(pag, pos) if pagename != "PageEnd": page_r = keylist[keylist.index(pagename) + 1] pageside["BtnAni"]["Right"]['command'] = lambda pag=page_r, pos=side: self.gui_pages_show(pag, pos) def gui_pages_buttons(self, parent, side): btnwin = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') btnwin.grid(row = 14, column = 2, padx = 2, pady = 2, sticky = 'nsew') btnwin.grid_columnconfigure(1, weight = 1) self.pagewidgets[side]["BtnWin"] = btnwin for position in ["Left", "Right"]: if position == "Left": col = [0, 0, 1] stick = 'e' elif position == "Right": col = [2, 1, 0] stick = 'w' aniwin = tk.Canvas(btnwin, background = self.customcolors['white'], borderwidth = 0, relief = 'ridge') aniwin.grid(row = 0, column = col[0], padx = 5, pady = 5, sticky = 'nsew') self.pagewidgets[side]["AniWin"][position] = aniwin lblani = tk.Label(aniwin, width = 1, height = 1) lblani.grid(row = 0, column = col[1], padx = 2, pady = 2, sticky = stick) self.pagewidgets[side]["LblAni"][position] = lblani btnani = tk.Button(aniwin) btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick) self.pagewidgets[side]["BtnAni"][position] = btnani ## Customize buttons. custom_pages(self, side) def gui_pages_create(self, parent, side, create = {}): self.pagewidgets.update({side : {"PageWin" : create, "BtnWin" : None, "BtnAni" : {"Left" : None, "Right" : None}, "AniWin" : {"Left" : None, "Right" : None}, "LblAni" : {"Left" : None, "Right" : None}, } }) for pagename in self.pagewidgets[side]["PageWin"].keys(): page = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.pagewidgets[side]["PageWin"][pagename] = page page.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = "nsew") page.grid_columnconfigure(1, weight = 1) self.gui_pages_buttons(parent = parent, side = side) self.gui_pages_show("PageStart", side = side) def gui_store(self, side, typewidgets): stored = [] for pagename in self.pagewidgets[side]["PageWin"].keys(): for widget in self.pagewidgets[side]["PageWin"][pagename].winfo_children(): if widget.winfo_class() in typewidgets: stored.append(widget) return stored def gui_srv(self): ## Create main containers. ------------------------------------------------------------------------------------------------------------------ self.masterwin = tk.Canvas(self, borderwidth = 3, relief = tk.RIDGE) self.btnsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.optsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.msgsrvwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) ## Layout main containers. self.masterwin.grid(row = 0, column = 0, sticky = 'nsew') self.btnsrvwin.grid(row = 0, column = 1, padx = 2, pady = 2, sticky = 'nw') self.optsrvwin.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = 'nsew') self.optsrvwin.grid_rowconfigure(0, weight = 1) self.optsrvwin.grid_columnconfigure(1, weight = 1) self.pagewidgets = {} ## Subpages of "optsrvwin". self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None, "PageEnd": None}) ## Continue to grid. self.msgsrvwin.grid(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew') self.msgsrvwin.grid_propagate(False) self.msgsrvwin.grid_columnconfigure(0, weight = 1) self.msgsrvwin.grid_rowconfigure(0, weight = 1) ## Create widgets (btnsrvwin) --------------------------------------------------------------------------------------------------------------- self.statesrv = tk.Label(self.btnsrvwin, text = 'Server\nState:\nStopped', font = self.othfont, foreground = self.customcolors['red']) self.runbtnsrv = tk.Button(self.btnsrvwin, text = 'START\nSERVER', background = self.customcolors['green'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.srv_on_start) self.shbtnclt = tk.Button(self.btnsrvwin, text = 'SHOW\nCLIENT', background = self.customcolors['magenta'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.clt_on_show) self.clearbtnsrv = tk.Button(self.btnsrvwin, text = 'CLEAR', background = self.customcolors['orange'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = lambda: self.on_clear([txsrv, txclt])) self.exitbtnsrv = tk.Button(self.btnsrvwin, text = 'EXIT', background = self.customcolors['black'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.on_exit) ## Layout widgets (btnsrvwin) self.statesrv.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') self.runbtnsrv.grid(row = 1, column = 0, padx = 2, pady = 2, sticky = 'ew') self.shbtnclt.grid(row = 2, column = 0, padx = 2, pady = 2, sticky = 'ew') self.clearbtnsrv.grid(row = 3, column = 0, padx = 2, pady = 2, sticky = 'ew') self.exitbtnsrv.grid(row = 4, column = 0, padx = 2, pady = 2, sticky = 'ew') ## Create widgets (optsrvwin:Srv:PageWin:PageStart) ----------------------------------------------------------------------------------------- # Version. ver = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'You are running server version: ' + srv_version, foreground = self.customcolors['red'], font = self.othfont) # Ip Address. srvipaddlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.optfont) self.srvipadd = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.srvipadd.insert('end', srv_options['ip']['def']) ToolTip(self.srvipadd, text = srv_options['ip']['help'], wraplength = self.wraplength) myipadd = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Your IP address is: {}'.format(get_ip_address()), foreground = self.customcolors['red'], font = self.othfont) # Port. srvportlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Port: ', font = self.optfont) self.srvport = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.srvport.insert('end', str(srv_options['port']['def'])) ToolTip(self.srvport, text = srv_options['port']['help'], wraplength = self.wraplength) # EPID. epidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'EPID: ', font = self.optfont) self.epid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.epid.insert('end', str(srv_options['epid']['def'])) ToolTip(self.epid, text = srv_options['epid']['help'], wraplength = self.wraplength) # LCID. lcidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'LCID: ', font = self.optfont) self.lcid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.lcid.insert('end', str(srv_options['lcid']['def'])) ToolTip(self.lcid, text = srv_options['lcid']['help'], wraplength = self.wraplength) # HWID. hwidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'HWID: ', font = self.optfont) self.hwid = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = (str(srv_options['hwid']['def']), 'RANDOM'), width = 17, height = 10, font = self.optfontredux) self.hwid.set(str(srv_options['hwid']['def'])) ToolTip(self.hwid, text = srv_options['hwid']['help'], wraplength = self.wraplength) # Client Count countlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Client Count: ', font = self.optfont) self.count = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.count.insert('end', str(srv_options['count']['def'])) ToolTip(self.count, text = srv_options['count']['help'], wraplength = self.wraplength) # Activation Interval. activlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Activation Interval: ', font = self.optfont) self.activ = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.activ.insert('end', str(srv_options['activation']['def'])) ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength) # Renewal Interval. renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Renewal Interval: ', font = self.optfont) self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.renew.insert('end', str(srv_options['renewal']['def'])) ToolTip(self.renew, text = srv_options['renewal']['help'], wraplength = self.wraplength) # Logfile. srvfilelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.optfont) self.srvfile = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.srvfile.insert('end', srv_options['lfile']['def']) self.srvfile.xview_moveto(1) ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength) srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', command = lambda: self.browse(self.srvfile, srv_options)) # Loglevel. srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.optfont) self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']), width = 10, height = 10, font = self.optfontredux, state = "readonly") self.srvlevel.set(srv_options['llevel']['def']) ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength) # Logsize. srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_float) self.srvsize.insert('end', srv_options['lsize']['def']) ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength) # Asynchronous messages. self.chkvalsrvasy = tk.BooleanVar() self.chkvalsrvasy.set(srv_options['asyncmsg']['def']) chksrvasy = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Async\nMsg', font = self.optfontredux, var = self.chkvalsrvasy, relief = 'groove') ToolTip(chksrvasy, text = srv_options['asyncmsg']['help'], wraplength = self.wraplength) # Listbox radiobuttons server. self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"], ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], self.optfontredux, changed = [(self.srvfile, srv_options['lfile']['def']), (srvfilebtnwin, ''), (self.srvsize, srv_options['lsize']['def']), (self.srvlevel, srv_options['llevel']['def'])], width = 10, height = 1, borderwidth = 2, relief = 'ridge') ## Layout widgets (optsrvwin:Srv:PageWin:PageStart) ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') srvipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') myipadd.grid(row = 2, column = 1, columnspan = 2, padx = 5, pady = 5, sticky = 'ew') srvportlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvport.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew') epidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e') self.epid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew') lcidlbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e') self.lcid.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew') hwidlbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e') self.hwid.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew') countlbl.grid(row = 7, column = 0, padx = 5, pady = 5, sticky = 'e') self.count.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew') activlbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e') self.activ.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew') renewlbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e') self.renew.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew') srvfilelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew') srvfilebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew') self.chksrvfile.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew') chksrvasy.grid(row = 11, column = 2, padx = 5, pady = 5, sticky = 'ew') srvlevellbl.grid(row = 12, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvlevel.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew') srvsizelbl.grid(row = 13, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvsize.grid(row = 13, column = 1, padx = 5, pady = 5, sticky = 'ew') ## Create widgets (optsrvwin:Srv:PageWin:PageEnd)------------------------------------------------------------------------------------------- # Timeout connection. timeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) self.timeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) self.timeout0.insert('end', str(srv_options['time0']['def'])) ToolTip(self.timeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) # Sqlite database. self.chkvalsql = tk.BooleanVar() self.chkvalsql.set(srv_options['sql']['def']) chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase', font = self.optfontredux, var = self.chkvalsql, relief = 'groove') ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength) ## Layout widgets (optsrvwin:Srv:PageWin:PageEnd) # a label for vertical aligning with PageStart tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0, height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') timeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') self.timeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') chksql.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') # Store server-side widgets. self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) self.storewidgets_srv.append(self.chksrvfile) ## Create widgets and layout (msgsrvwin) --------------------------------------------------------------------------------------------------- self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled', relief = 'ridge', font = self.msgfont) self.textboxsrv.put() def always_centered(self, geo, centered, refs): x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2) y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2) w, h, dx, dy = geo.split('+')[0].split('x') + geo.split('+')[1:] if w == refs[1]: if centered: self.geometry('+%d+%d' %(x, y)) centered = False elif w == refs[0]: if not centered: self.geometry('+%d+%d' %(x, y)) centered = True if dx != str(x) or dy != str(y): self.geometry('+%d+%d' %(x, 0)) self.after(200, self.always_centered, self.geometry(), centered, refs) def gui_complete(self): ## Create client widgets (optcltwin, msgcltwin, btncltwin) self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin. minw, minh = self.winfo_width(), self.winfo_height() self.iconify() self.gui_clt() maxw, minh = self.winfo_width(), self.winfo_height() ## Main window custom background. self.update_idletasks() # update Gui for custom background self.iconify() custom_background(self) ## Main window other modifications. self.eval('tk::PlaceWindow %s center' %self.winfo_pathname(self.winfo_id())) self.wm_attributes("-topmost", True) self.protocol("WM_DELETE_WINDOW", lambda: 0) ## Disable maximize button. self.resizable(False, False) ## Centered window. self.always_centered(self.geometry(), False, [minw, maxw]) def get_position(self, widget): x, y = (widget.winfo_x(), widget.winfo_y()) w, h = (widget.winfo_width(), widget.winfo_height()) return x, y, w, h def gui_clt(self): self.count_clear = 0 self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') xb, yb, wb, hb = self.get_position(self.btnsrvwin) self.btncltwin_X = xb + 2 self.btncltwin_Y = yb + hb + 10 self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw') self.optcltwin.grid(row = 0, column = 4, padx = 2, pady = 2, sticky = 'nsew') self.optcltwin.grid_rowconfigure(0, weight = 1) self.optcltwin.grid_columnconfigure(1, weight = 1) ## Subpages of "optcltwin". self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None, "PageEnd": None}) ## Continue to grid. self.msgcltwin.grid(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew') self.msgcltwin.grid_propagate(False) self.msgcltwin.grid_columnconfigure(0, weight = 1) self.msgcltwin.grid_rowconfigure(0, weight = 1) ## Create widgets (btncltwin) ---------------------------------------------------------------------------------------------------------------- self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, state = 'disabled', command = self.clt_on_start) ## Layout widgets (btncltwin) self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') ## Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------ # Version. cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version, foreground = self.customcolors['red'], font = self.othfont) # Ip Address. cltipaddlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.optfont) self.cltipadd = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.cltipadd.insert('end', clt_options['ip']['def']) ToolTip(self.cltipadd, text = clt_options['ip']['help'], wraplength = self.wraplength) # Port. cltportlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Port: ', font = self.optfont) self.cltport = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.cltport.insert('end', str(clt_options['port']['def'])) ToolTip(self.cltport, text = clt_options['port']['help'], wraplength = self.wraplength) # Mode. cltmodelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Mode: ', font = self.optfont) self.cltmode = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['mode']['choi']), width = 17, height = 10, font = self.optfontredux, state = "readonly") self.cltmode.set(clt_options['mode']['def']) ToolTip(self.cltmode, text = clt_options['mode']['help'], wraplength = self.wraplength) # CMID. cltcmidlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'CMID: ', font = self.optfont) self.cltcmid = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.cltcmid.insert('end', str(clt_options['cmid']['def'])) ToolTip(self.cltcmid, text = clt_options['cmid']['help'], wraplength = self.wraplength) # Machine Name. cltnamelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Machine Name: ', font = self.optfont) self.cltname = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.cltname.insert('end', str(clt_options['name']['def'])) ToolTip(self.cltname, text = clt_options['name']['help'], wraplength = self.wraplength) # Logfile. cltfilelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.optfont) self.cltfile = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont) self.cltfile.insert('end', clt_options['lfile']['def']) self.cltfile.xview_moveto(1) ToolTip(self.cltfile, text = clt_options['lfile']['help'], wraplength = self.wraplength) cltfilebtnwin = tk.Button(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Browse', command = lambda: self.browse(self.cltfile, clt_options)) # Loglevel. cltlevellbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.optfont) self.cltlevel = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['llevel']['choi']), width = 10, height = 10, font = self.optfontredux, state = "readonly") self.cltlevel.set(clt_options['llevel']['def']) ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength) # Logsize. cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_float) self.cltsize.insert('end', clt_options['lsize']['def']) ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength) # Asynchronous messages. self.chkvalcltasy = tk.BooleanVar() self.chkvalcltasy.set(clt_options['asyncmsg']['def']) chkcltasy = tk.Checkbutton(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Async\nMsg', font = self.optfontredux, var = self.chkvalcltasy, relief = 'groove') ToolTip(chkcltasy, text = clt_options['asyncmsg']['help'], wraplength = self.wraplength) # Listbox radiobuttons client. self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"], ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], self.optfontredux, changed = [(self.cltfile, clt_options['lfile']['def']), (cltfilebtnwin, ''), (self.cltsize, clt_options['lsize']['def']), (self.cltlevel, clt_options['llevel']['def'])], width = 10, height = 1, borderwidth = 2, relief = 'ridge') ## Layout widgets (optcltwin:Clt:PageWin:PageStart) cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') cltipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') cltportlbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltport.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew') cltmodelbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltmode.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew') cltcmidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltcmid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew') cltnamelbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltname.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew') cltfilelbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltfile.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew') cltfilebtnwin.grid(row = 6, column = 2, padx = 5, pady = 5, sticky = 'ew') self.chkcltfile.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew') chkcltasy.grid(row = 7, column = 2, padx = 5, pady = 5, sticky = 'ew') cltlevellbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltlevel.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew') cltsizelbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltsize.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew') # ugly fix when client-side mode is activated. templbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], bg = self.customcolors['lavender']).grid(row = 10, column = 0, padx = 35, pady = 54, sticky = 'e') ## Create widgets (optcltwin:Clt:PageWin:PageEnd) ------------------------------------------------------------------------------------------- ## Layout widgets (optcltwin:Clt:PageWin:PageEnd) # a label for vertical aligning with PageStart tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0, height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') ## Store client-side widgets. self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) self.storewidgets_clt.append(self.chkcltfile) ## Create widgets and layout (msgcltwin) ----------------------------------------------------------------------------------------------------- self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled', relief = 'ridge', font = self.msgfont) self.textboxclt.put() def prep_option(self, value): try: # is an INT return int(value) except (TypeError, ValueError): try: # is a FLOAT return float(value) except (TypeError, ValueError): # is a STRING. return value def prep_logfile(self, filepath, status): # FILE (pretty on, log view off, logfile yes) # FILEOFF (pretty on, log view off, no logfile) # STDOUT (pretty off, log view on, no logfile) # STDOUTOFF (pretty off, log view off, logfile yes) # FILESTDOUT (pretty off, log view on, logfile yes) if status == 'FILE': return filepath elif status in ['FILESTDOUT', 'STDOUTOFF']: return [status, filepath] elif status in ['STDOUT', 'FILEOFF']: return status def validate_int(self, value): return value == "" or value.isdigit() def validate_float(self, value): if value == "": return True try: float(value) return True except ValueError: return False def clt_on_show(self, force_remove = False, force_view = False): if self.optcltwin.winfo_ismapped() or force_remove: self.shbtnclt['text'] = 'SHOW\nCLIENT' self.optcltwin.grid_remove() self.msgcltwin.grid_remove() self.btncltwin.place_forget() elif not self.optcltwin.winfo_ismapped() or force_view: self.shbtnclt['text'] = 'HIDE\nCLIENT' self.optcltwin.grid() self.msgcltwin.grid() self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw') def srv_on_start(self): if self.runbtnsrv['text'] == 'START\nSERVER': self.on_clear([txsrv, txclt]) self.srv_actions_start() # wait for switch. while not serverthread.is_running_server: pass self.srv_toggle_all(on_start = True) # run thread for interrupting server when an error happens. self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt") self.srv_eject_thread.setDaemon(True) self.srv_eject_thread.start() elif self.runbtnsrv['text'] == 'STOP\nSERVER': serverthread.terminate_eject() def srv_eject(self): while not serverthread.eject: sleep(0.1) self.srv_actions_stop() def srv_actions_start(self): srv_config[srv_options['ip']['des']] = self.srvipadd.get() srv_config[srv_options['port']['des']] = self.prep_option(self.srvport.get()) srv_config[srv_options['epid']['des']] = self.epid.get() srv_config[srv_options['lcid']['des']] = self.prep_option(self.lcid.get()) srv_config[srv_options['hwid']['des']] = self.hwid.get() srv_config[srv_options['count']['des']] = self.prep_option(self.count.get()) srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get()) srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get()) srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get(), self.chksrvfile.state()) srv_config[srv_options['asyncmsg']['des']] = self.chkvalsrvasy.get() srv_config[srv_options['llevel']['des']] = self.srvlevel.get() srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get()) srv_config[srv_options['time0']['des']] = self.prep_option(self.timeout0.get()) srv_config[srv_options['sql']['des']] = self.chkvalsql.get() ## Redirect stdout. gui_redirector('stdout', redirect_to = TextRedirect.Log, redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) gui_redirector_setup() serverqueue.put('start') def srv_actions_stop(self): if serverthread.is_running_server: if serverthread.server is not None: server_terminate(serverthread, exit_server = True) # wait for switch. while serverthread.is_running_server: pass else: serverthread.is_running_server = False self.srv_toggle_all(on_start = False) def srv_toggle_all(self, on_start = True): self.srv_toggle_state() if on_start: self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'], foreground = self.customcolors['white']) for widget in self.storewidgets_srv: widget.configure(state = 'disabled') self.runbtnclt.configure(state = 'normal') else: self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'], foreground = self.customcolors['white']) for widget in self.storewidgets_srv: widget.configure(state = 'normal') if isinstance(widget, ListboxOfRadiobuttons): widget.change() self.runbtnclt.configure(state = 'disabled') def srv_toggle_state(self): if serverthread.is_running_server: txt, color = ('Server\nState:\nServing', self.customcolors['green']) else: txt, color = ('Server\nState:\nStopped', self.customcolors['red']) self.statesrv.configure(text = txt, foreground = color) def clt_on_start(self): if self.onlyclt: self.on_clear([txclt]) else: rng, add_newline = self.on_clear_setup() self.on_clear([txsrv, txclt], clear_range = [rng, None], newline_list = [add_newline, False]) self.clt_actions_start() # run thread for disabling interrupt server and client, when client running. self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt") self.clt_eject_thread.setDaemon(True) self.clt_eject_thread.start() for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]: widget.configure(state = 'disabled') def clt_actions_start(self): clt_config[clt_options['ip']['des']] = self.cltipadd.get() clt_config[clt_options['port']['des']] = self.prep_option(self.cltport.get()) clt_config[clt_options['mode']['des']] = self.cltmode.get() clt_config[clt_options['cmid']['des']] = self.cltcmid.get() clt_config[clt_options['name']['des']] = self.cltname.get() clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get(), self.chkcltfile.state()) clt_config[clt_options['asyncmsg']['des']] = self.chkvalcltasy.get() clt_config[clt_options['llevel']['des']] = self.cltlevel.get() clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get()) ## Redirect stdout. gui_redirector('stdout', redirect_to = TextRedirect.Log, redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) gui_redirector_setup() # run client (in a thread). self.clientthread = client_thread(name = "Thread-Clt") self.clientthread.setDaemon(True) self.clientthread.with_gui = True self.clientthread.start() def clt_eject(self): while self.clientthread.is_alive(): sleep(0.1) widgets = self.storewidgets_clt + [self.runbtnclt] if not self.onlyclt: widgets += [self.runbtnsrv] for widget in widgets: if isinstance(widget, ttk.Combobox): widget.configure(state = 'readonly') else: widget.configure(state = 'normal') if isinstance(widget, ListboxOfRadiobuttons): widget.change() def on_exit(self): if serverthread.is_running_server: if serverthread.server is not None: server_terminate(serverthread, exit_server = True) else: serverthread.is_running_server = False server_terminate(serverthread, exit_thread = True) self.destroy() def on_clear_setup(self): if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]): if self.count_clear == 0: self.ini = txsrv.index('end') add_newline = False else: if self.count_clear == 1: self.ini = '%s.0' %(int(self.ini[0]) - 1) else: self.ini = '%s.0' %(int(self.ini[0])) add_newline = True rng = [self.ini, 'end'] self.count_clear += 1 else: rng, add_newline = None, False self.count_clear = 0 return rng, add_newline def on_clear(self, widget_list, clear_range = None, newline_list = []): if newline_list == []: newline_list = len(widget_list) * [False] for num, couple in enumerate(zip(widget_list, newline_list)): widget, add_n = couple try: ini, fin = clear_range[num] except TypeError: ini, fin = '1.0', 'end' widget.configure(state = 'normal') widget.delete(ini, fin) if add_n: widget.insert('end', '\n') widget.configure(state = 'disabled')