1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3 // Free Software Foundation, Inc
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 // Written by Koos Vriezen <koos ! vriezen ? xs4all ! nl>
20
21 #ifdef KDE_USE_FINAL
22 #undef Always
23 #endif
24
25 #include "gnashconfig.h"
26 #include <cassert>
27 #include <qcstring.h>
28 #include <qpopupmenu.h>
29 #include <qtimer.h>
30
31 class KXMLGUIClient; // workaround for kde3.3 on sarge with gcc4, kactioncollection.h does not forward declare KXMLGUIClient
32 #include <klibloader.h>
33 #include <kdebug.h>
34 #include <kconfig.h>
35 #include <ksimpleconfig.h>
36 #include <kaction.h>
37 #include <kapplication.h>
38 #include <klocale.h>
39 #include <kinstance.h>
40 #include <kparts/factory.h>
41 #include <kstaticdeleter.h>
42
43 #include "klash_part.h"
44
45 //-----------------------------------------------------------------------------
46
47 class KlashFactory : public KParts::Factory {
48 public:
49 KDE_NO_CDTOR_EXPORT KlashFactory ();
50 KDE_NO_CDTOR_EXPORT virtual ~KlashFactory ();
51 KDE_NO_CDTOR_EXPORT virtual KParts::Part *createPartObject
52 (QWidget *wparent, const char *wname,
53 QObject *parent, const char *name,
54 const char *className, const QStringList &args);
instance()55 static KInstance * instance () { return s_instance; }
56 private:
57 static KInstance * s_instance;
58 };
59
60 K_EXPORT_COMPONENT_FACTORY (libklashpart, KlashFactory)
61
62 KInstance *KlashFactory::s_instance = 0;
63
KlashFactory()64 KlashFactory::KlashFactory () {
65 s_instance = new KInstance ("klash");
66 }
67
~KlashFactory()68 KlashFactory::~KlashFactory () {
69 delete s_instance;
70 }
71
createPartObject(QWidget * wparent,const char * wname,QObject * parent,const char * name,const char *,const QStringList & args)72 KParts::Part *KlashFactory::createPartObject
73 (QWidget *wparent, const char *wname,
74 QObject *parent, const char * name,
75 const char * /*cls*/, const QStringList & args) {
76 //kdDebug() << "KlashFactory::createPartObject " << cls << endl;
77 return new KlashPart (wparent, wname, parent, name, args);
78 }
79
80 //-----------------------------------------------------------------------------
81
getBoolValue(const QString & value)82 static bool getBoolValue (const QString & value) {
83 return (value.lower() != QString::fromLatin1("false") &&
84 value.lower() != QString::fromLatin1("off") &&
85 value.lower() != QString::fromLatin1("0"));
86 }
87
KlashPart(QWidget * wparent,const char * wname,QObject * parent,const char * name,const QStringList & args)88 KDE_NO_CDTOR_EXPORT KlashPart::KlashPart (QWidget * wparent, const char *wname,
89 QObject * parent, const char *name, const QStringList &args)
90 : KParts::ReadOnlyPart (parent, name),
91 //new KSimpleConfig ("klashrc")),
92 m_browserextension (new KlashBrowserExtension (this)),
93 m_liveconnectextension (new KlashLiveConnectExtension (this)),
94 m_process (0L),
95 m_width (0),
96 m_height (0),
97 m_autostart (false),
98 m_fullscreen (false),
99 m_started_emited (false) {
100 //kdDebug () << "KlashPart(" << this << ")::KlashPart ()" << endl;
101 setInstance (KlashFactory::instance (), true);
102 /*KAction *playact =*/ new KAction(i18n("P&lay"), QString ("player_play"), KShortcut (), this, SLOT(play ()), actionCollection (), "play");
103 /*KAction *pauseact =*/ new KAction(i18n("&Pause"), QString ("player_pause"), KShortcut (), this, SLOT(pause ()), actionCollection (), "pause");
104 /*KAction *stopact =*/ new KAction(i18n("&Stop"), QString ("player_stop"), KShortcut (), this, SLOT(stop ()), actionCollection (), "stop");
105 //new KAction (i18n ("Increase Volume"), QString ("player_volume"), KShortcut (), this, SLOT (increaseVolume ()), actionCollection (), "edit_volume_up");
106 //new KAction (i18n ("Decrease Volume"), QString ("player_volume"), KShortcut (), this, SLOT (decreaseVolume ()), actionCollection (), "edit_volume_down");
107 QStringList::const_iterator end = args.end ();
108 for (QStringList::const_iterator it = args.begin () ; it != end; ++it) {
109 int equalPos = (*it).find("=");
110 if (equalPos > 0) {
111 QString name = (*it).left (equalPos).lower ();
112 QString value = (*it).right ((*it).length () - equalPos - 1);
113 if (value.at(0)=='\"')
114 value = value.right (value.length () - 1);
115 if (value.at (value.length () - 1) == '\"')
116 value.truncate (value.length () - 1);
117 //kdDebug () << "name=" << name << " value=" << value << endl;
118 if (name == QString::fromLatin1("width")) {
119 m_width = value.toInt ();
120 } else if (name == QString::fromLatin1("height")) {
121 m_height = value.toInt ();
122 //} else if (name == QString::fromLatin1("type")) {
123 } else if (name == QString::fromLatin1("__khtml__pluginbaseurl")) {
124 m_docbase = KURL (value);
125 } else if (name == QString::fromLatin1("src")) {
126 m_src_url = value;
127 } else if (name == QString::fromLatin1 ("fullscreenmode")) {
128 m_fullscreen = getBoolValue (value);
129 } else if (name == QString::fromLatin1 ("autostart")) {
130 bool ok;
131 m_autostart = value.toInt (&ok);
132 if (!ok)
133 m_autostart = (value.lower () == "false");
134 }
135 m_args.push_back(name + QChar('=') + value);
136 }
137 }
138 KParts::Part::setWidget (new KlashView (wparent));
139 setXMLFile("klashpartui.rc");
140 setProgressInfoEnabled (false);
141
142 if (m_fullscreen)
143 fullScreen ();
144 }
145
~KlashPart()146 KDE_NO_CDTOR_EXPORT KlashPart::~KlashPart () {
147 kdDebug() << "KlashPart::~KlashPart" << endl;
148 stop ();
149 //delete m_config;
150 //m_config = 0L;
151 }
152
allowRedir(const KURL & url) const153 KDE_NO_EXPORT bool KlashPart::allowRedir (const KURL & url) const {
154 return kapp->authorizeURLAction ("redirect", m_docbase, url);
155 }
156
play()157 KDE_NO_EXPORT void KlashPart::play ()
158 {
159
160 QString procname;
161 char *gnash_env = getenv("KLASH_PLAYER");
162 if (!gnash_env) {
163 procname = GNASHBINDIR "/kde3-gnash";
164 } else {
165 procname = gnash_env;
166 }
167
168 stop ();
169 if (m_src_url.isEmpty ())
170 return;
171 m_process = new KProcess;
172 m_process->setUseShell (true);
173 m_process->setEnvironment (QString::fromLatin1 ("SESSION_MANAGER"), QString::fromLatin1 (""));
174
175 QString cmd = procname + QString(" -x ") +
176 QString::number(static_cast<KlashView*>(widget())->embedId());
177
178 if (m_width > 0 && m_height > 0)
179 cmd += QString(" -j ") + QString::number(m_width) +
180 QString(" -k ") + QString::number(m_height);
181
182 QString url = m_url.url();
183 if (!url.isEmpty())
184 cmd += QString(" -u ") + KProcess::quote(url);
185 url = m_docbase.url();
186 if (!url.isEmpty())
187 cmd += QString(" -U ") + KProcess::quote(url);
188
189 for (QStringList::const_iterator it=m_args.begin(), end=m_args.end();it != end; ++it)
190 cmd += QString(" -P ") + KProcess::quote(*it);
191
192 cmd += QString (" ") + KProcess::quote(m_src_url);
193
194 kdDebug () << cmd << endl;
195 *m_process << cmd;
196
197 connect (m_process, SIGNAL (processExited (KProcess *)),
198 this, SLOT (processStopped (KProcess *)));
199 m_process->start (KProcess::NotifyOnExit, KProcess::NoCommunication);
200 }
201
pause()202 KDE_NO_EXPORT void KlashPart::pause () {
203 }
204
stop()205 KDE_NO_EXPORT void KlashPart::stop () {
206 if (m_process) {
207 if (m_process->isRunning ()) {
208 ; // IPC close
209 //m_process->wait(2);
210
211 // Ignore SIGTERM, so we won't kill ourselves.
212 void (*oldhandler)(int) = signal(SIGTERM, SIG_IGN);
213
214 int pid = -1 * ::getpid();
215 assert(pid < -1);
216
217 // Terminate every process in our process group.
218 ::kill (pid, SIGTERM);
219
220 // Restore the old handler.
221 signal(SIGTERM, oldhandler);
222 m_process->wait(2);
223 }
224 delete m_process;
225 m_process = 0L;
226 }
227 }
228
openFile()229 bool KlashPart::openFile() {
230 if (!m_file.isEmpty ())
231 m_src_url = m_file;
232 play ();
233 return true;
234 }
235
openURL(const KURL & url)236 KDE_NO_EXPORT bool KlashPart::openURL (const KURL & url) {
237 kdDebug () << "KlashPart::openURL " << url.url() << endl;
238 emit started (0);
239 return KParts::ReadOnlyPart::openURL (url);
240 }
241
closeURL()242 KDE_NO_EXPORT bool KlashPart::closeURL () {
243 return KParts::ReadOnlyPart::closeURL ();
244 }
245
fullScreen()246 KDE_NO_EXPORT void KlashPart::fullScreen () {
247 }
248
setLoaded(int percentage)249 KDE_NO_EXPORT void KlashPart::setLoaded (int percentage) {
250 if (percentage < 100) {
251 m_browserextension->setLoadingProgress (percentage);
252 m_browserextension->infoMessage
253 (QString::number (percentage) + i18n ("% Cache fill"));
254 }
255 }
256
playingStarted()257 KDE_NO_EXPORT void KlashPart::playingStarted () {
258 //kdDebug () << "KlashPart::processStartedPlaying " << endl;
259 //if (m_settings->sizeratio && !m_noresize && m_source->width() > 0 && m_source->height() > 0)
260 // m_liveconnectextension->setSize (m_source->width(), m_source->height());
261 m_browserextension->setLoadingProgress (100);
262 if (m_started_emited) {
263 emit completed ();
264 m_started_emited = false;
265 }
266 m_liveconnectextension->started ();
267 m_browserextension->infoMessage (i18n("Klash: Playing"));
268 }
269
playingStopped()270 KDE_NO_EXPORT void KlashPart::playingStopped () {
271 if (m_started_emited) {
272 m_started_emited = false;
273 m_browserextension->setLoadingProgress (100);
274 emit completed ();
275 }
276 m_liveconnectextension->finished ();
277 m_browserextension->infoMessage (i18n ("Klash: Stop Playing"));
278 }
279
processStopped(KProcess *)280 KDE_NO_EXPORT void KlashPart::processStopped (KProcess *) {
281 QTimer::singleShot (0, this, SLOT (playingStopped ()));
282 }
283
284 //---------------------------------------------------------------------
285
KlashBrowserExtension(KlashPart * parent)286 KDE_NO_CDTOR_EXPORT KlashBrowserExtension::KlashBrowserExtension (KlashPart * parent)
287 : KParts::BrowserExtension (parent, "Klash Browser Extension") {
288 }
289
urlChanged(const QString & url)290 KDE_NO_EXPORT void KlashBrowserExtension::urlChanged (const QString & url) {
291 emit setLocationBarURL (url);
292 }
293
setLoadingProgress(int percentage)294 KDE_NO_EXPORT void KlashBrowserExtension::setLoadingProgress (int percentage) {
295 emit loadingProgress (percentage);
296 }
297
saveState(QDataStream & stream)298 KDE_NO_EXPORT void KlashBrowserExtension::saveState (QDataStream & stream) {
299 stream << static_cast <KlashPart *> (parent ())->source ();
300 }
301
restoreState(QDataStream & stream)302 KDE_NO_EXPORT void KlashBrowserExtension::restoreState (QDataStream & stream) {
303 QString url;
304 stream >> url;
305 static_cast <KlashPart *> (parent ())->openURL (KURL(url));
306 }
307
requestOpenURL(const KURL & url,const QString & target,const QString & service)308 KDE_NO_EXPORT void KlashBrowserExtension::requestOpenURL (const KURL & url, const QString & target, const QString & service) {
309 KParts::URLArgs args;
310 args.frameName = target;
311 args.serviceType = service;
312 emit openURLRequest (url, args);
313 }
314
315 //---------------------------------------------------------------------
316 /*
317 * add
318 * .error.errorCount
319 * .error.item(count)
320 * .errorDescription
321 * .errorCode
322 * .controls.stop()
323 * .controls.play()
324 */
325
326 enum JSCommand {
327 notsupported,
328 isfullscreen, isloop,
329 length, width, height, position, prop_source, source, setsource,
330 play, jsc_pause, start, stop,
331 prop_volume, volume, setvolume
332 };
333
334 struct KLASH_NO_EXPORT JSCommandEntry {
335 const char * name;
336 JSCommand command;
337 const char * defaultvalue;
338 const KParts::LiveConnectExtension::Type rettype;
339 };
340
341 // keep this list in alphabetic order
342 // http://service.real.com/help/library/guides/realonescripting/browse/htmfiles/embedmet.htm
343 static const JSCommandEntry JSCommandList [] = {
344 { "GetSource", source, 0L, KParts::LiveConnectExtension::TypeString },
345 { "GetTitle", notsupported, "title", KParts::LiveConnectExtension::TypeString },
346 { "GetVolume", volume, "100", KParts::LiveConnectExtension::TypeNumber },
347 { "Pause", jsc_pause, 0L, KParts::LiveConnectExtension::TypeBool },
348 { "Play", play, 0L, KParts::LiveConnectExtension::TypeBool },
349 { "SetSource", setsource, 0L, KParts::LiveConnectExtension::TypeBool },
350 { "SetVolume", setvolume, "true", KParts::LiveConnectExtension::TypeBool },
351 { "Start", start, 0L, KParts::LiveConnectExtension::TypeBool },
352 { "Stop", stop, 0L, KParts::LiveConnectExtension::TypeBool },
353 { "Volume", prop_volume, "100", KParts::LiveConnectExtension::TypeNumber },
354 { "pause", jsc_pause, 0L, KParts::LiveConnectExtension::TypeBool },
355 { "play", play, 0L, KParts::LiveConnectExtension::TypeBool },
356 { "stop", stop, 0L, KParts::LiveConnectExtension::TypeBool },
357 { "volume", volume, 0L, KParts::LiveConnectExtension::TypeBool },
358 };
359
getJSCommandEntry(const char * name,int start=0,int end=sizeof (JSCommandList)/sizeof (JSCommandEntry))360 static const JSCommandEntry * getJSCommandEntry (const char * name, int start = 0, int end = sizeof (JSCommandList)/sizeof (JSCommandEntry)) {
361 if (end - start < 2) {
362 if (start != end && !strcasecmp (JSCommandList[start].name, name))
363 return &JSCommandList[start];
364 return 0L;
365 }
366 int mid = (start + end) / 2;
367 int cmp = strcasecmp (JSCommandList[mid].name, name);
368 if (cmp < 0)
369 return getJSCommandEntry (name, mid + 1, end);
370 if (cmp > 0)
371 return getJSCommandEntry (name, start, mid);
372 return &JSCommandList[mid];
373 }
374
KlashLiveConnectExtension(KlashPart * parent)375 KDE_NO_CDTOR_EXPORT KlashLiveConnectExtension::KlashLiveConnectExtension (KlashPart * parent)
376 : KParts::LiveConnectExtension (parent), player (parent),
377 lastJSCommandEntry (0L),
378 m_started (false),
379 m_enablefinish (false) {
380 connect (parent, SIGNAL (started (KIO::Job *)), this, SLOT (started ()));
381 }
382
~KlashLiveConnectExtension()383 KDE_NO_CDTOR_EXPORT KlashLiveConnectExtension::~KlashLiveConnectExtension() {
384 //kdDebug () << "KlashLiveConnectExtension::~KlashLiveConnectExtension()" << endl;
385 }
386
started()387 KDE_NO_EXPORT void KlashLiveConnectExtension::started () {
388 m_started = true;
389 }
390
finished()391 KDE_NO_EXPORT void KlashLiveConnectExtension::finished () {
392 if (m_started && m_enablefinish) {
393 KParts::LiveConnectExtension::ArgList args;
394 args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("if (window.onFinished) onFinished();")));
395 emit partEvent (0, "eval", args);
396 m_started = true;
397 m_enablefinish = false;
398 }
399 }
400
get(const unsigned long id,const QString & name,KParts::LiveConnectExtension::Type & type,unsigned long & rid,QString & rval)401 KDE_NO_EXPORT bool KlashLiveConnectExtension::get
402 (const unsigned long id, const QString & name,
403 KParts::LiveConnectExtension::Type & type,
404 unsigned long & rid, QString & rval) {
405 const char * str = name.ascii ();
406 kdDebug () << "[01;35mget[00m " << str << endl;
407 const JSCommandEntry * entry = getJSCommandEntry (str);
408 if (!entry)
409 return false;
410 rid = id;
411 type = entry->rettype;
412 switch (entry->command) {
413 case prop_source:
414 type = KParts::LiveConnectExtension::TypeString;
415 rval = player->source ();
416 break;
417 case prop_volume:
418 //if (player->view ())
419 // rval = QString::number (??);
420 break;
421 default:
422 lastJSCommandEntry = entry;
423 type = KParts::LiveConnectExtension::TypeFunction;
424 }
425 return true;
426 }
427
put(const unsigned long,const QString & name,const QString & val)428 KDE_NO_EXPORT bool KlashLiveConnectExtension::put
429 (const unsigned long, const QString & name, const QString & val) {
430 kdDebug () << "[01;35mput[00m " << name << "=" << val << endl;
431 const JSCommandEntry * entry = getJSCommandEntry (name.ascii ());
432 if (!entry)
433 return false;
434 switch (entry->command) {
435 case prop_source: {
436 KURL url (val);
437 if (player->allowRedir (url))
438 player->openURL (url);
439 break;
440 }
441 case prop_volume:
442 //if (player->view ())
443 // ?? (val.toInt ());
444 break;
445 default:
446 return false;
447 }
448 return true;
449 }
450
call(const unsigned long id,const QString & name,const QStringList & args,KParts::LiveConnectExtension::Type & type,unsigned long & rid,QString & rval)451 KDE_NO_EXPORT bool KlashLiveConnectExtension::call
452 (const unsigned long id, const QString & name,
453 const QStringList & args, KParts::LiveConnectExtension::Type & type,
454 unsigned long & rid, QString & rval) {
455 const JSCommandEntry * entry = lastJSCommandEntry;
456 const char * str = name.ascii ();
457 if (!entry || strcmp (entry->name, str))
458 entry = getJSCommandEntry (str);
459 if (!entry)
460 return false;
461 kdDebug () << "[01;35mentry[00m " << entry->name << endl;
462 for (unsigned int i = 0; i < args.size (); ++i)
463 kdDebug () << " " << args[i] << endl;
464 //Klash::View * view = static_cast <Klash::View*> (player->widget ());
465 //if (!view)
466 // return false;
467 rid = id;
468 type = entry->rettype;
469 switch (entry->command) {
470 case notsupported:
471 if (entry->rettype != KParts::LiveConnectExtension::TypeVoid)
472 rval = entry->defaultvalue;
473 break;
474 case play:
475 if (args.size ()) {
476 KURL url (args.first ());
477 if (player->allowRedir (url))
478 ;//player->openNewURL (url);
479 } else
480 player->play ();
481 rval = "true";
482 break;
483 case start:
484 player->play ();
485 rval = "true";
486 break;
487 case stop:
488 player->stop ();
489 rval = "true";
490 break;
491 case jsc_pause:
492 player->pause ();
493 rval = "true";
494 break;
495 case length:
496 rval.setNum (0 /*player->source ()->length ()*/);
497 break;
498 case width:
499 rval.setNum (player->width ());
500 break;
501 case height:
502 rval.setNum (player->height ());
503 break;
504 case setsource:
505 rval ="false";
506 if (args.size ()) {
507 KURL url (args.first ());
508 if (player->allowRedir (url) && player->openURL (url))
509 rval = "true";
510 }
511 break;
512 case setvolume:
513 if (!args.size ())
514 return false;
515 //?? (args.first ().toInt ());
516 rval = "true";
517 break;
518 case source:
519 rval = player->url ().url ();
520 break;
521 case volume:
522 if (player->widget ())
523 rval = QString::number (0);
524 break;
525 default:
526 return false;
527 }
528 return true;
529 }
530
unregister(const unsigned long)531 KDE_NO_EXPORT void KlashLiveConnectExtension::unregister (const unsigned long) {
532 }
533
setSize(int w,int h)534 KDE_NO_EXPORT void KlashLiveConnectExtension::setSize (int w, int h) {
535 QCString jscode;
536 //jscode.sprintf("this.width=%d;this.height=%d;klash", w, h);
537 KParts::LiveConnectExtension::ArgList args;
538 args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("width")));
539 args.push_back (qMakePair (KParts::LiveConnectExtension::TypeNumber, QString::number (w)));
540 emit partEvent (0, "this.setAttribute", args);
541 args.clear();
542 args.push_back (qMakePair (KParts::LiveConnectExtension::TypeString, QString("height")));
543 args.push_back (qMakePair (KParts::LiveConnectExtension::TypeNumber, QString::number (h)));
544 emit partEvent (0, "this.setAttribute", args);
545 }
546
547 //-----------------------------------------------------------------------------
548
549 #include <X11/Xlib.h>
550 #include <X11/keysym.h>
551 #include <X11/Intrinsic.h>
552 #include <X11/StringDefs.h>
553 static const int XKeyPress = KeyPress;
554 #undef KeyPress
555 #undef Always
556 #undef Never
557 #undef Status
558 #undef Unsorted
559 #undef Bool
560
KlashEmbed(KlashView * view)561 KlashEmbed::KlashEmbed (KlashView * view) : QXEmbed (view), m_view (view) {}
562
~KlashEmbed()563 KlashEmbed::~KlashEmbed () {}
564
565 //-----------------------------------------------------------------------------
566
KlashView(QWidget * parent)567 KlashView::KlashView (QWidget * parent)
568 : QWidget (parent, "kde_klash_part"), m_embed (new KlashEmbed (this)) {}
569
~KlashView()570 KlashView::~KlashView () {}
571
resizeEvent(QResizeEvent *)572 void KlashView::resizeEvent (QResizeEvent *) {
573 m_embed->setGeometry (0, 0, width (), height ());
574 }
575
embedId()576 WId KlashView::embedId () {
577 return m_embed->winId ();
578 }
579
580 #include "klash_part.moc"
581