/// HAL.cpp
///
/// (c) 2001-2018 by Bastian Spiegel <bs@tkscript.de>
///     - Distributed under terms of the Lesser GNU General Public License (LGPL).
///       See COPYING and <http://www.gnu.org/licenses/licenses.html#LGPL> for further information.
///

/// Experimental: Define this to implement waitVBlank() on Linux. Does not work with NVidia closedsource drivers.
//#define USE_DRM_IOCTL_WAIT_VBLANK defined

//#define DEBUG_WINEVENT_CREATE_DESTROY defined


#include <stdio.h>
#include <stdlib.h>

#define YAC_PRINTF defined
#include <yac.h>

#ifdef YAC_WIN32
#include <windows.h>
#include <ddraw.h>
#include <Commctrl.h>
#endif

#ifdef YAC_LINUX
#include <unistd.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <pthread.h>
#include <errno.h>
#include <syscall.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
static struct timeval tv_start;

////#include <sched.h>
#ifdef USE_DRM_IOCTL_WAIT_VBLANK
#include <fcntl.h>
extern "C" {
#define class class__
#include <drm/drm.h>
#undef class
}
#include <sys/ioctl.h> 
#endif // USE_DRM_IOCTL_WAIT_VBLANK

#endif // YAC_LINUX


#include "HAL.h"
#include "Joystick.h"
#include "Key.h"
#include "Mouse.h"

extern YAC_Host *yac_host;
extern sUI clid_Joystick;
extern sUI clid_Key;

#include "../tkopengl/inc_opengl.h"

#include "sdl_includes.h"

static SDL_SysWMinfo syswminfo;

#include "Bitmap.h"
#include "HAL.h"

#include "tksdl_hooks.h"

tksdl_hook_t tksdl_hook_open;
tksdl_hook_t tksdl_hook_close;

extern _HAL *tks_HAL;

extern void*sc_context; // script execution context for HAL callbacks (onReopen, onKeyboard, onMouse, ...)

// ---- SDL "scrap" clipboard support (x11/qnx/win32)
#ifdef TKSDL_SCRAP_CLIPBOARD
extern int init_scrap(void);
#endif


#include "../tkopengl/tkopengl_shared.h"
#include "../tkopengl/tkopengl_shared.cpp"


// Statically allocated strings for sendEvent()
//  (note) dynamically allocating strings in sendEvent and deleting it in the event handler
//          causes random memory leaks since apparently not every sent event is handled/received
#define TKSDL_MAX_EVENT_STRINGS 256  // must be POT
static YAC_String *event_strings[TKSDL_MAX_EVENT_STRINGS];
static sUI next_event_string;


#ifdef DX_OMAP3
#include "omap3.h"
#endif // DX_OMAP3


#ifdef YAC_WIN32
static HANDLE win32_timerthread = NULL;
static HANDLE win32_timerevent = NULL;


static DWORD WINAPI tksdl_timer__threadentry(LPVOID _hal) {
   _SDL *hal = (_SDL*)_hal;

   yac_host->printf("[dbg] HAL::timerthread: threadId=%d\n", GetCurrentThreadId());

   for(;;)
   {
      DWORD res = ::WaitForSingleObject(win32_timerevent, hal->timer_interval);
      if(WAIT_TIMEOUT == res)
      {
         hal->sendTimerEvent();
      }
      else
      {
         yac_host->printf("[dbg] HAL::timerthread: exiting (res=0x%08x)\n", res);
         break;
      }
   }

   return 0;
}
#endif // YAC_WIN32


#ifdef YAC_LINUX
static sBool b_timerthread_running = YAC_FALSE;
static pthread_t timerthread_id;
static int timerthread_fd = -1;

static void *tksdl_timer__threadentry(void *_hal) {
   _SDL *hal = (_SDL*)_hal;

   yac_host->printf("[dbg] HAL::timerthread: threadId=" YAC_PRINTF_PTRPREFIX "%p LWPid=%d\n", pthread_self(), syscall(SYS_gettid));

   timerthread_fd = timerfd_create(CLOCK_MONOTONIC, 0/*flags*/);

   if(-1 != timerthread_fd)
   {
      struct itimerspec it;

      it.it_value.tv_sec = hal->timer_interval / 1000;
      it.it_value.tv_nsec = (hal->timer_interval % 1000) * (1000 * 1000);
         
      it.it_interval = it.it_value;

      int err = timerfd_settime(timerthread_fd,
                                0, /* flags */
                                &it,
                                NULL /* old */
                                );
      if(0 == err)
      {
         b_timerthread_running = YAC_TRUE;

         while(b_timerthread_running)
         {
            sU64 numExp;

            ssize_t numRead = read(timerthread_fd, &numExp, 8);

            if(8 == numRead)
            {
               hal->sendTimerEvent();
            }
            else
            {
               yac_host->printf("[~~~] HAL::timerthread: read() failed. numRead=%d, errno=%d(\"%s\").\n",
                                numRead, errno, strerror(errno)
                                );
            }
         }

         close(timerthread_fd);
         timerthread_fd = -1;
      }
      else
      {
         yac_host->printf("[---] HAL::timerthread: timerfd_settime() failed. errno=%d(\"%s\").\n",
                          errno, strerror(errno)
                          );
      }
   }
   else
   {
      yac_host->printf("[---] HAL::timerthread: timerfd_create() failed. errno=%d(\"%s\").\n",
                       errno, strerror(errno)
                       );
   }

   return NULL;
}
#endif // YAC_LINUX


static void hook_call(tksdl_hook_t *hook) {

   while(NULL != hook)
   {
      if(NULL != hook->fxn)
      {
         ////printf("xxx tksdl::hook_call: hook.fxn=0x%p\n", hook->fxn);
         hook->fxn();
      }
      
      hook = hook->next;
   }
}


_HAL::_HAL(void) {
   b_unicode         = 0;
   b_running         = 0;
   b_graphics_active = 0;
   b_need_redraw     = 0;
   b_event_polling   = 1;
   b_double_buffer   = 1;
   b_redrawing       = 0;
   b_allow_redraw    = 1;
   b_glerror         = 0;
   b_onclose_pending = 0;
   swap_interval     = 1;
   
   mtx_event_queued = yac_host->yacMutexCreate();
   b_timer_event_queued = 0;
   b_redraw_event_queued = 0;

   max_frame_deltatime = 0; //(1000.0f/99999999.0); 20;
   
   multisample_buffers = 0;
   multisample_samples = 0;

   stencil_bits = 0;
   
   int i;
   for(i=0; i<TKS_HAL_NUMSIGNALS; i++)
   {
      signal_funs[i] = NULL;
   }

   mouse.show                  = 1;
   mouse.def_auto_hide_timeout = 1500;
   mouse.auto_hide_timeout     = 0;
   mouse.pointer_mode          = TKS_MOUSE_POINTER_MOVE;
   mouse.b_touch_input         = YAC_FALSE;
   
   joysticks.enabled  = 0;
   joysticks.num      = 0;
   joysticks.elements = NULL;
   
   redraw_flag=TKS_REDRAW_ALL;
   
   win_sx     = 0;
   win_sy     = 0;
   fs_sx      = 0; // default = desktop resolution
   fs_sy      = 0;
   fs_z       = 32;
   view_sx    = 0;
   view_sy    = 0;
   view_z     = 0;
   view_flags = 0;
   tks_flags  = SDL_RESIZABLE;

   desktop_sx = 0;
   desktop_sy = 0;
   
#ifdef YAC_WIN32
   lpDD           = NULL;
   dw_monitorfreq = 0;
#endif
   
   audio.dsp_frequency = 44100.0f; // overwritten by AudioDevice::openDSP()
   
   fps.tick_buffer   = 10;
   fps.tick_interval = 1000.0f/60;
   fps.limit_fps     = 0;
   
   mouse.last_absx = 999;
   mouse.last_absy = 999;

#ifdef YAC_LINUX
   gettimeofday(&tv_start, NULL);
#endif // YAC_LINUX

   timer_interval = 0; // Disable timer

   // Export address of b_glerror flag for tkopengl plugin ('abuses' the callback API..)
   yac_host->yacCallbackSetFunByName("tksdl_b_glerror", (YAC_CFunctionPtr)&b_glerror);

   // Init event strings
   {
      sUI i;
      for(i=0; i<TKSDL_MAX_EVENT_STRINGS; i++)
      {
         event_strings[i] = YAC_New_String();
      }
      next_event_string = 0;
   }

   b_window_visible = false;
}

_HAL::~_HAL() {

   // yac_host->printf("xxx ~HAL 1 (ENTER)\n");

   if(::SDL_WasInit(SDL_INIT_VIDEO))
   {
      yac_host->printf("[dbg] tksdl::~HAL: SDL_QuitSubSystem(SDL_INIT_VIDEO)\n");
      ::SDL_QuitSubSystem(SDL_INIT_VIDEO);
      b_window_visible = false;
   }

   // yac_host->printf("xxx ~HAL 2\n");


#ifdef YAC_WIN32
   if(lpDD)
   {
      lpDD->Release();
      lpDD = NULL;
   }
#endif

   // yac_host->printf("xxx ~HAL 3 (LEAVE)\n");

   // Delete event strings
   {
      sUI i;
      for(i=0; i<TKSDL_MAX_EVENT_STRINGS; i++)
      {
         YAC_DELETE(event_strings[i]);
         event_strings[i] = NULL;
      }
   }

   yac_host->yacMutexDestroy(mtx_event_queued);
} 

void YAC_VCALL _HAL::yacGetSignalStringList(YAC_String *_sig) {
   // ---- the number represents the encoded argument type identifiers used by this function
   // ---- 1=int, 2=float, 3=object. bits 0+1 represent argument #0, bits 2+3 represent argument #1 and so on
   _sig->visit("onSignal:1 onKeyboard:3 onMouse:85 onJoyAxisMotion:21 onJoyButton:21 onJoyHatMotion:21 onJoyBallMotion:85 onDraw:0 onTick:0 onClose:0 onEvent:3 onOpen:0 onResize:0 onExpose:0 onApplicationActive:1 onMouseFocusActive:1 onIdle:0 onInputFocusActive:1 onDropFiles:53 onTimer:0 onCreateOtherWindow:1 onFocusOtherWindow:1");
}

void YAC_VCALL _HAL::yacRegisterSignal(sUI _id, YAC_FunctionHandle _fun) {
   ////printf("xxx tksdl::yacRegisterSignal: id=%u fun=0x%p\n", _id, _fun);
   if(_id < TKS_HAL_NUMSIGNALS)
   {
      signal_funs[_id] = _fun;
   }
}

sSI _HAL::_getNumJoysticks(void) {
   return joysticks.num;
}

void _HAL::_getJoystick(sSI _idx, YAC_Value *_r) {
   if(((sUI)_idx) < ((sUI)joysticks.num))
   {
      _Joystick *j = &((_Joystick*)joysticks.elements)[_idx];
      YAC_RETO(j, 0);
   }
   else
   {
      YAC_RETO(0, 0);
   }
}

void _HAL::_updateJoysticks(void) {
   if(joysticks.enabled)
   {
      ::SDL_JoystickUpdate();
   }
}


