File indexing completed on 2024-05-12 05:32:11

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
0006     SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #pragma once
0013 
0014 #include "effect/globals.h"
0015 
0016 #include <QHash>
0017 #include <QJSEngine>
0018 #include <QJSValue>
0019 #include <QStringList>
0020 #include <QTimer>
0021 
0022 #include <QDBusContext>
0023 #include <QDBusMessage>
0024 
0025 class QQmlComponent;
0026 class QQmlContext;
0027 class QQmlEngine;
0028 class QAction;
0029 class QMenu;
0030 class QRecursiveMutex;
0031 class QQuickWindow;
0032 class KConfigGroup;
0033 
0034 /// @c true == javascript, @c false == qml
0035 typedef QList<QPair<bool, QPair<QString, QString>>> LoadScriptList;
0036 
0037 namespace KWin
0038 {
0039 class Window;
0040 class QtScriptWorkspaceWrapper;
0041 
0042 class KWIN_EXPORT AbstractScript : public QObject
0043 {
0044     Q_OBJECT
0045 public:
0046     AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
0047     ~AbstractScript() override;
0048     int scriptId() const
0049     {
0050         return m_scriptId;
0051     }
0052     QString fileName() const
0053     {
0054         return m_fileName;
0055     }
0056     const QString &pluginName()
0057     {
0058         return m_pluginName;
0059     }
0060     bool running() const
0061     {
0062         return m_running;
0063     }
0064 
0065     KConfigGroup config() const;
0066 
0067 public Q_SLOTS:
0068     void stop();
0069     virtual void run() = 0;
0070 
0071 Q_SIGNALS:
0072     void runningChanged(bool);
0073 
0074 protected:
0075     void setRunning(bool running)
0076     {
0077         if (m_running == running) {
0078             return;
0079         }
0080         m_running = running;
0081         Q_EMIT runningChanged(m_running);
0082     }
0083 
0084 private:
0085     int m_scriptId;
0086     QString m_fileName;
0087     QString m_pluginName;
0088     bool m_running;
0089 };
0090 
0091 /**
0092  * In order to be able to construct QTimer objects in javascript, the constructor
0093  * must be declared with Q_INVOKABLE.
0094  */
0095 class ScriptTimer : public QTimer
0096 {
0097     Q_OBJECT
0098 
0099 public:
0100     Q_INVOKABLE ScriptTimer(QObject *parent = nullptr);
0101 };
0102 
0103 class Script : public AbstractScript, QDBusContext
0104 {
0105     Q_OBJECT
0106 public:
0107     Script(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
0108     virtual ~Script();
0109 
0110     Q_INVOKABLE QVariant readConfig(const QString &key, const QVariant &defaultValue = QVariant());
0111 
0112     Q_INVOKABLE void callDBus(const QString &service, const QString &path,
0113                               const QString &interface, const QString &method,
0114                               const QJSValue &arg1 = QJSValue(),
0115                               const QJSValue &arg2 = QJSValue(),
0116                               const QJSValue &arg3 = QJSValue(),
0117                               const QJSValue &arg4 = QJSValue(),
0118                               const QJSValue &arg5 = QJSValue(),
0119                               const QJSValue &arg6 = QJSValue(),
0120                               const QJSValue &arg7 = QJSValue(),
0121                               const QJSValue &arg8 = QJSValue(),
0122                               const QJSValue &arg9 = QJSValue());
0123 
0124     Q_INVOKABLE bool registerShortcut(const QString &objectName, const QString &text,
0125                                       const QString &keySequence, const QJSValue &callback);
0126 
0127     Q_INVOKABLE bool registerScreenEdge(int edge, const QJSValue &callback);
0128     Q_INVOKABLE bool unregisterScreenEdge(int edge);
0129 
0130     Q_INVOKABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
0131     Q_INVOKABLE bool unregisterTouchScreenEdge(int edge);
0132 
0133     /**
0134      * @brief Registers the given @p callback to be invoked whenever the UserActionsMenu is about
0135      * to be showed. In the callback the script can create a further sub menu or menu entry to be
0136      * added to the UserActionsMenu.
0137      *
0138      * @param callback Script method to execute when the UserActionsMenu is about to be shown.
0139      * @return void
0140      * @see actionsForUserActionMenu
0141      */
0142     Q_INVOKABLE void registerUserActionsMenu(const QJSValue &callback);
0143 
0144     /**
0145      * @brief Creates actions for the UserActionsMenu by invoking the registered callbacks.
0146      *
0147      * This method invokes all the callbacks previously registered with registerUseractionsMenuCallback.
0148      * The Client @p c is passed in as an argument to the invoked method.
0149      *
0150      * The invoked method is supposed to return a JavaScript object containing either the menu or
0151      * menu entry to be added. In case the callback returns a null or undefined or any other invalid
0152      * value, it is not considered for adding to the menu.
0153      *
0154      * The JavaScript object structure for a menu entry looks like the following:
0155      * @code
0156      * {
0157      *     title: "My Menu Entry",
0158      *     checkable: true,
0159      *     checked: false,
0160      *     triggered: function (action) {
0161      *         // callback when the menu entry is triggered with the QAction as argument
0162      *     }
0163      * }
0164      * @endcode
0165      *
0166      * To construct a complete Menu the JavaScript object looks like the following:
0167      * @code
0168      * {
0169      *     title: "My Menu Title",
0170      *     items: [{...}, {...}, ...] // list of menu entries as described above
0171      * }
0172      * @endcode
0173      *
0174      * The returned JavaScript object is introspected and for a menu entry a QAction is created,
0175      * while for a menu a QMenu is created and QActions for the individual entries. Of course it
0176      * is allowed to have nested structures.
0177      *
0178      * All created objects are (grand) children to the passed in @p parent menu, so that they get
0179      * deleted whenever the menu is destroyed.
0180      *
0181      * @param c The Client for which the menu is invoked, passed to the callback
0182      * @param parent The Parent for the created Menus or Actions
0183      * @return QList< QAction* > List of QActions obtained from asking the registered callbacks
0184      * @see registerUseractionsMenuCallback
0185      */
0186     QList<QAction *> actionsForUserActionMenu(Window *client, QMenu *parent);
0187 
0188 public Q_SLOTS:
0189     void run() override;
0190 
0191 private Q_SLOTS:
0192     /**
0193      * Callback for when loadScriptFromFile has finished.
0194      */
0195     void slotScriptLoadedFromFile();
0196 
0197     /**
0198      * Called when any reserve screen edge is triggered.
0199      */
0200     bool slotBorderActivated(ElectricBorder border);
0201 
0202 private:
0203     /**
0204      * Read the script from file into a byte array.
0205      * If file cannot be read an empty byte array is returned.
0206      */
0207     QByteArray loadScriptFromFile(const QString &fileName);
0208 
0209     /**
0210      * @brief Parses the @p value to either a QMenu or QAction.
0211      *
0212      * @param value The ScriptValue describing either a menu or action
0213      * @param parent The parent to use for the created menu or action
0214      * @return QAction* The parsed action or menu action, if parsing fails returns @c null.
0215      */
0216     QAction *scriptValueToAction(const QJSValue &value, QMenu *parent);
0217 
0218     /**
0219      * @brief Creates a new QAction from the provided data and registers it for invoking the
0220      * @p callback when the action is triggered.
0221      *
0222      * The created action is added to the map of actions and callbacks shared with the global
0223      * shortcuts.
0224      *
0225      * @param title The title of the action
0226      * @param checkable Whether the action is checkable
0227      * @param checked Whether the checkable action is checked
0228      * @param callback The callback to invoke when the action is triggered
0229      * @param parent The parent to be used for the new created action
0230      * @return QAction* The created action
0231      */
0232     QAction *createAction(const QString &title, const QJSValue &item, QMenu *parent);
0233 
0234     /**
0235      * @brief Parses the @p items and creates a QMenu from it.
0236      *
0237      * @param title The title of the Menu.
0238      * @param items JavaScript Array containing Menu items.
0239      * @param parent The parent to use for the new created menu
0240      * @return QAction* The menu action for the new Menu
0241      */
0242     QAction *createMenu(const QString &title, const QJSValue &items, QMenu *parent);
0243 
0244     QJSEngine *m_engine;
0245     QDBusMessage m_invocationContext;
0246     bool m_starting;
0247     QHash<int, QJSValueList> m_screenEdgeCallbacks;
0248     QHash<int, QAction *> m_touchScreenEdgeCallbacks;
0249     QJSValueList m_userActionsMenuCallbacks;
0250 };
0251 
0252 class DeclarativeScript : public AbstractScript
0253 {
0254     Q_OBJECT
0255 public:
0256     explicit DeclarativeScript(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
0257     ~DeclarativeScript() override;
0258 
0259 public Q_SLOTS:
0260     Q_SCRIPTABLE void run() override;
0261 
0262 private Q_SLOTS:
0263     void createComponent();
0264 
0265 private:
0266     QQmlContext *m_context;
0267     QQmlComponent *m_component;
0268 };
0269 
0270 class JSEngineGlobalMethodsWrapper : public QObject
0271 {
0272     Q_OBJECT
0273 public:
0274     //------------------------------------------------------------------
0275     // enums copy&pasted from kwinglobals.h for exporting
0276 
0277     enum ClientAreaOption {
0278         ///< geometry where a window will be initially placed after being mapped
0279         PlacementArea,
0280         ///< window movement snapping area?  ignore struts
0281         MovementArea,
0282         ///< geometry to which a window will be maximized
0283         MaximizeArea,
0284         ///< like MaximizeArea, but ignore struts - used e.g. for topmenu
0285         MaximizeFullArea,
0286         ///< area for fullscreen windows
0287         FullScreenArea,
0288         ///< whole workarea (all screens together)
0289         WorkArea,
0290         ///< whole area (all screens together), ignore struts
0291         FullArea,
0292         ///< one whole screen, ignore struts
0293         ScreenArea
0294     };
0295     Q_ENUM(ClientAreaOption)
0296     explicit JSEngineGlobalMethodsWrapper(DeclarativeScript *parent);
0297     ~JSEngineGlobalMethodsWrapper() override;
0298 
0299 public Q_SLOTS:
0300     QVariant readConfig(const QString &key, QVariant defaultValue = QVariant());
0301 
0302 private:
0303     DeclarativeScript *m_script;
0304 };
0305 
0306 /**
0307  * The heart of KWin::Scripting. Infinite power lies beyond
0308  */
0309 class KWIN_EXPORT Scripting : public QObject
0310 {
0311     Q_OBJECT
0312     Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
0313 private:
0314     explicit Scripting(QObject *parent);
0315     QStringList scriptList;
0316     QList<KWin::AbstractScript *> scripts;
0317     /**
0318      * Lock to protect the scripts member variable.
0319      */
0320     std::unique_ptr<QRecursiveMutex> m_scriptsLock;
0321 
0322     // Preferably call ONLY at load time
0323     void runScripts();
0324 
0325 public:
0326     ~Scripting() override;
0327     Q_SCRIPTABLE Q_INVOKABLE int loadScript(const QString &filePath, const QString &pluginName = QString());
0328     Q_SCRIPTABLE Q_INVOKABLE int loadDeclarativeScript(const QString &filePath, const QString &pluginName = QString());
0329     Q_SCRIPTABLE Q_INVOKABLE bool isScriptLoaded(const QString &pluginName) const;
0330     Q_SCRIPTABLE Q_INVOKABLE bool unloadScript(const QString &pluginName);
0331 
0332     /**
0333      * @brief Invokes all registered callbacks to add actions to the UserActionsMenu.
0334      *
0335      * @param c The Client for which the UserActionsMenu is about to be shown
0336      * @param parent The parent menu to which to add created child menus and items
0337      * @return QList< QAction* > List of all actions aggregated from all scripts.
0338      */
0339     QList<QAction *> actionsForUserActionMenu(Window *c, QMenu *parent);
0340 
0341     QQmlEngine *qmlEngine() const;
0342     QQmlEngine *qmlEngine();
0343     QQmlContext *declarativeScriptSharedContext() const;
0344     QQmlContext *declarativeScriptSharedContext();
0345     QtScriptWorkspaceWrapper *workspaceWrapper() const;
0346 
0347     AbstractScript *findScript(const QString &pluginName) const;
0348 
0349     static Scripting *self();
0350     static Scripting *create(QObject *parent);
0351 
0352 public Q_SLOTS:
0353     void scriptDestroyed(QObject *object);
0354     Q_SCRIPTABLE void start();
0355 
0356 private Q_SLOTS:
0357     void slotScriptsQueried();
0358 
0359 private:
0360     void init();
0361     LoadScriptList queryScriptsToLoad();
0362     static Scripting *s_self;
0363     QQmlEngine *m_qmlEngine;
0364     QQmlContext *m_declarativeScriptSharedContext;
0365     QtScriptWorkspaceWrapper *m_workspaceWrapper;
0366 };
0367 
0368 inline QQmlEngine *Scripting::qmlEngine() const
0369 {
0370     return m_qmlEngine;
0371 }
0372 
0373 inline QQmlEngine *Scripting::qmlEngine()
0374 {
0375     return m_qmlEngine;
0376 }
0377 
0378 inline QQmlContext *Scripting::declarativeScriptSharedContext() const
0379 {
0380     return m_declarativeScriptSharedContext;
0381 }
0382 
0383 inline QQmlContext *Scripting::declarativeScriptSharedContext()
0384 {
0385     return m_declarativeScriptSharedContext;
0386 }
0387 
0388 inline QtScriptWorkspaceWrapper *Scripting::workspaceWrapper() const
0389 {
0390     return m_workspaceWrapper;
0391 }
0392 
0393 inline Scripting *Scripting::self()
0394 {
0395     return s_self;
0396 }
0397 
0398 }