/***************************************************
* FILE NAME: advsplash.cpp
*
* Copyright 2003 - Present NSIS
*
* PURPOSE:
*    Splash screen in NSIS installers with fading
*    and transparency effects
*
* CHANGE HISTORY
*
* Author              Date          Modifications
*
* Justin                            Original
* Amir Szekely (kichik)      Converted to a plugin DLL
* Nik Medved (brainsucker)   Fading and transparency by 
* Takhir Bedertdinov 10-26-2004  Gif and jpeg support
*                    06-25-2005  modeless mode
**************************************************/


// For layered windows
#define _WIN32_WINNT 0x0500

#include <windows.h>
#include <windowsx.h>
#include <olectl.h>
#include <stdio.h>
#include <vfw.h>
#include "..\exdll\exdll.h"

#ifndef WS_EX_LAYERED 
#define WS_EX_LAYERED     0x00080000 
#define LWA_COLORKEY 0x00000001 
#define LWA_ALPHA     0x00000002 
#endif // ndef WS_EX_LAYERED 

#define RESOLUTION 32 // 30 fps ;) (32? I like SHR more than iDIV ;)
#define MAX_SHOW_TIME 60000


enum FADE_STATES {
   FADE_IN,
   FADE_SLEEP,
   FADE_OUT
};

const char classname[4] = "_sp";
HWND mciWnd = NULL, myWnd = NULL, hParent = NULL;
HINSTANCE g_hInstance;
HANDLE hThread = NULL;
BITMAP bm;
HBITMAP g_hbm;
int resolution = RESOLUTION;
int sleep_val, fadein_val, fadeout_val, state, timeleft, keycolor, alphaparam;
LPPICTURE pIPicture = NULL;
bool modal = true, nocancel = false;
UINT timerEvent;

typedef BOOL (_stdcall *_tSetLayeredWindowAttributesProc)(HWND hwnd, // handle to the layered window
  COLORREF crKey,      // specifies the color key
  BYTE bAlpha,         // value for the blend function
  DWORD dwFlags        // action
);
_tSetLayeredWindowAttributesProc SetLayeredWindowAttributesProc;

int getTransparencyColor(char* fn_src);


void sf(HWND hw)
{
   DWORD ctid = GetCurrentThreadId();
   DWORD ftid = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
   AttachThreadInput(ftid, ctid, TRUE);
   SetForegroundWindow(hw);
   AttachThreadInput(ftid, ctid, FALSE);
}

/*****************************************************
 * FUNCTION NAME: WndProc()
 * PURPOSE: 
 *    window proc. 
 * SPECIAL CONSIDERATIONS:
 *    for paint purposes mainly
 *****************************************************/
static LRESULT CALLBACK WndProc(HWND hwnd,
                                UINT uMsg,
                                WPARAM wParam,
                                LPARAM lParam)
{
   PAINTSTRUCT ps;
   RECT r;
   long w, h;

   switch (uMsg)
   {
   case WM_CREATE:
      SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0);
      SetWindowLong(hwnd,GWL_STYLE,0);

	  SetWindowPos(hwnd,NULL,
      r.left+(r.right-r.left-bm.bmWidth)/2,
      r.top+(r.bottom-r.top-bm.bmHeight)/2,
      bm.bmWidth,bm.bmHeight,
      SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOACTIVATE);

	  return 0;

   case WM_PAINT:
      BeginPaint(hwnd,&ps);
      if(pIPicture != NULL)
      {
         GetClientRect(hwnd,&r);
         pIPicture->get_Width(&w);
         pIPicture->get_Height(&h);
         pIPicture->Render(ps.hdc, r.left,
            r.top, r.right - r.left, r.bottom - r.top, 0, h, w, -h, &r);
      }
      EndPaint(hwnd,&ps);

   case WM_CLOSE:
      return 0;

   case WM_LBUTTONDOWN:
      if(nocancel) break;
   case WM_TIMER:
      timeKillEvent(timerEvent);
      sf(hwnd);
      PlaySound(0, 0, 0);
      DestroyWindow(hwnd);
      break;
   }
   return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