void _HAL::_triggerEvent(YAC_Object *_s) {
   YAC_String *s;

   if(YAC_BCHK(_s, YAC_CLID_STRING))
   {
      s = (YAC_String*)_s;
   }
   else
   {
      s = NULL;
   }

   if(NULL != s)
   {
      YAC_FunctionHandle f = signal_funs[TKS_SIG_HALEVENT];

      if(NULL != f)
      {
         YAC_Value args[1];
         args[0].initString(s, 0);
         yac_host->yacEvalFunction(sc_context, f, 1, args);
      }
   }
}

void _HAL::_sendEvent(YAC_Object *_s) {
   // yac_host->printf("xxx SDL::sendEvent\n");

   if(YAC_BCHK(_s, YAC_CLID_STRING))
   {
      SDL_Event user_event;

      // YAC_String *ns = YAC_New_String();  // memleak!
      YAC_String *es = event_strings[next_event_string];
      next_event_string = (next_event_string + 1) & (TKSDL_MAX_EVENT_STRINGS - 1);
      es->yacCopy((YAC_String*)_s);

      user_event.type       = SDL_USEREVENT;
      user_event.user.code  = TKS_EVENT_USER;
      user_event.user.data1 = (void *)es;
      user_event.user.data2 = (void *)size_t(_getTicks());

      ::SDL_PushEvent(&user_event);
   }
}

void _HAL::sendTimerEvent(void) {

   yac_host->yacMutexLock(mtx_event_queued);

   if(!b_timer_event_queued)
   {
      b_timer_event_queued = 1;

      SDL_Event user_event;
     
      // yac_host->printf("xxx SDL::sendTimerEvent\n");
      
      // called from timerthread
      
      user_event.type       = SDL_USEREVENT;
      user_event.user.code  = TKS_EVENT_TIMER;
      user_event.user.data1 = 0;
      user_event.user.data2 = 0;//(void *)_getTicks();
      
      ::SDL_PushEvent(&user_event);
   }

   yac_host->yacMutexUnlock(mtx_event_queued);
}

void _HAL::sendRedrawEvent(void) {

   yac_host->yacMutexLock(mtx_event_queued);

   if(!b_redraw_event_queued)
   {
      b_redraw_event_queued = 1;

      SDL_Event user_event;
      
      // yac_host->printf("xxx SDL::sendRedrawEvent\n");
      
      // usually called from UI thread (via Viewport.needRedraw())
      
      user_event.type       = SDL_USEREVENT;
      user_event.user.code  = TKS_EVENT_REDRAW;
      user_event.user.data1 = 0;
      user_event.user.data2 = 0;
      
      ::SDL_PushEvent(&user_event);
   }

   yac_host->yacMutexUnlock(mtx_event_queued);
}

void _HAL::startTimerThread(void) {

   stopTimerThread();

#ifdef YAC_WIN32
   win32_timerevent = ::CreateEvent(NULL, 1/*_bManualReset*/, 0, NULL);
   win32_timerthread = ::CreateThread( 
      NULL,              // default security attributes
      0,                 // use default stack size  
      tksdl_timer__threadentry,// thread function 
      (LPVOID)this,      // argument to thread function 
      0,                 // use default creation flags 
      NULL               // returns the thread identifier
                                       );
#endif // YAC_WIN32

#ifdef YAC_LINUX
   (void)pthread_create(&timerthread_id, NULL, tksdl_timer__threadentry, (void*) this);
#endif // YAC_LINUX
}

void _HAL::stopTimerThread(void) {
#ifdef YAC_WIN32
   if(NULL != win32_timerthread)
   {
      ::SetEvent(win32_timerevent);
      WaitForSingleObject(win32_timerthread, 3000); // wait for thread to exit 
      CloseHandle(win32_timerthread);
      CloseHandle(win32_timerevent);
      win32_timerthread = NULL;
      win32_timerevent = NULL;
   }
#endif // YAC_WIN32

#ifdef YAC_LINUX
   if(b_timerthread_running)
   {
      b_timerthread_running = YAC_FALSE;

      pthread_join(timerthread_id, NULL);
   }
#endif // YAC_LINUX
}

void _HAL::_enableKeyRepeat(sSI _d, sSI _r) {
   ::SDL_EnableKeyRepeat(_d, _r);
}

void _HAL::openJoysticks(void) {
   // ---- do not open twice ----
   if(!joysticks.enabled)
   {
      joysticks.enabled = 1;
      joysticks.num     = ::SDL_NumJoysticks();

      _Joystick *jo = new _Joystick[joysticks.num]();
      joysticks.elements = (void*)jo;

      int i;
      for(i=0; i<joysticks.num; i++)
      {
         jo[i].class_ID=clid_Joystick;
         jo[i].open(i);
      }
   }
}

void _HAL::closeJoysticks(void) {

   if(joysticks.enabled)
   {
      joysticks.enabled = 0;

      _Joystick * jo = (_Joystick*) joysticks.elements;

      if(NULL != jo)
      {
         int i;

         for(i=0; i<joysticks.num; i++)
         {
            jo[i].close();
         }

         delete [] jo;

         joysticks.elements = NULL;
         joysticks.num      = 0;
      }
   }
}


void _HAL::_exitEventLoop(void) {

   // printf("xxx HAL::exitEventLoop 1\n");

   if(b_graphics_active)
   {
      showCursor(1);
   }

   closeView();

   // printf("xxx HAL::exitEventLoop 2\n");

   stopTimerThread();

   b_running = 0;

   // printf("xxx HAL::exitEventLoop 3 (LEAVE)\n");
}

void _HAL::tick(void) {
   void *f = signal_funs[TKS_SIG_HALTICK];
   if(f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }
}

void _HAL::updateFPSAndLimit(void) {
   sSI dt;

   // (note) 'now' is set in loopDelay()

   // (note) this is called after SwapBuffers() has been called (in endPaint())
   //         nowadays GL drivers do not seem to wait for vsync in SwapBuffers() so
   //         to avoid high CPU load the thread is put to sleep for a while

   // yac_host->printf("xxx nfr=%d\n", (fps.num_frames_rendered&127));

   if(fps.limit_fps)
   {
      // dt = Delta between now (after graphics calls have been issued/swapbuffer has been called) and start of frame
      dt = _getTicks() - now;

      // yac_host->printf("xxx dt since framestart=%d\n", dt);

      if(dt<0) dt=0; // wrap-around

      sSI limit = 1000 / fps.limit_fps; // desired tickinterval
      if(dt < limit)
      {
#if 0 //def YAC_LINUX

         // Note: YMMV, as they say. On my relatively new Core2Duo notebook,
         //       this loop pushes the CPUload through the roof.
         //       On my old Celeron notebook, this works fine.

         sUI al = fps.last_frametime + (sUI)limit;
         while(_getTicks() < al)
         {
            // sched_yield()
            usleep(0);
         }
#else
         sUI millisecDelay = (sUI)(limit-dt);
         // if(!(fps.num_frames_rendered&127))
         // {
         //    yac_host->printf("xxx delay=%u\n", millisecDelay);
         // }
         ////::Sleep(millisecDelay);
         ::SDL_Delay(millisecDelay);
#endif // YAC_LINUX
      }
      else
      {
         // yac_host->printf("xxx dt=%d limit=%d\n", dt, limit);
      }

      // now = _getTicks();
      // dt  = now - fps.last_frametime;
   }

   // Delta between start of this frame / start of last frame
   dt = now - fps.last_frametime;
   // yac_host->printf("xxx dt=%d\n", dt);
   if(dt<0) dt=0; // wrap-around

   // Calculate precision and extrapolated frames per second
   if(tks_HAL->fps.b_fullframe)
   {
      dt = (sSI) (1000.0f / 60.0f);
   }

   sF32 np = ((sF32)dt) / fps.tick_interval;
   fps.precision      = fps.precision+(np-fps.precision)*0.75f;
   if(dt > 0)
   {
      fps.current_fps    = 1000.0f/((sF32)dt);
   }
   fps.last_frametime = now;
   fps.total_time    += dt;
   if(fps.num_frames_rendered > 0)
   {
      fps.average_fps    = ((sF32)fps.total_time)/((sF32)fps.num_frames_rendered);
      fps.average_fps    = 1000.0f/fps.average_fps;
   }

   // Calculate frames per second
   dt = now - fps.last_secondtime;

   if(dt < 0)
   {
      dt = 0; // wrap-around
   }

   if(dt >= 1000 && fps.second_frames_rendered)
   {
      fps.real_fps               = ((sF32)dt) / ((sF32)fps.second_frames_rendered);
      fps.real_fps               = (1000.0f / fps.real_fps);
      fps.last_secondtime        = now;
      fps.second_frames_rendered = 0;
   }
}

void _HAL::draw(void) {

   if(b_graphics_active)
   {

      if(!fps.last_frametime)
      {
         fps.last_frametime = now;
         fps.precision      = 1.0f;
      }

      if(!fps.last_secondtime)
      {
         fps.last_secondtime = now;
      }

      if(TKS_REDRAW_ALL == redraw_flag)
      {
         if(b_allow_redraw)
         {
            void *f = signal_funs[TKS_SIG_HALDRAW];
            if(f)
            {
               callOnIdle();

               beginPaint();
               // sSI dt = _getTicks();
               yac_host->yacEvalFunction(sc_context, f, 0, 0);
               /////glGetError(); // testcase, takes ~16ms w/ NVidia win32 driver (285.62, GF 9800 GT)
               // dt = _getTicks() - dt;
               // yac_host->printf("xxx onDraw took %d ms\n", dt);
               endPaint();
            }
         }
      }
      else
      {
         if(b_allow_redraw)
         {
            if(b_need_redraw)
            {
               b_need_redraw = 0;

               void *f = signal_funs[TKS_SIG_HALDRAW];
               if(f)
               {
                  beginPaint();
                  yac_host->yacEvalFunction(sc_context, f, 0, 0);
                  endPaint();
               }
            }
         }

         // Call idle functions 120 times per second (max)
         if( (now - fps.last_idletime) >= (1000/120) )
         {
            fps.last_idletime = now;

            callOnIdle();
         }

         //
         // (note) the Sleep(8) / SDL_Delay(8) call seems to burn a lot of cycles (!) (WinVista 32bit)
         //        CPU load was reduced from ~8% to ~2% (Syntracker2 application) simply by commenting out the following line !
         // (note) bizarre: if SDL_Delay(8) is called, CPU load goes to 100% after moving a window (resizing is OK.) wth !? 
         //        w/ SDL_Delay(1) this does not happen!
         //
         ::SDL_Delay(1);
      }

   }
}


sBool _HAL::loopDelay(void) { 

   now = _getTicks();

   ////printf("xxx now=%d last_time=%d\n", now, last_time);
   
   if(!last_time)
   {
      last_time = now;
      return 0;
   }
   else
   {
      if((last_time + ((sU32)(fps.tick_buffer*fps.tick_interval))) <= now)
      {
         last_time = now;
         sU16 i;
         for(i=0; i<fps.tick_buffer; i++) 
         {
            tick();
         }
         return 1;
      }
      else
      {
         if((last_time+((sU32)fps.tick_interval))<=now)
         {
            while(last_time<now)
            {
               tick();
               last_time += ((sU32)fps.tick_interval);
               if((last_time+fps.tick_interval*0.75f) >= now)
               {
                  return 1;
               }
            }
            ////last_time = now;
            return 1;
         }
         else 
         {
            return 0;
         }
      }
   }
}

