mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-02 14:38:38 +00:00
[Plugins] Update create script and add GTK3 how-to doc
- Updated create_plugin script to create a GTK3 plugin. - Added a document for updating a 1.3 plugin to be compatible with 2.0.
This commit is contained in:
parent
3d24998577
commit
a01481b26f
3 changed files with 204 additions and 74 deletions
|
@ -112,11 +112,11 @@ def create_plugin():
|
||||||
write_file(deluge_namespace, '__init__.py', NAMESPACE_INIT, False)
|
write_file(deluge_namespace, '__init__.py', NAMESPACE_INIT, False)
|
||||||
write_file(plugins_namespace, '__init__.py', NAMESPACE_INIT, False)
|
write_file(plugins_namespace, '__init__.py', NAMESPACE_INIT, False)
|
||||||
write_file(src, '__init__.py', INIT)
|
write_file(src, '__init__.py', INIT)
|
||||||
write_file(src, 'gtkui.py', GTKUI)
|
write_file(src, 'gtk3ui.py', GTK3UI)
|
||||||
write_file(src, 'webui.py', WEBUI)
|
write_file(src, 'webui.py', WEBUI)
|
||||||
write_file(src, 'core.py', CORE)
|
write_file(src, 'core.py', CORE)
|
||||||
write_file(src, 'common.py', COMMON)
|
write_file(src, 'common.py', COMMON)
|
||||||
write_file(data_dir, 'config.glade', GLADE)
|
write_file(data_dir, 'config.ui', GLADE)
|
||||||
write_file(data_dir, '%s.js' % safe_name, DEFAULT_JS)
|
write_file(data_dir, '%s.js' % safe_name, DEFAULT_JS)
|
||||||
|
|
||||||
# add an input parameter for this?
|
# add an input parameter for this?
|
||||||
|
@ -127,8 +127,7 @@ def create_plugin():
|
||||||
os.system(dev_link_path)
|
os.system(dev_link_path)
|
||||||
|
|
||||||
|
|
||||||
CORE = """
|
CORE = """from __future__ import unicode_literals
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -145,7 +144,8 @@ DEFAULT_PREFS = {
|
||||||
|
|
||||||
class Core(CorePluginBase):
|
class Core(CorePluginBase):
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.config = deluge.configmanager.ConfigManager('%(safe_name)s.conf', DEFAULT_PREFS)
|
self.config = deluge.configmanager.ConfigManager(
|
||||||
|
'%(safe_name)s.conf', DEFAULT_PREFS)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
pass
|
pass
|
||||||
|
@ -166,34 +166,32 @@ class Core(CorePluginBase):
|
||||||
return self.config.config
|
return self.config.config
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INIT = """
|
INIT = """from deluge.plugins.init import PluginInitBase
|
||||||
from deluge.plugins.init import PluginInitBase
|
|
||||||
|
|
||||||
|
|
||||||
class CorePlugin(PluginInitBase):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from core import Core as PluginClass
|
from .core import Core as PluginClass
|
||||||
self._plugin_cls = PluginClass
|
self._plugin_cls = PluginClass
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
|
|
||||||
class GtkUIPlugin(PluginInitBase):
|
class Gtk3UIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from gtkui import GtkUI as PluginClass
|
from .gtk3ui import Gtk3UI as PluginClass
|
||||||
self._plugin_cls = PluginClass
|
self._plugin_cls = PluginClass
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(Gtk3UIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
|
|
||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from webui import WebUI as PluginClass
|
from .webui import WebUI as PluginClass
|
||||||
self._plugin_cls = PluginClass
|
self._plugin_cls = PluginClass
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
SETUP = """
|
SETUP = """from setuptools import find_packages, setup
|
||||||
from setuptools import find_packages, setup
|
|
||||||
|
|
||||||
__plugin_name__ = '%(name)s'
|
__plugin_name__ = '%(name)s'
|
||||||
__author__ = '%(author_name)s'
|
__author__ = '%(author_name)s'
|
||||||
|
@ -222,16 +220,15 @@ setup(
|
||||||
entry_points=\"\"\"
|
entry_points=\"\"\"
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%%s = deluge.plugins.%%s:CorePlugin
|
%%s = deluge.plugins.%%s:CorePlugin
|
||||||
[deluge.plugin.gtkui]
|
[deluge.plugin.gtk3ui]
|
||||||
%%s = deluge.plugins.%%s:GtkUIPlugin
|
%%s = deluge.plugins.%%s:Gtk3UIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%%s = deluge.plugins.%%s:WebUIPlugin
|
%%s = deluge.plugins.%%s:WebUIPlugin
|
||||||
\"\"\" %% ((__plugin_name__, __plugin_name__.lower()) * 3)
|
\"\"\" %% ((__plugin_name__, __plugin_name__.lower()) * 3)
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
COMMON = """
|
COMMON = """from __future__ import unicode_literals
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
@ -239,17 +236,18 @@ from pkg_resources import resource_filename
|
||||||
|
|
||||||
|
|
||||||
def get_resource(filename):
|
def get_resource(filename):
|
||||||
return resource_filename('deluge.plugins.%(safe_name)s', os.path.join('data', filename))
|
return resource_filename(
|
||||||
|
'deluge.plugins.%(safe_name)s', os.path.join('data', filename))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GTKUI = """
|
GTK3UI = """from __future__ import unicode_literals
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import gtk
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
import deluge.component as component
|
import deluge.component as component
|
||||||
from deluge.plugins.pluginbase import GtkPluginBase
|
from deluge.plugins.pluginbase import Gtk3PluginBase
|
||||||
from deluge.ui.client import client
|
from deluge.ui.client import client
|
||||||
|
|
||||||
from .common import get_resource
|
from .common import get_resource
|
||||||
|
@ -257,23 +255,29 @@ from .common import get_resource
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GtkUI(GtkPluginBase):
|
class Gtk3UI(Gtk3PluginBase):
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.glade = gtk.glade.XML(get_resource('config.glade'))
|
self.builder = Gtk.Builder()
|
||||||
|
self.builder.add_from_file(get_resource('config.glade'))
|
||||||
|
|
||||||
component.get('Preferences').add_page('%(name)s', self.glade.get_widget('prefs_box'))
|
component.get('Preferences').add_page(
|
||||||
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
|
'%(name)s', self.builder.get_object('prefs_box'))
|
||||||
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
|
component.get('PluginManager').register_hook(
|
||||||
|
'on_apply_prefs', self.on_apply_prefs)
|
||||||
|
component.get('PluginManager').register_hook(
|
||||||
|
'on_show_prefs', self.on_show_prefs)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page('%(name)s')
|
component.get('Preferences').remove_page('%(name)s')
|
||||||
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').deregister_hook(
|
||||||
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
|
'on_apply_prefs', self.on_apply_prefs)
|
||||||
|
component.get('PluginManager').deregister_hook(
|
||||||
|
'on_show_prefs', self.on_show_prefs)
|
||||||
|
|
||||||
def on_apply_prefs(self):
|
def on_apply_prefs(self):
|
||||||
log.debug('applying prefs for %(name)s')
|
log.debug('applying prefs for %(name)s')
|
||||||
config = {
|
config = {
|
||||||
'test': self.glade.get_widget('txt_test').get_text()
|
'test': self.builder.get_object('txt_test').get_text()
|
||||||
}
|
}
|
||||||
client.%(safe_name)s.set_config(config)
|
client.%(safe_name)s.set_config(config)
|
||||||
|
|
||||||
|
@ -282,45 +286,43 @@ class GtkUI(GtkPluginBase):
|
||||||
|
|
||||||
def cb_get_config(self, config):
|
def cb_get_config(self, config):
|
||||||
\"\"\"callback for on show_prefs\"\"\"
|
\"\"\"callback for on show_prefs\"\"\"
|
||||||
self.glade.get_widget('txt_test').set_text(config['test'])
|
self.builder.get_object('txt_test').set_text(config['test'])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GLADE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
GLADE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
<!-- Generated with glade 3.18.3 -->
|
||||||
<!--Generated with glade3 3.4.5 on Fri Aug 8 23:34:44 2008 -->
|
<interface>
|
||||||
<glade-interface>
|
<requires lib="gtk+" version="3.0"/>
|
||||||
<widget class="GtkWindow" id="window1">
|
<object class="GtkWindow" id="window1">
|
||||||
<child>
|
<child>
|
||||||
<widget class="GtkHBox" id="prefs_box">
|
<object class="GtkBox" id="prefs_box">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<child>
|
<child>
|
||||||
<widget class="GtkLabel" id="label1">
|
<object class="GtkLabel" id="label1">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="label" translatable="yes">Test config value:</property>
|
<property name="label" translatable="yes">Test config value:</property>
|
||||||
</widget>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<widget class="GtkEntry" id="txt_test">
|
<object class="GtkEntry" id="txt_test">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
</widget>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</widget>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</widget>
|
</object>
|
||||||
</glade-interface>
|
</interface>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
WEBUI = """
|
WEBUI = """from __future__ import unicode_literals
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from deluge.plugins.pluginbase import WebPluginBase
|
from deluge.plugins.pluginbase import WebPluginBase
|
||||||
from deluge.ui.client import client
|
|
||||||
|
|
||||||
from .common import get_resource
|
from .common import get_resource
|
||||||
|
|
||||||
|
@ -338,17 +340,17 @@ class WebUI(WebPluginBase):
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_JS = """/*
|
DEFAULT_JS = """/**
|
||||||
Script: %(filename)s
|
* Script: %(filename)s
|
||||||
The client-side javascript code for the %(name)s plugin.
|
* The client-side javascript code for the %(name)s plugin.
|
||||||
|
*
|
||||||
Copyright:
|
* Copyright:
|
||||||
(C) %(author_name)s %(current_year)s <%(author_email)s>
|
* (C) %(author_name)s %(current_year)s <%(author_email)s>
|
||||||
|
*
|
||||||
This file is part of %(name)s and is licensed under GNU General Public License 3.0, or later, with
|
* This file is part of %(name)s and is licensed under GNU GPL 3.0, or
|
||||||
the additional special exception to link portions of this program with the OpenSSL library.
|
* later, with the additional special exception to link portions of this
|
||||||
See LICENSE for more details.
|
* program with the OpenSSL library. See LICENSE for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
%(name)sPlugin = Ext.extend(Deluge.Plugin, {
|
%(name)sPlugin = Ext.extend(Deluge.Plugin, {
|
||||||
constructor: function(config) {
|
constructor: function(config) {
|
||||||
|
@ -363,29 +365,21 @@ Copyright:
|
||||||
},
|
},
|
||||||
|
|
||||||
onEnable: function() {
|
onEnable: function() {
|
||||||
this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.%(name)sPage());
|
this.prefsPage = deluge.preferences.addPage(
|
||||||
|
new Deluge.ux.preferences.%(name)sPage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
new %(name)sPlugin();
|
new %(name)sPlugin();
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GPL = """#
|
GPL = """# -*- coding: utf-8 -*-
|
||||||
# -*- coding: utf-8 -*-#
|
|
||||||
|
|
||||||
# Copyright (C) %(current_year)d %(author_name)s <%(author_email)s>
|
# Copyright (C) %(current_year)d %(author_name)s <%(author_email)s>
|
||||||
#
|
#
|
||||||
# Basic plugin template created by:
|
# Basic plugin template created by the Deluge Team.
|
||||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
|
||||||
# 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
|
||||||
# 2009 Damien Churchill <damoxc@gmail.com>
|
|
||||||
# 2010 Pedro Algarvio <pedro@algarvio.me>
|
|
||||||
# 2017 Calum Lind <calumlind+deluge@gmail.com>
|
|
||||||
#
|
#
|
||||||
# This file is part of %(name)s and is licensed under GNU General Public License 3.0, or later, with
|
# This file is part of %(name)s and is licensed under GNU GPL 3.0, or later,
|
||||||
# the additional special exception to link portions of this program with the OpenSSL library.
|
# with the additional special exception to link portions of this program with
|
||||||
# See LICENSE for more details.
|
# the OpenSSL library. See LICENSE for more details.
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NAMESPACE_INIT = """__import__('pkg_resources').declare_namespace(__name__)
|
NAMESPACE_INIT = """__import__('pkg_resources').declare_namespace(__name__)
|
||||||
|
|
|
@ -6,3 +6,7 @@ the tutorials.
|
||||||
## Web JSON-RPC
|
## Web JSON-RPC
|
||||||
|
|
||||||
- [Connect to JSON-RPC using curl](curl-jsonrpc.md)
|
- [Connect to JSON-RPC using curl](curl-jsonrpc.md)
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
- [Update 1.3 plugin for 2.0](update-1.3-plugin.md)
|
||||||
|
|
132
docs/source/devguide/how-to/update-1.3-plugin.md
Normal file
132
docs/source/devguide/how-to/update-1.3-plugin.md
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
# How to update a Deluge 1.3 plugin for 2.0
|
||||||
|
|
||||||
|
With the new code in Deluge 2.0 there are changes that require authors of
|
||||||
|
existing plugins to update their plugins to work on Deluge 2.0.
|
||||||
|
|
||||||
|
The main changes are with Python 3 support and the new GTK3 user interface with
|
||||||
|
the dropping of GTK2. However it is still possible for a 1.3 plugin to be made
|
||||||
|
compatible with 2.0 and this guide aims to helps with that process.
|
||||||
|
|
||||||
|
Note that the Deluge 2.0 plugins now use namespace packaging which is not
|
||||||
|
compatible with Deluge 1.3.
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
### Python version matching
|
||||||
|
|
||||||
|
Ensure your code is both Python 2.7 and Python >=3.5 compatible.
|
||||||
|
|
||||||
|
In `1.3-stable` the plugins that were built with a specfific version of Python
|
||||||
|
could on be loaded if the system Python also matched.
|
||||||
|
|
||||||
|
This has change in Deluge 2.0 and it will load any Python version of plugin
|
||||||
|
eggs so compatibility is essential for end-users not to encounter issues.
|
||||||
|
|
||||||
|
### Six
|
||||||
|
|
||||||
|
Use [six] to assist with compatibility.
|
||||||
|
|
||||||
|
[six]: https://pythonhosted.org/six/
|
||||||
|
|
||||||
|
### Unicode literals
|
||||||
|
|
||||||
|
Add this to files to ensure strings and bytes separatation so there are no
|
||||||
|
surprises when running on Python 3.
|
||||||
|
|
||||||
|
## GTK 3 addition
|
||||||
|
|
||||||
|
In order to support both Deluge 1.3 and 2.0 all existing plugin GTK UI files
|
||||||
|
must be copied and then converted to contain only GTK3 code with the old files
|
||||||
|
still using PyGTK e.g.:
|
||||||
|
|
||||||
|
cp gtkui.py gtk3ui.py
|
||||||
|
|
||||||
|
### Convert from libglade to GtkBuilder
|
||||||
|
|
||||||
|
With PyGTK there were two library options for creating the user interface from
|
||||||
|
XML files by default Deluge plugins used libglade but that has been deprecated
|
||||||
|
and removed in GTK3. So the libglade `.glade` files will need converted to
|
||||||
|
GtkBuilder `.ui` files and the Python code updated.
|
||||||
|
|
||||||
|
https://developer.gnome.org/gtk2/stable/gtk-migrating-GtkBuilder.html
|
||||||
|
|
||||||
|
#### gtk-builder-convert
|
||||||
|
|
||||||
|
Install the `gtk-builder-convert` converter on Ubuntu with:
|
||||||
|
|
||||||
|
sudo apt install libgtk2.0-dev
|
||||||
|
|
||||||
|
To convert your GTK run it like so:
|
||||||
|
|
||||||
|
gtk-builder-convert data/config.glade data/config.ui
|
||||||
|
|
||||||
|
#### Glade UI designer for GTK2
|
||||||
|
|
||||||
|
The above conversion can be done in Glade UI designer (version <=3.8), ensuring
|
||||||
|
that the minimum Gtk version is set to 2.24 and any deprecated widgets are
|
||||||
|
fixed. The updated file should be saved with file extension `.ui`.
|
||||||
|
|
||||||
|
#### Python code changes
|
||||||
|
|
||||||
|
The code needs to replace `gtk.glade` references with `gtk.Builder` and the
|
||||||
|
first step is updating how the files are loaded:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- self.glade = gtk.glade.XML(get_resource("config.glade"))
|
||||||
|
+ self.builder = gtk.Builder()
|
||||||
|
+ self.builder.add_from_file(get_resource("config.ui"))
|
||||||
|
```
|
||||||
|
|
||||||
|
The next stage is to replace every occurange of these `glade` methods with
|
||||||
|
the `builder` equivalents:
|
||||||
|
|
||||||
|
glade.signal_autoconnect -> builder.connect_signals
|
||||||
|
glade.get_widget -> builder.get_object
|
||||||
|
|
||||||
|
### Migrate XML files to GTK3
|
||||||
|
|
||||||
|
If you open and save the file it will update with the new requirement header:
|
||||||
|
|
||||||
|
<!-- Generated with glade 3.18.3 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.0"/>
|
||||||
|
|
||||||
|
You can fix deprecated widgets but keep the minimum GTK version to <= 3.10 for
|
||||||
|
desktop compatiblity.
|
||||||
|
|
||||||
|
An example of migrating a Deluge plugin to GtkBuilder: [AutoAdd GtkBuilder]
|
||||||
|
|
||||||
|
### Gtk import rename
|
||||||
|
|
||||||
|
Move from PyGTK to GTK3 using Python bindings.
|
||||||
|
|
||||||
|
https://pygobject.readthedocs.io/en/latest/guide/porting.html
|
||||||
|
|
||||||
|
wget https://gitlab.gnome.org/GNOME/pygobject/raw/master/tools/pygi-convert.sh
|
||||||
|
cp gtkui.py gtk3ui.py
|
||||||
|
sh pygi-convert.sh gtk3ui.py
|
||||||
|
|
||||||
|
```diff
|
||||||
|
-import gtk
|
||||||
|
+from gi.repository import Gtk
|
||||||
|
```
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- self.builder = gtk.Builder()
|
||||||
|
+ self.builder = Gtk.Builder()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deluge GTK3
|
||||||
|
|
||||||
|
Imports will need updated from `deluge.ui.gtkui` to `deluge.ui.gtk3`.
|
||||||
|
|
||||||
|
### PluginBase
|
||||||
|
|
||||||
|
```diff
|
||||||
|
-from deluge.plugins.pluginbase import GtkPluginBase
|
||||||
|
+from deluge.plugins.pluginbase import Gtk3PluginBase
|
||||||
|
-class GtkUI(GtkPluginBase):
|
||||||
|
+class Gtk3UI(Gtk3PluginBase):
|
||||||
|
```
|
||||||
|
|
||||||
|
[autoadd gtkbuilder]: https://git.deluge-torrent.org/deluge/commit/?h=develop&id=510a8b50b213cab804d693a5f122f9c0d9dd1fb3
|
Loading…
Add table
Add a link
Reference in a new issue