/*****************************************************
 * FUNCTION NAME: SetTransparentRegion()
 * PURPOSE: 
 *    creates window region
 * SPECIAL CONSIDERATIONS:
 *    kind of skin creation from bmp
 *****************************************************/
void SetTransparentRegion(HWND myWnd)
{
    HDC dc;
    int x, y;
    HRGN region, cutrgn;
    BITMAPINFO bmi = {{sizeof(BITMAPINFOHEADER),bm.bmWidth,bm.bmHeight,1,32,BI_RGB,0,0,0,0,0}};
    int size = bm.bmWidth * bm.bmHeight*4;
    int *pbmp = (int*)GlobalAlloc(GPTR, size),
       *bmp;

    bmp = pbmp;
    dc = CreateCompatibleDC(NULL);
    SelectObject(dc, g_hbm);

    x = GetDIBits(dc, g_hbm, 0, bm.bmHeight, bmp, &bmi, DIB_RGB_COLORS);
    region = CreateRectRgn(0,0,bm.bmWidth,bm.bmHeight);

// Search for transparent pixels 
   for (y = bm.bmHeight-1; y >= 0; y--)
      for (x = 0; x < bm.bmWidth; )
         if ((*bmp & 0xFFFFFF) == keycolor) 
         {
            int j = x;
            while ((x < bm.bmWidth) && ((*bmp & 0xFFFFFF) == keycolor)) bmp++, x++;

// Cut transparent pixels from the original region
            cutrgn = CreateRectRgn(j, y, x, y+1);
            CombineRgn(region, region, cutrgn, RGN_XOR);
            DeleteObject(cutrgn);
         } else bmp++, x++;
                                
// Set resulting region.
   SetWindowRgn(myWnd, region, TRUE);
   DeleteObject(region);
   DeleteObject(dc);
   GlobalFree(pbmp);
}

/*****************************************************
 * FUNCTION NAME: TimeProc()
 * PURPOSE: 
 *    fade in/out tracking
 * SPECIAL CONSIDERATIONS:
 *    works without WM_TIMER
 *****************************************************/
void CALLBACK TimeProc(UINT uID,      
                       UINT uMsg,     
                       DWORD dwUser,  
                       DWORD dw1,     
                       DWORD dw2)
{
   int call = -1;

   switch (state)
   {
   case FADE_IN:
      if (timeleft == 0)
      {
         timeleft = sleep_val;
         state++;
         if (SetLayeredWindowAttributesProc != NULL) call = 255;                                   
      }
      else
      {
         call = ((fadein_val-timeleft)*255)/fadein_val;
         break;
      }
   case FADE_SLEEP:
      if (timeleft == 0)
      {
         timeleft = fadeout_val;
         state++;                        
      }
      else break;
   case FADE_OUT:
      if (timeleft == 0)
      {
         PostMessage((HWND)dwUser, WM_TIMER, 0, 0);
         return;
      }
      else
      {
         call = ((timeleft)*255)/fadeout_val; break;
      }
   }
// Transparency value aquired, and could be set...
   if ((call >= 0) && SetLayeredWindowAttributesProc != NULL)
      SetLayeredWindowAttributesProc((HWND)dwUser, keycolor, 
                                     call, 
                                     alphaparam);                
// Time is running out...
   timeleft--;
}

/*****************************************************
 * FUNCTION NAME: play()
 * PURPOSE: 
 *    PlaySound dll entry point
 * SPECIAL CONSIDERATIONS:
 *    makes PlaySound working on Win98
 *    works with /NOUNLOAD parameter only
 *    wav files are huge :-(
 *    but not flushes on the screen like mci window does
 *    Use it BEFORE 'show' call
 *****************************************************/
extern "C"
void __declspec(dllexport) play(HWND hwndParent,
                                int string_size,
                                char *variables,
                                stack_t **stacktop)
{
   char fn[MAX_PATH];
   char cmd[32] = "play";
   EXDLL_INIT();
   if(popstring(fn) == 0)
   {
      if(_stricmp(fn, "/loop") == 0)
      {
         strcat(cmd, " repeat");
         *fn = 0;
         popstring(fn);
      }
      if(*fn == 0 && mciWnd != NULL && IsWindow(mciWnd))
      {
         MCIWndDestroy(mciWnd);
         mciWnd = NULL;
      }
      else
      {
         if((mciWnd = MCIWndCreate(hwndParent, 0, MCIWNDF_NOPLAYBAR, fn)) != NULL)
         {
            ShowWindow(mciWnd, SW_HIDE);
            if(MCIWndCanPlay(mciWnd))
               MCIWndSendString(mciWnd, cmd);
         }
      }
   }
}

/*****************************************************
 * FUNCTION NAME: splashThread()
 * PURPOSE: 
 *    PlaySound dll entry point
 * SPECIAL CONSIDERATIONS:
 *    makes PlaySound working on Win98
 *    works with /NOUNLOAD parameter only
 *    wav files are huge :-(
 *    but not flushes on the screen like mci window does
 *    Use it BEFORE 'show' call
 *****************************************************/
DWORD WINAPI splashThread(LPVOID lpParameter) {
	MSG msg;
   static WNDCLASS wc;
   wc.lpfnWndProc = WndProc;
   wc.hInstance = g_hInstance;
   wc.hCursor = LoadCursor(NULL,IDC_ARROW);
   wc.lpszClassName = classname;
   if (RegisterClass(&wc)) 
   {
      pIPicture->get_Handle((OLE_HANDLE*)&g_hbm);
      GetObject((HGDIOBJ)g_hbm, sizeof(BITMAP), &bm);

      myWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST |
         (SetLayeredWindowAttributesProc != NULL ? WS_EX_LAYERED : 0),
         classname, classname, 0, 0, 0, 0, 0,
         hParent, NULL, g_hInstance, NULL);
// Set transparency / key color
      if (SetLayeredWindowAttributesProc != NULL)
         SetLayeredWindowAttributesProc(myWnd, keycolor, 
                                fadein_val > 0 ? 0 : 255, 
                                alphaparam);
      if(keycolor != -1)
         SetTransparentRegion(myWnd);
                        
// Start up timer...
      state = FADE_IN;
      timeleft = fadein_val;
      timerEvent = timeSetEvent(resolution, RESOLUTION/4, TimeProc,
               (DWORD)myWnd, TIME_PERIODIC);


      while (IsWindow(myWnd) && GetMessage(&msg,myWnd,0,0))
      {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
      }

// Stop currently playing wave, we want to exit
      if(mciWnd != NULL && IsWindow(mciWnd)) MCIWndDestroy(mciWnd);

      pIPicture->Release();
      pIPicture = NULL; // do not re-use with /NOUNLOAD
// We should UnRegister class, since Windows NT series never does this by itself
      UnregisterClass(wc.lpszClassName, g_hInstance);
   }

	myWnd = NULL;
   PlaySound(0, 0, 0);

	return 0;
}


/*****************************************************
 * FUNCTION NAME: show()
 * PURPOSE: 
 *    image banner "show" dll entry point
 * SPECIAL CONSIDERATIONS:
 *    
 *****************************************************/
extern "C"
void __declspec(dllexport) show(HWND hwndParent,
                                int string_size,
                                char *variables,
                                stack_t **stacktop)
{
   DEVMODE dm;
   char fn[MAX_PATH];
   unsigned short wcPort[MAX_PATH];
   DWORD dwThreadId;
	DWORD dwMainThreadId = GetCurrentThreadId();

   EXDLL_INIT();
   hParent = hwndParent;

// 3 fade parameters and keycolor from stack come first
   popstring(fn);
   sleep_val = strtol(fn, NULL, 0);
   popstring(fn);
   fadein_val = strtol(fn, NULL, 0);
   popstring(fn);
   fadeout_val = strtol(fn, NULL, 0);
   popstring(fn);
   keycolor = strtol(fn, NULL, 0);

// other parameters (if any) + filename
   while(!popstring(fn) && fn[0] == '/')
   {
      if(_stricmp(fn, "/nocancel") == 0)
         nocancel = true;
      else modal = false;
   }

   if(keycolor == -2)
      keycolor = getTransparencyColor(fn);

// Check for winXP/2k at 32 bpp transparency support
   dm.dmSize = sizeof(DEVMODE);
   EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm);
   if(dm.dmBitsPerPel >= 32)
   {
      HANDLE user32 = GetModuleHandle("user32");
      SetLayeredWindowAttributesProc = (_tSetLayeredWindowAttributesProc)
         GetProcAddress((HINSTANCE)user32, "SetLayeredWindowAttributes");
   }

   if (SetLayeredWindowAttributesProc == NULL)
   {
// Fading+transparency is unsupported at old windows versions...
      resolution = (sleep_val + fadein_val + fadeout_val) / 2; 
      fadeout_val = fadein_val = 0;
      sleep_val = 1;
   }
   else 
   {
// div them by resolution - 32 steps of fade in/out
      sleep_val >>= 5;
      fadein_val >>= 5;
      fadeout_val >>= 5;
// MS RGB - BGR format
      alphaparam = LWA_ALPHA | (keycolor == -1 ? 0 : LWA_COLORKEY);
      if(keycolor != -1)
         keycolor = ((keycolor & 0xFF) << 16) + (keycolor & 0xFF00) +
            ((keycolor & 0xFF0000) >> 16);
   }


// who wrote this script :-))
   MultiByteToWideChar(CP_ACP,0,fn,-1, wcPort,sizeof(wcPort)/2);
   OleLoadPicturePath(wcPort, 0, 0, 0, IID_IPicture, (void**)&pIPicture);
   if (pIPicture == NULL ||
      (sleep_val+fadein_val+fadeout_val) <= 0)
      return;

   if(modal)
   {
      splashThread(0);
   }
   else
   {
// start thread and wait for window initialization
      hThread = CreateThread(0, 0, splashThread, NULL, 0, &dwThreadId);
	   while (myWnd == NULL || !IsWindowVisible(myWnd))
			   Sleep(10);
      sf(myWnd);
   }
   
}


/*****************************************************
 * FUNCTION NAME: wait()
 * PURPOSE: 
 *    image banner "wait" dll entry point
 * SPECIAL CONSIDERATIONS:
 *    
 *****************************************************/
extern "C"
void __declspec(dllexport) wait(HWND hwndParent,
                                int string_size,
                                char *variables,
                                stack_t **stacktop)
{
   if(!modal && hThread != NULL)
   {
      WaitForSingleObject(hThread, MAX_SHOW_TIME);
	   CloseHandle(hThread);
   }
}

/*****************************************************
 * FUNCTION NAME: stop()
 * PURPOSE: 
 *    image banner "stop" dll entry point
 * SPECIAL CONSIDERATIONS:
 *    
 *****************************************************/
extern "C"
void __declspec(dllexport) stop(HWND hwndParent,
                                int string_size,
                                char *variables,
                                stack_t **stacktop)
{
   if(!modal && hThread != NULL)
   {
      if(IsWindow(myWnd))
      {
         SendMessage(myWnd, WM_TIMER, 0, 0);
      }
      WaitForSingleObject(hThread, 1000);
	   CloseHandle(hThread);
   }
}

/*****************************************************
 * FUNCTION NAME: hwnd()
 * PURPOSE: 
 *    retrieves target window handle
 * SPECIAL CONSIDERATIONS:
 *    
 *****************************************************/
extern "C"
void __declspec(dllexport) hwnd(HWND hwndParent,
                                int string_size,
                                char *variables,
                                stack_t **stacktop)
{
   char s[16] = "";
   if(IsWindow(myWnd))
      wsprintf(s, "%d", myWnd);
   pushstring(s);
}


/*****************************************************
 * FUNCTION NAME: DllMain()
 * PURPOSE: 
 *    Dll main (initialization) entry point
 * SPECIAL CONSIDERATIONS:
 *    
 *****************************************************/
BOOL WINAPI DllMain(HANDLE hInst,
                    ULONG ul_reason_for_call,
                    LPVOID lpReserved)
{
   g_hInstance = (HINSTANCE)hInst;
   return TRUE;
}


