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