ScriptFu: Add script-fu-interpreter akin to other interpreters

Why:
1) users can install .scm scripts to plug-ins dir
2) Crashing scripts do not crash extension-script-fu

Scripts (.scm files) have a shebang and are executable
and in a same-named subdir of plugin dir.

Interpreter/scripts create PDB procs of type PLUGIN unlike extension-script-fu
which creates PDB procs of type TEMPORARY, owned by extension-script-fu.

Unlike other interpreters, the interpreter is-a plugin outright,
not by virtue of the script subclassing GimpPlugin and using GI.

More details in /plug-ins/script-fu/interpreter/README
This commit is contained in:
lloyd konneker 2022-06-19 16:03:06 -04:00 committed by Lloyd Konneker
parent a0ba969450
commit d5a83429b4
35 changed files with 1473 additions and 74 deletions

View file

@ -25,7 +25,7 @@
;Install script for GIMP and GTK+
;requires Inno Setup 6
;
;See directories.isi
;See directories.isi
;
;Changelog:
;
@ -751,7 +751,7 @@ begin
begin
DebugMsg('DoConfigOverride', 'First call');
Result := False;
ConfigOverride := coDontOverride;
@ -819,7 +819,7 @@ begin
'/usr/bin/python=' + ExpandConstant('{app}\bin\{#PYTHON}') + #10 +
'/usr/bin/python3=' + ExpandConstant('{app}\bin\{#PYTHON}') + #10 +
':Python:E::py::python:'#10;
if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
begin
DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
@ -840,7 +840,7 @@ begin
'/usr/bin/luajit=' + ExpandConstant('{app}\bin\luajit.exe') + #10 +
'/usr/bin/lua=' + ExpandConstant('{app}\bin\luajit.exe') + #10 +
':Lua:E::lua::luajit:'#10;
if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
begin
DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
@ -848,13 +848,32 @@ begin
end;
end;
#endif
// not optional
// !!! use comma for binfmt delimiter and full Windows path in interpreter field of binfmt
begin
InterpFile := ExpandConstant('{app}\lib\gimp\{#DIR_VER}\interpreters\gimp-script-fu-interpreter.interp');
DebugMsg('PrepareInterp','Writing interpreter file for gimp-script-fu-interpreter: ' + InterpFile);
InterpContent := 'gimp-script-fu-interpreter=' + ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
'gimp-script-fu-interpreter-3.0=' + ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
'/usr/bin/gimp-script-fu-interpreter=' + ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
',ScriptFu,E,,scm,,' + ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + ','#10;
if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
begin
DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
SuppressibleMsgBox(CustomMessage('ErrorUpdatingScriptFu') + ' (2)',mbInformation,mb_ok,IDOK);
end;
end;
end;
procedure PrepareGimpEnvironment();
var EnvFile,Env: String;
begin
StatusLabel(CustomMessage('SettingUpEnvironment'),'');
//set PATH to be used by plug-ins
@ -862,7 +881,7 @@ begin
DebugMsg('PrepareGimpEnvironment','Setting environment in ' + EnvFile);
Env := #10'PATH=${gimp_installation_dir}\bin';
if IsComponentSelected('gimp32on64') then
begin
@ -941,7 +960,7 @@ procedure CustomizeOnClick(Sender: TObject);
begin
DebugMsg('Install mode','Custom');
InstallMode := imCustom;
CleanUpCustomWelcome();
WizardForm.NextButton.OnClick(TNewButton(Sender).Parent);
@ -1044,9 +1063,9 @@ begin
else
exit;
DebugMsg('SelectComponentsFaceLift','2');
WizardForm.ComponentsList.OnClick := @ComponentsListOnClick;
lblDescription := TNewStaticText.Create(WizardForm.ComponentsList.Parent)
with lblDescription do
begin
@ -1055,7 +1074,7 @@ begin
AutoSize := True;
Caption := CustomMessage('ComponentsDescription');
end;
pnlDescription := TPanel.Create(WizardForm.ComponentsList.Parent);
with pnlDescription do
begin
@ -1069,7 +1088,7 @@ begin
end;
lblDescription.Parent := WizardForm.ComponentsList.Parent; //place lblDescription above pnlDescription
lblComponentDescription := TNewStaticText.Create(pnlDescription);
with lblComponentDescription do
begin
@ -1081,7 +1100,7 @@ begin
Height := Parent.Height - ScaleY(20);
Top := ScaleY(12);
end;
end;
@ -1090,7 +1109,7 @@ var rtfNewReadyMemo: TRichEditViewer;
begin
DebugMsg('ReadyFaceLift','');
WizardForm.ReadyMemo.Visible := False;
rtfNewReadyMemo := TRichEditViewer.Create(WizardForm.ReadyMemo.Parent);
with rtfNewReadyMemo do
begin
@ -1214,7 +1233,7 @@ begin
Components := ['Gimp','Deps','Debug','Translations','MyPaint','Python','Ghostscript','Lua','Gimp32'];
ComponentDesc := '';
for i := 0 to TNewCheckListBox(pSender).Items.Count - 1 do
if TNewCheckListBox(pSender).Selected[i] then
begin
@ -1277,7 +1296,7 @@ var sText: String;
begin
DebugMsg('UpdateReadyMemo','');
(* Prepare the text for new Ready Memo *)
sText := RTFHeader;
if pMemoDirInfo <> '' then
sText := sText + ParseReadyMemoText(pSpace,pMemoDirInfo) + '\sb100';
@ -1286,7 +1305,7 @@ begin
If pMemoTasksInfo<>'' then
sText := sText + '\sb100' + ParseReadyMemoText(pSpace,pMemoTasksInfo);
ReadyMemoRichText := Copy(sText,1,Length(sText)-6) + '}';
Result := 'If you see this, something went wrong';
@ -1299,7 +1318,7 @@ var NewBitmap1,NewBitmap2: TFileStream;
begin
WelcomeBitmapBottom := TBitmapImage.Create(WizardForm);
with WelcomeBitmapBottom do
begin
begin
Left := 0;
Top := 0;
Parent := WizardForm;
@ -1309,7 +1328,7 @@ begin
end;
DebugMsg('UpdateWizardImages','Height: ' + IntToStr(WizardForm.WizardBitmapImage.Height));
if WizardForm.WizardBitmapImage.Height < 386 then //use smaller image when not using Large Fonts
begin
try
@ -1375,7 +1394,7 @@ begin
DebugMsg('DoUninstall','Install directory doesn'#39't exist: ' + InstallDir + ', resuming install');
oResult := InResult
end else
begin
begin
oResult := rogUninstallFailed;
end;
@ -1472,7 +1491,7 @@ begin
UninstallString := UninstallString + ' /NORESTART';
DoUninstall(UninstallString, OldPath, lblInfo2, Result);
end;
end;
@ -1509,7 +1528,7 @@ begin
end;
procedure CurPageChanged(pCurPageID: Integer);
procedure CurPageChanged(pCurPageID: Integer);
begin
DebugMsg('CurPageChanged','ID: '+IntToStr(pCurPageID));
case pCurPageID of
@ -1678,15 +1697,15 @@ begin
SetArrayLength(Buttons,2);
Message[0] := CustomMessage('Require32BPP');
Buttons[0] := CustomMessage('Require32BPPContinue');
Buttons[1] := CustomMessage('Require32BPPExit');
if (not WizardSilent) and
Buttons[1] := CustomMessage('Require32BPPExit');
if (not WizardSilent) and
(MessageWithURL(Message, CustomMessage('Require32BPPTitle'), Buttons, mbError, 2, 2) = 2) then
Result := False
else
Result := True;
end
else
Result := True;
Result := True;
end;
@ -1733,7 +1752,7 @@ begin
Result := RestartSetupAfterReboot(); //resume install after reboot - skip all setting pages, and install directly
if Result then
Result := BPPTooLowWarning();
Result := BPPTooLowWarning();
if not Result then //no need to do anything else
exit;
@ -1743,7 +1762,7 @@ begin
SetArrayLength(Buttons,2);
Buttons[0] := CustomMessage('DevelopmentButtonContinue');
Buttons[1] := CustomMessage('DevelopmentButtonExit');
if (not WizardSilent) and
if (not WizardSilent) and
(MessageWithURL(Message, CustomMessage('DevelopmentWarningTitle'), Buttons, mbError, 2, 2) = 2) then
begin
Result := False;
@ -1810,4 +1829,3 @@ end;
#include "uninst.isi"
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")

View file

@ -3106,6 +3106,7 @@ plug-ins/script-fu/libscriptfu/Makefile
plug-ins/script-fu/libscriptfu/ftx/Makefile
plug-ins/script-fu/libscriptfu/tinyscheme/Makefile
plug-ins/script-fu/server/Makefile
plug-ins/script-fu/interpreter/Makefile
plug-ins/script-fu/scripts/Makefile
plug-ins/script-fu/scripts/images/Makefile
plug-ins/selection-to-path/Makefile

View file

@ -49,7 +49,7 @@ AM_LDFLAGS = \
$(framework_cocoa) \
$(xnone)
SUBDIRS = libscriptfu scripts server
SUBDIRS = libscriptfu scripts server interpreter
# Each plugin must be installed in a dir of the same name as the executable
script_fudir = $(gimpplugindir)/plug-ins/script-fu

View file

@ -0,0 +1,6 @@
/Makefile.in
/Makefile
/.deps
/_libs
/.libs
/script-fu-interpreter.exe

View file

@ -0,0 +1,102 @@
## Process this file with automake to produce Makefile.in
# derived from app/Makefile.am, another installed binary
if PLATFORM_OSX
xobjective_c = "-xobjective-c"
xobjective_cxx = "-xobjective-c++"
xnone = "-xnone"
framework_cocoa = -framework Cocoa
endif
if OS_WIN32
win32_ldflags = -mwindows -Wl,--tsaware $(WIN32_LARGE_ADDRESS_AWARE)
if HAVE_EXCHNDL
exchndl = -lexchndl
endif
else
libm = -lm
endif
if ENABLE_RELOCATABLE_RESOURCES
munix = -Wl,-rpath '-Wl,$$ORIGIN/../lib'
endif
if HAVE_WINDRES
include $(top_srcdir)/build/windows/gimprc-plug-ins.rule
script_fu_interpreter_RC = script-fu-interpreter.rc.o
endif
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm)
# link to libscriptfu
libgimp_scriptfu = $(top_builddir)/plug-ins/script-fu/libscriptfu/libgimp-scriptfu-$(GIMP_API_VERSION).la
# include srcdir parent to find libscriptfu include files
AM_CPPFLAGS = \
-I$(top_srcdir) \
$(GTK_CFLAGS) \
$(GEGL_CFLAGS) \
-I$(includedir) \
-I$(srcdir)/.. \
-DG_LOG_DOMAIN=\"scriptfu\"
AM_CFLAGS = \
$(xobjective_c)
AM_CXXFLAGS = \
$(xobjective_cxx)
AM_LDFLAGS = \
$(munix) \
$(win32_ldflags) \
$(framework_cocoa) \
$(xnone)
# interpreter is-a plugin but is-a bin_PROGRAM
# i.e. installs to usual place of executables e.g. /usr/bin so shebangs find it.
bin_PROGRAMS = gimp-script-fu-interpreter-@GIMP_API_VERSION@
gimp_script_fu_interpreter_@GIMP_API_VERSION@_SOURCES = \
script-fu-interpreter.c \
script-fu-interpreter.h \
script-fu-interpreter-plugin.c
# link with libgimp-scriptfu
# link with the usual gimp suspects
gimp_script_fu_interpreter_@GIMP_API_VERSION@_LDADD = \
$(libgimp_scriptfu) \
$(libgimpmath) \
$(libgimp) \
$(libgimpbase) \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimpconfig) \
$(libgimpcolor) \
$(GTK_LIBS) \
$(GTK_MAC_INTEGRATION_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \
$(exchndl) \
$(script_fu_interpreter_RC)
install-exec-hook:
if DEFAULT_BINARY
cd $(DESTDIR)$(bindir) \
&& rm -f gimp-script-fu-interpreter$(EXEEXT) \
&& $(LN_S) gimp-script-fu-interpreter-$(GIMP_APP_VERSION)$(EXEEXT) gimp-script-fu-interpreter$(EXEEXT)
endif
uninstall-local:
if DEFAULT_BINARY
rm -f $(DESTDIR)$(bindir)/gimp-script-fu-interpreter$(EXEEXT)
endif

View file

@ -0,0 +1,178 @@
# About script-fu-interpreter
It is GIMP's Scheme interpreter akin to any other language interpreter,
rather than a GIMP extension as is the plugin extension-script-fu.
This interpreter (like all the ScriptFu plugins) embeds a TinyScheme interpreter.
It is an executable program that is passed the name of a .scm file.
The combination defines one or more PDB procedures of type PLUGIN.
Differences from extension-script-fu
====================================
Since gimp-script-fu-interpreter and extension-script-fu use the same
embedded interpreter (they both wrap TinyScheme)
there is no difference in the language interpreted.
Any differences are in the larger behavior of plugins.
### PDB Procedure type
Defines PDB procedure(s) of type PLUGIN.
Unlike for the scriptfu extension,
where a .scm file defines PDB procedure(s) of type TEMPORARY
(owned by the PDB procedure of type PLUGIN named extension-script-fu)
### Protocol
Uses protocol to GIMP like other interpreters e.g. Python
(query, create, and run phases.)
The protocol to GIMP is unlike the protocol to the GIMP extension extension-script-fu.
(Note that "extension" has many meanings. It can denote: a protocol,
or "suffix of a filename", or "a resource that extends GIMP."
extension-script-fu is "a resource that extends GIMP" and it also uses
the "extension" protocol,
while gimp-script-fu-interpreter is a "a resource that extends GIMP" but uses
the "plugin" protocol.)
### Process lifetimes
Executed many times, for many phases,
unlike extension-script-fu which stays executing and gets a remote procedure call
from GIMP to run a PDB procedure.
### Process concurrency
Each invocation of a plugin is in a separate process.
One plugin process crash does not affect others.
Unlike extension-script-fu, where a crash means the GIMP app must be restarted
to restart extension-script-fu.
### GUI concurrency
Each plugin can have its own GUI visible concurrently
with the GUI of other ScriptFu plugins.
For extension-script-fu, an open dialog
prevents other plugins in /scripts (implemented by extension-script-fu)
from opening a dialog.
Instead extension-script-fu opens such dialogs sequentially.
This difference is not very important,
since most users work sequentially.
Most dialogs for plugins do not do anything substantive
until a user closes the dialog with the OK button.
### Calls between scripts
In extension-script-fu, a call to another PDB procedure
implemented by TEMPORARY procedure owned by extension-script-fu
does not leave the process.
In gimp-script-fu-interpreter, a call to another PDB procedure
implemented in another plugin file starts another process.
For other plugins, most calls to another PDB procedure starts another process.
The exception is when one plugin file implements many PDB procedures.
One common case is when one plugin file implements its own TEMPORARY PDB procedures that
exist only for the duration of the plugin's lifetime.
Naming
======
script-fu-interpreter is the informal name.
Source is located in /plug-ins/script-fu/interpreter
Filename of the executable is gimp-script-fu-interpreter-3.0.
The name is versioned by a number corresponding to the API
and the major version of GIMP (when script-fu-interpreter was introduced).
We expect plugin authors to be insulated from changes to script-fu-interpreter,
for the duration of the GIMP 3 version.
About .scm scripts for script-fu-interpreter
============================================
The contents of a .scm file queried by script-fu-interpreter
are the same as those handled by extension-script-fu
except for the addition of a shebang:
#!/usr/bin/env gimp-script-fu-interpreter-3.0
(define (script-fu-test img drawable)
...
(script-fu-register "script-fu-test"
...
### Query of scripts
As for other interpreters, a plugin script file must have certain attributes
to be queried by GIMP. But a queried file may define many PDB procedures.
A .scm file queried by script-fu-interpreter:
- must have permission to execute.
- must be in a directory names the same as the file's base name (less suffix.)
A directory containing an .scm file queried by script-fu-interpreter
is usually a subdirectory of one of the /plug-ins directories,
unlike for extension-script-fu, where the .scm files are in a /scripts dir.
A plugin directory should contain only one queriable .scm file:
only one file can have the same base name as the directory and
GIMP will query that file.
### Defining many PDB procedures per directory
When GIMP queries, script-fu-interpreter will load *ALL* the .scm files
in the same directory (regardless of shebang or execute permission.)
Any of the .scm files can declare and register a PDB procedure.
Any single .scm file can declare and register many PDB procedures.
Similarly, when GIMP runs a named PDB procedure defined in an .scm file,
script-fu-interpreter will actually load *ALL* the .scm files
in the same directory, but run only the define run function so named.
A plugin directory that contains many .scm files having a shebang
will also work, since only one can be named the same as the parent directory,
and GIMP will only query it,
but find the other .scm files with a shebang.
### Requery of scripts
As with other plugins, GIMP caches plugin definitions between sessions.
GIMP queries plugin files at startup.
GIMP will not requery any plugin files which have not changed since cached.
GIMP will not query a plugin file that is added to the file system
until GIMP is restarted.
(Filters>Development>ScriptFu>Refresh Scripts will not requery script files
handled by gimp-script-fu-interpreter, only those handled by extension-script-fu.)
### Errors during query
Most errors occurring during query appear only in the console.
If you are a plugin author, you should start GIMP in a console
so you can see such errors.
A script may be malformed because it does not define
a "run" function having the same name as the PDB procedure name declared
in the call to script-fu-register().
Formerly, such a malformed script was successfully queried but
the script would throw a "undefined variable" error at run time.
Now, such a malformed script is not queried successfully,
but throws a warning to the console:
"Run function not defined, or does not match PDB procedure name:"
Test scripts
============
Small test scripts for gimp-script-fu-interpreter
are located in /plug-ins/script-fu/scripts/test
The script ts-helloworld.scm,
formerly installed in /scripts and handled by extension-script-fu,
is now installed in /plug-ins/ts-helloworld/ts-helloworld.scm
and is now handled by gimp-script-fu-interpreter.
It appears as "Filters>Development>Script-Fu>Test>Hello World..."

View file

@ -0,0 +1,42 @@
scriptfuInclude = include_directories('..')
executable_name = 'gimp-script-fu-interpreter-' + gimp_api_version
plugin_sources = [
'script-fu-interpreter.c',
'script-fu-interpreter-plugin.c',
]
if platform_windows
plugin_sources += windows.compile_resources(
plugin_rc,
args: [
'--define', 'ORIGINALFILENAME_STR="@0@"'.format(executable_name+'.exe'),
'--define', 'INTERNALNAME_STR="@0@"' .format(executable_name),
'--define', 'TOP_SRCDIR="@0@"' .format(meson.source_root()),
],
include_directories: [
rootInclude, appInclude,
],
)
endif
# !!! Installs as a usual binary say to /usr/bin, unlike extension-script-fu
# GIMP queries scripts with shebangs, which invokes gimp-script-fu-interpreter-3.0.
executable(executable_name,
plugin_sources,
dependencies: [
libgimpui_dep,
math,
],
c_args: [
'-DG_LOG_DOMAIN="scriptfu"',
],
include_directories: [
scriptfuInclude,
],
link_with : libscriptfu,
install: true,
)

View file

@ -0,0 +1,140 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* This file understands how to make a GimpPlugin of the interpreter.
* This is mostly boilerplate for any plugin.
* It understands little about ScriptFu internals,
* hidden by script-fu-interpreter.[ch] and libscriptfu.
*/
#include "config.h"
#include <glib.h>
#include <libgimp/gimp.h>
#include "libscriptfu/script-fu-intl.h"
#include "script-fu-interpreter.h"
/* ScriptFuInterpreter subclasses GimpPlugIn */
#define SCRIPT_FU_INTERPRETER_TYPE (script_fu_interpreter_get_type ())
G_DECLARE_FINAL_TYPE (ScriptFuInterpreter, script_fu_interpreter, SCRIPT, FU_INTERPRETER, GimpPlugIn)
struct _ScriptFuInterpreter
{
GimpPlugIn parent_instance;
};
static GList * script_fu_interpreter_query_procedures (GimpPlugIn *plug_in);
static GimpProcedure * script_fu_interpreter_create_procedure (GimpPlugIn *plug_in,
const gchar *name);
G_DEFINE_TYPE (ScriptFuInterpreter, script_fu_interpreter, GIMP_TYPE_PLUG_IN)
/* An alias to argv[1], which is the path to the .scm file.
* Each instance of ScriptFuInterpreter is specialized by the script passed in argv[1]
* This var need not belong to the class or to an instance,
* because there will only be one instance of ScriptFuInterpreter per plugin process.
*/
static gchar * path_to_this_script;
/* Connect to Gimp. See libgimp/gimp.c.
*
* Can't use GIMP_MAIN macro, it doesn't omit argv[0].
*
* First arg is app cited in the shebang.
* Second arg is the .scm file containing the shebang.
* Second to last arg is the "phase" e.g. -query or -run
* Last arg is the mode for crash dumps.
* Typical argv:
* gimp-script-fu-interpreter-3.0 ~/.config/GIMP/2.99/plug-ins/fu/fu
* -gimp 270 12 11 -query 1
*/
int main (int argc, char *argv[])
{
g_debug ("Enter script-fu-interpreter main");
/* Alias path to this plugin's script file. */
path_to_this_script = argv[1];
/* gimp_main will create an instance of the class given by the first arg, a GType.
* The class is a subclass of GimpPlugIn (with overridden query and create methods.)
* GIMP will subsequently callback the query or create methods,
* or the run_func of the PDB procedure of the plugin,
* depending on the "phase" arg in argv,
* which is set by the gimp plugin manager, which is invoking this interpreter.
*/
/* Omit argv[0] when passing to gimp */
gimp_main (SCRIPT_FU_INTERPRETER_TYPE, argc-1, &argv[1] );
g_debug ("Exit script-fu-interpreter.");
}
DEFINE_STD_SET_I18N
static void
script_fu_interpreter_class_init (ScriptFuInterpreterClass *klass)
{
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = script_fu_interpreter_query_procedures;
plug_in_class->create_procedure = script_fu_interpreter_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
/* called by the GType system to initialize instance of the class. */
static void
script_fu_interpreter_init (ScriptFuInterpreter *script_fu)
{
/* Nothing to do. */
}
/* Return the names of PDB procedures implemented. A callback from GIMP. */
static GList *
script_fu_interpreter_query_procedures (GimpPlugIn *plug_in)
{
GList *result = NULL;
g_debug ("queried");
result = script_fu_interpreter_list_defined_proc_names (plug_in, path_to_this_script);
if (g_list_length (result) < 1)
g_warning ("No procedures defined in %s", path_to_this_script);
/* Caller is GIMP and it will free the list. */
return result;
}
/* Create and return a GimpPDBProcedure,
* for the named one of the PDB procedures that the script implements.
* A callback from GIMP.
*
* Also set attributes on the procedure, most importantly, menu items (optional.)
* Also create any menus/submenus that the script defines e.g. Filters>My
*/
static GimpProcedure *
script_fu_interpreter_create_procedure (GimpPlugIn *plug_in,
const gchar *proc_name)
{
return script_fu_interpreter_create_proc_at_path (plug_in,
proc_name,
path_to_this_script);
}

View file

@ -0,0 +1,192 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <glib.h>
#include <libgimp/gimp.h>
#include "libscriptfu/script-fu-lib.h"
#include "script-fu-interpreter.h"
/* Implementation of the outer ScriptFuInterpreter.
* This understands ScriptFu internals
* i.e. uses libscriptfu shared with other ScriptFu plugins e.g. extension-script-fu.
*/
/* We don't need to load (into the interpreter) any .scm files handled by extension-script-fu.
* Namely, the .scm in the GIMP installation /scripts or in the user local /scripts dirs.
*
* During startup, GIMP might call gimp-script-fu-interpreter
* to query new files such as /plug-ins/fu/fu.scm.
* This is before extension-script-fu starts.
* But all the .scm files handled by extension-script-fu are type TEMPORARY and not needed
* for a /plug-ins/fu.scm to be queried.
* The only Scheme executed during query are calls to script-fu-register.
* Later, when a /plug-ins/fu.scm is run, it can call temporary PDB procedures
* that extension-script-fu provides.
*
* When we call script_fu_init_embedded_interpreter(),
* the passed paths should include the path to /scripts
* because that is the location of scripts for initialization and compatibility
* (script-fu.init, plug-in-compat.init and script-fu-compat.init,
* which are really scheme files.)
*
* scrip-fu-interpreter always inits embedded interpreter(allow_register=TRUE)
* In the "run" phase, you don't need script-fu-register to be defined, but its harmless.
*/
static GFile *script_fu_get_plugin_parent_path (const gchar *path_to_this_script);
static void script_fu_free_path_list (GList **list);
/* Return a list of PDB procedure names defined in all .scm files in
* the parent dir of the given path, which is a filename of the one being queried.
*
* Each .scm file may contain many calls to script-fu-register, which defines a PDB procedure.
* All .scm files in the parent dir are searched.
*
* This executable is named script-fu-interpreter
* but no PDB procedure is named "script-fu-interpreter".
* Instead, the interpreter registers PDB procs named from name strings
* give in the in script-fu-register() calls in the interpreted scripts.
*
* Caller must free the list.
*/
GList *
script_fu_interpreter_list_defined_proc_names (GimpPlugIn *plug_in,
const gchar *path_to_this_script)
{
GList *name_list = NULL; /* list of strings */
GList *path_list = NULL; /* list of GFile */
/* path_list is /scripts dir etc. from which we will load compat and init scripts.
* second argument TRUE means define script-fu-register into the interpreter.
*/
path_list = script_fu_search_path ();
script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE);
script_fu_free_path_list (&path_list);
/* Reuse path_list, now a list of one path, the parent dir of the queried script. */
path_list = g_list_append (path_list,
script_fu_get_plugin_parent_path (path_to_this_script));
name_list = script_fu_find_scripts_list_proc_names (plug_in, path_list);
script_fu_free_path_list (&path_list);
/* Usually name_list is not NULL i.e. not empty.
* But an .scm file that is not an actual GIMP plugin, or broken, may yield empty list.
*/
return name_list;
}
/* Create a PDB proc of type PLUGIN with the given name.
* Unlike extension-script-fu, create proc of type PLUGIN.
*
* We are in "create procedure" phase of call from GIMP.
* Create a PDB procedure that the script-fu-interpreter wraps.
*
* A GimpPDBProcedure has a run function, here script_fu_script_proc()
* of this outer interpreter.
* Sometime after the create, GIMP calls the run func, passing a name aka command.
* In ScriptFu, the same name is used for the PDB proc and the Scheme function
* which is the inner run func defined in the script.
* script_fu_script_proc calls the TinyScheme interpreter to evaluate
* the inner run func in the script.
*/
GimpProcedure *
script_fu_interpreter_create_proc_at_path (GimpPlugIn *plug_in,
const gchar *proc_name,
const gchar *path_to_this_script
)
{
GimpProcedure *procedure = NULL;
GList *path_list = NULL; /* list of GFile */
g_debug ("script_fu_interpreter_create_proc_at_path, name: %s", proc_name);
/* Require proc_name is a suitable name for a PDB procedure eg "script-fu-test".
* (Not tested for canonical name "script-fu-<something>")
* Require proc_name is a name that was queried earlier.
* Require the proc_name was defined in some .scm file
* in the same directory as the .scm file passed as argv[0].
* The name of the .scm file eg "/plug-ins/fu/fu.scm"
* can be entirely different from proc_name.
*
* Otherwise, we simply won't find the proc_name defined in any .scm file,
* and will fail gracefully, returning NULL.
*/
path_list = script_fu_search_path ();
path_list = g_list_append (path_list,
script_fu_get_plugin_parent_path (path_to_this_script));
/* path_list are the /scripts dir, for .init and compat.scm, plus the path to this.
* second arg TRUE means define script-fu-register so it is effective.
*/
script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE);
/* Reuse path_list, now a list of only the path to this script. */
script_fu_free_path_list (&path_list);
path_list = g_list_append (path_list,
script_fu_get_plugin_parent_path (path_to_this_script));
procedure = script_fu_find_scripts_create_PDB_proc_plugin (plug_in, path_list, proc_name);
script_fu_free_path_list (&path_list);
/* When procedure is not NULL, assert:
* some .scm was evaluated.
* the script defined many PDB procedures locally, i.e. in script-tree
* we created a single PDB procedure (but not put it in the GIMP PDB)
*
* Ensure procedure is-a GimpProcedure or NULL.
* GIMP is the caller and will put non-NULL procedure in the PDB.
*/
return procedure;
}
/* Return GFile of the parent directory of this plugin, whose filename is given.
*
* Caller must free the GFile.
*/
static GFile *
script_fu_get_plugin_parent_path (const gchar *path_to_this_script)
{
GFile *path = NULL;
GFile *parent_path = NULL;
/* A libgimp GimpPlugin does not know its path,
* but its path was passed in argv to this interpreter.
* The path is to a file being queried e.g. "~/.config/GIMP/2.99/plug-ins/fu/fu.scm"
*/
g_debug ("path to this plugin %s", path_to_this_script);
path = g_file_new_for_path (path_to_this_script);
parent_path = g_file_get_parent (path);
g_object_unref (path);
return parent_path;
}
/* Free a list of paths at the given handle.
* Ensures that the pointer to the list is NULL, prevents "dangling."
* g_list_free_full alone does not do that.
*/
static void
script_fu_free_path_list (GList **list)
{
/* !!! g_steal_pointer takes a handle. */
g_list_free_full (g_steal_pointer (list), g_object_unref);
}

View file

@ -0,0 +1,29 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __SCRIPT_FU_INTERPRETER_H__
#define __SCRIPT_FU_INTERPRETER_H__
GList *script_fu_interpreter_list_defined_proc_names (
GimpPlugIn *plug_in,
const gchar *path_to_this_plugin);
GimpProcedure *script_fu_interpreter_create_proc_at_path (
GimpPlugIn *plug_in,
const gchar *proc_name,
const gchar *path_to_this_script);
#endif /* __SCRIPT_FU_INTERPRETER_H__ */

View file

@ -119,7 +119,9 @@ libgimp_scriptfu_@GIMP_API_VERSION@_la_SOURCES = \
scheme-wrapper.c \
scheme-wrapper.h \
script-fu-lib.c \
script-fu-lib.h
script-fu-lib.h \
script-fu-proc-factory.h \
script-fu-proc-factory.c
EXTRA_libgimp_scriptfu_@GIMP_API_VERSION@_la_DEPENDENCIES = $(scriptfu_def)

View file

@ -14,6 +14,7 @@ libscriptfu_sources = [
'script-fu-errors.c',
'script-fu-compat.c',
'script-fu-lib.c',
'script-fu-proc-factory.c',
]
# !! just "library(...)" which means shared versus static depends on configuration of project.

View file

@ -19,7 +19,7 @@
#include "config.h"
#include <libgimp/gimp.h>
/* FIXME script-fu-types.h refers to GtkAdjustment. */
/* FIXME We only need gimpui because script-fu-types.h refers to GtkAdjustment. */
#include <libgimp/gimpui.h>
#include "script-fu-lib.h"
@ -28,6 +28,7 @@
#include "scheme-wrapper.h" /* tinyscheme_init etc, */
#include "script-fu-scripts.h" /* script_fu_find_scripts */
#include "script-fu-interface.h" /* script_fu_interface_is_active */
#include "script-fu-proc-factory.h"
/*
@ -67,13 +68,43 @@ script_fu_find_and_register_scripts ( GimpPlugIn *plugin,
script_fu_find_scripts (plugin, paths);
}
/*
* Init the embedded interpreter.
*
* allow_register:
* TRUE: allow loaded scripts to register PDB procedures.
* The scheme functions script-fu-register and script-fu-menu-register are
* defined to do something.
* FALSE: The scheme functions script-fu-register and script-fu-menu-register are
* defined but do nothing.
*
* Note that the embedded interpreter always defines scheme functions
* for all PDB procedures already existing when the interpreter starts
* (currently bound at startup, but its possible to lazy bind.)
* allow_register doesn't affect that.
*/
void
script_fu_init_embedded_interpreter ( GList *paths,
gboolean allow_register,
GimpRunMode run_mode)
{
g_debug ("script_fu_init_embedded_interpreter");
tinyscheme_init (paths, allow_register);
ts_set_run_mode (run_mode);
/*
* Ensure the embedded interpreter is running
* and has loaded its internal Scheme scripts
* and has defined existing PDB procs as Scheme foreign functions
* (is ready to interpret PDB-like function calls in scheme scripts.)
*
* scripts/...init and scripts/...compat.scm are loaded
* iff paths includes the "/scripts" dir.
*
* The .scm file(s) for plugins are loaded
* iff paths includes their parent directory (e.g. /scripts)
* Loaded does not imply yet registered in the PDB
* (yet, they soon might be for some phases of the plugin.)
*/
}
void
@ -155,7 +186,6 @@ script_fu_search_path (void)
GList *path = NULL;
path_str = gimp_gimprc_query ("script-fu-path");
if (path_str)
{
GError *error = NULL;
@ -170,6 +200,23 @@ script_fu_search_path (void)
g_clear_error (&error);
}
}
return path;
}
GimpProcedure *
script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in,
GList *paths,
const gchar *name)
{
/* Delegate to factory. */
return script_fu_proc_factory_make_PLUGIN (plug_in, paths, name);
}
GList *
script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in,
GList *paths)
{
/* Delegate to factory. */
return script_fu_proc_factory_list_names (plug_in, paths);
}