void _HAL::initFPS(void) {
   fps.num_frames_rendered     = 0;
   fps.total_time              = 0;
   fps.last_frametime          = 0;
   fps.average_fps             = 0.0f; 
   fps.current_fps             = 0.0f;
   fps.real_fps                = 0.0f;
   fps.second_time             = 0;
   fps.last_secondtime         = 0;
   fps.second_frames_rendered  = 0;
   fps.precision               = 1.0;
   fps.last_idletime           = 0;

   now = last_time = 0;

   if(::getenv("TKSDL_FULLFRAME"))
   {
      printf("[dbg] tksdl::HAL: forcing precision=1.0 (relying on vsync)\n");
      fps.b_fullframe = 1;
   }
   else
   {
      fps.b_fullframe = 0;
   }

}

void _HAL::callOnKeyboard(sU32 _vkey, sU32 _vmod, sUI _unicode) {
   sU32 bp_vkey=_vkey;

   if(0 != bp_vkey)
   {
      _Key k;

      k.class_ID=clid_Key;
      k.vkey=bp_vkey;
      k.vmod=_vmod;
      k.unicode=_unicode;

      YAC_FunctionHandle f = signal_funs[TKS_SIG_HALKEYBOARD];

      if(NULL != f)
      {
         YAC_Value args[1];
         args[0].initObject(&k, 0);
         yac_host->yacEvalFunction(sc_context, f, 1, args);
      }
   }
}

void _HAL::callOnOpen(void) {

   // yac_host->printf("xxx tksdl: callOnOpen ENTER\n");

   ::SDL_WM_SetCaption((char*)viewport_caption.chars, 0);

   pushGLContext();

   hook_call(&tksdl_hook_open);

   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALOPEN];

   if(NULL != f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }

   b_onclose_pending = YAC_TRUE;

   popGLContext();

   // yac_host->printf("xxx tksdl: callOnOpen LEAVE\n");
}

sBool _HAL::callOnClose(sBool _bAllowScriptCbk) {
   sBool ret = YAC_TRUE;

   // yac_host->printf("xxx tksdl: callOnClose pending=%d\n", b_onclose_pending);

   if(b_onclose_pending)
   {
      YAC_FunctionHandle f = signal_funs[TKS_SIG_HALCLOSE];

      // // tkopengl_shared->_glFinish();

      pushGLContext();

      if(_bAllowScriptCbk)
      {
         if(NULL != f)
         {
            YAC_Value r;
            r.initInt(YAC_TRUE);
            ////yac_host->printf("xxx tksdl: callOnClose yacEvalFunctionReturn 1\n");
            yac_host->yacEvalFunctionReturn(sc_context, f, 0, 0, &r);
            ////yac_host->printf("xxx tksdl: callOnClose yacEvalFunctionReturn 2\n");
            ret = (YAC_FALSE != r.value.int_val);
            r.unsetFast();
         }
      }
      
      if(ret)
      {
         ////yac_host->printf("xxx tksdl: callOnClose hook_call 1\n");
         hook_call(&tksdl_hook_close);
         ////yac_host->printf("xxx tksdl: callOnClose hook_call 2\n");
         
#ifdef DX_OMAP3
         omap3_view_close();
#endif // DX_OMAP3

         b_onclose_pending = YAC_FALSE;
      }

      popGLContext();
   }

   // yac_host->printf("xxx tksdl: callOnClose LEAVE\n");

   return ret;
}

void _HAL::callOnResize(void) {

   // yac_host->printf("xxx tksdl: callOnResize\n");

   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALRESIZE];

   if(NULL != f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }

   callOnOpen();
}

void _HAL::callOnExpose(void) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALEXPOSE];

   if(NULL != f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }
}

void _HAL::callOnApplicationActive(sSI _gain) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALAPPLICATIONACTIVE];

   if(NULL != f)
   {
      YAC_Value args[1];
      args[0].initInt(_gain);
      yac_host->yacEvalFunction(sc_context, f, 1, args);
   }
}

void _HAL::callOnMouseFocusActive(sSI _gain) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALMOUSEFOCUSACTIVE];

   if(NULL != f)
   {
      YAC_Value args[1];
      args[0].initInt(_gain);
      yac_host->yacEvalFunction(sc_context, f, 1, args);
   }
}

void _HAL::callOnInputFocusActive(sSI _gain) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALINPUTFOCUSACTIVE];

   if(NULL != f)
   {
      YAC_Value args[1];
      args[0].initInt(_gain);
      yac_host->yacEvalFunction(sc_context, f, 1, args);
   }
}

void _HAL::callOnIdle(void) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALIDLE];

   if(NULL != f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }
}

void _HAL::callOnTimer(void) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALTIMER];

   if(NULL != f)
   {
      yac_host->yacEvalFunction(sc_context, f, 0, 0);
   }
}

void _HAL::callOnJoyAxisMotion(sSI _dev, sSI _axis, sSI _val) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALJOYAXISMOTION];

   if(NULL != f)
   {
      YAC_Value args[3];
      args[0].initInt(_dev);
      args[1].initInt(_axis);
      args[2].initInt(_val);
      yac_host->yacEvalFunction(sc_context, f, 3, args);
   }
}

void _HAL::callOnJoyButton(sSI _dev, sSI _button, sSI _pressed) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALJOYBUTTON];

   if(NULL != f)
   {
      YAC_Value args[3];
      args[0].initInt(_dev);
      args[1].initInt(_button);
      args[2].initInt(_pressed);
      yac_host->yacEvalFunction(sc_context, f, 3, args);
   }
}

void _HAL::callOnJoyHatMotion(sSI _dev, sSI _hat, sSI _state) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALJOYHATMOTION];

   if(NULL != f)
   {
      YAC_Value args[3];
      args[0].initInt(_dev);
      args[1].initInt(_hat);
      args[2].initInt(_state);
      yac_host->yacEvalFunction(sc_context, f, 3, args);
   }
}

void _HAL::callOnJoyBallMotion(sSI _dev, sSI _hat, sSI _dx, sSI _dy) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALJOYBALLMOTION];

   if(NULL != f)
   {
      YAC_Value args[4];
      args[0].initInt(_dev);
      args[1].initInt(_hat);
      args[2].initInt(_dx);
      args[3].initInt(_dy);
      yac_host->yacEvalFunction(sc_context, f, 4, args);
   }
}

void _HAL::callOnMouse(sSI _mx, sSI _my, sSI _cstate, sSI _nstate) {
   mouse.cstate = (sU16)_cstate;
   mouse.nstate = (sU16)_nstate;

   // yac_host->printf("callMouseHandler: _mx=%i _my=%i absx=%i absy=%i cstate=%04x nstate=%04x grabbed=%i\n", _mx, _my, mouse.absx, mouse.absy, _cstate, _nstate, mouse.grabbed);

   if(mouse.b_warp_changed && (0==_nstate))
   {
      mouse.b_warp_changed = 0;
      return;
   }

   // if(_mx == tks_HAL->view_sx / 2)
   // {
   //    if(_my == tks_HAL->view_sy / 2)
   //    {
   //       yac_host->printf("xxx mouse: is center\n");
   //       if(tks_HAL->b_fullscreen)
   //       {
   //          _mx = tks_HAL->mouse.last_absx;
   //          _my = tks_HAL->mouse.last_absy;
   //          yac_host->printf("mouse WARP to last_abs=(%d; %d)\n", mouse.last_absx, mouse.last_absy);
   //          ::SDL_WarpMouse(_mx, _my);
   //       }
   //    }
   // }

   if(mouse.grabbed)
   {
      if(mouse.b_warp_changed)
      {
         mouse.dx = 0;
         mouse.dy = 0;
         mouse.b_warp_changed = 0;
      }
      else if(mouse.b_grab_changed)
      {
         mouse.b_grab_changed = 0;
         mouse.dx = _mx - tks_HAL->mouse.grab_start_x;
         mouse.dy = _my - tks_HAL->mouse.grab_start_y;
      }
      else
      {
         mouse.dx = _mx - tks_HAL->view_sx / 2;
         mouse.dy = _my - tks_HAL->view_sy / 2;
      }
      
      mouse.warp_sx = tks_HAL->view_sx / 2;
      mouse.warp_sy = tks_HAL->view_sy / 2;
      _mx = mouse.dx;
      _my = mouse.dy;
      mouse.absx += _mx;
      mouse.absy += _my;
      
      if(mouse.absx < 0) 
      {
         mouse.absx = 0;
      }
      else if(mouse.absx >= tks_HAL->view_sx)
      {
         mouse.absx = tks_HAL->view_sx-1;
      }
      if(mouse.absy < 0)
      {
         mouse.absy = 0;
      }
      else if(mouse.absy >= tks_HAL->view_sy)
      {
         mouse.absy = tks_HAL->view_sy-1;
      }

      _mx = mouse.absx;
      _my = mouse.absy;
   }
   else
   {
      if(mouse.b_warp_changed)
      {
         mouse.dx = 0;
         mouse.dy = 0;
         mouse.b_warp_changed = 0;
      }
      else if(mouse.b_grab_changed)
      {
         mouse.b_grab_changed = 0;
         mouse.dx = _mx - mouse.grab_start_x;
         mouse.dy = _my - mouse.grab_start_y;
      }
      else
      {
         mouse.dx = _mx-mouse.absx;
         mouse.dy = _my-mouse.absy;
      }

      mouse.absx = _mx;
      mouse.absy = _my;
   }

   mouse.last_absx = mouse.absx;
   mouse.last_absy = mouse.absy;

   // yac_host->printf("mouse last_abs=(%d; %d)\n", mouse.last_absx, mouse.last_absy);
   
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALMOUSE];

   if(NULL != f)
   {
      YAC_Value args[4];
      args[0].initInt(_mx);
      args[1].initInt(_my);
      args[2].initInt(_cstate);
      args[3].initInt(_nstate);
      yac_host->yacEvalFunction(sc_context, f, 4, args);
   }
}

void _HAL::callOnCreateOtherWindow(sUI _hwnd) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALCREATEOTHERWINDOW];

   if(NULL != f)
   {
      YAC_Value args[1];
      args[0].initInt(sSI(_hwnd));
      yac_host->yacEvalFunction(sc_context, f, 1, args);
   }
}

void _HAL::callOnFocusOtherWindow(sUI _hwnd) {
   YAC_FunctionHandle f = signal_funs[TKS_SIG_HALFOCUSOTHERWINDOW];

   if(NULL != f)
   {
      YAC_Value args[1];
      args[0].initInt(sSI(_hwnd));
      yac_host->yacEvalFunction(sc_context, f, 1, args);
   }
}

sBool _HAL::_pollEvent(void) {
   // hack to work around hanging VST plugins
   SDL_Event ev;
   int r = ::SDL_PollEvent(&ev);
   return r;
}

