# Demonstrates using a taskbar icon to create and navigate between desktops

import _thread
import io
import time
import traceback

import pywintypes
import win32api
import win32con
import win32gui
import win32process
import win32service

## "Shell_TrayWnd" is class of system tray window, broadcasts "TaskbarCreated" when initialized


def desktop_name_dlgproc(hwnd, msg, wparam, lparam):
    """Handles messages from the desktop name dialog box"""
    if msg in (win32con.WM_CLOSE, win32con.WM_DESTROY):
        win32gui.DestroyWindow(hwnd)
    elif msg == win32con.WM_COMMAND:
        if wparam == win32con.IDOK:
            desktop_name = win32gui.GetDlgItemText(hwnd, 72)
            print("new desktop name: ", desktop_name)
            win32gui.DestroyWindow(hwnd)
            create_desktop(desktop_name)

        elif wparam == win32con.IDCANCEL:
            win32gui.DestroyWindow(hwnd)


def get_new_desktop_name(parent_hwnd):
    """Create a dialog box to ask the user for name of desktop to be created"""
    msgs = {
        win32con.WM_COMMAND: desktop_name_dlgproc,
        win32con.WM_CLOSE: desktop_name_dlgproc,
        win32con.WM_DESTROY: desktop_name_dlgproc,
    }
    # dlg item [type, caption, id, (x,y,cx,cy), style, ex style
    style = (
        win32con.WS_BORDER
        | win32con.WS_VISIBLE
        | win32con.WS_CAPTION
        | win32con.WS_SYSMENU
    )  ## |win32con.DS_SYSMODAL
    h = win32gui.CreateDialogIndirect(
        win32api.GetModuleHandle(None),
        [
            ["One ugly dialog box !", (100, 100, 200, 100), style, 0],
            [
                "Button",
                "Create",
                win32con.IDOK,
                (10, 10, 30, 20),
                win32con.WS_VISIBLE
                | win32con.WS_TABSTOP
                | win32con.BS_HOLLOW
                | win32con.BS_DEFPUSHBUTTON,
            ],
            [
                "Button",
                "Never mind",
                win32con.IDCANCEL,
                (45, 10, 50, 20),
                win32con.WS_VISIBLE | win32con.WS_TABSTOP | win32con.BS_HOLLOW,
            ],
            ["Static", "Desktop name:", 71, (10, 40, 70, 10), win32con.WS_VISIBLE],
            ["Edit", "", 72, (75, 40, 90, 10), win32con.WS_VISIBLE],
        ],
        parent_hwnd,
        msgs,
    )  ## parent_hwnd, msgs)

    win32gui.EnableWindow(h, True)
    hcontrol = win32gui.GetDlgItem(h, 72)
    win32gui.EnableWindow(hcontrol, True)
    win32gui.SetFocus(hcontrol)


def new_icon(hdesk, desktop_name):
    """Runs as a thread on each desktop to create a new tray icon and handle its messages"""
    global id
    id += 1
    hdesk.SetThreadDesktop()
    ## apparently the threads can't use same hinst, so each needs its own window class
    windowclassname = "PythonDesktopManager" + desktop_name
    wc = win32gui.WNDCLASS()
    wc.hInstance = win32api.GetModuleHandle(None)
    wc.lpszClassName = windowclassname
    wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW | win32con.CS_GLOBALCLASS
    wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
    wc.hbrBackground = win32con.COLOR_WINDOW
    wc.lpfnWndProc = icon_wndproc
    windowclass = win32gui.RegisterClass(wc)
    style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
    hwnd = win32gui.CreateWindow(
        windowclass,
        "dm_" + desktop_name,
        win32con.WS_SYSMENU,
        0,
        0,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        0,
        0,
        wc.hInstance,
        None,
    )
    win32gui.UpdateWindow(hwnd)
    flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP
    notify_info = (
        hwnd,
        id,
        flags,
        win32con.WM_USER + 20,
        hicon,
        "Desktop Manager (%s)" % desktop_name,
    )
    window_info[hwnd] = notify_info
    ## wait for explorer to initialize system tray for new desktop
    tray_found = 0
    while not tray_found:
        try:
            tray_found = win32gui.FindWindow("Shell_TrayWnd", None)
        except win32gui.error:
            traceback.print_exc
            time.sleep(0.5)
    win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, notify_info)
    win32gui.PumpMessages()