View file

@ -42,4 +42,10 @@ void script_fu_run_read_eval_print_loop (void);
void script_fu_register_quit_callback (void (*func) (void));
void script_fu_register_post_command_callback (void (*func) (void));
GimpProcedure *script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in,
GList *paths,
const gchar *name);
GList *script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in,
GList *paths);
#endif /* __SCRIPT_FU_LIB_H__ */

View file

@ -0,0 +1,205 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <glib.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "tinyscheme/scheme-private.h"
#include "script-fu-types.h"
#include "script-fu-scripts.h"
#include "script-fu-script.h"
#include "script-fu-proc-factory.h"
/* Local functions */
static void script_fu_add_menu_to_procedure (GimpProcedure *procedure,
SFScript *script);
/* Methods to register PDB procs. A factory makes objects, here PDB procedures.
*
* Used by the outer script-fu-interpreter
*
* This is in libscriptfu to hide the SFScript type from outer plugins.
* These methods use instances of type SFScript as specs for procedures.
*
* FUTURE: migrate code.
* There are two flavors of factory-like code: for PDBProcType TEMPORARY and PLUGIN.
* extension-script-fu outer plugin only makes TEMPORARY
* script-fu-interpreter outer plugin only makes PLUGIN type
* This source file supports only script-fu-interpreter.
* script_fu_find_scripts() in script-fu-scripts.c is also a factory-like method,
* and could be extracted to a separate source file.
* Maybe more code sharing between the two flavors.
*/
/* Create and return a single PDB procedure of type PLUGIN,
* for the given proc name, by reading the script file in the given paths.
* Also add a menu for the procedure.
*
* PDB proc of type PLUGIN has permanent lifetime, unlike type TEMPORARY.
*
* The list of paths is usually just one directory, a subdir of /plug-ins.
* The directory may contain many .scm files.
* The plugin manager only queries one .scm file,
* having the same name as its parent dir and and having execute permission.
* But here we read all the .scm files in the directory.
* Each .scm file may register (and define run func for) many PDB procedures.
*
* Here, one name is passed, and though we load all the .scm files,
* we only create a PDB procedure for the passed name.
*/
GimpProcedure *
script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in,
GList *paths,
const gchar *proc_name)
{
SFScript * script = NULL;
GimpProcedure * procedure = NULL;
/* Reads all .scm files at paths, even though only one is pertinent.
* The returned script_tree is also in the state of the interpreter,
* we don't need the result here.
*/
(void) script_fu_find_scripts_into_tree (plug_in, paths);
/* Get the pertinent script from the tree. */
script = script_fu_find_script (proc_name);
if (script)
{
procedure = script_fu_script_create_PDB_procedure (
plug_in,
script,
script_fu_script_proc, /* run_func */
GIMP_PDB_PROC_TYPE_PLUGIN);
script_fu_add_menu_to_procedure (procedure, script);
}
else
{
g_warning ("Failed to find script: %s.", proc_name);
}
return procedure;
}
/* Traverse the list of scripts, for each defined name of a PDB proc,
* add it list whose handle is given.
*
* Order is not important. Could just as well prepend.
*
* This is a GTraverseFunction
*/
static gboolean
script_fu_append_script_names (gpointer *foo G_GNUC_UNUSED,
GList *scripts,
GList **name_list)
{
for (GList * list = scripts; list; list = g_list_next (list))
{
SFScript *script = list->data;
if ( !script_fu_is_defined (script->name))
{
g_warning ("Run function not defined, or does not match PDB procedure name: %s",
script->name);
continue;
}
/* Must assign result from g_list_append back to name_list */
*name_list = g_list_append ( (GList *) *name_list, g_strdup (script->name));
}
return FALSE; /* We traversed all. */
}
/* Load script texts (.scm files) in the given paths.
* Iterate over all loaded scripts to get the PDB proc names they define.
* Return a list of the names.
*/
GList *
script_fu_proc_factory_list_names (GimpPlugIn *plug_in,
GList *paths)
{
GList * result_list = NULL;
GTree * script_tree = NULL;
/* Load (eval) all .scm files in all dirs in paths. */
script_tree = script_fu_find_scripts_into_tree (plug_in, paths);
/* Iterate over the tree, adding each script name to result list */
g_tree_foreach (script_tree,
(GTraverseFunc) script_fu_append_script_names,
&result_list);
return result_list;
}
/* From scriptfu's internal data, add any menu to given procedure in the PDB.
* Requires that a script was just eval'ed so that scriptfu's list of menus
* declared in a script is valid.
* Requires the proc exists in PDB.
*
* Not ensure the PDB proc has a menu, when no menu was defined in the script.
*
* Derived from script_fu_install_menu, but that is specific to TEMPORARY procs.
* Also, unlike script_fu_install_menu, we don't nuke the menu list as we proceed.
*
* For each "create" of a procedure, the gimp-script-fu-interpreter is started anew,
* and a new script_menu_list is derived from the .scm file.
* We don't traverse the menu list more than once per session, which soon exits.
*/
static void
script_fu_add_menu_to_procedure (GimpProcedure *procedure,
SFScript *script)
{
GList *menu_list;
gboolean did_add_menu = FALSE;
menu_list = script_fu_get_menu_list ();
/* menu_list might be NULL: for loop will have no iterations. */
/* Each .scm file can declare many menu paths.
* Traverse the list to find the menu path defined for the procedure.
* Each SFMenu points to the procedure (SFScript) it belongs to.
*/
for (GList * traverser = menu_list; traverser; traverser = g_list_next (traverser))
{
SFMenu *menu = traverser->data;
if (menu->script == script)
{
g_debug ("Add menu: %s", menu->menu_path);
gimp_procedure_add_menu_path (procedure, menu->menu_path);
did_add_menu = TRUE;
break;
}
}
/* Some procedures don't have menu path.
* It is normal, but not common, to define procs of type PLUGIN that don't appear in the menus.
* No part of GIMP defaults a menu path for procedures.
* A menu label without a menu path is probably a mistake by the script author.
*/
if ( ! did_add_menu )
{
/* Unusual for a .scm file to have no menu paths, but not an error. */
g_debug ("No menu paths! Does the procedure name in script-fu-menu-register match?");
/* FUTURE if the script defines a menu *label*, declare an error. */
}
/* script_menu_list is a reference we do not need to free. */
}

