Line data Source code
1 : /* Copyright (C) 2007-2012 by George Williams */
2 : /*
3 : * Redistribution and use in source and binary forms, with or without
4 : * modification, are permitted provided that the following conditions are met:
5 :
6 : * Redistributions of source code must retain the above copyright notice, this
7 : * list of conditions and the following disclaimer.
8 :
9 : * Redistributions in binary form must reproduce the above copyright notice,
10 : * this list of conditions and the following disclaimer in the documentation
11 : * and/or other materials provided with the distribution.
12 :
13 : * The name of the author may not be used to endorse or promote products
14 : * derived from this software without specific prior written permission.
15 :
16 : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 : * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 : * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 : * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 : * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 : * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 : * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 : * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 : * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 : * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 : */
27 : /* Python Interface to FontForge */
28 :
29 : // to get asprintf() defined from stdio.h on GNU platforms
30 : #define _GNU_SOURCE 1
31 :
32 : #include "ffglib.h"
33 :
34 : #include <fontforge-config.h>
35 :
36 : #ifndef _NO_PYTHON
37 : #include "Python.h"
38 : #include "structmember.h"
39 :
40 : #include "cvundoes.h"
41 : #include "fontforgeui.h"
42 : #include "ttf.h"
43 : #include "plugins.h"
44 : #include "ustring.h"
45 : #include "scripting.h"
46 : #include "scriptfuncs.h"
47 : #include <math.h>
48 : #include <unistd.h>
49 : #include <sys/types.h>
50 : #include <dirent.h>
51 : #include <errno.h>
52 : #include <stdarg.h>
53 : #include <stdio.h>
54 : #include <fcntl.h>
55 : #include "ffpython.h"
56 :
57 : #include "gnetwork.h"
58 : #ifdef BUILD_COLLAB
59 : #include "collab/zmq_kvmsg.h"
60 : #endif
61 : #include "collabclientui.h"
62 :
63 : /**
64 : * Use this to track if the script has joined a collab session.
65 : * if not then we get to very quickly avoid the collab code path :)
66 : */
67 : static int inPythonStartedCollabSession = 0;
68 :
69 : static struct python_menu_info {
70 : PyObject *func;
71 : PyObject *check_enabled; /* May be None (which I change to NULL) */
72 : PyObject *data; /* May be None (left as None) */
73 : } *cvpy_menu_data = NULL, *fvpy_menu_data = NULL;
74 : static int cvpy_menu_cnt=0, cvpy_menu_max = 0;
75 : static int fvpy_menu_cnt=0, fvpy_menu_max = 0;
76 :
77 : GMenuItem2 *cvpy_menu, *fvpy_menu;
78 :
79 0 : static void py_tllistcheck(struct gmenuitem *mi,PyObject *owner,
80 : struct python_menu_info *menu_data, int menu_cnt) {
81 : PyObject *arglist, *result;
82 :
83 0 : if ( menu_data==NULL || mi == NULL )
84 0 : return;
85 :
86 0 : for ( mi = mi->sub; mi !=NULL && (mi->ti.text!=NULL || mi->ti.line); ++mi ) {
87 0 : if ( mi->mid==-1 ) /* Submenu */
88 0 : continue;
89 0 : if ( mi->mid<0 || mi->mid>=menu_cnt ) {
90 0 : fprintf( stderr, "Bad Menu ID in python menu %d\n", mi->mid );
91 0 : mi->ti.disabled = true;
92 0 : continue;
93 : }
94 0 : if ( menu_data[mi->mid].check_enabled==NULL ) {
95 0 : mi->ti.disabled = false;
96 0 : continue;
97 : }
98 0 : arglist = PyTuple_New(2);
99 0 : Py_XINCREF(menu_data[mi->mid].data);
100 0 : Py_XINCREF(owner);
101 0 : PyTuple_SetItem(arglist,0,menu_data[mi->mid].data);
102 0 : PyTuple_SetItem(arglist,1,owner);
103 0 : result = PyEval_CallObject(menu_data[mi->mid].check_enabled, arglist);
104 0 : Py_DECREF(arglist);
105 0 : if ( result==NULL )
106 : /* Oh. An error. How fun. See below */;
107 0 : else if ( !PyInt_Check(result)) {
108 0 : char *menu_item_name = u2utf8_copy(mi->ti.text);
109 0 : LogError(_("Return from enabling function for menu item %s must be boolean"), menu_item_name );
110 0 : free( menu_item_name );
111 0 : mi->ti.disabled = true;
112 : } else
113 0 : mi->ti.disabled = PyInt_AsLong(result)==0;
114 0 : Py_XDECREF(result);
115 0 : if ( PyErr_Occurred()!=NULL )
116 0 : PyErr_Print();
117 : }
118 : }
119 :
120 0 : static void py_menuactivate(struct gmenuitem *mi,PyObject *owner,
121 : struct python_menu_info *menu_data, int menu_cnt) {
122 : PyObject *arglist, *result;
123 :
124 0 : if ( mi->mid==-1 ) /* Submenu */
125 0 : return;
126 0 : if ( mi->mid<0 || mi->mid>=menu_cnt ) {
127 0 : fprintf( stderr, "Bad Menu ID in python menu %d\n", mi->mid );
128 0 : return;
129 : }
130 0 : if ( menu_data[mi->mid].func==NULL ) {
131 0 : return;
132 : }
133 0 : arglist = PyTuple_New(2);
134 0 : Py_XINCREF(menu_data[mi->mid].data);
135 0 : Py_XINCREF(owner);
136 0 : PyTuple_SetItem(arglist,0,menu_data[mi->mid].data);
137 0 : PyTuple_SetItem(arglist,1,owner);
138 0 : result = PyEval_CallObject(menu_data[mi->mid].func, arglist);
139 0 : Py_DECREF(arglist);
140 0 : Py_XDECREF(result);
141 0 : if ( PyErr_Occurred()!=NULL )
142 0 : PyErr_Print();
143 : }
144 :
145 0 : void cvpy_tllistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
146 0 : CharView *cv = (CharView *) GDrawGetUserData(gw);
147 0 : PyObject *pysc = PySC_From_SC(cv->b.sc);
148 :
149 0 : if ( cvpy_menu_data==NULL )
150 0 : return;
151 :
152 0 : sc_active_in_ui = cv->b.sc;
153 0 : layer_active_in_ui = CVLayer((CharViewBase *) cv);
154 0 : PyFF_Glyph_Set_Layer(sc_active_in_ui,layer_active_in_ui);
155 0 : py_tllistcheck(mi,pysc,cvpy_menu_data,cvpy_menu_cnt);
156 0 : sc_active_in_ui = NULL;
157 0 : layer_active_in_ui = ly_fore;
158 : }
159 :
160 0 : static void cvpy_menuactivate(GWindow gw,struct gmenuitem *mi,GEvent *e) {
161 0 : CharView *cv = (CharView *) GDrawGetUserData(gw);
162 0 : PyObject *pysc = PySC_From_SC(cv->b.sc);
163 :
164 0 : if ( cvpy_menu_data==NULL )
165 0 : return;
166 :
167 0 : sc_active_in_ui = cv->b.sc;
168 0 : layer_active_in_ui = CVLayer((CharViewBase *) cv);
169 0 : PyFF_Glyph_Set_Layer(sc_active_in_ui,layer_active_in_ui);
170 0 : py_menuactivate(mi,pysc,cvpy_menu_data,cvpy_menu_cnt);
171 0 : sc_active_in_ui = NULL;
172 0 : layer_active_in_ui = ly_fore;
173 : }
174 :
175 0 : void fvpy_tllistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
176 0 : FontViewBase *fv = (FontViewBase *) GDrawGetUserData(gw);
177 0 : PyObject *pyfv = PyFV_From_FV(fv);
178 :
179 0 : if ( fvpy_menu_data==NULL )
180 0 : return;
181 :
182 0 : fv_active_in_ui = fv;
183 0 : layer_active_in_ui = fv->active_layer;
184 0 : py_tllistcheck(mi,pyfv,fvpy_menu_data,fvpy_menu_cnt);
185 0 : fv_active_in_ui = NULL;
186 : }
187 :
188 0 : static void fvpy_menuactivate(GWindow gw,struct gmenuitem *mi,GEvent *e) {
189 0 : FontViewBase *fv = (FontViewBase *) GDrawGetUserData(gw);
190 0 : PyObject *pyfv = PyFV_From_FV(fv);
191 :
192 0 : if ( fvpy_menu_data==NULL )
193 0 : return;
194 :
195 0 : fv_active_in_ui = fv;
196 0 : layer_active_in_ui = fv->active_layer;
197 0 : py_menuactivate(mi,pyfv,fvpy_menu_data,fvpy_menu_cnt);
198 0 : fv_active_in_ui = NULL;
199 : }
200 :
201 : enum { menu_fv=1, menu_cv=2 };
202 : static struct flaglist menuviews[] = {
203 : { "Font", menu_fv },
204 : { "Glyph", menu_cv },
205 : { "Char", menu_cv },
206 : FLAGLIST_EMPTY
207 : };
208 :
209 0 : static int MenuDataAdd(PyObject *func,PyObject *check,PyObject *data,int is_cv) {
210 0 : Py_INCREF(func);
211 0 : if ( check!=NULL )
212 0 : Py_INCREF(check);
213 0 : Py_INCREF(data);
214 :
215 0 : if ( is_cv ) {
216 0 : if ( cvpy_menu_cnt >= cvpy_menu_max )
217 0 : cvpy_menu_data = realloc(cvpy_menu_data,(cvpy_menu_max+=10)*sizeof(struct python_menu_info));
218 0 : cvpy_menu_data[cvpy_menu_cnt].func = func;
219 0 : cvpy_menu_data[cvpy_menu_cnt].check_enabled = check;
220 0 : cvpy_menu_data[cvpy_menu_cnt].data = data;
221 0 : return( cvpy_menu_cnt++ );
222 : } else {
223 0 : if ( fvpy_menu_cnt >= fvpy_menu_max )
224 0 : fvpy_menu_data = realloc(fvpy_menu_data,(fvpy_menu_max+=10)*sizeof(struct python_menu_info));
225 0 : fvpy_menu_data[fvpy_menu_cnt].func = func;
226 0 : fvpy_menu_data[fvpy_menu_cnt].check_enabled = check;
227 0 : fvpy_menu_data[fvpy_menu_cnt].data = data;
228 0 : return( fvpy_menu_cnt++ );
229 : }
230 : }
231 :
232 0 : static void InsertSubMenus(PyObject *args,GMenuItem2 **mn, int is_cv) {
233 : int i, j, cnt;
234 : PyObject *func, *check, *data;
235 : char *shortcut_str;
236 : GMenuItem2 *mmn;
237 :
238 : /* I've done type checking already */
239 0 : cnt = PyTuple_Size(args);
240 0 : func = PyTuple_GetItem(args,0);
241 0 : if ( (check = PyTuple_GetItem(args,1))==Py_None )
242 0 : check = NULL;
243 0 : data = PyTuple_GetItem(args,2);
244 0 : if ( PyTuple_GetItem(args,4)==Py_None )
245 0 : shortcut_str = NULL;
246 : else {
247 : #if PY_MAJOR_VERSION >= 3
248 : PyObject *obj = PyUnicode_AsUTF8String(PyTuple_GetItem(args,4));
249 : shortcut_str = PyBytes_AsString(obj);
250 : #else /* PY_MAJOR_VERSION >= 3 */
251 0 : shortcut_str = PyBytes_AsString(PyTuple_GetItem(args,4));
252 : #endif /* PY_MAJOR_VERSION >= 3 */
253 : }
254 :
255 0 : for ( i=5; i<cnt; ++i ) {
256 0 : PyObject *submenu_utf8 = PYBYTES_UTF8(PyTuple_GetItem(args,i));
257 0 : unichar_t *submenuu = utf82u_copy( PyBytes_AsString(submenu_utf8) );
258 0 : Py_DECREF(submenu_utf8);
259 :
260 0 : j = 0;
261 0 : if ( *mn != NULL ) {
262 0 : for ( j=0; (*mn)[j].ti.text!=NULL || (*mn)[j].ti.line; ++j ) {
263 0 : if ( (*mn)[j].ti.text==NULL )
264 0 : continue;
265 0 : if ( u_strcmp((*mn)[j].ti.text,submenuu)==0 )
266 0 : break;
267 : }
268 : }
269 0 : if ( *mn==NULL || (*mn)[j].ti.text==NULL ) {
270 0 : *mn = realloc(*mn,(j+2)*sizeof(GMenuItem2));
271 0 : memset(*mn+j,0,2*sizeof(GMenuItem2));
272 : }
273 0 : mmn = *mn;
274 0 : if ( mmn[j].ti.text==NULL ) {
275 0 : mmn[j].ti.text = submenuu;
276 0 : mmn[j].ti.fg = mmn[j].ti.bg = COLOR_DEFAULT;
277 0 : if ( i!=cnt-1 ) {
278 0 : mmn[j].mid = -1;
279 0 : mmn[j].moveto = is_cv ? cvpy_tllistcheck : fvpy_tllistcheck;
280 0 : mn = &mmn[j].sub;
281 : } else {
282 0 : mmn[j].shortcut = shortcut_str;
283 0 : mmn[j].invoke = is_cv ? cvpy_menuactivate : fvpy_menuactivate;
284 0 : mmn[j].mid = MenuDataAdd(func,check,data,is_cv);
285 : }
286 : } else {
287 0 : if ( i!=cnt-1 )
288 0 : mn = &mmn[j].sub;
289 : else {
290 0 : mmn[j].shortcut = shortcut_str;
291 0 : mmn[j].invoke = is_cv ? cvpy_menuactivate : fvpy_menuactivate;
292 0 : mmn[j].mid = MenuDataAdd(func,check,data,is_cv);
293 0 : fprintf( stderr, "Redefining menu item %s\n", u2utf8_copy(submenuu) );
294 0 : free(submenuu);
295 : }
296 : }
297 : }
298 0 : }
299 :
300 : /* (function,check_enabled,data,(char/font),shortcut_str,{sub-menu,}menu-name) */
301 0 : static PyObject *PyFF_registerMenuItem(PyObject *self, PyObject *args) {
302 : int i, cnt;
303 : int flags;
304 : PyObject *utf8_name;
305 :
306 0 : if ( !no_windowing_ui ) {
307 0 : cnt = PyTuple_Size(args);
308 0 : if ( cnt<6 ) {
309 0 : PyErr_Format(PyExc_TypeError, "Too few arguments");
310 0 : return( NULL );
311 : }
312 0 : if (!PyCallable_Check(PyTuple_GetItem(args,0))) {
313 0 : PyErr_Format(PyExc_TypeError, "First argument is not callable" );
314 0 : return( NULL );
315 : }
316 0 : if (PyTuple_GetItem(args,1)!=Py_None &&
317 0 : !PyCallable_Check(PyTuple_GetItem(args,1))) {
318 0 : PyErr_Format(PyExc_TypeError, "Second argument is not callable" );
319 0 : return( NULL );
320 : }
321 0 : flags = FlagsFromTuple(PyTuple_GetItem(args,3), menuviews, "menu window" );
322 0 : if ( flags==-1 ) {
323 0 : PyErr_Format(PyExc_ValueError, "Unknown window for menu" );
324 0 : return( NULL );
325 : }
326 0 : if ( PyTuple_GetItem(args,4)!=Py_None ) {
327 : #if PY_MAJOR_VERSION >= 3
328 : PyObject *obj = PyUnicode_AsUTF8String(PyTuple_GetItem(args,4));
329 : if ( obj==NULL )
330 : return( NULL );
331 : char *shortcut_str = PyBytes_AsString(obj);
332 : if ( shortcut_str==NULL ) {
333 : Py_DECREF( obj );
334 : return( NULL );
335 : }
336 : Py_DECREF( obj );
337 : #else /* PY_MAJOR_VERSION >= 3 */
338 0 : char *shortcut_str = PyBytes_AsString(PyTuple_GetItem(args,4));
339 0 : if ( shortcut_str==NULL )
340 0 : return( NULL );
341 : #endif /* PY_MAJOR_VERSION >= 3 */
342 : }
343 0 : for ( i=5; i<cnt; ++i ) {
344 0 : utf8_name = PYBYTES_UTF8(PyTuple_GetItem(args,i));
345 0 : if ( utf8_name==NULL )
346 0 : return( NULL );
347 0 : Py_DECREF(utf8_name);
348 : }
349 0 : if ( flags&menu_fv )
350 0 : InsertSubMenus(args,&fvpy_menu,false );
351 0 : if ( flags&menu_cv )
352 0 : InsertSubMenus(args,&cvpy_menu,true );
353 : }
354 :
355 0 : Py_RETURN_NONE;
356 : }
357 :
358 :
359 0 : static PyObject *PyFFFont_CollabSessionStart(PyFF_Font *self, PyObject *args)
360 : {
361 : #ifdef BUILD_COLLAB
362 : int port_default = collabclient_getDefaultBasePort();
363 : int port = port_default;
364 : char address[IPADDRESS_STRING_LENGTH_T];
365 : if( !getNetworkAddress( address ))
366 : {
367 : snprintf( address, IPADDRESS_STRING_LENGTH_T-1,
368 : "%s", HostPortPack( "127.0.0.1", port ));
369 : }
370 : else
371 : {
372 : snprintf( address, IPADDRESS_STRING_LENGTH_T-1,
373 : "%s", HostPortPack( address, port ));
374 : }
375 :
376 : if ( PySequence_Size(args) == 1 )
377 : {
378 : char* uaddr = 0;
379 : if ( !PyArg_ParseTuple(args,"es","UTF-8",&uaddr) )
380 : return( NULL );
381 :
382 : strcpy( address, uaddr );
383 : }
384 : FontViewBase *fv = self->fv;
385 :
386 :
387 : HostPortUnpack( address, &port, port_default );
388 :
389 : TRACE("address:%s\n", address );
390 : TRACE("port:%d\n", port );
391 :
392 : void* cc = collabclient_new( address, port );
393 : fv->collabClient = cc;
394 : collabclient_sessionStart( cc, (FontView*)fv );
395 : TRACE("connecting to server...sent the sfd for session start.\n");
396 : inPythonStartedCollabSession = 1;
397 :
398 : #endif
399 0 : Py_RETURN( self );
400 : }
401 :
402 0 : static void* pyFF_maybeCallCVPreserveState( PyFF_Glyph *self ) {
403 : #ifndef BUILD_COLLAB
404 0 : return 0;
405 : #else
406 : if( !inPythonStartedCollabSession )
407 : return 0;
408 :
409 : CharView* cv = 0;
410 : static GHashTable* ht = 0;
411 : if( !ht )
412 : {
413 : ht = g_hash_table_new( g_direct_hash, g_direct_equal );
414 : }
415 : fprintf(stderr,"hash size:%d\n", g_hash_table_size(ht));
416 :
417 : gpointer cache = g_hash_table_lookup( ht, self->sc );
418 : if( cache )
419 : {
420 : return cache;
421 : }
422 :
423 : SplineFont *sf = self->sc->parent;
424 : FontView* fv = (FontView*)FontViewFind( FontViewFind_bySplineFont, sf );
425 : if( !fv )
426 : {
427 : fprintf(stderr,"Collab error: can not find fontview for the SplineFont of the active char\n");
428 : }
429 : else
430 : {
431 : int old_no_windowing_ui = no_windowing_ui;
432 : no_windowing_ui = 0;
433 :
434 : // FIXME: need to find the existing cv if available!
435 : cv = CharViewCreate( self->sc, fv, -1 );
436 : g_hash_table_insert( ht, self->sc, cv );
437 : fprintf(stderr,"added... hash size:%d\n", g_hash_table_size(ht));
438 : CVPreserveState( &cv->b );
439 : collabclient_CVPreserveStateCalled( &cv->b );
440 :
441 : no_windowing_ui = old_no_windowing_ui;
442 : printf("called CVPreserveState()\n");
443 : }
444 :
445 : return cv;
446 : #endif
447 : }
448 :
449 0 : static void pyFF_sendRedoIfInSession_Func_Real( void* cvv )
450 : {
451 : #ifdef BUILD_COLLAB
452 : CharView* cv = (CharView*)cvv;
453 : if( inPythonStartedCollabSession && cv )
454 : {
455 : collabclient_sendRedo( &cv->b );
456 : printf("collabclient_sendRedo()...\n");
457 : }
458 : #endif
459 0 : }
460 :
461 :
462 : static PyObject *CollabSessionSetUpdatedCallback = NULL;
463 :
464 0 : static PyObject *PyFFFont_CollabSessionJoin(PyFF_Font *self, PyObject *args)
465 : {
466 : #ifdef BUILD_COLLAB
467 :
468 : char* address = collabclient_makeAddressString(
469 : "localhost", collabclient_getDefaultBasePort());
470 :
471 : if ( PySequence_Size(args) == 1 )
472 : {
473 : char* uaddr = 0;
474 : if ( !PyArg_ParseTuple(args,"es","UTF-8",&uaddr) )
475 : return( NULL );
476 :
477 : address = uaddr;
478 : }
479 : FontViewBase *fv = self->fv;
480 :
481 : TRACE("PyFFFont_CollabSessionJoin() address:%s fv:%p\n", address, self->fv );
482 : void* cc = collabclient_newFromPackedAddress( address );
483 : TRACE("PyFFFont_CollabSessionJoin() address:%s cc1:%p\n", address, cc );
484 : fv->collabClient = cc;
485 : TRACE("PyFFFont_CollabSessionJoin() address:%s cc2:%p\n", address, fv->collabClient );
486 : FontViewBase* newfv = collabclient_sessionJoin( cc, (FontView*)fv );
487 : // here fv->collabClient is 0 and there is a new fontview.
488 : TRACE("PyFFFont_CollabSessionJoin() address:%s cc3:%p\n", address, fv->collabClient );
489 : TRACE("PyFFFont_CollabSessionJoin() address:%s cc4:%p\n", address, newfv->collabClient );
490 :
491 : inPythonStartedCollabSession = 1;
492 : PyObject* ret = PyFV_From_FV_I( newfv );
493 : Py_RETURN( ret );
494 : #endif
495 :
496 0 : Py_RETURN( self );
497 : }
498 :
499 :
500 : #ifdef BUILD_COLLAB
501 : static void InvokeCollabSessionSetUpdatedCallback(PyFF_Font *self) {
502 : if( CollabSessionSetUpdatedCallback )
503 : {
504 : PyObject *arglist;
505 : PyObject *result;
506 :
507 : arglist = Py_BuildValue("(O)", self);
508 : result = PyObject_CallObject(CollabSessionSetUpdatedCallback, arglist);
509 : Py_DECREF(arglist);
510 : }
511 : }
512 : #endif
513 :
514 0 : static PyObject *PyFFFont_CollabSessionRunMainLoop(PyFF_Font *self, PyObject *args)
515 : {
516 : #ifdef BUILD_COLLAB
517 : int timeoutMS = 1000;
518 : int iterationTime = 50;
519 : int64_t originalSeq = collabclient_getCurrentSequenceNumber( self->fv->collabClient );
520 :
521 : TRACE("PyFFFont_CollabSessionRunMainLoop() called fv:%p\n", self->fv );
522 : TRACE("PyFFFont_CollabSessionRunMainLoop() called cc:%p\n", self->fv->collabClient );
523 : for( ; timeoutMS > 0; timeoutMS -= iterationTime )
524 : {
525 : g_usleep( iterationTime * 1000 );
526 : MacServiceReadFDs();
527 : }
528 :
529 : TRACE("originalSeq:%ld\n",(long int)(originalSeq));
530 : TRACE(" newSeq:%ld\n",(long int)(collabclient_getCurrentSequenceNumber( self->fv->collabClient )));
531 :
532 : if( originalSeq < collabclient_getCurrentSequenceNumber( self->fv->collabClient ))
533 : {
534 : printf("***********************\n");
535 : printf("*********************** calling python updated function!!\n");
536 : printf("***********************\n");
537 : printf("***********************\n");
538 : InvokeCollabSessionSetUpdatedCallback( self );
539 : }
540 : #endif
541 :
542 0 : Py_RETURN( self );
543 : }
544 :
545 :
546 0 : static PyObject *PyFFFont_CollabSessionSetUpdatedCallback(PyFF_Font *self, PyObject *args)
547 : {
548 0 : PyObject *result = NULL;
549 : PyObject *temp;
550 :
551 0 : if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
552 0 : if (!PyCallable_Check(temp)) {
553 0 : PyErr_SetString(PyExc_TypeError, "parameter must be callable");
554 0 : return NULL;
555 : }
556 0 : Py_XINCREF(temp);
557 0 : Py_XDECREF(CollabSessionSetUpdatedCallback);
558 0 : CollabSessionSetUpdatedCallback = temp;
559 : /* Boilerplate to return "None" */
560 0 : Py_INCREF(Py_None);
561 0 : result = Py_None;
562 : }
563 0 : return result;
564 : }
565 :
566 : ////////////////////////////////////////////////////////////////////////////////
567 : ////////////////////////////////////////////////////////////////////////////////
568 : ////////////////////////////////////////////////////////////////////////////////
569 :
570 : #ifdef FONTFORGE_CAN_USE_GTK
571 :
572 : #define GMenuItem GMenuItem_Glib
573 : #define GTimer GTimer_GTK
574 : #define GList GList_Glib
575 : #include <gdk/gdkx.h>
576 : #undef GTimer
577 : #undef GList
578 : #undef GMenuItem
579 :
580 :
581 : static void GtkWindowToMainEventLoop_fd_callback( int fd, void* datas )
582 : {
583 : // printf("GtkWindowToMainEventLoop_fd_callback()\n");
584 : gboolean may_block = false;
585 : g_main_context_iteration( g_main_context_default(), may_block );
586 : }
587 :
588 :
589 :
590 : static PyObject *PyFFFont_addGtkWindowToMainEventLoop(PyFF_Font *self, PyObject *args)
591 : {
592 : PyObject *result = NULL;
593 : PyObject *temp;
594 : int v = 0;
595 :
596 : if ( !PyArg_ParseTuple( args, "i", &v ))
597 : return( NULL );
598 :
599 : gpointer gdkwindow = gdk_xid_table_lookup( v );
600 :
601 : if( gdkwindow )
602 : {
603 : Display* d = GDK_WINDOW_XDISPLAY(gdkwindow);
604 : int fd = XConnectionNumber(d);
605 : if( fd )
606 : {
607 : gpointer udata = 0;
608 : GDrawAddReadFD( 0, fd, udata, GtkWindowToMainEventLoop_fd_callback );
609 : }
610 : }
611 :
612 : /* Boilerplate to return "None" */
613 : Py_INCREF(Py_None);
614 : result = Py_None;
615 : return result;
616 : }
617 :
618 : static PyObject *PyFFFont_getGtkWindowMainEventLoopFD(PyFF_Font *self, PyObject *args)
619 : {
620 : PyObject *result = NULL;
621 : PyObject *temp;
622 : int v = 0;
623 :
624 : if ( !PyArg_ParseTuple( args, "i", &v ))
625 : return( NULL );
626 :
627 : gpointer gdkwindow = gdk_xid_table_lookup( v );
628 :
629 : if( gdkwindow )
630 : {
631 : Display* d = GDK_WINDOW_XDISPLAY(gdkwindow);
632 : int fd = XConnectionNumber(d);
633 : if( fd )
634 : {
635 : return( Py_BuildValue("i", fd ));
636 : }
637 : }
638 :
639 : /* Boilerplate to return "None" */
640 : Py_INCREF(Py_None);
641 : result = Py_None;
642 : return result;
643 : }
644 :
645 : static PyObject *PyFFFont_removeGtkWindowToMainEventLoop(PyFF_Font *self, PyObject *args)
646 : {
647 : PyObject *result = NULL;
648 : PyObject *temp;
649 : int v = 0;
650 :
651 : if ( !PyArg_ParseTuple( args, "i", &v ))
652 : return( NULL );
653 :
654 : gpointer gdkwindow = gdk_xid_table_lookup( v );
655 :
656 : if( gdkwindow )
657 : {
658 : Display* d = GDK_WINDOW_XDISPLAY(gdkwindow);
659 : int fd = XConnectionNumber(d);
660 : if( fd )
661 : {
662 : gpointer udata = 0;
663 : GDrawRemoveReadFD( 0, fd, udata );
664 : }
665 : }
666 :
667 : /* Boilerplate to return "None" */
668 : Py_INCREF(Py_None);
669 : result = Py_None;
670 : return result;
671 : }
672 :
673 : static PyObject *PyFFFont_removeGtkWindowToMainEventLoopByFD(PyFF_Font *self, PyObject *args)
674 : {
675 : PyObject *result = NULL;
676 : PyObject *temp;
677 : int v = 0;
678 :
679 : if ( !PyArg_ParseTuple( args, "i", &v ))
680 : return( NULL );
681 :
682 : int fd = v;
683 : gpointer udata = 0;
684 : GDrawRemoveReadFD( 0, fd, udata );
685 :
686 : /* Boilerplate to return "None" */
687 : Py_INCREF(Py_None);
688 : result = Py_None;
689 : return result;
690 : }
691 :
692 : #else
693 :
694 : #define EMPTY_METHOD \
695 : { \
696 : PyObject *result = NULL; \
697 : /* Boilerplate to return "None" */ \
698 : Py_INCREF(Py_None); \
699 : result = Py_None; \
700 : return result; \
701 : }
702 : \
703 0 : static PyObject *PyFFFont_addGtkWindowToMainEventLoop(PyFF_Font *self, PyObject *args)
704 0 : { EMPTY_METHOD; }
705 0 : static PyObject *PyFFFont_getGtkWindowMainEventLoopFD(PyFF_Font *self, PyObject *args)
706 0 : { EMPTY_METHOD; }
707 0 : static PyObject *PyFFFont_removeGtkWindowToMainEventLoop(PyFF_Font *self, PyObject *args)
708 0 : { EMPTY_METHOD; }
709 0 : static PyObject *PyFFFont_removeGtkWindowToMainEventLoopByFD(PyFF_Font *self, PyObject *args)
710 0 : { EMPTY_METHOD; }
711 :
712 : #endif
713 :
714 : ////////////////////////////////////////////////////////////////////////////////
715 : ////////////////////////////////////////////////////////////////////////////////
716 : ////////////////////////////////////////////////////////////////////////////////
717 :
718 :
719 :
720 0 : static PyObject *PyFF_getLastChangedName(PyObject *UNUSED(self), PyObject *args) {
721 : PyObject *ret;
722 0 : ret = Py_BuildValue("s", Collab_getLastChangedName() );
723 0 : return( ret );
724 : }
725 0 : static PyObject *PyFF_getLastChangedPos(PyObject *UNUSED(self), PyObject *args) {
726 : PyObject *ret;
727 0 : ret = Py_BuildValue("i", Collab_getLastChangedPos() );
728 0 : return( ret );
729 : }
730 0 : static PyObject *PyFF_getLastChangedCodePoint(PyObject *UNUSED(self), PyObject *args) {
731 : PyObject *ret;
732 0 : ret = Py_BuildValue("i", Collab_getLastChangedCodePoint() );
733 0 : return( ret );
734 : }
735 0 : static PyObject *PyFF_getLastSeq(PyFF_Font *self, PyObject *args)
736 : {
737 0 : int64_t seq = collabclient_getCurrentSequenceNumber( self->fv->collabClient );
738 : PyObject *ret;
739 0 : ret = Py_BuildValue("i", seq );
740 0 : return( ret );
741 : }
742 :
743 :
744 :
745 : /******************************/
746 : /******************************/
747 : /******************************/
748 :
749 : PyMethodDef PyFF_FontUI_methods[] = {
750 : { "CollabSessionStart", (PyCFunction) PyFFFont_CollabSessionStart, METH_VARARGS, "Start a collab session at the given address (or the public IP address by default)" },
751 :
752 : { "CollabSessionJoin", (PyCFunction) PyFFFont_CollabSessionJoin, METH_VARARGS, "Join a collab session at the given address (or localhost by default)" },
753 : { "CollabSessionRunMainLoop", (PyCFunction) PyFFFont_CollabSessionRunMainLoop, METH_VARARGS, "Run the main loop, checking for and reacting to Collab messages for the given number of milliseconds (or 1 second by default)" },
754 : { "CollabSessionSetUpdatedCallback", (PyCFunction) PyFFFont_CollabSessionSetUpdatedCallback, METH_VARARGS, "Python function to call after a new collab update has been applied" },
755 :
756 : { "CollabLastChangedName", (PyCFunction) PyFF_getLastChangedName, METH_VARARGS, "" },
757 : { "CollabLastChangedPos", (PyCFunction) PyFF_getLastChangedPos, METH_VARARGS, "" },
758 : { "CollabLastChangedCodePoint", (PyCFunction) PyFF_getLastChangedCodePoint, METH_VARARGS, "" },
759 : { "CollabLastSeq", (PyCFunction) PyFF_getLastSeq, METH_VARARGS, "" },
760 :
761 :
762 : PYMETHODDEF_EMPTY /* Sentinel */
763 : };
764 :
765 : PyMethodDef module_fontforge_ui_methods[] = {
766 :
767 : // allow python code to expose it's gtk mainloop to fontforge
768 : { "addGtkWindowToMainEventLoop", (PyCFunction) PyFFFont_addGtkWindowToMainEventLoop, METH_VARARGS, "fixme." },
769 : { "getGtkWindowMainEventLoopFD", (PyCFunction) PyFFFont_getGtkWindowMainEventLoopFD, METH_VARARGS, "fixme." },
770 : { "removeGtkWindowToMainEventLoop", (PyCFunction) PyFFFont_removeGtkWindowToMainEventLoop, METH_VARARGS, "fixme." },
771 : { "removeGtkWindowToMainEventLoopByFD", (PyCFunction) PyFFFont_removeGtkWindowToMainEventLoopByFD, METH_VARARGS, "fixme." },
772 :
773 :
774 : PYMETHODDEF_EMPTY /* Sentinel */
775 : };
776 :
777 :
778 : static PyMethodDef*
779 80 : copyUIMethodsToBaseTable( PyMethodDef* ui, PyMethodDef* md )
780 : {
781 : TRACE("copyUIMethodsToBaseTable()\n");
782 : // move md to the first target slot
783 6920 : for( ; md->ml_name; )
784 : {
785 6760 : md++;
786 : }
787 560 : for( ; ui->ml_name; ui++, md++ )
788 : {
789 480 : memcpy( md, ui, sizeof(PyMethodDef));
790 : }
791 80 : return md;
792 : }
793 :
794 : static void python_ui_fd_callback( int fd, void* udata );
795 0 : static void python_ui_setup_callback( bool makefifo )
796 : {
797 : #ifndef __MINGW32__
798 0 : int fd = 0;
799 0 : int err = 0;
800 : char *userCacheDir, *sockPath;
801 :
802 0 : userCacheDir = getFontForgeUserDir(Cache);
803 0 : if ( userCacheDir==NULL ) {
804 0 : LogError("PythonUISetup: failed to discover user cache dir path");
805 0 : return;
806 : }
807 :
808 0 : asprintf(&sockPath, "%s/python-socket", userCacheDir);
809 0 : free(userCacheDir);
810 :
811 0 : if( makefifo ) {
812 0 : err = mkfifo( sockPath, 0600 );
813 0 : if ( err==-1 && errno!=EEXIST) {
814 0 : LogError("PythonUISetup: unable to mkfifo('%s'): errno %d\n", sockPath, errno);
815 0 : free(sockPath);
816 0 : return;
817 : }
818 : }
819 :
820 0 : fd = open( sockPath, O_RDONLY | O_NDELAY );
821 0 : if ( fd==-1) {
822 0 : LogError("PythonUISetup: unable to open socket '%s': errno %d\n", sockPath, errno);
823 0 : free(sockPath);
824 0 : return;
825 : }
826 0 : free(sockPath);
827 :
828 0 : void* udata = 0;
829 0 : GDrawAddReadFD( 0, fd, udata, python_ui_fd_callback );
830 0 : return;
831 : #endif
832 : }
833 :
834 0 : static void python_ui_fd_callback( int fd, void* udata )
835 : {
836 : #ifndef __MINGW32__
837 : char data[ 1024*100 + 1 ];
838 0 : memset(data, '\0', 1024*100 );
839 : // sleep( 1 );
840 0 : int sz = read( fd, data, 1024*100 );
841 : // fprintf( stderr, "python_ui_fd_callback() sz:%d d:%s\n", sz, data );
842 :
843 0 : CharView* cv = CharViewFindActive();
844 0 : if( cv )
845 : {
846 0 : int layer = 0;
847 0 : PyFF_ScriptString( cv->b.fv, cv->b.sc, layer, data );
848 : }
849 :
850 0 : GDrawRemoveReadFD( 0, fd, udata );
851 0 : python_ui_setup_callback( 0 );
852 : #endif
853 0 : }
854 :
855 0 : void PythonUI_namedpipe_Init(void) {
856 0 : python_ui_setup_callback( 1 );
857 :
858 0 : }
859 :
860 40 : void PythonUI_Init(void) {
861 : TRACE("PythonUI_Init()\n");
862 40 : FfPy_Replace_MenuItemStub(PyFF_registerMenuItem);
863 40 : set_pyFF_maybeCallCVPreserveState_Func( pyFF_maybeCallCVPreserveState );
864 40 : set_pyFF_sendRedoIfInSession_Func( pyFF_sendRedoIfInSession_Func_Real );
865 :
866 40 : copyUIMethodsToBaseTable( PyFF_FontUI_methods, PyFF_Font_methods );
867 40 : copyUIMethodsToBaseTable( module_fontforge_ui_methods, module_fontforge_methods );
868 :
869 :
870 40 : }
871 : #endif
872 :
|