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 () << "get " << 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 () << "put " << 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 () << "entry " << 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