/* * %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 #include #include // 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 #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) { driver_free(sd->bin); 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); }