View file

@ -0,0 +1,27 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __SCRIPT_FU_PDB_PROC_FACTORY_H__
#define __SCRIPT_FU_PDB_PROC_FACTORY_H__
GimpProcedure *script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in,
GList *paths,
const gchar *name);
GList *script_fu_proc_factory_list_names (GimpPlugIn *plug_in,
GList *paths);
#endif /* __SCRIPT_FU_PDB_PROC_FACTORY__ */

View file

@ -43,6 +43,8 @@ static gboolean script_fu_script_param_init (SFScript *script,
gint n);
/*
* Function definitions
*/
@ -165,26 +167,59 @@ script_fu_script_free (SFScript *script)
g_slice_free (SFScript, script);
}
/*
* From the script, create a temporary PDB procedure,
* and install it as owned by the scriptfu extension PDB proc.
*/
void
script_fu_script_install_proc (GimpPlugIn *plug_in,
SFScript *script,
GimpRunFunc run_func)
{
GimpProcedure *procedure;
const gchar *menu_label = NULL;
gint arg_count[SF_DISPLAY] = { 0, };
gint i;
g_return_if_fail (GIMP_IS_PLUG_IN (plug_in));
g_return_if_fail (script != NULL);
g_return_if_fail (run_func != NULL);
procedure = script_fu_script_create_PDB_procedure (plug_in,
script,
run_func,
GIMP_PDB_PROC_TYPE_TEMPORARY);
gimp_plug_in_add_temp_procedure (plug_in, procedure);
g_object_unref (procedure);
}
/*
* Create and return a GimpProcedure.
* Caller typically either:
* install it owned by self as TEMPORARY type procedure
* OR return it as the result of a create_procedure callback from GIMP (PLUGIN type procedure.)
*
* Caller must unref the procedure.
*/
GimpProcedure *
script_fu_script_create_PDB_procedure (GimpPlugIn *plug_in,
SFScript *script,
GimpRunFunc run_func,
GimpPDBProcType plug_in_type)
{
GimpProcedure *procedure;
const gchar *menu_label = NULL;
gint arg_count[SF_DISPLAY] = { 0, };
gint i;
g_debug ("script_fu_script_create_PDB_procedure: %s of type %i", script->name, plug_in_type);
/* Allow scripts with no menus */
if (strncmp (script->menu_label, "<None>", 6) != 0)
menu_label = script->menu_label;
procedure = gimp_procedure_new (plug_in, script->name,
GIMP_PDB_PROC_TYPE_TEMPORARY,
plug_in_type,
run_func, script, NULL);
gimp_procedure_set_image_types (procedure, script->image_types);
@ -509,8 +544,7 @@ script_fu_script_install_proc (GimpPlugIn *plug_in,
gimp_procedure_add_argument (procedure, pspec);
}
gimp_plug_in_add_temp_procedure (plug_in, procedure);
g_object_unref (procedure);
return procedure;
}
void