sBool _HAL::processSDLEvent(sBool _poll) {
   ////yac_host->printf("processSDLEvent\n");
   SDL_Event ev;
   int r;
   if(_poll) r=::SDL_PollEvent(&ev);
   else r=::SDL_WaitEvent(&ev);

   if(r)
   {
      pushGLContext();

      // yac_host->printf("[...] got SDL event. type=%i\n", ev.type);
      sU32 vkey=0;
      sU32 vmod=0;
      switch(ev.type)
      {
      case SDL_JOYAXISMOTION:
         callOnJoyAxisMotion(ev.jaxis.which, ev.jaxis.axis, ev.jaxis.value);
         break;
         
      case SDL_JOYBUTTONDOWN:
      case SDL_JOYBUTTONUP:
         callOnJoyButton(ev.jbutton.which, ev.jbutton.button, ev.jbutton.state==SDL_PRESSED);
         break;
         
      case SDL_JOYHATMOTION:
         callOnJoyHatMotion(ev.jhat.which, ev.jhat.hat, ev.jhat.value);
         break;
         
      case SDL_JOYBALLMOTION:
         callOnJoyBallMotion(ev.jball.which, ev.jball.ball, ev.jball.xrel, ev.jball.yrel);
         break;
         
      case SDL_VIDEORESIZE:
         {
            tks_HAL->openView(ev.resize.w, ev.resize.h, 0/**z**/, 0/**fs**/, 1/**resize**/);
            tks_HAL->callOnResize();
         }
         break;
         
      case SDL_USEREVENT:
         if(TKS_EVENT_USER == ev.user.code)
         {
            if(ev.user.data1)
            {
               // ---- deliver delayed onAction event ----
               _triggerEvent((YAC_Object*)ev.user.data1);
               // YAC_DELETE( ((YAC_Object*)ev.user.data1) ); // memleak! (now using event_strings instead)
               ev.user.data1=0;
            }
         }
         else if(TKS_EVENT_TIMER == ev.user.code)
         {
            yac_host->yacMutexLock(mtx_event_queued);
            b_timer_event_queued = 0;
            yac_host->yacMutexUnlock(mtx_event_queued);

            callOnTimer();
         }
         else
         {
            // TKS_EVENT_REDRAW (just to wakeup from WaitEvent)
            yac_host->yacMutexLock(mtx_event_queued);
            b_redraw_event_queued = 0;
            yac_host->yacMutexUnlock(mtx_event_queued);
         }
         break;

      case SDL_ACTIVEEVENT:
         if( (ev.active.state & SDL_APPACTIVE) == SDL_APPACTIVE)
         {
            if(ev.active.gain)
            {
               b_graphics_active=1;
               // ...

               // Hack to work-around lost user events
               b_timer_event_queued = 0;
               b_redraw_event_queued = 0;
            }
            callOnApplicationActive((sSI)ev.active.gain);
         }
         if( (ev.active.state & SDL_APPMOUSEFOCUS) == SDL_APPMOUSEFOCUS)
         {
            if(ev.active.gain)
            {
               if(mouse.pointer_mode==TKS_MOUSE_POINTER_ENTER)
               {
                  mouse.auto_hide_timeout=mouse.def_auto_hide_timeout;
                  mouse.show=TKS_MOUSE_SHOW_ENABLE;
               }
            }
            callOnMouseFocusActive((sSI)ev.active.gain);
         }
         if( (ev.active.state & SDL_APPINPUTFOCUS) == SDL_APPINPUTFOCUS)
         {
            callOnInputFocusActive((sSI)ev.active.gain);
         }
         break;
         
      case SDL_VIDEOEXPOSE:
         updateUnicode();
         b_graphics_active=1;
         callOnExpose();
         break;

      case SDL_KEYDOWN:
         vkey|=VKEY_PRESSED;
         
      case SDL_KEYUP:
         vkey|=ev.key.keysym.sym;
         vmod=ev.key.keysym.mod&~KMOD_NUM;

         // (note) lctrl-a..z unicodes are reported as 1..26
         // yac_host->printf("vkey=%08x vmod=%08x unicode=%i enableUNICODE=%i.\n", vkey, vmod, (sUI)ev.key.keysym.unicode, ::SDL_EnableUNICODE(-1));
         callOnKeyboard(vkey, vmod, (sUI)ev.key.keysym.unicode);

#ifdef YAC_LINUX
         // Fixes a bug witnessed under linux where the unicode field is always 0 until another application
         // has been focused and then the sdl window is activated again
         updateUnicode();
#endif
         break;
         
      case SDL_MOUSEBUTTONUP:
         switch(ev.button.button)
         {
         case SDL_BUTTON_LEFT:
            mouse.cstate&=~TKS_MOUSE_LBUTTON;
            mouse.nstate=TKS_MOUSE_LBUTTON;
            break;
         case SDL_BUTTON_RIGHT:
            mouse.cstate&=~TKS_MOUSE_RBUTTON;
            mouse.nstate=TKS_MOUSE_RBUTTON;
            break;
         case SDL_BUTTON_MIDDLE:
            mouse.cstate&=~TKS_MOUSE_MBUTTON;
            mouse.nstate=TKS_MOUSE_MBUTTON;
            break;
         case SDL_BUTTON_WHEELUP:
            mouse.cstate&=~TKS_MOUSE_WHEELUP;
            mouse.nstate=TKS_MOUSE_WHEELUP;
            break;
         case SDL_BUTTON_WHEELDOWN:
            mouse.cstate&=~TKS_MOUSE_WHEELDOWN;
            mouse.nstate=TKS_MOUSE_WHEELDOWN;
            break;
         }

         if(!mouse.b_touch_input)
         {
            callOnMouse(ev.button.x, ev.button.y, mouse.cstate, mouse.nstate);
         }
         break;
         
     case SDL_MOUSEBUTTONDOWN:
         switch(ev.button.button)
         {
         case SDL_BUTTON_LEFT:
            mouse.cstate|=TKS_MOUSE_LBUTTON;
            mouse.nstate=TKS_MOUSE_LBUTTON;
            break;
         case SDL_BUTTON_RIGHT:
            mouse.cstate|=TKS_MOUSE_RBUTTON;
            mouse.nstate=TKS_MOUSE_RBUTTON;
            break;
         case SDL_BUTTON_MIDDLE:
            mouse.cstate|=TKS_MOUSE_MBUTTON;
            mouse.nstate=TKS_MOUSE_MBUTTON;
            break;
         case SDL_BUTTON_WHEELUP:
            mouse.cstate|=TKS_MOUSE_WHEELUP;
            mouse.nstate=TKS_MOUSE_WHEELUP;
            break;
         case SDL_BUTTON_WHEELDOWN:
            mouse.cstate|=TKS_MOUSE_WHEELDOWN;
            mouse.nstate=TKS_MOUSE_WHEELDOWN;
            break;
         }

         if(!mouse.b_touch_input)
         {
            callOnMouse(ev.button.x, ev.button.y, mouse.cstate, mouse.nstate);
         }
         break;
            
     case SDL_MOUSEMOTION:
        if(!mouse.b_touch_input)
        {
           callOnMouse(ev.motion.x, ev.motion.y, mouse.cstate, 0);
        }

        if(!mouse.grabbed && mouse.pointer_mode==TKS_MOUSE_POINTER_MOVE)
        {
           mouse.auto_hide_timeout = mouse.def_auto_hide_timeout;
           mouse.show              = TKS_MOUSE_SHOW_ENABLE;
        }
        else
        {
// // #ifdef YAC_WIN32
// //            // workaround to keep mouse pointer hidden
// //            //TKS_SHOWCURSOR(mouse.show);
// // #endif
        }
        break;

#ifdef YAC_WIN32
     case SDL_SYSWMEVENT:
     {
        SDL_SysWMEvent *wm = &ev.syswm;
        // printf("xxx tksdl: SDL_SYSWMEVENT: msg=0x%08x (%d)\n", wm->msg->msg, wm->msg->msg);
        switch(wm->msg->msg)
        {
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))
#define GET_POINTERID_WPARAM(wParam)                (LOWORD(wParam))
#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag)    (((DWORD)HIWORD(wParam) & (flag)) == (flag))
#define IS_POINTER_NEW_WPARAM(wParam)               IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW)
#define IS_POINTER_INRANGE_WPARAM(wParam)           IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE)
#define IS_POINTER_INCONTACT_WPARAM(wParam)         IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT)
#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam)       IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON)
#define IS_POINTER_SECONDBUTTON_WPARAM(wParam)      IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON)
#define IS_POINTER_THIRDBUTTON_WPARAM(wParam)       IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON)
#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam)      IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON)
#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam)       IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON)
#define IS_POINTER_PRIMARY_WPARAM(wParam)           IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY)
#define HAS_POINTER_CONFIDENCE_WPARAM(wParam)       IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE)
#define IS_POINTER_CANCELED_WPARAM(wParam)          IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED)
#endif
           case WM_POINTERDOWN:
           {
              POINT p;
              p.x = GET_X_LPARAM(wm->msg->lParam);
              p.y = GET_Y_LPARAM(wm->msg->lParam);
              (void)::ScreenToClient(syswminfo.window, &p);
              // sBool bPress = (0 != IS_POINTER_FIRSTBUTTON_WPARAM(wm->msg->wParam));
              // yac_host->printf("xxx tksdl:: WM_POINTERDOWN p=(%d; %d) bPress=%d\n", x, y, bPress);
              mouse.cstate = 1;
              mouse.nstate = 1;
              callOnMouse(p.x, p.y, mouse.cstate, mouse.nstate);
           }
           break;

           case WM_POINTERUP:
           {
              POINT p;
              p.x = GET_X_LPARAM(wm->msg->lParam);
              p.y = GET_Y_LPARAM(wm->msg->lParam);
              (void)::ScreenToClient(syswminfo.window, &p);
              // sBool bPress = (0 != IS_POINTER_FIRSTBUTTON_WPARAM(wm->msg->wParam));
              // yac_host->printf("xxx tksdl:: WM_POINTERUP p=(%d; %d) bPress=%d\n", x, y, bPress);
              mouse.cstate = 0;
              mouse.nstate = 1;
              callOnMouse(p.x, p.y, mouse.cstate, mouse.nstate);
           }
           break;

           case WM_POINTERENTER:
           {
              POINT p;
              p.x = GET_X_LPARAM(wm->msg->lParam);
              p.y = GET_Y_LPARAM(wm->msg->lParam);
              (void)::ScreenToClient(syswminfo.window, &p);
              // sBool bPress = (0 != IS_POINTER_FIRSTBUTTON_WPARAM(wm->msg->wParam));
              // yac_host->printf("xxx tksdl:: WM_POINTERENTER p=(%d; %d) bPress=%d\n", x, y, bPress);
              // mouse.nstate = bPress;
              // callOnMouse(x, y, mouse.cstate, mouse.nstate);
              // mouse.cstate = mouse.nstate;
           }
           break;

           case WM_POINTERLEAVE:
           {
              POINT p;
              p.x = GET_X_LPARAM(wm->msg->lParam);
              p.y = GET_Y_LPARAM(wm->msg->lParam);
              (void)::ScreenToClient(syswminfo.window, &p);
              // sBool bPress = (0 != IS_POINTER_FIRSTBUTTON_WPARAM(wm->msg->wParam));
              // yac_host->printf("xxx tksdl:: WM_POINTERLEAVE p=(%d; %d) bPress=%d\n", x, y, bPress);
           }
           break;

           case WM_POINTERUPDATE:
           {
              POINT p;
              p.x = GET_X_LPARAM(wm->msg->lParam);
              p.y = GET_Y_LPARAM(wm->msg->lParam);
              (void)::ScreenToClient(syswminfo.window, &p);
              // sBool bPress = (0 != IS_POINTER_FIRSTBUTTON_WPARAM(wm->msg->wParam));
              // yac_host->printf("xxx tksdl:: WM_POINTERUPDATE p=(%d; %d) bPress=%d\n", x, y, bPress);
              callOnMouse(p.x, p.y, mouse.cstate, 0);
           }
           break;

           case WM_DROPFILES:
           {
              HDROP hDrop = (HDROP) wm->msg->wParam;
              POINT p;
              if(TRUE == ::DragQueryPoint(hDrop, &p))
              {
                 ////printf("xxx tksdl: WM_DROPFILES: p=(%d; %d)\n", p.x, p.y);

                 UINT numFiles = ::DragQueryFile(hDrop, 0xffffffff, NULL, 0);
                 ////printf("xxx tksdl: WM_DROPFILES: dropped %u file(s)\n", numFiles);
                 if(numFiles > 0)
                 {
                    static char buf[MAX_PATH];
                    UINT i;

                    YAC_FunctionHandle f = signal_funs[TKS_SIG_HALDROPFILES];

                    if(NULL != f)
                    {
                       YAC_StringArray *a = YAC_New_StringArray();
                       a->yacArrayAlloc(numFiles, 0,0,0);
                       a->num_elements = a->max_elements;
                       for(i=0; i<numFiles; i++)
                       {
                          ::DragQueryFile(hDrop, i, buf, MAX_PATH);
                          ////printf("xxx tksdl: WM_DROPFILES: file[%d] = \"%s\"\n", i, buf);

                          YAC_Value r;
                          a->yacArrayGet(sc_context, i, &r);
                          YAC_String *s= r.value.string_val;
                          s->copy(buf);
                       }
                       YAC_Value args[3];
                       args[0].initInt(p.x);
                       args[1].initInt(p.y);
                       args[2].initObject(a, 1); // let runtime take care of array deletion
                       yac_host->yacEvalFunction(sc_context, f, 3, args);
                    }
                 }
              }
           }
           break;

           // case WM_DEVICECHANGE:

#if 0
           case 0x2cc:  // WM_TABLET_FIRST+0xc, sent when touchscreen is touched
              break;
#endif
              
        }
     }
     break;
#endif // YAC_WIN32
               
     case SDL_QUIT:
        // printf("xxx SDL_QUIT: hook_call hook=0x%p\n", &tksdl_hook_close);

        if(callOnClose(YAC_TRUE/*bAllowScriptCbk*/))
        {
           b_graphics_active = YAC_FALSE;

           // printf("xxx SDL_QUIT: stopTimerThread 1\n");

           stopTimerThread();

           // printf("xxx SDL_QUIT: stopTimerThread 2\n");

           b_running = 0;
        }
        break;

     default:
        break;
      }

     popGLContext();
     return 1;
   }
   return 0;
}

