home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / components / minimize.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  18.0 KB  |  470 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. from xpcom import components
  19. import ctypes
  20. import logging
  21. from ctypes.wintypes import DWORD, HWND, HANDLE, LPCWSTR, WPARAM, LPARAM, RECT, POINT
  22. UINT = ctypes.c_uint
  23. WCHAR = ctypes.c_wchar
  24. INT = ctypes.c_int
  25.  
  26. WH_MOUSE_LL = 14
  27. WH_MOUSE    = 7
  28.  
  29. WM_NULL = 0x0000
  30. WM_USER = 0x0400
  31. WM_TRAYICON = WM_USER+0x1EEF
  32. WM_GETICON = 0x007F
  33. WM_SETICON = 0x0080
  34. WM_STYLECHANGED = 0x007D
  35. WM_SIZE = 0x0005
  36. WM_GETMINMAXINFO = 0x0024
  37. WM_ACTIVATEAPP = 0x001C
  38. WM_ACTIVATE = 0x0006
  39. WM_INITMENUPOPUP = 0x0117
  40. WM_MOUSEMOVE = 0x0200
  41. WS_EX_APPWINDOW = 0x00040000L
  42. WS_MAXIMIZE = 0x01000000L
  43. SIZE_MAXIMIZED = 2
  44. ICON_SMALL = 0
  45. ICON_BIG   = 1
  46.  
  47. IMAGE_ICON = 1
  48.  
  49. LR_LOADFROMFILE = 0x0010
  50.  
  51. NIF_MESSAGE = 0x00000001
  52. NIF_ICON    = 0x00000002
  53. NIF_TIP     = 0x00000004
  54.  
  55. WM_LBUTTONDOWN                 = 0x0201
  56. WM_LBUTTONUP                   = 0x0202
  57. WM_LBUTTONDBLCLK               = 0x0203
  58. WM_RBUTTONDOWN                 = 0x0204
  59. WM_RBUTTONUP                   = 0x0205
  60. WM_RBUTTONDBLCLK               = 0x0206
  61. WM_MBUTTONDOWN                 = 0x0207
  62. WM_MBUTTONUP                   = 0x0208
  63. WM_MBUTTONDBLCLK               = 0x0209
  64. WM_XBUTTONDOWN                 = 0x020B
  65. WM_XBUTTONUP                   = 0x020C
  66. WM_XBUTTONDBLCLK               = 0x020D
  67.  
  68. NIM_ADD     = 0
  69. NIM_DELETE  = 2
  70.  
  71. IDI_APPLICATION = 32512
  72. IDI_WINLOGO = 32517
  73.  
  74. ABM_GETTASKBARPOS = 5
  75. ABM_GETSTATE      = 4
  76.  
  77. ABS_AUTOHIDE   = 1
  78. ABE_LEFT       = 0
  79. ABE_TOP        = 1
  80. ABE_RIGHT      = 2
  81. ABE_BOTTOM     = 3
  82.  
  83. IDANI_OPEN         = 1
  84. IDANI_CAPTION      = 3
  85.  
  86. SW_HIDE = 0
  87. SW_SHOWMINIMIZED = 2
  88. SW_SHOW = 5
  89. SW_RESTORE = 9
  90.  
  91. GWL_WNDPROC = -4
  92.  
  93. MONITOR_DEFAULTTONEAREST = 0x00000002
  94.  
  95. def LOWORD(dword): return dword & 0x0000ffff
  96. def HIWORD(dword): return dword >> 16
  97.  
  98. WNDPROCTYPE = ctypes.WINFUNCTYPE(ctypes.c_int, HWND, UINT, WPARAM, LPARAM)
  99. WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_int, HWND, LPARAM)
  100.  
  101. import config
  102. import prefs
  103. import resources
  104.  
  105. class GUID(ctypes.Structure):
  106.     _fields_ = [("Data1", ctypes.c_int32),
  107.                 ("Data2", ctypes.c_int16),
  108.                 ("Data3", ctypes.c_int16),
  109.                 ("Data4", ctypes.c_byte * 8),
  110.                 ]
  111.  
  112. class TIMEOUT(ctypes.Union):
  113.     _fields_ = [("uTimeout", UINT),
  114.                 ("uVersion", UINT),
  115.                 ]
  116.  
  117. class NOTIFYICONDATA(ctypes.Structure):
  118.     _fields_ = [("cbSize", DWORD),
  119.                 ("hWnd", HWND),
  120.                 ("uID", UINT),
  121.                 ("uFlags", UINT),
  122.                 ("uCallbackMessage", UINT),
  123.                 ("hIcon", HANDLE),
  124.                 ("szTip", WCHAR * 64),
  125.                 ("dwState", DWORD),
  126.                 ("dwStateMask", DWORD),
  127.                 ("szInfo", WCHAR * 256),
  128.                 ("timeout", TIMEOUT),
  129.                 ("szInfoTitle", WCHAR * 64),
  130.                 ("dwInfoFlags", DWORD),
  131.                 ("guidItem", GUID),
  132.                 ("hBalloonIcon", HANDLE),
  133.                 ]
  134.  
  135. class WNDCLASSEX(ctypes.Structure):
  136.     _fields_ = [("cbSize", UINT),
  137.                 ("style", UINT),
  138.                 ("lpfnWndProc", WNDPROCTYPE),
  139.                 ("cbClsExtra", INT),
  140.                 ("cbWndExtra", INT),
  141.                 ("hInstance", HANDLE),
  142.                 ("hIcon", HANDLE),
  143.                 ("hCursor", HANDLE),
  144.                 ("hBrush", HANDLE),
  145.                 ("lpszMenuName", LPCWSTR),
  146.                 ("lpszClassName", LPCWSTR),
  147.                 ("hIconSm", HANDLE),
  148.                 ]
  149. class APPBARDATA(ctypes.Structure):
  150.     _fields_ = [("cbSize", DWORD),
  151.                 ("hWnd", HANDLE),
  152.                 ("uCallbackMessage", UINT),
  153.                 ("uEdge", UINT),
  154.                 ("rc", RECT),
  155.                 ("lParam", LPARAM)]
  156.  
  157. class STYLESTRUCT(ctypes.Structure):
  158.     _fields_ = [("styleOld", DWORD),
  159.                 ("styleNew", DWORD)]
  160.  
  161. class MINMAXINFO(ctypes.Structure):
  162.     _fields_ = [("ptReserved", POINT),
  163.                 ("ptMaxSize", POINT),
  164.                 ("ptMaxPosition", POINT),
  165.                 ("ptMinTrackSize", POINT),
  166.                 ("ptMaxTrackSize",POINT)]
  167.  
  168. class WINDOWPLACEMENT(ctypes.Structure):
  169.     _fields_ = [
  170.                 ("length", UINT),
  171.                 ("flags", UINT),
  172.                 ("showCmd", UINT),
  173.                 ("ptMinPosition", POINT),
  174.                 ("ptMaxPosition", POINT),
  175.                 ("rcNormalPosition", RECT),
  176.     ]
  177.  
  178. def getTaskbarEdge():
  179.     appBarData = APPBARDATA(0,0,0,0,RECT(0,0,0,0),0)
  180.     if (ctypes.windll.shell32.SHAppBarMessage(ABM_GETTASKBARPOS,ctypes.byref(appBarData)) != 0):
  181.         return appBarData.uEdge
  182.     else:
  183.         return -1
  184.  
  185. def getTaskbarHeight():
  186.     appBarData = APPBARDATA(0,0,0,0,RECT(0,0,0,0),0)
  187.     if ((ctypes.windll.shell32.SHAppBarMessage(ABM_GETSTATE,ctypes.byref(appBarData)) & ABS_AUTOHIDE) != 0):
  188.         return 1
  189.     if (ctypes.windll.shell32.SHAppBarMessage(ABM_GETTASKBARPOS,ctypes.byref(appBarData)) != 0):
  190.         if appBarData.uEdge in [ABE_LEFT, ABE_RIGHT]:
  191.             return (appBarData.rc.right - appBarData.rc.left)
  192.         else:
  193.             return (appBarData.rc.bottom - appBarData.rc.top)
  194.     else:
  195.         return -1
  196.  
  197. # Returns true iff the Windows task bar is on the current monitor
  198. def taskbarOnCurrentMonitor():
  199.     if len(Minimize.oldWindowProcs) > 0:
  200.         appBarData = APPBARDATA(0,0,0,0,RECT(0,0,0,0),0)
  201.         if (ctypes.windll.shell32.SHAppBarMessage(ABM_GETTASKBARPOS,ctypes.byref(appBarData)) != 0):
  202.             taskbarMonitor = ctypes.windll.user32.MonitorFromRect(ctypes.pointer(appBarData.rc), MONITOR_DEFAULTTONEAREST)
  203.             appMonitor = ctypes.windll.user32.MonitorFromWindow (Minimize.oldWindowProcs.keys()[0], MONITOR_DEFAULTTONEAREST)
  204.             return appMonitor == taskbarMonitor
  205.  
  206.     # Assume that if there's trouble, the task bar is on the current monitor
  207.     return True
  208.  
  209. def PyWindProc(hWnd, uMsg, wParam, lParam):
  210.     mousePos = ctypes.windll.user32.GetMessagePos()
  211.     mouseX = LOWORD(mousePos)
  212.     mouseY = HIWORD(mousePos)
  213.     if uMsg == WM_TRAYICON:
  214.         if lParam in [WM_LBUTTONUP, WM_MBUTTONUP]:
  215.             Minimize.minimizers[hWnd].minimizeOrRestore()
  216.         elif lParam == WM_RBUTTONUP:
  217.             Minimize.minimizers[hWnd].showPopup(mouseX, mouseY)
  218.  
  219.     #components.classes['@mozilla.org/consoleservice;1'].getService(components.interfaces.nsIConsoleService).logStringMessage("PYWINPROC %d %d %d %d" % (hWnd, uMsg, wParam, lParam))
  220.     return ctypes.windll.user32.CallWindowProcW(ctypes.windll.user32.DefWindowProcW,hWnd, uMsg, wParam, lParam)
  221.  
  222. def PyMainWindProc(hWnd, uMsg, wParam, lParam):
  223.     if uMsg == WM_GETMINMAXINFO and len(Minimize.minimizers) > 0 and taskbarOnCurrentMonitor():
  224.         info = ctypes.cast(lParam, ctypes.POINTER(MINMAXINFO)).contents
  225.         edge = getTaskbarEdge()
  226.         height = getTaskbarHeight()
  227.         pybridge = components.classes["@participatoryculture.org/dtv/pybridge;1"].getService(components.interfaces.pcfIDTVPyBridge)
  228.         window = Minimize.minimizers[Minimize.minimizers.keys()[0]].window
  229.         if edge == 3: # Taskbar is on the bottom
  230.             info.ptMaxSize.x = window.screen.width
  231.             info.ptMaxSize.y = window.screen.height - height
  232.             info.ptMaxPosition.x = 0
  233.             info.ptMaxPosition.y = 0
  234.         elif edge == 2: # Taskbar is on the right
  235.             info.ptMaxSize.x = window.screen.width - height
  236.             info.ptMaxSize.y = window.screen.height
  237.             info.ptMaxPosition.x = 0
  238.             info.ptMaxPosition.y = 0
  239.         elif edge == 1: # Taskbar is on top
  240.             info.ptMaxSize.x = window.screen.width
  241.             info.ptMaxSize.y = window.screen.height - height
  242.             info.ptMaxPosition.x = 0
  243.             info.ptMaxPosition.y = height
  244.         elif edge == 0: # Taskbar is on the left
  245.             info.ptMaxSize.x = window.screen.width - height
  246.             info.ptMaxSize.y = window.screen.height
  247.             info.ptMaxPosition.x = height
  248.             info.ptMaxPosition.y = 0
  249.             
  250.     return ctypes.windll.user32.CallWindowProcW(Minimize.oldWindowProcs[hWnd],hWnd, uMsg, wParam, lParam)
  251.  
  252. WindProc = WNDPROCTYPE(PyWindProc)
  253. MainWindProc = WNDPROCTYPE(PyMainWindProc)
  254.  
  255. class Minimize:
  256.     _com_interfaces_ = [components.interfaces.pcfIDTVMinimize]
  257.     _reg_clsid_ = "{C8F996EC-599E-4749-9A70-EE9B7662981F}"
  258.     _reg_contractid_ = "@participatoryculture.org/dtv/minimize;1"
  259.     _reg_desc_ = "Minimizize and restorizor windizows"
  260.  
  261.     minimizers = {}
  262.     oldWindowProcs = {}
  263.  
  264.     def __init__(self):
  265.         self.iconinfo = None
  266.         self.wclassName = ctypes.c_wchar_p(u"PCF:DTV:Minimize:MessageWindowClass")
  267.         self.wname = ctypes.c_wchar_p(u"PCF:DTV:Minimize:MessageWindow")
  268.         self._gethrefcomp = components.classes["@participatoryculture.org/dtv/gethref;1"].getService(components.interfaces.pcfIDTVGetHREF)
  269.         self.hInst = ctypes.windll.kernel32.GetModuleHandleW(0)
  270.         self.wndClass = WNDCLASSEX(ctypes.sizeof(WNDCLASSEX),
  271.                                    ctypes.c_uint(0x4200), # CS_NOCLOSE | CS_GLOBALCLASS
  272.                                    WindProc,
  273.                                    0,
  274.                                    0,
  275.                                    self.hInst,
  276.                                    0,
  277.                                    0,
  278.                                    0,
  279.                                    0,
  280.                                    self.wclassName,
  281.                                    0)
  282.         ctypes.windll.user32.RegisterClassExW(ctypes.byref(self.wndClass))
  283.         self.trayIconWindow = ctypes.windll.user32.CreateWindowExW(
  284.             WS_EX_APPWINDOW,
  285.             self.wclassName,
  286.             self.wname,
  287.             0x20000000L, #WS_MINIMIZE
  288.             0x80000000, #CW_USEDEFAULT
  289.             0x80000000, #CW_USEDEFAULT
  290.             0x80000000, #CW_USEDEFAULT
  291.             0x80000000, #CW_USEDEFAULT
  292.             ctypes.windll.user32.GetDesktopWindow(),
  293.             0,
  294.             self.hInst,
  295.             0)
  296.         Minimize.minimizers[self.trayIconWindow] = self
  297.  
  298.         # By default, everything uses the XULRunner icon
  299.         # Use the Democracy icon instead
  300.         self.iconloc = ctypes.c_wchar_p(resources.path("..\\%s.ico")%config.get(prefs.SHORT_APP_NAME))
  301.         self.hIcon = ctypes.windll.user32.LoadImageW(0, self.iconloc, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
  302.  
  303.         self.minimized = []
  304.  
  305.     def updateIcon(self):
  306.         if config.get(prefs.MINIMIZE_TO_TRAY):
  307.             self.addTrayIcon()
  308.         else:
  309.             self.delTrayIcon()
  310.  
  311.     def __del__(self):
  312.         del Minimize.minimizers[self.trayIconWindow]
  313.         self.delTrayIcon()
  314.  
  315.     def registerMainWindowProc(self, win):
  316.         self.window = win
  317.         href = self.getHREFFromDOMWindow(win)
  318.         Minimize.oldWindowProcs[href.value] = ctypes.windll.user32.SetWindowLongW(href,GWL_WNDPROC, MainWindProc)
  319.  
  320.     def contextMenuHack(self):
  321.         """Hack to make context menus work, must be called BEFORE the menu is
  322.         shown.
  323.         """
  324.         # Need to make the XUL window the foreground window.  See 
  325.         # http://support.microsoft.com/kb/135788
  326.         # If the msdn URL doesn't work, try searhing for Q135788
  327.         ctypes.windll.user32.SetForegroundWindow(self.trayIconWindow)
  328.  
  329.     def contextMenuHack2(self):
  330.         """Hack to make context menus work, must be called AFTER the menu is
  331.         shown.
  332.         """
  333.         ctypes.windll.user32.PostMessageA(self.trayIconWindow, WM_NULL, 0, 0)
  334.  
  335.     def getHREFFromBaseWindow(self, win):
  336.         return ctypes.c_int(self._gethrefcomp.getit(win))
  337.  
  338.     def getHREFFromDOMWindow(self,win):
  339.         win = win.queryInterface(components.interfaces.nsIInterfaceRequestor)
  340.         win = win.getInterface(components.interfaces.nsIWebNavigation)
  341.         win = win.queryInterface(components.interfaces.nsIDocShellTreeItem)
  342.         win = win.treeOwner
  343.         win = win.queryInterface(components.interfaces.nsIInterfaceRequestor)
  344.         win = win.getInterface(components.interfaces.nsIXULWindow)
  345.         win = win.docShell
  346.         win = win.queryInterface(components.interfaces.nsIDocShell)
  347.         win = win.queryInterface(components.interfaces.nsIBaseWindow)
  348.         return self.getHREFFromBaseWindow(win)
  349.  
  350.     def minimizeAll(self):
  351.         self.minimized = []
  352.         mediator = components.classes["@mozilla.org/appshell/window-mediator;1"].getService(components.interfaces.nsIWindowMediator)
  353.         winList = mediator.getEnumerator(None)
  354.         while (winList.hasMoreElements()):
  355.             win = winList.getNext()
  356.             href = self.getHREFFromDOMWindow(win)
  357.             self.minimized.append(href)
  358.             self.minimize(href)
  359.  
  360.     def addTrayIcon(self):
  361.         self.iconinfo = NOTIFYICONDATA(ctypes.sizeof(NOTIFYICONDATA),
  362.                                   self.trayIconWindow,
  363.                                   1,
  364.                                   NIF_ICON | NIF_MESSAGE | NIF_TIP,
  365.                                   WM_TRAYICON,
  366.                                   self.hIcon,
  367.                                   config.get(prefs.LONG_APP_NAME),
  368.                                   0,
  369.                                   0,
  370.                                   config.get(prefs.LONG_APP_NAME)+" is AWESOME",
  371.                                   TIMEOUT(0,0),
  372.                                   config.get(prefs.LONG_APP_NAME)+" is COOL",
  373.                                   0,
  374.                                   GUID(0,0,0,(ctypes.c_byte*8)(0,0,0,0,0,0,0,0)),
  375.                                   0
  376.                                   )
  377.         ctypes.windll.shell32.Shell_NotifyIconW(NIM_ADD,ctypes.byref(self.iconinfo))
  378.  
  379.     def delTrayIcon(self):
  380.         if self.iconinfo:
  381.             ctypes.windll.shell32.Shell_NotifyIconW(NIM_DELETE,ctypes.byref(self.iconinfo))
  382.  
  383.     def getTrayRect(self):
  384.         trayRect = RECT(0,0,0,0)
  385.  
  386.         # I don't think this ever actually works with modern windows,
  387.         # but gAIM does this, so I'll do it to like the sheep I am -NN
  388.         trayWindow = ctypes.windll.user32.FindWindowExW(0,0,"Shell_TrayWnd",0)
  389.         if trayWindow != 0:
  390.             trayNotifyWindow = ctypes.windll.user32.FindWindowEx(trayWindow,0,"TrayNotifyWnd",0)
  391.             if trayNotifyWindow != 0:
  392.                 ctypes.windll.user32.GetWindowRect(trayNotifyWindow, ctypes.byref(trayRect))
  393.                 return trayRect
  394.  
  395.         # That hack didn't work, let's find the tray notify window the
  396.         # by finding the task bar
  397.         appBarData = APPBARDATA(0,0,0,0,RECT(0,0,0,0),0)
  398.         if (ctypes.windll.shell32.SHAppBarMessage(ABM_GETTASKBARPOS,ctypes.byref(appBarData)) != 0):
  399.             if appBarData.uEdge in [ABE_LEFT, ABE_RIGHT]:
  400.                 trayRect.top=appBarData.rc.bottom-100
  401.                 trayRect.bottom=appBarData.rc.bottom-16
  402.                 trayRect.left=appBarData.rc.left
  403.                 trayRect.right=appBarData.rc.right
  404.             else: # ABE_TOP, ABE_BOTTOM
  405.                 trayRect.top=appBarData.rc.top
  406.                 trayRect.bottom=appBarData.rc.bottom
  407.                 trayRect.left=appBarData.rc.right-100
  408.                 trayRect.right=appBarData.rc.right-16
  409.             return trayRect
  410.         # Give up
  411.         return None
  412.     
  413.     def showPopup(self, x, y):
  414.         jsbridge = components.classes["@participatoryculture.org/dtv/jsbridge;1"].getService(components.interfaces.pcfIDTVJSBridge)
  415.         jsbridge.showPopup(x, y)
  416.  
  417.     def minimize(self, href):
  418.         fromer = RECT(0,0,0,0)
  419.         ctypes.windll.user32.GetWindowRect(href,ctypes.byref(fromer))
  420.         to = self.getTrayRect()
  421.         if to:
  422.             ctypes.windll.user32.DrawAnimatedRects(href,IDANI_CAPTION,ctypes.byref(fromer),ctypes.byref(to))
  423.         ctypes.windll.user32.ShowWindow(href, ctypes.c_int(SW_HIDE))
  424.  
  425.     def restore(self, href):
  426.         placement = WINDOWPLACEMENT()
  427.         rv = ctypes.windll.user32.GetWindowPlacement(href, 
  428.                 ctypes.byref(placement))
  429.         if rv and placement.showCmd == SW_SHOWMINIMIZED:
  430.             show_flag = SW_RESTORE
  431.         else:
  432.             show_flag = SW_SHOW
  433.         ctypes.windll.user32.ShowWindow(href, ctypes.c_int(show_flag))
  434.         ctypes.windll.user32.SetForegroundWindow(href)
  435.  
  436.         fromer = RECT(0,0,0,0)
  437.         ctypes.windll.user32.GetWindowRect(href,ctypes.byref(fromer))
  438.         to = self.getTrayRect()
  439.         if to:
  440.             ctypes.windll.user32.DrawAnimatedRects(href,IDANI_CAPTION,ctypes.byref(to),ctypes.byref(fromer))
  441.  
  442.  
  443.     def minimizeOrRestore(self):
  444.         pybridge = components.classes["@participatoryculture.org/dtv/pybridge;1"].getService(components.interfaces.pcfIDTVPyBridge)
  445.         if len(self.minimized) > 0:
  446.             for href in self.minimized:
  447.                 self.restore(href)
  448.             self.minimized = []
  449.         else:
  450.             self.minimizeAll()
  451.             pybridge.pause()
  452.         pybridge.updateTrayMenus()
  453.  
  454.     def isMinimized(self):
  455.         for mini in Minimize.minimizers.values():
  456.             if len(mini.minimized) > 0:
  457.                 return True
  458.         return False
  459.         
  460.  
  461. def configDidChange(key, value):
  462.     if key is prefs.MINIMIZE_TO_TRAY.key:
  463.         for mini in Minimize.minimizers:
  464.             if value:
  465.                 Minimize.minimizers[mini].addTrayIcon()
  466.             else:
  467.                 Minimize.minimizers[mini].delTrayIcon()
  468.  
  469. config.addChangeCallback(configDidChange)
  470.