1 /*
2 * Copyright (C) 2007-2021 Kim Woelders
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies of the Software, its documentation and marketing & publicity
13 * materials, and acknowledgment shall be given in the documentation, materials
14 * and software packages that this Software was used.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23 #include "config.h"
24
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <GL/gl.h>
29 #include <GL/glu.h>
30 #include <GL/glx.h>
31 #include <X11/keysym.h>
32
33 #include "E.h"
34 #include "animation.h"
35 #include "cursors.h"
36 #include "desktops.h"
37 #include "eimage.h"
38 #include "eglx.h"
39 #include "emodule.h"
40 #include "eobj.h"
41 #include "events.h"
42 #include "ewins.h"
43 #include "grabs.h"
44 #include "util.h"
45
46 #define ENABLE_DEBUG 1
47 #if ENABLE_DEBUG
48 #define Dprintf(fmt...) do { if(EDebug(EDBUG_TYPE_GLX))Eprintf(fmt); } while(0)
49 #define D2printf(fmt...) do { if(EDebug(EDBUG_TYPE_GLX)>1)Eprintf(fmt); } while(0)
50 #else
51 #define Dprintf(fmt...)
52 #define D2printf(fmt...)
53 #endif /* ENABLE_DEBUG */
54
55 static struct {
56 int mode;
57 } Conf_glwin;
58
59 static struct {
60 char active;
61 } Mode_glwin;
62
63 typedef struct {
64 EObj *eo;
65 char grabbing;
66 EWin *ewin;
67 } GLWindow;
68
69 static void GlwinExit(void);
70
71 static GLWindow GLWin;
72 static const char *image = "pix/about.png";
73
74 static char light; /* Lighting on/off */
75 static GLfloat rot_x; /* X rotation */
76 static GLfloat rot_y; /* Y rotation */
77 static GLfloat speed_x; /* X rotation speed */
78 static GLfloat speed_y; /* Y rotation speed */
79 static GLfloat bg_z; /* Background z */
80 static unsigned int t0, tn;
81
82 #define N_TEXTURES 5
83 static unsigned int sel_bg;
84 static unsigned int filter;
85 static ETexture *texture[N_TEXTURES];
86 static int sel_ewin;
87
88 static unsigned int
GetDTime(void)89 GetDTime(void)
90 {
91 return GetTimeMs() - t0;
92 }
93
94 static void
TexturesLoad(void)95 TexturesLoad(void)
96 {
97 EImage *im;
98
99 /* Texture 0 - None */
100 texture[0] = NULL;
101
102 if (!texture[1])
103 {
104 im = ThemeImageLoad(image);
105 if (!im)
106 {
107 Eprintf("Could not load: %s\n", image);
108 }
109 else
110 {
111 /* Texture 1 - Filter: None */
112 texture[1] = EGlTextureFromImage(im, 0);
113
114 /* Texture 2 - Filter: Linear */
115 texture[2] = EGlTextureFromImage(im, 1);
116
117 /* Texture 3 - Mipmap */
118 texture[3] = EGlTextureFromImage(im, 2);
119
120 EImageFree(im);
121 }
122 }
123
124 if (!texture[4])
125 {
126 /* Texture 4 - BG pixmap */
127 texture[4] =
128 EGlTextureFromDrawable(DeskGetBackgroundPixmap(DesksGetCurrent()),
129 0);
130 }
131 }
132
133 static void
SceneResize(unsigned int width,unsigned int height)134 SceneResize(unsigned int width, unsigned int height)
135 {
136 Dprintf("SceneResize\n");
137
138 glViewport(0, 0, width, height);
139 glMatrixMode(GL_PROJECTION);
140 glLoadIdentity();
141 glOrtho(0, width, 0, height, -1000.f, 1000.f);
142 glMatrixMode(GL_MODELVIEW);
143 }
144
145 static GLfloat light_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
146 static GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
147 static GLfloat light_position[] = { 0.0f, 0.0f, 2.0f, 1.0f };
148
149 static void
SceneInitLight(void)150 SceneInitLight(void)
151 {
152 glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);
153 glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
154 glLightfv(GL_LIGHT1, GL_POSITION, light_position);
155 glEnable(GL_LIGHT1);
156 }
157
158 #define L 0.0f
159 #define R 1.0f
160 #define T 0.0f
161 #define B 1.0f
162
163 static void
DrawBackground(ETexture * et,GLfloat w,GLfloat h)164 DrawBackground(ETexture * et, GLfloat w, GLfloat h)
165 {
166 if (!et)
167 return;
168
169 glBindTexture(et->target, et->texture);
170
171 glBegin(GL_QUADS);
172 glNormal3f(0.0f, 0.0f, 1.0f);
173 glTexCoord2f(L, B);
174 glVertex3f(0, 0, bg_z);
175 glTexCoord2f(L, T);
176 glVertex3f(0, h, bg_z);
177 glTexCoord2f(R, T);
178 glVertex3f(w, h, bg_z);
179 glTexCoord2f(R, B);
180 glVertex3f(w, 0, bg_z);
181 glEnd();
182 }
183
184 static void
DrawQube(ETexture * et,GLfloat x,GLfloat y,GLfloat z,GLfloat w,GLfloat h,GLfloat rx,GLfloat ry)185 DrawQube(ETexture * et, GLfloat x, GLfloat y, GLfloat z, GLfloat w, GLfloat h,
186 GLfloat rx, GLfloat ry)
187 {
188 GLfloat w2, h2, t;
189
190 if (!et)
191 return;
192
193 glBindTexture(et->target, et->texture);
194
195 switch (filter)
196 {
197 default:
198 case 0:
199 glTexParameteri(et->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
200 glTexParameteri(et->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
201 break;
202 case 1:
203 glTexParameteri(et->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
204 glTexParameteri(et->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
205 break;
206 }
207
208 glPushMatrix();
209
210 #if 0
211 x = round(x);
212 y = round(y);
213 #endif
214
215 glTranslatef(x, y, z);
216 #if 0
217 glScalef(sz, sz, sz);
218 #endif
219 glRotatef(rx, 1.0f, 0.0f, 0.0f); /* Rotate around X axis */
220 glRotatef(ry, 0.0f, 1.0f, 0.0f); /* Rotate around Y axis */
221
222 t = 4.0f;
223 w2 = round(w / 2.f);
224 h2 = round(h / 2.f);
225
226 glBegin(GL_QUADS);
227 #if 1
228 /* Front */
229 glNormal3f(0.0f, 0.0f, 1.0f);
230 glTexCoord2f(L, B);
231 glVertex3f(-w2, -h2, t);
232 glTexCoord2f(R, B);
233 glVertex3f(w2, -h2, t);
234 glTexCoord2f(R, T);
235 glVertex3f(w2, h2, t);
236 glTexCoord2f(L, T);
237 glVertex3f(-w2, h2, t);
238 #endif
239 #if 1
240 /* Back */
241 glNormal3f(0.0f, 0.0f, 1.0f);
242 glTexCoord2f(L, B);
243 glVertex3f(w2, -h2, -t);
244 glTexCoord2f(R, B);
245 glVertex3f(-w2, -h2, -t);
246 glTexCoord2f(R, T);
247 glVertex3f(-w2, h2, -t);
248 glTexCoord2f(L, T);
249 glVertex3f(w2, h2, -t);
250 #endif
251 #if 1
252 /* Right */
253 glNormal3f(1.0f, 0.0f, 0.0f);
254 glTexCoord2f(L, B);
255 glVertex3f(w2, -h2, t);
256 glTexCoord2f(R, B);
257 glVertex3f(w2, -h2, -t);
258 glTexCoord2f(R, T);
259 glVertex3f(w2, h2, -t);
260 glTexCoord2f(L, T);
261 glVertex3f(w2, h2, t);
262 #endif
263 #if 1
264 /* Left */
265 glNormal3f(-1.0f, 0.0f, 0.0f);
266 glTexCoord2f(L, B);
267 glVertex3f(-w2, -h2, -t);
268 glTexCoord2f(R, B);
269 glVertex3f(-w2, -h2, t);
270 glTexCoord2f(R, T);
271 glVertex3f(-w2, h2, t);
272 glTexCoord2f(L, T);
273 glVertex3f(-w2, h2, -t);
274 #endif
275 #if 1
276 /* Top */
277 glNormal3f(0.0f, 1.0f, 0.0f);
278 glTexCoord2f(L, B);
279 glVertex3f(w2, h2, t);
280 glTexCoord2f(R, B);
281 glVertex3f(w2, h2, -t);
282 glTexCoord2f(R, T);
283 glVertex3f(-w2, h2, -t);
284 glTexCoord2f(L, T);
285 glVertex3f(-w2, h2, t);
286 #endif
287 #if 1
288 /* Bottom */
289 glNormal3f(0.0f, -1.0f, 0.0f);
290 glTexCoord2f(L, B);
291 glVertex3f(w2, -h2, -t);
292 glTexCoord2f(R, B);
293 glVertex3f(w2, -h2, t);
294 glTexCoord2f(R, T);
295 glVertex3f(-w2, -h2, t);
296 glTexCoord2f(L, T);
297 glVertex3f(-w2, -h2, -t);
298 #endif
299 glEnd();
300
301 glPopMatrix();
302 }
303
304 static void
SceneDraw2(unsigned int tms,EWin ** ewins,int num)305 SceneDraw2(unsigned int tms, EWin ** ewins, int num)
306 {
307 double t;
308 int i, j, k, nx, ny;
309 GLfloat x, y, w, h, dx, dy, sz;
310 EObj *eo;
311
312 t = 1e-3 * tms;
313
314 w = EobjGetW(GLWin.eo);
315 h = EobjGetH(GLWin.eo);
316 w = (3 * w) / 4;
317
318 DrawBackground(texture[sel_bg], w, h);
319
320 i = (int)sqrt(w * h / (1.0 * num));
321 nx = (int)((w + i - 1) / i);
322 if (nx <= 0)
323 nx = 1;
324 ny = (num + nx - 1) / nx;
325 if (ny <= 0)
326 ny = 1;
327 #if 0
328 Eprintf("wxh=%fx%f num=%d nx,ny=%d,%d\n", w, h, num, nx, ny);
329 #endif
330 w = EobjGetW(GLWin.eo) / nx;
331 h = EobjGetH(GLWin.eo) / ny;
332
333 k = 0;
334 for (j = 0; j < ny; j++)
335 {
336 for (i = 0; i < nx; i++)
337 {
338 if (k >= num)
339 break;
340 x = i * w;
341 y = j * h;
342 eo = EoObj(ewins[k]);
343 dx = 100.0f * exp(-t);
344 dx = (fabs(dx) < 1.0) ? 0. : dx * sin(5. * t);
345 dy = 100.0f * exp(-t);
346 dy = (fabs(dy) < 1.0) ? 0. : dy * cos(5. * t);
347 sz = (k == sel_ewin) ? 0.6f : 0.5f;
348 DrawQube(EobjGetTexture(eo),
349 dx + (0.5f + i) * w, dy + (0.5f + j) * h, 500.0f,
350 sz * EobjGetW(eo), sz * EobjGetH(eo), rot_x, rot_y);
351 #if 1
352 if (k == sel_ewin)
353 {
354 glColor3f(1., 0., 0.);
355 glRectf(x, y, x + w, y + h);
356 #define X0 x
357 #define Y0 y
358 #define W w
359 #define H h
360 glLineWidth(2.);
361 glColor3f(0., 1., 0.);
362 glBegin(GL_LINE_LOOP);
363 glVertex3f(X0, Y0, 0);
364 glVertex3f(X0 + W, Y0, 0);
365 glVertex3f(X0 + W, Y0 + H, 0);
366 glVertex3f(X0, Y0 + H, 0);
367 glEnd();
368 glColor4f(1., 1., 1., 1.);
369 }
370 #endif
371 k++;
372 }
373 }
374 }
375
376 static void
SceneDraw1(unsigned int tms,EWin ** ewins,int num)377 SceneDraw1(unsigned int tms, EWin ** ewins, int num)
378 {
379 double t1, arg;
380 int i;
381 GLfloat w, h, dx, dy, sz;
382 EObj *eo;
383
384 w = EobjGetW(GLWin.eo);
385 h = EobjGetH(GLWin.eo);
386
387 t1 = 2 * M_PI * (-exp(-(20e-3 * (tms - tn))) / num);
388
389 DrawBackground(texture[sel_bg], w, h);
390
391 for (i = 0; i < num; i++)
392 {
393 arg = t1 + M_PI / 2. - (i - sel_ewin) * 2. * M_PI / num;
394 dx = (.5 + .3 * cos(arg)) * w;
395 dy = (.5 - .3 * sin(arg)) * h;
396
397 eo = EoObj(ewins[i]);
398 if (i == sel_ewin)
399 sz = 0.5 + .01 * cos(10e-3 * (tms - tn));
400 else
401 sz = 0.3;
402 DrawQube(EobjGetTexture(eo), dx, dy, 500.0,
403 sz * EobjGetW(eo), sz * EobjGetH(eo), rot_x, rot_y);
404 }
405 }
406
407 static EWin **
GlwinEwins(int * pnum)408 GlwinEwins(int *pnum)
409 {
410 int i, j, num;
411 EWin *const *ewins;
412 EWin **lst, *ewin;
413
414 ewins = EwinListGetAll(&num);
415 lst = EMALLOC(EWin *, num);
416
417 for (i = j = 0; i < num; i++)
418 {
419 ewin = ewins[i];
420 if (!EoIsShown(ewin))
421 continue;
422 if (ewin->props.skip_focuslist || ewin->props.skip_ext_task)
423 continue;
424 lst[j++] = ewin;
425 }
426 *pnum = j;
427
428 return lst;
429 }
430
431 static void
SceneDraw(void)432 SceneDraw(void)
433 {
434 unsigned int t;
435 EWin **ewins;
436 int num;
437
438 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
439 glLoadIdentity();
440
441 t = GetDTime();
442
443 ewins = GlwinEwins(&num);
444 if (sel_ewin < 0)
445 sel_ewin = num - 1;
446 else if (sel_ewin >= num)
447 sel_ewin = 0;
448 GLWin.ewin = ewins[sel_ewin];
449
450 switch (Conf_glwin.mode)
451 {
452 default:
453 SceneDraw1(t, ewins, num);
454 break;
455 case 1:
456 SceneDraw2(t, ewins, num);
457 break;
458 }
459
460 glXSwapBuffers(disp, EobjGetXwin(GLWin.eo));
461
462 Efree(ewins);
463
464 rot_x += speed_x;
465 rot_y += speed_y;
466 }
467
468 static int
GlwinRun(EObj * eobj __UNUSED__,int remaining __UNUSED__,void * state __UNUSED__)469 GlwinRun(EObj * eobj __UNUSED__, int remaining __UNUSED__,
470 void *state __UNUSED__)
471 {
472 if (!GLWin.eo)
473 return 0;
474 SceneDraw();
475 return 1;
476 }
477
478 static int
GlwinKeyPress(GLWindow * gw,EX_KeySym keysym)479 GlwinKeyPress(GLWindow * gw, EX_KeySym keysym)
480 {
481 switch (keysym)
482 {
483 case XK_q:
484 case XK_Escape:
485 return 1;
486
487 case XK_b:
488 sel_bg += 1;
489 if (sel_bg >= N_TEXTURES)
490 sel_bg = 0;
491 break;
492 case XK_f:
493 filter += 1;
494 if (filter >= 2)
495 filter = 0;
496 break;
497
498 case XK_g: /* Toggle grabs */
499 if (gw->grabbing)
500 {
501 GrabPointerRelease();
502 GrabKeyboardRelease();
503 gw->grabbing = 0;
504 }
505 else
506 {
507 GrabPointerSet(EobjGetWin(gw->eo), ECSR_GRAB, 0);
508 GrabKeyboardSet(EobjGetWin(gw->eo));
509 gw->grabbing = 1;
510 }
511 break;
512
513 case XK_l:
514 light = !light;
515 if (!light)
516 glDisable(GL_LIGHTING);
517 else
518 glEnable(GL_LIGHTING);
519 break;
520
521 case XK_R: /* Reset */
522 rot_x = rot_y = 0.0f;
523 /* FALLTHROUGH */
524
525 case XK_S: /* Stop */
526 bg_z = -2.0f;
527 speed_x = 0.0f;
528 speed_y = 0.0f;
529 break;
530
531 case XK_Page_Up:
532 bg_z -= 0.10f;
533 break;
534 case XK_Page_Down:
535 bg_z += 0.10f;
536 break;
537
538 case XK_Up:
539 speed_x -= 0.10f;
540 break;
541 case XK_Down:
542 speed_x += 0.10f;
543 break;
544 case XK_Right:
545 speed_y += 0.10f;
546 break;
547 case XK_Left:
548 speed_y -= 0.10f;
549 break;
550
551 case XK_m:
552 Conf_glwin.mode++;
553 Conf_glwin.mode %= 2;
554 break;
555
556 case XK_n:
557 case XK_Tab:
558 sel_ewin++;
559 tn = GetDTime();
560 break;
561 case XK_p:
562 sel_ewin--;
563 break;
564
565 case XK_Return:
566 if (!gw->ewin)
567 {
568 Eprintf("No win\n");
569 break;
570 }
571 EwinOpActivate(gw->ewin, OPSRC_USER, Conf.warplist.raise_on_select);
572 return 1;
573 }
574 #define TI(no) ((texture[no]) ? (int)texture[no]->texture : -1)
575 Dprintf("bg=%d(%d) filter=%d l=%d z=%.2f spx/y=%.2f/%.2f\n",
576 sel_bg, TI(sel_bg), filter, light, bg_z, speed_x, speed_y);
577
578 return 0;
579 }
580
581 static void
GlwinEvent(Win win __UNUSED__,XEvent * ev,void * prm)582 GlwinEvent(Win win __UNUSED__, XEvent * ev, void *prm)
583 {
584 GLWindow *gw = (GLWindow *) prm;
585 EX_KeySym keysym;
586 int done = 0;
587
588 switch (ev->type)
589 {
590 default:
591 break;
592 case EX_EVENT_DAMAGE_NOTIFY:
593 return;
594 }
595
596 Dprintf("GlwinEvent ev %d\n", ev->type);
597
598 switch (ev->type)
599 {
600 default:
601 break;
602 case KeyPress:
603 keysym = XLookupKeysym(&ev->xkey, ev->xkey.state);
604 done = GlwinKeyPress(gw, keysym);
605 break;
606 #if 0
607 case EnterNotify:
608 GrabKeyboardSet(EobjGetWin(GLWin.eo));
609 break;
610 case LeaveNotify:
611 GrabKeyboardRelease();
612 break;
613 #endif
614 case MapNotify:
615 GlwinKeyPress(gw, XK_g);
616 AnimatorAdd(GLWin.eo, ANIM_GLWIN, GlwinRun, -1, 0, 0, NULL);
617 break;
618 #if 0
619 case ConfigureNotify:
620 if (ev->xconfigure.width == EobjGetW(GLWin.eo) &&
621 ev->xconfigure.height == EobjGetH(GLWin.eo))
622 break;
623 SceneResize(ev->xconfigure.width, ev->xconfigure.height);
624 break;
625 #endif
626 }
627
628 if (done)
629 GlwinExit();
630 }
631
632 static int
GlwinCreate(const char * title __UNUSED__,int width,int height)633 GlwinCreate(const char *title __UNUSED__, int width, int height)
634 {
635 Win win;
636 int x, y;
637
638 #if 0
639 win = RROOT;
640 #else
641 win = VROOT;
642 #endif
643 x = ((win->w - width) / 2);
644 y = ((win->h - height) / 2);
645
646 GLWin.eo = EobjWindowCreate(EOBJ_TYPE_GLX, x, y, width, height, 0, "GLwin");
647 if (!GLWin.eo)
648 return -1;
649 win = EobjGetWin(GLWin.eo);
650 GLWin.eo->fade = GLWin.eo->shadow = 1;
651
652 EventCallbackRegister(win, GlwinEvent, &GLWin);
653
654 ESelectInput(win, ExposureMask | KeyPressMask | ButtonPressMask |
655 StructureNotifyMask);
656
657 EGlWindowConnect(WinGetXwin(win));
658
659 GLWin.grabbing = 0;
660 GLWin.ewin = NULL;
661
662 EobjMap(GLWin.eo, 1);
663
664 t0 = GetTimeMs();
665 tn = t0;
666
667 return 0;
668 }
669
670 static void
GlwinInit(void)671 GlwinInit(void)
672 {
673 bg_z = -2.0f;
674 sel_bg = 0;
675 filter = 0;
676 sel_ewin = 0;
677 light = 0;
678
679 if (GlwinCreate("GLwin", 640, 480))
680 {
681 Eprintf("Failed to create window\n");
682 return;
683 }
684
685 Mode_glwin.active = 1;
686
687 TexturesLoad();
688
689 SceneResize(EobjGetW(GLWin.eo), EobjGetW(GLWin.eo));
690
691 SceneInitLight();
692
693 glFlush();
694 }
695
696 static void
GlwinExit(void)697 GlwinExit(void)
698 {
699 if (!Mode_glwin.active)
700 return;
701
702 Dprintf("GlTestExit\n");
703
704 if (GLWin.eo)
705 {
706 EventCallbackUnregister(EobjGetWin(GLWin.eo), GlwinEvent, &GLWin);
707 EobjWindowDestroy(GLWin.eo);
708 GLWin.eo = NULL;
709 }
710
711 #if 0
712 unsigned int i;
713
714 for (i = 0; i < N_TEXTURES; i++)
715 {
716 EGlTextureDestroy(texture[i]);
717 texture[i] = NULL;
718 }
719 #endif
720
721 Mode_glwin.active = 0;
722 }
723
724 /*
725 * GLwin Module
726 */
727
728 static void
GlwinSighan(int sig,void * prm __UNUSED__)729 GlwinSighan(int sig, void *prm __UNUSED__)
730 {
731 switch (sig)
732 {
733 case ESIGNAL_START:
734 break;
735
736 case ESIGNAL_EXIT:
737 GlwinExit();
738 break;
739 }
740 }
741
742 static void
GlwinIpc(const char * params)743 GlwinIpc(const char *params)
744 {
745 const char *cmd;
746
747 cmd = params;
748
749 if (!cmd)
750 {
751 GlwinInit();
752 }
753 }
754
755 static const IpcItem GlwinIpcArray[] = {
756 {
757 GlwinIpc,
758 "glwin", NULL,
759 "Glwin functions",
760 " glwin\n"}
761 ,
762 };
763
764 static const CfgItem GlwinCfgItems[] = {
765 CFG_ITEM_INT(Conf_glwin, mode, 0),
766 };
767
768 /*
769 * Module descriptor
770 */
771 extern const EModule ModGlwin;
772
773 const EModule ModGlwin = {
774 "glwin", NULL,
775 GlwinSighan,
776 MOD_ITEMS(GlwinIpcArray),
777 MOD_ITEMS(GlwinCfgItems)
778 };
779