View file

@ -46,5 +46,11 @@ gchar * script_fu_script_get_command (SFScript *scrip
gchar * script_fu_script_get_command_from_params (SFScript *script,
const GimpValueArray *args);
GimpProcedure * script_fu_script_create_PDB_procedure (GimpPlugIn *plug_in,
SFScript *script,
GimpRunFunc run_func,
GimpPDBProcType plug_in_type);
#endif /* __SCRIPT_FU_SCRIPT__ */

View file

@ -43,13 +43,6 @@
#include "script-fu-intl.h"
typedef struct
{
SFScript *script;
gchar *menu_path;
} SFMenu;
/*
* Local Functions
*/
@ -65,11 +58,6 @@ static void script_fu_install_menu (SFMenu *menu);
static gboolean script_fu_remove_script (gpointer foo,
GList *scripts,
gpointer data);
static GimpValueArray * script_fu_script_proc (GimpProcedure *procedure,
const GimpValueArray *args,
gpointer data);
static SFScript * script_fu_find_script (const gchar *name);
static gchar * script_fu_menu_map (const gchar *menu_path);
static gint script_fu_menu_compare (gconstpointer a,
@ -88,13 +76,25 @@ static GList *script_menu_list = NULL;
* Function definitions
*/
void
script_fu_find_scripts (GimpPlugIn *plug_in,
GList *path)
/* Traverse list of paths, finding .scm files.
* Load and eval any found script texts.
* Script texts will call Scheme functions script-fu-register()
* and script-fu-menu-register(),
* which insert a SFScript record into script_tree,
* and insert a SFMenu record into script_menu_list.
* These are side effects on the state of the outer (SF) interpreter.
*
* Return the tree of scripts, as well as keeping a local pointer to the tree.
* The other result (script_menu_list) is not returned, see script_fu_get_menu_list().
*
* Caller should free script_tree and script_menu_list,
* This should only be called once.
*/
GTree *
script_fu_find_scripts_into_tree ( GimpPlugIn *plug_in,
GList *paths)
{
GList *list;
/* Make sure to clear any existing scripts */
/* Clear any existing scripts */
if (script_tree != NULL)
{
g_tree_foreach (script_tree,
@ -103,16 +103,46 @@ script_fu_find_scripts (GimpPlugIn *plug_in,
g_tree_destroy (script_tree);
}
if (! path)
return;
script_tree = g_tree_new ((GCompareFunc) g_utf8_collate);
for (list = path; list; list = g_list_next (list))
if (paths)
{
script_fu_load_directory (list->data);
GList *list;
for (list = paths; list; list = g_list_next (list))
{
script_fu_load_directory (list->data);
}
}
/*
* Assert result is not NULL, but may be an empty tree.
* When paths is NULL, or no scripts found at paths.
*/
g_debug ("script_fu_find_scripts_into_tree found %i scripts", g_tree_nnodes (script_tree));
return script_tree;
}
/*
* Return list of SFMenu for recently loaded scripts.
* List is non-empty only after a call to script_fu_find_scripts_into_tree().
*/
GList *
script_fu_get_menu_list (void)
{
return script_menu_list;
}
/* Find scripts, create and install TEMPORARY PDB procedures,
* owned by self PDB procedure (e.g. extension-script-fu.)
*/
void
script_fu_find_scripts (GimpPlugIn *plug_in,
GList *path)
{
script_fu_find_scripts_into_tree (plug_in, path);
/* Now that all scripts are read in and sorted, tell gimp about them */
g_tree_foreach (script_tree,
(GTraverseFunc) script_fu_install_script,
@ -571,12 +601,13 @@ script_fu_run_command (const gchar *command,
GString *output;
gboolean success = FALSE;
g_debug ("script_fu_run_command: %s", command);
output = g_string_new (NULL);
ts_register_output_func (ts_gstring_output_func, output);
if (ts_interpret_string (command))
{
g_set_error (error, 0, 0, "%s", output->str);
g_set_error (error, GIMP_PLUG_IN_ERROR, 0, "%s", output->str);
}
else
{
@ -593,6 +624,8 @@ script_fu_load_directory (GFile *directory)
{
GFileEnumerator *enumerator;
g_debug ("Load dir: %s", g_file_get_parse_name (directory));
enumerator = g_file_enumerate_children (directory,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
@ -665,8 +698,10 @@ script_fu_load_script (GFile *file)
}
}
/*
* The following function is a GTraverseFunction.
/* This is-a GTraverseFunction.
*
* Traverse. For each, install TEMPORARY PDB proc.
* Returning FALSE means entire list was traversed.
*/
static gboolean
script_fu_install_script (gpointer foo G_GNUC_UNUSED,
@ -680,8 +715,12 @@ script_fu_install_script (gpointer foo G_GNUC_UNUSED,
{
SFScript *script = list->data;
script_fu_script_install_proc (plug_in, script,
script_fu_script_proc);
const gchar* name = script->name;
if (script_fu_is_defined (name))
script_fu_script_install_proc (plug_in, script,
script_fu_script_proc);
else
g_warning ("Run function not defined, or does not match PDB procedure name: %s", name);
}
return FALSE;
@ -690,13 +729,14 @@ script_fu_install_script (gpointer foo G_GNUC_UNUSED,
static void
script_fu_install_menu (SFMenu *menu)
{
GimpPlugIn *plug_in = gimp_get_plug_in ();
GimpProcedure *procedure;
GimpPlugIn *plug_in = gimp_get_plug_in ();
GimpProcedure *procedure = NULL;
procedure = gimp_plug_in_get_temp_procedure (plug_in,
menu->script->name);
gimp_procedure_add_menu_path (procedure, menu->menu_path);
if (procedure)
gimp_procedure_add_menu_path (procedure, menu->menu_path);
g_free (menu->menu_path);
g_slice_free (SFMenu, menu);
@ -726,7 +766,15 @@ script_fu_remove_script (gpointer foo G_GNUC_UNUSED,
return FALSE;
}
static GimpValueArray *
/* This is the outer "run func" for this plugin.
* When called, the name of the inner run func (code in Scheme language)
* is the first element of the value array.
* Form a command (text in Scheme language) that is a call to the the inner run func,
* evaluate it, and return the result, marshalled into a GimpValueArray.
*
* In the name 'script_fu_script_proc', 'proc' is a verb meaning 'process the script'
*/
GimpValueArray *
script_fu_script_proc (GimpProcedure *procedure,
const GimpValueArray *args,
gpointer data)
@ -841,7 +889,7 @@ script_fu_lookup_script (gpointer *foo G_GNUC_UNUSED,
return FALSE;
}
static SFScript *
SFScript *
script_fu_find_script (const gchar *name)
{
gconstpointer script = name;
@ -916,3 +964,43 @@ script_fu_menu_compare (gconstpointer a,
return retval;
}
/* Is name a defined symbol in the interpreter state?
* (Defined in any script already loaded.)
* Where "symbol" has the usual lisp meaning: a unique name associated with
* a variable or function.
*
* The most common use is
* test the name of a PDB proc, which in ScriptFu must match
* a defined function that is the inner run function.
* I.E. check for typos by author of script.
* Used during query, to preflight so that we don't install a PDB proc
* that won't run later (during the run phase)
* giving "undefined symbol" for extension-script-fu.
* Note that if instead we create a PDB proc having no defined run func,
* script-fu-interpreter would load and define a same-named scheme function
* that calls the PDB, and can enter an infinite loop.
*/
gboolean
script_fu_is_defined (const gchar * name)
{
gchar *scheme_text;
GError *error = NULL;
gboolean result;
/* text to be interpreted is a call to an internal scheme function. */
scheme_text = g_strdup_printf (" (symbol? %s ) ", name);
/* Use script_fu_run_command, it correctly handles the string yielded.
* But we don't need the string yielded.
* If defined, string yielded is "#t", else is "Undefined symbol" or "#f"
*/
result = script_fu_run_command (scheme_text, &error);
if (!result)
{
g_debug ("script_fu_is_defined returns false");
/* error contains string yielded by interpretation. */
g_error_free (error);
}
return result;
}

View file

@ -26,5 +26,13 @@ pointer script_fu_add_script (scheme *sc,
pointer script_fu_add_menu (scheme *sc,
pointer a);
GTree * script_fu_find_scripts_into_tree (GimpPlugIn *plug_in,
GList *path);
SFScript * script_fu_find_script (const gchar *name);
GList * script_fu_get_menu_list (void);
GimpValueArray * script_fu_script_proc (GimpProcedure *procedure,
const GimpValueArray *args,
gpointer data);
gboolean script_fu_is_defined (const gchar *name);
#endif /* __SCRIPT_FU_SCRIPTS__ */

View file

@ -103,5 +103,10 @@ typedef struct
SFArg *args;
} SFScript;
typedef struct
{
SFScript *script; // script which defined this menu path and label
gchar *menu_path;
} SFMenu;
#endif /* __SCRIPT_FU_TYPES__ */

View file

@ -13,3 +13,5 @@ EXPORTS
script_fu_register_quit_callback
script_fu_register_post_command_callback
script_fu_search_path
script_fu_find_scripts_create_PDB_proc_plugin
script_fu_find_scripts_list_proc_names

View file

@ -10,6 +10,7 @@
subdir('libscriptfu')
subdir('scripts')
subdir('server')
subdir('interpreter')
executable_name = 'script-fu'

View file

@ -59,9 +59,17 @@ scripts = \
test_scripts = \
contactsheet.scm \
test-sphere.scm \
test-sphere.scm
# scripts interpreted by gimp-script-fu-interpreter
# Each installs to a subdir of /plug-ins
# Each should have a shebang and execute permission
independent_scripts = \
ts-helloworld.scm
ts_helloworlddir = $(gimpplugindir)/plug-ins/ts-helloworld
ts_helloworld_SCRIPTS = ts-helloworld.scm
scriptdata_DATA = $(scripts)
@ -69,4 +77,4 @@ if GIMP_UNSTABLE
scriptdata_DATA += $(test_scripts)
endif
EXTRA_DIST = $(scripts) $(test_scripts)
EXTRA_DIST = $(scripts) $(test_scripts) $(independent_scripts)

View file

@ -1,5 +1,6 @@
subdir('images')
# scripts interpreted by extension-script-fu, installed to /scripts
scripts = [
'add-bevel.scm',
'addborder.scm',
@ -58,7 +59,6 @@ if not stable
scripts += [
'contactsheet.scm',
'test-sphere.scm',
'ts-helloworld.scm',
]
endif
@ -66,3 +66,22 @@ install_data(
scripts,
install_dir: gimpdatadir / 'scripts',
)
# scripts interpreted by gimp-script-fu-interpreter
# Each installed in subdirectory of /plug-in
# Each have a shebang and executable permission.
# Like other interpreted plugins.
# Lacking a shebang, a .interp file is needed to associate .scm suffix
scripts_independent = [
{ 'name': 'ts-helloworld' },
]
foreach plugin : scripts_independent
name = plugin.get('name')
srcs = plugin.get('srcs', name + '.scm')
install_data(srcs,
install_dir: gimpplugindir / 'plug-ins' / name,
install_mode: 'rwxr-xr-x')
endforeach

View file

@ -0,0 +1,5 @@
Scripts to test script-fu-interpreter.
Not usually installed.
To use, copy the test<x> dirs into /plugins and ensure the .scm files have execute permission.

View file

@ -0,0 +1,31 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Basic test of a .scm file interpreted by script-fu-interpreter
;
; Setup: copy this file w/ executable permission, and its parent dir to /plug-ins
; Example: to ~/.gimp-2.99/plug-ins/test0/test0.scm
; (That is custom to one user.)
; Expect "Test>Test SF interpreter 0" in the menus
; Expect when chosen, message on GIMP message bar.
; Also, remove the execute permission.
; Then expect not appear in GIMP menus (not queried.)
; Also, make the name different from its parent dir.
; Then expect not appear in GIMP menus (not queried.)
(define (script-fu-test0)
(gimp-message "Hello script-fu-test0")
)
(script-fu-register "script-fu-test0"
"Test SF interpreter 0"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "script-fu-test0" "<Image>/Test")

View file

@ -0,0 +1,43 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Basic test that a second .scm file is also queried.
; Expect "Test>Test SF interpreter 1" in the menus
; Expect when chosen, message on GIMP message bar.
; Also tests that one .scm file can define two PDB procedures
; File is queried once, yielding two names.
; Two separate procedures created.
(define (script-fu-test1)
(gimp-message "Hello script-fu-test1")
)
(script-fu-register "script-fu-test1"
"Test SF interpreter 01"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "script-fu-test1" "<Image>/Test")
(define (script-fu-test2)
(gimp-message "Hello script-fu-test2")
)
(script-fu-register "script-fu-test2"
"Test SF interpreter 02"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "script-fu-test2" "<Image>/Test")

View file

@ -0,0 +1,31 @@
; !!! No shebang here
; Test a second .scm file in the same directory as a queried .scm.
; The second .scm file need not be executable.
; The second .scm file need not have a shebang.
; The gimp-script-fu-interpreter will nevertheless load the second .scm
; while it is querying the first, executable .scm in the dir.
; The plugin manager queries the first executable,
; and gimp-script-fu-interpreter loads (and returns defined names in)
; the second during the query of the first.
; Expect "Test>Test SF interpreter 3" in the menus
; Expect when chosen, message on GIMP message bar.
; plug-ins/test1/test1.scm is executable
; plug-ins/test1/test3.scm is NOT executable
(define (script-fu-test3)
(gimp-message "Hello script-fu-test3")
)
(script-fu-register "script-fu-test3"
"Test SF interpreter 3"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "script-fu-test3" "<Image>/Test")

View file

@ -0,0 +1,25 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Test a .scm file that does not call script-fu-menu-register
; The menu will NOT default.
; Expect "Test SF interpreter 4" to NOT EXIST in any menu
; Expect the PDB proc "script-fu-test4" does appear in the PDB Brower
; Two test cases:
; Alongside an executable script: plug-ins/test4/test4.scm NOT executable
; Executable, in its own directory: plug-ins/test1/test4.scm is executable
(define (script-fu-test4)
(gimp-message "Hello script-fu-test4")
)
(script-fu-register "script-fu-test4"
"Test SF interpreter 4"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
; !!! No call to script-fu-menu-register

View file

@ -0,0 +1,16 @@
#!/usr/bin/env gimp-script-fu-interpreter
; Test a .scm file with an invalid shebang
; Note "-3.0" missing above.
; The test depends on platform and env and .interp
; Must not be a file system link from gimp-script-fu-interpreter to gimp-script-fu-interpreter-3.0
; Must not be a .interp file having "gimp-script-fu-interpreter=gimp-script-fu-interpreter-3.0"
; Expect in the console: "/usr/bin/env: 'script-fu-interpreter': No such file or directory"
(define (script-fu-test5)
(gimp-message "Hello script-fu-test5")
)
; !!! No call to script-fu-menu-register

View file

@ -0,0 +1,12 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Test a .scm file that does not register any procedure
; Expect in the console:
; "(test6.scm:164): scriptfu-WARNING **: 10:06:07.966: No procedures defined in /work/.home/.config/GIMP/2.99/plug-ins/test6/test6.scm"
(define (script-fu-test6)
(gimp-message "Hello script-fu-test6")
)
; !!! No call to script-fu-register

View file

@ -0,0 +1,28 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Test non-canonical name for PDB procedure
; gimp-script-fu-interpreter does not enforce canonical name.
; Other parts of GIMP (PDB) does not enforce canonical name
; for PDB procedures defined by .scm scripts.
; Canonical means starts with "script-fu-"
; Here the name doesn't, its just "test7"
; Expect "Test>Test SF interpreter 7" in the menus
; Expect when chosen, message on GIMP message bar.
(define (test7)
(gimp-message "Hello test7")
)
(script-fu-register "test7"
"Test SF interpreter 7"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "test7" "<Image>/Test")

View file

@ -0,0 +1,39 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; Test mismatch between name of defined run function and name for PDB procedure
; Not a high priority: a rare syntax error in a plugin text.
; If authors follow a template, they won't make this mistake.
; The names must match exactly.
; Here, "mismatch" the name of the defined run function
; does not match "script-fu-test8" the name of the PDB proc.
; Expect a warning in the text console as the plugin text is queried:
; script-fu: WARNING: Run function not defined, or does not match PDB procedure name: script-fu-test8.
; Expect the PDB procedure to not exist
; If we don't detect this syntax error:
; A PDB procedure is created.
; When invoked from Test>Test SF interpreter 8"
; the interpreter enters an infinite loop.
; There is no harm to the GIMP app, but the interpreter process can only be killed.
; During the run phase, the "(define foo)"
; should re-define an existing definition in the interpreter state.
; Instead, since the name is mismatched,
; the foo function remains defined to be a call to the PDB procedure named foo.
; So script-fu-script-proc instead calls the PDB again, an infinite loop.
(define (mismatch)
(gimp-message "mismatch")
)
(script-fu-register "script-fu-test8"
"Test SF interpreter 8"
_"Just gives a message from Gimp"
"lkk"
"lkk"
"2022"
"" ; all image types
)
(script-fu-menu-register "script-fu-test8" "<Image>/Test")

View file

@ -1,3 +1,5 @@
#!/usr/bin/env gimp-script-fu-interpreter-3.0
; "Hello, World" Test v1.00 February 29, 2004
; by Kevin Cozens <kcozens@interlog.com>
;