void _HAL::resetMouse(void) {
   mouse.absx           = 0;
   mouse.absy           = 0;
   mouse.dx             = 0;
   mouse.dy             = 0;
   mouse.cstate         = 0;
   mouse.nstate         = 0;
   mouse.grabbed        = 0;
   mouse.grab_start_x   = 0;
   mouse.grab_start_y   = 0;
   mouse.b_warp_changed = 0;
   mouse.warp_sx        = 0;
   mouse.warp_sy        = 0;
}

void _HAL::_eventLoop(void) {
   static sU32 last_ticks=0;
  
   b_running = 1;
   last_time = 0;
   now       = 0;

   // Start timer
   if(timer_interval > 0)
   {
      startTimerThread();
   }

   resetMouse();
   
   sUI numEventsSinceRedraw = 0;
   
   while(b_running)
   {
      b_running = yac_host->yacRunning();

      if(b_graphics_active)
      {
         // invoke trace function, redraws rt-graphics screen 

         if(loopDelay())
         {
            if( b_running )
            {
               //yac_host->printf("xxx processSDLEvent polling=%i\n", b_event_polling);
               if(processSDLEvent(b_event_polling))
               {
                  do 
                  {
                     numEventsSinceRedraw++; // Hack to refresh the screen if the event queue is flooded with mouse events

                     if(numEventsSinceRedraw >= 8)
                     {
                        numEventsSinceRedraw = 0;

                        if(b_need_redraw)
                        {
                           draw();
                        }
                     }

                     if(mouse.warp_sx)
                     {
                        // yac_host->printf("xxx SDL_WarpMouse to %d %d\n", mouse.warp_sx, mouse.warp_sy);
                        ::SDL_WarpMouse(mouse.warp_sx, mouse.warp_sy);
                        mouse.warp_sx        = 0;
                        mouse.b_warp_changed = 1;
                     }

                     // Note: hack to keep the cursor disabled/enabled
                     if(mouse.show==TKS_MOUSE_SHOW_ENABLE)
                     {
                        showCursor(1);
                     }
                     else
                     {
                        if(mouse.show==TKS_MOUSE_SHOW_DISABLE)
                        {
                           showCursor(0);
                        }
                     }

                  } while( b_running && processSDLEvent(1) );
               } // if processSDLEvent()
               else
               {
                  // Does not necessarely draw anything but 
                  //  also handles the# delay (via usleep) until the next frame has been reached (REDRAW_ALL).
                  //
                  //  In "dirty" redraw mode, this will call onDraw() if a redraw request is pending.
                  if(TKS_REDRAW_ALL != redraw_flag)
                  {
                     if(b_need_redraw)
                     {
                        draw();
                     }
                  }
               }
               
               // Mouse pointer auto-hiding
               sU32 delta_ticks;
               if(!last_ticks)
               {
                  last_ticks = _getTicks();
                  delta_ticks=0;
               }
               else
               {
                  delta_ticks = last_ticks;
                  last_ticks  = _getTicks();
                  delta_ticks = last_ticks - delta_ticks;
               }

               if(mouse.auto_hide_timeout)
               {
                  mouse.auto_hide_timeout-=delta_ticks;
                  if(((sSI)mouse.auto_hide_timeout)<=0)
                  {
                     mouse.auto_hide_timeout=0;
                     mouse.show=TKS_MOUSE_SHOW_DISABLE;
                  }
               }
            } // if b_running
         } // if loopDelay

         numEventsSinceRedraw = 0;
         draw();

      } // if b_graphics_active
      else
      {
         // ---- no graphics output ----
         ::SDL_Delay(10);
      }
   } // while b_running

   ///////(void)callOnClose();
}

void _HAL::_reportJoystickEvents(sSI _b) {
   ::SDL_JoystickEventState(_b?SDL_ENABLE:SDL_DISABLE);
}

void _HAL::_delay(sSI _to) {
   ::SDL_Delay((sU32)_to);
}

sUI _HAL::_getTicks(void) {
   /* (todo) use yac_host->yacMilliSeconds() */
#ifdef YAC_LINUX
   // Get current time and calc delta to window open
   struct timeval tv;
   gettimeofday(&tv, NULL);
   tv.tv_sec  = tv.tv_sec  - tv_start.tv_sec;
   tv.tv_usec = tv.tv_usec - tv_start.tv_usec;
   return (sUI) (tv.tv_sec*1000 + (tv.tv_usec / 1000));
#else
   return ::SDL_GetTicks();
#endif // YAC_LINUX
}

void _HAL::_setRedrawFlag(sSI _fl) {
   redraw_flag = (sU8)_fl;
}

sSI _HAL::_getRedrawFlag(void) {
   return redraw_flag;
}

void _HAL::loadGLDefaults(void) {
   tkopengl_shared->_glDisable(GL_BLEND);
   tkopengl_shared->_glDisable(GL_ALPHA_TEST);
   tkopengl_shared->_glDisable(GL_COLOR_LOGIC_OP);
   tkopengl_shared->_glDisable(GL_COLOR_MATERIAL);
   tkopengl_shared->_glDisable(GL_CULL_FACE);
   tkopengl_shared->_glDisable(GL_DEPTH_TEST);
   tkopengl_shared->_glDisable(GL_SCISSOR_TEST);
   tkopengl_shared->_glDisable(GL_STENCIL_TEST);
   tkopengl_shared->_glDepthFunc(GL_LESS);
   tkopengl_shared->_glClearDepth(1.0f);
   tkopengl_shared->_glDisable(GL_DITHER);
   tkopengl_shared->_glDisable(GL_FOG);
   tkopengl_shared->_glDisable(GL_INDEX_LOGIC_OP);
   tkopengl_shared->_glDisable(GL_LIGHT0);
   tkopengl_shared->_glDisable(GL_LIGHT1);
   tkopengl_shared->_glDisable(GL_LIGHT2);
   tkopengl_shared->_glDisable(GL_LIGHT3);
   tkopengl_shared->_glDisable(GL_LIGHT4);
   tkopengl_shared->_glDisable(GL_LIGHT5);
   tkopengl_shared->_glDisable(GL_LIGHT6);
   tkopengl_shared->_glDisable(GL_LIGHT7);
   tkopengl_shared->_glDisable(GL_LIGHTING);
   tkopengl_shared->_glDisable(GL_LINE_SMOOTH);
   tkopengl_shared->_glDisable(GL_LINE_STIPPLE);
   // tkopengl_shared->_glDisable(GL_MAP1_COLOR_4);
   // tkopengl_shared->_glDisable(GL_MAP1_INDEX);
   // tkopengl_shared->_glDisable(GL_MAP1_NORMAL);
   // tkopengl_shared->_glDisable(GL_MAP1_TEXTURE_COORD_1);
   // tkopengl_shared->_glDisable(GL_MAP1_TEXTURE_COORD_2);
   // tkopengl_shared->_glDisable(GL_MAP1_TEXTURE_COORD_3);
   // tkopengl_shared->_glDisable(GL_MAP1_TEXTURE_COORD_4);
   // tkopengl_shared->_glDisable(GL_MAP1_VERTEX_3);
   // tkopengl_shared->_glDisable(GL_MAP1_VERTEX_4);
   // tkopengl_shared->_glDisable(GL_MAP2_COLOR_4);
   // tkopengl_shared->_glDisable(GL_MAP2_INDEX);
   // tkopengl_shared->_glDisable(GL_MAP2_NORMAL);
   // tkopengl_shared->_glDisable(GL_MAP2_TEXTURE_COORD_1);
   // tkopengl_shared->_glDisable(GL_MAP2_TEXTURE_COORD_2);
   // tkopengl_shared->_glDisable(GL_MAP2_TEXTURE_COORD_3);
   // tkopengl_shared->_glDisable(GL_MAP2_TEXTURE_COORD_4);
   // tkopengl_shared->_glDisable(GL_MAP2_VERTEX_3);
   // tkopengl_shared->_glDisable(GL_MAP2_VERTEX_4);
   tkopengl_shared->_glDisable(GL_NORMALIZE);
   tkopengl_shared->_glDisable(GL_POINT_SMOOTH);
   tkopengl_shared->_glDisable(GL_POINT_SPRITE);
   // tkopengl_shared->_glDisable(GL_POLYGON_OFFSET_FILL);
   // tkopengl_shared->_glDisable(GL_POLYGON_OFFSET_LINE);
   // tkopengl_shared->_glDisable(GL_POLYGON_OFFSET_POINT);
   tkopengl_shared->_glDisable(GL_POLYGON_SMOOTH);
   tkopengl_shared->_glDisable(GL_POLYGON_STIPPLE);
   tkopengl_shared->_glDisable(GL_TEXTURE_1D);
   tkopengl_shared->_glDisable(GL_TEXTURE_2D);
   tkopengl_shared->_glDisable(GL_TEXTURE_3D);
   tkopengl_shared->_glDisable(GL_TEXTURE_GEN_Q);
   tkopengl_shared->_glDisable(GL_TEXTURE_GEN_R);
   tkopengl_shared->_glDisable(GL_TEXTURE_GEN_S);
   tkopengl_shared->_glDisable(GL_TEXTURE_GEN_T);

   ////glShadeModel(GL_FLAT);
   tkopengl_shared->_glShadeModel(GL_SMOOTH);

   tkopengl_shared->_glHint(GL_FOG_HINT, GL_NICEST);
   tkopengl_shared->_glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
 	tkopengl_shared->_glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
   tkopengl_shared->_glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
   tkopengl_shared->_glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

   tkopengl_shared->_glBindFramebuffer(GL_FRAMEBUFFER, 0);
   tkopengl_shared->_glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
   tkopengl_shared->_glDepthMask(GL_TRUE);

   tkopengl_shared->_glDisable(GL_SAMPLE_COVERAGE);
}

