Re: [Geany-Devel] My non-C plugin roadmap

2015-03-30 Thread Thomas Martitz

Am 30.03.2015 um 14:33 schrieb Lex Trotman:



What is is_plugin()? If its a function in Geany how does it get to
know about new types of plugins without being hard coded?

It knows because the extensions are registered by the function below
called by another plugin right?  How do you make sure that the
register function has been called before you come across a file with
that extension?



For now, I kept it simple: Geany simply restarts the scan for plugins 
loop when new extensions are added during the the scan (remember that 
during the scan, each file is loaded and its init() is called, before 
the next file is even attempted). The PM dialog is refreshed in the same 
way when a pluxy is activated by the user.


It can be made smarter, but it's good enough at the moment.

https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L878 and 
https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1272






It's new, small helper function I added. It loops through all known file
extensions, and returns the first pluxy (a PluginProxy *) for which a) the
supported file extension matches and b) the probe hook returned true (or is
NULL, for standard plugins).
$file is not a plugin if is_plugin returns NULL, i.e. no pluxy was found.

https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L844


File extensions and the proxy hooks (probe, load, unload) are registered by
a plugin during its init() through the new plugin_register_proxy() function.

Ok, so this registers a new type of plugin by its extension(s) and
that type is associated with a plugin that provides:

- the loader functionality?
- interface wrappers/bindings (like geanypy does)?
- starts/loads any other things, like the Python interpretor or a JVM
or Haskell runtime?



Yes, but the 2nd and 3rd points are entirely up to the pluxy. My 
demopluxy.so doesn't do anything fancy. it creates a dummy plugin out of 
a GKeyFile.


But the loader/unloader function is mandatory, it also acts as the entry 
point for pluxies to start their bindings/vm machinery if necessary.






Here the pluxy added to the list of registered pluxies. This list is
initialized with the simulated pluxy that provides standard plugins (this is
not a plugin, it's contained in plugins.c, it's just to keep the code paths
equal).

https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1601


which matches
additional file extensions (as provided by pluxies), it also calls the
probe() hook to resolve ambiguous files (e.g. .so files, they can be core
or
libpeas plugins)

I'm guessing probe() is a function that looks for something in the .so
that distinguishes if its new or old loader, but what about others?



It depends on the pluxy what the prope() function does! For my peasy pluxy
(that provides generic support for libpeas-based plugins), it looks if there
is a matching *.plugin for a given *.so, and if yes return a code so that
Geany does not attempt to process the .so itself.

https://github.com/kugel-/peasy/blob/master/src/peasy.c#L73

There is no probe() for standard plugins, it accepts all .so.

If it doesn't call probe how does it know if its a traditional plugin,
or a peas one or maybe some other version that makes a .so file?
(Haskell anybody :)


Geany doesn't and cannot know it's a peas plugin, though it could be 
smarter at determining it's a standard plugin. But I currently 
implemented a scheme where the first pluxy that matches wins, but if it 
doesn't match all other pluxes are tried. Geany itself is tried last.


This should work for all real-world cases, even when there are multiple 
(more than 2) providers for .so files. If the pluxies are accurate 
enough at determining their own filetypes then no conflicts arise.


Geany is always tried last, so if it gets to process a .so file, then it 
assumes it's a standard plugin like it's done in git master (no change 
here)




In the load hook of the pluxy. Either the pluxy calls it directly or it
decides to provide a suitable binding so that the non-C script can call it
itself, but it has to be during the execution of the load hook.

Ok, so if there are all these hooks, probably later the traditional
plugins can be just another pre-registered set of hooks built into
Geany, but I agree with your approach of not trying to do that all in
the first step, leave the existing code as little changed as possible
until the new system settles down and then change the existing code to
use it.



It's done this way already, the hooks are compiled into Geany. As I said 
the list of pluxies is initialized with a simulated one that provides 
the standard plugins { .extension = { so, NULL }, .probe = NULL, .load 
= plugin_load_so, .unload = plugin_unload_so };



Best regards
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel


Re: [Geany-Devel] My non-C plugin roadmap

