Everything Everywhere All At Once
This page is generated directly from the script that I use to test Gtk-Stream. The syntax and idioms used within are guaranteed to work with the latest version.
The script is intended to showcase all the features of GTK-Stream. Don't expect it to do anything useful. Here is what it looks like :
Initialization
First of all, you need to start the Gtk-Stream coprocess. Since
version 0.11.3, gtk-stream
provides a --hook bash
option that
writes all the boilerplate for you.
GTK-Stream boilerplate in Bash
$ gtk-stream --hook bash
coproc GTK_STREAM { exec gtk-stream; }
trap 'kill -INT "$GTK_STREAM_PID"; wait' EXIT
exec {GTK_STREAM_EVENTS}<&"${GTK_STREAM[0]}"
exec {GTK_STREAM_MESSAGES}>&"${GTK_STREAM[1]}"
function GTK.send() {
if (($# > 0)); then
printf "$@" >&"$GTK_STREAM_MESSAGES"
else
cat >&"$GTK_STREAM_MESSAGES"
fi
}
function GTK.receive() {
read -u "$GTK_STREAM_EVENTS" "$@"
}
Using this option, all we have to do is :
eval "$(gtk-stream --hook bash)"
We'll look for images in the same directory as the main script, so let's find out where it is
HERE="$(dirname "$0")"
And we can communicate with the GTK-Stream coprocess via the
GTK.send
and GTK.receive
functions.
Starting the application
Now to the good part. First of all we need to open an application
GTK.send '<application application-id="net.gtk-stream.example.basic">'
And give it some style
GTK.send <<EOF
<style>
.frame { color: red; margin: 10px; }
.tux { min-width: 100px; border: 1px solid teal; }
</style>
EOF
And then open a window. This is a testing window, so it contains a little of everything that Gtk-Stream handles. If you need more features, you can request them on the issue tracker
GTK.send <<EOF
<add-icon-path path="$HERE/img" />
<window id="window1" title="Hello to all !" default-width="800" default-height="600" icon-name="logo-gtk-sm">
<scrolled-window>
<box id="box1" orientation="vertical">
<box orientation="horizontal">
<icon file="$HERE/img/logo-gtk-sm.png" />
<button id="hello"><label text="Hello" /></button>
<button id="hello_after"><label text="Hello after" /></button>
<button id="close"><label text="Close" /></button>
<button id="progress"><label text="Progress" /></button>
<button id="open"><label text="Open..." /></button>
<button id="remove"><label text="Remove me" /></button>
</box>
<separator orientation="horizontal" />
<box>
<entry id="entry_key" placeholder-text="A key" />
<entry hexpand="true" id="entry_value" placeholder-text="A value" />
</box>
<button id="add_item"><label text="Add to dropdown" /></button>
<grid>
<cell x="0" y="0"><label hexpand="true" hexpand-set="true" text="Switch me" /></cell>
<cell x="1" y="0"><switch managed="true" id="switch1" /></cell>
<cell x="0" y="1"><label hexpand="true" hexpand-set="true" text="Switch me too" /></cell>
<cell x="1" y="1"><switch id="switch2" /></cell>
</grid>
<link id="link1"><label text="Click me" /></link>
<scale id="scale1" orientation="horizontal" adjustment="0:20:14" />
<progress-bar id="prog" show-text="true" text="progress" fraction="0.2" />
<dropdown id="dd" enable-search="true" search-match-mode="substring">
<item key="a" value="Pick me"><label text="Pick me" /></item>
<item key="b" value="No, pick me"><label text="No, pick me" /></item>
<item key="c" value="No, me"><label text="No, me" /></item>
</dropdown>
<paned vexpand="true" vexpand-set="true" orientation="horizontal">
<picture css-classes="tux" file="$HERE/img/tux.png" />
<label text="Middle" />
<label text="Right" />
</paned>
<frame css-classes="frame">
<frame-label><label text="Some frame" /></frame-label>
<grid id="main_grid" hexpand="true" hexpand-set="true">
<cell x="0" y="0"><label hexpand="true" hexpand-set="true" text="A" /></cell>
<cell x="1" y="0" h="2"><label hexpand="true" hexpand-set="true" text="B" /></cell>
<cell x="0" y="1"><label hexpand="true" hexpand-set="true" text="C" /></cell>
</grid>
</frame>
<box>
<switch id="stack-switch" />
<stack id="stack">
<label id="stack/a" text="I'm on top" />
<label id="stack/b" text="No, I am" />
</stack>
</box>
<box>
<stack id="stack2">
<stack-page title="AAA"><label id="stack2/a" text="AAAAAA" /></stack-page>
<stack-page title="BBB"><label id="stack2/b" text="BBBBBB" /></stack-page>
</stack>
<box-prepend><stack-side-bar stack="stack2" /></box-prepend>
</box>
</box>
</scrolled-window>
</window>
EOF
The Main Loop (in Bash)
Now we have a window up. Let's define some logic ! Here are some variables that will track the state of our application.
i=0
progress=20
declare -A SWITCH_ACTIONS=( )
stack_visible="a"
dd_key=""
dd_val=""
And we can start receiving events, one at a time
while GTK.receive ev; do
We can print them
printf "Received event: %s\n" "$ev"
We can also parse them. They are always of the form
$WIDGET_ID:$EVENT_TYPE[:$ADDITIONAL_DATA]
case "$ev" in
First example, but important : we can react to events by sending
more actions to the coprocess (in this case, setting the
visible-child
property of a Gtk.Stack)
stack-switch:switch:on)
GTK.send '<set-prop id="stack" name="visible-child" value="stack/b" />';;
stack-switch:switch:off)
GTK.send '<set-prop id="stack" name="visible-child" value="stack/a" />';;
Other than that, you're still in Bash, so you can spawn background tasks or whatever, and those tasks can also communicate with the coprocess (because they inherited the file descriptor)
switch1:switch:on)
(
set -e
sleep 2
GTK.send '<set-prop id="%s" name="state" value="true" />' "${ev%%:*}"
) &
SWITCH_ACTIONS[${ev%%:*}]="$!"
;;
switch1:switch:off)
switch_id="${ev%%:*}"
if [ -n "${SWITCH_ACTIONS[$switch_id]}" ]; then
kill "${SWITCH_ACTIONS[$switch_id]}"
unset SWITCH_ACTIONS[$switch_id]
fi
GTK.send '<set-prop id="%s" name="state" value="false" />' "$switch_id"
;;
This is more of the same, setting a property when a link is clicked
link1:clicked)
GTK.send '<set-prop id="link1" name="visited" value="true" />'
;;
Another similar but important feature : you can react to an event by creating new widgets, and adding them where you want.
Here, we use the i
variable to count the number of times a label
has been inserted that way, and show that number in the labels
themselves.
hello:clicked)
GTK.send '<insert into="box1"><label text="Hello "%d"" /></insert>' "$i"
((i++))
;;
hello_after:clicked)
GTK.send '<insert into="box1"><box-prepend after="link1"><label text="Hello "%d"" /></box-prepend></insert>' "$i"
((i++))
;;
progress:clicked)
((progress+=5))
GTK.send '<set-prop id="prog" name="fraction" value="%d.%02d" />' "$((progress / 100))" "$((progress % 100))"
;;
In the same vein, we can add items to a dropdown, simply by adding
an
entry_key:changed:*) dd_key="${ev#entry_key:changed:}";;
entry_value:changed:*) dd_val="${ev#entry_value:changed:}";;
add_item:clicked)
GTK.send '<insert into="dd"><item key="%s" value="%s"><label text="%s" /></item></insert>' "$dd_key" "$dd_val" "$dd_val";;
We can also remove any widget from its parent (including the widget that received the event in the first place)
remove:clicked)
GTK.send '<remove id="remove" />';;
File dialogs are cool. Let's use one
open:clicked)
GTK.send '<file-dialog id="file" parent="window1" />'
;;
When it exits, if the user has selected a file, do a ls -l
file:selected:*)
ls -l "${ev#file:selected:}";;
And finally, we can close a window
close:clicked)
GTK.send '<close-window id="window1" />';;
When the main window is closed, exit our main loop
window1:close-request)
break;;
esac
done
And close the application. You don't have to do that since Gtk-Stream will be killed when the script exits.
It's good manners to do it anyway.
GTK.send '</application>'