You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
9.2 KiB
326 lines
9.2 KiB
15 years ago
|
=head1 OVERVIEW
|
||
|
|
||
|
This document describes how to write puke addons and additional
|
||
|
widgets. It assumes a good knowledge of C++, perl and X/Qt workings
|
||
|
under Linux.
|
||
|
|
||
|
=head1 1. Description and Background
|
||
|
|
||
|
=over 6
|
||
|
|
||
|
Puke's a generic protocol allowing dsirc to communicate with ksirc.
|
||
|
Communications works over a unix domain socket between multiple
|
||
|
client dsirc process's to a single ksirc process. All communications
|
||
|
is done via a variable length message with the following layout:
|
||
|
|
||
|
=begin text
|
||
|
|
||
|
struct PukeMessage {
|
||
|
unsigned int iHeader;
|
||
|
int iCommand;
|
||
|
int iWinId;
|
||
|
int iArg;
|
||
|
int iTextSize;
|
||
|
char *cArg;
|
||
|
}
|
||
|
|
||
|
=end text
|
||
|
|
||
|
None of the fields except for iCommand, iWinId and iHeader have any restrictions
|
||
|
on their content and may contain arbitrary values. iCommand and iWinId must
|
||
|
contain an int and it used by ksirc to determine the destination and
|
||
|
handler of the actual command. (and of course it's meaning). iHeader is a
|
||
|
fixed pattern used to identify the start of a header message should it loose
|
||
|
syncronization. The current pattern used it 2863311530, which is:
|
||
|
10101010101010101010101010101010.
|
||
|
|
||
|
=item Internal handling by kSirc:
|
||
|
|
||
|
Messages are received by a generic handler, PukeController where the message
|
||
|
is passed to the iWinId's messageDipatcher for final processing. The
|
||
|
iWinId of 1 through 10 are reserved for internal use, and 1 is
|
||
|
currently set at the window ID for the PukeController itself.
|
||
|
|
||
|
Connect a signal to PukeControllers writeBuffer (signal's generally
|
||
|
called outputMessage) and pass the fd and PukeMessage to be sent to
|
||
|
the client. No parsing of the output message is done.
|
||
|
|
||
|
=item Internal handling by dsirc:
|
||
|
|
||
|
All received messages are handled by an internal callback methods. 3
|
||
|
sets of callbacks are checked for handlers in the following order:
|
||
|
|
||
|
$PUKE_HANDLER{$cmd}{$winid}
|
||
|
$PUKE_HANDLER{-$cmd}{$winid}
|
||
|
$PUKE_DEF_HANDLER{$cmd}
|
||
|
|
||
|
If no handler is found an error is printed.
|
||
|
|
||
|
Output is handled by the PukeSendMessage function. PBase defines an
|
||
|
alternate routine sendMessage which should be a lot friendlier.
|
||
|
|
||
|
=head1 2. How to create a new widget
|
||
|
|
||
|
There are 2 parts to creating a widget, the C++ code and the
|
||
|
supporting perl5-oop object.
|
||
|
|
||
|
=head2 2.1 C++ Widget code
|
||
|
|
||
|
The C++ code must be able to hand all required settings and messages
|
||
|
for the widget. Each new widget iherites it's parent and so forth
|
||
|
allowing for a nice oop layout. The widget structure the author is
|
||
|
following is the same as Qt's. Their seems to work well, why
|
||
|
re-invent the wheel?
|
||
|
|
||
|
=item 2.1.1 General Layout, etc
|
||
|
|
||
|
Figure where your new widget goes in the heirachy. If it's a simple
|
||
|
Qt widget, I recommend using their existing layout. Man pages list
|
||
|
what widgets inherit what.
|
||
|
|
||
|
The general idea behind the widget layout should be to be to provide
|
||
|
all the functionality of the widget to dsirc perl script. Incoming
|
||
|
messages are handled via the messageHandler and ALL messages should
|
||
|
return an ACK with current state info.
|
||
|
|
||
|
New widgets are created as shared objects and loaded on the fly. This
|
||
|
means you don't need to recompile ksirc to use new widgets etc.
|
||
|
|
||
|
Generally you'll have to inherit PWidget at a minimum.
|
||
|
|
||
|
Functions you HAVE TO overrite:
|
||
|
|
||
|
B<1. createWidget>
|
||
|
|
||
|
This function creates a new widget of your type and returns a
|
||
|
*PWidget.
|
||
|
|
||
|
B<2. messageHandler>
|
||
|
|
||
|
This function receives ALL your commands.
|
||
|
|
||
|
B<3. widget() and setWidget(YourWidget *)>
|
||
|
|
||
|
These set and return your widget.
|
||
|
|
||
|
|
||
|
If you care about inheritance, which you should, all these functions
|
||
|
should be virtual. (Since we are using pointers to PWidget's
|
||
|
everywhere, it's a good bet you want your children's overriden
|
||
|
functions called, not yours)
|
||
|
|
||
|
The structure internally will have to hold a local copy of the widget,
|
||
|
and connect to it's destroy signal so you can know when it has been
|
||
|
destroyed.
|
||
|
|
||
|
=item 2.1.2 createWidget
|
||
|
|
||
|
createWidget is defined as:
|
||
|
|
||
|
PWidget *createWidget(widgetId *pwi, PWidget *parent);
|
||
|
|
||
|
It is called everytime a new widget of yours is required. The
|
||
|
widgetId will be the identifier for your widget and must be kept for
|
||
|
all future commands. PWidget::setWidgetId(pwi) should be called to
|
||
|
set the widget id. The *parent is the parent of the current widget.
|
||
|
Generally PWidget->widget() is passed to the contructor of your
|
||
|
widget. If it's 0, there is no parent. Simeplfied code for a the
|
||
|
PFrame is:
|
||
|
|
||
|
=begin text
|
||
|
|
||
|
extern "C" {
|
||
|
PWidget *createWidget(widgetId *pwi, PWIdget *parent);
|
||
|
}
|
||
|
|
||
|
PWidget *createWidget(widgetId *pwi, PWIdget *parent){
|
||
|
QFrame *f;
|
||
|
PFrame *pf = new PFrame(parent);
|
||
|
if(parent != 0){
|
||
|
f = new QFrame(parent->widget());
|
||
|
}
|
||
|
else{
|
||
|
f = new QPFrame();
|
||
|
}
|
||
|
pf->setWidget(f);
|
||
|
pf->setWidgetId(pwi);
|
||
|
return pf;
|
||
|
}
|
||
|
|
||
|
=end text
|
||
|
|
||
|
Note: you have to check parent for null since calling NULL->widget()
|
||
|
results in Bad Things (tm).
|
||
|
|
||
|
=item 2.1.3 messageHandler
|
||
|
|
||
|
This receives all commands, etc. It should process required commands,
|
||
|
if a command is unkown pass it to the parent. PFrame example:
|
||
|
|
||
|
=begin text
|
||
|
|
||
|
class PFrame : public PWidget
|
||
|
...
|
||
|
void messageHandler(int fd, PukeMessage *pm);
|
||
|
...
|
||
|
|
||
|
void PFrame::messageHandler(int fd, PukeMessage *pm) {
|
||
|
PukeMessage pmRet;
|
||
|
switch(pm->iCommand){
|
||
|
case PUKE_QFRAME_SET_FRAME:
|
||
|
widget()->setFrameStyle(pm->iArg);
|
||
|
pmRet.iCommand = PUKE_QFRAME_SET_FRAME_ACK;
|
||
|
pmRet.iWinId = pm->iWinId;
|
||
|
pmRet.iArg = widget()->frameStyle();
|
||
|
pmRet.cArg[0] = 0;
|
||
|
emit outputMessage(fd, &pmRet);
|
||
|
break;
|
||
|
default:
|
||
|
PWidget::messageHandler(fd, pm);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
=end text
|
||
|
|
||
|
=item 2.1.4 widget and setWidget
|
||
|
|
||
|
Both these functions should be overriden and return your widget type,
|
||
|
and set your widget. For setWidget you should connect required
|
||
|
signals and eventFilters you are using.
|
||
|
|
||
|
Make sure to call the parents setWidget in setWidget so it can connect
|
||
|
filters etc.
|
||
|
|
||
|
BEWARE: You might get the widget into setWidget being null (from the
|
||
|
destructor).
|
||
|
|
||
|
Another PFrame example (APE ;) ):
|
||
|
|
||
|
=begin text
|
||
|
void PFrame::setWidget(QFrame *_f)
|
||
|
{
|
||
|
frame = _f;
|
||
|
PWidget::setWidget(_f);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
QFrame *PFrame::widget()
|
||
|
{
|
||
|
return frame;
|
||
|
}
|
||
|
|
||
|
=end text
|
||
|
|
||
|
=item 2.1.5 Destructor
|
||
|
|
||
|
Ok, unfortunaly since we have this internal widget floating arround
|
||
|
the destructor has to a little maigc.
|
||
|
|
||
|
Call the destructor as such:
|
||
|
|
||
|
delete widget();
|
||
|
setWidget(0);
|
||
|
|
||
|
This will clear the widget from now and all parents and delete it.
|
||
|
you never want it deleted twice. (deleting 0 won't hurt)
|
||
|
|
||
|
=head2 2.2 The Perl code
|
||
|
|
||
|
Most of the perl oop is pretty straight forward, command simply issue
|
||
|
a require sendMessage and off everything goes. There's one problem.
|
||
|
|
||
|
You can't get information back on the current read cycle. Huh? I can
|
||
|
hear most people saying. It means say someone wanted to do $widget =
|
||
|
$widget->height() and you didn't have the height information locally,
|
||
|
there's no way to get the information and return it to them. Why? You
|
||
|
issue a sendMessage(...) but until dsirc returns to the main select()
|
||
|
loop, we never know there's more to read. We can't return to the main
|
||
|
select loop until we return from our current function. What does this
|
||
|
mean? We have to store all the information locally.
|
||
|
|
||
|
This also brings up another intresting aspect. Sometimes a widget may
|
||
|
depend on a prior command before it can complete. This is the purpose
|
||
|
of canRun function, and onNext. It's use will have to be explained
|
||
|
latter.
|
||
|
|
||
|
To help with this problem, pbase.pm sets up a fairly complicated set
|
||
|
of message and event queues. Be warned when you issue a sendMessage,
|
||
|
it might not get sent right away.
|
||
|
|
||
|
I'll provide example bellow of how I've done certain functions, this
|
||
|
is certainly not the only way to do it. Feel free to use any format
|
||
|
you like aslong as it get's the job done.
|
||
|
|
||
|
Ok, so how do we do this?
|
||
|
|
||
|
=item 2.2.1 Perl oop? where do I start?
|
||
|
|
||
|
Read the perltoot and perlobj man pages.
|
||
|
|
||
|
=item 2.2.2 What to inherit etc.
|
||
|
|
||
|
You probably want to inherit the same object as your C function does.
|
||
|
At very least you'll want to inherit PWidget.
|
||
|
|
||
|
=item 2.2.3 new and DESTROY
|
||
|
|
||
|
Your new function should look something like (APE?):
|
||
|
|
||
|
=begin text
|
||
|
|
||
|
sub new {
|
||
|
my $class = shift;
|
||
|
my $self = $class->SUPER::new($class, @_);
|
||
|
|
||
|
$self->{widgetType} = $::PWIDGET_FRAME;
|
||
|
|
||
|
if($class eq 'PFrame'){
|
||
|
$self->create();
|
||
|
}
|
||
|
|
||
|
return $self;
|
||
|
}
|
||
|
|
||
|
=end text
|
||
|
|
||
|
$self is the blessed variable and it returned from the super class.
|
||
|
You should always do it this way. Setting widgetType defines the type
|
||
|
of widget, and needs to be set before calling create.
|
||
|
|
||
|
If we are creating an object of our own class we call create() which
|
||
|
acutally sends out the correct creation messages, etc. You can
|
||
|
override the create function, but do be warned it might not be a good
|
||
|
idea. Make sure you understand what and how it does it first!
|
||
|
|
||
|
=item 2.2.4 sendMessage
|
||
|
|
||
|
sendMessage is the main form of communicating with kSirc. Generable
|
||
|
arguments are:
|
||
|
|
||
|
=begin text
|
||
|
|
||
|
sendMessage('iCommand' => command number,
|
||
|
'iArg' => interger argument,
|
||
|
'cArg' => character string,
|
||
|
'CallBack' => pointer to sub, generally sub{...}
|
||
|
);
|
||
|
|
||
|
=end text
|
||
|
|
||
|
You'll note it's a hash so order doesn't count. The callback is a 1
|
||
|
shot call back (should be) so don't count on it getting hit twice.
|
||
|
The call back's first argument will be a \%ARGS with the return
|
||
|
arguments.
|
||
|
|
||
|
=item 2.2.5 Final notes on perl
|
||
|
|
||
|
Most of the function calls will just send out messages, etc. For call
|
||
|
backs the general form I've found works well is: sub {$self->blah()}.
|
||
|
|
||
|
=head1 3. Interfacing with the kSirc's main windows and functions
|
||
|
|
||
|
NOT implemented yet.
|
||
|
|
||
|
=back
|