summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHenrik Rydberg <rydberg@euromail.se>2011-04-28 18:42:33 +0200
committerHenrik Rydberg <rydberg@euromail.se>2011-04-28 18:42:33 +0200
commit5423ccd321c1c84b0e7736b10b2fe9eeb1833e16 (patch)
treefc12f236a865dceb6008d9bc5691b0fd0cbe582b /src
parentd87cde46e25c058ffc76d3871ca4e2ceb3e57991 (diff)
Introduce gesture frames
This patch extends the API with parallel new/delete functions, aiming to eventually replace the open/close function. The new functions give access to the grail gesture frames, containing gestural transform information. This information is useful in its own right, and will eventually replace the internal recognizer. Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/grail-frame.c399
-rw-r--r--src/grail-impl.h38
-rw-r--r--src/grail-init.c241
4 files changed, 681 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index b26574c..5011c1f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -26,7 +26,9 @@ libutouch_grail_la_SOURCES = \
26 grail-event.c \ 26 grail-event.c \
27 grail-impl.h \ 27 grail-impl.h \
28 grail-api.c \ 28 grail-api.c \
29 grail-legacy.c 29 grail-legacy.c \
30 grail-init.c \
31 grail-frame.c
30 32
31# These are flags required to properly set up 33# These are flags required to properly set up
32# the build. -fvisibility requires GCC > 4 (or clang) 34# the build. -fvisibility requires GCC > 4 (or clang)
diff --git a/src/grail-frame.c b/src/grail-frame.c
new file mode 100644
index 0000000..cf38452
--- /dev/null
+++ b/src/grail-frame.c
@@ -0,0 +1,399 @@
1/*****************************************************************************
2 *
3 * grail - Gesture Recognition And Instantiation Library
4 *
5 * Copyright (C) 2010 Canonical Ltd.
6 * Copyright (C) 2010 Henrik Rydberg <rydberg@bitmath.org>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23#include "grail-impl.h"
24#include <stdlib.h>
25#include <string.h>
26#include <math.h>
27
28static void set_center_velocity_and_radius(struct grail_impl *impl,
29 struct grail_element *slot)
30{
31 const struct utouch_contact **tc = slot->touches;
32 double x, y, vx, vy, r2, dx, dy;
33 int i;
34
35 switch (slot->num_touches) {
36 case 1:
37 x = tc[0]->x;
38 y = tc[0]->y;
39 vx = tc[0]->vx;
40 vy = tc[0]->vy;
41 r2 = 0;
42 break;
43 case 2:
44 dx = 0.5 * (tc[1]->x - tc[0]->x);
45 dy = 0.5 * (tc[1]->y - tc[0]->y);
46 x = tc[0]->x + dx;
47 y = tc[0]->y + dy;
48 vx = 0.5 * (tc[0]->vx + tc[1]->vx);
49 vy = 0.5 * (tc[0]->vy + tc[1]->vy);
50 r2 = dx * dx + dy * dy;
51 break;
52 default:
53 x = y = vx = vy = r2 = 0;
54 for (i = 0; i < slot->num_touches; i++) {
55 x += tc[i]->x;
56 y += tc[i]->y;
57 vx += tc[i]->vx;
58 vy += tc[i]->vy;
59 }
60 x /= slot->num_touches;
61 y /= slot->num_touches;
62 vx /= slot->num_touches;
63 vy /= slot->num_touches;
64 for (i = 0; i < slot->num_touches; i++) {
65 dx = tc[i]->x - x;
66 dy = tc[i]->y - y;
67 r2 += dx * dx + dy * dy;
68 }
69 r2 /= slot->num_touches;
70 break;
71 }
72
73 slot->center.x = x;
74 slot->center.y = y;
75 slot->velocity.x = 1000 * vx;
76 slot->velocity.y = 1000 * vy;
77 slot->radius2 = r2;
78}
79
80static void set_moveness_pivot_and_drag(struct grail_impl *impl,
81 struct grail_element *slot,
82 double ds, double dc)
83{
84 const struct grail_control *ctl = impl->ctl;
85 const struct grail_element *pslot = slot->prev;
86 double mx = slot->center.x - pslot->center.x;
87 double my = slot->center.y - pslot->center.y;
88 float *T = slot->transform;
89
90 slot->moveness = 1;
91 slot->pivot = pslot->center;
92
93 if (slot->num_touches > 1) {
94 double wx = (1 - dc) * mx + ds * my;
95 double wy = (1 - dc) * my - ds * mx;
96 double w2 = wx * wx + wy * wy;
97 if (w2 > 0) {
98 double q = (mx * mx + my * my) / w2;
99 double s = ctl->pivot_unbound ?
100 q : sqrt(pslot->radius2 / w2);
101 if (s < q) {
102 slot->moveness = 1 - s / q;
103 slot->pivot.x += s * wx;
104 slot->pivot.y += s * wy;
105 } else {
106 slot->moveness = 0;
107 slot->pivot.x += q * wx;
108 slot->pivot.y += q * wy;
109 }
110 }
111 }
112
113 mx *= slot->moveness;
114 my *= slot->moveness;
115
116 T[0] = dc;
117 T[1] = ds;
118 T[2] = (1 - dc) * slot->pivot.x - ds * slot->pivot.y + mx;
119 T[3] = -ds;
120 T[4] = dc;
121 T[5] = (1 - dc) * slot->pivot.y + ds * slot->pivot.x + my;
122
123 slot->drag.x = pslot->drag.x + mx;
124 slot->drag.y = pslot->drag.y + my;
125}
126
127static void start_slot(struct grail_impl *impl,
128 struct grail_element *slot,
129 const struct utouch_frame *touch)
130{
131 float *T = slot->transform;
132
133 slot->id = impl->seqid++ & GRAIL_ID_MAX;
134 slot->expect_mask = GRAIL_EXPECT_MASK;
135 slot->active_mask = 0;
136 slot->start_time = touch->time;
137 set_center_velocity_and_radius(impl, slot);
138 slot->start_center = slot->center;
139 T[0] = T[4] = 1;
140 T[1] = T[2] = T[3] = T[5] = 0;
141 slot->pivot = slot->center;
142 slot->drag.x = 0;
143 slot->drag.y = 0;
144 slot->scale2 = 1;
145 slot->angle = 0;
146}
147
148static void update_slot(struct grail_impl *impl,
149 struct grail_element *slot,
150 double ds, double dc)
151{
152 const struct grail_element *pslot = slot->prev;
153
154 slot->id = pslot->id;
155 slot->start_time = pslot->start_time;
156 slot->start_center = pslot->start_center;
157 slot->expect_mask = pslot->expect_mask;
158 slot->active_mask = pslot->active_mask;
159
160 set_center_velocity_and_radius(impl, slot);
161 set_moveness_pivot_and_drag(impl, slot, ds, dc);
162
163 slot->scale2 = pslot->scale2 * (ds * ds + dc * dc);
164 slot->angle = pslot->angle + ds / dc; /* atan2(ds, dc) */
165}
166
167static void stop_slot(struct grail_impl *impl,
168 struct grail_element *slot)
169{
170 const struct grail_element *pslot = slot->prev;
171 float *T = slot->transform;
172
173 slot->id = -1;
174 slot->num_touches = 0;
175 slot->start_time = pslot->start_time;
176 slot->start_center = pslot->start_center;
177 slot->expect_mask = 0;
178 slot->active_mask = pslot->active_mask;
179 slot->center = pslot->center;
180 slot->velocity = pslot->velocity;
181 slot->radius2 = pslot->radius2;
182 T[0] = T[4] = 1;
183 T[1] = T[2] = T[3] = T[5] = 0;
184 slot->moveness = 1;
185 slot->pivot = pslot->pivot;
186 slot->drag = pslot->drag;
187 slot->scale2 = pslot->scale2;
188 slot->angle = pslot->angle;
189}
190
191static void set_slot_one(struct grail_impl *impl,
192 struct grail_element *slot,
193 const struct utouch_frame *touch,
194 const struct utouch_contact *t1)
195{
196 const struct grail_element *pslot = slot->prev;
197 const struct utouch_contact *p1 = pslot->touches[0];
198
199 if (!t1->active) {
200 stop_slot(impl, slot);
201 return;
202 }
203
204 slot->touches[0] = t1;
205 slot->num_touches = 1;
206
207 if (pslot->num_touches != slot->num_touches || t1->id != p1->id) {
208 start_slot(impl, slot, touch);
209 return;
210 }
211
212 update_slot(impl, slot, 0, 1);
213}
214
215static void set_slot_two(struct grail_impl *impl,
216 struct grail_element *slot,
217 const struct utouch_frame *touch,
218 const struct utouch_contact *t1,
219 const struct utouch_contact *t2)
220{
221 const struct grail_element *pslot = slot->prev;
222 const struct utouch_contact *p1 = pslot->touches[0];
223 const struct utouch_contact *p2 = pslot->touches[1];
224 double tx, ty, px, py, d2;
225
226 if (!t1->active || !t2->active) {
227 stop_slot(impl, slot);
228 return;
229 }
230
231 slot->touches[0] = t1;
232 slot->touches[1] = t2;
233 slot->num_touches = 2;
234
235 if (pslot->num_touches != slot->num_touches ||
236 t1->id != p1->id || t2->id != p2->id) {
237 start_slot(impl, slot, touch);
238 return;
239 }
240
241 tx = t2->x - t1->x;
242 ty = t2->y - t1->y;
243 px = p2->x - p1->x;
244 py = p2->y - p1->y;
245
246 d2 = px * px + py * py;
247 if (d2 > 0) {
248 px /= d2;
249 py /= d2;
250 }
251
252 update_slot(impl, slot, tx * py - ty * px, tx * px + ty * py);
253}
254
255static void set_slot_multi(struct grail_impl *impl,
256 struct grail_element *slot,
257 struct grail_frame *frame,
258 const struct utouch_frame *touch)
259{
260 const struct grail_element *pslot = slot->prev;
261 struct grail_element **slots = frame->slots;
262 int i, j, n = impl->num_touches;
263 struct grail_element *best = 0;
264
265 if (touch->num_active < 3) {
266 stop_slot(impl, slot);
267 return;
268 }
269
270 memcpy(slot->touches, touch->active,
271 touch->num_active * sizeof(slot->touches[0]));
272 slot->num_touches = touch->num_active;
273
274 if (pslot->num_touches != slot->num_touches) {
275 start_slot(impl, slot, touch);
276 return;
277 }
278
279 for (i = 0; i < slot->num_touches; i++) {
280 if (slot->touches[i]->id != pslot->touches[i]->id) {
281 start_slot(impl, slot, touch);
282 return;
283 }
284 }
285
286 for (i = 0; i < impl->num_touches; i++) {
287 for (j = i + 1; j < impl->num_touches; j++) {
288 struct grail_element *s = slots[n++];
289 if (!s->num_touches)
290 continue;
291 if (!best || s->radius2 > best->radius2)
292 best = s;
293 }
294 }
295
296 update_slot(impl, slot, best->transform[1], best->transform[0]);
297}
298
299static void set_slots(struct grail_impl *impl,
300 struct grail_frame *frame,
301 const struct utouch_frame *touch)
302{
303 struct grail_element **slots = frame->slots;
304 struct utouch_contact *const *tc = touch->slots;
305 int i, j, n = 0;
306
307 for (i = 0; i < impl->num_touches; i++)
308 set_slot_one(impl, slots[n++], touch, tc[i]);
309
310 for (i = 0; i < impl->num_touches; i++)
311 for (j = i + 1; j < impl->num_touches; j++)
312 set_slot_two(impl, slots[n++], touch, tc[i], tc[j]);
313
314 set_slot_multi(impl, slots[n++], frame, touch);
315}
316
317static void collect_transforms(struct grail_impl *impl,
318 struct grail_frame *frame,
319 const struct utouch_frame *touch)
320{
321 const struct utouch_surface *s = utouch_frame_get_surface(impl->fh);
322 const struct grail_control *ctl = impl->ctl;
323 float c_x = ctl->bar_center_x * (s->mapped_max_x - s->mapped_min_x);
324 float c_y = ctl->bar_center_y * (s->mapped_max_y - s->mapped_min_y);
325 float d_x = ctl->bar_drag_x * (s->mapped_max_x - s->mapped_min_x);
326 float d_y = ctl->bar_drag_y * (s->mapped_max_y - s->mapped_min_y);
327 float ds2 = ctl->bar_scale * ctl->bar_scale;
328 float dt;
329 int i;
330
331 for (i = 0; i < impl->num_slots; i++) {
332 struct grail_element *s = frame->slots[i];
333
334 if (!s->num_touches)
335 continue;
336
337 dt = touch->time - s->start_time;
338 if (dt > ctl->glue_ms) {
339 unsigned int mask = s->active_mask;
340
341 if (fabs(s->center.x - s->start_center.x) > c_x)
342 mask |= GRAIL_EXPECT_CENTER_X;
343 if (fabs(s->center.y - s->start_center.y) > c_y)
344 mask |= GRAIL_EXPECT_CENTER_Y;
345 if (fabs(s->drag.x) > d_x)
346 mask |= GRAIL_EXPECT_DRAG_X;
347 if (fabs(s->drag.y) > d_y)
348 mask |= GRAIL_EXPECT_DRAG_Y;
349 if (fabs(s->scale2 - 1) > ds2)
350 mask |= GRAIL_EXPECT_SCALE;
351 if (fabs(s->angle) > ctl->bar_angle)
352 mask |= GRAIL_EXPECT_ANGLE;
353
354 s->active_mask = mask;
355
356 if (dt < ctl->drop_x_ms)
357 mask |= GRAIL_EXPECT_CENTER_X;
358 if (dt < ctl->drop_y_ms)
359 mask |= GRAIL_EXPECT_CENTER_Y;
360 if (dt < ctl->drop_x_ms)
361 mask |= GRAIL_EXPECT_DRAG_X;
362 if (dt < ctl->drop_y_ms)
363 mask |= GRAIL_EXPECT_DRAG_Y;
364 if (dt < ctl->drop_scale_ms)
365 mask |= GRAIL_EXPECT_SCALE;
366 if (dt < ctl->drop_angle_ms)
367 mask |= GRAIL_EXPECT_ANGLE;
368
369 s->expect_mask &= mask;
370 }
371
372 frame->ongoing[frame->num_ongoing++] = s;
373 }
374}
375
376const struct grail_frame GRAIL_PUBLIC *
377grail_pump_frame(grail_handle ge, const struct utouch_frame *touch)
378{
379 struct grail_impl *impl = ge->impl;
380 struct grail_frame *frame = impl->frames[impl->nextframe];
381 const struct grail_frame *prev = frame->prev;
382 int i;
383
384 if (touch->slot_revision == touch->prev->slot_revision &&
385 !prev->num_ongoing)
386 return 0;
387
388 frame->touch = touch;
389 frame->num_ongoing = 0;
390 for (i = 0; i < impl->num_slots; i++)
391 frame->slots[i]->prev = prev->slots[i];
392
393 set_slots(impl, frame, touch);
394 collect_transforms(impl, frame, touch);
395
396 impl->nextframe = (impl->nextframe + 1) % impl->num_frames;
397
398 return frame;
399}
diff --git a/src/grail-impl.h b/src/grail-impl.h
index 8f73a2d..e9b1038 100644
--- a/src/grail-impl.h
+++ b/src/grail-impl.h
@@ -33,6 +33,32 @@
33 33
34#define DIM_TOUCH 32 34#define DIM_TOUCH 32
35#define DIM_TOUCH_BYTES ((DIM_TOUCH + 7) >> 3) 35#define DIM_TOUCH_BYTES ((DIM_TOUCH + 7) >> 3)
36#define GRAIL_ID_MAX 0xffff
37
38#define MIN(a, b) ((a) < (b) ? (a) : (b))
39#define MAX(a, b) ((a) > (b) ? (a) : (b))
40
41typedef void *grail_select_callback;
42
43/*
44 * In this implementation, there can be N one-gestures, N (N - 1) / 2
45 * two-gestures, and one global gesture.
46 */
47static inline int get_slot_count(int n)
48{
49 return n + n * (n - 1) / 2 + 1;
50}
51
52int create_grail2(struct grail_impl *x,
53 utouch_frame_handle fh,
54 unsigned int num_frames,
55 void *select,
56 unsigned int version,
57 unsigned int control_size,
58 unsigned int frame_size,
59 unsigned int slot_size);
60
61void destroy_grail2(struct grail_impl *x);
36 62
37struct grail_impl { 63struct grail_impl {
38 struct evemu_device *evemu; 64 struct evemu_device *evemu;
@@ -45,6 +71,18 @@ struct grail_impl {
45 int ongoing; 71 int ongoing;
46 int gesture; 72 int gesture;
47 FILE *fptest; 73 FILE *fptest;
74 /* new stuff below */
75 struct grail_control *ctl;
76 grail_select_callback select;
77 int num_frames;
78 int num_slots;
79 int num_touches;
80 int nextframe;
81 int seqid;
82 unsigned int control_size;
83 unsigned int frame_size;
84 unsigned int slot_size;
85 struct grail_frame **frames;
48}; 86};
49 87
50#endif 88#endif
diff --git a/src/grail-init.c b/src/grail-init.c
new file mode 100644
index 0000000..18e6996
--- /dev/null
+++ b/src/grail-init.c
@@ -0,0 +1,241 @@
1/*****************************************************************************
2 *
3 * grail - Gesture Recognition And Instantiation Library
4 *
5 * Copyright (C) 2010 Canonical Ltd.
6 * Copyright (C) 2010 Henrik Rydberg <rydberg@bitmath.org>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23#include "grail-impl.h"
24#include <stdlib.h>
25#include <string.h>
26#include <math.h>
27
28static struct grail_control *create_control(int size)
29{
30 struct grail_control *c = calloc(1, size);
31
32 if (!c)
33 return 0;
34
35 c->glue_ms = 60;
36 c->bar_center_x = 0.03;
37 c->bar_center_y = 0.03;
38 c->bar_drag_x = 0.03;
39 c->bar_drag_y = 0.03;
40 c->bar_scale = 0.3;
41 c->bar_angle = 0.1;
42 c->drop_x_ms = 300;
43 c->drop_y_ms = 300;
44 c->drop_scale_ms = 300;
45 c->drop_angle_ms = 300;
46
47 return c;
48}
49
50static void destroy_slots(struct grail_element **slots, int nslot)
51{
52 int i;
53
54 if (slots) {
55 for (i = nslot - 1; i >= 0; i--)
56 free(slots[i]);
57 free(slots);
58 }
59}
60
61static void destroy_frame(struct grail_frame *frame, int nslot)
62{
63 if (frame) {
64 destroy_slots(frame->slots, nslot);
65 free(frame->ongoing);
66 free(frame);
67 }
68}
69
70static void destroy_frames(struct grail_frame **frames, int nframe, int nslot)
71{
72 int i;
73
74 if (frames) {
75 for (i = nframe - 1; i >= 0; i--)
76 destroy_frame(frames[i], nslot);
77 free(frames);
78 }
79}
80
81static struct grail_element **create_slots(int nslot, int ntouch, int size)
82{
83 struct grail_element **slots;
84 struct grail_element *s;
85 int i;
86
87 slots = calloc(nslot, sizeof(slots[0]));
88 if (!slots)
89 return 0;
90
91 for (i = 0; i < nslot; i++) {
92 s = calloc(1, size + ntouch * sizeof(void *));
93 if (!s)
94 goto out;
95 s->slot = i;
96 s->id = -1;
97 s->touches = (void *)((char *)s + size);
98 slots[i] = s;
99 }
100
101 return slots;
102 out:
103 destroy_slots(slots, nslot);
104 return 0;
105}
106
107static struct grail_frame *create_frame(int nslot, int ntouch,
108 int frame_size, int slot_size)
109{
110 struct grail_frame *frame;
111 int i;
112
113 frame = calloc(1, frame_size);
114 if (!frame)
115 return 0;
116
117 frame->ongoing = calloc(nslot, sizeof(frame->ongoing[0]));
118 frame->slots = create_slots(nslot, ntouch, slot_size);
119 if (!frame->ongoing || !frame->slots)
120 goto out;
121
122 return frame;
123 out:
124 destroy_frame(frame, nslot);
125 return 0;
126}
127
128static struct grail_frame **create_frames(int nframe, int nslot, int ntouch,
129 int frame_size, int slot_size)
130{
131 struct grail_frame **frames;
132 struct grail_frame *f;
133 int i;
134
135 frames = calloc(nframe, sizeof(frames[0]));
136 if (!frames)
137 return 0;
138
139 for (i = 0; i < nframe; i++) {
140 f = create_frame(nslot, ntouch, frame_size, slot_size);
141 if (!f)
142 goto out;
143 frames[i] = f;
144 }
145
146 return frames;
147 out:
148 destroy_frames(frames, nframe, nslot);
149 return 0;
150}
151
152int create_grail2(struct grail_impl *x,
153 utouch_frame_handle fh,
154 unsigned int num_frames,
155 grail_select_callback select,
156 unsigned int version,
157 unsigned int control_size,
158 unsigned int frame_size,
159 unsigned int slot_size)
160{
161 struct utouch_surface *s = utouch_frame_get_surface(fh);
162 unsigned int ntouch = utouch_frame_get_num_slots(fh);
163 unsigned int nslot = get_slot_count(ntouch);
164 int i, j;
165
166 x->select = select;
167 x->control_size = MAX(control_size, sizeof(struct grail_control));
168 x->frame_size = MAX(frame_size, sizeof(struct grail_frame));
169 x->slot_size = MAX(slot_size, sizeof(struct grail_element));
170
171 x->num_frames = num_frames;
172 x->num_slots = nslot;
173 x->num_touches = ntouch;
174
175 x->ctl = create_control(x->control_size);
176 if (!x->ctl)
177 goto freemem;
178
179 x->frames = create_frames(num_frames, nslot, ntouch,
180 x->frame_size, x->slot_size);
181 if (!x->frames)
182 goto freemem;
183
184 for (i = 0; i < num_frames; i++)
185 x->frames[(i + 1) % num_frames]->prev = x->frames[i];
186
187 return 0;
188
189 freemem:
190 destroy_grail2(x);
191 return -ENOMEM;
192}
193
194void destroy_grail2(struct grail_impl *x)
195{
196 destroy_frames(x->frames, x->num_frames, x->num_slots);
197 free(x->ctl);
198}
199
200grail_handle GRAIL_PUBLIC grail_new_raw(utouch_frame_handle fh,
201 unsigned int num_frames,
202 grail_select_callback select,
203 unsigned int version,
204 unsigned int control_size,
205 unsigned int frame_size,
206 unsigned int slot_size)
207{
208 struct grail *ge;
209 struct grail_impl *x;
210
211 ge = calloc(1, sizeof(*ge));
212 if (!ge)
213 return 0;
214 x = calloc(1, sizeof(*x));
215 if (!x)
216 goto out;
217 x->fh = fh;
218 if (create_grail2(x, fh, num_frames, select,
219 version, control_size, frame_size, slot_size))
220 goto out;
221
222 ge->impl = x;
223 return ge;
224
225 out:
226 free(x);
227 free(ge);
228 return 0;
229}
230
231void GRAIL_PUBLIC grail_delete(grail_handle ge)
232{
233 destroy_grail2(ge->impl);
234 free(ge->impl);
235 free(ge);
236}
237
238struct grail_control GRAIL_PUBLIC *grail_get_control(grail_handle ge)
239{
240 return ge->impl->ctl;
241}