def setup(self):
        """
        Setup py3status and spawn i3status/events/modules threads.
        """

        # log py3status and python versions
        self.log("=" * 8)
        msg = "Starting py3status version {version} python {python_version}"
        self.log(msg.format(**self.config))

        # if running from git then log the branch and last commit
        self._log_gitversion()

        self.log("window manager: {}".format(self.config["wm_name"]))

        if self.config["debug"]:
            self.log(f"py3status started with config {self.config}")

        # read i3status.conf
        config_path = self.config["i3status_config_path"]
        self.log("config file: {}".format(self.config["i3status_config_path"]))
        self.config["py3_config"] = process_config(config_path, self)

        # autodetect output_format
        output_format = self.config["py3_config"]["general"]["output_format"]
        if output_format is None:
            if sys.stdout.isatty():
                print("py3status: trying to auto-detect output_format setting")
                print('py3status: auto-detected "term"')
                output_format = "term"

        self.config["py3_config"]["general"]["output_format"] = output_format or "i3bar"

        # read resources
        if "resources" in str(self.config["py3_config"].values()):
            from subprocess import check_output

            resources = check_output(["xrdb", "-query"]).decode().splitlines()
            self.config["resources"] = {
                k: v.strip() for k, v in (x.split(":", 1) for x in resources)
            }

        # setup i3status thread
        self.i3status_thread = I3status(self)

        # If standalone or no i3status modules then use the mock i3status
        # else start i3status thread.
        i3s_modules = self.config["py3_config"]["i3s_modules"]
        if self.config["standalone"] or not i3s_modules:
            self.i3status_thread.mock()
            i3s_mode = "mocked"
        else:
            for module in i3s_modules:
                self.log(f"adding module {module}")
            i3s_mode = "started"
            self.i3status_thread.start()
            while not self.i3status_thread.ready:
                if not self.i3status_thread.is_alive():
                    # i3status is having a bad day, so tell the user what went
                    # wrong and do the best we can with just py3status modules.
                    err = self.i3status_thread.error
                    self.notify_user(err)
                    self.i3status_thread.mock()
                    i3s_mode = "mocked"
                    break
                time.sleep(0.1)
        if self.config["debug"]:
            self.log(
                "i3status thread {} with config {}".format(i3s_mode, self.config["py3_config"])
            )

        # add i3status thread monitoring task
        if i3s_mode == "started":
            task = CheckI3StatusThread(self.i3status_thread, self)
            self.timeout_queue_add(task)

        # setup input events thread
        self.events_thread = Events(self)
        self.events_thread.daemon = True
        self.events_thread.start()
        if self.config["debug"]:
            self.log("events thread started")

        # initialise the command server
        self.commands_thread = CommandServer(self)
        self.commands_thread.daemon = True
        self.commands_thread.start()
        if self.config["debug"]:
            self.log("commands thread started")

        # initialize the udev monitor (lazy)
        self.udev_monitor = UdevMonitor(self)

        # suppress modules' output wrt issue #20
        if not self.config["debug"]:
            sys.stdout = Path("/dev/null").open("w")
            sys.stderr = Path("/dev/null").open("w")

        # make sure we honor custom i3bar protocol stop/resume signals
        # while providing users a way to opt out from that feature
        # using the 0 value as specified by the i3bar protocol
        custom_stop_signal = self.config["py3_config"].get("py3status", {}).get("stop_signal")
        if custom_stop_signal is not None:
            try:
                # 0 is a special value for i3bar protocol, use it as-is
                if custom_stop_signal == 0:
                    self.stop_signal = custom_stop_signal
                else:
                    self.stop_signal = Signals(custom_stop_signal)
            except ValueError:
                error = (
                    f"py3status.stop_signal '{custom_stop_signal}' is invalid "
                    f"and should be a number between 0 (disable) and 31"
                )
                self.log(error, level="error")
                raise Exception(error)

        # SIGTSTP can be received and indicates that all output should
        # stop and we should consider py3status suspended.  It is however
        # important that any processes using i3 ipc should continue to receive
        # those events otherwise it can lead to a stall in i3.
        signal(SIGTSTP, self.i3bar_stop)
        # SIGCONT indicates output should be resumed.
        signal(SIGCONT, self.i3bar_start)

        # get the list of py3status configured modules
        self.py3_modules = self.config["py3_config"]["py3_modules"]

        # get a dict of all user provided modules
        self.log("modules include paths: {}".format(self.config["include_paths"]))
        user_modules = self.get_user_configured_modules()
        if self.config["debug"]:
            self.log(f"user_modules={user_modules}")

        if self.py3_modules:
            # load and spawn i3status.conf configured modules threads
            self.load_modules(self.py3_modules, user_modules)

        # determine the target output format
        self.output_format = OutputFormat.instance_for(
            self.config["py3_config"]["general"]["output_format"]
        )

        # determine the output separator, if needed
        color_separator = None
        if self.config["py3_config"]["general"]["colors"]:
            color_separator = self.config["py3_config"]["general"]["color_separator"]
        self.output_format.format_separator(
            self.config["py3_config"]["general"].get("separator", None),
            color_separator,
        )

        Setup py3status and spawn i3status/events/modules threads.
        
py3status/core.py