FvwmScript(1)

πŸ’– FvwmScript(1) πŸ’–

okay bestie, this is FvwmScript(1) from the Fvwm Modules section πŸŽ€ part 1 of 4, so get comfy ✨


✨ NAME ✨

FvwmScript β€” module to build graphic user interface πŸ–ΌοΈ

🌸 SYNOPSIS 🌸

FvwmScript must be spawned by Fvwm. It's not going to work from the command line.

ngl this one's a hard rule πŸ’€ FvwmScript must be spawned by Fvwm, and it will not work from the command line.

πŸ¦‹ DESCRIPTION πŸ¦‹

FvwmScript is a module that lets you build so many graphical apps ✨ like desktop accessories, button panels with pop up menus, modal dialogs... literally it's giving everything. When it starts up, it reads the file you name on the command line, and that file is the script. The script is not part of Fvwm's configuration file, so keep them separate, periodt πŸ’…

An FvwmScript script is fully controllable with the keyboard, which is so iconic 🫢 (Shift)-Tab circulates around the widgets, Return simulates a mouse click, the arrows move the cursor or change the values of the widget, and Escape "cancels" for Menu and PopupMenu.

🧚 INVOCATION 🧚

You can invoke it by inserting the line Module FvwmScript name_of_script in the .fvwm2rc file 🌈 If name_of_script starts with a slash, it's a fully qualified path and that file gets read. If it doesn't start with a slash, FvwmScript is gonna check a few different places πŸ‘€ If .fvwm2rc has the command line *FvwmScript: Path path_of_the_script_directory, FvwmScript tries that directory first. If that doesn't work, it tries the system configuration directory and then the user configuration directory, same as the "Read" command in the fvwm man page.

The command to start FvwmScript can sit on a line by itself if you want it spawned during fvwm's initialization πŸ“ Or you can bind it to a menu, a mouse button, or a keystroke so it shows up later on.

πŸ’œ CONFIGURATION OPTIONS πŸ’œ

These commands work in the config file (check fvwm(1), section MODULE COMMANDS, for the deets) πŸ¦‹ They only kick in if the matching script commands aren't used in the script.

  • *FvwmScript: DefaultFont font β€” sets the default font πŸ”€ If you don't set it here or with the Font command in the script, the fixed font is assumed.
  • *FvwmScript: DefaultFore color β€” sets the default foreground color 🎨 If it's not set here or with the ForeColor command in the script, black is used.
  • *FvwmScript: DefaultBack color β€” sets the default background color 🩢 If it's not set here or with the BackColor command in the script, grey85 is used.
  • *FvwmScript: DefaultHilight color β€” sets the default hilight color ✨ If it's not set here or with the HilightColor command in the script, grey100 is used.
  • *FvwmScript: DefaultShadow color β€” sets the default shadow color πŸŒ‘ If it's not set here or with the ShadowColor command in the script, grey55 is used.
  • *FvwmScript: DefaultColorset colorset β€” tells the module to use colorset colorset as its default colorset 🌈

πŸŽ€ ANATOMY OF A SCRIPT πŸŽ€

FvwmScript uses its own programming language πŸ‘‘ A script has five parts, and I'm obsessed with how they're organized:

  1. Heading β€” general characteristics of the window and default properties for all widgets.
  2. Initialisation β€” instructions that run at startup.
  3. Periodic tasks β€” instructions that run every second.
  4. Quit function β€” instructions that run when the script exits.
  5. Widget descriptions β€” the last part, where the widgets live.

A widget can be one of eleven types of items 🧁 text labels, single-line text inputs, radio buttons, checkbox, push buttons, horizontal and vertical scrollbars, rectangles, pop up menus, swallowexecs, and mini scrollbars.

🌈 HEADING OF A SCRIPT 🌈