2015-03-30 Thread Lex Trotman
[...]

Ok, this explains some of what I was asking on the other thread, so
now I can ask the more specific questions below that are the key
points in the confusion.

 with my new loader (no pluxies) it goes like this, and this is *very*
 similar to git master.

 user opens PM dialog
 1 Geany calls load_all_plugins(), which calls load_plugins_from_path($path)
 2 for each $file in $path, Geany checks if the extension is G_MODULE_SUFFIX,
 and calls plugin_new($file, ...)
 3 plugin_new() calls the plugins's  geany_load_module() (if new-style
 plugin, calls version_check, set_info() for old-style ones)
 4 geany_load_module() is implemented by the plugin, and registers itself
 with geany_plugin_register()
  geany_plugin_register() adds the plugin to the plugin list, so that the PM
 can sort and show it

 Now, with pluxies, it is completely the same except for:
 2* for each $file in $path, Geany calls is_plugin($file)

What is is_plugin()? If its a function in Geany how does it get to
know about new types of plugins without being hard coded?

 which matches
 additional file extensions (as provided by pluxies), it also calls the
 probe() hook to resolve ambiguous files (e.g. .so files, they can be core or
 libpeas plugins)

I'm guessing probe() is a function that looks for something in the .so
that distinguishes if its new or old loader, but what about others?

 3* plugin_new() calls the load() hook registered by pluxies for the given
 extension. for standard plugins (without proxy) there is a predefined
 plugin_load_so() funtion that gets called instead.

How does the load hook get defined for new types of plugins?

 4* The load-hook calls geany_plugin_register(), here Geany core and proxies
 work the same way

Where is the geany_plugin_register() defined for a plugin written in a
language that isn't C/C++/Vala that can produce a .so file?


 I designed it such, that the difference between standard plugins and proxied
 plugins is all contained in the load hook. The rest of Geany does not know
 about the difference. This ensures proxied plugins are first class citizens.


Thats the correct target I agree, I just don't understand the design
details yet.

Cheers
Lex
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel


Re: [Geany-Devel] My non-C plugin roadmap

2015-03-30 Thread Thomas Martitz

Hello,

some if your question are easier to answer by looking at the code, I'll 
link the appropriate sections.



Am 30.03.2015 um 13:16 schrieb Lex Trotman:

[...]

Ok, this explains some of what I was asking on the other thread, so
now I can ask the more specific questions below that are the key
points in the confusion.


with my new loader (no pluxies) it goes like this, and this is *very*
similar to git master.


user opens PM dialog

1 Geany calls load_all_plugins(), which calls load_plugins_from_path($path)
2 for each $file in $path, Geany checks if the extension is G_MODULE_SUFFIX,
and calls plugin_new($file, ...)
3 plugin_new() calls the plugins's  geany_load_module() (if new-style
plugin, calls version_check, set_info() for old-style ones)
4 geany_load_module() is implemented by the plugin, and registers itself
with geany_plugin_register()
 geany_plugin_register() adds the plugin to the plugin list, so that the PM
can sort and show it

Now, with pluxies, it is completely the same except for:
2* for each $file in $path, Geany calls is_plugin($file)

What is is_plugin()? If its a function in Geany how does it get to
know about new types of plugins without being hard coded?



It's new, small helper function I added. It loops through all known file 
extensions, and returns the first pluxy (a PluginProxy *) for which a) 
the supported file extension matches and b) the probe hook returned true 
(or is NULL, for standard plugins).

$file is not a plugin if is_plugin returns NULL, i.e. no pluxy was found.

https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L844


File extensions and the proxy hooks (probe, load, unload) are registered 
by a plugin during its init() through the new plugin_register_proxy() 
function. Here the pluxy added to the list of registered pluxies. This 
list is initialized with the simulated pluxy that provides standard 
plugins (this is not a plugin, it's contained in plugins.c, it's just to 
keep the code paths equal).


https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1601




which matches
additional file extensions (as provided by pluxies), it also calls the
probe() hook to resolve ambiguous files (e.g. .so files, they can be core or
libpeas plugins)