void _HAL::beginPaint(void) {
   if(!b_redrawing)
   {
      // // SDL_GLContext_Rebind();  // hack for Softube and Waves VST plugin UIs

      // Import shared functions from tkopengl
      tkopengl_shared_resolve();

      pushGLContext();

      loadGLDefaults();

#ifndef DX_GLES
      if(tks_HAL->b_double_buffer)
      {
         tkopengl_shared->_glDrawBuffer(GL_BACK);
      }
#endif // DX_GLES

      tkopengl_shared->_glBindFramebuffer(GL_FRAMEBUFFER, 0);  // [13Feb2018]
      tkopengl_shared->_glDisable(GL_SCISSOR_TEST);

      tkopengl_shared->_glViewport(0, 0, tks_HAL->view_sx, tks_HAL->view_sy);

      tkopengl_shared->_glMatrixMode(GL_TEXTURE);
      tkopengl_shared->_glLoadIdentity();

      tkopengl_shared->_glMatrixMode(GL_MODELVIEW);
      tkopengl_shared->_glLoadIdentity();

      b_redrawing = YAC_TRUE;
   }
}

void _HAL::endPaint(void) {
   if(b_redrawing)
   {
      if(b_graphics_active)
      {
         //
         // (note) glFlush() call is **NOT** superfluous, at least not with desktop opengl
         //       (commenting out this call breaks the tkui library!)
         //

         //
         // (note) NVidia SwapBuffers() blocks when vsync is enabled.
         //       ATI's implementation seems to just queue the commands and return immediately.
         //       Unless the framerate is limited (FPS.limit=60;), this means that more
         //       frames are queued for rendering than are made visible.
         //       The solution to this is to synchronize with the GL and wait for the last
         //       SwapBuffers() to finish before queueing a new SwapBuffers call.
         //
         // (update) 29Dec2011: apparently the NVidia SwapBuffers behaviour changed, SwapBuffers() does not block anymore
         //                     (tested on Geforce 9800 GT / WinVista 32bit, latest drivers)
         //

         // sUI tickStart = _getTicks();

         ::glFlush();

#ifdef DX_GLES
#ifdef DX_OMAP3
         ////SDL_Flip((SDL_Surface *)sdl_surf);
         omap3_view_swapbuffers(swap_interval);
#else
         // (todo) generic GLES swapbuffers
         yac_host->printf("[~~~] HAL::endPaint: missing swapBuffers call\n");
#endif // DX_OMAP3

#else
         ::SDL_GL_SwapBuffers(); // may block (NVidia) or simply queue and return (ATI)
#endif // DX_GLES

         // ::SDL_Delay(1);

          // sUI dt = _getTicks() - tickStart;
          // yac_host->printf("[dbg] SwapBuffers took %u ticks\n", dt); // => 0 millisec on

         // Increase framecounter
         fps.second_frames_rendered++;
         fps.num_frames_rendered++;

         updateFPSAndLimit();
      }

      popGLContext();

      b_redrawing = YAC_FALSE;
   }
}

void _HAL::pushGLContext(void) {
#ifdef _WIN32
   // (note) needs patched SDL library from tksdl/ folder
   SDL_GLContext_Push();  // save previous GL context (e.g. Softube and Waves VST plugin UIs)
#endif
}

void _HAL::popGLContext(void) {
#ifdef _WIN32
   // (note) needs patched SDL library from tksdl/ folder
   SDL_GLContext_Pop();  // restore previous GL context (e.g. Softube and Waves VST plugin UIs)
#endif
}

sBool _HAL::openWindow(sSI _sx, sSI _sy) {
   win_sx = (sU16)_sx;
   win_sy = (sU16)_sy;

   b_fullscreen = 0;

   return openView(_sx, _sy, 0, 0, 0);
}

sBool _HAL::openScreen(sSI _sx, sSI _sy, sSI _z) {
   fs_sx = (sU16)_sx;
   fs_sy = (sU16)_sy;

   if(!win_sx)
   {
      win_sx = fs_sx;
      win_sy = fs_sy;
   }

   fs_z         = (sU8)_z;
   b_fullscreen = 1;

   return openView(_sx, _sy, _z, 1, 0);
}

sSI _HAL::openView(sU16 _sx, sU16 _sy, sU8 _z, 
                   sBool _bFullscreen, sBool _bResize) {

   yac_host->printf("[dbg] HAL::openView: s=(%u; %u) z=%u bFS=%d bResize=%d\n",
                    _sx, _sy, _z, _bFullscreen, _bResize
                    );

#ifdef DX_OMAP3
   if(_sx > 800)
      _sx = 800;
   if(_sy > 480)
      _sy = 480;
#endif


   if(!_bResize)
   {
      // close previously opened window. calls onClose()
      closeView();

      const char *viewSize =  ::getenv("TKSDL_VIEWSIZE");
      if(NULL != viewSize)
      {
         sUI vsx = 0;
         sUI vsy = 0;
         sscanf(viewSize, "%u;%u", &vsx, &vsy);
         yac_host->printf("[...] tksdl::HAL::openView: override view size=(%u; %u)\n", vsx, vsy);
         _sx = (sU16)vsx;
         _sy = (sU16)vsy;
      }
   }
   else
   {
      // just resize the current window
      callOnClose(YAC_FALSE/*bAllowScriptCbk*/);
   }
   
   if(!::SDL_WasInit(SDL_INIT_VIDEO))
   {
      yac_host->printf("[dbg] tksdl::HAL::openView: SDL_InitSubSystem(SDL_INIT_VIDEO)\n");
      ::SDL_InitSubSystem(SDL_INIT_VIDEO);
      yac_host->printf("[dbg] tksdl::HAL::openView: SDL_InitSubSystem(SDL_INIT_VIDEO) ..DONE\n");
   }
   
   view_sx = _sx;
   view_sy = _sy;
   
   sUI flags = 0;

   ////flags |= SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL;

#ifndef DX_GLES
   flags |= SDL_OPENGL /*| SDL_RESIZABLE*/; 
#endif

#ifdef DX_OMAP3
   flags |= SDL_FULLSCREEN;
#endif

   if(_bFullscreen)
   {
      view_z  = _z;
      flags  |= SDL_FULLSCREEN;
   }
   else
   {
      const SDL_VideoInfo *info = ::SDL_GetVideoInfo();

      if(NULL == info)
      {
         yac_host->printf("\n[---] openView: SDL_GetVideoInfo failed.\n");
         return 0;
      }
      view_z = info->vfmt->BitsPerPixel;

      if(0 == desktop_sx)
      {
         desktop_sx = info->current_w; // initialized w/ desktop resolution before 1st SDL_SetVideoMode() call (since SDL 1.2.10)
         desktop_sy = info->current_h;
      }

      if(yac_host->yacGetDebugLevel() > 7)
      {
         yac_host->printf("\n[...] openView: desktop resolution=(%i; %i) color depth=%ibit\n",
                          desktop_sx,
                          desktop_sy,
                          info->vfmt->BitsPerPixel);
      }
   }
   
   //if(!_bResize)
   {
      ::SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );   // require at least 5 bits per channel
      ::SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5);
      ::SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5);
      ::SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16);
      ::SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, b_double_buffer);

      // yac_host->printf("xxx tksdl: multisample_buffers=%u multisample_samples=%u\n", multisample_buffers, multisample_samples);
      if( (multisample_buffers>0) && (multisample_samples>0) )
      {
         ::SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, multisample_buffers);
         ::SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, multisample_samples); 
      }
      if(stencil_bits > 0)
      {
         ::SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, stencil_bits);
      }
   }

   yac_host->printf("[dbg] tksdl::HAL::openView: SDL_SetVideoMode(%u, %u, %u, 0x%08x)\n",
                    view_sx, view_sy,
                    view_z,
                    flags|tks_flags
                    );

   SDL_Surface *surf = ::SDL_SetVideoMode(view_sx, view_sy,
                                          view_z,
                                          flags|tks_flags
                                          );

   yac_host->printf("[dbg] tksdl::HAL::openView: SDL_SetVideoMode -> surf=%p\n", surf);

#ifdef DX_OMAP3
   sdl_surf = (void*)surf;
#endif

   if(NULL == surf)
   {
      yac_host->printf("\n[---] SDL_SetVideoMode(%i, %i, %i, %08x) failed.\n", 
                       view_sx, view_sy, view_z, flags);
      return 0;
   }

   // ---- workaround for SDL bug with very small windows (wrong surface geometry)
   {
      ////int clipbox[4];
      ////::glGetIntegerv(GL_VIEWPORT, clipbox);  
      view_sx = surf->w;
      view_sy = surf->h;
   }

   // // //if(clipbox[2]>view_sx) // old driver workaround
   // // {
   // //    //removed 03Nov2007 //view_sx=clipbox[2];
   // // }
   // // //if(clipbox[3]>view_sy)
   // // {
   // //    //removed 03Nov2007 //view_sy=clipbox[3];
   // // }

   ////yac_host->printf("[...] SDL_SetViewMode() OK: surface geo=(%i; %i) pitch=%i clipw=%i cliph=%i glh=%i glw=%i.\n", view_sx, view_sy, surf->pitch, surf->clip_rect.w, surf->clip_rect.h, clipbox[2], clipbox[3]);

   // ---- video mode is set
   if(_bFullscreen)
   {
      view_flags |= _HAL::VP_FULLSCREEN;
   }

   view_flags |= _HAL::VP_OPENED;

