1
/* This file is part of Clementine.
2
Copyright 2010, David Sansome <me@davidsansome.com>
4
Clementine is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
9
Clementine is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
19
#include <frameobject.h>
22
#include "pythonengine.h"
23
#include "pythonscript.h"
24
#include "sipAPIclementine.h"
25
#include "library/library.h"
30
const char* PythonEngine::kModulePrefix = "clementinescripts";
31
PythonEngine* PythonEngine::sInstance = NULL;
34
void initclementine();
45
PythonEngine::PythonEngine(ScriptManager* manager)
46
: LanguageEngine(manager),
49
Q_ASSERT(sInstance == NULL);
52
setenv("PYTHONPATH", (QCoreApplication::applicationDirPath() + "/../PlugIns").toLocal8Bit().constData(), 1);
56
PythonEngine::~PythonEngine() {
64
const sipAPIDef* PythonEngine::GetSIPApi() {
65
#if defined(SIP_USE_PYCAPSULE)
66
return (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0);
69
PyObject *sip_module_dict;
72
/* Import the SIP module. */
73
sip_module = PyImport_ImportModule("sip");
75
if (sip_module == NULL)
78
/* Get the module's dictionary. */
79
sip_module_dict = PyModule_GetDict(sip_module);
81
/* Get the "_C_API" attribute. */
82
c_api = PyDict_GetItemString(sip_module_dict, "_C_API");
87
/* Sanity check that it is the right type. */
88
if (!PyCObject_Check(c_api))
91
/* Get the actual pointer from the object. */
92
return (const sipAPIDef *)PyCObject_AsVoidPtr(c_api);
96
bool PythonEngine::EnsureInitialised() {
100
AddLogLine("Initialising python...", false);
103
// On Windows we statically link against SIP and PyQt, so add those modules
104
// to Python's inittab here.
105
PyImport_AppendInittab(const_cast<char*>("sip"), initsip);
106
PyImport_AppendInittab(const_cast<char*>("PyQt4.Qt"), initQt);
107
PyImport_AppendInittab(const_cast<char*>("PyQt4.QtCore"), initQtCore);
108
PyImport_AppendInittab(const_cast<char*>("PyQt4.QtGui"), initQtGui);
109
PyImport_AppendInittab(const_cast<char*>("PyQt4.QtNetwork"), initQtNetwork);
112
// Add the Clementine builtin module
113
PyImport_AppendInittab(const_cast<char*>("clementine"), initclementine);
116
Py_SetProgramName(const_cast<char*>("clementine"));
117
PyEval_InitThreads();
120
// Get the clementine module so we can put stuff in it
121
clementine_module_ = PyImport_ImportModule("clementine");
122
if (!clementine_module_) {
123
AddLogLine("Failed to import the clementine module", true);
124
if (PyErr_Occurred()) {
130
sip_api_ = GetSIPApi();
132
// Add objects to the module
133
if (manager()->data().valid_) {
134
AddObject(manager()->data().library_->backend(), sipType_LibraryBackend, "library");
135
AddObject(manager()->data().library_view_, sipType_LibraryView, "library_view");
136
AddObject(manager()->data().player_, sipType_PlayerInterface, "player");
137
AddObject(manager()->data().playlists_, sipType_PlaylistManagerInterface, "playlists");
138
AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model");
139
AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog");
140
AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager");
143
AddObject(manager()->ui(), sipType_UIInterface, "ui");
144
AddObject(this, sipType_PythonEngine, "pythonengine");
146
// Create a module for scripts
147
PyImport_AddModule(kModulePrefix);
149
// Run the startup script - this redirects sys.stdout and sys.stderr to our
151
QFile python_startup(":pythonstartup.py");
152
python_startup.open(QIODevice::ReadOnly);
153
QByteArray python_startup_script = python_startup.readAll();
155
if (PyRun_SimpleString(python_startup_script.constData()) != 0) {
156
AddLogLine("Could not execute startup code", true);
161
PyEval_ReleaseLock();
167
Script* PythonEngine::CreateScript(const ScriptInfo& info) {
168
// Initialise Python if it hasn't been done yet
169
if (!EnsureInitialised()) {
173
Script* ret = new PythonScript(this, info);
174
loaded_scripts_[ret->info().id()] = ret; // Used by RegisterNativeObject during startup
183
void PythonEngine::DestroyScript(Script* script) {
185
loaded_scripts_.remove(script->info().id());
189
void PythonEngine::AddObject(void* object, const _sipTypeDef* type,
190
const char * name) const {
191
PyObject* python_object = sip_api_->api_convert_from_type(object, type, NULL);
192
PyModule_AddObject(clementine_module_, name, python_object);
195
void PythonEngine::AddLogLine(const QString& message, bool error) {
196
manager()->AddLogLine("Python", message, error);
199
Script* PythonEngine::FindScriptMatchingId(const QString& id) const {
200
foreach (const QString& script_id, loaded_scripts_.keys()) {
201
if (script_id == id || id.startsWith(script_id + ".")) {
202
return loaded_scripts_[script_id];
208
void PythonEngine::RegisterNativeObject(QObject* object) {
209
// This function is called from Python, we need to figure out which script
210
// called it, so we look at the __package__ variable in the bottom stack
213
PyFrameObject* frame = PyEval_GetFrame();
215
qWarning() << __PRETTY_FUNCTION__ << "unable to get stack frame";
218
while (frame->f_back) {
219
frame = frame->f_back;
222
PyObject* __package__ = PyMapping_GetItemString(
223
frame->f_globals, const_cast<char*>("__package__"));
225
qWarning() << __PRETTY_FUNCTION__ << "unable to get __package__";
229
QString package = PyString_AsString(__package__);
230
Py_DECREF(__package__);
231
package.remove(QString(kModulePrefix) + ".");
233
Script* script = FindScriptMatchingId(package);
235
qWarning() << __PRETTY_FUNCTION__ << "unable to find script for package" << package;
239
// Finally got the script - tell it about this object so it will get destroyed
240
// when the script is unloaded.
241
script->AddNativeObject(object);
243
// Save the script as a property on the object so we can remove it later
244
object->setProperty("owning_python_script", QVariant::fromValue(script));
245
connect(object, SIGNAL(destroyed(QObject*)), SLOT(NativeObjectDestroyed(QObject*)));
248
void PythonEngine::NativeObjectDestroyed(QObject* object) {
249
Script* script = object->property("owning_python_script").value<Script*>();
250
if (!script || !loaded_scripts_.values().contains(script))
253
script->RemoveNativeObject(object);