/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2008-2014. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
#include <stdio.h>
#include <signal.h>
#include <wx/wx.h>
// Avoid including these in dcbuffer below
#include "wx/dcmemory.h"
#include "wx/dcclient.h"
#include "wx/window.h"
// Ok ugly but needed for wxBufferedDC crash workaround
#define private public
#include <wx/dcbuffer.h>
#undef private
#include "wxe_impl.h"
#include "wxe_events.h"
#include "wxe_return.h"
#include "wxe_gl.h"
IMPLEMENT_APP_NO_MAIN(WxeApp)
DECLARE_APP(WxeApp)
DEFINE_EVENT_TYPE(wxeEVT_META_COMMAND)
#define WXE_NORMAL 0
#define WXE_CALLBACK 1
#define WXE_STORED 2
// Globals initiated in wxe_init.cpp
extern ErlDrvMutex *wxe_status_m;
extern ErlDrvCond *wxe_status_c;
extern ErlDrvMutex * wxe_batch_locker_m;
extern ErlDrvCond * wxe_batch_locker_c;
extern ErlDrvTermData init_caller;
extern int wxe_status;
wxList * wxe_batch = NULL;
wxList * wxe_batch_cb_saved = NULL;
int wxe_batch_caller = 0; // inside batch if larger than 0
/* ************************************************************
* Commands from erlang
* Called by emulator thread
* ************************************************************/
void push_command(int op,char * buf,int len, wxe_data *sd)
{
/* fprintf(stderr, "Op %d %d [%ld] %d\r\n", op, (int) driver_caller(sd->port_handle),
wxe_batch->size(), wxe_batch_caller),fflush(stderr); */
wxeCommand *Cmd = new wxeCommand(op, buf, len, sd);
erl_drv_mutex_lock(wxe_batch_locker_m);
wxe_batch->Append(Cmd);
if(wxe_batch_caller > 0) {
// wx-thread is waiting on batch end in cond_wait
erl_drv_cond_signal(wxe_batch_locker_c);
} else {
// wx-thread is waiting gui-events
if(op == WXE_BATCH_BEGIN) {
wxe_batch_caller = 1;
}
erl_drv_cond_signal(wxe_batch_locker_c);
wxWakeUpIdle();
}
erl_drv_mutex_unlock(wxe_batch_locker_m);
}
void meta_command(int what, wxe_data *sd) {
if(what == PING_PORT) {
erl_drv_mutex_lock(wxe_batch_locker_m);
if(wxe_batch_caller > 0) {
wxeCommand *Cmd = new wxeCommand(WXE_DEBUG_PING, NULL, 0, sd);
wxe_batch->Append(Cmd);
erl_drv_cond_signal(wxe_batch_locker_c);
}
wxWakeUpIdle();
erl_drv_mutex_unlock(wxe_batch_locker_m);
} else {
if(sd) {
wxeMetaCommand Cmd(sd, what);
wxTheApp->AddPendingEvent(Cmd);
}
}
}
void send_msg(const char * type, wxString * msg) {
wxeReturn rt = wxeReturn(WXE_DRV_PORT, init_caller);
rt.addAtom((char *) "wxe_driver");
rt.addAtom((char *) type);
rt.add(msg);
rt.addTupleCount(3);
rt.send();
}
/* ************************************************************
* Init WxeApp the application emulator
* ************************************************************/
bool WxeApp::OnInit()
{
global_me = new wxeMemEnv();
wxe_batch = new wxList;
wxe_batch_cb_saved = new wxList;
cb_buff = NULL;
recurse_level = 0;
delayed_cleanup = new wxList;
delayed_delete = new wxList;
wxe_ps_init2();
// wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); // Hmm printpreview doesn't work in 2.9 with this
Connect(wxID_ANY, wxEVT_IDLE, (wxObjectEventFunction) (wxEventFunction) &WxeApp::idle);
Connect(CREATE_PORT, wxeEVT_META_COMMAND,(wxObjectEventFunction) (wxEventFunction) &WxeApp::newMemEnv);
Connect(DELETE_PORT, wxeEVT_META_COMMAND,(wxObjectEventFunction) (wxEventFunction) &WxeApp::destroyMemEnv);
Connect(WXE_SHUTDOWN, wxeEVT_META_COMMAND,(wxObjectEventFunction) (wxEventFunction) &WxeApp::shutdown);
// fprintf(stderr, "Size void* %d: long %d long long %d int64 %d \r\n",
// sizeof(void *), sizeof(long), sizeof(long long), sizeof(wxInt64));
initEventTable();
wxInitAllImageHandlers();
#ifdef _MACOSX
/* Create a default MenuBar so that we can intercept the quit command */
wxMenuBar *macMB = new wxMenuBar;
wxMenuBar::MacSetCommonMenuBar(macMB);
macMB->MacInstallMenuBar();
macMB->Connect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED,
(wxObjectEventFunction) (wxEventFunction) &WxeApp::dummy_close);
#endif
SetExitOnFrameDelete(false);
init_nonconsts(global_me, init_caller);
erl_drv_mutex_lock(wxe_status_m);
wxe_status = WXE_INITIATED;
erl_drv_cond_signal(wxe_status_c);
erl_drv_mutex_unlock(wxe_status_m);
return TRUE;
}
void WxeApp::shutdown(wxeMetaCommand& Ecmd) {
ExitMainLoop();
}
void WxeApp::dummy_close(wxEvent& Ev) {
// fprintf(stderr, "Dummy Close invoked\r\n");
// wxMac really wants a top level window which command-q quits if there are no
// windows open, and this will kill the erlang, override default handling
}
// Called by wx thread
void WxeApp::idle(wxIdleEvent& event) {
event.Skip(true);
dispatch_cmds();
}
/* ************************************************************
* Erlang Command execution *
* ************************************************************/
/* Callback from printer and event callbacks */
void pre_callback()
{
// no-op
}
void handle_event_callback(ErlDrvPort port, ErlDrvTermData process)
{
WxeApp * app = (WxeApp *) wxTheApp;
ErlDrvMonitor monitor;
// Is thread safe if pdl have been incremented
if(driver_monitor_process(port, process, &monitor) == 0) {
// Should we be able to handle commands when recursing? probably
erl_drv_mutex_lock(wxe_batch_locker_m);
//fprintf(stderr, "\r\nCB EV Start %lu \r\n", process);fflush(stderr);
app->recurse_level++;
app->dispatch_cb(wxe_batch, wxe_batch_cb_saved, process);
app->recurse_level--;
//fprintf(stderr, "CB EV done %lu \r\n", process);fflush(stderr);
wxe_batch_caller = 0;
erl_drv_mutex_unlock(wxe_batch_locker_m);
driver_demonitor_process(port, &monitor);
}
}
void WxeApp::dispatch_cmds()
{
erl_drv_mutex_lock(wxe_batch_locker_m);
recurse_level++;
int level = dispatch(wxe_batch_cb_saved, 0, WXE_STORED);
dispatch(wxe_batch, level, WXE_NORMAL);
recurse_level--;
wxe_batch_caller = 0;
erl_drv_mutex_unlock(wxe_batch_locker_m);
// Cleanup old memenv's and deleted objects
if(recurse_level == 0) {
if(delayed_delete->size() > 0)
for( wxList::compatibility_iterator node = delayed_delete->GetFirst();
node;
node = delayed_delete->GetFirst()) {
wxeCommand *event = (wxeCommand *)node->GetData();
delayed_delete->Erase(node);
wxe_dispatch(*event);
event->Delete();
}
if(delayed_cleanup->size() > 0)
for( wxList::compatibility_iterator node = delayed_cleanup->GetFirst();
node;
node = delayed_cleanup->GetFirst()) {
wxeMetaCommand *event = (wxeMetaCommand *)node->GetData();
delayed_cleanup->Erase(node);
destroyMemEnv(*event);
delete event;
}
}
}
// Should have erl_drv_mutex_lock(wxe_batch_locker_m);
// when entering this function and it should be released
// afterwards
int WxeApp::dispatch(wxList * batch, int blevel, int list_type)
{
int ping = 0;
// erl_drv_mutex_lock(wxe_batch_locker_m); must be locked already
while(true)
{
if (batch->size() > 0) {
for( wxList::compatibility_iterator node = batch->GetFirst();
node;
node = batch->GetFirst())
{
wxeCommand *event = (wxeCommand *)node->GetData();
batch->Erase(node);
switch(event->op) {
case WXE_BATCH_END:
{--blevel; }
break;
case WXE_BATCH_BEGIN:
{blevel++; }
break;
case WXE_DEBUG_PING:
// When in debugger we don't want to hang waiting for a BATCH_END
// that never comes, because a breakpoint have hit.
ping++;
if(ping > 2)
blevel = 0;
break;
case WXE_CB_RETURN:
// erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after
// whatever cleaning is necessary
if(event->len > 0) {
cb_buff = (char *) driver_alloc(event->len);
memcpy(cb_buff, event->buffer, event->len);
}
return blevel;
default:
erl_drv_mutex_unlock(wxe_batch_locker_m);
if(event->op < OPENGL_START) {
// fprintf(stderr, " c %d (%d) \r\n", event->op, blevel);
wxe_dispatch(*event);
} else {
gl_dispatch(event->op,event->buffer,event->caller,event->bin);
}
erl_drv_mutex_lock(wxe_batch_locker_m);
break;
}
event->Delete();
}
} else {
if((list_type == WXE_STORED) || (blevel <= 0 && list_type == WXE_NORMAL)) {
// erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after
// whatever cleaning is necessary
return blevel;
}
// sleep until something happens
//fprintf(stderr, "%s:%d sleep %d %d %d %d \r\n", __FILE__, __LINE__, batch->size(), callback_returned, blevel, is_callback);fflush(stderr);
wxe_batch_caller++;
while(batch->size() == 0) {
erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m);
}
}
}
}
void WxeApp::dispatch_cb(wxList * batch, wxList * temp, ErlDrvTermData process) {
int callback_returned = 0;
while(true) {
if (batch->size() > 0) {
for( wxList::compatibility_iterator node = batch->GetFirst();
node;
node = batch->GetFirst())
{
wxeCommand *event = (wxeCommand *)node->GetData();
wxeMemEnv *memenv = getMemEnv(event->port);
batch->Erase(node);
// fprintf(stderr, " Ev %d %lu\r\n", event->op, event->caller);
if(event->caller == process || // Callbacks from CB process only
event->op == WXE_CB_START || // Event callback start change process
event->op == WXE_CB_DIED || // Event callback process died
// Allow connect_cb during CB i.e. msg from wxe_server.
(memenv && event->caller == memenv->owner))
{
switch(event->op) {
case WXE_BATCH_END:
case WXE_BATCH_BEGIN:
case WXE_DEBUG_PING:
break;
case WXE_CB_RETURN:
if(event->len > 0) {
cb_buff = (char *) driver_alloc(event->len);
memcpy(cb_buff, event->buffer, event->len);
} // continue
case WXE_CB_DIED:
callback_returned = 1;
return;
case WXE_CB_START:
// CB start from now accept message from CB process only
process = event->caller;
break;
default:
erl_drv_mutex_unlock(wxe_batch_locker_m);
size_t start=temp->GetCount();
if(event->op < OPENGL_START) {
// fprintf(stderr, " cb %d \r\n", event->op);
wxe_dispatch(*event);
} else {
gl_dispatch(event->op,event->buffer,event->caller,event->bin);
}
erl_drv_mutex_lock(wxe_batch_locker_m);
if(temp->GetCount() > start) {
// We have recursed dispatch_cb and messages for this
// callback may be saved on temp list move them
// to orig list
for(wxList::compatibility_iterator node = temp->Item(start);
node;
node = node->GetNext()) {
wxeCommand *ev = (wxeCommand *)node->GetData();
if(ev->caller == process) {
batch->Append(ev);
temp->Erase(node);
}
}
}
if(callback_returned)
return;
break;
}
event->Delete();
} else {
// fprintf(stderr, " save %d \r\n", event->op);
temp->Append(event);
}
}
} else {
if(callback_returned) {
return;
}
// sleep until something happens
//fprintf(stderr, "%s:%d sleep %d %d %d %d \r\n", __FILE__, __LINE__, batch->size(), callback_returned, blevel, is_callback);fflush(stderr);
while(batch->size() == 0) {
erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m);
}
}
}
}
/* Memory handling */
void WxeApp::newMemEnv(wxeMetaCommand& Ecmd) {
wxeMemEnv * memenv = new wxeMemEnv();
driver_pdl_inc_refc(Ecmd.pdl);
for(int i = 0; i < global_me->next; i++) {
memenv->ref2ptr[i] = global_me->ref2ptr[i];
}
memenv->next = global_me->next;
refmap[Ecmd.port] = memenv;
memenv->owner = Ecmd.caller;
ErlDrvTermData rt[] = {ERL_DRV_ATOM, driver_mk_atom((char *)"wx_port_initiated")};
erl_drv_send_term(WXE_DRV_PORT,Ecmd.caller,rt,2);
}
void WxeApp::destroyMemEnv(wxeMetaCommand& Ecmd)
{
// Clear incoming cmd queue first
// dispatch_cmds();
wxWindow *parent = NULL;
wxeMemEnv * memenv = refmap[Ecmd.port];
if(wxe_debug) {
wxString msg;
msg.Printf(wxT("Destroying all memory "));
send_msg("debug", &msg);
}
// pre-pass delete all dialogs first since they might crash erlang otherwise
for(int i=1; i < memenv->next; i++) {
wxObject * ptr = (wxObject *) memenv->ref2ptr[i];
if(ptr) {
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
if(refd->alloc_in_erl && refd->type == 2) {
wxDialog *win = (wxDialog *) ptr;
if(win->IsModal()) {
win->EndModal(-1);
}
parent = win->GetParent();
if(parent) {
ptrMap::iterator parentRef = ptr2ref.find(parent);
if(parentRef == ptr2ref.end()) {
// The parent is already dead delete the parent ref
win->SetParent(NULL);
}
}
if(recurse_level > 0) {
// Delay delete until we are out of dispatch*
delayed_cleanup->Append(Ecmd.Clone());
} else {
delete win;
}
}
}
}
}
if(recurse_level > 0)
return;
// First pass, delete all top parents/windows of all linked objects
// fprintf(stderr, "close port %x\r\n", Ecmd.port);fflush(stderr);
for(int i=1; i < memenv->next; i++) {
void * ptr = memenv->ref2ptr[i];
if(ptr) {
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
if(refd->alloc_in_erl && refd->type == 0) {
parent = (wxWindow *) ptr;
// fprintf(stderr, "window %x %d\r\n", (int) parent, refd->ref);
while(parent->GetParent()) {
parent = parent->GetParent();
// fprintf(stderr, " parent %x \r\n", (int) parent);
}
ptrMap::iterator pdata = ptr2ref.find(parent);
if(pdata != ptr2ref.end()) {
delete parent;
} // else parent is already deleted
}
} else {
// fprintf(stderr, "Error found no ref in %d => %x\r\n", i, ptr);
}
}
}
// Second pass delete everything else allocated
// everything linked from windows should now be deleted
for(int i=1; i < memenv->next; i++) {
void * ptr = memenv->ref2ptr[i];
if(ptr) {
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
if(refd->alloc_in_erl) {
int type = refd->type;
if((refd->type == 1) && ((wxObject *)ptr)->IsKindOf(CLASSINFO(wxBufferedDC))) {
((wxBufferedDC *)ptr)->m_dc = NULL; // Workaround
}
wxString msg;
if((refd->type == 0)) { // Maybe also class 1
wxClassInfo *cinfo = ((wxObject *)ptr)->GetClassInfo();
msg.Printf(wxT("Memory leak: {wx_ref, %d, %s}"),
refd->ref, cinfo->GetClassName());
send_msg("error", &msg);
} else {
delete_object(ptr, refd);
}
if(type == 0 || type > 2) {
// Delete refs for leaks and non overridden allocs
delete refd;
ptr2ref.erase(it);
} // overridden allocs deletes meta-data in clearPtr
} else { // Not alloced in erl just delete references
if(refd->ref >= global_me->next) { // if it is not part of global ptrs
delete refd;
ptr2ref.erase(it);
}
}
}
}
}
// // Assert ?
// for(ptrMap::iterator it = ptr2ref.begin(); it != ptr2ref.end(); it++) {
// wxeRefData *refd = it->second;
// if(refd->ref >= global_me->next)
// fprintf(stderr, "L %d %d %d\r\n", refd->ref, refd->type, refd->alloc_in_erl);
// }
// fflush(stderr);
delete memenv;
driver_pdl_dec_refc(Ecmd.pdl);
refmap.erase((ErlDrvTermData) Ecmd.port);
}
wxeMemEnv * WxeApp::getMemEnv(ErlDrvTermData port) {
return refmap[port];
}
int WxeApp::newPtr(void * ptr, int type, wxeMemEnv *memenv) {
int ref;
intList free = memenv->free;
if(free.IsEmpty()) {
ref = memenv->next++;
} else {
ref = free.Pop();
};
if(ref >= memenv->max) {
memenv->max *= 2;
memenv->ref2ptr =
(void **) driver_realloc(memenv->ref2ptr,memenv->max * sizeof(void*));
}
memenv->ref2ptr[ref] = ptr;
if(wxe_debug) {
wxString msg;
msg.Printf(wxT("Creating {wx_ref, %d, unknown} at %p "), ref, ptr);
send_msg("debug", &msg);
}
ptr2ref[ptr] = new wxeRefData(ref, type, true, memenv);
// fprintf(stderr, "ptr %x id %d\r\n", (int) ptr,ref);
return ref;
}
int WxeApp::getRef(void * ptr, wxeMemEnv *memenv) {
if(!ptr) return 0; // NULL and zero is the same
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
if(refd->memenv == memenv || refd->memenv == global_me) {
// Found it return
return refd->ref;
} // else
// Old reference to deleted object, release old and recreate in current memenv.
ptr2ref.erase(it);
}
int ref;
intList free = memenv->free;
if(free.IsEmpty()) {
ref = memenv->next++;
} else {
ref = free.Pop();
};
if(ref >= memenv->max) {
memenv->max *= 2;
memenv->ref2ptr =
(void **) driver_realloc(memenv->ref2ptr,memenv->max * sizeof(void*));
}
memenv->ref2ptr[ref] = ptr;
ptr2ref[ptr] = new wxeRefData(ref, 0, false, memenv);
return ref;
}
void WxeApp::clearPtr(void * ptr) {
ptrMap::iterator it;
it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
intList free = refd->memenv->free;
int ref = refd->ref;
refd->memenv->ref2ptr[ref] = NULL;
free.Append(ref);
if(wxe_debug) {
wxString msg;
msg.Printf(wxT("Deleting {wx_ref, %d, unknown} at %p "), ref, ptr);
send_msg("debug", &msg);
}
if(((int) refd->pid) != -1) {
// Send terminate pid to owner
wxeReturn rt = wxeReturn(WXE_DRV_PORT,refd->memenv->owner, false);
rt.addAtom("_wxe_destroy_");
rt.add(ERL_DRV_PID, refd->pid);
rt.addTupleCount(2);
rt.send();
refd->pid = -1;
};
if(refd->type == 1 && ((wxObject*)ptr)->IsKindOf(CLASSINFO(wxSizer))) {
wxSizerItemList list = ((wxSizer*)ptr)->GetChildren();
for(wxSizerItemList::compatibility_iterator node = list.GetFirst();
node; node = node->GetNext()) {
wxSizerItem *item = node->GetData();
wxObject *content=NULL;
if((content = item->GetWindow()))
if(ptr2ref.end() == ptr2ref.find(content)) {
wxString msg;
wxClassInfo *cinfo = ((wxObject *)ptr)->GetClassInfo();
msg.Printf(wxT("Double usage detected of window at %p in sizer {wx_ref, %d, %s}"),
content, ref, cinfo->GetClassName());
send_msg("error", &msg);
((wxSizer*)ptr)->Detach((wxWindow*)content);
}
if((content = item->GetSizer()))
if(ptr2ref.end() == ptr2ref.find(content)) {
wxString msg;
wxClassInfo *cinfo = ((wxObject *)ptr)->GetClassInfo();
msg.Printf(wxT("Double usage detected of sizer at %p in sizer {wx_ref, %d, %s}"),
content, ref, cinfo->GetClassName());
send_msg("error", &msg);
((wxSizer*)ptr)->Detach((wxSizer*)content);
}
}
}
delete refd;
ptr2ref.erase(it);
}
}
void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
int index = *(int *) bp;
if(!memenv) {
throw wxe_badarg(index);
}
void * temp = memenv->ref2ptr[index];
if((index < memenv->next) && ((index == 0) || (temp > NULL)))
return temp;
else {
throw wxe_badarg(index);
}
}
void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
int index = *(int *) bp;
if(!memenv)
throw wxe_badarg(index);
void * temp = memenv->ref2ptr[index];
if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
ptrMap::iterator it;
it = ptr2ref.find(temp);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
refd->pid = pid;
return ;
}
};
throw wxe_badarg(index);
}