/* * %CopyrightBegin% * * Copyright Ericsson AB 2008-2017. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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; 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(2000); cb_buff = NULL; recurse_level = 0; delayed_delete = new wxeFifo(100); delayed_cleanup = new wxList; wxe_ps_init2(); wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); 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; } 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 } void WxeApp::OnAssertFailure(const wxChar *file, int line, const wxChar *cfunc, const wxChar *cond, const wxChar *cmsgUser) { wxString msg; wxString func(cfunc); wxString msgUser(cmsgUser); msg.Printf(wxT("wxWidgets Assert failure: %s(%d): \"%s\""), file, line, cond); if ( !func.empty() ) { msg << wxT(" in ") << func << wxT("()"); } // and the message itself if ( !msgUser.empty() ) { msg << wxT(" : ") << msgUser; } send_msg("error", &msg); } // Called by wx thread void WxeApp::idle(wxIdleEvent& event) { event.Skip(true); if(dispatch_cmds()) event.RequestMore(); } /* ************************************************************ * 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, process); app->recurse_level--; // fprintf(stderr, "CB EV done %lu \r\n", process);fflush(stderr); driver_demonitor_process(port, &monitor); } } int WxeApp::dispatch_cmds() { int more = 0; if(wxe_status != WXE_INITIATED) return more; recurse_level++; // fprintf(stderr, "\r\ndispatch_normal %d\r\n", recurse_level);fflush(stderr); wxe_queue->cb_start = 0; more = dispatch(wxe_queue); // fprintf(stderr, "\r\ndispatch_done %d\r\n", recurse_level);fflush(stderr); 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(); } delayed_delete->Cleanup(); 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; } } return more; } #define BREAK_BATCH 10000 int WxeApp::dispatch(wxeFifo * batch) { int ping = 0; int blevel = 0; int wait = 0; // Let event handling generate events sometime 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); switch(event->op) { case WXE_BATCH_END: if(blevel>0) { blevel--; if(blevel==0) wait += BREAK_BATCH/4; } 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 1; 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(); erl_drv_mutex_lock(wxe_batch_locker_m); batch->Cleanup(); } if(blevel <= 0 || wait >= BREAK_BATCH) { erl_drv_mutex_unlock(wxe_batch_locker_m); if(blevel > 0) { return 1; // We are still in a batch but we can let wx check for events } else { return 0; } } // sleep until something happens // fprintf(stderr, "%s:%d sleep %d %d %d\r\n", __FILE__, __LINE__, batch->m_n, blevel, wait);fflush(stderr); wxe_needs_signal = 1; wait += 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, ErlDrvTermData process) { wxeCommand *event; unsigned int peek; erl_drv_mutex_lock(wxe_batch_locker_m); peek = batch->Cleanup(batch->cb_start); while(true) { while((event = batch->Peek(&peek)) != NULL) { 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)) { erl_drv_mutex_unlock(wxe_batch_locker_m); 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: batch->cb_start = 0; event->Delete(); erl_drv_mutex_lock(wxe_batch_locker_m); batch->Strip(); erl_drv_mutex_unlock(wxe_batch_locker_m); return; case WXE_CB_START: // CB start from now accept message from CB process only process = event->caller; break; default: batch->cb_start = peek; // In case of recursive callbacks if(event->op < OPENGL_START) { wxe_dispatch(*event); } else { gl_dispatch(event->op,event->buffer,event->caller,event->bin); } break; } event->Delete(); erl_drv_mutex_lock(wxe_batch_locker_m); peek = batch->Cleanup(peek); } } // sleep until something happens // fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__, // peek, batch->m_n);fflush(stderr); wxe_needs_signal = 1; while(peek >= batch->m_n) { erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); peek = batch->Cleanup(peek); } 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 == 8) && ((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 if(refd->type != 4) { 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; const wxChar *class_info = wxT("unknown"); if(type < 10) { wxClassInfo *cinfo = ((wxObject *)ptr)->GetClassInfo(); class_info = cinfo->GetClassName(); } msg.Printf(wxT("Creating {wx_ref, %d, %s} at %p "), ref, class_info, 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(((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 != (void *)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 != (void *) NULL))) { ptrMap::iterator it; it = ptr2ref.find(temp); if(it != ptr2ref.end()) { wxeRefData *refd = it->second; refd->pid = pid; return ; } }; throw wxe_badarg(index); }