2
\$\begingroup\$

This is a big script with a GUI basically. I have 4 page classes, that are children of the main Wizard class. I do most of the threading in Step3 and the scripts are simple - creating a new user, editing reg files and installing chrome. Everything works perfectly here, until the main window is dragged - then the app lags a lot and needs to be re-launched.

Also I use some of my own written packages and if need be I can include them also. So I believe the problem is with tkinter and threading, as is usual in these cases. I believe the problem is in the class Step3

I would like some help with the lagging of the main window, or maybe entirely different but better options for doing stuff like this exists?

import os
import json
import threading
import ctypes
import sys
import subprocess
import queue
import shutil

import elevate
import tkinter as tk
from tkinter import messagebox
from tkinter import font as tkfont
from tkinter import ttk
from PIL import Image, ImageTk
import webbrowser

from pack import newuser
from pack import alluserlist
from pack import switchuser
from pack import editreg
from pack import install
from pack import autologin
from pack import extension_editreg


class Wizard(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        data = {"COMPLETED": False, "STARTED": True, "USER CREATED": False, "AFTER RESTART": False}

        print('starting')

        if not os.path.exists('C:\\Users\\Public\\app'):
            if getattr(sys, 'frozen', False):
                # frozen
                path_to_file = os.path.dirname(sys.executable)
            else:
                # unfrozen
                path_to_file = os.path.dirname(os.path.realpath(__file__))

            os.mkdir('C:\\Users\\Public\\app')

        self.geometry("750x400+520+235")
        self.maxsize(750, 500)
        self.minsize(750, 500)

        # fonts
        self.font_big = tkfont.Font(family='Malgun Gothic', size=18, weight="bold")

        self.font_small = tkfont.Font(family='Malgun Gothic', size=11)

        self.font_smaller = tkfont.Font(family='Malgun Gothic', size=7)

        self.title("setup")

        try:
            self.iconbitmap("icon.ico")
        except:
            self.iconbitmap("icon")

        self.current_step = None
        self.steps = [Step1(self, controller=self), Step2(self, controller=self), Step3(self, controller=self),
                      Step4(self, controller=self)]

        self.button_frame = tk.Frame(self, bg="#cfcfcf", bd=1)
        self.button_frame.place(width=750, height=60, x=0, y=440)

        self.back_button = tk.Button(self.button_frame, text="<< Back", width=10, height=1, command=self.back)
        self.next_button = tk.Button(self.button_frame, text="Next >>", width=10, height=1, command=self.next)
        self.finish_button = tk.Button(self.button_frame, text="Finish", width=10, height=1, command=self.finish)

        self.protocol("WM_DELETE_WINDOW", self.on_closing)

        try:
            with open("progress.json", 'r') as f:
                data = json.loads(f.read())
                for key, value in data.items():
                    if key == 'STARTED' and value:
                        self.current_step = 0
                        self.show_step(0)
                    elif key == "USER CREATED" and value:
                        current_step = self.steps[self.current_step]
                        current_step.pack_forget()
                        self.current_step = 0
                        self.show_step(2)

        except FileNotFoundError:
            with open("progress.json", 'w+') as f:
                json.dump(data, f)
                f.flush()
                os.fsync(f)
                self.show_step(0)

    def back(self):
        self.next_button.configure(state='active')
        self.show_step(self.current_step - 1)

    def next(self):
        self.show_step(self.current_step + 1)

    def finish(self):
        sys.exit()

    def show_step(self, step):
        if self.current_step is not None:
            # remove current step
            current_step = self.steps[self.current_step]
            current_step.pack_forget()

        self.current_step = step

        new_step = self.steps[step]
        new_step.pack(fill="both", expand=True)
        new_step.focus_set()

        if step == 0:
            # first step
            self.back_button.pack_forget()
            self.next_button.pack(side="right", padx=10)
            self.finish_button.pack_forget()
        elif step == len(self.steps) - 1:
            # last step
            self.finish_button.pack(side="right", padx=10)
            self.back_button.pack_forget()
            self.next_button.pack_forget()
        elif step == 1:
            self.back_button.pack(side="right", padx=15)
            self.next_button.configure(state='disabled')
            self.next_button.pack(side="right", padx=10)
            self.finish_button.pack_forget()
        elif step == 2:
            self.back_button.pack_forget()
            self.next_button.pack_forget()
            self.finish_button.pack_forget()
        else:
            # all other steps
            self.back_button.pack(side="right", padx=15)
            self.next_button.pack(side="right", padx=10)
            self.finish_button.pack_forget()

    def on_closing(self):
        if self.current_step == 3:
            self.destroy()
        else:
            if messagebox.askyesno("Cancel", "Are you sure you want to cancel the setup?"):
                self.destroy()


class Step1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.controller = controller

        self.pack(side="top", fill="both", expand=True)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        label_big = tk.Label(self, text="Welcome!", wraplength=450,
                             justify='left', font=controller.font_big)
        label_big.place(x=250, y=16)

        label1 = tk.Label(self, text="This will setup your windows machine as a kiosk.", wraplength=450,
                          justify='left', font=controller.font_small)
        label1.place(x=250, y=130)

        label2 = tk.Label(self,
                          text="A new user will be created, which will be used as the kiosk user. Next Google Chrome will be installed. Then Google Chrome will be setup so that it launches automatically and logs in every time the machine is launched.",
                          wraplength=450,
                          justify='left', font=controller.font_small)
        label2.place(x=250, y=170)

        label3 = tk.Label(self,
                          text="It is recommended you save your work as the setup will change the active user to the created one.",
                          wraplength=450,
                          justify='left', font=controller.font_small)
        label3.place(x=250, y=270)

        label3 = tk.Label(self,
                          text="Click Next to continue, or Cancel to exit the setup.",
                          wraplength=450,
                          justify='left', font=controller.font_small)
        label3.place(x=250, y=400)

        try:
            load = Image.open("picture.jpg")
        except:
            load = Image.open("picture")

        render = ImageTk.PhotoImage(load)
        img = tk.Label(self, image=render, width=200, height=440)
        img.image = render
        img.place(x=0, y=0)


class Step2(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.labels = []

        self.controller = controller

        self.pass_key = tk.IntVar()
        self.auto_key = tk.IntVar(value=1)

        self.controller = controller

        self.header = tk.Label(self, text="Windows user creation", justify='left', font=controller.font_big)
        self.header.place(x=25, y=15)
        self.txt = tk.Label(self, text="You will need to create a new user for this machine.", wraplength=450,
                            justify='left', font=controller.font_small)
        self.txt.place(x=25, y=50)

        self.cb = tk.Checkbutton(self, text="No password", variable=self.pass_key, font=controller.font_small,
                                 command=self.activatecheck_pass)
        self.cb.place(x=25, y=320)

        self.cbauto = tk.Checkbutton(self, text="Automatic login", variable=self.auto_key, font=controller.font_small)
        self.cbauto.place(x=25, y=350)

        self.name_text = tk.Label(self, text='Account name', font=controller.font_small)
        self.name_text.place(x=25, y=175)
        self.name_box = tk.Entry(self, width=50)
        self.name_box.place(x=30, y=200)
        self.name_text_info = tk.Label(self, text='The account name should be between 4 and 16 characters.',
                                       font=controller.font_smaller)
        self.name_text_info.place(x=25, y=220)

        self.pass_text = tk.Label(self, text='Account password', font=controller.font_small)
        self.pass_text.place(x=25, y=250)
        self.pass_box = tk.Entry(self, width=50, show="*")
        self.pass_box.place(x=30, y=275)
        self.pass_text_info = tk.Label(self, text='The password should be between 4 and 16 characters.',
                                       font=controller.font_smaller)
        self.pass_text_info.place(x=25, y=295)

        self.save_button = tk.Button(self, text="Save", width=10, height=1, command=self.save)
        self.save_button.place(x=415, y=400)

        self.information = tk.Label(self, text='For changes to take effect, please press the Save button',
                                    font=self.controller.font_small)
        self.information.place(x=25, y=400)

    def save(self):
        a = self.name_box.get()

        if 16 >= len(a) >= 4:
            self.print_good('namebox')

            with open("progress.json", 'r') as f:
                data = json.load(f)
                data["USERNAME"] = a
                data['PASSWORD'] = None
            with open("progress.json", 'w') as f:
                json.dump(data, f)
                f.flush()
                os.fsync(f)
            # allow user to continue, enable next button
            self.controller.next_button.configure(state='active')
        else:
            self.controller.next_button.configure(state='disabled')
            self.print_bad('namebox')

        if self.pass_box.cget('state') == 'normal':
            b = self.pass_box.get()

            if 16 >= len(b) >= 4:
                self.print_good('passbox')

                with open("progress.json", 'r') as f:
                    data = json.load(f)
                    data["PASSWORD"] = b
                with open("progress.json", 'w') as f:
                    json.dump(data, f)
                    f.flush()
                    os.fsync(f)

            else:
                self.controller.next_button.configure(state='disabled')
                self.print_bad('passbox')

        # registry
        self.registry = autologin.Reg(None, None)

        if self.auto_key.get() == 1:  # checked
            val0, val1, val2 = self.registry.get_reg_val()

            if val2 == '': val2 = None

            with open("progress.json", 'r') as f:
                data = json.load(f)
                data["AutoAdminLogon"] = val0
                data['DefaultUserName'] = val1
                data['DefaultPassword'] = val2
            with open("progress.json", 'w') as f:
                json.dump(data, f)
                f.flush()
                os.fsync(f)

        elif self.auto_key.get() == 0:  # unchecked
            with open("progress.json") as f:
                data = json.load(f)

            for element in list(data):
                if 'AutoAdminLogon' in element:
                    del data['AutoAdminLogon']
                if 'DefaultUserName' in element:
                    del data['DefaultUserName']
                if 'DefaultPassword' in element:
                    del data['DefaultPassword']

            with open("progress.json", 'w') as f:
                json.dump(data, f)
                f.flush()
                os.fsync(f)

        self.registry.close()

    def print_good(self, n):
        if n == 'namebox':
            self.good_name = tk.Label(self, text=u'\u2713', font=self.controller.font_small)
            self.good_name.place(x=340, y=195)
        elif n == 'passbox':
            self.good_pass = tk.Label(self, text=u'\u2713', font=self.controller.font_small)
            self.good_pass.place(x=340, y=270)
            self.labels.append(self.good_pass)

    def print_bad(self, n):
        if n == 'namebox':
            self.bad_name = tk.Label(self, text=u'\u2717', font=self.controller.font_small)
            self.bad_name.place(x=340, y=195)
        elif n == 'passbox':
            self.bad_pass = tk.Label(self, text=u'\u2717', font=self.controller.font_small)
            self.bad_pass.place(x=340, y=270)
            self.labels.append(self.bad_pass)

    def activatecheck_pass(self):
        if self.pass_key.get() == 0:  # whenever unchecked
            self.pass_box.config(state='normal')
        elif self.pass_key.get() == 1:  # whenever checked
            for self.label in self.labels: self.label.destroy()
            self.pass_box.delete(0, 'end')
            self.pass_box.config(state='disabled')

            with open("progress.json", 'r') as f:
                data = json.load(f)
                if 'PASSWORD' in data:
                    data['PASSWORD'] = None
                    with open("progress.json", 'w') as g:
                        json.dump(data, g)
                        g.flush()
                        os.fsync(g)


class Step3(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.things = []

        self.controller = controller

        ar = True  # after restart
        try:
            with open("progress.json", 'r') as f:
                data = json.load(f)
        except FileNotFoundError:
            ar = False

        if not ar:
            self.header = tk.Label(self,
                                   text="Please wait until we complete the setup. Please note that the active user will be changed to the created one. The setup will continue automatically on startup.",
                                   wraplength=710,
                                   justify='left', font=controller.font_small)
        elif ar:
            self.header = tk.Label(self,
                                   text="The setup will end shortly. When Google Chrome opens, please wait until a pop-up appears, then enable the virtual keyboard extension by pressing \"Enable extension\" on the upper right corner. After enabling the extension, please close Google Chrome",
                                   wraplength=710,
                                   justify='left', font=controller.font_small)

        self.header.place(x=25, y=15)

        self.p = ttk.Progressbar(self, orient='horizontal', length=500, takefocus=True, mode='indeterminate')
        self.p.place(x=125, y=200)

        t = threading.Thread(target=self.check, args=(self.things,))
        t.setDaemon(True)
        t.start()

    def check(self, *options):
        import time
        thename = None
        while True:
            if self.winfo_ismapped():
                break
        self.p.start()

        with open("progress.json", 'r') as f:
            data = json.load(f)
            if not data['AFTER RESTART']:
                if not data['USER CREATED']:
                    # read required data from progress.json
                    self.read(self.things)

                    for needpass, username, *password in options:
                        thename = username
                        main = threading.Thread(target=newuser.CreateUserAndShare, args=(needpass, username, *password))
                        main.start()

                    userlist = alluserlist.listusers()

                    # while thename not in userlist:
                    # userlist = alluserlist.listusers()

                    with open("progress.json", 'w') as g:
                        data["USER CREATED"] = True
                        json.dump(data, g)
                        g.flush()
                        os.fsync(g)

                # create thing to launch setup on startup of new user with the help of editreg.py
                self.registry = editreg.Reg(None, 'C:\\Users\\Public\\app')

                with open("progress.json", 'w') as g:
                    data['REG_VAL'] = self.registry.get_reg_val()
                    json.dump(data, g)
                    g.flush()
                    os.fsync(g)

                self.registry.write_reg_val()
                self.registry.close()

                self.registry = editreg.Reg(None, None)
                self.registry.disable_privacy_experience()  # disable first startup privacy experience
                self.registry.close()

                self._registry = autologin.Reg(None, None)
                if 'AutoAdminLogon' in data:
                    self._registry.write_reg_val(data['USERNAME'], data['PASSWORD'])

                self._registry.close()

                with open("progress.json", 'w') as g:
                    data['AFTER RESTART'] = True
                    json.dump(data, g)
                    g.flush()
                    os.fsync(g)

                if getattr(sys, 'frozen', False):
                    # frozen
                    path_to_file = os.path.dirname(sys.executable)
                else:
                    # unfrozen
                    path_to_file = os.path.dirname(os.path.realpath(__file__))

                shutil.copytree(path_to_file, 'C:\\Users\\Public\\app', dirs_exist_ok=True)

                # log out from the old user to be able to log in to the new
                time.sleep(2)
                switchuser.do()

            # AFTER RESTART
            else:
                print('after')
                if getattr(sys, 'frozen', False):
                    # frozen
                    path_to_file = os.path.dirname(sys.executable)
                else:
                    # unfrozen
                    path_to_file = os.path.dirname(os.path.realpath(__file__))

                self.registry = editreg.Reg(None, path_to_file)

                reg_before = data['REG_VAL']

                self.registry.del_reg_val(reg_before)

                self.registry.close()

                self.registry = editreg.Reg(None, None)
                self.registry.enable_privacy_experience()  # enable first startup privacy experience, simply delete key
                self.registry.close()

                # set sleep and power off to Never
                subprocess.call("powercfg -change standby-timeout-ac 0 & powercfg -change -monitor-timeout-ac 0",
                                shell=True)

                # install chrome
                q = queue.Queue()
                z = threading.Thread(target=install.run, args=(q,))
                z.start()
                z.join()

                path = q.get()

                # make chrome kiosk launch when windows launches
                self.registry.write_chrome(path)
                self.registry.close()

                # install chrome virtual keyboard extension
                self._registry = extension_editreg.Reg(None, None)
                self._registry.write_reg_val()
                self._registry.close()

                # open chrome
                subprocess.run(path)

                self.p.stop()
                self.controller.next()

    def read(self, thelist):
        with open("progress.json", 'r') as f:
            data = json.load(f)
            print(data)
            if 'PASSWORD' in data:
                thelist.append(True)
                for key, value in data.items():
                    if key == 'USERNAME' or key == 'PASSWORD':
                        thelist.append(value)
            else:
                thelist.append(False)
                for key, value in data.items():
                    if key == 'USERNAME':
                        thelist.append(value)


class Step4(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.controller = controller

        label1 = tk.Label(self, text="Setup complete!", font=self.controller.font_big)
        label1.place(x=250, y=15)

        load = Image.open("picture.jpg")
        render = ImageTk.PhotoImage(load)
        img = tk.Label(self, image=render, width=200, height=440)
        img.image = render
        img.place(x=0, y=0)

        label2 = tk.Label(self, text="The setup has been successfully completed.", font=self.controller.font_small)
        label2.place(x=250, y=70)

        label3 = tk.Label(self, text="visit ",
                          font=self.controller.font_small)
        label3.place(x=250, y=120)
        link1 = tk.Label(self, text="here", fg="blue", cursor="hand2", font=self.controller.font_small)
        link1.place(x=250, y=145)
        link1.bind("<Button-1>", lambda e: self.callback("https://www.google.com"))

        label4 = tk.Label(self, text="text", font=self.controller.font_small)
        label4.place(x=373, y=145)

        label5 = tk.Label(self,
                          text="it will launch after restart",
                          font=self.controller.font_small, wraplength=435,
                          justify='left')
        label5.place(x=250, y=180)

        label6 = tk.Label(self, text="Press Finish to restart your machine and run it as a kiosk! does not work yet",
                          font=self.controller.font_small)
        label6.place(x=250, y=400)

        t = threading.Thread(target=self.is_completed)
        t.setDaemon(True)
        t.start()

    def check(self, *options):
        import time
        thename = None
        while True:
            if self.winfo_ismapped():
                break
        self.p.start()

    def is_completed(self):
        while True:
            if self.winfo_ismapped():
                break

        try:
            with open("progress.json", 'r') as f:
                data = json.load(f)
            with open("progress.json", 'w') as g:
                data['COMPLETED'] = True
                json.dump(data, g)
                g.flush()
                os.fsync(g)
        except json.decoder.JSONDecodeError:
            sys.exit()

        print('removing json')
        os.remove('progress.json')

    def callback(self, url):
        webbrowser.open_new(url)


if __name__ == "__main__":
    elevate.elevate(show_console=False)
    wiz = Wizard()
    wiz.mainloop()
\$\endgroup\$
  • 1
    \$\begingroup\$ Does the code work? Does the main window appear? Code that is not working as expected is off-topic for code review. \$\endgroup\$ – pacmaninbw Jan 21 at 19:21
  • 2
    \$\begingroup\$ As I said, the code does work as intended. The problem is the lag when dragging \$\endgroup\$ – Daniel Zaksevski Jan 21 at 19:22
  • \$\begingroup\$ Suggest you simplify the above so that we don't need import pack. Or link to a repo where we can try it in full. Also, you can put hardcoded values on top as constants. \$\endgroup\$ – Abdur-Rahmaan Janhangeer Jan 21 at 20:27
  • 1
    \$\begingroup\$ @Abdur-RahmaanJanhangeer Please don't suggest simplifications. On Code Review, we need to see the real deal. \$\endgroup\$ – Mast Jan 22 at 13:53
  • 2
    \$\begingroup\$ @Abdur-RahmaanJanhangeer Removing is something else than simplifying. When in doubt, the original code is much preferred over a simplification. If a simplification is absolutely necessary, pleas keep this, this and the help center in mind. Details matter! Daniel, I strongly recommend against modifying the code if what you've currently posted is how you're currently using it.You may just simplify the performance problem away if you take Abdur's advice.We've seen it happen. \$\endgroup\$ – Mast Jan 22 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.