I'm guessing probe() is a function that looks for something in the .so
that distinguishes if its new or old loader, but what about others?



It depends on the pluxy what the prope() function does! For my peasy 
pluxy (that provides generic support for libpeas-based plugins), it 
looks if there is a matching *.plugin for a given *.so, and if yes 
return a code so that Geany does not attempt to process the .so itself.


https://github.com/kugel-/peasy/blob/master/src/peasy.c#L73

There is no probe() for standard plugins, it accepts all .so. Whether 
it's a new or old style plugin is determined later. It *could* be in a 
probe() hook for standard plugins as well, I just didn't happen to 
implement it that way (yet), because plugin_load_so needs to distinguish 
between the two anyway.


https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L472




3* plugin_new() calls the load() hook registered by pluxies for the given
extension. for standard plugins (without proxy) there is a predefined
plugin_load_so() funtion that gets called instead.

How does the load hook get defined for new types of plugins?


Via the new API function plugin_register_proxy().
https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1601




4* The load-hook calls geany_plugin_register(), here Geany core and proxies
work the same way

Where is the geany_plugin_register() defined for a plugin written in a
language that isn't C/C++/Vala that can produce a .so file?


In the load hook of the pluxy. Either the pluxy calls it directly or it 
decides to provide a suitable binding so that the non-C script can call 
it itself, but it has to be during the execution of the load hook.


The demopluxy does it in the load() hook, right after parsing the 
metadata of the example plugin (I completely made up a fake plugin 
format for demonstration purposes):


https://github.com/kugel-/geany/blob/pluxy/plugins/demopluxy.c#L169

I have done it the same way for peasy too, because libpeas plugins can 
be python or js, and I didn't create bindings yet. But it shows working 
this way.


https://github.com/kugel-/peasy/blob/master/src/peasy.c#L130





I designed it such, that the difference between standard plugins and proxied
plugins is all contained in the load hook. The rest of Geany does not know
about the difference. This ensures proxied plugins are first class citizens.


Thats the correct target I agree, I just don't understand the design
details yet.



Thanks for the heads up!

Best regards
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel


Re: [Geany-Devel] My non-C plugin roadmap

2015-03-30 Thread Lex Trotman
On 30 March 2015 at 22:43, Thomas Martitz ku...@rockbox.org wrote:
 Hello,

 some if your question are easier to answer by looking at the code, I'll link
 the appropriate sections.


No problem, the explanations below about how things are meant to be
used are more help than looking at the code by itself, what is needed
is the user guide, which these explanations make a start at, don't
lose them :)


 Am 30.03.2015 um 13:16 schrieb Lex Trotman:

 [...]

 Ok, this explains some of what I was asking on the other thread, so
 now I can ask the more specific questions below that are the key
 points in the confusion.

 with my new loader (no pluxies) it goes like this, and this is *very*
 similar to git master.

 user opens PM dialog

 1 Geany calls load_all_plugins(), which calls
 load_plugins_from_path($path)
 2 for each $file in $path, Geany checks if the extension is
 G_MODULE_SUFFIX,
 and calls plugin_new($file, ...)
 3 plugin_new() calls the plugins's  geany_load_module() (if new-style
 plugin, calls version_check, set_info() for old-style ones)
 4 geany_load_module() is implemented by the plugin, and registers itself
 with geany_plugin_register()
  geany_plugin_register() adds the plugin to the plugin list, so that the
 PM
 can sort and show it

 Now, with pluxies, it is completely the same except for:
 2* for each $file in $path, Geany calls is_plugin($file)

 What is is_plugin()? If its a function in Geany how does it get to
 know about new types of plugins without being hard coded?


It knows because the extensions are registered by the function below
called by another plugin right?  How do you make sure that the
register function has been called before you come across a file with
that extension?



 It's new, small helper function I added. It loops through all known file
 extensions, and returns the first pluxy (a PluginProxy *) for which a) the
 supported file extension matches and b) the probe hook returned true (or is
 NULL, for standard plugins).
 $file is not a plugin if is_plugin returns NULL, i.e. no pluxy was found.

 https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L844


 File extensions and the proxy hooks (probe, load, unload) are registered by
 a plugin during its init() through the new plugin_register_proxy() function.