The syntax is as follows 🫧

  • WindowTitle string β€” sets the window title πŸͺŸ
  • WindowSize width height β€” sets the window size. width and height are numerical values πŸ“
  • WindowPosition x y β€” sets the window position. x and y are numerical values πŸ“
  • ForeColor {color} β€” sets the default foreground color for all widgets 🎨
  • BackColor {color} β€” sets the default background color for all widgets 🩷
  • HilightColor {color} β€” sets the default hilight color for all widgets ✨
  • ShadowColor {color} β€” sets the default shadow color for all widgets πŸŒ‘
  • Colorset {n} β€” sets the default colorset for all widgets 🌸
  • Font {font} β€” sets the default font for all widgets πŸ”€
  • UseGettext [locale_path] β€” enables the gettext mechanism, which the WindowLocaleTitle, LocaleTitle, and ChangeLocaleTitle instructions and the Gettext function use 🌍 If you don't give an argument, the default FvwmScript locale catalog gets used. That catalog lives under the locale fvwm installation directory, and the text domain is FvwmScript (install_prefix/share/locale/*/LC_MESSAGES/FvwmScript.mo). You can reset this catalog or add some catalogs the exact same way as the LocalePath fvwm command (see the fvwm manual page) 🫢 This instruction should be placed before the WindowLocaleTitle instruction, no skipping!
  • WindowLocaleTitle string β€” sets the window title, but uses the locale catalog(s) defined with UseGettext πŸ’–

Part 2 is coming up next with the initialisation, periodic tasks, quit function, and widget stuff πŸ‘€πŸ”₯

✨ LIST OF WIDGETS ✨

okay bestie, there are fifteen types of widgets πŸ’– let's go through them one by one, no cap 🫢

πŸ’… CheckBox

Displays a check box with a string next to it βœ…

  • Title: the title of the check box.
  • Value: if Value is equal to 1, the box is checked. Otherwise it is not.

The Size property is ignored πŸ™ˆ

πŸ¦‹ HDipstick

Displays a horizontal dipstick. This widget can be used to display disk usage πŸ’Ύ

  • Value: specify the current value of the dipstick.
  • MinValue: specify the minimum value of the dipstick.
  • MaxValue: specify the maximum value of the dipstick.

A minimum size of 30x11 is imposed πŸ“

🌸 HScrollBar

Displays a horizontal scrollbar, lowkey iconic πŸŽ€

  • Value: position of the thumb.
  • MaxValue: upper limit of Value.
  • MinValue: lower limit of Value.

The height property is ignored and a minimum width is imposed. The width should be at least the range plus 37 if you want every value to be selectable. For example, a min of 0 and a max of 10 has a range of 11, so it should have a minimum width of 48 ✨

🧚 ItemDraw

Displays an icon and/or a string πŸ–ΌοΈ

  • Title: string to display.
  • Icon: icon to display.
  • MaxValue: x coordinate of the cursor.
  • MinValue: y coordinate of the cursor.

The size is made large enough to contain the title and/or the icon.

πŸ“ List

Displays a list. The List lets the user choose between various options πŸ“‹

  • Value: specify which option is selected.
  • MinValue: first visible option.
  • Title: the title contains the options displayed in the list. The syntax is the following: {Option 1|Option 2|...|Option N}. All menus are displayed at the top of the window.

A minimum height of three items is imposed, and the width is made to be at least 108 πŸ‘‘

Displays a menu which lets the user choose an option. Items of type Menu are laid out from left to right along the top of the window. The size and position properties are ignored, so no babysitting πŸ™…β€β™€οΈ

  • Value: specify which option is selected.
  • Title: the title contains the options displayed in the menu. The syntax is the following: {Option 1|Option 2|...|Option N}.

πŸ”₯ MiniScroll

Displays a very small vertical scrollbar 🀏

  • Value: position of the thumb.
  • MaxValue: upper limit of Value.
  • MinValue: lower limit of Value.

The size is set to 19x34 πŸ’œ

🌈 PopupMenu

Displays a pop up menu 🫧

  • Value: specify what option is selected.
  • Title: the title has the following syntax: {Option 1|Option 2|...|Option N}. "Option 1|Option 2|...|Option N" is the pop up menu which is displayed when the mouse button is pressed.

The size property is ignored.

πŸ’– PushButton

Displays a push button with an icon and/or a string. It's giving clickable πŸ–±οΈ

  • Title: this string has the following syntax {Title of the button|Option 1|Option 2|Option3|...|Option N}. "Option 1|Option 2|...|Option N" is the pop up menu which is displayed when the right button is pressed.
  • Icon: icon to display.

The button is made large enough to fit the icon and or label.

πŸ¦„ RadioButton

Displays a radio button with a string πŸ”˜

  • Title: title of the radio button.
  • Value: if Value is equal to 1, the box is checked. Otherwise it is not.

