-
Notifications
You must be signed in to change notification settings - Fork 22
HowTo: Porting to a new panel
This guide will explain the steps to port Multiload-ng to an unsupported panel.
I tried to make this process as easy as possible. I will continue to improve the code to make it even easier. However, I understand that there are a lot of things to consider when making a port of something. So I think this guide is aimed to intermediate users. Beginners may find hard to even read this.
Just follow these steps and (hopefully) you will have your multiload-ng running in your panel (as long your panel support external plugins! With an extra effort I think it could be possible to compile this plugin inside a panel without external plugin support)
Actual steps may be different from panel to panel. You are encouraged to look at the source of other successful ports while reading this guide. In order to fully understand this document, you should read it alongside a working piece of code.
I suggest studying lxpanel plugin, the most simple and the one with fewer lines of code.
Here is what's required to build Multiload-ng and port to a new panel:
- Intermediate knowledge of C: pointers, callbacks, structures, etc.
- Little knowledge of GTK+
- Little knowledge of GObject signals/callback mechanism
- Little knowledge of Makefile syntax
- Little knowledge of Autotools, in particular of AC_ macros (configure.ac)
The more you know, the better, of course. But most of the times you can just copy-paste the examples of this tutorial.
If you's like to learn about some of above subjects, a simple Google search can go a long way.
You have to install Autotools (automake, autoconf, etc) and gcc compiler. Chances are that you already have these.
- Source-based distros (like Gentoo) and those targeted to "power users" (Arch, Slackware) have those by default.
- Debian/Ubuntu users will need to execute in terminal:
sudo apt-get install build-essential
. - Other distros may have different behaviors (refer to the Wiki of your Linux distribution).
Note: here I am assuming that the panel (and the plugins that already exist) is written in C. This is almost always true. Things are a little different if the panel is not written in C, in that case some parts of this guide could not be so helpful.
This could take a while, depending on how much luck you have.
You will have to get all the documentation that you can find about the panel and the plugin code.
Here a quick list of places where you can search:
- If you are lucky enough, panel developers made a web page for this purpose (this is the case of LxPanel)
- Look into the documentation of the panel, e.g. subdirectory doc in the source or in installed content
- Look into the source of the panel: there is sometimes an examples subdirectory
- The examples can also be outside the source: just use Google to find examples or tutorials online
- Many panels ship with a dummy plugin, which is purposely simple and does nothing. It exists just to be examined.
- XFCE: in XFCE wiki there is a web page with necessary informations.
- LXDE: in LXDE wiki there is a web page with necessary informations.
- MATE: developers mantain the mate-university repository at GitHub, containing full source of simple applets with commented code.
You have to find any dependency needed to build an empty package. Track them down, we will need it later.
They can be found in the documentation, if present, else you can usually find them in Makefile or configure script of the example source code.
At this point you should have sufficient elements to proceed to the actual plugin writing. Let's code!
It's time to get a local copy of source code. git is the preferred method of doing that, but it's not mandatory.
Open terminal in a directory of your choice, and type:
git clone https://github.com/udda/multiload-ng
Download a ZIP containing the source here. Unpack in a directory of your choice.
If you already found the source of an empty (dummy) plugin for your panel, this step will take very little time.
I can't help much here, as the actual procedure varies broadly from code to code. You have to figure out yourself how to practice the following hints.
Go to the directory where you have downloaded/cloned Multiload-ng source. Make a subdirectory called with the name of the panel you wish to port to.
Until I find a cleaner way, you have to edit the file configure.ac found in the source root directory. Look into it, there are some lines of code that are duplicated for each existing port. Duplicate them once again, of course changing the name of the panel (subdirectory) with the one you are porting. I will change this to a modular approach sooner or later.
This file is included from main configure.ac, if you added relevant lines to it (see above). Put in it any dependency you found in step 2. Look at configure.m4 in other directories for example.
This file is used by automake to generate a Makefile.
In the panel directory you created before, create an empty Makefile.am and fill with code from Makefile.am from another port. I suggest copying lxpanel's Makefile.am because it's the most simple. Obiviously you have to change references to lxpanel with the new name. If needed by the plugin, add other automake definitions here.
You have to write a very simple plugin for your panel. Put the source in the panel directory you created before.
The only requirement is that it must be prepared to contain a single GtkWidget (the actual plugin, we will see this later), nothing else.
By now, instead of the plugin, put a GtkLabel with some text, you will need it to test whether everything works.
If you can, to keep things simple, try to keep everything into a single source file.
This is the hardest part. Through trial and error, try to build and install your empty plugin, without errors. Instructions on how to build and install are in README.
Then make sure that your plugin shows in the plugins list of the panel and, more importantly, that you can embed one in your panel. You should see in your panel the text you put in the GtkLabel.
MAKE SURE EVERYTHING WORKS BEFORE GOING TO THE NEXT STEP!
Well done! You have a working panel module. Now let's make it a Multiload-ng port!
In addition to the includes that already are in the code, add the following lines at the beginning of your plugin.c:
#include <config.h>
#include "common/about-data.h"
#include "common/multiload.h"
#include "common/ui.h"
-
config.h
is generated by configure -
common/about-data.h
contains some constants like application name, authors, website, and so on -
common/multiload.h
contains definitions of Multiload-ng related structures and settings -
common/ui.h
contains prototypes of some functions you will use
Every GTK panel plugin has some signals (like size-changed, orientation-changed, destroy, etc). You need to connect to a callback at least the ones related to size/orientation change. This has to be done in plugin constructor. Here is an example:
g_signal_connect (G_OBJECT (plugin), "size-changed", G_CALLBACK (my_size_changed_cb), arg);
- The first argument is the object which receives the signal (usually the plugin)
- The second argument is the signal itself (in this case is the change of panel size - which in turn causes the change of plugin size)
- The third argument is the callback function that will be executed every time the plugin receives the signal (arg #2)
- The fourth argument is the argument to pass on the callback function. It can be any pointer, usually it is the multiload widget.
Note: the callback prototype changes with signal and with plugin API. Refer to panel documentation to learn how to write callbacks for specific signals for your panel (or just look at panel example code, if you found some).
There are some functions that wraps advanced functionalities on a single line of code. You will probably want to use them in your implementation. They are defined in "common/ui.h".
// Reads user settings
void multiload_ui_read (MultiloadPlugin *ma);
// Save user settings (you usually don't need to call this directly,
// as it's called automatically by configure dialog when closed)
void multiload_ui_save (MultiloadPlugin *ma);
// Shows help, probably a web page
void multiload_ui_show_help();
// Shows about dialog
void multiload_ui_show_about (GtkWindow* parent);
// Returns a GtkDialog* with preferences dialog. Show this with gtk_widget_show().
GtkWidget* multiload_ui_configure_dialog_new (MultiloadPlugin *ma, GtkWindow *parent);
// Starts system monitor. Automatically finds the one installed among a list of well-known system monitors.
void multiload_ui_start_system_monitor(MultiloadPlugin *ma);
There is some code (few lines) that MUST be put in the plugin constructor.
These generate Multiload widget, start the timers and load user preferences.
The returned GtkWidget can then be embedded in your plugin.
MultiloadPlugin *multiload = multiload_new ();
This line creates the plugin. Must be called before any other multiload function. Put this as early as you can in the constructor code.
Embed the GtkWidget* multiload->container
in your plugin, in the place where you put the GtkLabel before. This container alone holds all Multiload-ng inner widgets and structures, you don't have to worry about those.
multiload_ui_read (multiload);
This reads user settings. If it's the first run it will load default settings. The argument of this function is the MultiloadPlugin*
you created in the lines above.
multiload_start(multiload);
This starts the timers and the drawing routines. After that line, multiload-ng is up and running.
Structure MultiloadPlugin has a general purpose field that holds a pointer:
void* multiload->panel_data
This field is not used by Multiload-ng, it's there to be used by ports (like yours). You can use it to store persistent data, like the panel handle or the settings object. Once again, look how lxpanel port is implemented.
You MUST implement ALL these functions. These are used by "common/ui.h" to provide one-line function calls to complex operations. Plugin will segfault sooner or later if you don't implement all of these.
If one or more functions are not required for your implementation, just write an empty body if void, or a return TRUE
if gboolean. Again, look at the already existing implementations (lxpanel is the most simple one).
Most of the functions are related to load/save of preferences. This extra level of abstraction is needed because every panel uses different backends for settings (e.g. XFCE and LXDE use internal functions that write plain-text files, MATE uses GSettings, and so on). This approach avoids the need to write the same load/save function many times.
// Returns a pointer to settings object (panel-specific). Called by multiload_ui_read().
gpointer multiload_ps_settings_open_for_read(MultiloadPlugin *ma);
// Returns a pointer to settings object (panel-specific). Called by multiload_ui_save().
gpointer multiload_ps_settings_open_for_save(MultiloadPlugin *ma);
// Save settings and return TRUE if succeeds. Called by multiload_ui_save().
gboolean multiload_ps_settings_save(gpointer settings);
// Closes settings object. Called by multiload_ui_read() and multiload_ui_save().
void multiload_ps_settings_close(gpointer settings);
// Reads integer value into *destination, given key. Called by multiload_ui_read();
void multiload_ps_settings_get_int(gpointer settings, const gchar *key, int *destination);
// Reads boolean value into *destination, given key. Called by multiload_ui_read();
void multiload_ps_settings_get_boolean(gpointer settings, const gchar *key, gboolean *destination);
// Reads string value into *destination, given key. Called by multiload_ui_read();
void multiload_ps_settings_get_string(gpointer settings, const gchar *key, gchar *destination, size_t maxlen);
// Sets integer value in given key. Called by multiload_ui_save();
void multiload_ps_settings_set_int(gpointer settings, const gchar *key, int value);
// Sets boolean value in given key. Called by multiload_ui_save();
void multiload_ps_settings_set_boolean(gpointer settings, const gchar *key, gboolean value);
// Sets string value in given key. Called by multiload_ui_save();
void multiload_ps_settings_set_string(gpointer settings, const gchar *key, const gchar *value);
// When preferences dialog is closed, it first calls multiload_ui_save(), then this callback function.
// Use this, for example, to unblock menu, or reactivate disabled widgets.
void multiload_ps_preferences_closed_cb(MultiloadPlugin *ma);
Before proceeding, build again your code (build instructions in README). If you encounter some errors, fix them.
Now install the plugin, and see whether it works, like you did in Step 4. A blue CPU graph should move over your panel!
When this happens, you are ready to move to next step. We are almost done.
Your plugin port works, congratulations! You can now relax and take care of less critical parts of the plugin, to make it "perfect".
Some plugins allow to add custom menu items when user right click on the plugin. If it's possible, it's recommended to add the following menu items:
- Configure
- Help
- About
- Start system monitor
Add required callbacks to "activate" signal of menu items.
Check in panel documentation which signals a plugin can receive. Connect to a callback those you find suitable.
If one or more of your newly created files contain translatable strings, you should include them to po/POTFILES.in
.
Build against new changes, install and test, like you did before (you did, right?). If everything is OK, proceed to final code modifications.
This step is optional but highly recommended. Now that your code works fine, it's time to clean it up.
Why should you do that? After all, the code is just OK! Well, actually there are a number of reasons:
- Dramatically increase readability of the code
- Make it easier to spot bugs/mistakes in the code, saves you from painful hours of debugging
- If you ever decide to send a patch/pull request, it will be more likely to be accepted (this is true for EVERY project)
- It's a VERY GOOD habit that you should always exercise
- Try to keep your files as small as possible
- Use proper and consistent indentation/brackets style
- Comment obscure code
- When possible, keep all the source together in a single, tidy C file
- Minimize the number of external files
- Avoid hardcoded constants
- Follow general coding standards
Congratulations! You completed the guide! I hope you are happy with your brand new plugin for your panel. I am happy with you.
I would appreciate very much if you would share your precious work with me, so I can integrate in the main source.
You can send a patch to [email protected], or make a pull request.
Don't worry if you think your code it's not "pretty" or if it does not work properly: you can even text me with incomplete code, "hints" on how to proceed for a specific panel, or just port requests. I will read and analyze all of them.
If your code passes every test, I will eventually include it into main source. I could edit your code (to fix bugs, add features or make coding style more consistent) but don't worry, your credits will remain intact. Your name will always show up in the first lines of source files.
Best luck!