## Archive for the ‘Programming’ Category

### Writing Konqueror-Plugins with Ruby

Monday, March 7th, 2011

Hi!

You may not know about it: But you can write Konqueror-plugins, and you can use a scripting-language like Ruby. KDE/KParts/Konqueror have a very nice plugin-system, you can write plugins using C++, but you can also use a scripting-language like Ruby or Python. It should even work with Perl (untested), unfortunately for KJS and QtScript there is no plugin-factory implementing the support. Well, let us have a look at a simple plugin written in Ruby. There are four important files:

#### A desktop-file containing some meta-data

It works like a normal plugin-desktop-file (see e.g. /usr/share/kde4/apps/khtml/kpartplugins/plugin_adblock.desktop), but you need two special lines:

X-KDE-Library=krubypluginfactory
X-KDE-PluginKeyword=MyPlugin/MyPlugin.rb

Well, X-KDE-PluginKeyword is a not very expressive name for the name of the script to use (because it may be used for other stuff as well), but what is krubypluginfactory? It is a very cool invention, like the usual KDE-plugin-factories it instantiates plugin-objects, but this one instantiates special objects, were virtual method- and slot-invocations will be delegated to Ruby.

As usual you can use X-KDE-ParentApp (in our case konqueror) to tell KParts not to load the plugin in other applications using the KPart.

#### A .rc-GUI-XMl-file

You may have heard about KXmlGui – you need it to define actions in the menubar etc., but you will even need it if you do not access any GUI-elements, otherwise Konqueror (or any application using KParts-plugins) will not find it:

<!DOCTYPE kpartgui>
<kpartplugin name="MyPlugin" library="krubypluginfactory" version="0.1">
<separator group="tools_operations" />
<Action name="tools_MyPlugin" group="tools_operations" />
</kpartplugin>

#### A CMake-file (CMakeLists.txt)

project(myplugin)
find_package(KDE4 REQUIRED)
include (KDE4Defaults)
install(PROGRAMS MyPlugin.rb DESTINATION ${DATA_INSTALL_DIR}/MyPlugin) install(FILES myplugin.rc myplugin.desktop DESTINATION${DATA_INSTALL_DIR}/khtml/kpartplugins)

That one is really trivial, some elementary checks, then it will be installed to the correct directories, Ruby is an interpreted language.

#### The Ruby-Code

The name of the Ruby-class has to match the filename, otherwise KRubyPluginFactory will not be able to find it (KPythonPluginFactory works differently, there you have to create a separate function to instantiate the plugin-object). Like normal plugins they have to inherit KParts::Plugin, the constructor can take a parent-object (that is the KParts::Part-instance), a parent-widget and a list of arguments (which will usually be empty). There it can initialize the actions declared in the XML-file and add signal-slot-connections such that something will happen. What should happen in my example-plugin? It will find bad-links to sites of evil companies (Microsoft, Apple, SAP, Oracle, Facebook, Google, but the list can be easily extended to your own moral standards :D), and when it finds such a link, it will get angry and wil replace all the links on the website with good links, e.g. to Planet KDE, GNU, Wikipedia or the-user.org. That for it uses the DOM-API exposed by KHTML.

#!/usr/bin/env ruby
# Qt
require 'Qt'
# KDE-libraries
require 'korundum4'
# KHTML
require 'khtml'
# Ruby-CGI-library (for url-handling)
require 'cgi'

include KDE
include Qt

module MyPlugin # has to match the directory-name

class MyPlugin < KParts::Plugin # has to match the file-name
# Slot-Declarations look that way, “a bit” C++-ish
slots 'toggle(bool)', 'hoverUrl(const QString&)'
def initialize(parent, parentWidget, args)
super(parent)

if !parent.is_a? KDE::HTMLPart
qWarning("MyPlugin: Not a KHTML-Part")
return
end
@part = parent

#
@action.checkable = true
# the action declared in the XML-file

# unfortunately you have to provide parameter lists
connect(@action, SIGNAL('toggled(bool)'),
self,    SLOT('toggle(bool)'))

"http://the-user.org",
"http://gnu.org",
"http://en.wikipedia.org"]
end
def toggle(active)
if active
connect(@part, SIGNAL('onURL(const QString&)'),
self,  SLOT('hoverUrl(const QString&)'))

# normal Qt API
Qt::MessageBox::information(nil, "Success!",
"Now you are safe, you will only visit good sites!")
else
disconnect(@part, SIGNAL('onURL(const QString&)'),
self,  SLOT('hoverUrl(const QString&)'))
Qt::MessageBox::information(nil, "Success!",
end
end
# replace all the links recursively using the DOM-API
href = DOM::DOMString.new("href")
if     dom.nodeName.string == "A"
&& dom.attributes.getNamedItem(href) != nil
oldUrl = dom.attributes.getNamedItem(href).nodeValue.string
+ "/#" + CGI::escape(oldUrl)
dom.attributes.getNamedItem(href).nodeValue
= DOM::DOMString.new(newUrl)
end

# recursion
children = dom.childNodes
for i in 0..(children.length-1)
child = children.item(i)
end
end
# slot invoked when hovering a link
def hoverUrl(url)
# this will happen when you move the cursor away
return if url == nil

found = false
sharpIndex = url.index("#")
if pos != nil && (sharpIndex == nil || pos < sharpIndex)
found = true
break
end
end

# maybe the link is not evil
return unless found

end