#ifdef DX_OMAP3
   omap3_view_open();
#endif // DX_OMAP3

#ifdef YAC_WIN32
   ::InitCommonControls();
#endif

   viewOpened(_bResize);

   // OK
   return 1;
}

void _HAL::closeView(void) {

   // yac_host->printf("[dbg] HAL::closeView (ENTER)\n");

   // [23Mar2018] actually close window when Viewport.close() is called
   if(::SDL_WasInit(SDL_INIT_VIDEO))
   {
      yac_host->printf("[dbg] tksdl::HAL::closeView: SDL_QuitSubSystem(SDL_INIT_VIDEO)\n");
      ::SDL_QuitSubSystem(SDL_INIT_VIDEO);
      b_window_visible = false;
   }

   if(0 != (view_flags & VP_OPENED))
   {
      // yac_host->printf("[dbg] HAL::closeView: callOnClose(0)\n");

      callOnClose(YAC_FALSE/*bAllowScriptCbk*/);

      view_sx           = 0;
      view_sy           = 0;
      view_z            = 0;
      view_flags        = 0;
      b_graphics_active = 0;

#ifdef YAC_WIN32
      if(lpDD)
      {
         lpDD->Release();
         lpDD = NULL;
      }
#endif

   }

   // yac_host->printf("[dbg] HAL::closeView (LEAVE)\n");
}

void _HAL::viewOpened(sBool _bResize) {

   b_window_visible = true;

   SDL_VERSION(&syswminfo.version);
   SDL_GetWMInfo(&syswminfo);

#ifdef YAC_WIN32
   if(lpDD)
   {
      lpDD->Release();
   }
   // ---- get monitor refresh rate ----
   ::DirectDrawCreate((LPGUID)0, &lpDD, NULL);
   lpDD->GetMonitorFrequency(&dw_monitorfreq);
   if(yac_host->yacGetDebugLevel() > 7)
   {
      yac_host->printf("[...] Monitor frequency=%iHz\n", dw_monitorfreq);
   }

   // Enable drag'n'drop
   SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
   ::DragAcceptFiles(syswminfo.window, TRUE);

#endif

   resetMouse();

   b_graphics_active = 1;
   
   // show GL driver info
   if(yac_host->yacGetDebugLevel() > 7)
   {
      const GLubyte *gls = ::glGetString(GL_VENDOR);
      yac_host->printf("[...] OpenGL vendor    : \"%s\"\n", (const char*)gls);
      gls = ::glGetString(GL_RENDERER);
      yac_host->printf("[...]        renderer  : \"%s\"\n", (const char*)gls);
      gls = ::glGetString(GL_VERSION);
      yac_host->printf("[...]        version   : \"%s\"\n", (const char*)gls);
      gls = ::glGetString(GL_EXTENSIONS);
      yac_host->printf("[...]        extensions: \"%s\"\n", (const char*)gls);
   }

#ifdef TKSDL_SCRAP_CLIPBOARD
   init_scrap();
#endif

   initFPS();

   // Override glGetError() calls
   {
      char *s = ::getenv("TKSDL_GLERROR");
      if(NULL != s)
      {
         b_glerror = atoi(s) ? 1 : 0;
         printf("[dbg] tksdl::HAL: %s glGetError() calls via environment variable.\n", b_glerror ? "enabling" : "disabling");
      }
   }

   /////hook_call(&tksdl_hook_open);

   // Reset timer/redraw queue flag (event queue was re-created)
   yac_host->yacMutexLock(mtx_event_queued);
   b_timer_event_queued  = 0;
   b_redraw_event_queued = 0;
   yac_host->yacMutexUnlock(mtx_event_queued);
   

   if(!_bResize)
   {
      // (note) in case of resize, onResize() is called, followed by onOpen()
      callOnOpen();
   }

}

sBool _HAL::toggleFullScreen(void) {
   sBool r = 0;

   closeView();

   if(b_fullscreen)
   {
      r = openWindow(win_sx, win_sy);
   }
   else
   {
      YAC_Value cret;

      r = openScreen(fs_sx, fs_sy, fs_z);

      if(!r)
      {
         r = openWindow(win_sx, win_sy);
      }
      else
      {
         ::SDL_WM_GrabInput(SDL_GRAB_OFF);
      }
   }

   callOnResize();

   return r;
}

void _HAL::showWindow(void) {
   if(!b_window_visible)
   {
      b_window_visible = true;

#ifdef YAC_WIN32      
      ::ShowWindow(syswminfo.window, SW_SHOWNORMAL);
#else
      // (todo) XMapWindow() x11
#endif

      ::SDL_WM_SetCaption((char*)viewport_caption.chars, 0);
   }
}

void _HAL::hideWindow(void) {
   if(b_window_visible)
   {
      b_window_visible = false;
      
#ifdef YAC_WIN32      
      ::ShowWindow(syswminfo.window, SW_HIDE);
#else
      // (todo) x11
#endif
   }
}

void _HAL::useWindow(sU16 _sx, sU16 _sy) {
   win_sx  = (sU16)_sx;
   win_sy  = (sU16)_sy;
   view_sx = _sx;
   view_sy = _sy;
   
}

#ifdef YAC_LINUX
void _HAL::interruptScreenSaverX11(void) {
   XKeyEvent event;

   // printf("xxx syswminfo.info.x11.gfxdisplay=%p\n", syswminfo.info.x11.gfxdisplay);

   /* see http://www.doctort.org/adam/nerd-notes/x11-fake-keypress-event.html */
   event.display     = syswminfo.info.x11.gfxdisplay;
   event.window      = XDefaultRootWindow(syswminfo.info.x11.gfxdisplay);//0/*root*/;//syswminfo.info.window;
   event.root        = XDefaultRootWindow(syswminfo.info.x11.gfxdisplay);
   event.subwindow   = None;
   event.time        = CurrentTime;
   event.x           = 1;
   event.y           = 1;
   event.x_root      = 1;
   event.y_root      = 1;
   event.same_screen = True;
   event.keycode     = XKeysymToKeycode(event.display, XK_Down/*keycode*/);
   event.state       = 0/*modifiers*/;

   event.type = KeyPress;
   XSendEvent(event.display, event.window, True, KeyPressMask, (XEvent *)&event);

   event.type = KeyRelease;
   XSendEvent(event.display, event.window, True, KeyPressMask, (XEvent *)&event);

}
#endif // YAC_LINUX

void _HAL::waitVBlank(void) {
#ifdef YAC_WIN32
   if(lpDD)
   {
      lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, NULL);
   }
#elif defined(YAC_LINUX)

/*
  - http://readlist.com/lists/lists.freedesktop.org/xorg/0/4871.html:
     card_fd = open( "/dev/dri/card0", O_RDONLY );
     drm_wait_vblank_t wait_vblank;
     wait_vblank.request.type = _DRM_VBLANK_RELATIVE;
     wait_vblank.request.sequence = 1;
     wait_vblank.request.signal = 0;
     int rc = ioctl( card_fd, DRM_IOCTL_WAIT_VBLANK, &wait_vblank );
*/
#ifdef USE_DRM_IOCTL_WAIT_VBLANK
   static int card_fd = -2;
   
   if(-2 == card_fd)
   {
      // TODO: device is never closed
      card_fd = open( "/dev/dri/card0", O_RDONLY );
      // Open/Exists ?
      if(-1 != card_fd)
      {
         drm_wait_vblank_t wait_vblank;
         wait_vblank.request.type = _DRM_VBLANK_RELATIVE;
         wait_vblank.request.sequence = 1;
         wait_vblank.request.signal = 0;
         int rc = ioctl( card_fd, DRM_IOCTL_WAIT_VBLANK, &wait_vblank );
         if(-1 == rc)
         {
            // Failed.. :(
            close(card_fd);
            card_fd = -1; // don't try again
         }
      }
   }
#endif // USE_DRM_IOCTL_WAIT_VBLANK
#endif // YAC_LINUX
}

sUI _HAL::getMonitorFrequency(void) {
#ifdef YAC_WIN32
   if(lpDD)
   {
      lpDD->GetMonitorFrequency(&dw_monitorfreq);
      return dw_monitorfreq;
   }
   yac_host->printf("[...] Viewport::getFrequency: no ddraw pointer set, no window/screen opened?\n");
#endif
   return 60; // xxx default frequency
}

void _HAL::_setEventPolling(sSI _b) {
   b_event_polling = (0 != _b);
}

sSI _HAL::_getEventPolling(void) {
   return b_event_polling;
}

void _HAL::showCursor(sBool _st) {
   if(mouse.show != _st)
   {
      mouse.show = _st; 
      ::SDL_ShowCursor(_st?SDL_ENABLE:SDL_DISABLE);
   }
}

void _HAL::setCaption(YAC_String *_s) {
   // yac_host->printf("xxx HAL::setCaption 1\n");
   viewport_caption.copy(_s);
   // yac_host->printf("xxx HAL::setCaption 2 b_window_visible=%d\n", b_window_visible);

   if(b_window_visible)  // (note) hangs when window is currently hidden
   {
      ::SDL_WM_SetCaption((char*)viewport_caption.chars, 0);
   }

   // yac_host->printf("xxx HAL::setCaption LEAVE\n");
}

sSI _HAL::_enableUNICODE(sSI _bEnabled) {

   if(_bEnabled >= 0)
   {
      b_unicode = _bEnabled;
   }

   return ::SDL_EnableUNICODE(_bEnabled);
}

void _HAL::updateUnicode(void) {
   ///yac_host->printf("updateUniCode(%i)=%i\n", b_unicode, ::SDL_EnableUNICODE(b_unicode));
   ::SDL_EnableUNICODE(b_unicode);
}

sSI _HAL::_isApplicationActive(void) {
   return b_graphics_active;
}

void _HAL::_setTimerInterval(sUI _intervalMillisec) {
   timer_interval = _intervalMillisec;
}

sUI _HAL::_getTimerInterval(void) {
   return timer_interval;
}

sUI _HAL::_getNativeWindowHandle(void) {
   // (note) this should be interchangeable between 32 and 64bit applications
#ifdef YAC_WIN32
   return (sUI)syswminfo.window;
#else
   return (sUI)syswminfo.info.x11.window;
#endif // YAC_WIN32
}

sUI _HAL::_getNativeProcessId(void) {
#ifdef YAC_WIN32
   return ::GetCurrentProcessId();
#endif
   return 0;
}

void _HAL::_allowShowNativeWindow(sUI _otherProcessId) {
#ifdef YAC_WIN32
   (void)::AllowSetForegroundWindow(_otherProcessId);
#endif
}

void _HAL::_showNativeWindow(sUI _nativeWindowHandle) {
#ifdef YAC_WIN32
   // (note) requires permission by other process (see allowShowNativeWindow())
   // ::SetFocus((HWND)_nativeWindowHandle);
   // ::SetActiveWindow((HWND)_nativeWindowHandle);
   ::SetForegroundWindow((HWND)_nativeWindowHandle);
#endif // YAC_WIN32
}