The size property is ignored.

🩷 Rectangle

Displays a rectangle. This type of widget can be used to decorate the window πŸ–ΌοΈβœ¨

🦒 SwallowExec

This type of widget causes FvwmScript to spawn a process, and capture the first window whose name or resource is equal to Title, then display it in the script window. Main character energy 🎬

  • Title: specify the window name to be captured and displayed in the script window.
  • SwallowExec: specify the command line to execute to spawn the process. Modules can also be swallowed πŸ§ƒ
  • Value: specify the look of the border. Possible values: -1, 0, 1.

The size is made to be at least 30x30 πŸ“

🫧 TextField

Displays a text input field. The text input field can be used to edit a single-line string ⌨️

  • Title: content of the text field.
  • Value: position of the insert point.
  • MinValue: position of the end of the selection.
  • MaxValue: first visible character of the title.

The height property is ignored. The width is made to be at least 40 pixels wider than the initial contents.

🌷 VDipstick

Displays a vertical dipstick πŸ“Š

  • Value: specify the current value of the dipstick.
  • MinValue: specify the minimum value of the dipstick.
  • MaxValue: specify the maximum value of the dipstick.

The size is made to be at least 11x30.

πŸͺ„ VScrollBar

Displays a vertical scrollbar πŸ’…

  • Value: position of the thumb.
  • MaxValue: upper limit of Value.
  • MinValue: lower limit of Value.

The width property is ignored and a minimum height is imposed. The height should be at least the range plus 37 if you want every value to be selectable. For example, a min of 0 and a max of 10 has a range of 11, so it should have a minimum height of 48 🫢


✨ INSTRUCTIONS ✨

here's the rundown on every instruction, so take notes bestie πŸ“

  • HideWidget id: hide the widget numbered id πŸ«₯
  • ShowWidget id: show the widget numbered id πŸ‘€
  • ChangeValue id1 id2: set the value of the widget numbered id1 to id2.
  • ChangeMaxValue id1 id2: set the maximum value of the widget numbered id1 to id2.
  • ChangeMinValue id1 id2: set the minimum value of the widget numbered id1 to id2.
  • ChangeTitle id1 id2: set the title of the widget numbered id1 to id2.
  • ChangeWindowTitle string: set the title of the window to string.
  • ChangeWindowTitleFromArg numarg: set the title of the window to the value of the numarg-th script argument 🏷️
  • ChangeLocaleTitle id1 id2: same vibe as ChangeTitle, but it uses the locale catalog(s) defined with UseGettext 🌍
  • ChangeIcon id1 id2: set the icon of the widget numbered id1 to id2.
  • ChangeForeColor id1 {color}: set the foreground color of the widget numbered id1 to {color}.
  • ChangeBackColor id1 {color}: set the background color of the widget numbered id1 to {color}.
  • ChangeColorSet id1 id2: set the colorset of the widget numbered id1 to id2. Specifying widget 0 sets the main window colorset 🌈
  • ChangePosition id1 x y: move the widget numbered id1 to position (x,y).
  • ChangeSize id1 width height: set the size of the widget numbered id1 to (width,height).
  • ChangeFont id1 newfont: set the font of the widget numbered id1 to newfont.
  • WarpPointer id: warp the mouse pointer into the widget numbered id πŸ–±οΈ
  • WriteToFile filename {str1} {str2} etc: write to the file filename the string made by concatenating all the arguments str1, str2, etc. πŸ“
  • Do {command args}: execute the fvwm command inside the Do block. Any fvwm command described in the fvwm2 man page can be used. Commands are sent from this module to the fvwm main program for processing. The length of the command and arguments cannot exceed 988 characters.
Heads up, the Do limit is 988 characters total, so keep those commands short and sweet πŸ’€
  • Set $var={str1} {str2} etc: concatenate all the arguments into one string and set the variable $var to that string 🧁
  • Quit: quit the program πŸ‘‹
  • SendSignal id1 id2: send a message numbered id2 to widget id1 πŸ“¨
  • SendToScript id_script {str11} {str2} etc: send a message to the script identified by id_script. The message is the concatenation of str1, str2, and so on.
  • Key Keyname Modifier id sig str1 str2 etc: binds a keyboard key to the instruction SendSignal id sig, and sets the "last string" to the concatenation of str1, str2, and so on (see the LastString function). The Keyname and Modifiers fields are defined the same way as in the fvwm Key command βŒ¨οΈπŸ’–