Ok, so this registers a new type of plugin by its extension(s) and
that type is associated with a plugin that provides:

- the loader functionality?
- interface wrappers/bindings (like geanypy does)?
- starts/loads any other things, like the Python interpretor or a JVM
or Haskell runtime?


 Here the pluxy added to the list of registered pluxies. This list is
 initialized with the simulated pluxy that provides standard plugins (this is
 not a plugin, it's contained in plugins.c, it's just to keep the code paths
 equal).

 https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1601


 which matches
 additional file extensions (as provided by pluxies), it also calls the
 probe() hook to resolve ambiguous files (e.g. .so files, they can be core
 or
 libpeas plugins)

 I'm guessing probe() is a function that looks for something in the .so
 that distinguishes if its new or old loader, but what about others?



 It depends on the pluxy what the prope() function does! For my peasy pluxy
 (that provides generic support for libpeas-based plugins), it looks if there
 is a matching *.plugin for a given *.so, and if yes return a code so that
 Geany does not attempt to process the .so itself.

 https://github.com/kugel-/peasy/blob/master/src/peasy.c#L73

 There is no probe() for standard plugins, it accepts all .so.

If it doesn't call probe how does it know if its a traditional plugin,
or a peas one or maybe some other version that makes a .so file?
(Haskell anybody :)

 Whether it's a
 new or old style plugin is determined later. It *could* be in a probe() hook
 for standard plugins as well, I just didn't happen to implement it that way
 (yet), because plugin_load_so needs to distinguish between the two anyway.

 https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L472


 3* plugin_new() calls the load() hook registered by pluxies for the given
 extension. for standard plugins (without proxy) there is a predefined
 plugin_load_so() funtion that gets called instead.

 How does the load hook get defined for new types of plugins?


 Via the new API function plugin_register_proxy().
 https://github.com/kugel-/geany/blob/pluxy/src/plugins.c#L1601

Ok, understand now



 4* The load-hook calls geany_plugin_register(), here Geany core and
 proxies
 work the same way

 Where is the geany_plugin_register() defined for a plugin written in a
 language that isn't C/C++/Vala that can produce a .so file?


 In the load hook of the pluxy. Either the pluxy calls it directly or it
 decides to provide a suitable binding so that the non-C script can call it
 itself, but it has to be during the execution of the load hook.

Ok, so if there are all these hooks, probably later the 

Re: [Geany-Devel] My non-C plugin roadmap

2015-03-29 Thread Thomas Martitz

Am 29.03.2015 um 05:20 schrieb Lex Trotman:

Thomas,

Thanks for that, it now makes it clearer where you are going, and
allows the individual steps to make more sense.

I agree with your four problems (shortened):

- linkage
- keybindings
- plugin context
- allow for proxies

My concern is that the solutions seem complex, though, having followed
your attempts over time to address these issues, I do understand that
some of the simple options don't actually cut it.

Will need to think on it further.



Which of these steps seem complex to you in particular? Perhaps I can 
explain more detail on those to make things clearer for you. For me, 
none of these is really that complex, both in terms of conceptual 
complexity and lines of code changed. The most complex is probably the 
pluxy part, but still not really complex IMO (it's just a bit of 
refactoring of the internal load/unload functions functions and an API 
that allows plugins to add such functions for some filetypes). If 
anything, complexity is added by maintaining backward compatibility.


My current roadmap is such that it requires the least amount of changes 
to Geany core while still enabling libpeas-based and other non-C plugins 
to co-exist with standard plugins and not be second class citizens.


Other simple options (that provide a solution for the above, i.e. not 
current geanypy) don't exist. In fact, the other solution I have tried 
brought significant changes to Geany core to support libpeas-plugins 
directly in the core. Simple isn't always best too, we should strive to 
make all of this effort future proof but at the same time maintain 
backward compatibility.



Best regards.
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel


Re: [Geany-Devel] My non-C plugin roadmap

2015-03-29 Thread Thomas Martitz

Am 29.03.2015 um 19:17 schrieb Colomban Wendling:

Le 29/03/2015 00:23, Thomas Martitz a écrit :


- New API functions to allow plugins to act as proxy plugins (pluxies).
These pluxies can then implement whatever is needed to execute code in
the in the actual plugin, like invoking an interpreter or firing up a
java vm. The pluxies call the new loader's API function on behalf of the
actual plugin. The API function to implement the pluxies is a simple
geany_register_pluxy() that, much like the normal plugin loader, that
pluxies use to pass a function pointer table which implements the
necessary hooks (probe(), load() and unload())

That's the part I'm really fuzzy about.  I really don't see why we need
this specific layer (maybe in an ideal world not bothering about how
Geany currently does it): as asked on the other thread, why do we need
anything beside geany_plugin_register() (required for everyone) and
geany_plugin_unregister() (required only when registered sub-plugins, as
the parent need to clean them up when it itself quits)?




I'll answer this question here in the hope the other thread can 
concentrate on the new loader itself. But it also kind of depends on how 
I designed the loader so I guess there will be some cross talk.


As with git master, Geany's core loader scans the plugin folder on 
startup and on opening the PM dialog. For each recognized plugin file it 
allocates a GeanyPluginPrivate and calls geany_load_module().


So this is where we come from:
- Geany initiates the scan (on behalf of the user)
- Geany has certain files it recognizes as plugins. It won't attempt 
other files.
- Geany allocates and frees the structure that holds everything it needs 
to know, this structure is opaque for actual plugins (and we need this 
to be able to extend it)


So, to support pluxies as first class citizens, I designed it such that 
Geany becomes able to scan additional files and hand the initialization 
of plugins to the proxy (instead of calling geany_load_module()). The 
idea is that Geany still performs the actual scan and builds up the 
plugin list in the PM dialog, so it's completely synchronous. Geany 
should still allocate the GeanyPluginPrivate structure for each plugin, 
for the proxied ones too. Note that proxy plugins too call the same 
geany_plugin_register() on behalf of the proxied plugins, like standard 
plugins have to too. Geany fully supervises which plugins are loaded, 
and when, so that it can provide a unified experience for the user, 
regardless of the plugin type that's behind the scenes.


Therefore my mechanism is based on plugins letting Geany know which 
files they can support, so that Geany's loader can scan for plugin files 
like ever, except it can match more types, and calls into the proxy to 
complete loading. For unloading the proxy is also called, to complete 
plugin finalization. For this, plugins need a way to provide Geany with 
the information it needs to load the additional plugins: file 
extension(s), and load/unload callbacks (and a probe, to resolve 
ambiguous file extensions).


To give a practical example:

with my new loader (no pluxies) it goes like this, and this is *very* 
similar to git master.


 user opens PM dialog
1 Geany calls load_all_plugins(), which calls load_plugins_from_path($path)
2 for each $file in $path, Geany checks if the extension is 
G_MODULE_SUFFIX, and calls plugin_new($file, ...)
3 plugin_new() calls the plugins's  geany_load_module() (if new-style 
plugin, calls version_check, set_info() for old-style ones)
4 geany_load_module() is implemented by the plugin, and registers itself 
with geany_plugin_register()
 geany_plugin_register() adds the plugin to the plugin list, so that 
the PM can sort and show it


Now, with pluxies, it is completely the same except for:
2* for each $file in $path, Geany calls is_plugin($file) which matches 
additional file extensions (as provided by pluxies), it also calls the 
probe() hook to resolve ambiguous files (e.g. .so files, they can be 
core or libpeas plugins)
3* plugin_new() calls the load() hook registered by pluxies for the 
given extension. for standard plugins (without proxy) there is a 
predefined plugin_load_so() funtion that gets called instead.
4* The load-hook calls geany_plugin_register(), here Geany core and 
proxies work the same way


I designed it such, that the difference between standard plugins and 
proxied plugins is all contained in the load hook. The rest of Geany 
does not know about the difference. This ensures proxied plugins are 
first class citizens.



