diff --git a/deluge/scripts/create_plugin.py b/deluge/scripts/create_plugin.py
index 638c036db..6479d1f31 100644
--- a/deluge/scripts/create_plugin.py
+++ b/deluge/scripts/create_plugin.py
@@ -112,11 +112,11 @@ def create_plugin():
write_file(deluge_namespace, '__init__.py', NAMESPACE_INIT, False)
write_file(plugins_namespace, '__init__.py', NAMESPACE_INIT, False)
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, 'core.py', CORE)
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)
# add an input parameter for this?
@@ -127,8 +127,7 @@ def create_plugin():
os.system(dev_link_path)
-CORE = """
-from __future__ import unicode_literals
+CORE = """from __future__ import unicode_literals
import logging
@@ -145,7 +144,8 @@ DEFAULT_PREFS = {
class Core(CorePluginBase):
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):
pass
@@ -166,34 +166,32 @@ class Core(CorePluginBase):
return self.config.config
"""
-INIT = """
-from deluge.plugins.init import PluginInitBase
+INIT = """from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
- from core import Core as PluginClass
+ from .core import Core as PluginClass
self._plugin_cls = PluginClass
super(CorePlugin, self).__init__(plugin_name)
-class GtkUIPlugin(PluginInitBase):
+class Gtk3UIPlugin(PluginInitBase):
def __init__(self, plugin_name):
- from gtkui import GtkUI as PluginClass
+ from .gtk3ui import Gtk3UI as PluginClass
self._plugin_cls = PluginClass
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super(Gtk3UIPlugin, self).__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
- from webui import WebUI as PluginClass
+ from .webui import WebUI as PluginClass
self._plugin_cls = PluginClass
super(WebUIPlugin, self).__init__(plugin_name)
"""
-SETUP = """
-from setuptools import find_packages, setup
+SETUP = """from setuptools import find_packages, setup
__plugin_name__ = '%(name)s'
__author__ = '%(author_name)s'
@@ -222,16 +220,15 @@ setup(
entry_points=\"\"\"
[deluge.plugin.core]
%%s = deluge.plugins.%%s:CorePlugin
- [deluge.plugin.gtkui]
- %%s = deluge.plugins.%%s:GtkUIPlugin
+ [deluge.plugin.gtk3ui]
+ %%s = deluge.plugins.%%s:Gtk3UIPlugin
[deluge.plugin.web]
%%s = deluge.plugins.%%s:WebUIPlugin
\"\"\" %% ((__plugin_name__, __plugin_name__.lower()) * 3)
)
"""
-COMMON = """
-from __future__ import unicode_literals
+COMMON = """from __future__ import unicode_literals
import os.path
@@ -239,17 +236,18 @@ from pkg_resources import 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 = """
-from __future__ import unicode_literals
+GTK3UI = """from __future__ import unicode_literals
-import gtk
import logging
+from gi.repository import Gtk
+
import deluge.component as component
-from deluge.plugins.pluginbase import GtkPluginBase
+from deluge.plugins.pluginbase import Gtk3PluginBase
from deluge.ui.client import client
from .common import get_resource
@@ -257,23 +255,29 @@ from .common import get_resource
log = logging.getLogger(__name__)
-class GtkUI(GtkPluginBase):
+class Gtk3UI(Gtk3PluginBase):
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('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
- component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
+ component.get('Preferences').add_page(
+ '%(name)s', self.builder.get_object('prefs_box'))
+ 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):
component.get('Preferences').remove_page('%(name)s')
- component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
- component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
+ component.get('PluginManager').deregister_hook(
+ 'on_apply_prefs', self.on_apply_prefs)
+ component.get('PluginManager').deregister_hook(
+ 'on_show_prefs', self.on_show_prefs)
def on_apply_prefs(self):
log.debug('applying prefs for %(name)s')
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)
@@ -282,45 +286,43 @@ class GtkUI(GtkPluginBase):
def cb_get_config(self, config):
\"\"\"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 = """
-
-
-
-
+GLADE = """
+
+
+
+
1
-
+
-
-
+
+
"""
-WEBUI = """
-from __future__ import unicode_literals
+WEBUI = """from __future__ import unicode_literals
import logging
from deluge.plugins.pluginbase import WebPluginBase
-from deluge.ui.client import client
from .common import get_resource
@@ -338,17 +340,17 @@ class WebUI(WebPluginBase):
pass
"""
-DEFAULT_JS = """/*
-Script: %(filename)s
- The client-side javascript code for the %(name)s plugin.
-
-Copyright:
- (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
- the additional special exception to link portions of this program with the OpenSSL library.
- See LICENSE for more details.
-*/
+DEFAULT_JS = """/**
+ * Script: %(filename)s
+ * The client-side javascript code for the %(name)s plugin.
+ *
+ * Copyright:
+ * (C) %(author_name)s %(current_year)s <%(author_email)s>
+ *
+ * This file is part of %(name)s and is licensed under GNU GPL 3.0, or
+ * later, with the additional special exception to link portions of this
+ * program with the OpenSSL library. See LICENSE for more details.
+ */
%(name)sPlugin = Ext.extend(Deluge.Plugin, {
constructor: function(config) {
@@ -363,29 +365,21 @@ Copyright:
},
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();
"""
-GPL = """#
-# -*- coding: utf-8 -*-#
-
+GPL = """# -*- coding: utf-8 -*-
# Copyright (C) %(current_year)d %(author_name)s <%(author_email)s>
#
-# Basic plugin template created by:
-# Copyright (C) 2008 Martijn Voncken
-# 2007-2009 Andrew Resch
-# 2009 Damien Churchill
-# 2010 Pedro Algarvio
-# 2017 Calum Lind
+# Basic plugin template created by the Deluge Team.
#
-# This file is part of %(name)s and is licensed under GNU General Public License 3.0, or later, with
-# the additional special exception to link portions of this program with the OpenSSL library.
-# See LICENSE for more details.
-#
-
+# This file is part of %(name)s and is licensed under GNU GPL 3.0, or later,
+# with the additional special exception to link portions of this program with
+# the OpenSSL library. See LICENSE for more details.
"""
NAMESPACE_INIT = """__import__('pkg_resources').declare_namespace(__name__)
diff --git a/docs/source/devguide/how-to/index.md b/docs/source/devguide/how-to/index.md
index da978c16b..6260d3ccc 100644
--- a/docs/source/devguide/how-to/index.md
+++ b/docs/source/devguide/how-to/index.md
@@ -6,3 +6,7 @@ the tutorials.
## Web JSON-RPC
- [Connect to JSON-RPC using curl](curl-jsonrpc.md)
+
+## Plugins
+
+- [Update 1.3 plugin for 2.0](update-1.3-plugin.md)
diff --git a/docs/source/devguide/how-to/update-1.3-plugin.md b/docs/source/devguide/how-to/update-1.3-plugin.md
new file mode 100644
index 000000000..837739294
--- /dev/null
+++ b/docs/source/devguide/how-to/update-1.3-plugin.md
@@ -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:
+
+
+
+
+
+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