✨ ARGUMENTS ✨

most commands take arguments, and there are two kinds: numbers and strings πŸ”’πŸ”€. A numerical argument is a value between -32000 and +32000. A string is always surrounded with braces {}. Variables always begin with the character $ and can contain both numbers and strings, so they're super flexible πŸ¦‹

✨ FUNCTIONS ✨

okay bestie, functions are where the script actually gets to think πŸ§ πŸ’– All functions take arguments, and they can hand back a string or a number. The syntax is always:

(function argument1 argument2 etc)

Here's the complete list, no cap πŸ’…

(GetTitle id)

Gives you the title of the widget numbered id πŸŽ€

(GetValue id)

Gives you the current value of the widget numbered id ✨

(GetMinValue id)

Gives you the current Min value of the widget numbered id πŸ“‰

(GetMaxValue id)

Gives you the current Max value of the widget numbered id πŸ“ˆ

(GetFore id)

Gives you the current RGB foreground value of the widget numbered id, in the hex format RRGGBB 🎨

(GetBack id)

Gives you the current RGB background value of the widget numbered id, in the hex format RRGGBB 🌸

(GetHilight id)

Gives you the current RGB hilight value of the widget numbered id, in the hex format RRGGBB ✨

(GetShadow id)

Gives you the current RGB shadow value of the widget numbered id, in the hex format RRGGBB πŸŒ™

(GetOutput {str} int1 int2)

This one is lowkey iconic πŸ”₯ It runs the command str, grabs its standard output, and returns the word sitting on line int1 and in position int2. If int2 is -1, you get the whole line back instead.

(NumToHex int)

Turns int into its hexadecimal value πŸ¦‹

(HexToNum {str})

Turns str into its decimal value. Heads up, str has to be a hexadecimal value or it's not gonna work πŸ™ˆ

(Add int1 int2)

Returns the result of (int1+int2) βž•

(Mult int1 int2)

Returns the result of (int1*int2) βœ–οΈ

(Div int1 int2)

Returns the result of (int1/int2) βž—

(StrCopy {str} int1 int2)

Returns the chunk of string that sits between position int1 and position int2. For example, (StrCopy {Hello} 1 2) returns {He} πŸ“

(LaunchScript {str})

This launches the script named str and gives you back an identification number 🧚 You need that number to use SendToScript and ReceiveFromScript. The string str holds the script name plus some arguments.

(GetScriptArgument {int})

Returns the argument script that got passed in through LaunchScript. If int is equal to zero, you get the name of the script instead πŸ’œ

(GetScriptFather)

Returns the identification number of the script father πŸ‘‘

(ReceivFromScript {int})

Returns the message sent by the script numbered int πŸ’Œ

(RemainderOfDiv {int1 int2}): t

Returns the remainder of the division (int1/int2) 🌈

(GetTime)

Returns the time in seconds ⏰

(GetPid)

Returns the process id of the script πŸ†”

(Gettext {str})

Returns the translation of str, using the locale catalog(s) you set up with UseGettext 🌍

(SendMsgAndGet {comId} {cmd} bool)

This is the one for chatting with external programs 🫢 It sends the command cmd with identifier comId to an external program that's ready to talk using a protocol that's specific to FvwmScript.

If bool is 0, FvwmScript doesn't wait for an answer. The returned value is 1 if the message made it to the external program, and 0 if it didn't.

If bool is 1, FvwmScript waits for an answer, and the return value is that answer (a line of no more than 32000 characters). If the communication fails, the returned value is 0.

See the section A COMMUNICATION PROTOCOL for a description of the communication protocol used.

(Parse {str} int)

Here, str has to look like this:

X1S1X2S2X3S3...SnXn

The Xn are numbers made of four decimal digits, and each Sn is a string of length exactly Xn. The returned value is the string Sint.

If int is out of range (e.g., >n), you get the empty string back. If str doesn't match that shape, the return value is unpredictable, bestie πŸ’€ (though on average it's empty).

This one is super useful for handling strings that come back from SendMsgAndGet πŸ’–

(LastString)

This returns the "current working string" for the Key instruction and the SendString command (check the COMMANDS section). At startup it's empty. But when a Key binding gets detected (or a SendString command gets received), the string is set to whatever string is associated with that instruction (or with that command) ✨


