1 /* Copyright (C) 2012 by Ben Martin */
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 
28 #include <fontforge-config.h>
29 
30 #include "gdraw.h"
31 #include "gfile.h"
32 #include "hotkeys.h"
33 #include "intl.h"
34 #include "ustring.h"
35 
36 #include <errno.h>
37 #include <locale.h>
38 #include <unistd.h>
39 
40 #ifdef __MINGW32__
41 #define fsync _commit
42 #endif
43 
44 static int hotkeySystemCanUseMacCommand = 0;
45 
46 struct dlistnode* hotkeys = 0;
47 
hotkeyGetWindowTypeString(Hotkey * hk)48 static char* hotkeyGetWindowTypeString( Hotkey* hk )
49 {
50     char* pt = strchr(hk->action,'.');
51     if( !pt )
52 	return 0;
53     int len = pt - hk->action;
54     static char buffer[HOTKEY_ACTION_MAX_SIZE+1];
55     strncpy( buffer, hk->action, len );
56     buffer[len] = '\0';
57     return buffer;
58 }
59 
60 /**
61  * Does the hotkey 'hk' have the right window_type to trigger its
62  * action on the window 'w'.
63  */
hotkeyHasMatchingWindowTypeString(char * windowType,Hotkey * hk)64 static int hotkeyHasMatchingWindowTypeString( char* windowType, Hotkey* hk ) {
65     if( !windowType )
66 	return 0;
67     char* pt = strchr(hk->action,'.');
68     if( !pt )
69 	return 0;
70 
71     int len = pt - hk->action;
72     if( strlen(windowType) < len )
73 	return 0;
74     int rc = strncmp( windowType, hk->action, len );
75     if( !rc )
76 	return 1;
77     return 0;
78 }
79 
80 /**
81  * Does the hotkey 'hk' have the right window_type to trigger its
82  * action on the window 'w'.
83  */
84 /*
85  * Unused
86 static int hotkeyHasMatchingWindowType( GWindow w, Hotkey* hk ) {
87     char* windowType = GDrawGetWindowTypeName( w );
88     return hotkeyHasMatchingWindowTypeString( windowType, hk );
89 }
90 */
91 
92 static struct dlistnodeExternal*
hotkeyFindAllByStateAndKeysym(char * windowType,uint16 state,uint16 keysym)93 hotkeyFindAllByStateAndKeysym( char* windowType, uint16 state, uint16 keysym ) {
94 
95     struct dlistnodeExternal* ret = 0;
96     struct dlistnode* node = hotkeys;
97     for( ; node; node=node->next ) {
98 	Hotkey* hk = (Hotkey*)node;
99 	if( hk->keysym ) {
100 	    if( keysym == hk->keysym ) {
101 		if( state == hk->state ) {
102 		    if( hotkeyHasMatchingWindowTypeString( windowType, hk ) ) {
103 			dlist_pushfront_external( (struct dlistnode **)&ret, hk );
104 		    }
105 		}
106 	    }
107 	}
108     }
109     return ret;
110 }
111 
112 
hotkeyFindByStateAndKeysym(char * windowType,uint16 state,uint16 keysym)113 static Hotkey* hotkeyFindByStateAndKeysym( char* windowType, uint16 state, uint16 keysym ) {
114 
115     struct dlistnode* node = hotkeys;
116     for( ; node; node=node->next ) {
117 	Hotkey* hk = (Hotkey*)node;
118 	if( hk->keysym ) {
119 	    if( keysym == hk->keysym ) {
120 		if( state == hk->state ) {
121 		    if( hotkeyHasMatchingWindowTypeString( windowType, hk ) ) {
122 			return hk;
123 		    }
124 		}
125 	    }
126 	}
127     }
128     return 0;
129 }
130 
131 
hotkeyFindAllByEvent(GWindow w,GEvent * event)132 struct dlistnodeExternal* hotkeyFindAllByEvent( GWindow w, GEvent *event ) {
133     char* windowType = GDrawGetWindowTypeName( w );
134     return hotkeyFindAllByStateAndKeysym( windowType,
135 					  event->u.chr.state,
136 					  event->u.chr.keysym );
137 }
138 
139 
hotkeyFindByEvent(GWindow w,GEvent * event)140 Hotkey* hotkeyFindByEvent( GWindow w, GEvent *event ) {
141     char* windowType = GDrawGetWindowTypeName( w );
142     return hotkeyFindByStateAndKeysym( windowType, event->u.chr.state, event->u.chr.keysym );
143 }
144 
145 /**
146  * Return the file name of the user defined hotkeys.
147  * The return value must be freed by the caller.
148  *
149  * If extension is not null it will be postpended to the returned path.
150  * This way you get to avoid dealing with appending to a string in c.
151  */
getHotkeyFilename(char * extension)152 static char* getHotkeyFilename(char* extension) {
153     char *ret = NULL;
154     char buffer[1025];
155     char *ffdir = getFontForgeUserDir(Config);
156 
157     if ( ffdir==NULL ) {
158         fprintf(stderr,_("Cannot find your hotkey definition file!\n"));
159         return NULL;
160     }
161     if( !extension )
162         extension = "";
163 
164     sprintf(buffer,"%s/hotkeys%s", ffdir, extension);
165     ret = copy(buffer);
166     free(ffdir);
167     return ret;
168 }
169 
170 /**
171  * Remove leading and trailing " " characters. N memory allocations
172  * are performed, null is injected at the end of string and if there are leading
173  * spaces the return value will be past them.
174  */
trimspaces(char * line)175 static char* trimspaces( char* line ) {
176    while ( line[strlen(line)-1]==' ' )
177 	line[strlen(line)-1] = '\0';
178    while( *line == ' ' )
179 	++line;
180     return line;
181 }
182 
hotkeySetFull(char * action,char * keydefinition,int append,int isUserDefined)183 static Hotkey* hotkeySetFull( char* action, char* keydefinition, int append, int isUserDefined )
184 {
185     Hotkey* hk = calloc(1,sizeof(Hotkey));
186     if ( hk==NULL ) return 0;
187     strncpy( hk->action, action, HOTKEY_ACTION_MAX_SIZE );
188     HotkeyParse( hk, keydefinition );
189 
190     // If we didn't get a hotkey (No Shortcut)
191     // then we move along
192     if( !hk->state && !hk->keysym ) {
193 	free(hk);
194 	return 0;
195     }
196 
197     // If we already have a binding for that hotkey combination
198     // for this window, forget the old one. One combo = One action.
199     if( !append ) {
200 	Hotkey* oldkey = hotkeyFindByStateAndKeysym( hotkeyGetWindowTypeString(hk),
201 						     hk->state, hk->keysym );
202 	if( oldkey ) {
203 	    dlist_erase( &hotkeys, (struct dlistnode *)oldkey );
204 	    free(oldkey);
205 	}
206     }
207 
208     hk->isUserDefined = isUserDefined;
209     dlist_pushfront( &hotkeys, (struct dlistnode *)hk );
210     return hk;
211 }
hotkeySet(char * action,char * keydefinition,int append)212 Hotkey* hotkeySet( char* action, char* keydefinition, int append )
213 {
214     int isUserDefined = 1;
215     return hotkeySetFull( action, keydefinition, append, isUserDefined );
216 }
217 
218 
219 
220 /**
221  * Load all the hotkeys from the file at filename, marking them as userdefined
222  * if isUserDefined is set.
223  */
loadHotkeysFromFile(const char * filename,int isUserDefined,int warnIfNotFound)224 static void loadHotkeysFromFile( const char* filename, int isUserDefined, int warnIfNotFound )
225 {
226     char line[1100];
227     FILE* f = fopen(filename,"r");
228     if( !f ) {
229 	if( warnIfNotFound ) {
230 	    fprintf(stderr,_("Failed to open hotkey definition file: %s\n"), filename );
231 	}
232 	return;
233     }
234 
235     while ( fgets(line,sizeof(line),f)!=NULL ) {
236 	int append = 0;
237 
238 	if ( *line=='#' )
239 	    continue;
240 	char* pt = strchr(line,':');
241 	if ( pt==NULL )
242 	    continue;
243 	*pt = '\0';
244 	char* keydefinition = pt+1;
245 	chomp( keydefinition );
246 	keydefinition = trimspaces( keydefinition );
247 	char* action = line;
248 	if( line[0] == '+' ) {
249 	    append = 1;
250 	    action++;
251 	}
252 
253 	hotkeySetFull( action, keydefinition, append, isUserDefined );
254     }
255     fclose(f);
256 }
257 
258 /**
259  * Load all the default hotkeys for this locale and then the users
260  * ~/.Fontforge/hotkeys.
261  */
hotkeysLoad()262 void hotkeysLoad()
263 {
264     char localefn[PATH_MAX+1];
265     char* p = 0;
266     char* sharedir = getShareDir();
267 
268     snprintf(localefn,PATH_MAX,"%s/hotkeys/default", sharedir );
269     loadHotkeysFromFile( localefn, false, true );
270 
271     // FUTURE: perhaps find out how to convert en_AU.UTF-8 that setlocale()
272     //   gives to its fallback of en_GB. There are likely to be a bunch of other
273     //   languages which are similar but have specific locales
274     char* currentlocale = copy(setlocale(LC_MESSAGES, 0));
275     snprintf(localefn,PATH_MAX,"%s/hotkeys/%s", sharedir, currentlocale);
276     loadHotkeysFromFile( localefn, false, false );
277     while((p = strrchr( currentlocale, '.' ))) {
278 	*p = '\0';
279 	snprintf(localefn,PATH_MAX,"%s/hotkeys/%s", sharedir, currentlocale);
280 	loadHotkeysFromFile( localefn, false, false );
281     }
282     while((p = strrchr( currentlocale, '_' ))) {
283 	*p = '\0';
284 	snprintf(localefn,PATH_MAX,"%s/hotkeys/%s", sharedir, currentlocale);
285 	loadHotkeysFromFile( localefn, false, false );
286     }
287     free(currentlocale);
288 
289     char* fn = getHotkeyFilename(0);
290     if( !fn ) {
291 	return;
292     }
293     loadHotkeysFromFile( fn, true, false );
294     free(fn);
295 }
296 
hotkeysSaveCallback(Hotkey * hk,FILE * f)297 static void hotkeysSaveCallback(Hotkey* hk,FILE* f) {
298     if( hk->isUserDefined ) {
299 	fprintf( f, "%s:%s\n", hk->action, hk->text );
300     }
301 }
302 
303 /**
304  * Save all the user defined hotkeys back to the users
305  * ~/.Fontforge/hotkeys file.
306  */
hotkeysSave()307 void hotkeysSave() {
308     char* fn = getHotkeyFilename(".new");
309     if( !fn ) {
310 	return;
311     }
312     FILE* f = fopen(fn,"w");
313     if( !f ) {
314 	free(fn);
315 	fprintf(stderr,_("Failed to open your hotkey definition file for updates.\n"));
316 	return;
317     }
318 
319     dlist_foreach_reverse_udata( &hotkeys,
320 				 (dlist_foreach_udata_func_type)hotkeysSaveCallback, f );
321     fsync(fileno(f));
322     fclose(f);
323 
324     //
325     // Atomic rename of new over the old.
326     //
327     char* newpath = getHotkeyFilename(0);
328 #ifdef __MINGW32__
329     //Atomic rename doesn't exist on Windows.
330     unlink(newpath);
331 #endif
332     int rc = rename( fn, newpath );
333     int e = errno;
334     free(fn);
335     free(newpath);
336     if( rc == -1 ) {
337 	fprintf(stderr,_("Failed to rename the new hotkeys file over your old one!\n"));
338 	fprintf(stderr,_("Reason:%s\n"), strerror(e));
339     }
340 }
341 
342 
hotkeysGetKeyDescriptionFromAction(char * action)343 char* hotkeysGetKeyDescriptionFromAction( char* action ) {
344     struct dlistnode* node = hotkeys;
345     for( ; node; node=node->next ) {
346 	Hotkey* hk = (Hotkey*)node;
347 	if(!strcmp(hk->action,action)) {
348 	    return hk->text;
349 	}
350     }
351     return 0;
352 }
353 
354 
355 /**
356  * Find a hotkey by the action. This is useful for menus to find out
357  * what hotkey is currently bound to them. So if the user changes
358  * file/open to be alt+j then the menu can adjust the hotkey is is
359  * displaying to show the user what key they have assigned.
360  */
hotkeyFindByAction(char * action)361 static Hotkey* hotkeyFindByAction( char* action ) {
362     struct dlistnode* node = hotkeys;
363     for( ; node; node=node->next ) {
364 	Hotkey* hk = (Hotkey*)node;
365 	if(!strcmp(hk->action,action)) {
366 	    return hk;
367 	}
368     }
369     return 0;
370 }
371 
isImmediateKey(GWindow w,char * path,GEvent * event)372 Hotkey* isImmediateKey( GWindow w, char* path, GEvent *event )
373 {
374     char* wt = GDrawGetWindowTypeName(w);
375     if(!wt)
376 	return 0;
377 
378     char* subMenuName = "_ImmediateKeys";
379     char line[PATH_MAX+1];
380     snprintf(line,PATH_MAX,"%s.%s.%s",wt, subMenuName, path );
381 //    printf("line:%s\n",line);
382     Hotkey* hk = hotkeyFindByAction( line );
383     if( !hk )
384 	return 0;
385     if( !hk->action )
386 	return 0;
387 
388     if( event->u.chr.keysym == hk->keysym )
389 	return hk;
390 
391     return 0;
392 }
393 
hotkeyFindByMenuPathInSubMenu(GWindow w,char * subMenuName,char * path)394 Hotkey* hotkeyFindByMenuPathInSubMenu( GWindow w, char* subMenuName, char* path ) {
395 
396     char* wt = GDrawGetWindowTypeName(w);
397     if(!wt)
398 	return 0;
399 
400     char line[PATH_MAX+1];
401     snprintf(line,PATH_MAX,"%s.%s%s%s",wt, subMenuName, ".Menu.", path );
402 //    printf("line:%s\n",line);
403     return(hotkeyFindByAction(line));
404 }
405 
hotkeyFindByMenuPath(GWindow w,char * path)406 Hotkey* hotkeyFindByMenuPath( GWindow w, char* path ) {
407 
408     char* wt = GDrawGetWindowTypeName(w);
409     if(!wt)
410 	return 0;
411 
412     char line[PATH_MAX+1];
413     snprintf(line,PATH_MAX,"%s%s%s",wt, ".Menu.", path );
414     return(hotkeyFindByAction(line));
415 }
416 
417 
hotkeyTextToMacModifiers(char * keydesc)418 char* hotkeyTextToMacModifiers( char* keydesc )
419 {
420     keydesc = copy( keydesc );
421     keydesc = str_replace_all( keydesc, "Ctrl", "⌘", 1 );
422     keydesc = str_replace_all( keydesc, "Ctl", "⌘", 1 );
423     keydesc = str_replace_all( keydesc, "Command", "⌘", 1 );
424     keydesc = str_replace_all( keydesc, "Cmd", "⌘", 1 );
425     keydesc = str_replace_all( keydesc, "Shift", "⇧", 1 );
426     keydesc = str_replace_all( keydesc, "Shft", "⇧", 1 );
427     keydesc = str_replace_all( keydesc, "Alt", "⎇", 1 );
428     keydesc = str_replace_all( keydesc, "+", "", 1 );
429     return keydesc;
430 }
431 
432 
hotkeyTextWithoutModifiers(char * hktext)433 char* hotkeyTextWithoutModifiers( char* hktext ) {
434     if( !strcmp( hktext, "no shortcut" )
435 	|| !strcmp( hktext, "No shortcut" )
436 	|| !strcmp( hktext, "No Shortcut" ))
437 	return "";
438 
439     char* p = strrchr( hktext, '+' );
440     if( !p )
441 	return hktext;
442 
443     //
444     // Handle Control++ by moving back over the last plus
445     //
446     if( p > hktext )
447     {
448 	char* pp = p - 1;
449 	if( *pp == '+' )
450 	    --p;
451     }
452     return p+1;
453 }
454 
455 
hotkeySystemSetCanUseMacCommand(int v)456 void hotkeySystemSetCanUseMacCommand( int v )
457 {
458     hotkeySystemCanUseMacCommand = v;
459 }
460 
hotkeySystemGetCanUseMacCommand()461 int hotkeySystemGetCanUseMacCommand()
462 {
463     return hotkeySystemCanUseMacCommand;
464 }
465