# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Windows process management library.

This supports the following APIs:

- obj = createProcess(args: list<str>, env: dict<str,str> = None)
- obj = Process(pid: int)

The object returned has the following methods

- obj.getpid() -> int
- obj.kill(how: int = 0)
- obj.() -> bool
- obj.wait()
- obj.getstatus() -> int # -1 if still active

"""

import win32api
import win32con
import win32event
import win32process

from pywintypes import error as Error # Raised by Process() and proc.kill()

# CreateProcess flag (winbase.h): run a console application with no console
# window at all. STARTF_USESHOWWINDOW + SW_HIDE alone is NOT reliable for
# console apps -- the OS/conhost still creates the console window and only then
# applies the hidden show-state, so a black window FLASHES on the interactive
# desktop before it disappears. Every scheduler task (gather/engine/transfer,
# and the scheduler itself) is python.exe, a console app launched here on every
# ~15-minute cycle, so that flash is exactly what users see. CREATE_NO_WINDOW
# suppresses the console entirely -- no window is ever created, so nothing to
# flash. It only affects console apps; GUI/tray apps (AvSysTray) are unchanged.
# Not exposed by name on every pywin32, so define the literal.
CREATE_NO_WINDOW = 0x08000000

class Process(object):

    def __init__(self, pid, handle=None):
        self.pid = pid
        if handle is None:
            # If no handle is passed in, get one
            perms = win32con.PROCESS_QUERY_INFORMATION
            handle = win32api.OpenProcess(perms, False, pid)
            # Try to upgrade permissions to allow killing;
            # if we can't, keep the previous handle
            try:
                perms |= win32con.PROCESS_TERMINATE
                handle = win32api.OpenProcess(perms, False, pid)
            except:
                pass
        self.handle = handle

    def getpid(self):
        # An accessor function for consistency
        return self.pid

    def isalive(self):
        status = win32process.GetExitCodeProcess(self.handle)
        return status == win32con.STILL_ACTIVE

    def wait(self):
        win32event.WaitForSingleObject(self.handle, win32event.INFINITE)

    def getstatus(self):
        status = win32process.GetExitCodeProcess(self.handle)
        if status == win32con.STILL_ACTIVE:
            return -1
        else:
            return int(status & 0xffffffff)

    def kill(self, how=0):
        win32process.TerminateProcess(self.handle, how)

def createProcess(args, env=None, nice=False):
    # See page 330 of Python Programming on Win32
    si = win32process.STARTUPINFO()
    si.dwFlags = win32process.STARTF_USESHOWWINDOW
    si.wShowWindow = win32con.SW_HIDE
    if nice:
        # XXX Not supported on Windows NT and 95/98/ME
        cflags = 0x4000 # XXX win32process.BELOW_NORMAL_PRIORITY_CLASS
    else:
        cflags = 0 # Default is the lowest of current process and NORMAL
    # Never pop (or flash) a console window for the child; see CREATE_NO_WINDOW.
    cflags |= CREATE_NO_WINDOW
    hprocess, hthread, pid, tid = win32process.CreateProcess(
        None, # App name
        quotedJoin(args), # Command line
        None, # Process security
        None, # Thread security
        False, # Inherit handles
        cflags, # Creation flags
        env, # Environment
        None, # Current directory
        si) # Startup info
    return Process(pid, hprocess)

def quotedJoin(args):
    # Join arguments with double quotes around them
    result = []
    for arg in args:
        if " " in arg:
            arg = '"' + arg + '"'
        result.append(arg)
    return " ".join(result)