#if 0
#ifdef YAC_WIN32
// (note) https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx (SetWindowsHookEx)
// (note) https://msdn.microsoft.com/en-us/library/windows/desktop/ms644977(v=vs.85).aspx (CBTProc)
// (note) https://stackoverflow.com/questions/18188002/hooking-for-create-window-and-want-to-get-class-name 
static HHOOK g_cbt_hook = NULL;
//SUPPRESSDIALOGHOOK_API
LRESULT CALLBACK CBTProc(__in  int nCode, __in  WPARAM wParam, __in  LPARAM lParam)
{
   if(HCBT_CREATEWND == nCode)
   {
      // wParam = handle to the new window
      // lParam = long pointer to a CBT_CREATEWND structure
      HWND hwnd = (HWND)wParam;
      CHAR name[1024] = {0};
      GetClassName(hwnd, name, sizeof(name));
      printf("xxx tksdl::HAL::CBTProc: HCBT_CREATEWND className=\"%s\" hwnd=0x%08x\n", name, hwnd);
   }
   else if(HCBT_DESTROYWND == nCode)
   {
      HWND hwnd = (HWND)wParam;
      CHAR name[1024] = {0};
      GetClassName(hwnd, name, sizeof(name));
      printf("xxx tksdl::HAL::CBTProc: HCBT_DESTROYWND className=\"%s\" hwnd=0x%08x\n", name, hwnd);
   }
   return CallNextHookEx(g_cbt_hook, nCode, wParam, lParam);
}
#endif

void _HAL::_installWindowCreateAndDestroyHook(void) {
#ifdef YAC_WIN32
   if(NULL == g_cbt_hook)
   {
      yac_host->printf("[dbg] tksdl: installWindowCreateAndDestroyHook()\n");
      // g_cbt_hook = ::SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTProc, ::GetModuleHandle(NULL), GetCurrentThreadId()/*dwThreadId*/);   // works but only for windows created by current thread
      // g_cbt_hook = ::SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTProc, ::GetModuleHandle(NULL), 0/*dwThreadId*/); // does not work (1428)
      // g_cbt_hook = ::SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTProc, NULL, GetCurrentThreadId()/*dwThreadId*/);  // works but only for windows created by current thread
      g_cbt_hook = ::SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTProc, NULL, GetCurrentThreadId()/*dwThreadId*/);
      if(NULL == g_cbt_hook)
      {
         yac_host->printf("[---] tksdl: installWindowCreateAndDestroyHook: SetWindowsHookEx() failed. GetLastError()=%d\n", GetLastError());
      }
   }
#endif
}

void _HAL::_uninstallWindowCreateAndDestroyHook(void) {
#ifdef YAC_WIN32
   if(NULL != g_cbt_hook)
   {
      yac_host->printf("[dbg] tksdl: uninstallWindowCreateAndDestroyHook()\n");
      ::UnhookWindowsHookEx(g_cbt_hook);
      g_cbt_hook = NULL;
   }
#endif
}

#else

#ifdef YAC_WIN32
// (note) https://msdn.microsoft.com/en-us/library/ms697187.aspx (Event constants)
// (note) https://msdn.microsoft.com/en-us/library/ms696160.aspx (SetWinEventHook)
// (note) https://msdn.microsoft.com/en-us/library/ms697317.aspx (WinEventProc)

static HWINEVENTHOOK g_winevent_hook = NULL;
static VOID CALLBACK loc_winevent_proc(HWINEVENTHOOK hWinEventHook,
                                       DWORD event,
                                       HWND hwnd,
                                       LONG idObject,
                                       LONG idChild,
                                       DWORD dwEventThread,
                                       DWORD dwmsEventTime
                                       ) {
   // printf("xxx tksdl:loc_winevent_proc called event=0x%08x hwnd=0x%08x idObject=%d\n", event, hwnd, idObject);
   if(EVENT_OBJECT_CREATE == event)
   {
      if(0 != hwnd)
      {
         if(OBJID_WINDOW/*0*/ == idObject)
         {
            CHAR name[1024] = {0};
            GetClassName(hwnd, name, sizeof(name));

#ifdef DEBUG_WINEVENT_CREATE_DESTROY
            yac_host->printf("[dbg] tksdl::HAL::loc_winevent_proc: EVENT_OBJECT_CREATE className=\"%s\" hwnd=0x%08x\n", name, hwnd);
#endif // DEBUG_WINEVENT_CREATE_DESTROY

            tks_HAL->callOnCreateOtherWindow(sSI(hwnd));
         }
      }
   }
   else if(EVENT_OBJECT_DESTROY  == event)
   {
      if(0 != hwnd)
      {
         if(OBJID_WINDOW/*0*/ == idObject)
         {
            // (note) never reached for WINDOW objects ?!!
            CHAR name[1024] = {0};
            GetClassName(hwnd, name, sizeof(name));

#ifdef DEBUG_WINEVENT_CREATE_DESTROY
            yac_host->printf("[dbg] tksdl::HAL::loc_winevent_proc: EVENT_OBJECT_DESTROY className=\"%s\" hwnd=0x%08x\n", name, hwnd);
#endif // DEBUG_WINEVENT_CREATE_DESTROY
         }
      }
   }
   else if(EVENT_OBJECT_FOCUS == event)
   {
      if(0 != hwnd)
      {
         if(OBJID_CLIENT/*-4*/ == idObject)
         {
            CHAR name[1024] = {0};
            GetClassName(hwnd, name, sizeof(name));

#ifdef DEBUG_WINEVENT_CREATE_DESTROY
            yac_host->printf("[dbg] tksdl::HAL::loc_winevent_proc: EVENT_OBJECT_FOCUS className=\"%s\" hwnd=0x%08x\n", name, hwnd);
#endif // DEBUG_WINEVENT_CREATE_DESTROY
            tks_HAL->callOnFocusOtherWindow(sSI(hwnd));
         }
      }
   }
}
#endif

void _HAL::_installWindowCreateAndDestroyHook(void) {
#ifdef YAC_WIN32
   if(NULL == g_winevent_hook)
   {
      yac_host->printf("[dbg] tksdl: installWindowCreateAndDestroyHook()\n");
      g_winevent_hook = ::SetWinEventHook(EVENT_OBJECT_CREATE/*0x8000*/,
                                          EVENT_OBJECT_FOCUS/*0x8005*/,
                                          NULL/*hmodWinEventProc*/,
                                          &loc_winevent_proc,
                                          ::GetCurrentProcessId(),
                                          0/*idThread*/,
                                          0/*dwFlags*/
                                          );
      // g_winevent_hook = ::SetWinEventHook(0,
      //                                     0xFFFFu,
      //                                     NULL/*hmodWinEventProc*/,
      //                                     &loc_winevent_proc,
      //                                     ::GetCurrentProcessId(),
      //                                     0/*idThread*/,
      //                                     0/*dwFlags*/
      //                                     );
         
      if(NULL == g_winevent_hook)
      {
         yac_host->printf("[---] tksdl: installWindowCreateAndDestroyHook: SetWinEventHook() failed. GetLastError()=%d\n", GetLastError());
      }
   }
#endif
}

void _HAL::_uninstallWindowCreateAndDestroyHook(void) {
#ifdef YAC_WIN32
   if(NULL != g_winevent_hook)
   {
      yac_host->printf("[dbg] tksdl: uninstallWindowCreateAndDestroyHook()\n");
      ::UnhookWinEvent(g_winevent_hook);
      g_winevent_hook = NULL;
   }
#endif
}

#endif // 0

void _HAL::_showOtherWindow(sUI _hwnd) {
   if(0 != _hwnd)
   {
#ifdef YAC_WIN32
      ::ShowWindow((HWND)_hwnd, SW_SHOWNORMAL);
      ::SetActiveWindow((HWND)_hwnd);
      ::SetForegroundWindow((HWND)_hwnd);
#else
      // (todo) x11
#endif // YAC_WIN32
   }
}

void _HAL::_hideOtherWindow(sUI _hwnd) {
   if(0 != _hwnd)
   {
#ifdef YAC_WIN32
      ::ShowWindow((HWND)_hwnd, SW_HIDE);
#else
      // (todo) x11
#endif // YAC_WIN32
   }
}

#ifdef YAC_WIN32
extern "C" {
#ifndef DPI_ENUMS_DECLARED
typedef enum PROCESS_DPI_AWARENESS {
    PROCESS_DPI_UNAWARE = 0,
    PROCESS_SYSTEM_DPI_AWARE = 1,
    PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
#endif // DPI_ENUMS_DECLARED
typedef HRESULT (*SetProcessDpiAwareness_fxn_t)(PROCESS_DPI_AWARENESS value);
typedef UINT (*GetDpiForWindow_fxn_t) (HWND hwnd);
typedef void (*EnableMouseInPointer_fxn_t)(BOOL fEnable);
}
#endif // YAC_WIN32

void _HAL::setDpiAwareness(sBool _bEnable) {
#ifdef YAC_WIN32
   HMODULE hDLL = ::LoadLibrary("Shcore.dll");
   if(NULL != hDLL)
   {
      SetProcessDpiAwareness_fxn_t setProcessDpiAwareness = (SetProcessDpiAwareness_fxn_t)
         ::GetProcAddress(hDLL, "SetProcessDpiAwareness");
      if(NULL != setProcessDpiAwareness)
      {
         yac_host->printf("[dbg] tksdl::HAL::setEnableDpiAwareness: calling SetProcessDpiAwareness() bEnable=%d\n", _bEnable);
         (void)setProcessDpiAwareness(_bEnable ? PROCESS_PER_MONITOR_DPI_AWARE : PROCESS_DPI_UNAWARE);
      }
      ::FreeLibrary(hDLL);
   }
#endif // YAC_WIN32
}

sUI _HAL::getDpi(void) {
   sUI r = 96;
#ifdef YAC_WIN32
   HMODULE hDLL = ::LoadLibrary("User32.dll");
   if(NULL != hDLL)
   {
      GetDpiForWindow_fxn_t getDpiForWindow = (GetDpiForWindow_fxn_t)
         ::GetProcAddress(hDLL, "GetDpiForWindow");
      if(NULL != getDpiForWindow)
      {
         yac_host->printf("[dbg] tksdl::HAL::getDpi: calling GetDpiForWindow()\n");
         r = getDpiForWindow(syswminfo.window);
      }
      ::FreeLibrary(hDLL);
   }
#endif // YAC_WIN32
   return r;
}

// Enable WM_POINTER messages (Windows 8 or newer)
void _HAL::setTouchInput(sBool _bEnable) {
#ifdef YAC_WIN32
   HMODULE hDLL = ::LoadLibrary("User32.dll");
   if(NULL != hDLL)
   {
      EnableMouseInPointer_fxn_t enableMouseInPointer = (EnableMouseInPointer_fxn_t)
         ::GetProcAddress(hDLL, "EnableMouseInPointer");
      if(NULL != enableMouseInPointer)
      {
         yac_host->printf("[dbg] tksdl::HAL::setEnableTouchInput: EnableMouseInPointer() detected (win8+)\n");
         (void)enableMouseInPointer(_bEnable);
         tks_HAL->mouse.b_touch_input = _bEnable;
      }
      ::FreeLibrary(hDLL);
   }
#endif // YAC_WIN32
}
