gtk-stream 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gtk_stream/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ import gi
2
+ gi.require_version("Gtk", "4.0")
3
+ from gi.repository import Gtk, Gdk, GLib, Gio, GObject
4
+
@@ -0,0 +1,85 @@
1
+ import sys
2
+
3
+ from . import Gtk, GLib, Gdk
4
+ from .common import printEvent
5
+ from .properties import parse_property
6
+
7
+ class GtkStreamApp(Gtk.Application):
8
+ def __init__(self, name = None, **kwargs):
9
+ super().__init__(**kwargs)
10
+ if name != None:
11
+ GLib.set_application_name(name)
12
+ self.namedWidgets = { }
13
+ self.namedWindows = { }
14
+
15
+ self.callback_queue = []
16
+
17
+ def run_when_idle_before_startup(cb):
18
+ self.callback_queue.append(cb)
19
+ self.run_when_idle = run_when_idle_before_startup
20
+
21
+ def on_startup(_):
22
+ for cb in self.callback_queue:
23
+ GLib.idle_add(cb)
24
+ self.run_when_idle = GLib.idle_add
25
+ self.connect('startup', on_startup)
26
+
27
+ def nameWidget(self, id, w):
28
+ if id is not None:
29
+ self.namedWidgets[id] = w
30
+
31
+ def openFileDialog(self, id, parent):
32
+ def cb():
33
+ dialog = Gtk.FileDialog()
34
+ dialog.props.modal = True
35
+ def on_choose(_, b):
36
+ try:
37
+ file = dialog.open_finish(b)
38
+ print(f"{id}:selected:{file.get_path()}")
39
+ sys.stdout.flush()
40
+ except GLib.GError as e:
41
+ print(f"{id}:none-selected")
42
+ sys.stdout.flush()
43
+
44
+ dialog.open(parent = self.namedWindows[parent], callback = on_choose)
45
+ self.run_when_idle(cb)
46
+ def newWindow(self, document, id, title = "Window", width = None, height = None):
47
+ def cb():
48
+ win = Gtk.Window(application=self)
49
+ win.set_title(title)
50
+ if width != None and height != None:
51
+ win.set_default_size(int(width), int(height))
52
+ self.namedWindows[id] = win
53
+ win.set_child(document.render())
54
+ win.connect('close-request', printEvent('close-request', id))
55
+ win.present()
56
+ return False
57
+ self.run_when_idle(cb)
58
+ def addStyle(self, style):
59
+ def cb():
60
+ provider = Gtk.CssProvider()
61
+ provider.load_from_data(style)
62
+ Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
63
+ self.run_when_idle(cb)
64
+ def closeWindow(self, id):
65
+ def cb():
66
+ self.namedWindows[id].close()
67
+ self.run_when_idle(cb)
68
+ def removeWidget(self, id):
69
+ def cb():
70
+ w = self.namedWidgets[id]
71
+ w.get_parent().remove(w)
72
+ self.run_when_idle(cb)
73
+ def appendWidget(self, to, document):
74
+ def cb():
75
+ if to in self.namedWidgets:
76
+ w = self.namedWidgets[to]
77
+ w.attach_child(document)
78
+ else:
79
+ raise Exception(f"Error: unknown widget id '{to}'")
80
+ self.run_when_idle(cb)
81
+ def setProp(self, id, name, value):
82
+ def cb():
83
+ w = self.namedWidgets[id]
84
+ w.set_property(name, parse_property(name, value))
85
+ self.run_when_idle(cb)
@@ -0,0 +1,14 @@
1
+ import io
2
+ import xml.sax as sax
3
+ import signal
4
+
5
+ from .parser import GtkStreamXMLHandler
6
+
7
+ def main():
8
+ handler = GtkStreamXMLHandler()
9
+ parser = sax.make_parser()
10
+ parser.setContentHandler(handler)
11
+ try:
12
+ parser.parse(io.FileIO(0, 'r', closefd=False))
13
+ finally:
14
+ handler.app.release()
gtk_stream/common.py ADDED
@@ -0,0 +1,10 @@
1
+ import sys
2
+
3
+ def printEvent(event, id):
4
+ def ret(w):
5
+ try:
6
+ print(f"{id}:{event}", file=sys.stdout)
7
+ sys.stdout.flush()
8
+ except:
9
+ print("Broken pipe, right ?", file=sys.stderr)
10
+ return ret
@@ -0,0 +1,11 @@
1
+ from .classes.Document import Document
2
+
3
+ from .classes.Label import Label
4
+ from .classes.Button import Button
5
+ from .classes.ProgressBar import ProgressBar
6
+ from .classes.Dropdown import Dropdown, Item
7
+
8
+ from .classes.Box import Box
9
+ from .classes.Paned import Paned
10
+ from .classes.Frame import Frame
11
+ from .classes.Grid import Grid, Cell
@@ -0,0 +1,10 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class Box(Document):
5
+ def __init__(self, app, **kwargs):
6
+ super().__init__(app, **kwargs)
7
+ def render_raw(self):
8
+ return Gtk.Box()
9
+ def attach_child(self, w, d):
10
+ w.append(d.render())
@@ -0,0 +1,13 @@
1
+ from ... import Gtk
2
+ from ...common import printEvent
3
+ from .. import Document
4
+
5
+ class Button(Document):
6
+ def __init__(self, app, id, **kwargs):
7
+ super().__init__(app, id = id, **kwargs)
8
+ def render_raw(self):
9
+ button = Gtk.Button()
10
+ button.connect('clicked', printEvent('clicked', self.id))
11
+ return button
12
+ def attach_child(self, w, d):
13
+ w.set_child(d.render())
@@ -0,0 +1,30 @@
1
+ import sys
2
+ from ...properties import parse_property
3
+
4
+ class Document:
5
+ def __init__(self, app, id = None, **attrs):
6
+ self.id = id
7
+ self.app = app
8
+ self.props = { attr: parse_property(attr, val) for (attr, val) in attrs.items() }
9
+ self.children = []
10
+
11
+ def add_child(self, child):
12
+ self.children.append(child)
13
+
14
+ def render_raw(self):
15
+ """Method to render the document to a widet"""
16
+ raise Exception("Method 'render' not implemented")
17
+ def set_properties(self, w):
18
+ self.app.nameWidget(self.id, w)
19
+ for (p,v) in self.props.items():
20
+ w.set_property(p, v)
21
+ w.attach_child = lambda d: self.attach_child(w, d)
22
+ def render(self):
23
+ w = self.render_raw()
24
+ self.set_properties(w)
25
+ for child in self.children:
26
+ self.attach_child(w, child)
27
+ return w
28
+
29
+ def attach_child(self, w, child):
30
+ raise Exception("Unimplemented method 'attach_child'")
@@ -0,0 +1,50 @@
1
+ from ... import Gtk, Gio, GObject
2
+ from .. import Document
3
+
4
+ class Item(Document):
5
+ def __init__(self, app, value, **kwargs):
6
+ super().__init__(app, **kwargs)
7
+ self.child = None
8
+ self.value = value
9
+ def add_child(self, child):
10
+ self.child = child
11
+ def render(self):
12
+ return self.child.render()
13
+ class _ItemObject(GObject.Object):
14
+ def __init__(self, doc):
15
+ super().__init__()
16
+ self.doc = doc
17
+ self.value = doc.value
18
+
19
+ @GObject.Property(type=str)
20
+ def item_value(self):
21
+ return self.value
22
+
23
+ class Dropdown(Document):
24
+ def __init__(self, app, id, **kwargs):
25
+ super().__init__(app, id=id, **kwargs)
26
+ def render_raw(self):
27
+ model = Gio.ListStore(item_type=_ItemObject)
28
+ for item in self.children:
29
+ model.append(_ItemObject(item))
30
+
31
+ factory = Gtk.SignalListItemFactory()
32
+ def on_list_setup(_, list_item):
33
+ list_item.item_value = None
34
+ def on_list_bind(_, list_item):
35
+ item = list_item.get_item()
36
+ if list_item.item_value != item.value:
37
+ list_item.item_value = item.value
38
+ list_item.set_child(item.doc.render())
39
+ factory.connect("setup", on_list_setup)
40
+ factory.connect("bind", on_list_bind)
41
+
42
+ ret = Gtk.DropDown(model=model, expression=Gtk.PropertyExpression.new(_ItemObject, None, 'item_value'), factory=factory)
43
+
44
+ def on_selection_change(w,d):
45
+ print(f"{self.id}:selected:{w.get_selected_item().value}")
46
+ sys.stdout.flush()
47
+ ret.connect('notify::selected-item', on_selection_change)
48
+ return ret
49
+ def attach_child(self, w, d):
50
+ pass
@@ -0,0 +1,14 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class Frame(Document):
5
+ def __init__(self, app, label=None, **kwargs):
6
+ super().__init__(app,**kwargs)
7
+ self.label = label
8
+ def render_raw(self):
9
+ ret = Gtk.Frame()
10
+ if self.label != None:
11
+ ret.set_label(self.label)
12
+ return ret
13
+ def attach_child(self, w, d):
14
+ w.set_child(d.render())
@@ -0,0 +1,24 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class Cell(Document):
5
+ def __init__(self, app, x, y, w="1", h="1"):
6
+ super().__init__(app)
7
+ self.child = None
8
+ self.x = int(x)
9
+ self.y = int(y)
10
+ self.w = int(w)
11
+ self.h = int(h)
12
+ def render(self):
13
+ return self.child.render()
14
+ def add_child(self, child):
15
+ self.child = child
16
+
17
+ class Grid(Document):
18
+ def __init__(self, app, **kwargs):
19
+ super().__init__(app, **kwargs)
20
+
21
+ def render_raw(self):
22
+ return Gtk.Grid()
23
+ def attach_child(self, w, d):
24
+ w.attach(d.render(), d.x, d.y, d.w, d.h)
@@ -0,0 +1,11 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class Label(Document):
5
+ def __init__(self, app, text, **kwargs):
6
+ super().__init__(app, **kwargs)
7
+ self.text = text
8
+ def render_raw(self):
9
+ l = Gtk.Label()
10
+ l.set_text(self.text)
11
+ return l
@@ -0,0 +1,20 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class Paned(Document):
5
+ def __init__(self, app, **kwargs):
6
+ super().__init__(app, **kwargs)
7
+ def render(self):
8
+ if len(self.children) == 1:
9
+ return self.children[0].render()
10
+
11
+ l = self.children[0].render()
12
+ for (r,rem_size) in zip(self.children[1:], range(len(self.children),1,-1)):
13
+ j = Gtk.Paned()
14
+ j.set_shrink_start_child(False)
15
+ j.set_shrink_end_child(False)
16
+ j.props.start_child = l
17
+ j.props.end_child = r.render()
18
+ self.set_properties(j)
19
+ l = j
20
+ return l
@@ -0,0 +1,8 @@
1
+ from ... import Gtk
2
+ from .. import Document
3
+
4
+ class ProgressBar(Document):
5
+ def __init__(self, app, **kwargs):
6
+ super().__init__(app, **kwargs)
7
+ def render_raw(self):
8
+ return Gtk.ProgressBar()
File without changes
gtk_stream/parser.py ADDED
@@ -0,0 +1,165 @@
1
+ import threading
2
+ import signal
3
+ import xml.sax as sax
4
+
5
+ from . import GLib
6
+ from .documents import Document, Label, Box, Button, ProgressBar, Dropdown, Item, Paned, Frame, Grid, Cell
7
+ from .application import GtkStreamApp
8
+
9
+ class _Object:
10
+ pass
11
+
12
+ WIDGET_DOCUMENTS = {
13
+ 'progress-bar': ProgressBar,
14
+ 'label': Label,
15
+ 'box': Box,
16
+ 'button': Button,
17
+ 'dropdown': Dropdown,
18
+ 'item': Item,
19
+ 'paned': Paned,
20
+ 'grid': Grid,
21
+ 'cell': Cell,
22
+ 'frame': Frame
23
+ }
24
+
25
+ class GtkStreamXMLHandler(sax.ContentHandler):
26
+ def __init__(self):
27
+ self.transition_enter = self.transE_conn
28
+ self.transition_leave = self.transL_final
29
+ self.transition_chars = self.ignore_chars
30
+ self.namedWidgets = { }
31
+ self.windows = { }
32
+
33
+ def setNamed(self, attrs, ):
34
+ if 'id' in attrs:
35
+ self.namedWidgets[attrs['id']] = widget
36
+ def ignore_chars(self, s):
37
+ pass
38
+
39
+ def transE_final(self, name, attrs):
40
+ raise Exception(f"Unexpected tag '{name}'")
41
+ def transL_final(self, name):
42
+ raise Exception(f"Unexpected end tag '{name}'")
43
+ def transL_tag(self, tag, enter, leave_parent, leave = None):
44
+ def ret(name):
45
+ if name == tag:
46
+ if leave != None:
47
+ leave()
48
+ self.transition_enter = enter
49
+ self.transition_leave = leave_parent
50
+ else:
51
+ raise Exception(f"Error: expected end tag '{tag}', got '{name}'")
52
+ return ret
53
+
54
+ def transE_conn(self, name, attrs):
55
+ match name:
56
+ case 'application':
57
+ self.app = GtkStreamApp(**attrs)
58
+ def on_activate(a):
59
+ a.hold()
60
+ self.app.connect('activate', on_activate)
61
+ def appMain():
62
+ self.app.run([])
63
+ threading.Thread(target = appMain).start()
64
+ def on_sigint(a,b):
65
+ def cb():
66
+ self.app.quit()
67
+ sys.exit(0)
68
+ GLib.idle_add(cb)
69
+ signal.signal(signal.SIGINT, on_sigint)
70
+ self.transition_enter = self.transE_message
71
+ self.transition_leave = self.transL_tag('application', self.transE_final, self.transL_final)
72
+ case _:
73
+ raise Exception("Error: expected 'application' tag")
74
+ def transE_message(self, name, attrs):
75
+ leave_parent = self.transition_leave
76
+ match name:
77
+ case 'style':
78
+ style = _Object()
79
+ def onchars(s):
80
+ style.chars = s
81
+ def leave():
82
+ self.transition_chars = self.ignore_chars
83
+ self.app.addStyle(style.chars)
84
+ self.transition_chars = onchars
85
+ self.transition_enter = self.transE_final
86
+ self.transition_leave = self.transL_tag('style', self.transE_message, leave_parent, leave)
87
+
88
+ case 'window':
89
+ if 'id' in attrs:
90
+ store = _Object()
91
+ def leave():
92
+ self.app.newWindow(store.child, **attrs)
93
+ def setChild(c):
94
+ store.child = c
95
+ self.transition_enter = self.transE_addChild(setChild)
96
+ self.transition_leave = self.transL_tag('window', self.transE_message, leave_parent, leave)
97
+ else:
98
+ raise Exception("Error: expected attribute 'id' in 'window' tag")
99
+
100
+ case 'file-dialog':
101
+ id = attrs.get('id')
102
+ parent = attrs.get('parent')
103
+ if id != None and parent != None:
104
+ self.app.openFileDialog(id, parent)
105
+ self.transition_enter = self.transE_final
106
+ self.transition_leave = self.transL_tag('file-dialog', self.transE_message, leave_parent)
107
+ else:
108
+ raise Exception("Error: expected 'id' and 'parent' attributes on 'file-chooser'")
109
+
110
+ case 'close-window':
111
+ if 'id' in attrs:
112
+ def leave():
113
+ self.app.closeWindow(attrs['id'])
114
+ self.transition_enter = self.transE_final
115
+ self.transition_leave = self.transL_tag('close-window', self.transE_message, leave_parent, leave)
116
+ else:
117
+ raise Exception("Error: expected 'id' attribute in 'close-window' tag")
118
+
119
+ case 'set-prop':
120
+ self.app.setProp(**attrs)
121
+ self.transition_enter = self.transE_final
122
+ self.transition_leave = self.transL_tag('set-prop', self.transE_message, leave_parent)
123
+
124
+ case 'append':
125
+ if 'to' in attrs:
126
+ children = []
127
+ def leave():
128
+ for child in children:
129
+ self.app.appendWidget(attrs['to'], child)
130
+ self.transition_enter = self.transE_addChild(lambda child: children.append(child))
131
+ self.transition_leave = self.transL_tag('append', self.transE_message, leave_parent, leave)
132
+ else:
133
+ raise Exception("Expected 'to' attribute of 'append' message")
134
+
135
+ case 'remove':
136
+ if 'id' in attrs:
137
+ def leave():
138
+ self.app.removeWidget(attrs['id'])
139
+ self.transition_enter = self.transE_final
140
+ self.transition_leave = self.transL_tag('remove', self.transE_message, leave_parent, leave)
141
+ else:
142
+ raise Exception("Expected 'id' attribute of 'remove' message")
143
+
144
+ case _:
145
+ raise Exception(f"Error: unknown message '{name}'")
146
+
147
+ def transE_addChild(self, addChild):
148
+ def ret(name, attrs):
149
+ leave_parent = self.transition_leave
150
+ doc_class = WIDGET_DOCUMENTS.get(name)
151
+ if doc_class != None:
152
+ doc = doc_class(self.app, **attrs)
153
+ addChild(doc)
154
+ self.transition_enter = self.transE_addChild(lambda child: doc.add_child(child))
155
+ self.transition_leave = self.transL_tag(name, self.transE_addChild(addChild), leave_parent)
156
+ else:
157
+ raise Exception(f"Error: Unknown widget {name}")
158
+ return ret
159
+
160
+ def characters(self, s):
161
+ self.transition_chars(s)
162
+ def startElement(self, name, attrs):
163
+ self.transition_enter(name, attrs)
164
+ def endElement(self, name):
165
+ self.transition_leave(name)
@@ -0,0 +1,38 @@
1
+ from . import Gtk
2
+
3
+ def _parse_orientation_property(val):
4
+ return (Gtk.Orientation.HORIZONTAL) if val == "horizontal" else (Gtk.Orientation.VERTICAL)
5
+ def _parse_boolean_property(val):
6
+ return True if val == "true" else False
7
+ def _parse_float_property(val):
8
+ return float(val)
9
+ def _parse_int_property(val):
10
+ return int(val)
11
+ def _parse_searchMode_property(val):
12
+ match val:
13
+ case 'exact':
14
+ return Gtk.StringFilterMatchMode.EXACT
15
+ case 'substring':
16
+ return Gtk.StringFilterMatchMode.SUBSTRING
17
+ case _:
18
+ return Gtk.StringFilterMatchMode.PREFIX
19
+ def _parse_css_classes_property(val):
20
+ return val.split()
21
+
22
+ _PARSE_PROPERTY = {
23
+ 'css-classes': _parse_css_classes_property,
24
+ 'orientation': _parse_orientation_property,
25
+ 'fraction': _parse_float_property,
26
+ 'show-text': _parse_boolean_property,
27
+ 'enable-search': _parse_boolean_property,
28
+ 'search-match-mode': _parse_searchMode_property,
29
+ 'hexpand': _parse_boolean_property,
30
+ 'hexpand-set': _parse_boolean_property,
31
+ 'vexpand': _parse_boolean_property,
32
+ 'vexpand-set': _parse_boolean_property,
33
+ 'xalign': _parse_float_property,
34
+ 'yalign': _parse_float_property
35
+ }
36
+
37
+ def parse_property(prop, val):
38
+ return _PARSE_PROPERTY.get(prop, lambda x: x)(val)
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: gtk-stream
3
+ Version: 0.1
4
+ Summary: A simple stream-oriented GUI protocol
5
+ Author-email: Marc Coiffier <marc.coiffier@univ-grenoble-alpes.fr>
6
+ Project-URL: Homepage, https://coiffiem.gricad-pages.univ-grenoble-alpes.fr/gtk-stream/
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Topic :: Software Development :: User Interfaces
10
+ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
11
+ Requires-Dist: pygobject
@@ -0,0 +1,22 @@
1
+ gtk_stream/__init__.py,sha256=pDhUSN4AYNsP8_tilU1zmDCYdiv7ak4PE1TW0qFrDlg,99
2
+ gtk_stream/application.py,sha256=FZe4gQm5U0NGKKpi_Pp7VqxMtlbdbNJOYhcradzeSH4,3068
3
+ gtk_stream/command_line.py,sha256=c2ghqIQB5hYxtCszss1z84yKoeRYvaMJJMi4aiTspWg,312
4
+ gtk_stream/common.py,sha256=6v6vIFzi1mZvATUzSIb9hPKLcdCexjfBJJm2vafD8Ps,241
5
+ gtk_stream/parser.py,sha256=GHq08hKSEVBphtnGm4G_evfE2PeB0QLzp5PDM6fR890,6731
6
+ gtk_stream/properties.py,sha256=e6A2tjNjTVIGbNNQE8rOQUwB1uP9AZvIJl3MzdhbPIg,1392
7
+ gtk_stream/documents/__init__.py,sha256=JArZ2Ry5u4Y9hyzdoBG_W47PgqZ-Og1UdWDdUVjhlW0,331
8
+ gtk_stream/documents/classes/Box.py,sha256=6l9Z3GpC0Yv34jcGNTwlNLM5DpYyQ1iDihJsu1nxcy8,259
9
+ gtk_stream/documents/classes/Button.py,sha256=CYTKNl0OR-xPB9VWvyXtnAtZWuQQ9CxrtxoqjxiwgMM,404
10
+ gtk_stream/documents/classes/Document.py,sha256=scoJrdHen_1WRuqn3aN6FJUesHXPZZyKwmPuPDjjbzI,985
11
+ gtk_stream/documents/classes/Dropdown.py,sha256=uDH1yfYoEGugen6O62EMf7Dh9WfqFjUj_fgxFZ395VY,1718
12
+ gtk_stream/documents/classes/Frame.py,sha256=p8OcgYBki1NWe2tveZfRwb0aQzRAeHQa9wHmpjgRJoc,391
13
+ gtk_stream/documents/classes/Grid.py,sha256=SRDbMhPZHV372KN7Qr9vvamH2xwr39eojeWmI0Rf8hE,622
14
+ gtk_stream/documents/classes/Label.py,sha256=BeL3zlf9YPuBGYS_YtHI0Q-MoVVLPao7kPIzPlwpyHY,275
15
+ gtk_stream/documents/classes/Paned.py,sha256=4jZGEebhv9pNzTyYbQ8lyx_nsKd9Nmr10703V5n_kNw,644
16
+ gtk_stream/documents/classes/ProgressBar.py,sha256=2_KCboyweGsqpBck61TvVGZYIsN8QVzAyqwwTDh-pDU,212
17
+ gtk_stream/documents/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ gtk_stream-0.1.dist-info/METADATA,sha256=Hq2t55xlbQkAi4hXM9-0sykB31YXCN16TQq1aztu4tI,504
19
+ gtk_stream-0.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
+ gtk_stream-0.1.dist-info/entry_points.txt,sha256=PmhKTb4MMQM6dN2HJcoDSMI8L0lZIFIlFn-BgdfPDpo,60
21
+ gtk_stream-0.1.dist-info/top_level.txt,sha256=vE9zfHGe9Ke7FSe0wBK2WYJI-BpcQNu6xDC3Cu5O8rQ,11
22
+ gtk_stream-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gtk-stream = gtk_stream.command_line:main
@@ -0,0 +1 @@
1
+ gtk_stream