/*
* %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;
wxeFifo * wxe_queue = NULL;
wxeFifo * wxe_queue_cb_saved = NULL;
unsigned int wxe_needs_signal = 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); */
erl_drv_mutex_lock(wxe_batch_locker_m);
wxe_queue->Add(op, buf, len, sd);
if(wxe_needs_signal) {
// wx-thread is waiting on batch end in cond_wait
erl_drv_cond_signal(wxe_batch_locker_c);
erl_drv_mutex_unlock(wxe_batch_locker_m);
} else {
// wx-thread is waiting gui-events
erl_drv_mutex_unlock(wxe_batch_locker_m);
wxWakeUpIdle();
}
}
void meta_command(int what, wxe_data *sd) {
if(what == PING_PORT && wxe_status == WXE_INITIATED) {
erl_drv_mutex_lock(wxe_batch_locker_m);
if(wxe_needs_signal) {
wxe_queue->Add(WXE_DEBUG_PING, NULL, 0, sd);
erl_drv_cond_signal(wxe_batch_locker_c);
}
wxWakeUpIdle();
erl_drv_mutex_unlock(wxe_batch_locker_m);
} else {
if(sd && wxe_status == WXE_INITIATED) {
wxeMetaCommand Cmd(sd, what);
wxTheApp->AddPendingEvent(Cmd);
if(what == DELETE_PORT) {
free(sd);
}
}
}
}
void send_msg(const char * type, const 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_queue = new wxeFifo(1000);
wxe_queue_cb_saved = new wxeFifo(200);
cb_buff = NULL;
recurse_level = 0;
delayed_delete = new wxeFifo(10);
delayed_cleanup = 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;
}
#ifdef _MACOSX
void WxeApp::MacOpenFile(const wxString &filename) {
send_msg("open_file", &filename);
}
#endif
void WxeApp::shutdown(wxeMetaCommand& Ecmd) {
wxe_status = WXE_EXITING;
ExitMainLoop();
delete wxe_queue;
delete wxe_queue_cb_saved;
}
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;
if(wxe_status != WXE_INITIATED)
return;
// 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
// fprintf(stderr, "\r\nCB EV Start %lu \r\n", process);fflush(stderr);
app->recurse_level++;
app->dispatch_cb(wxe_queue, wxe_queue_cb_saved, process);
app->recurse_level--;
// fprintf(stderr, "CB EV done %lu \r\n", process);fflush(stderr);
driver_demonitor_process(port, &monitor);
}
}
void WxeApp::dispatch_cmds()
{
if(wxe_status != WXE_INITIATED)
return;
recurse_level++;
int level = dispatch(wxe_queue_cb_saved, 0, WXE_STORED);
dispatch(wxe_queue, level, WXE_NORMAL);
recurse_level--;
// Cleanup old memenv's and deleted objects
if(recurse_level == 0) {
wxeCommand *curr;
while((curr = delayed_delete->Get()) != NULL) {
wxe_dispatch(*curr);
curr->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;
}
if(wxe_queue_cb_saved->m_old) {
driver_free(wxe_queue_cb_saved->m_old);
wxe_queue_cb_saved->m_old = NULL;
}
if(delayed_delete->m_old) {
driver_free(delayed_delete->m_old);
delayed_delete->m_old = NULL;
}
}
}
int WxeApp::dispatch(wxeFifo * batch, int blevel, int list_type)
{
int ping = 0;
wxeCommand *event;
if(list_type == WXE_NORMAL) erl_drv_mutex_lock(wxe_batch_locker_m);
while(true) {
while((event = batch->Get()) != NULL) {
if(list_type == WXE_NORMAL) erl_drv_mutex_unlock(wxe_batch_locker_m);
switch(event->op) {
case -1:
break;
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:
if(event->len > 0) {
cb_buff = (char *) driver_alloc(event->len);
memcpy(cb_buff, event->buffer, event->len);
}
event->Delete();
return blevel;
default:
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);
}
break;
}
event->Delete();
if(list_type == WXE_NORMAL) erl_drv_mutex_lock(wxe_batch_locker_m);
}
if(list_type == WXE_STORED)
return blevel;
if(blevel <= 0) { // list_type == WXE_NORMAL
if(wxe_queue->m_old) {
driver_free(wxe_queue->m_old);
wxe_queue->m_old = NULL;
}
erl_drv_mutex_unlock(wxe_batch_locker_m);
return blevel;
}
// sleep until something happens
//fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__, batch->m_n, blevel);fflush(stderr);
wxe_needs_signal = 1;
while(batch->m_n == 0) {
erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m);
}
wxe_needs_signal = 0;
}
}
void WxeApp::dispatch_cb(wxeFifo * batch, wxeFifo * temp, ErlDrvTermData process) {
wxeCommand *event;
erl_drv_mutex_lock(wxe_batch_locker_m);
while(true) {
while((event = batch->Get()) != NULL) {
erl_drv_mutex_unlock(wxe_batch_locker_m);
wxeMemEnv *memenv = getMemEnv(event->port);
// 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 -1:
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:
event->Delete();
return;
case WXE_CB_START:
// CB start from now accept message from CB process only
process = event->caller;
break;
default:
size_t start=temp->m_n;
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);
}
if(temp->m_n > start) {
erl_drv_mutex_lock(wxe_batch_locker_m);
// We have recursed dispatch_cb and messages for this
// callback may be saved on temp list move them
// to orig list
for(unsigned int i=start; i < temp->m_n; i++) {
wxeCommand *ev = &temp->m_q[(temp->m_first+i) % temp->m_max];
if(ev->caller == process) {
batch->Append(ev);
}
}
erl_drv_mutex_unlock(wxe_batch_locker_m);
}
break;
}
event->Delete();
} else {
// fprintf(stderr, " save %d %lu\r\n", event->op, event->caller);
temp->Append(event);
}
erl_drv_mutex_lock(wxe_batch_locker_m);
}
// sleep until something happens
// fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__,
// batch->m_n, temp->m_n);fflush(stderr);
wxe_needs_signal = 1;
while(batch->m_n == 0) {
erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m);
}
wxe_needs_signal = 0;
}
}
/* 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(!memenv) {
wxString msg;
msg.Printf(wxT("MemEnv already deleted"));
send_msg("debug", &msg);
return;
}
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*
} else {
delete win;
}
}
}
}
}
if(recurse_level > 0) {
delayed_cleanup->Append(Ecmd.Clone());
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) {
if((refd->type == 4) && ((wxObject *)ptr)->IsKindOf(CLASSINFO(wxBufferedDC))) {
((wxBufferedDC *)ptr)->m_dc = NULL; // Workaround
}
wxString msg;
bool cleanup_ref=true;
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 {
cleanup_ref = delete_object(ptr, refd);
}
if(cleanup_ref) {
// 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);
}
wxeRefData * WxeApp::getRefData(void *ptr) {
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
wxeRefData *refd = it->second;
return refd;
}
return NULL;
}
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->pid, 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);
}