/* A* ------------------------------------------------------------------- B* This file contains source code for the PyMOL computer program C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. D* ------------------------------------------------------------------- E* It is unlawful to modify or remove this copyright notice. F* ------------------------------------------------------------------- G* Please see the accompanying LICENSE file for further information. H* ------------------------------------------------------------------- I* Additional authors of this source file include: -* -* -* Z* ------------------------------------------------------------------- */ /* meaning of defines _PYMOL_MONOLITHIC: means that we're building PyMOL and its Python C dependencies as one C library. That means we need to explicitly call the initialization functions for these libraries on startup. */ #include"os_python.h" #include"os_predef.h" #include"os_std.h" #include"Base.h" /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 #include"os_proprietary.h" #include #endif /* END PROPRIETARY CODE SEGMENT */ #ifdef _PYMOL_MINGW #define putenv _putenv #endif #include"os_time.h" #include"os_unix.h" #include"MemoryDebug.h" #include"Base.h" #include"Err.h" #include"P.h" #include"PConv.h" #include"Ortho.h" #include"Cmd.h" #include"main.h" #include"AtomInfo.h" #include"CoordSet.h" #include"Util.h" #include"Executive.h" #include"PyMOLOptions.h" #include"PyMOL.h" #include "Lex.h" #include "Seeker.h" #ifdef _PYMOL_IP_PROPERTIES #include"Property.h" #endif #include /** * Use with functions which return a PyObject - if they return NULL, then an * unexpected exception has occured. */ #define ASSERT_PYOBJECT_NOT_NULL(obj, return_value) \ if (!obj) { \ PyErr_Print(); \ return return_value; \ } static int label_copy_text(char *dst, const char *src, int len, int max) { dst += len; while(len < max) { if(!*src) break; *(dst++) = *(src++); len++; } *dst = 0; return len; } static int label_next_token(WordType dst, const char **expr) { const char *p = *expr; char *q = dst; char ch; int tok_len = 0; int tok_max = sizeof(WordType) - 1; /* skip leading whitespace (if any) */ while((ch = *p)) { if(ch > 33) break; p++; } /* copy the token */ while((ch = *p)) { if(((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || ((ch >= '0') && (ch <= '9')) || ((ch == '_'))) { if(tok_len < tok_max) { *(q++) = ch; tok_len++; } } else { break; } p++; } *q = 0; if(p != *expr) *expr = p; else if(*p) *expr = p + 1; /* always advance input by at least one character */ /* let caller know whether we read anything */ return (q != dst); } int PLabelExprUsesVariable(PyMOLGlobals * G, const char *expr, const char *var) { char ch, quote = 0; int escaped = false; while((ch = *(expr++))) { if(!quote) { if(ch == '\'') { quote = ch; } else if(ch == '"') { quote = ch; } else if((ch < 33) || (ch == '+') || (ch == '(') || (ch == ')')) { /* nop */ } else if(ch > 32) { WordType tok; expr--; if(label_next_token(tok, &expr)) { if(!strcmp(tok, var)) { return 1; } } } } else { if(ch == quote) { quote = 0; } else if(ch == '\\') { if(!escaped) { escaped = true; } else { escaped = false; } } } } return 0; } int PLabelAtomAlt(PyMOLGlobals * G, AtomInfoType * at, const char *model, const char *expr, int index) { /* alternate C implementation which bypasses Python expressions -- works only for simple label formats "..."+property+... */ int result = false; OrthoLineType label; int label_len = 0; int label_max = sizeof(OrthoLineType); OrthoLineType buffer; char ch, quote = 0; int escaped = false; const char *origexpr = expr; label[0] = 0; while((ch = *(expr++))) { if(!quote) { if(ch == '\'') { quote = ch; } else if(ch == '"') { quote = ch; } else if((ch < 33) || (ch == '+') || (ch == '(') || (ch == ')')) { /* nop */ } else if(ch > 32) { WordType tok; int tokresult = true; expr--; if(label_next_token(tok, &expr)) { /* brain-dead linear string matching */ buffer[0] = 0; if(!strcmp(tok, "model")) { label_len = label_copy_text(label, model, label_len, label_max); } else if(!strcmp(tok, "index")) { sprintf(buffer, "%d", index + 1); } else if(!strcmp(tok, "type")) { if(at->hetatm) label_len = label_copy_text(label, "HETATM", label_len, label_max); else label_len = label_copy_text(label, "ATOM", label_len, label_max); } else if(!strcmp(tok, "name")) { label_len = label_copy_text(label, LexStr(G, at->name), label_len, label_max); } else if(!strcmp(tok, "resn")) { label_len = label_copy_text(label, LexStr(G, at->resn), label_len, label_max); } else if(!strcmp(tok, "resi")) { sprintf(buffer, "%d%c", at->resv, at->inscode); } else if(!strcmp(tok, "resv")) { sprintf(buffer, "%d", at->resv); } else if(!strcmp(tok, "chain")) { label_len = label_copy_text(label, LexStr(G, at->chain), label_len, label_max); } else if(!strcmp(tok, "alt")) { label_len = label_copy_text(label, at->alt, label_len, label_max); } else if(!strcmp(tok, "segi")) { label_len = label_copy_text(label, LexStr(G, at->segi), label_len, label_max); } else if(!strcmp(tok, "ss")) { label_len = label_copy_text(label, at->ssType, label_len, label_max); } else if(!strcmp(tok, "vdw")) { sprintf(buffer, "%1.2f", at->vdw); } else if(!strcmp(tok, "elec_radius")) { sprintf(buffer, "%1.2f", at->elec_radius); } else if(!strcmp(tok, "text_type")) { const char *st = LexStr(G, at->textType); label_len = label_copy_text(label, st, label_len, label_max); } else if(!strcmp(tok, "custom")) { const char *st = LexStr(G, at->custom); label_len = label_copy_text(label, st, label_len, label_max); } else if(!strcmp(tok, "elem")) { label_len = label_copy_text(label, at->elem, label_len, label_max); } else if(!strcmp(tok, "geom")) { sprintf(buffer, "%d", at->geom); } else if(!strcmp(tok, "valence")) { sprintf(buffer, "%d", at->valence); } else if(!strcmp(tok, "rank")) { sprintf(buffer, "%d", at->rank); } else if(!strcmp(tok, "flags")) { if(at->flags) { sprintf(buffer, "%X", at->flags); } else { strcpy(buffer, "0"); } } else if(!strcmp(tok, "q")) { sprintf(buffer, "%1.2f", at->q); } else if(!strcmp(tok, "b")) { sprintf(buffer, "%1.2f", at->b); } else if(!strcmp(tok, "numeric_type")) { if(at->customType != cAtomInfoNoType) sprintf(buffer, "%d", at->customType); else { strcpy(buffer, "?"); } } else if(!strcmp(tok, "partial_charge")) { sprintf(buffer, "%1.3f", at->partialCharge); } else if(!strcmp(tok, "formal_charge")) { sprintf(buffer, "%d", at->formalCharge); } else if(!strcmp(tok, "stereo")) { strcpy(buffer, AtomInfoGetStereoAsStr(at)); } else if(!strcmp(tok, "color")) { sprintf(buffer, "%d", at->color); } else if(!strcmp(tok, "cartoon")) { sprintf(buffer, "%d", at->cartoon); } else if(!strcmp(tok, "ID")) { sprintf(buffer, "%d", at->id); } else if(!strcmp(tok, "str")) { /* nop */ } else { tokresult = false; } if(buffer[0]) { label_len = label_copy_text(label, buffer, label_len, label_max); } } else { if (tok[0]){ label_len = label_copy_text(label, "?", label_len, label_max); label_len = label_copy_text(label, tok, label_len, label_max); } else { tokresult = false; } } result |= tokresult; } else { if(label_len < label_max) { label[label_len] = '?'; label_len++; result = true; } } } else { if(ch == quote) { quote = 0; result = true; } else if(ch == '\\') { if(!escaped) { escaped = true; } else { if(label_len < label_max) { label[label_len] = ch; label_len++; } escaped = false; } } else { if(label_len < label_max) { label[label_len] = ch; label_len++; label[label_len] = 0; } } } } if (!result && !label[0]){ // if label is not set, just use expression as a string for label strncpy(label, origexpr, OrthoLineLength); result = true; } LexDec(G, at->label); at->label = result ? LexIdx(G, label) : 0; return (result); } #ifndef _PYMOL_NOPY /* all of the following Python objects must be invariant & global for the application */ /* these are module / module properties -- global and static for a given interpreter */ /* local to this C code module */ static PyObject *P_pymol = NULL; static PyObject *P_pymol_dict = NULL; /* must be refomed into globals and instance properties */ static PyObject *P_cmd = NULL; static PyObject *P_povray = NULL; static PyObject *P_traceback = NULL; static PyObject *P_parser = NULL; static PyObject *P_main = NULL; static PyObject *P_vfont = NULL; /* module import helper */ static PyObject * PImportModuleOrFatal(const char * name) { PyObject * mod = PyImport_ImportModule(name); if(!mod) { fprintf(stderr, "PyMOL-Error: can't find '%s'\n", name); exit(EXIT_FAILURE); } return mod; } static PyObject * PGetAttrOrFatal(PyObject * o, const char * name) { PyObject * attr = PyObject_GetAttrString(o, name); if(!attr) { fprintf(stderr, "PyMOL-Error: can't find '%s'\n", name); exit(EXIT_FAILURE); } return attr; } /* used elsewhere */ PyObject *P_menu = NULL; /* menu definitions are currently global */ PyObject *P_xray = NULL; /* okay as global */ PyObject *P_chempy = NULL; /* okay as global */ PyObject *P_models = NULL; /* okay as global */ PyObject *P_setting = NULL; /* okay as global -- just used for names */ PyObject *P_CmdException = nullptr; PyObject *P_QuietException = nullptr; static PyMappingMethods wrapperMappingMethods, settingMappingMethods; static PyTypeObject Wrapper_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wrapper.Wrapper", /* tp_name */ 0, /* tp_basicsize */ }; static PyTypeObject settingWrapper_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wrapper.SettingWrapper", /* tp_name */ 0, /* tp_basicsize */ }; /* * If `wob` is not in a valid state (outside iterate-family context), raise * an error and return false. */ static bool check_wrapper_scope(WrapperObject * wobj) { if (wobj && wobj->obj) return true; PyErr_SetString(PyExc_RuntimeError, "wrappers cannot be used outside the iterate-family commands"); return false; } /* * key: Python int (setting index) or str (setting name) * * Return the setting index or -1 for unknown `key` * * Raise LookupError if `key` doesn't name a known setting */ static int get_and_check_setting_index(PyMOLGlobals * G, PyObject * key) { int setting_id; if(PyInt_Check(key)) { setting_id = PyInt_AS_LONG(key); } else { key = PyObject_Str(key); setting_id = SettingGetIndex(G, PyString_AS_STRING(key)); Py_DECREF(key); } if (setting_id < 0 || setting_id >= cSetting_INIT) { PyErr_SetString(PyExc_LookupError, "unknown setting"); return -1; } return setting_id; } /* * Access a setting with iterate et. al. * * s[key] * * obj: `s` object in iterate-family namespace * * Raise LookupError if `key` doesn't name a known setting */ static PyObject *SettingWrapperObjectSubScript(PyObject *obj, PyObject *key){ auto& wobj = reinterpret_cast(obj)->wobj; int setting_id; PyObject *ret = NULL; if (!check_wrapper_scope(wobj)) { return NULL; } auto G = wobj->G; if ((setting_id = get_and_check_setting_index(G, key)) == -1) { return NULL; } if (wobj->idx >= 0){ // atom-state level ret = SettingGetIfDefinedPyObject(G, wobj->cs, wobj->idx, setting_id); } if (!ret){ // atom level ret = SettingGetIfDefinedPyObject(G, wobj->atomInfo, setting_id); if (!ret) { // object-state, object, or global ret = SettingGetPyObject(G, wobj->cs ? wobj->cs->Setting : NULL, wobj->obj->Setting, setting_id); } } return PConvAutoNone(ret); } /* * Set an atom or atom-state level setting with alter or alter_state. * * s[key] = val * * obj: `s` object in cmd.alter/cmd.alter_state namespace * * Return 0 on success or -1 on failure. * * Raise TypeError if setting not modifiable in the current context, and * LookupError if `key` doesn't name a known setting */ static int SettingWrapperObjectAssignSubScript(PyObject *obj, PyObject *key, PyObject *val){ auto& wobj = reinterpret_cast(obj)->wobj; if (!check_wrapper_scope(wobj)) { return -1; } int setting_id; auto G = wobj->G; if (wobj->read_only){ PyErr_SetString(PyExc_TypeError, "Use alter/alter_state to modify settings"); return -1; } if ((setting_id = get_and_check_setting_index(G, key)) == -1) { return -1; } if (wobj->idx >= 0) { // atom-state level if(!SettingLevelCheck(G, setting_id, cSettingLevel_astate)) { PyErr_SetString(PyExc_TypeError, "only atom-state level settings can be set in alter_state function"); return -1; // failure } else if (CoordSetSetSettingFromPyObject(G, wobj->cs, wobj->idx, setting_id, val)) { } } else { // atom level if(!SettingLevelCheck(G, setting_id, cSettingLevel_atom)) { PyErr_SetString(PyExc_TypeError, "only atom-level settings can be set in alter function"); return -1; // failure } else if (AtomInfoSetSettingFromPyObject(G, wobj->atomInfo, setting_id, val)) { AtomInfoSettingGenerateSideEffects(G, wobj->obj, setting_id, wobj->atm); } } return 0; // success } /* * Python iterator over atom or atom-state setting indices */ static PyObject* SettingWrapperObjectIter(PyObject *self) { auto& wobj = reinterpret_cast(self)->wobj; if (!check_wrapper_scope(wobj)) { return NULL; } int unique_id = wobj->atomInfo->unique_id; if (wobj->idx >= 0) { unique_id = wobj->cs->atom_state_setting_id ? wobj->cs->atom_state_setting_id[wobj->idx] : 0; } PyObject * items = SettingUniqueGetIndicesAsPyList(wobj->G, unique_id); PyObject * iter = PyObject_GetIter(items); Py_XDECREF(items); return iter; } /* * Allows attribute-like syntax for item lookups * * o.key -> o[key] if `key` is not an attribute of `o` */ static PyObject* PyObject_GenericGetAttrOrItem(PyObject *o, PyObject *key) { PyObject *ret = PyObject_GenericGetAttr(o, key); if (!PyErr_Occurred()) return ret; PyErr_Clear(); return PyObject_GetItem(o, key); } /* * Allows attribute-like syntax for item assignment * * `o.key = value` -> `o[key] = value` */ static int PyObject_GenericSetAttrAsItem(PyObject *o, PyObject *key, PyObject *value) { return PyObject_SetItem(o, key, value); } /* * iterate-family namespace implementation: lookup * * Raise NameError if state attributes are accessed outside of iterate_state */ static PyObject * WrapperObjectSubScript(PyObject *obj, PyObject *key){ static PyObject * pystr_HETATM = PyString_InternFromString("HETATM"); static PyObject * pystr_ATOM = PyString_InternFromString("ATOM"); static PyObject * pystr_QuestionMark = PyString_InternFromString("?"); WrapperObject *wobj = (WrapperObject*)obj; if (!check_wrapper_scope(wobj)) return NULL; PyMOLGlobals * G = wobj->G; const char *aprop; AtomPropertyInfo *ap; PyObject *ret = NULL; bool borrowed = false; PyObject *keyobj = PyObject_Str(key); aprop = PyString_AS_STRING(keyobj); ap = PyMOL_GetAtomPropertyInfo(wobj->G->PyMOL, aprop); Py_DECREF(keyobj); if (ap){ #ifdef _PYMOL_IP_EXTRAS #ifdef NO_MMLIBS // static -> only show these warnings once static bool warning_shown_text_type = false; #endif switch (ap->id) { case ATOM_PROP_STEREO: if (ObjectMoleculeUpdateMMStereoInfoForState(G, wobj->obj, wobj->state - 1) < 0) { PyErr_SetString(PyExc_RuntimeError, "please install rdkit or set SCHRODINGER variable"); return NULL; } break; case ATOM_PROP_TEXT_TYPE: #ifndef NO_MMLIBS ObjectMoleculeUpdateAtomTypeInfoForState(G, wobj->obj, wobj->state - 1, 1, 0); #else if (!warning_shown_text_type && !(wobj->cs && wobj->cs->validTextType)) { warning_shown_text_type = true; PRINTFB(G, FB_ObjectMolecule, FB_Warnings) " Notice: Automatic 'text_type' assignment feature removed in PyMOL 2.0.\n" ENDFB(G); } #endif break; } #endif switch (ap->Ptype){ case cPType_string: { char *val = (char*)(((char*)wobj->atomInfo) + ap->offset); ret = PyString_FromString(val); } break; case cPType_schar: { signed char val = *(signed char*)(((char*)wobj->atomInfo) + ap->offset); ret = PyInt_FromLong((long)val); } break; case cPType_int: { int val = *(int*)(((char*)wobj->atomInfo) + ap->offset); ret = PyInt_FromLong((long)val); } break; case cPType_int_as_string: { const char *st = LexStr(wobj->G, *reinterpret_cast (((char*)wobj->atomInfo) + ap->offset)); ret = PyString_FromString(st); } break; case cPType_float: { float val = *(float*)(((char*)wobj->atomInfo) + ap->offset); ret = PyFloat_FromDouble(val); } break; case cPType_char_as_type: { ret = wobj->atomInfo->hetatm ? pystr_HETATM : pystr_ATOM; borrowed = true; } break; case cPType_model: ret = PyString_FromString(wobj->obj->Name); break; case cPType_index: { ret = PyInt_FromLong((long)wobj->atm + 1); } break; case cPType_int_custom_type: { int val = *(int*)(((char*)wobj->atomInfo) + ap->offset); if(val != cAtomInfoNoType){ ret = PyInt_FromLong((long)val); } else { ret = pystr_QuestionMark; borrowed = true; } } break; case cPType_xyz_float: { if (wobj->idx >= 0){ ret = PyFloat_FromDouble(wobj->cs->coordPtr(wobj->idx)[ap->offset]); } else { PyErr_SetString(PyExc_NameError, "x/y/z only available in iterate_state and alter_state"); } } break; case cPType_settings: if (!wobj->settingWrapperObject) { wobj->settingWrapperObject = PyType_GenericNew(&settingWrapper_Type, Py_None, Py_None); reinterpret_cast(wobj->settingWrapperObject)->wobj = wobj; } ret = wobj->settingWrapperObject; borrowed = true; break; case cPType_properties: PyErr_SetString(PyExc_NotImplementedError, "'properties/p' not supported in Open-Source PyMOL"); break; case cPType_state: ret = PyInt_FromLong((long)wobj->state); break; default: switch (ap->id) { case ATOM_PROP_RESI: { char resi[8]; AtomResiFromResv(resi, sizeof(resi), wobj->atomInfo); ret = PyString_FromString(resi); } break; case ATOM_PROP_STEREO: { auto mmstereotype = AtomInfoGetStereoAsStr(wobj->atomInfo); ret = PyString_FromString(mmstereotype); } break; case ATOM_PROP_ONELETTER: { const char * st = LexStr(G, wobj->atomInfo->resn); char abbr[2] = {SeekerGetAbbr(G, st, 'O', 'X'), 0}; ret = PyString_FromString(abbr); } break; default: PyErr_SetString(PyExc_SystemError, "unhandled atom property type"); } } } else { /* if not an atom property, check if local variable in dict */ if (wobj->dict) { ret = PyDict_GetItem(wobj->dict, key); } if (ret) { borrowed = true; } else { PyErr_SetNone(PyExc_KeyError); } } if (borrowed) PXIncRef(ret); return ret; } /* * iterate-family namespace implementation: assignment * * Raise TypeError for read-only variables */ static int WrapperObjectAssignSubScript(PyObject *obj, PyObject *key, PyObject *val){ WrapperObject *wobj = (WrapperObject*)obj; if (!check_wrapper_scope(wobj)) { return -1; } { char aprop[16]; PyObject *keyobj = PyObject_Str(key); UtilNCopy(aprop, PyString_AS_STRING(keyobj), sizeof(aprop)); Py_DECREF(keyobj); AtomPropertyInfo *ap = PyMOL_GetAtomPropertyInfo(wobj->G->PyMOL, aprop); if (ap){ #ifdef _PYMOL_IP_EXTRAS if (wobj->cs) { switch (ap->id) { case ATOM_PROP_STEREO: wobj->cs->validMMStereo = MMPYMOLX_PROP_STATE_USER; break; case ATOM_PROP_TEXT_TYPE: wobj->cs->validTextType = MMPYMOLX_PROP_STATE_USER; break; } } #endif short changed = false; if (wobj->read_only){ PyErr_SetString(PyExc_TypeError, "Use alter/alter_state to modify values"); return -1; } // alter_state: must be setting x/y/z or flags if (wobj->idx >= 0) { if (ap->Ptype == cPType_xyz_float) { float * v = wobj->cs->coordPtr(wobj->idx) + ap->offset; PConvPyObjectToFloat(val, v); return 0; } } switch (ap->Ptype){ case cPType_string: { PyObject *valobj = PyObject_Str(val); const char *valstr = PyString_AS_STRING(valobj); char *dest = (char*)(((char*)wobj->atomInfo) + ap->offset); if (strlen(valstr) > ap->maxlen){ strncpy(dest, valstr, ap->maxlen); } else { strcpy(dest, valstr); } Py_DECREF(valobj); changed = true; } break; case cPType_schar: { int valint = PyInt_AsLong(val); signed char *dest; if (valint == -1 && PyErr_Occurred()) break; dest = (signed char*)(((char*)wobj->atomInfo) + ap->offset); *dest = valint; changed = true; } break; case cPType_int: { int valint = PyInt_AsLong(val); int *dest; if (valint == -1 && PyErr_Occurred()) break; dest = (int*)(((char*)wobj->atomInfo) + ap->offset); *dest = valint; changed = true; } break; case cPType_int_as_string: { auto dest = reinterpret_cast (((char*)wobj->atomInfo) + ap->offset); PyObject *valobj = PyObject_Str(val); const char *valstr = PyString_AS_STRING(valobj); LexDec(wobj->G, *dest); *dest = LexIdx(wobj->G, valstr); Py_DECREF(valobj); changed = true; } break; case cPType_float: { float *dest = (float*)(((char*)wobj->atomInfo) + ap->offset); changed = PConvPyObjectToFloat(val, dest); } break; case cPType_char_as_type: { PyObject *valobj = PyObject_Str(val); const char *valstr = PyString_AS_STRING(valobj); wobj->atomInfo->hetatm = ((valstr[0] == 'h') || (valstr[0] == 'H')); Py_DECREF(valobj); changed = true; } break; case cPType_int_custom_type: { PyObject *valobj = PyObject_Str(val); const char *valstr = PyString_AS_STRING(valobj); int *dest = (int*)(((char*)wobj->atomInfo) + ap->offset); if (valstr[0] == '?'){ *dest = cAtomInfoNoType; } else { int valint = PyInt_AS_LONG(val); *dest = valint; } Py_DECREF(valobj); changed = true; } break; case cPType_xyz_float: PyErr_SetString(PyExc_NameError, "x/y/z only available in alter_state"); return -1; default: switch (ap->id) { case ATOM_PROP_RESI: if (PConvPyIntToInt(val, &wobj->atomInfo->resv)) { wobj->atomInfo->inscode = '\0'; } else { PyObject *valobj = PyObject_Str(val); wobj->atomInfo->setResi(PyString_AS_STRING(valobj)); Py_DECREF(valobj); } break; case ATOM_PROP_STEREO: { PyObject *valobj = PyObject_Str(val); const char *valstr = PyString_AS_STRING(valobj); AtomInfoSetStereo(wobj->atomInfo, valstr); Py_DECREF(valobj); } break; default: PyErr_Format(PyExc_TypeError, "'%s' is read-only", aprop); return -1; } } if (changed){ switch (ap->id){ case ATOM_PROP_ELEM: wobj->atomInfo->protons = 0; wobj->atomInfo->vdw = 0; AtomInfoAssignParameters(wobj->G, wobj->atomInfo); break; case ATOM_PROP_RESV: wobj->atomInfo->inscode = '\0'; break; case ATOM_PROP_SS: wobj->atomInfo->ssType[0] = toupper(wobj->atomInfo->ssType[0]); break; case ATOM_PROP_FORMAL_CHARGE: wobj->atomInfo->chemFlag = false; break; } } } else { /* if not an atom property, then its a local variable, store it */ if (!wobj->dict) { wobj->dict = PyDict_New(); } PyDict_SetItem(wobj->dict, key, val); } } return 0; /* 0 success, -1 failure */ } /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 static PyObject *P_time = NULL; static PyObject *P_sleep = NULL; #endif /* END PROPRIETARY CODE SEGMENT */ static void PUnlockAPIWhileBlocked(PyMOLGlobals * G); static void PLockAPIWhileBlocked(PyMOLGlobals * G); #define P_log_file_str "_log_file" /** * Acquire the status lock (blocking) * @pre GIL */ void PLockStatus(PyMOLGlobals * G) { /* assumes we have the GIL */ PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->lock_api_status, "acquire", nullptr)); } /** * Acquire the status lock (non-blocking) * @return True if lock could be acquired * @pre GIL */ int PLockStatusAttempt(PyMOLGlobals * G) { /* assumes we have the GIL */ unique_PyObject_ptr got_lock( PYOBJECT_CALLMETHOD(G->P_inst->lock_api_status, "acquire", "i", 0)); ASSERT_PYOBJECT_NOT_NULL(got_lock, true); return PyObject_IsTrue(got_lock.get()); } /** * Release the status lock * @pre GIL */ void PUnlockStatus(PyMOLGlobals * G) { /* assumes we have the GIL */ PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->lock_api_status, "release", nullptr)); } /** * Acquire GLUT lock * @pre GIL */ static void PLockGLUT(PyMOLGlobals * G) { /* assumes we have the GIL */ PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->lock_api_glut, "acquire", nullptr)); } /** * Release GLUT lock * @pre GIL */ static void PUnlockGLUT(PyMOLGlobals * G) { /* assumes we have the GIL */ PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->lock_api_glut, "release", nullptr)); } static long P_glut_thread_id = -1; /* enables us to keep glut out if by chance it grabs the API * in the middle of a nested API based operation */ static void PCatchInit(void); /* PyObject *GetBondsDict(PyMOLGlobals *G) { PyObject *result = NULL; result = PyObject_GetAttrString(P_chempy,"bonds"); if(!result) ErrMessage(G,"PyMOL","can't find 'chempy.bonds.bonds'"); return(result); } */ PyObject *PGetFontDict(PyMOLGlobals * G, float size, int face, int style) { /* assumes we have a valid interpreter lock */ PyObject *result = NULL; if(!P_vfont) { P_vfont = PyImport_ImportModule("pymol.vfont"); } if(!P_vfont) { PRINTFB(G, FB_Python, FB_Errors) " PyMOL-Error: can't find module 'vfont'" ENDFB(G); } else { result = PYOBJECT_CALLMETHOD(P_vfont, "get_font", "fii", size, face, style); } return (PConvAutoNone(result)); } int PComplete(PyMOLGlobals * G, char *str, int buf_size) { int ret = false; PyObject *result; const char *st2; PBlockAndUnlockAPI(G); if(G->P_inst->complete) { result = PYOBJECT_CALLFUNCTION(G->P_inst->complete, "s", str); if(result) { if(PyString_Check(result)) { st2 = PyString_AsString(result); UtilNCopy(str, st2, buf_size); ret = true; } Py_DECREF(result); } } PLockAPIAndUnblock(G); return (ret); } int PTruthCallStr0(PyObject * object, const char *method) { int result = false; PyObject *tmp; tmp = PYOBJECT_CALLMETHOD(object, method, ""); if(tmp) { if(PyObject_IsTrue(tmp)) result = 1; Py_DECREF(tmp); } return (result); } int PTruthCallStr(PyObject * object, const char *method, const char *argument) { int result = false; PyObject *tmp; tmp = PYOBJECT_CALLMETHOD(object, method, "s", argument); if(tmp) { if(PyObject_IsTrue(tmp)) result = 1; Py_DECREF(tmp); } return (result); } int PTruthCallStr1i(PyObject * object, const char *method, int argument) { int result = false; PyObject *tmp; tmp = PYOBJECT_CALLMETHOD(object, method, "i", argument); if(tmp) { if(PyObject_IsTrue(tmp)) result = 1; Py_DECREF(tmp); } return (result); } int PTruthCallStr1s(PyObject * object, const char *method, const char *argument) { int result = false; PyObject *tmp; tmp = PYOBJECT_CALLMETHOD(object, method, "s", argument); if(tmp) { if(PyObject_IsTrue(tmp)) result = 1; Py_DECREF(tmp); } return (result); } int PTruthCallStr4i(PyObject * object, const char *method, int a1, int a2, int a3, int a4) { int result = false; PyObject *tmp; tmp = PYOBJECT_CALLMETHOD(object, method, "iiii", a1, a2, a3, a4); if(tmp) { if(PyObject_IsTrue(tmp)) result = 1; Py_DECREF(tmp); } return (result); } PyObject *PXIncRef(PyObject * obj) { if(!obj) obj = Py_None; Py_XINCREF(obj); return obj; } void PXDecRef(PyObject * obj) { Py_XDECREF(obj); } static ov_status CacheCreateEntry(PyObject ** result, PyObject * input) { ov_status status = OV_STATUS_FAILURE; if(input && PyTuple_Check(input)) { ov_size tuple_size = PyTuple_Size(input); ov_size tot_size = tuple_size; PyObject *hash_code = PyTuple_New(tuple_size); PyObject *entry = PyList_New(6); if(hash_code && entry) { /* compute hash codes & total input size */ ov_size i; status = OV_STATUS_SUCCESS; for(i = 0; i < tuple_size; i++) { PyObject *item = PyTuple_GetItem(input, i); long hash_long; if(item != Py_None) { /* here we are assuming that different Python versions will * hash tuples of ints & floats in exactly the same way (at least to 31 bits of significance) */ hash_long = 0x7FFFFFFF & PyObject_Hash(item); /* pos 32 bit # to preserve 32-bit/64-bit compat */ } else { hash_long = 0; /* None doesn't hash consistently from Python version to version */ } PyTuple_SetItem(hash_code, i, PyInt_FromLong(hash_long)); if(PyTuple_Check(item)) { tot_size += PyTuple_Size(item); } } PyList_SetItem(entry, 0, PyInt_FromLong(tot_size)); PyList_SetItem(entry, 1, hash_code); PyList_SetItem(entry, 2, PXIncRef(input)); PyList_SetItem(entry, 3, PXIncRef(NULL)); PyList_SetItem(entry, 4, PyInt_FromLong(0)); /* access count */ PyList_SetItem(entry, 5, PyFloat_FromDouble(0.0)); /* timestamp */ } if(!OV_OK(status)) { PXDecRef(hash_code); PXDecRef(entry); } else { *result = entry; } } if(PyErr_Occurred()) PyErr_Print(); return status; } ov_status PCacheSet(PyMOLGlobals * G, PyObject * entry, PyObject * output) { ov_status status = OV_STATUS_FAILURE; if(G->P_inst->cache && output) { ov_size tuple_size = PyTuple_Size(output); ov_size tot_size = tuple_size + PyInt_AsLong(PyList_GetItem(entry, 0)); status = OV_STATUS_SUCCESS; { ov_size i; for(i = 0; i < tuple_size; i++) { PyObject *item = PyTuple_GetItem(output, i); if(PyTuple_Check(item)) { tot_size += PyTuple_Size(item); } } } PyList_SetItem(entry, 0, PyInt_FromLong(tot_size)); /* update total size */ PyList_SetItem(entry, 3, PXIncRef(output)); PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->cmd, "_cache_set", "OiO", entry, SettingGetGlobal_i(G, cSetting_cache_max), G->P_inst->cmd)); /* compute the hash codes */ } if(PyErr_Occurred()) PyErr_Print(); return status; } ov_status PCacheGet(PyMOLGlobals * G, PyObject ** result_output, PyObject ** result_entry, PyObject * input) { ov_status status = OV_STATUS_NO; if(G->P_inst->cache) { PyObject *entry = NULL; PyObject *output = NULL; if(OV_OK(CacheCreateEntry(&entry, input))) { output = PYOBJECT_CALLMETHOD(G->P_inst->cmd, "_cache_get", "OOO", entry, Py_None, G->P_inst->cmd); if(output == Py_None) { Py_DECREF(output); output = NULL; } else { status = OV_STATUS_YES; } } /* compute the hash codes */ if(OV_OK(status)) { *result_entry = entry; *result_output = output; } else { PXDecRef(entry); PXDecRef(output); } } if(PyErr_Occurred()) PyErr_Print(); return status; } void PSleepWhileBusy(PyMOLGlobals * G, int usec) { #ifndef WIN32 struct timeval tv; PRINTFD(G, FB_Threads) " PSleep-DEBUG: napping.\n" ENDFD; tv.tv_sec = 0; tv.tv_usec = usec; select(0, NULL, NULL, NULL, &tv); PRINTFD(G, FB_Threads) " PSleep-DEBUG: nap over.\n" ENDFD; #else /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(P_sleep, "f", usec / 1000000.0)); PUnblock(G); /* END PROPRIETARY CODE SEGMENT */ #endif } void PSleepUnlocked(PyMOLGlobals * G, int usec) { /* can only be called by the glut process */ #ifndef WIN32 struct timeval tv; PRINTFD(G, FB_Threads) " PSleep-DEBUG: napping.\n" ENDFD; tv.tv_sec = 0; tv.tv_usec = usec; select(0, NULL, NULL, NULL, &tv); PRINTFD(G, FB_Threads) " PSleep-DEBUG: nap over.\n" ENDFD; #else /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(P_sleep, "f", usec / 1000000.0)); PUnblock(G); /* END PROPRIETARY CODE SEGMENT */ #endif } void PSleep(PyMOLGlobals * G, int usec) { /* can only be called by the glut process */ #ifndef WIN32 struct timeval tv; PUnlockAPIAsGlut(G); PRINTFD(G, FB_Threads) " PSleep-DEBUG: napping.\n" ENDFD; tv.tv_sec = 0; tv.tv_usec = usec; select(0, NULL, NULL, NULL, &tv); PRINTFD(G, FB_Threads) " PSleep-DEBUG: nap over.\n" ENDFD; PLockAPIAsGlut(G, true); #else /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ PBlockAndUnlockAPI(G); PXDecRef(PYOBJECT_CALLFUNCTION(P_sleep, "f", usec / 1000000.0)); PLockAPIAndUnblock(G); /* END PROPRIETARY CODE SEGMENT */ #endif } static PyObject *PCatchWrite(PyObject * self, PyObject * args); static PyObject *PCatch_install(PyObject * self, PyObject * args); static void my_interrupt(int a) { exit(EXIT_FAILURE); } void PDumpTraceback(PyObject * err) { PYOBJECT_CALLMETHOD(P_traceback, "print_tb", "O", err); } void PDumpException() { PYOBJECT_CALLMETHOD(P_traceback, "print_exc", ""); } static WrapperObject * WrapperObjectNew() { auto wobj = (WrapperObject *)PyType_GenericNew(&Wrapper_Type, Py_None, Py_None); wobj->dict = NULL; wobj->settingWrapperObject = NULL; #ifdef _PYMOL_IP_PROPERTIES wobj->propertyWrapperObject = NULL; #endif return wobj; } int PAlterAtomState(PyMOLGlobals * G, PyCodeObject *expr_co, int read_only, ObjectMolecule *obj, CoordSet *cs, int atm, int idx, int state, PyObject * space) /* assumes Blocked python interpreter */ { int result = true; auto wobj = WrapperObjectNew(); wobj->G = G; wobj->obj = obj; wobj->cs = cs; wobj->atomInfo = obj->AtomInfo + atm; wobj->atm = atm; wobj->idx = idx; wobj->read_only = read_only; wobj->state = state + 1; PXDecRef(PyEval_EvalCode(expr_co, space, (PyObject*)wobj)); WrapperObjectReset(wobj); if(PyErr_Occurred()) { PyErr_Print(); result = false; } return result; } int PAlterAtom(PyMOLGlobals * G, ObjectMolecule *obj, CoordSet *cs, PyCodeObject *expr_co, int read_only, int atm, PyObject * space) { int state = (obj->DiscreteFlag ? obj->AtomInfo[atm].discrete_state : 0) - 1; return PAlterAtomState(G, expr_co, read_only, obj, cs, atm, /* idx */ -1, state, space); } /* * String conversion which takes "label_digits" setting into account. */ static int PLabelPyObjectToStrMaxLen(PyMOLGlobals * G, PyObject * obj, char *buffer, int maxlen) { if (obj && PyFloat_Check(obj)) { snprintf(buffer, maxlen + 1, "%.*f", SettingGetGlobal_i(G, cSetting_label_digits), PyFloat_AsDouble(obj)); return true; } return PConvPyObjectToStrMaxLen(obj, buffer, maxlen); } int PLabelAtom(PyMOLGlobals * G, ObjectMolecule *obj, CoordSet *cs, PyCodeObject *expr_co, int atm) { int result = true; PyObject *P_inst_dict = G->P_inst->dict; PyObject *resultPyObject; OrthoLineType label; AtomInfoType * ai = obj->AtomInfo + atm; if (!expr_co){ // unsetting label LexAssign(G, ai->label, 0); return true; } auto wobj = WrapperObjectNew(); wobj->G = G; wobj->obj = obj; wobj->cs = cs; wobj->atomInfo = ai; wobj->atm = atm; wobj->idx = -1; wobj->read_only = true; if (obj->DiscreteFlag) { wobj->state = obj->AtomInfo[atm].discrete_state; } else { wobj->state = 0; } resultPyObject = PyEval_EvalCode(expr_co, P_inst_dict, (PyObject*)wobj); WrapperObjectReset(wobj); if(PyErr_Occurred()) { PyErr_Print(); result = false; } else { result = true; if(!PLabelPyObjectToStrMaxLen(G, resultPyObject, label, sizeof(OrthoLineType) - 1)) result = false; if(PyErr_Occurred()) { PyErr_Print(); result = false; } if(result) { LexAssign(G, ai->label, label); } else { ErrMessage(G, "Label", "Aborting on error. Labels may be incomplete."); } } PXDecRef(resultPyObject); return (result); } /** * - Release API lock with flushing * - Pop valid context * - Release GLUT lock * @pre no GIL */ void PUnlockAPIAsGlut(PyMOLGlobals * G) { /* must call with unblocked interpreter */ PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", 0, G->P_inst->cmd)); /* NOTE this may flush the command buffer! */ PLockStatus(G); PyMOL_PopValidContext(G->PyMOL); PUnlockStatus(G); PUnlockGLUT(G); PUnblock(G); } /** * - Release API lock without flushing * - Pop valid context * - Release GLUT lock * @pre no GIL */ void PUnlockAPIAsGlutNoFlush(PyMOLGlobals * G) { /* must call with unblocked interpreter */ PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", -1, G->P_inst->cmd)); /* prevents flushing of the buffer */ PLockStatus(G); PyMOL_PopValidContext(G->PyMOL); PUnlockStatus(G); PUnlockGLUT(G); PUnblock(G); } /** * Acquire API lock * @param block_if_busy If false and PyMOL is busy, do not block * @return False if API lock could not be acquired * @pre GIL */ static int get_api_lock(PyMOLGlobals * G, int block_if_busy) { if (!block_if_busy) { unique_PyObject_ptr got_lock( PYOBJECT_CALLFUNCTION(G->P_inst->lock_attempt, "O", G->P_inst->cmd)); ASSERT_PYOBJECT_NOT_NULL(got_lock, false); if (PyObject_IsTrue(got_lock.get())) { return true; } PLockStatus(G); bool busy = PyMOL_GetBusy(G->PyMOL, false); PUnlockStatus(G); if (busy) { return false; } } PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->lock, "O", G->P_inst->cmd)); return true; } /** * - Acquire GLUT lock * - Push valid context * - Acquire API lock * - Wait for glut_thread_keep_out == 0 * * @param block_if_busy If false and PyMOL is busy, API lock is non-blocking * @return False if locks could not be acquired * @pre no GIL */ int PLockAPIAsGlut(PyMOLGlobals * G, int block_if_busy) { PBlock(G); PLockGLUT(G); PLockStatus(G); PyMOL_PushValidContext(G->PyMOL); PUnlockStatus(G); while (true) { if(!get_api_lock(G, block_if_busy)) { PLockStatus(G); PyMOL_PopValidContext(G->PyMOL); PUnlockStatus(G); PUnlockGLUT(G); PUnblock(G); return false; /* busy -- so allow main to update busy status display (if any) */ } if (G->P_inst->glut_thread_keep_out == 0) { break; } /* IMPORTANT: keeps the glut thread out of an API operation... */ /* NOTE: the keep_out variable can only be changed or read by the thread holding the API lock, therefore it is safe even through increment isn't atomic. */ PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", -1, G->P_inst->cmd)); /* prevent buffer flushing */ #ifndef WIN32 { struct timeval tv; PUnblock(G); tv.tv_sec = 0; tv.tv_usec = 50000; select(0, NULL, NULL, NULL, &tv); PBlock(G); } #else /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ PXDecRef(PYOBJECT_CALLFUNCTION(P_sleep, "f", 0.050)); /* END PROPRIETARY CODE SEGMENT */ #endif } PUnblock(G); /* API is now locked, so we can free up Python... */ return true; } /* THESE CALLS ARE REQUIRED FOR MONOLITHIC COMPILATION TO SUCCEED UNDER WINDOWS. */ #ifndef _PYMOL_EMBEDDED #ifdef __cplusplus extern "C" { #endif /* * void initExtensionClass(void); * void initsglite(void); */ void init_champ(void); #ifdef __cplusplus } #endif #endif #ifdef _PYMOL_MONOLITHIC #ifndef _PYMOL_EMBEDDED /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 #ifdef _PYMOL_NUMPY_INIT void init_numpy(); void initmultiarray(); void initarrayfns(); void initlapack_lite(); void initumath(); void initranlib(); #endif #endif /* END PROPRIETARY CODE SEGMENT */ #endif #endif #ifdef _PYMOL_MONOLITHIC #ifndef _PYMOL_EMBEDDED #ifdef __cplusplus extern "C" { #endif /* * void initExtensionClass(void); * void initsglite(void); */ void init_champ(void); #ifdef _PYMOL_PYOMM void init_pyomm(void); #endif #ifdef __cplusplus } #endif #endif #endif /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #if 0 // def WIN32 static int IsSecurityRequired() { DWORD WindowsVersion = GetVersion(); DWORD WindowsMajorVersion = (DWORD) (LOBYTE(LOWORD(WindowsVersion))); DWORD WindowsMinorVersion = (DWORD) (HIBYTE(LOWORD(WindowsVersion))); if(WindowsVersion >= 0x80000000) return FALSE; return TRUE; } #endif /* END PROPRIETARY CODE SEGMENT */ void PSetupEmbedded(PyMOLGlobals * G, int argc, char **argv) { /* This routine is called if we are running with an embedded Python interpreter */ PyObject *args; /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #if 0 // def WIN32 /* Windows PyMOL now ships with Python 2.5 for both 32 and 64 bit */ #ifndef EMBEDDED_PYTHONHOME #define EMBEDDED_PYTHONHOME "\\py27" #endif { /* Automatically hide the window if this process was started as a vanilla console application (by double-clicking). Conveniently, this doesn't hide the window when launched from a command window. */ HWND hwndFound; if(hwndFound = FindWindowA(NULL, argv[0])) { ShowWindow(hwndFound, SW_HIDE); } } { /* if PYMOL_PATH and/or PYTHONHOME isn't in the environment coming in, then the user may simply have clicked PyMOL.exe, in which case we need to consult the registry regarding the location of the install */ static char line1[8092]; static char line2[8092]; { /* If no PYMOL_PATH specific, but we were launched with an * absolute path, then try using that path first. With embedded * builds, the .EXE should always be located at the root of * $PYMOL_PATH */ char *pymol_path = getenv("PYMOL_PATH"); if((!pymol_path) && (argc > 0) && argv[0][0] && (argv[0][1] == ':') && (argv[0][2] == '\\')) { char *p; strcpy(line1, "PYMOL_PATH="); strcat(line1, argv[0]); p = line1 + strlen(line1); while(*p != '\\') { *p = 0; p--; } *p = 0; putenv(line1); } } { OrthoLineType path_buffer; HKEY phkResult; DWORD lpcbData; DWORD lpType = REG_SZ; int r1, r2; char *pymol_path; char *pythonhome; int pythonhome_set = false; int restart_flag = false; pymol_path = getenv("PYMOL_PATH"); pythonhome = getenv("PYTHONHOME"); if((!pymol_path) || (!pythonhome)) { lpcbData = sizeof(OrthoLineType) - 1; r1 = RegOpenKeyExA(HKEY_CLASSES_ROOT, #ifdef PYMOL_EVAL "Software\\PyMOL\\PyMOL Eval\\PYMOL_PATH", #else "Software\\PyMOL\\PyMOL\\PYMOL_PATH", #endif 0, KEY_EXECUTE, &phkResult); if(r1 != ERROR_SUCCESS) { r1 = RegOpenKeyExA(HKEY_CURRENT_USER, #ifdef PYMOL_EVAL "Software\\PyMOL\\PyMOL Eval\\PYMOL_PATH", #else "Software\\PyMOL\\PyMOL\\PYMOL_PATH", #endif 0, KEY_EXECUTE, &phkResult); } if(r1 == ERROR_SUCCESS) { r2 = RegQueryValueExA(phkResult, "", NULL, &lpType, (LPBYTE) path_buffer, &lpcbData); if(r2 == ERROR_SUCCESS) { /* use environment variable PYMOL_PATH first, registry entry second */ if(!pymol_path) { strcpy(line1, "PYMOL_PATH="); strcat(line1, path_buffer); _putenv(line1); if(!pythonhome) { /* only set PYTHONHOME if already setting new PYMOL_PATH */ pythonhome_set = true; strcpy(line2, "PYTHONHOME="); strcat(line2, path_buffer); strcat(line2, EMBEDDED_PYTHONHOME); restart_flag = true; _putenv(line2); } } } RegCloseKey(phkResult); } /* this allows us to just specify PYMOL_PATH with no registry entries */ if((!pythonhome_set) && (pymol_path) && (!pythonhome)) { strcpy(line2, "PYTHONHOME="); strcat(line2, pymol_path); strcat(line2, EMBEDDED_PYTHONHOME); _putenv(line2); restart_flag = true; } } if(restart_flag && getenv("PYMOL_PATH") && getenv("PYTHONHOME")) { /* now that we have the environment defined, restart the process * so that Python can use the new environment. If we don't do * this, then Python won't see the new environment vars. Why not? */ /* note that we use CreateProcesss to launch the console * application instead of exec or spawn in order to hide the * console window. Otherwise a console window might appear, and * that would suck. */ char command[8092]; static char cmd_line[8092]; char *p, *q; int a; /* copy arguments, installing quotes around them */ sprintf(command, "%s\\pymol.exe", getenv("PYMOL_PATH")); p = cmd_line; sprintf(p, "\"%s\"", command); p += strlen(p); *(p++) = ' '; *p = 0; for(a = 1; a <= argc; a++) { q = argv[a]; if(q) { if(*q != '"') { /* add quotes if not present */ *(p++) = '"'; while(*q) { *(p++) = *(q++); } *(p++) = '"'; } else { while(*q) { *(p++) = *(q++); } } *(p++) = 32; *p = 0; } } { LPSECURITY_ATTRIBUTES lpSA = NULL; PSECURITY_DESCRIPTOR lpSD = NULL; STARTUPINFOA si; PROCESS_INFORMATION pi; HANDLE hProcess = GetCurrentProcess(); ZeroMemory(&si, sizeof(STARTUPINFOA)); si.cb = sizeof(STARTUPINFOA); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; if(IsSecurityRequired()) { lpSD = GlobalAlloc(GPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); InitializeSecurityDescriptor(lpSD, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(lpSD, -1, 0, 0); lpSA = (LPSECURITY_ATTRIBUTES) GlobalAlloc(GPTR, sizeof(SECURITY_ATTRIBUTES)); lpSA->nLength = sizeof(SECURITY_ATTRIBUTES); lpSA->lpSecurityDescriptor = lpSD; lpSA->bInheritHandle = TRUE; } if(CreateProcessA(NULL, (LPSTR) cmd_line, lpSA, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); } else { printf("ERROR: Unable to restart PyMOL process with new environment:\n"); system("set"); /* dump the environment. */ printf("CreateProcess failed, code %d: %s\n", GetLastError(), cmd_line); printf("PyMOL will now terminate.\n"); } if(lpSA != NULL) GlobalFree(lpSA); if(lpSD != NULL) GlobalFree(lpSD); _exit(0); } } } } #endif /* END PROPRIETARY CODE SEGMENT */ /* compatibility for old compile-time defines */ #ifdef _PYMOL_SETUP_PY21 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif #ifdef _PYMOL_SETUP_PY22 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif #ifdef _PYMOL_SETUP_PY23 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif #ifdef _PYMOL_SETUP_PY24 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif #ifdef _PYMOL_SETUP_PY25 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif #ifdef _PYMOL_SETUP_PY26 #ifndef _PYMOL_SETUP_PY_EXT #define _PYMOL_SETUP_PY_EXT #endif #endif /* should we set up PYTHONHOME in the ext directory? */ #ifdef _PYMOL_SETUP_PY_EXT { static char line1[8092]; static char line2[8092]; if(!getenv("PYMOL_PATH")) { /* if PYMOL_PATH isn't defined... */ /* was our startup path absolute? */ if((argc > 0) && (argv[0][0] == '/')) { /* PYMOL was started with an absolute path, so try using that... */ char *p; strcpy(line1, "PYMOL_PATH="); strcat(line1, argv[0]); p = line1 + strlen(line1); while(*p != '/') { *p = 0; p--; } *p = 0; putenv(line1); } else if((argc > 0) && getenv("PWD") && ((argv[0][0] == '.') || (strstr(argv[0], "/")))) { /* was the path relative? */ char *p; strcpy(line1, "PYMOL_PATH="); strcat(line1, getenv("PWD")); strcat(line1, "/"); strcat(line1, argv[0]); p = line1 + strlen(line1); while(*p != '/') { *p = 0; p--; } *p = 0; putenv(line1); } else { /* otherwise, just try using the current working directory */ if(getenv("PWD")) { strcpy(line1, "PYMOL_PATH="); strcat(line1, getenv("PWD")); putenv(line1); } } } /* now set PYTHONHOME so that we use the right binary libraries for this executable */ if(getenv("PYMOL_PATH")) { strcpy(line2, "PYTHONHOME="); strcat(line2, getenv("PYMOL_PATH")); strcat(line2, "/ext"); putenv(line2); } } #endif #ifndef _PYMOL_EMBEDDED Py_Initialize(); PyEval_InitThreads(); #endif init_cmd(); #ifdef _PYMOL_MONOLITHIC #ifndef _PYMOL_EMBEDDED /* * initExtensionClass(); * initsglite(); */ /* initialize champ */ init_champ(); #ifdef _PYMOL_PYOMM init_pyomm(); #endif /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 #ifdef _PYMOL_NUMPY_INIT /* initialize numeric python */ init_numpy(); initmultiarray(); initarrayfns(); initlapack_lite(); initumath(); initranlib(); #endif #endif /* END PROPRIETARY CODE SEGMENT */ #endif #endif PyRun_SimpleString("import os\n"); PyRun_SimpleString("import sys\n"); /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 { /* getenv('PYMOL_PATH') and os.environ['PYMOL_PATH'] aren't automatically synchronized on Windows, so here we do the job manually... */ char *pymol_path = getenv("PYMOL_PATH"); if(pymol_path) { PyObject *os = PyImport_AddModule("os"); /* borrowed ref */ char *buffer = pymol::malloc(strlen(pymol_path) + 100); if(os && buffer) { PyObject *envir = PyObject_GetAttrString(os, "environ"); if(envir) { if(!PTruthCallStr1s(envir, "__contains__", "PYMOL_PATH")) { sprintf(buffer, "os.environ['PYMOL_PATH']=r'''%s'''\n", pymol_path); PyRun_SimpleString(buffer); } } PXDecRef(envir); } FreeP(buffer); } } /* ultimate fallback -- try using the current working directory */ PyRun_SimpleString ("if 'PYMOL_PATH' not in os.environ: os.environ['PYMOL_PATH']=os.getcwd()\n"); #endif /* END PROPRIETARY CODE SEGMENT */ P_main = PyImport_AddModule("__main__"); if(!P_main) ErrFatal(G, "PyMOL", "can't find '__main__'"); args = PConvStringListToPyList(argc, argv); /* prepare our argument list */ if(!args) ErrFatal(G, "PyMOL", "can't process arguments."); /* copy arguments to __main__.pymol_argv */ PyObject_SetAttrString(P_main, "pymol_argv", args); PyRun_SimpleString ("import __main__\nif not hasattr(sys,'argv'): sys.argv=__main__.pymol_argv"); PyRun_SimpleString("if (os.environ['PYMOL_PATH']+'/modules') not in sys.path: sys.path.insert(0,os.environ['PYMOL_PATH']+'/modules')\n"); /* needed for semistatic pymol */ } void PConvertOptions(CPyMOLOptions * rec, PyObject * options) { const char *load_str; rec->pmgui = !PyInt_AsLong(PyObject_GetAttrString(options, "no_gui")); rec->internal_gui = PyInt_AsLong(PyObject_GetAttrString(options, "internal_gui")); rec->internal_feedback = PyInt_AsLong(PyObject_GetAttrString(options, "internal_feedback")); rec->show_splash = PyInt_AsLong(PyObject_GetAttrString(options, "show_splash")); rec->security = PyInt_AsLong(PyObject_GetAttrString(options, "security")); rec->game_mode = PyInt_AsLong(PyObject_GetAttrString(options, "game_mode")); rec->force_stereo = PyInt_AsLong(PyObject_GetAttrString(options, "force_stereo")); rec->winX = PyInt_AsLong(PyObject_GetAttrString(options, "win_x")); rec->winY = PyInt_AsLong(PyObject_GetAttrString(options, "win_y")); rec->winPX = PyInt_AsLong(PyObject_GetAttrString(options, "win_px")); rec->winPY = PyInt_AsLong(PyObject_GetAttrString(options, "win_py")); rec->blue_line = PyInt_AsLong(PyObject_GetAttrString(options, "blue_line")); rec->external_gui = PyInt_AsLong(PyObject_GetAttrString(options, "external_gui")); rec->siginthand = PyInt_AsLong(PyObject_GetAttrString(options, "sigint_handler")); rec->reuse_helper = PyInt_AsLong(PyObject_GetAttrString(options, "reuse_helper")); rec->auto_reinitialize = PyInt_AsLong(PyObject_GetAttrString(options, "auto_reinitialize")); rec->keep_thread_alive = PyInt_AsLong(PyObject_GetAttrString(options, "keep_thread_alive")); rec->quiet = PyInt_AsLong(PyObject_GetAttrString(options, "quiet")); #ifdef _PYMOL_IP_EXTRAS rec->incentive_product = true; PyObject_SetAttrString(options, "incentive_product", PyInt_FromLong(1)); #else rec->incentive_product = PyInt_AsLong(PyObject_GetAttrString(options, "incentive_product")); #endif rec->multisample = PyInt_AsLong(PyObject_GetAttrString(options, "multisample")); rec->window_visible = PyInt_AsLong(PyObject_GetAttrString(options, "window_visible")); rec->read_stdin = PyInt_AsLong(PyObject_GetAttrString(options, "read_stdin")); rec->presentation = PyInt_AsLong(PyObject_GetAttrString(options, "presentation")); rec->defer_builds_mode = PyInt_AsLong(PyObject_GetAttrString(options, "defer_builds_mode")); rec->full_screen = PyInt_AsLong(PyObject_GetAttrString(options, "full_screen")); load_str = PyString_AsString(PyObject_GetAttrString(options, "after_load_script")); rec->sphere_mode = PyInt_AsLong(PyObject_GetAttrString(options, "sphere_mode")); rec->stereo_capable = PyInt_AsLong(PyObject_GetAttrString(options, "stereo_capable")); rec->stereo_mode = PyInt_AsLong(PyObject_GetAttrString(options, "stereo_mode")); rec->zoom_mode = PyInt_AsLong(PyObject_GetAttrString(options, "zoom_mode")); rec->no_quit = PyInt_AsLong(PyObject_GetAttrString(options, "no_quit")); rec->launch_status = PyInt_AsLong(PyObject_GetAttrString(options, "launch_status")); rec->gldebug = PyInt_AsLong(PyObject_GetAttrString(options, "gldebug")); rec->openvr_stub = PyInt_AsLong(PyObject_GetAttrString(options, "openvr_stub")); if(load_str) { if(load_str[0]) { UtilNCopy(rec->after_load_script, load_str, PYMOL_MAX_OPT_STR); } } if(PyErr_Occurred()) { PyErr_Print(); } } void PGetOptions(CPyMOLOptions * rec) { PyObject *pymol, *invocation, *options; pymol = PImportModuleOrFatal("pymol"); invocation = PGetAttrOrFatal(pymol, "invocation"); /* get a handle to the invocation module */ options = PGetAttrOrFatal(invocation, "options"); PConvertOptions(rec, options); Py_XDECREF(invocation); Py_XDECREF(options); Py_XDECREF(pymol); } /** * Runs a string in the namespace of the pymol global module * @pre GIL */ void PRunStringModule(PyMOLGlobals * G, const char *str) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->exec, "Os", P_pymol, str)); } /** * Runs a string in the namespace of the pymol instance * @pre GIL */ void PRunStringInstance(PyMOLGlobals * G, const char *str) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->exec, "Os", G->P_inst->obj, str)); } void WrapperObjectReset(WrapperObject *wo){ if (wo->settingWrapperObject) { reinterpret_cast(wo->settingWrapperObject)->wobj = NULL; Py_DECREF(wo->settingWrapperObject); } #ifdef _PYMOL_IP_PROPERTIES if (wo->propertyWrapperObject) { reinterpret_cast(wo->propertyWrapperObject)->wobj = NULL; Py_DECREF(wo->propertyWrapperObject); } #endif Py_XDECREF(wo->dict); Py_DECREF(wo); } void PInit(PyMOLGlobals * G, int global_instance) { /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 #ifdef _PYMOL_MONOLITHIC #ifndef _PYMOL_EMBEDDED #define _PYMOL_INIT_MODULES #endif #endif #endif /* END PROPRIETARY CODE SEGMENT */ #ifdef _PYMOL_INIT_MODULES /* Win32 module build: includes pyopengl, numpy, and sglite */ /* sglite * initExtensionClass(); * initsglite(); */ init_champ(); #ifdef _PYMOL_PYOMM init_pyomm(); #endif /* initialize numeric python */ #ifdef _PYMOL_NUMPY_INIT init_numpy(); initmultiarray(); initarrayfns(); initlapack_lite(); initumath(); initranlib(); #endif /* initialize PyOpenGL */ #endif #if PY_MAJOR_VERSION < 3 // Support implicit utf-8 encoding (important for labeling!) // str(u"...unicode...") -> b"...utf-8..." // PyString_AsString(unicodeobj) -> "...utf-8..." PyUnicode_SetDefaultEncoding("utf-8"); #endif if(true /* global_instance */) { PCatchInit(); /* setup standard-output catch routine */ } P_pymol = PImportModuleOrFatal("pymol"); P_pymol_dict = PyModule_GetDict(P_pymol); Py_XINCREF(P_pymol_dict); if(!P_pymol_dict) ErrFatal(G, "PyMOL", "can't find globals for 'pymol'"); if(global_instance) { /* if global singleton PyMOL... */ G->P_inst = pymol::calloc(1); G->P_inst->obj = P_pymol; G->P_inst->dict = P_pymol_dict; { int a; SavedThreadRec *str = G->P_inst->savedThread; for(a = 0; a < MAX_SAVED_THREAD; a++) { (str++)->id = -1; } } } { G->P_inst->exec = PGetAttrOrFatal(P_pymol, "exec_str"); if(global_instance) { PCatch_install(NULL, NULL); } P_traceback = PImportModuleOrFatal("traceback"); P_cmd = PImportModuleOrFatal("pymol.cmd"); if(global_instance) { /* implies global singleton pymol, so set up the global handle */ PyObject_SetAttrString(P_cmd, "_COb", PyCObject_FromVoidPtr((void *) &SingletonPyMOLGlobals, NULL)); /* cmd module is itself the api for the global PyMOL instance */ G->P_inst->cmd = P_cmd; } /* right now, all locks are global -- eventually some of these may become instance-specific in order to improve concurrency */ G->P_inst->lock = PGetAttrOrFatal(G->P_inst->cmd, "lock"); G->P_inst->lock_attempt = PGetAttrOrFatal(G->P_inst->cmd, "lock_attempt"); G->P_inst->unlock = PGetAttrOrFatal(G->P_inst->cmd, "unlock"); G->P_inst->lock_api_status = PGetAttrOrFatal(G->P_inst->cmd, "lock_api_status"); G->P_inst->lock_api_glut = PGetAttrOrFatal(G->P_inst->cmd, "lock_api_glut"); /* 'do' command */ G->P_inst->cmd_do = PGetAttrOrFatal(G->P_inst->cmd, "do"); /* cache */ G->P_inst->cache = PyObject_GetAttrString(G->P_inst->obj, "_cache"); /* invariant stuff */ P_menu = PImportModuleOrFatal("pymol.menu"); P_setting = PImportModuleOrFatal("pymol.setting"); P_povray = PImportModuleOrFatal("pymol.povray"); #ifdef _PYMOL_XRAY P_xray = PImportModuleOrFatal("pymol.xray"); #endif /* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */ #ifdef WIN32 P_time = PImportModuleOrFatal("time"); P_sleep = PGetAttrOrFatal(P_time, "sleep"); #endif /* END PROPRIETARY CODE SEGMENT */ P_parser = PImportModuleOrFatal("pymol.parser"); { PyObject *fn_closure = PyObject_GetAttrString(P_parser, "new_parse_closure"); G->P_inst->parse = PYOBJECT_CALLFUNCTION(fn_closure, "O", G->P_inst->cmd); PXDecRef(fn_closure); if(!G->P_inst->parse) ErrFatal(G, "PyMOL", "can't create 'parse' function closure"); } { PyObject *fn_closure = PyObject_GetAttrString(P_parser, "new_complete_closure"); G->P_inst->complete = PYOBJECT_CALLFUNCTION(fn_closure, "O", G->P_inst->cmd); PXDecRef(fn_closure); if(!G->P_inst->complete) ErrFatal(G, "PyMOL", "can't create 'complete' function closure"); } { PyObject *fn_closure = PGetAttrOrFatal(P_pymol, "_colortype"); G->P_inst->colortype = PYOBJECT_CALLFUNCTION(fn_closure, "O", G->P_inst->cmd); PXDecRef(fn_closure); } P_chempy = PImportModuleOrFatal("chempy"); P_models = PImportModuleOrFatal("chempy.models"); P_CmdException = PGetAttrOrFatal(P_pymol, "CmdException"); P_QuietException = PGetAttrOrFatal(P_cmd, "QuietException"); /* backwards compatibility */ PRunStringModule(G, "glutThread = thread.get_ident()"); P_glut_thread_id = PyThread_get_thread_ident(); #ifndef WIN32 if(G->Option->siginthand) { signal(SIGINT, my_interrupt); } #endif if (!Wrapper_Type.tp_basicsize) { Wrapper_Type.tp_basicsize = sizeof(WrapperObject); Wrapper_Type.tp_flags = Py_TPFLAGS_DEFAULT; wrapperMappingMethods.mp_length = NULL; wrapperMappingMethods.mp_subscript = &WrapperObjectSubScript; wrapperMappingMethods.mp_ass_subscript = &WrapperObjectAssignSubScript; Wrapper_Type.tp_as_mapping = &wrapperMappingMethods; settingWrapper_Type.tp_basicsize = sizeof(SettingPropertyWrapperObject); settingWrapper_Type.tp_flags = Py_TPFLAGS_DEFAULT; settingWrapper_Type.tp_iter = &SettingWrapperObjectIter; settingMappingMethods.mp_length = NULL; settingMappingMethods.mp_subscript = &SettingWrapperObjectSubScript; settingMappingMethods.mp_ass_subscript = &SettingWrapperObjectAssignSubScript; settingWrapper_Type.tp_as_mapping = &settingMappingMethods; settingWrapper_Type.tp_getattro = PyObject_GenericGetAttrOrItem; settingWrapper_Type.tp_setattro = PyObject_GenericSetAttrAsItem; if (PyType_Ready(&Wrapper_Type) < 0 || PyType_Ready(&settingWrapper_Type) < 0 ){ PRINTFB(G, FB_Python, FB_Errors) " PInit: Wrapper_Type, settingWrapper_Type, propertyWrapper_Type not ready\n" ENDFB(G); return; } Py_INCREF(&Wrapper_Type); Py_INCREF(&settingWrapper_Type); } } #ifdef _PYMOL_NO_MSGPACKC // fallback MMTF support PyRun_SimpleString( "import pymol.importing;" "pymol.importing.loadfunctions.setdefault('mmtf'," "pymol.importing.load_mmtf)"); #endif } int PPovrayRender(PyMOLGlobals * G, const char *header, const char *inp, const char *file, int width, int height, int antialias) { PyObject *result; int ok; PBlock(G); result = PYOBJECT_CALLMETHOD(P_povray, "render_from_string", "sssiii", header, inp, file, width, height, antialias); ok = PyObject_IsTrue(result); Py_DECREF(result); PUnblock(G); return (ok); } void PFree(PyMOLGlobals * G) { PXDecRef(G->P_inst->parse); PXDecRef(G->P_inst->complete); PXDecRef(G->P_inst->colortype); } /** * Terminates the application with a call to `exit(code)`. */ void PExit(PyMOLGlobals * G, int code) { ExecutiveDelete(G, "all"); PBlock(G); PyMOL_PushValidContext(G->PyMOL); PyMOL_Stop(G->PyMOL); PyMOL_PopValidContext(G->PyMOL); #ifndef _PYMOL_NO_MAIN if(G->Main) { MainFree(); } #endif PyMOL_Free(G->PyMOL); #if 1 /* we're having trouble with threading errors after calling Py_Exit, so for the time being, let's just take the process down at this point, instead of allowing PyExit to be called. */ exit(code); #else Py_Exit(code); #endif } /** * Add `str` to the command queue */ void PParse(PyMOLGlobals * G, const char *str) { OrthoCommandIn(G, str); } /** * Call `cmd.do(str)` * * Effectively calls `PParse(str.splitlines())` with logging and echo. */ void PDo(PyMOLGlobals * G, const char *str) { /* assumes we already hold the re-entrant API lock */ int blocked; PyObject *ret ; blocked = PAutoBlock(G); ret = PYOBJECT_CALLFUNCTION(G->P_inst->cmd_do, "s", str); Py_XDECREF(ret); PAutoUnblock(G, blocked); } /* * Write `str` to the log file (if one is open). * * str: command or expression to log * format: cPLog_pml (`str` is PyMOL command) * cPLog_pym (`str` is Python expression) * cPLog_no_flush (write `str` as is) * cPLog_pml_lf (unused TODO remove?) * * See also equivalent Python impelemtation: cmd.log() */ void PLog(PyMOLGlobals * G, const char *str, int format) { int mode; int a = sizeof(OrthoLineType) - 15; int blocked; PyObject *log; OrthoLineType buffer = ""; mode = SettingGetGlobal_i(G, cSetting_logging); if(mode) { blocked = PAutoBlock(G); log = PyDict_GetItemString(P_pymol_dict, P_log_file_str); if(log && (log != Py_None)) { if(format == cPLog_no_flush) { PYOBJECT_CALLMETHOD(log, "write", "s", str); /* maximize responsiveness (for real-time) */ } else { switch (mode) { case cPLog_pml: /* .pml file */ switch (format) { case cPLog_pml_lf: strcpy(buffer, str); break; case cPLog_pml: case cPLog_pym: strcpy(buffer, str); strcat(buffer, "\n"); break; } break; case cPLog_pym: /* .pym file */ if((str[0] == '_') && (str[1]) == ' ') str += 2; switch (format) { case cPLog_pml_lf: a = strlen(str); while(a && str[a - 1] < 32) a--; /* trim CR/LF etc. */ case cPLog_pml: if (str[0] == '/') { strncat(buffer, str + 1, a - 1); strcat(buffer, "\n"); } else { strcpy(buffer, "cmd.do('''"); char * b = buffer + strlen(buffer); for (; a && *str; --a) { if (*str == '\\' || *str == '\'') { *(b++) = '\\'; } *(b++) = *(str++); } strcpy(b, "''')\n"); } break; case cPLog_pym: strcpy(buffer, str); strcat(buffer, "\n"); break; } } PYOBJECT_CALLMETHOD(log, "write", "s", buffer); PYOBJECT_CALLMETHOD(log, "flush", ""); } } PAutoUnblock(G, blocked); } } /** * Call flush() on the open log file handle */ void PLogFlush(PyMOLGlobals * G) { int mode; PyObject *log; int blocked; mode = SettingGetGlobal_i(G, cSetting_logging); if(mode) { blocked = PAutoBlock(G); log = PyDict_GetItemString(P_pymol_dict, P_log_file_str); if(log && (log != Py_None)) { PYOBJECT_CALLMETHOD(log, "flush", ""); } PAutoUnblock(G, blocked); } } #define PFLUSH_REPORT_UNCAUGHT_EXCEPTIONS(G) \ if (PyErr_Occurred()) { \ PyErr_Print(); \ PRINTFB(G, FB_Python, FB_Errors) \ " %s: Uncaught exception. PyMOL may have a bug.\n", __func__ ENDFB(G); \ } /** * Execute commands from the command queue. * If the current thread holds the GIL, do nothing. Otherwise the queue will * be empty after this call. * @return False if the queue was empty */ int PFlush(PyMOLGlobals * G) { /* NOTE: ASSUMES unblocked Python threads and a locked API */ if(!OrthoCommandWaiting(G)) return false; if (PAutoBlock(G)) { if(!(PIsGlutThread() && G->P_inst->glut_thread_keep_out)) { /* don't run if we're currently banned */ auto ortho = G->Ortho; while(!OrthoCommandIsEmpty(*ortho)){ auto buffer = OrthoCommandOut(*ortho); OrthoCommandSetBusy(G, true); OrthoCommandNest(G, 1); // TODO if we release here, another thread might acquire the API lock // and block us when we try to lock again. (see PYMOL-3249) // Example: set_key F1, ray async=1 // => hitting F1 will block GUI updates, even though ray // is running async // PUnlockAPIWhileBlocked(G); PFLUSH_REPORT_UNCAUGHT_EXCEPTIONS(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->parse, "si", buffer.c_str(), 0)); PFLUSH_REPORT_UNCAUGHT_EXCEPTIONS(G); // TODO see above // PLockAPIWhileBlocked(G); OrthoCommandSetBusy(G, false); /* make sure no commands left at this level */ while(OrthoCommandWaiting(G)) PFlushFast(G); OrthoCommandNest(G, -1); } } PUnblock(G); } return true; } /** * Execute commands from the command queue. * @return False if the queue was empty * @pre GIL * @post queue is empty */ int PFlushFast(PyMOLGlobals * G) { /* NOTE: ASSUMES we currently have blocked Python threads and an unlocked API */ int did_work = false; auto ortho = G->Ortho; while(!OrthoCommandIsEmpty(*ortho)){ auto buffer = OrthoCommandOut(*ortho); OrthoCommandSetBusy(G, true); OrthoCommandNest(G, 1); did_work = true; PFLUSH_REPORT_UNCAUGHT_EXCEPTIONS(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->parse, "si", buffer.c_str(), 0)); PFLUSH_REPORT_UNCAUGHT_EXCEPTIONS(G); OrthoCommandSetBusy(G, false); /* make sure no commands left at this level */ while(OrthoCommandWaiting(G)) PFlushFast(G); OrthoCommandNest(G, -1); } return did_work; } void PBlockLegacy() { PBlock(SingletonPyMOLGlobals); } void PUnblockLegacy() { PUnblock(SingletonPyMOLGlobals); } /** * Acquire the GIL. * @pre no GIL * @post GIL */ void PBlock(PyMOLGlobals * G) { if(!PAutoBlock(G)) { ErrFatal(G, "PBlock", "Threading error detected. Terminating..."); } } /** * Acquire the GIL. * Return false if the current thread already holds the GIL. */ int PAutoBlock(PyMOLGlobals * G) { #ifndef _PYMOL_EMBEDDED SavedThreadRec *SavedThread = G->P_inst->savedThread; auto id = PyThread_get_thread_ident(); for (auto a = MAX_SAVED_THREAD - 1; a; --a) { if (SavedThread[a].id == id) { // Aquire GIL PyEval_RestoreThread(SavedThread[a].state); SavedThread[a].id = -1; return 1; } } return 0; #else return 1; #endif } /** * Can be called anytime, no lock conditions * @return True if the current thread is the GUI thread */ int PIsGlutThread(void) { return (PyThread_get_thread_ident() == P_glut_thread_id); } /** * Release GIL * @pre GIL * @post no GIL */ void PUnblock(PyMOLGlobals * G) { #ifndef _PYMOL_EMBEDDED SavedThreadRec *SavedThread = G->P_inst->savedThread; auto a = MAX_SAVED_THREAD - 1; /* NOTE: ASSUMES a locked API */ /* reserve a space while we have a lock */ for (; a; --a) { if (SavedThread[a].id == -1) { SavedThread[a].id = PyThread_get_thread_ident(); break; } } // Release GIL SavedThread[a].state = PyEval_SaveThread(); #endif } /** * Release GIL if flag=true */ void PAutoUnblock(PyMOLGlobals * G, int flag) { if(flag) PUnblock(G); } /** * Acquire GIL, release API lock and flush * @pre no GIL * @post GIL * @post no API lock */ void PBlockAndUnlockAPI(PyMOLGlobals * G) { PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", 0, G->P_inst->cmd)); } /** * Acquire API lock * @param block_if_busy If false and PyMOL is busy, do not block * @return False if API lock could not be acquired * @pre no GIL */ int PLockAPI(PyMOLGlobals * G, int block_if_busy) { int result = true; PBlock(G); if(block_if_busy) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->lock, "O", G->P_inst->cmd)); } else { /* not blocking if PyMOL is busy */ PyObject *got_lock = PYOBJECT_CALLFUNCTION(G->P_inst->lock_attempt, "O", G->P_inst->cmd); if(got_lock) { result = PyInt_AsLong(got_lock); Py_DECREF(got_lock); } } PUnblock(G); return result; } /** * Release API lock with flushing * @pre no GIL * @pre API lock */ void PUnlockAPI(PyMOLGlobals * G) { PBlock(G); PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", 0, G->P_inst->cmd)); PUnblock(G); } /** * Release API lock without flushing * @pre GIL * @pre API lock * @post no API lock */ static void PUnlockAPIWhileBlocked(PyMOLGlobals * G) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->unlock, "iO", -1, G->P_inst->cmd)); } /** * Acquire API lock * @pre GIL * @post GIL * @post API lock */ static void PLockAPIWhileBlocked(PyMOLGlobals * G) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->lock, "O", G->P_inst->cmd)); } /** * Try to acquire API lock (non-blocking if busy) and release GIL * @return False if API lock could not be acquired * @pre GIL * @post no GIL if API lock could be acquired */ int PTryLockAPIAndUnblock(PyMOLGlobals * G) { int result = get_api_lock(G, false); if(result) { PUnblock(G); } return result; } /** * Acquire API lock and release the GIL * @pre GIL * @post no GIL * @post API Lock */ void PLockAPIAndUnblock(PyMOLGlobals * G) { PXDecRef(PYOBJECT_CALLFUNCTION(G->P_inst->lock, "O", G->P_inst->cmd)); PUnblock(G); } /** * Assign a variable in the pymol namespace, like * @verbatim import pymol setattr(pymol, name, value) @endverbatim * @pre no GIL */ void PDefineFloat(PyMOLGlobals * G, const char *name, float value) { char buffer[OrthoLineLength]; sprintf(buffer, "%s = %f\n", name, value); PBlock(G); PRunStringModule(G, buffer); PUnblock(G); } /* A static module */ static PyObject *PCatchWrite(PyObject * self, PyObject * args) { char *str; PyArg_ParseTuple(args, "s", &str); if(str[0]) { if(SingletonPyMOLGlobals) { if(Feedback(SingletonPyMOLGlobals, FB_Python, FB_Output)) { OrthoAddOutput(SingletonPyMOLGlobals, str); } } } return PConvAutoNone(Py_None); } static PyObject *PCatchWritelines(PyObject * self, PyObject * args) { PyObject *seq; int len; PyArg_ParseTuple(args, "O", &seq); if(seq && PySequence_Check(seq)) { if((len = PySequence_Size(seq)) > 0) { int i; for(i = 0; i < len; i++) { PyObject *obj = PySequence_GetItem(seq, i); if(obj && PyString_Check(obj)) { const char *str = PyString_AsString(obj); if(SingletonPyMOLGlobals) { if(Feedback(SingletonPyMOLGlobals, FB_Python, FB_Output)) { OrthoAddOutput(SingletonPyMOLGlobals, str); } } } Py_XDECREF(obj); } } } return PConvAutoNone(Py_None); } static PyObject *PCatchFlush(PyObject * self, PyObject * args) { fflush(stdout); fflush(stderr); return PConvAutoNone(Py_None); } static PyObject *PCatchIsAtty(PyObject * self, PyObject * args) { Py_RETURN_FALSE; } static PyObject *PCatch_install(PyObject * self, PyObject * args) { PyRun_SimpleString( "import sys, pcatch\n" "if sys.stdout is not pcatch:" "pcatch.closed = False;" "pcatch.encoding = 'UTF-8';" "sys.stderr = sys.stdout = pcatch"); return PConvAutoNone(Py_None); } static PyMethodDef PCatch_methods[] = { {"writelines", PCatchWritelines, METH_VARARGS}, {"write", PCatchWrite, METH_VARARGS}, {"flush", PCatchFlush, METH_VARARGS}, {"isatty", PCatchIsAtty, METH_VARARGS}, // called by pip.main(["install", "..."]) {"_install", PCatch_install, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; void PCatchInit(void) { #if PY_MAJOR_VERSION < 3 PyImport_AddModule("pcatch"); Py_InitModule("pcatch", PCatch_methods); #else static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "pcatch", NULL, -1, PCatch_methods }; PyObject * pcatch = PyModule_Create(&moduledef); if (pcatch) { PyDict_SetItemString(PyImport_GetModuleDict(), "pcatch", pcatch); Py_DECREF(pcatch); } #endif } #endif