Search code examples
pythonpynputtqdm

Pause and Unpause TQDM Progress Bar Using Pynput Key Press


I'm creating a small program that uses the TQDM progress bar library, where I would like to press space to "pause" and "unpause" the progress bar. The Progress bar should update every second, and run to 100% when the amount of time entered completes. Please see the attempt I did below, where when running, the progress bar never updates on it's own. Appreciate your feedback.

from tqdm import tqdm
from pynput import keyboard

class TimerTimer:
    def __init__(self, fileLoc) -> None:
        self.timer_list = self.get_timer_list(fileLoc)
        self.timer_length = len(self.timer_list)
        self.kb_listener = None
        self.init_val = 0
        self.wait = False

    def on_press_or_release(self, key):
        if key == keyboard.Key.esc:
            print("Exiting")
            exit()
        if key == keyboard.Key.space:
            self.wait = not self.wait
            printStr = "\nPaused...\n" if self.wait else "\nResumed!"
            print(printStr)
            return False

    def timer_with_progress(self, time_limit, name):
        print("{} for {} seconds:".format(name, time_limit))
        t = tqdm(total=time_limit, desc=name, ncols=100, initial=self.init_val)

        #for i in tqdm(range(time_limit), desc=name, ncols=100, initial=self.init_val):
        for i in range(time_limit):
            # sleep(1)
            # The event listener will be running in this block
            with keyboard.Events() as events:
                # Block at most one second
                event = events.get(1.0)
                if event is None:
                    break
                elif event.key == keyboard.Key.esc:
                    print("Exiting")
                    exit()
                elif event.key == keyboard.Key.space:
                    self.wait = not self.wait
                    printStr = "\nPaused...\n" if self.wait else "\nResumed!"
                    print(printStr)
                else:
                    break

            t.update()
            # t.refresh()
        t.close()

    def run(self):
        for index in self.timer_list:
            timer_cmd_list = ["self.timer_with_progress(5, 'Rest')",
                            "self.timer_with_progress(self.timer_list[index]['durationSeconds'], self.timer_list[index]['timerName'])"]
            for cmd in timer_cmd_list:
                if not self.wait:
                    exec(cmd)
                else:
                    if self.kb_listener is None:
                        self.kb_listener = keyboard.Listener(on_press = self.on_press_or_release) # , on_release = self.on_press_or_release)
                        self.kb_listener.start()
                    self.kb_listener.join() # Waits until the key is pressed again
                    self.wait = not self.wait # Once the button is pressed again, it changes the flag


if __name__=="__main__":
    tt = TimerTimer(filename)
    tt.run()

Solution

  • So after some research, I saw that progressbar2 appears to implement a better interrupt to accomplish what I was aiming to achieve. See below:

    import progressbar
    from pynput import keyboard
    from time import sleep
    
    class TimerTimer:
        def __init__(self, fileLoc) -> None:
            self.timer_list = self.get_timer_list(fileLoc)
            self.timer_length = len(self.timer_list)
            self.wait = False
    
        def on_press(self, key):
            pass
    
        def on_release(self, key):
            if key == keyboard.Key.space:
                self.wait = not self.wait
                printStr = "Paused..." if self.wait else "\nResuming..."
                print(printStr)
            elif key == keyboard.Key.esc:
                print("Exiting")
                exit()
    
        def timer_with_progress(self, time_limit: int, name: str, iter:int=1) -> None:
            """Function that runs the progressbar2 progress bar.
    
            Keyword arguments:
            time_limit -- (int) The time limit (in seconds) for the progress bar.
            name -- (string) The Name of the progress bar, or the brief description.
            iter -- (int) The iteration time in seconds.  (default 1)
            """
            print("{} for {} seconds:".format(color.BOLD + name +color.END, time_limit))
            bar = progressbar.ProgressBar(max_value=time_limit)
            i = 0
            # Adding keyboard listener
            listener = keyboard.Listener(on_press=self.on_press, on_release=self.on_release)
            listener.start()
            while i <= time_limit:
                sleep(iter)
                if not self.wait:
                    bar.update(i)
                    i += iter
            bar.finish()
    
        def run(self):
            for index in self.timer_list:
                self.timer_with_progress(5, 'Rest')
                self.timer_with_progress(self.timer_list[index]['durationSeconds'], self.timer_list[index]['timerName'])
    
    
    if __name__=="__main__":
        tt = TimerTimer(filename)
        tt.run()