I hope you better understand my concept now. Let me emphasize again that 
it's focused having relatively little impact on Geany's core or existing 
plugins so that we can smoothly transition to a new non-C plugin world 
full of joy!


Best regards
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel


Re: [Geany-Devel] My non-C plugin roadmap

2015-03-28 Thread Lex Trotman
Thomas,

Thanks for that, it now makes it clearer where you are going, and
allows the individual steps to make more sense.

I agree with your four problems (shortened):

- linkage
- keybindings
- plugin context
- allow for proxies

My concern is that the solutions seem complex, though, having followed
your attempts over time to address these issues, I do understand that
some of the simple options don't actually cut it.

Will need to think on it further.

Cheers
Lex

On 29 March 2015 at 10:23, Thomas Martitz ku...@rockbox.org wrote:
 Hello,

 I was asked to share a bit about my roadmap regarding plugins. I'll try to
 give you a better idea with this post.

 My ultimate goal is to implement a clean and maintainable way to support
 non-C plugins, preferably using existing widely used techniques. I concluded
 that libpeas[1] in conjunction with gobject-introspection can provide the
 base for this. Since Geany is not at all prepared for this I have several
 infrastructure which I do want to get merged into Geany. When Geany core is
 sufficiently setup for this, the non-C plugin enablement can happen outside
 the core, as a plugin, to stabilize there.

 So, here's the set of infrastructure changes for the core. Please let me
 stress that all of this will happen in backward-compatible manner, no
 existing plugins break as part of this.

 - linkage-cleanup (PR#429) - This changes the way plugins access Geany API
 functions. Instead of exporting a pointer to a struct of structs of API
 function pointers, now the APIs are exported directly. This work also
 includes an effort to stop exporting all function (we do this currently as a
 workaround to allow gtkbuilder to work), so *only* API function are exported
 and plugins cannot call internal function anymore. This change is also
 required to allow gobject-introspection to find geany functions at runtime
 (through g_module_symbol()) in the future.
 - new API functions for registering keybindings (PR#376). The current API
 functions do not allow context information to be stored and passed to the
 key handler function. This makes it very hard for non-C plugins to use these
 function. So what's  needed are key handlers that get the necessary context
 information. This allows interprepted language plugins to register
 keybindings.
 - A new plugin loader mechanism (a thread about this is already running on
 the devel list): Similarly to the keybindings, the plugin_* functions
 implemented by plugins do not carry any context information, making it hard
 for non-C plugins to implement them properly. Therefore a new loader
 mechaism is needed so that the context information can be passed. The loader
 works such that an API function is called to register a function pointer
 table. This is crucial to possibly support plugins that register other
 plugins (so called pluxies) which is simply not possible with the current
 mechaism. The current loader is kept for backwards compatibility (but will
 not receive new features).
 - New API functions to allow plugins to act as proxy plugins (pluxies).
 These pluxies can then implement whatever is needed to execute code in the
 in the actual plugin, like invoking an interpreter or firing up a java vm.
 The pluxies call the new loader's API function on behalf of the actual
 plugin. The API function to implement the pluxies is a simple
 geany_register_pluxy() that, much like the normal plugin loader, that
 pluxies use to pass a function pointer table which implements the necessary
 hooks (probe(), load() and unload())

 Once this is in place in the core, my roadmap contains the following items,
 which are implemented (at least initially) in a plugin, so no further
 changes to the cure should be necessary.
 - Modify geanypy to use the new pluxy APIs. This will finally enable geanypy
 to show the python plugins in the normal PM dialog and support keybindings
 - Create a new pluxy that supports libpeas-based plugins (codename: peasy).
 Peasy will use libpeas to load plugins and their metadata.
 - Part of the peasy work is also work on creating vala and
 gobject-introspection bindings for Geany's API functions, so that we can
 support python, javascript and lua out of the box.

 This is my roadmap so far. It changed quite a bit since I started this
 non-C-plugins effort a year ago, but I hope it will be good for everyone.
 Please share your opinions on this or ask questions.

 Best regards.
 ___
 Devel mailing list
 Devel@lists.geany.org
 https://lists.geany.org/cgi-bin/mailman/listinfo/devel
___
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel