// Harpoon.cc --- ActivityMonitor for W32
//
// Copyright (C) 2007 Ray Satiro <raysatiro@yahoo.com>
// Copyright (C) 2007, 2008, 2010 Rob Caelers <robc@krandor.org>
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//

#define _CRT_SECURE_NO_WARNINGS
#include <string>

#include <windows.h>
#include <winuser.h>
#include "HarpoonHelper.h"
#include "Debug.h"
#include "Config.h"

using namespace std;

HarpoonHelper::HarpoonHelper(char *args)
  : args(args)
{
}

HarpoonHelper::~HarpoonHelper()
{
  terminate();
}

bool
HarpoonHelper::init(HINSTANCE hInstance)
{
  TRACE_ENTER("HarpoonHelper::init");
  this->hInstance = hInstance;

  DWORD dwStyle, dwExStyle;

  dwStyle = WS_OVERLAPPED;
  dwExStyle = WS_EX_TOOLWINDOW;

  WNDCLASSEX wclass = {
    sizeof(WNDCLASSEX), 0, harpoon_window_proc, 0, 0, hInstance, NULL, NULL, NULL, NULL, HARPOON_HELPER_WINDOW_CLASS, NULL};

  notification_class = RegisterClassEx(&wclass);
  if (!notification_class)
    return FALSE;

  notification_window = CreateWindowEx(dwExStyle,
                                       HARPOON_HELPER_WINDOW_CLASS,
                                       HARPOON_HELPER_WINDOW_CLASS,
                                       dwStyle,
                                       CW_USEDEFAULT,
                                       CW_USEDEFAULT,
                                       CW_USEDEFAULT,
                                       CW_USEDEFAULT,
                                       NULL,
                                       NULL,
                                       hInstance,
                                       NULL);

  if (!notification_window)
    {
      UnregisterClass(HARPOON_WINDOW_CLASS, hInstance);
      notification_class = 0;
      return FALSE;
    }

  init_critical_filename_list();

  bool debug = false;
  bool mouse_lowlevel = false;
  bool keyboard_lowlevel = true;

  if (LOBYTE(LOWORD(GetVersion())) >= 6)
    {
      mouse_lowlevel = true;
    }

  Config config;
  config.get_value("advanced/harpoon/debug", debug);
  config.get_value("advanced/harpoon/mouse_lowlevel", mouse_lowlevel);
  config.get_value("advanced/harpoon/keyboard_lowlevel", keyboard_lowlevel);

  if (!harpoon_init(critical_filename_list, (BOOL)debug))
    {
      TRACE_RETURN(false);
      return false;
    }

  TRACE_RETURN(true);
  return true;
}

//! Stops the activity monitoring.
void
HarpoonHelper::terminate()
{
  harpoon_exit();
}

void
HarpoonHelper::run()
{
  TRACE_ENTER("HarpoonHelper::run");
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  TRACE_EXIT();
}

void
HarpoonHelper::init_critical_filename_list()
{
  TRACE_ENTER("HarpoonHelper::init_critical_filename_list");
  Config config;
  int i;

  for (i = 0; i < HARPOON_MAX_UNBLOCKED_APPS; ++i)
    critical_filename_list[i][0] = '\0';

  // Task Manager is always on the critical_filename_list
  if (GetVersion() >= 0x80000000)
    {
      // Windows Me/98/95
      strcpy(critical_filename_list[0], "taskman.exe");
    }
  else if (!check_for_taskmgr_debugger(critical_filename_list[0]))
    {
      strcpy(critical_filename_list[0], "taskmgr.exe");
    }

  strcpy(critical_filename_list[1], "workrave.exe");
  strcpy(critical_filename_list[2], args);

  TRACE_MSG(args);

  int filecount = 0;
  config.get_value("advanced/critical_files/filecount", filecount);

  if (filecount > 0)
    {
      if (filecount >= HARPOON_MAX_UNBLOCKED_APPS - 2)
        {
          filecount = HARPOON_MAX_UNBLOCKED_APPS - 3;
        }

      char loc[40];
      string buffer;

      for (i = 1; i <= filecount; ++i)
        {
          sprintf(loc, "advanced/critical_files/file%d", i);
          if (config.get_value(loc, buffer))
            {
              strncpy_s(critical_filename_list[i + 2], buffer.c_str(), 510);
              critical_filename_list[i][510] = '\0';
            }
        }
    }
  TRACE_EXIT();
}

bool
HarpoonHelper::check_for_taskmgr_debugger(char *out)
{
  HKEY hKey = NULL;
  LONG err;
  DWORD size;
  unsigned char *p, *p2, *buffer;

  // If there is a debugger for taskmgr, it's always critical
  err = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
                      "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
                      "Image File Execution Options\\taskmgr.exe",
                      0,
                      KEY_QUERY_VALUE,
                      &hKey);

  if (err != ERROR_SUCCESS)
    {
      RegCloseKey(hKey);
      return false;
    }

  // get the size, in bytes, required for buffer
  err = RegQueryValueExA(hKey, "Debugger", NULL, NULL, NULL, &size);

  if (err != ERROR_SUCCESS || !size)
    {
      RegCloseKey(hKey);
      return false;
    }

  if (!(buffer = (unsigned char *)malloc(size + 1)))
    {
      RegCloseKey(hKey);
      return false;
    }

  err = RegQueryValueExA(hKey, "Debugger", NULL, NULL, (LPBYTE)buffer, &size);

  if (err != ERROR_SUCCESS || !size)
    {
      free(buffer);
      RegCloseKey(hKey);
      return false;
    }

  buffer[size] = '\0';

  // get to innermost quoted
  for (p2 = buffer; *p2 == '\"'; ++p2)
    ;
  if (p2 != buffer)
    // e.g. "my debugger.exe" /y /x
    {
      if ((p = _mbschr(p2, '\"')))
        *p = '\0';
    }
  else
    // e.g. debugger.exe /y /x
    {
      if ((p = _mbschr(p2, ' ')))
        *p = '\0';
    }

  // Search the path to find where the filename starts:
  if ((p = (unsigned char *)_mbsrchr(p2, '\\')))
    // Point to first (mb) filename character
    ++p;
  else
    // No path.
    p = p2;

  _mbstrncpy_lowercase(out, (char *)p, 510);
  out[510] = '\0';

  RegCloseKey(hKey);
  free(buffer);
  return true;
}

LRESULT CALLBACK
HarpoonHelper::harpoon_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  TRACE_ENTER("HarpoonHelper::harpoon_window_proc");
  int evt_type;
  evt_type = uMsg - WM_USER;

  TRACE_MSG(evt_type);
  if (evt_type >= 0 && evt_type < HARPOON_HELPER_EVENT__SIZEOF)
    {
      switch ((HarpoonHelperEventType)evt_type)
        {
        case HARPOON_HELPER_INIT:
          TRACE_MSG("init");
          break;

        case HARPOON_HELPER_EXIT:
          TRACE_MSG("exit");
          PostQuitMessage(0);
          break;

        case HARPOON_HELPER_BLOCK:
          TRACE_MSG("block");
          harpoon_block_input();
          break;

        case HARPOON_HELPER_UNBLOCK:
          TRACE_MSG("unblock");
          harpoon_unblock_input();
          break;
        }
    }

  TRACE_EXIT();
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