πŸŽ€ CONDITIONAL LOOPS πŸŽ€

There are three kinds of conditional loops, and we're about to meet all of them πŸ¦‹ First up, the If-Then-Else instruction, which looks like this:

If $ToDo=={Open xcalc} Then
 Do {Exec xcalc &}             # List of instructions
Else
Begin
 Do {Exec killall xcalc &}     # List of instructions
 Do {Exec echo xcalc killed > /dev/console}
End

The second part, Else-Begin-End, is optional. If the loop only has one instruction in it, you can skip Begin and End entirely πŸ’…

Next is the While-Do instruction:

While $i<5 Do
Begin
 Set $i=(Add i 1)              # List of instructions
End

Quick rule to remember: two strings get compared with ==, and two numbers get compared with <, <=, ==, >=, or > πŸ”₯

Last but not least, the For-Do-Begin-End loop:

For $i=1 To 20 Do
Begin
 Do {Exec xcalc &}             # List of instructions
End

πŸ’… COMMANDS πŸ’…

The following fvwm command can be run at any time, so go wild 🌸

SendToModule ScriptName SendString id sig str

It sends the string to any module whose alias or name matches ScriptName πŸ’Œ

SendString id sig str

When an FvwmScript gets a message like this, it sends the signal numbered sig to the Widget id, and the string str can be grabbed with the LastString function. Let's do an example, because examples are everything πŸ₯°

Say you've got a script called MyScript with this widget:

Widget 50
Property
 Type PushButton
 Title {Quit}
 ...
Main
Case message of

  SingleClic:
  Begin
    Quit
  End

  1 :
  Begin
    Set $str = (LastString)
    If $str == {Quit} Then
      Quit
    Else
      ChangeTitle 33 $str
  End

End

Then this command:

SendToModule MyScript SendString 50 1 str

forces MyScript to exit if str is equal to "Quit". And if it isn't? It changes the title of Widget 33 to str instead πŸ¦‹

This command can be used to change the window title πŸŽ€

SendToModule ScriptName ChangeWindowTitle  newTitle [oldTitle]

It makes any module whose alias or name matches ScriptName change its associated window title to newTitle. The optional argument oldTitle is useful when there are several instances of the same script. It lets you avoid renaming all of those instances, by naming the window that belongs to the target script (see the example below) πŸ‘―β€β™€οΈ

+ I Module FvwmScript FvwmStorageSend "/dev/hda6"
+ I Wait FvwmStorageSend
+ I SendToModule FvwmStorageSend ChangeWindowTitle HDA6
+ I Module FvwmScript FvwmStorageSend "/dev/hda1"
+ I Wait FvwmStorageSend
+ I SendToModule FvwmStorageSend ChangeWindowTitle HDA1 FvwmStorageSend

Without the FvwmStorageSend argument in that last case, the SendToModule command would've changed the name of both instances of FvwmStorageSend to HDA1 😭 So yeah, don't skip that argument.


πŸ¦‹ EXAMPLES πŸ¦‹

You'll find example scripts in the fvwm configuration directory πŸ“‚

FvwmScript-BellSetup, FvwmScript-KeyboardSetup, FvwmScript-PointerSetup and FvwmScript-ScreenSetup are a set of scripts that tweak X settings πŸ–±οΈ They save your preferences into a file named ~/.xinit-fvwmrc. (If you want a different file name, pass it in as the first argument of the script.) If you want these preferences loaded on every startup, add the line ".xinit-fvwmrc" to your .xinitrc (or .xsession) file before you start fvwm ✨

FvwmScript-BaseConfig is a big one πŸ’œ It changes fvwm's focus and paging mouse policy, window placement, opacity, and other features of the move and resize commands, plus snap attraction and shading animation. It saves your preferences into a file named .FvwmBaseConfig in the user's data directory (that's $HOME/.fvwm, or $FVWM_USERDIR if it's set). To load these preferences on every startup, add the line "Read .FvwmBaseConfig" to your fvwm configuration file. Want a different file name? Give it as the first argument of the script.

When you click Ok or Apply, an fvwm function called BaseConfigOkFunc or BaseConfigApplyFunc gets called, if you've defined it. This is handy for reloading application styles that the script has wiped out. For example: AddToFunc BaseConfigOkFunc I Read MyAppStyle 🫢