def create_desktop(desktop_name, start_explorer=1):
    """Creates a new desktop and spawns a thread running on it
    Will also start a new icon thread on an existing desktop
    """
    sa = pywintypes.SECURITY_ATTRIBUTES()
    sa.bInheritHandle = 1

    try:
        hdesk = win32service.CreateDesktop(
            desktop_name, 0, win32con.MAXIMUM_ALLOWED, sa
        )
    except win32service.error:
        traceback.print_exc()
        errbuf = io.StringIO()
        traceback.print_exc(None, errbuf)
        win32api.MessageBox(0, errbuf.getvalue(), "Desktop creation failed")
        return
    if start_explorer:
        s = win32process.STARTUPINFO()
        s.lpDesktop = desktop_name
        prc_info = win32process.CreateProcess(
            None,
            "Explorer.exe",
            None,
            None,
            True,
            win32con.CREATE_NEW_CONSOLE,
            None,
            "c:\\",
            s,
        )

    th = _thread.start_new_thread(new_icon, (hdesk, desktop_name))
    hdesk.SwitchDesktop()


def icon_wndproc(hwnd, msg, wp, lp):
    """Window proc for the tray icons"""
    if lp == win32con.WM_LBUTTONDOWN:
        ## popup menu won't disappear if you don't do this
        win32gui.SetForegroundWindow(hwnd)

        curr_desktop = win32service.OpenInputDesktop(0, True, win32con.MAXIMUM_ALLOWED)
        curr_desktop_name = win32service.GetUserObjectInformation(
            curr_desktop, win32con.UOI_NAME
        )
        winsta = win32service.GetProcessWindowStation()
        desktops = winsta.EnumDesktops()
        m = win32gui.CreatePopupMenu()
        desktop_cnt = len(desktops)
        ## *don't* create an item 0
        for d in range(1, desktop_cnt + 1):
            mf_flags = win32con.MF_STRING
            ## if you switch to winlogon yourself, there's nothing there and you're stuck
            if desktops[d - 1].lower() in ("winlogon", "disconnect"):
                mf_flags |= win32con.MF_GRAYED | win32con.MF_DISABLED
            if desktops[d - 1] == curr_desktop_name:
                mf_flags |= win32con.MF_CHECKED
            win32gui.AppendMenu(m, mf_flags, d, desktops[d - 1])
        win32gui.AppendMenu(m, win32con.MF_STRING, desktop_cnt + 1, "Create new ...")
        win32gui.AppendMenu(m, win32con.MF_STRING, desktop_cnt + 2, "Exit")

        x, y = win32gui.GetCursorPos()
        d = win32gui.TrackPopupMenu(
            m,
            win32con.TPM_LEFTBUTTON | win32con.TPM_RETURNCMD | win32con.TPM_NONOTIFY,
            x,
            y,
            0,
            hwnd,
            None,
        )
        win32gui.PumpWaitingMessages()
        win32gui.DestroyMenu(m)
        if d == desktop_cnt + 1:  ## Create new
            get_new_desktop_name(hwnd)
        elif d == desktop_cnt + 2:  ## Exit
            win32gui.PostQuitMessage(0)
            win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, window_info[hwnd])
            del window_info[hwnd]
            origin_desktop.SwitchDesktop()
        elif d > 0:
            hdesk = win32service.OpenDesktop(
                desktops[d - 1], 0, 0, win32con.MAXIMUM_ALLOWED
            )
            hdesk.SwitchDesktop()
        return 0
    else:
        return win32gui.DefWindowProc(hwnd, msg, wp, lp)


window_info = {}
origin_desktop = win32service.OpenInputDesktop(0, True, win32con.MAXIMUM_ALLOWED)
origin_desktop_name = win32service.GetUserObjectInformation(
    origin_desktop, win32service.UOI_NAME
)

hinst = win32api.GetModuleHandle(None)
try:
    hicon = win32gui.LoadIcon(hinst, 1)  ## python.exe and pythonw.exe
except win32gui.error:
    hicon = win32gui.LoadIcon(hinst, 135)  ## pythonwin's icon
id = 0

create_desktop(str(origin_desktop_name), 0)

## wait for first thread to initialize its icon
while not window_info:
    time.sleep(1)

## exit when last tray icon goes away
while window_info:
    win32gui.PumpWaitingMessages()
    time.sleep(3)