FvwmScript-Buttons is a buttons panel that can replace FvwmButtons. It supports popup menus and requires xload, xclock, FvwmPager, and TkDesk πŸ”₯ FvwmScript-Colorset lets you edit your colorset 🎨 FvwmScript-Date lets you set the date and time ⏰ FvwmScript-FileBrowser is a file browser that the other scripts use behind the scenes πŸ“ FvwmScript-Find is an elementary front-end to find πŸ” FvwmScript-Quit lets you quit fvwm, restart fvwm or some other window manager, or shut down and reboot the computer πŸ‘‘ FvwmScript-ScreenDump is a screen dumper πŸ“Έ And FvwmScript-WidgetDemo is a pure example script 🌈 See the next section for FvwmScript-ComExample πŸ’–

✨ A COMMUNICATION PROTOCOL ✨

okay so FvwmScript is a weak (but simple) programming language πŸ’… bestie, if you're dealing with a LOT of data or complex algorithms, don't force it. Hand that work to an external program (perl, for example) and then "send" the info you need into your FvwmScript script 🧚. There are three ways to do this:

  • GetOutput 🌸: simple, but you have to rerun the external program every single time you want info from it, and that can cause performance problems 😬
  • SendMsgAndGet πŸ¦‹: this one extends FvwmScript so any programming language that can deal with named pipes (fifos) can join the party. This section is all about it πŸ’œ
  • fvwm-themes-com πŸŽ€: this comes from the fvwm-themes package. Ngl, SendMsgAndGet is basically an implementation of fvwm-themes-com inside FvwmScript, and it gives better performance ✨

Basically, you start an "external" program (the program for short) from your FvwmScript script (the script for short). The program runs in the background, and you use the SendMsgAndGet function in your script to ask questions or give instructions 🫢. The program has to strictly follow a certain communication protocol, no exceptions πŸ‘‘.

First up, there's an identifier called comId for the communication. For a good implementation, it should contain the process id of the script. So use the GetPid function and pass the comId to the program via an option πŸ”₯.

The protocol uses two fifos in the fvwm user directory, named .tmp-com-in-comId and .tmp-com-out-comId. The program should create and listen on the .tmp-com-in-comId fifo. Then, when FvwmScript runs a function like this:

Set $answer = (SendMsgAndGet {comId} {cmd} bool)

FvwmScript writes the cmd onto that in fifo πŸ“. The program reads it and runs the matching action. It should remove the in fifo so multiple communications can happen 🌈

Now the bool part, which is lowkey the most important bit:

  • If bool is 0, FvwmScript does not wait for an answer. It returns 1 if the previous actions succeeded and 0 if they failed. In that case, the program should "go back" to the in fifo πŸ”
  • If bool is 1, FvwmScript waits 20 sec for an answer from the program and then returns that answer to the script. Note that bool is not passed to the program, because the program has to know which commands need an answer on its own 🧠

To answer, the program creates the .tmp-com-out-comId fifo and writes the answer on it. It should wait until FvwmScript reads the answer, then remove the out fifo and go back to the in fifo πŸ’–. The answer should be one line of no more than 32000 characters. If you need multiple lines, take a look at the Parse function so they get handled as one line ✨

A simple way to understand this protocol, and to write scripts and programs that use it, is to check out the example FvwmScript-ComExample and fvwm-script-ComExample.pl. Both can be found in the fvwm data directory. (not useful) πŸ’€ Also, this implementation of the protocol is smart enough to handle questions like: What to do if the script exits for a bad reason? What to do if the program exits for a bad reason? ...etc. 🧚


πŸ› BUGS πŸ›

FvwmScript crashes if widgets are accessed that have not been defined. Don't do it, bestie 😭

πŸ‘©β€πŸ’» AUTHOR πŸ‘©β€πŸ’»

Frederic Cordier (cordie97@cui.unige.ch or f-cord96@univ-lyon1.fr) πŸ’œ

🫢 CONTRIBUTOR 🫢

Eddy J. Gurney (eddy@gizmo.aa.ans.net) 🌸


That's the whole page, bestie πŸŽ€ Source: 3rd Berkeley Distribution, version 2.7.0, dated 19 October 2022 πŸ¦‹ FvwmScript(1) signing off πŸ’…βœ¨