/* Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact) Copyright 2018 Albert Graef Read and process the configuration file ~/.midizaprc Lines starting with # are comments. The file is a sequence of sections defining translation classes. Each section takes the following form: [name] regex CC<0..127> output # control change PC<0..127> output # program change PB output # pitch bend CP output # channel pressure KP:[#b]<-11..11> output # key pressure (aftertouch) [#b]<-11..11> output # note When focus is on a window whose class or title matches regex, the following translation class is in effect. An empty regex for the last class will always match, allowing default translations. Any output sequences not bound in a matched section will be loaded from the default section if they are bound there. Each "[name] regex" line introduces the list of MIDI message translations for the named translation class. The name is only used for debugging output, and needn't be unique. The following lines indicate what output should be produced for the given MIDI messages. MIDI messages are on channel 1 by default; a suffix of the form -<1..16> can be used to specify a different MIDI channel. E.g., C3-10 denotes note C3 on MIDI channel 10. Note messages are specified using the cutomary notation (note name A..G, optionally followed by an accidental, # or b, followed by a (zero-based) MIDI octave number. Note that all MIDI octaves start at the note C, so B0 comes before C1. By default, C5 denotes middle C, A5 is the chamber pitch (usually at 440 Hz). Enharmonic spellings are equivalent, so, e.g., D# and Eb denote exactly the same MIDI note. More details on the syntax of MIDI messages can be found in the comments preceding the parse_midi() routine below. */ #include "midizap.h" int default_debug_regex = 0; int default_debug_strokes = 0; int default_debug_keys = 0; int default_debug_midi = 0; int debug_regex = 0; int debug_strokes = 0; int debug_keys = 0; int debug_midi = 0; int midi_octave = 0; char *jack_client_name, *jack_in_regex[2], *jack_out_regex[2]; char * allocate(size_t len) { char *ret = (char *)calloc(1, len); if (ret == NULL) { fprintf(stderr, "out of memory!\n"); exit(1); } return ret; } char * alloc_strcat(char *a, char *b) { size_t len = 0; char *result; if (a != NULL) { len += strlen(a); } if (b != NULL) { len += strlen(b); } result = allocate(len+1); result[0] = '\0'; if (a != NULL) { strcpy(result, a); } if (b != NULL) { strcat(result, b); } return result; } static char *read_line_buffer = NULL; static int read_line_buffer_length = 0; #define BUF_GROWTH_STEP 1024 // read a line of text from the given file into a managed buffer. // returns a partial line at EOF if the file does not end with \n. // exits with error message on read error. char * read_line(FILE *f, char *name) { int pos = 0; char *new_buffer; int new_buffer_length; if (read_line_buffer == NULL) { read_line_buffer_length = BUF_GROWTH_STEP; read_line_buffer = allocate(read_line_buffer_length); read_line_buffer[0] = '\0'; } while (1) { read_line_buffer[read_line_buffer_length-1] = '\377'; if (fgets(read_line_buffer+pos, read_line_buffer_length-pos, f) == NULL) { if (feof(f)) { if (pos > 0) { // partial line at EOF return read_line_buffer; } else { return NULL; } } perror(name); exit(1); } if (read_line_buffer[read_line_buffer_length-1] != '\0') { return read_line_buffer; } if (read_line_buffer[read_line_buffer_length-2] == '\n') { return read_line_buffer; } new_buffer_length = read_line_buffer_length + BUF_GROWTH_STEP; new_buffer = allocate(new_buffer_length); memcpy(new_buffer, read_line_buffer, read_line_buffer_length); free(read_line_buffer); pos = read_line_buffer_length-1; read_line_buffer = new_buffer; read_line_buffer_length = new_buffer_length; } } static translation *first_translation_section = NULL; static translation *last_translation_section = NULL; translation *default_translation, *default_midi_translation[2]; translation * new_translation_section(char *name, int mode, char *regex) { translation *ret = (translation *)allocate(sizeof(translation)); int err; memset(ret, 0, sizeof(translation)); if (debug_strokes) { printf("------------------------\n[%s] %s%s\n\n", name, mode==1?"TITLE ":mode==2?"CLASS ":"", regex); } ret->next = NULL; ret->name = alloc_strcat(name, NULL); ret->mode = mode; if (regex == NULL || *regex == '\0') { ret->is_default = 1; if (!strcmp(name, "MIDI")) default_midi_translation[0] = ret; else if (!strcmp(name, "MIDI2")) { default_midi_translation[1] = ret; ret->portno = 1; } else default_translation = ret; } else { ret->is_default = 0; err = regcomp(&ret->regex, regex, REG_EXTENDED|REG_NOSUB); if (err != 0) { regerror(err, &ret->regex, read_line_buffer, read_line_buffer_length); fprintf(stderr, "error compiling regex for [%s]: %s\n", name, read_line_buffer); regfree(&ret->regex); free(ret->name); free(ret); return NULL; } } if (first_translation_section == NULL) { first_translation_section = ret; last_translation_section = ret; } else { last_translation_section->next = ret; last_translation_section = ret; } return ret; } void free_strokes(stroke *s) { stroke *next; while (s != NULL) { next = s->next; if (s->steps) free(s->steps); free(s); s = next; } } static int stroke_data_cmp(const void *a, const void *b) { const stroke_data *ad = (const stroke_data*)a; const stroke_data *bd = (const stroke_data*)b; if (ad->chan == bd->chan) return ad->data - bd->data; else return ad->chan - bd->chan; } static void finish_stroke_data(stroke_data **sd, uint16_t *n, uint16_t *a) { if (*a && *a > *n) { // realloc to needed size *sd = realloc(*sd, (*n)*sizeof(stroke_data)); *a = *n; } // sort by chan/data for faster access qsort(*sd, *n, sizeof(stroke_data), stroke_data_cmp); } static void free_stroke_data(stroke_data *sd, uint16_t n) { uint16_t i; for (i = 0; i < n; i++) { free_strokes(sd[i].s[0]); free_strokes(sd[i].s[1]); if (sd[i].steps[0]) free(sd[i].steps[0]); if (sd[i].steps[1]) free(sd[i].steps[1]); } free(sd); } static int *stepsdup(int n_steps, int *steps) { if (n_steps) { int *ret = malloc(n_steps*sizeof(int)); memcpy(ret, steps, n_steps*sizeof(int)); return ret; } else return 0; } static stroke **find_stroke_data(stroke_data **sd, int chan, int data, int index, int step, int n_steps, int *steps, int incr, int mod, int anyshift, uint16_t *n, uint16_t *a) { uint16_t i; for (i = 0; i < *n; i++) { if ((*sd)[i].chan == chan && (*sd)[i].data == data) { // existing entry if ((*sd)[i].s[index]) return 0; (*sd)[i].step[index] = step; (*sd)[i].n_steps[index] = n_steps; (*sd)[i].steps[index] = stepsdup(n_steps, steps); (*sd)[i].is_incr = incr; (*sd)[i].mod = mod; (*sd)[i].anyshift = anyshift; return &(*sd)[i].s[index]; } } // add a new entry if (*n >= *a) { // make some room *a = (*a)?2*(*a):8; *sd = realloc(*sd, (*a)*sizeof(stroke_data)); } memset(&(*sd)[*n], 0, sizeof(stroke_data)); (*sd)[*n].chan = chan; (*sd)[*n].data = data; (*sd)[*n].step[index] = step; (*sd)[*n].n_steps[index] = n_steps; (*sd)[*n].steps[index] = stepsdup(n_steps, steps); (*sd)[*n].is_incr = incr; (*sd)[*n].mod = mod; (*sd)[*n].anyshift = anyshift; return &(*sd)[(*n)++].s[index]; } static int check_stroke_data(stroke_data *sd, int chan, int data, uint16_t n) { uint16_t i; for (i = 0; i < n; i++) { if (sd[i].chan == chan && sd[i].data == data) return 1; } return 0; } static stroke **find_note(translation *tr, int shift, int chan, int data, int index, int mod, int step, int n_steps, int *steps, int anyshift) { if (check_stroke_data(tr->notes[shift], chan, data, tr->n_notes[shift])) return 0; else return find_stroke_data(&tr->note[shift], chan, data, index, step, n_steps, steps, 0, mod, anyshift, &tr->n_note[shift], &tr->a_note[shift]); } static stroke **find_notes(translation *tr, int shift, int chan, int data, int index, int step, int anyshift) { if (check_stroke_data(tr->note[shift], chan, data, tr->n_note[shift])) return 0; else return find_stroke_data(&tr->notes[shift], chan, data, index, step, 0, 0, 0, 0, anyshift, &tr->n_notes[shift], &tr->a_notes[shift]); } static stroke **find_pc(translation *tr, int shift, int chan, int data, int index, int anyshift) { return find_stroke_data(&tr->pc[shift], chan, data, index, 0, 0, 0, 0, 0, anyshift, &tr->n_pc[shift], &tr->a_pc[shift]); } static stroke **find_cc(translation *tr, int shift, int chan, int data, int index, int mod, int step, int n_steps, int *steps, int anyshift) { if (check_stroke_data(tr->ccs[shift], chan, data, tr->n_ccs[shift])) return 0; else return find_stroke_data(&tr->cc[shift], chan, data, index, step, n_steps, steps, 0, mod, anyshift, &tr->n_cc[shift], &tr->a_cc[shift]); } static stroke **find_ccs(translation *tr, int shift, int chan, int data, int index, int step, int incr, int anyshift) { if (check_stroke_data(tr->cc[shift], chan, data, tr->n_cc[shift])) return 0; else return find_stroke_data(&tr->ccs[shift], chan, data, index, step, 0, 0, incr, 0, anyshift, &tr->n_ccs[shift], &tr->a_ccs[shift]); } static stroke **find_kp(translation *tr, int shift, int chan, int data, int index, int mod, int step, int n_steps, int *steps, int anyshift) { if (check_stroke_data(tr->kps[shift], chan, data, tr->n_kps[shift])) return 0; else return find_stroke_data(&tr->kp[shift], chan, data, index, step, n_steps, steps, 0, mod, anyshift, &tr->n_kp[shift], &tr->a_kp[shift]); } static stroke **find_kps(translation *tr, int shift, int chan, int data, int index, int step, int anyshift) { if (check_stroke_data(tr->kp[shift], chan, data, tr->n_kp[shift])) return 0; else return find_stroke_data(&tr->kps[shift], chan, data, index, step, 0, 0, 0, 0, anyshift, &tr->n_kps[shift], &tr->a_kps[shift]); } static stroke **find_cp(translation *tr, int shift, int chan, int index, int mod, int step, int n_steps, int *steps, int anyshift) { if (check_stroke_data(tr->cps[shift], chan, 0, tr->n_cps[shift])) return 0; else return find_stroke_data(&tr->cp[shift], chan, 0, index, step, n_steps, steps, 0, mod, anyshift, &tr->n_cp[shift], &tr->a_cp[shift]); } static stroke **find_cps(translation *tr, int shift, int chan, int index, int step, int anyshift) { if (check_stroke_data(tr->cp[shift], chan, 0, tr->n_cp[shift])) return 0; else return find_stroke_data(&tr->cps[shift], chan, 0, index, step, 0, 0, 0, 0, anyshift, &tr->n_cps[shift], &tr->a_cps[shift]); } static stroke **find_pb(translation *tr, int shift, int chan, int index, int mod, int step, int n_steps, int *steps, int anyshift) { if (check_stroke_data(tr->pbs[shift], chan, 0, tr->n_pbs[shift])) return 0; else return find_stroke_data(&tr->pb[shift], chan, 0, index, step, n_steps, steps, 0, mod, anyshift, &tr->n_pb[shift], &tr->a_pb[shift]); } static stroke **find_pbs(translation *tr, int shift, int chan, int index, int step, int anyshift) { if (check_stroke_data(tr->pb[shift], chan, 0, tr->n_pb[shift])) return 0; else return find_stroke_data(&tr->pbs[shift], chan, 0, index, step, 0, 0, 0, 0, anyshift, &tr->n_pbs[shift], &tr->a_pbs[shift]); } static void dup_stroke_data(stroke_data **sd, uint16_t *n, uint16_t *a, stroke_data *sd0, uint16_t n0, stroke_data *sd1, uint16_t n1); void finish_translation_section(translation *tr) { int k; if (tr) { for (k=1; kpc[k], &tr->n_pc[k], &tr->a_pc[k], 0, 0, tr->pc[0], tr->n_pc[0]); dup_stroke_data(&tr->note[k], &tr->n_note[k], &tr->a_note[k], tr->notes[k], tr->n_notes[k], tr->note[0], tr->n_note[0]); dup_stroke_data(&tr->notes[k], &tr->n_notes[k], &tr->a_notes[k], tr->note[k], tr->n_note[k], tr->notes[0], tr->n_notes[0]); dup_stroke_data(&tr->cc[k], &tr->n_cc[k], &tr->a_cc[k], tr->ccs[k], tr->n_ccs[k], tr->cc[0], tr->n_cc[0]); dup_stroke_data(&tr->ccs[k], &tr->n_ccs[k], &tr->a_ccs[k], tr->cc[k], tr->n_cc[k], tr->ccs[0], tr->n_ccs[0]); dup_stroke_data(&tr->pb[k], &tr->n_pb[k], &tr->a_pb[k], tr->pbs[k], tr->n_pbs[k], tr->pb[0], tr->n_pb[0]); dup_stroke_data(&tr->pbs[k], &tr->n_pbs[k], &tr->a_pbs[k], tr->pb[k], tr->n_pb[k], tr->pbs[0], tr->n_pbs[0]); dup_stroke_data(&tr->kp[k], &tr->n_kp[k], &tr->a_kp[k], tr->kps[k], tr->n_kps[k], tr->kp[0], tr->n_kp[0]); dup_stroke_data(&tr->kps[k], &tr->n_kps[k], &tr->a_kps[k], tr->kp[k], tr->n_kp[k], tr->kps[0], tr->n_kps[0]); dup_stroke_data(&tr->cp[k], &tr->n_cp[k], &tr->a_cp[k], tr->cps[k], tr->n_cps[k], tr->cp[0], tr->n_cp[0]); dup_stroke_data(&tr->cps[k], &tr->n_cps[k], &tr->a_cps[k], tr->cp[k], tr->n_cp[k], tr->cps[0], tr->n_cps[0]); } for (k=0; kpc[k], &tr->n_pc[k], &tr->a_pc[k]); finish_stroke_data(&tr->note[k], &tr->n_note[k], &tr->a_note[k]); finish_stroke_data(&tr->notes[k], &tr->n_notes[k], &tr->a_notes[k]); finish_stroke_data(&tr->cc[k], &tr->n_cc[k], &tr->a_cc[k]); finish_stroke_data(&tr->ccs[k], &tr->n_ccs[k], &tr->a_ccs[k]); finish_stroke_data(&tr->pb[k], &tr->n_pb[k], &tr->a_pb[k]); finish_stroke_data(&tr->pbs[k], &tr->n_pbs[k], &tr->a_pbs[k]); finish_stroke_data(&tr->kp[k], &tr->n_kp[k], &tr->a_kp[k]); finish_stroke_data(&tr->kps[k], &tr->n_kps[k], &tr->a_kps[k]); finish_stroke_data(&tr->cp[k], &tr->n_cp[k], &tr->a_cp[k]); finish_stroke_data(&tr->cps[k], &tr->n_cps[k], &tr->a_cps[k]); } } } void free_translation_section(translation *tr) { int k; if (tr != NULL) { free(tr->name); if (!tr->is_default) { regfree(&tr->regex); } for (k=0; kpc[k], tr->n_pc[k]); free_stroke_data(tr->note[k], tr->n_note[k]); free_stroke_data(tr->cc[k], tr->n_cc[k]); free_stroke_data(tr->ccs[k], tr->n_ccs[k]); free_stroke_data(tr->pb[k], tr->n_pb[k]); free_stroke_data(tr->pbs[k], tr->n_pbs[k]); } free(tr); } } void free_all_translations(void) { translation *tr = first_translation_section; translation *next; while (tr != NULL) { next = tr->next; free_translation_section(tr); tr = next; } first_translation_section = NULL; last_translation_section = NULL; default_translation = default_midi_translation[0] = default_midi_translation[1] = NULL; } char *config_file_name = NULL; static time_t config_file_modification_time; static char *token_src = NULL; // similar to strtok, but it tells us what delimiter was found at the // end of the token, handles double quoted strings specially, and // hardcodes the delimiter set. char * token(char *src, char *delim_found) { char *delims = " \t\n/\""; char *d; char *token_start; if (src == NULL) { src = token_src; } if (src == NULL) { *delim_found = '\0'; return NULL; } token_start = src; while (*src) { d = delims; while (*d && *src != *d) { d++; } if (*d) { if (src == token_start) { src++; token_start = src; if (*d == '"') { while (*src && *src != '"' && *src != '\n') { src++; } } else { continue; } } *delim_found = *d; if (*src) { *src = '\0'; token_src = src+1; } else { token_src = NULL; } return token_start; } src++; } token_src = NULL; *delim_found = '\0'; if (src == token_start) { return NULL; } return token_start; } typedef struct _keysymmapping { char *str; KeySym sym; } keysymmapping; static keysymmapping key_sym_mapping[] = { #include "keys.h" { "XK_Button_1", XK_Button_1 }, { "XK_Button_2", XK_Button_2 }, { "XK_Button_3", XK_Button_3 }, { "XK_Scroll_Up", XK_Scroll_Up }, { "XK_Scroll_Down", XK_Scroll_Down }, { NULL, 0 } }; KeySym string_to_KeySym(char *str) { size_t len = strlen(str) + 1; int i = 0; while (key_sym_mapping[i].str != NULL) { if (!strncmp(str, key_sym_mapping[i].str, len)) { return key_sym_mapping[i].sym; } i++; } return 0; } char * KeySym_to_string(KeySym ks) { int i = 0; while (key_sym_mapping[i].sym != 0) { if (key_sym_mapping[i].sym == ks) { return key_sym_mapping[i].str; } i++; } return NULL; } static char *note_name(int n) { static char *note_names[] = { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" }; if (n < 0 && n%12) return note_names[12+n%12]; else return note_names[n%12]; } static int note_octave(int n) { if (n < 0 && n%12) return n/12-1 + midi_octave; else return n/12 + midi_octave; } static int datavals(int val, int step, int *steps, int n_steps) { if (val < 0) return -datavals(-val, step, steps, n_steps); else if (val < n_steps) return steps[val]; else if (n_steps) return steps[n_steps-1]; else if (step) return step*val; else return val; } void print_stroke(stroke *s, int mod, int step, int n_steps, int *steps, int val) { char *str; if (s != NULL) { if (s->keysym) { str = KeySym_to_string(s->keysym); if (str == NULL) { printf("0x%x", (int)s->keysym); str = "???"; } printf("%s/%c ", str, s->press ? 'D' : 'U'); } else if (s->shift) { printf("SHIFT%d ", s->shift); } else if (!s->status) { printf("NOP "); } else { int status = s->status & 0xf0; int channel = (s->status & 0x0f) + 1; char suffix[3] = ""; if (s->incr) strcpy(suffix, "~"); if (s->swap) strcat(suffix, "'"); if (s->change) strcat(suffix, "?"); if (s->recursive) printf("$"); if (s->feedback) printf(s->feedback==2?"^":"!"); switch (status) { case 0x90: if (mod) { int q = s->swap?val%mod:val/mod, r = s->swap?val/mod:val%mod; int d = s->data + datavals(q, step, steps, n_steps); int v = datavals(r, s->step, s->steps, s->n_steps); printf("%s%d[%d]-%d%s ", note_name(d), note_octave(d), v, channel, suffix); } else if (s->steps) { printf("%s%d{", note_name(s->data), note_octave(s->data)); for (int i = 0; i < s->n_steps; i++) printf("%s%d", i?",":"", s->steps[i]); printf("}-%d%s ", channel, suffix); } else if (s->step) printf("%s%d[%d]-%d%s ", note_name(s->data), note_octave(s->data), s->step, channel, suffix); else printf("%s%d-%d%s ", note_name(s->data), note_octave(s->data), channel, suffix); break; case 0xa0: if (mod) { int q = s->swap?val%mod:val/mod, r = s->swap?val/mod:val%mod; int d = s->data + datavals(q, step, steps, n_steps); int v = datavals(r, s->step, s->steps, s->n_steps); printf("KP:%s%d[%d]-%d%s ", note_name(d), note_octave(d), v, channel, suffix); } else if (s->steps) { printf("KP:%s%d{", note_name(s->data), note_octave(s->data)); for (int i = 0; i < s->n_steps; i++) printf("%s%d", i?",":"", s->steps[i]); printf("}-%d%s ", channel, suffix); } else if (s->step) printf("KP:%s%d[%d]-%d%s ", note_name(s->data), note_octave(s->data), s->step, channel, suffix); else printf("KP:%s%d-%d%s ", note_name(s->data), note_octave(s->data), channel, suffix); break; case 0xb0: { // check for pseudo CC messages denoting a macro int data = s->data; char *tok = data>=128?"M":"CC"; data %= 128; if (mod) { int q = s->swap?val%mod:val/mod, r = s->swap?val/mod:val%mod; int d = data + datavals(q, step, steps, n_steps); int v = datavals(r, s->step, s->steps, s->n_steps); printf("%s%d[%d]-%d%s ", tok, d, v, channel, suffix); } else if (s->steps) { printf("%s%d{", tok, data); for (int i = 0; i < s->n_steps; i++) printf("%s%d", i?",":"", s->steps[i]); printf("}-%d%s ", channel, suffix); } else if (s->step) printf("%s%d[%d]-%d%s ", tok, data, s->step, channel, suffix); else printf("%s%d-%d%s ", tok, data, channel, suffix); break; } case 0xc0: if (mod) { int v = datavals(s->swap?val%mod:val/mod, s->step, s->steps, s->n_steps); printf("PC%d-%d%s ", v, channel, suffix); } else printf("PC%d-%d%s ", s->data, channel, suffix); break; case 0xd0: if (mod) { int v = datavals(s->swap?val/mod:val%mod, s->step, s->steps, s->n_steps); printf("CP[%d]-%d%s ", v, channel, suffix); } else if (s->steps) { printf("CP{"); for (int i = 0; i < s->n_steps; i++) printf("%s%d", i?",":"", s->steps[i]); printf("}-%d%s ", channel, suffix); } else if (s->step) printf("CP[%d]-%d%s ", s->step, channel, suffix); else printf("CP-%d%s ", channel, suffix); break; case 0xe0: if (mod) { int v = datavals(s->swap?val/mod:val%mod, s->step, s->steps, s->n_steps); printf("PB[%d]-%d%s ", v-8192, channel, suffix); } else if (s->steps) { printf("PB{"); for (int i = 0; i < s->n_steps; i++) printf("%s%d", i?",":"", s->steps[i]); printf("}-%d%s ", channel, suffix); } else if (s->step) printf("PB[%d]-%d%s ", s->step, channel, suffix); else printf("PB-%d%s ", channel, suffix); break; default: // this can't happen break; } } } } void print_stroke_sequence(char *name, char *up_or_down, stroke *s, int mod, int step, int n_steps, int *steps, int val) { if (up_or_down && *up_or_down) printf("%s[%s]: ", name, up_or_down); else printf("%s: ", name); while (s) { print_stroke(s, mod, step, n_steps, steps, val); s = s->next; } printf("\n"); } stroke **first_stroke; stroke *last_stroke; stroke **press_first_stroke; stroke **release_first_stroke; int is_keystroke, is_bidirectional, is_nop, midi_release, explicit_release; int mode; char *current_translation; char *key_name; int first_release_stroke; // is this the first stroke of a release? KeySym regular_key_down; #define NUM_MODIFIERS 64 stroke modifiers_down[NUM_MODIFIERS]; int modifier_count; int midi_channel; void append_stroke(KeySym sym, int press) { stroke *s = (stroke *)allocate(sizeof(stroke)); memset(s, 0, sizeof(stroke)); s->keysym = sym; s->press = press; if (*first_stroke) { last_stroke->next = s; } else { *first_stroke = s; } last_stroke = s; } void append_shift(int shift) { stroke *s = (stroke *)allocate(sizeof(stroke)); memset(s, 0, sizeof(stroke)); s->shift = shift; if (*first_stroke) { last_stroke->next = s; } else { *first_stroke = s; } last_stroke = s; } void append_nop(void) { stroke *s = (stroke *)allocate(sizeof(stroke)); memset(s, 0, sizeof(stroke)); if (*first_stroke) { last_stroke->next = s; } else { *first_stroke = s; } last_stroke = s; is_nop = is_keystroke; } void append_midi(int status, int data, int step, int n_steps, int *steps, int swap, int change, int incr, int recursive, int feedback) { stroke *s = (stroke *)allocate(sizeof(stroke)); memset(s, 0, sizeof(stroke)); s->status = status; s->data = data; s->swap = swap; s->change = change; s->step = step; s->n_steps = n_steps; s->steps = stepsdup(n_steps, steps); s->incr = incr; s->recursive = recursive; s->feedback = feedback; // if this is a keystroke event, for all messages but program change (which // has no "on" and "off" states), mark the event as "dirty" so that the // corresponding "off" event gets added later to the "release" strokes s->dirty = is_keystroke && ((status&0xf0) != 0xc0); if (*first_stroke) { last_stroke->next = s; } else { *first_stroke = s; } last_stroke = s; midi_release = 1; } // s->press values in modifiers_down: // PRESS -> down // HOLD -> held // PRESS_RELEASE -> released, but to be re-pressed if necessary // RELEASE -> up void mark_as_down(KeySym sym, int hold) { int i; for (i=0; i NUM_MODIFIERS) { fprintf(stderr, "too many modifiers down in [%s]%s\n", current_translation, key_name); return; } modifiers_down[modifier_count].keysym = sym; modifiers_down[modifier_count].press = hold ? HOLD : PRESS; modifier_count++; } void mark_as_up(KeySym sym) { int i; for (i=0; i" | "~" | "'" Case is insignificant. Numbers are always in decimal. The meaning of the first number depends on the context (octave number for notes and key pressure, the actual data byte for other messages). This can optionally be followed by a number in brackets, denoting a step size, or (in some translations) a list of values in curly braces. Also optionally, the suffix with the third number (after the dash) denotes the MIDI channel; otherwise the default MIDI channel is used. Note that not all combinations are possible -- "pb" and "cp" have no data byte, "pc" no step size and flag; and "ch" must *not* occur on the lhs at all, and is followed by just a channel number. (In fact, "ch" is no real MIDI message at all; it just sets the default MIDI channel for subsequent messages in the output sequence.) The optional flag at the end of the token indicates an "incremental" controller or pitch bend value which responds to up ("+") and down ("-") changes; it is only permitted (with one exception, see below) on the lhs of a translation. In addition, "<" and ">" can be used in lieu of "-" and "-" to indicate a relative controller in "sign bit" representation, where controller values > 64 denote down, and values < 64 up changes. This notation is only permitted with "cc". It is used for endless rotary encoders, jog wheels and the like, as can be found, e.g., on Mackie-like units. The flags "=" and "~" are used in lieu of "+"/"-" or "<"/">", respectively, to denote a "bidirectional" translation which applies to both positive and negative changes of the parameter value. Since bidirectional translations cannot have distinct keystroke sequences for up and down changes associated with them, this makes most sense with pure MIDI translations. Among these, only the "~" flag is also permitted on the rhs of a translation, and only with "cc", where it is used to denote a relative (sign bit) controller change on output. Finally, the special "transposition" flag "'" is used in so-called "mod" translations where it swaps offset and data value; please check the documentation for details. */ static int note_number(char c, char b, int k) { c = tolower(c); b = tolower(b); if (c < 'a' || c > 'g' || (b && b != '#' && b != 'b')) return -1; // either wrong note name or invalid accidental else { static int note_numbers[] = { -3, -1, 0, 2, 4, 5, 7 }; int m = note_numbers[c-'a'], a = (b=='#')?1:(b=='b')?-1:0; if (m<0) k++; return m + a + 12*k; } } #define MAXSTEPS 16384 static char *parse_steps(char *tok, char *p, int *step, int *n_steps, int **steps) { int l, n; char c = *p++, d = c=='[' ? ']' : '}'; if (sscanf(p, "%d%n", &l, &n) == 1) { p += n; if (c == '{') { int n_st = 1; static int st[MAXSTEPS]; st[0] = l; while (*p == ',' || *p == ':' || *p == '-') { char c = *p++; if (sscanf(p, "%d%n", &l, &n) == 1) { p += n; } else return 0; if (c == ':') { // ':l' repeats the last value l-1 times if (l <= 0) { // remove the last value if (n_st > 0) n_st--; } else if (n_st > 0 && n_st < MAXSTEPS) { int last = st[n_st-1]; for (int i = 1; i < l; i++) { st[n_st++] = last; if (n_st == MAXSTEPS) { fprintf(stderr, "warning: too many steps: %s\n", tok); break; } } } } else if (c == '-') { // '-l' denotes an enumeration starting at the last value if (n_st <= 0) return 0; int last = st[n_st-1]; if (l >= last) { for (int i = last+1; i <= l; i++) { st[n_st++] = i; if (n_st == MAXSTEPS) { fprintf(stderr, "warning: too many steps: %s\n", tok); break; } } } else { for (int i = last-1; i >= l; i--) { st[n_st++] = i; if (n_st == MAXSTEPS) { fprintf(stderr, "warning: too many steps: %s\n", tok); break; } } } if (st[n_st-1] != l) break; } else if (n_st < MAXSTEPS) { st[n_st++] = l; if (n_st == MAXSTEPS) fprintf(stderr, "warning: too many steps: %s\n", tok); } } *n_steps = n_st; *steps = st; *step = 0; } else { *n_steps = 0; *steps = 0; *step = l; } if (*p == d) return ++p; else return 0; } else { return 0; } } int parse_midi(char *tok, char *s, int lhs, int mode, int recursive, int *status, int *data, int *step, int *n_steps, int **steps, int *incr, int *dir, int *mod, int *swap, int *change) { char *p = tok, *t; int n, m = -1, k = midi_channel; s[0] = 0; while (*p && !isdigit(*p) && !strchr("+-=<>~'[{:", *p)) p++; if (p == tok || p-tok > 10) return 0; // no valid token // the token by itself strncpy(s, tok, p-tok); s[p-tok] = 0; // normalize to lowercase for (t = s; *t; t++) *t = tolower(*t); // octave number or data byte if (strcmp(s, "pb") && strcmp(s, "cp")) { if ((*p == '-' || isdigit(*p))) { if (sscanf(p, "%d%n", &m, &n) == 1) p += n; else return 0; } else if (!strcmp(s, "kp")) { // key pressure, must be followed by colon and note name if (*p == ':' && p[1]) { char c = *++p, b = *++p; if (*p == '#' || tolower(*p) == 'b') p++; else b = 0; int k = note_number(c, b, 0); if (k < 0) return 0; if ((*p == '-' || isdigit(*p)) && sscanf(p, "%d%n", &m, &n) == 1) { // octave number m = k + 12 * (m - midi_octave); p += n; } else { return 0; } } else { return 0; } } else { return 0; } } // step size / modulus *mod = 0; int step2 = 0, n_steps2 = 0, *steps2 = 0; if (*p == '[' || *p == '{') { if (p[0] == '[' && p[1] == ']') { // basic mod translation with zero offset; only permitted on the lhs if (!lhs) return 0; *step = -1; // sentinel value, modulus will be filled in later p += 2; } else if ((p = parse_steps(tok, p, step, n_steps, steps))) { if (*n_steps) { // only permitted on the rhs in mod translations if (lhs || mode < 2) return 0; } else if (!*step || (lhs && *step<0)) // must be nonzero / positive on lhs return 0; if (*p == '[' || *p == '{') { // possible step size on lhs for mod translations (we just record it // here, will be resolved later) if ((p = parse_steps(tok, p, &step2, &n_steps2, &steps2))) { if (!n_steps2 && !step2) return 0; // must be nonzero } else { return 0; } } } else { return 0; } } else { // sentinel value; for the lhs, this will be filled in below; for // the rhs this indicates the default value *step = 0; *n_steps = 0; *steps = 0; } // suffix with MIDI channel (not permitted with 'ch') if (p[0] == '-' && isdigit(p[1])) { if (strcmp(s, "ch") == 0) return 0; if (sscanf(++p, "%d%n", &k, &n) == 1) { // check that it is a valid channel number if (k < 1 || k > 16) return 0; k--; // actual MIDI channel in the range 0..15 p += n; } else { return 0; } } *incr = *dir = *swap = *change = 0; if (*p == '\'') { // swap flag (only on rhs in mod translations) if (lhs || mode < 2) return 0; *swap = 1; p++; if (*p == '?') { // change flag *change = 1; p++; } } else if (*p == '?') { // change flag (only on rhs in mod translations) if (lhs || mode < 2) return 0; *change = 1; p++; if (*p == '\'') { // swap flag *swap = 1; p++; } } else if (*p && strchr("+-=<>~", *p)) { // incremental flag (messages with data only, not "ch" or "pc") if (strcmp(s, "ch") == 0) return 0; if (strcmp(s, "pc") == 0) return 0; // these are only permitted with "cc" if (strchr("<>~", *p) && strcmp(s, "cc")) return 0; if (lhs) { // *incr = 2 indicates an endless, sign-bit controller *incr = strchr("+-=", *p) ? 1 : 2; // *dir is -1 or +1 for down and up changes, but can also be zero for // *bidirectional translations ("=" and "~") *dir = (*p == '-' || *p == '<') ? -1 : (*p == '+' || *p == '>') ? 1 : 0; } else { // only the "~" form is permitted in output messages, where it indicates // an endless, sign-bit encoder if (*p != '~') return 0; else if (mode) fprintf(stderr, "warning: incremental flag ignored: %s\n", tok); else *incr = 2; } p++; } // check for trailing garbage if (*p) return 0; // check for the different messages types we support if (strcmp(s, "ch") == 0) { if (lhs) return 0; // not permitted on lhs if (*step || *n_steps) return 0; // not permitted if (*swap || *change || steps2 || n_steps2) return 0; // not permitted // we return a bogus status of 0 here, along with the MIDI channel // in the data byte; also check that the MIDI channel is in the // proper range if (m < 1 || m > 16) return 0; *status = 0; *data = m-1; return 1; } else if (strcmp(s, "pb") == 0) { // pitch bend, no data byte *status = 0xe0 | k; *data = 0; // step size on lhs indicates modulus if non-incremental if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 16384; } if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default return 1; } else if (strcmp(s, "cp") == 0) { // channel pressure, no data byte *status = 0xd0 | k; *data = 0; // step size on lhs indicates modulus if non-incremental if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 128; } if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default return 1; } else if (strcmp(s, "pc") == 0) { // program change if (*step || *n_steps) return 0; // not permitted if (steps2 || n_steps2) return 0; // not permitted if (m < 0 || m > 127) return 0; *status = 0xc0 | k; *data = m; return 1; } else if (strcmp(s, "cc") == 0) { // control change if (m < 0 || m > 127) return 0; *status = 0xb0 | k; *data = m; // step size on lhs indicates modulus if non-incremental if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 128; } if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default return 1; } else if (strcmp(s, "m") == 0) { // macro, encoded as a pseudo cc message which cannot actually occur on // input; this is only permitted in macro calls or on the lhs of a mod // translation if (m < 0 || m > 127) return 0; *status = 0xb0 | k; *data = m+128; // step size on lhs indicates modulus if non-incremental if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 128; } else if (lhs || !recursive) return 0; // not permitted if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default return 1; } else if (strcmp(s, "kp") == 0) { // key pressure if (m < 0 || m > 127) return 0; *status = 0xa0 | k; *data = m; // step size on lhs indicates modulus if non-incremental if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 128; } if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default return 1; } else { // step size on lhs indicates modulus if (lhs && *step && !*incr) { *mod = *step; *step = step2; *n_steps = n_steps2; *steps = steps2; if (*mod < 0) *mod = 128; } if (lhs && *incr && *step < 0) return 0; // not permitted if (lhs && !*step) *step = 1; // default // we must be looking at a MIDI note here, with m denoting the // octave number; first character is the note name (must be a..g); // optionally, the second character may denote an accidental (# or b) n = note_number(s[0], s[1], m - midi_octave); if (n < 0 || n > 127) return 0; *status = 0x90 | k; *data = n; return 1; } } static int chk(stroke **s) { return !s || *s; } static void dup_stroke_data(stroke_data **sd, uint16_t *n, uint16_t *a, stroke_data *sd0, uint16_t n0, stroke_data *sd1, uint16_t n1) { for (int i = 0; i < n1; i++) { if (sd1[i].anyshift) { int nindex = sd1[i].mod?1:2; // no release seq in mod translations for (int index = 0; index < nindex; index++) { stroke **t = sd0 && check_stroke_data(sd0, sd1[i].chan, sd1[i].data, n0) ? 0 : find_stroke_data(sd, sd1[i].chan, sd1[i].data, index, sd1[i].step[index], sd1[i].n_steps[index], sd1[i].steps[index], sd1[i].is_incr, sd1[i].mod, 0, n, a); // only add a default translation if we don't have one already if (!chk(t)) { stroke *s = sd1[i].s[index]; first_stroke = t; is_keystroke = 0; while (s) { if (s->keysym) { append_stroke(s->keysym, s->press); } else if (s->shift) { append_shift(s->shift); } else if (!s->status) { append_nop(); } else { append_midi(s->status, s->data, s->step, s->n_steps, s->steps, s->swap, s->change, s->incr, s->recursive, s->feedback); } s = s->next; } } } } } } int start_translation(translation *tr, char *which_key) { int status, data, step, n_steps, *steps, incr, dir, mod, swap, change, anyshift; char buf[100]; //printf("start_translation(%s)\n", which_key); if (tr == NULL) { fprintf(stderr, "missing translation section: %s\n", which_key); return 1; } current_translation = tr->name; key_name = which_key; is_keystroke = is_bidirectional = is_nop = anyshift = 0; midi_release = explicit_release = 0; first_release_stroke = 0; regular_key_down = 0; modifier_count = 0; midi_channel = 0; int k = 0, offs = 0; if (isdigit(which_key[0]) && which_key[1] == '^') { offs = 2; k = which_key[0]-'0'; if (k<0 || k>N_SHIFTS) { fprintf(stderr, "invalid shift key: [%s]%s\n", current_translation, which_key); return 1; } } else if (*which_key == '^') { offs = k = 1; } else { anyshift = 1; } if (parse_midi(which_key+offs, buf, 1, 0, 0, &status, &data, &step, &n_steps, &steps, &incr, &dir, &mod, &swap, &change)) { int chan = status & 0x0f; mode = incr?0:mod?2:1; switch (status & 0xf0) { case 0x90: if (incr) { // note (step up, down) if (step <= 0) { fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); return 1; } first_stroke = find_notes(tr, k, chan, data, dir>0, step, anyshift); if (!dir) { is_bidirectional = 1; release_first_stroke = find_notes(tr, k, chan, data, 1, step, anyshift); } } else if (mod) { // note mod first_stroke = find_note(tr, k, chan, data, 0, mod, step, n_steps, steps, anyshift); } else { // note on/off first_stroke = find_note(tr, k, chan, data, 0, 0, 0, 0, 0, anyshift); release_first_stroke = find_note(tr, k, chan, data, 1, 0, 0, 0, 0, anyshift); is_keystroke = 1; } break; case 0xc0: // pc: To make our live easier and for consistency with the other // messages, we treat this exactly like a note/cc on/off, even though // this message has no off state. Thus, when we receive a pc, it's // supposed to be treated as a "press" sequence immediately followed by // the corresponding "release" sequence. first_stroke = find_pc(tr, k, chan, data, 0, anyshift); release_first_stroke = find_pc(tr, k, chan, data, 1, anyshift); is_keystroke = 1; break; case 0xb0: if (incr) { // cc (step up, down) if (step <= 0) { fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); return 1; } first_stroke = find_ccs(tr, k, chan, data, dir>0, step, incr>1, anyshift); if (!dir) { // This is a bidirectional translation (=, ~). We first fill in the // "down" part (pointed to by first_stroke). When finishing off the // translation, we then create an exact duplicate of the sequence // for the "up" part. Note that we (ab)use the release_first_stroke // variable, which normally records the release part of a key // translation, here to remember the "up" part of the translation, // so that we can fill in that part later. is_bidirectional = 1; release_first_stroke = find_ccs(tr, k, chan, data, 1, step, incr>1, anyshift); } } else if (mod) { // cc mod first_stroke = find_cc(tr, k, chan, data, 0, mod, step, n_steps, steps, anyshift); } else { // cc on/off first_stroke = find_cc(tr, k, chan, data, 0, 0, 0, 0, 0, anyshift); release_first_stroke = find_cc(tr, k, chan, data, 1, 0, 0, 0, 0, anyshift); is_keystroke = 1; } break; case 0xa0: if (incr) { // kp (step up, down) if (step <= 0) { fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); return 1; } first_stroke = find_kps(tr, k, chan, data, dir>0, step, anyshift); if (!dir) { is_bidirectional = 1; release_first_stroke = find_kps(tr, k, chan, data, 1, step, anyshift); } } else if (mod) { // kp mod first_stroke = find_kp(tr, k, chan, data, 0, mod, step, n_steps, steps, anyshift); } else { // kp on/off first_stroke = find_kp(tr, k, chan, data, 0, 0, 0, 0, 0, anyshift); release_first_stroke = find_kp(tr, k, chan, data, 1, 0, 0, 0, 0, anyshift); is_keystroke = 1; } break; case 0xd0: if (incr) { // cp (step up, down) if (step <= 0) { fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); return 1; } first_stroke = find_cps(tr, k, chan, dir>0, step, anyshift); if (!dir) { is_bidirectional = 1; release_first_stroke = find_cps(tr, k, chan, 1, step, anyshift); } } else if (mod) { // cp mod first_stroke = find_cp(tr, k, chan, 0, mod, step, n_steps, steps, anyshift); } else { // cp on/off first_stroke = find_cp(tr, k, chan, 0, 0, 0, 0, 0, anyshift); release_first_stroke = find_cp(tr, k, chan, 1, 0, 0, 0, 0, anyshift); is_keystroke = 1; } break; case 0xe0: if (incr) { // pb (step up, down) if (step <= 0) { fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); return 1; } first_stroke = find_pbs(tr, k, chan, dir>0, step, anyshift); if (!dir) { is_bidirectional = 1; release_first_stroke = find_pbs(tr, k, chan, 1, step, anyshift); } } else if (mod) { // pb mod first_stroke = find_pb(tr, k, chan, 0, mod, step, n_steps, steps, anyshift); } else { // pb on/off first_stroke = find_pb(tr, k, chan, 0, 0, 0, 0, 0, anyshift); release_first_stroke = find_pb(tr, k, chan, 1, 0, 0, 0, 0, anyshift); is_keystroke = 1; } break; default: // this can't happen fprintf(stderr, "unexpected error: [%s]%s\n", current_translation, which_key); return 1; } } else { fprintf(stderr, "syntax error: [%s]%s\n", current_translation, which_key); return 1; } if (chk(first_stroke) || (is_bidirectional && chk(release_first_stroke))) { fprintf(stderr, "already defined: [%s]%s\n", current_translation, which_key); return 1; } press_first_stroke = first_stroke; return 0; } void add_keysym(KeySym sym, int press_release) { //printf("add_keysym(0x%x, %d)\n", (int)sym, press_release); switch (press_release) { case PRESS: append_stroke(sym, 1); mark_as_down(sym, 0); break; case RELEASE: append_stroke(sym, 0); mark_as_up(sym); break; case HOLD: append_stroke(sym, 1); mark_as_down(sym, 1); break; case PRESS_RELEASE: default: if (first_release_stroke) { re_press_temp_modifiers(); } if (regular_key_down != 0) { append_stroke(regular_key_down, 0); } append_stroke(sym, 1); regular_key_down = sym; first_release_stroke = 0; break; } } void add_release(int all_keys) { //printf("add_release(%d)\n", all_keys); release_modifiers(all_keys); if (!all_keys) { if (!*first_stroke) append_nop(); first_stroke = release_first_stroke; if (midi_release) { // walk the list of "press" strokes, find all "dirty" (as yet unhandled) // MIDI events in there and add them to the "release" strokes (unless // there's an explicit release sequence in which case we output nothing) stroke *s = *press_first_stroke; while (s) { if (!s->keysym && !s->shift && s->dirty) { if (!explicit_release) append_midi(s->status, s->data, s->step, s->n_steps, s->steps, s->swap, s->change, s->incr, s->recursive, s->feedback); s->dirty = 0; } s = s->next; } } } if (regular_key_down) append_stroke(regular_key_down, 0); if (all_keys && is_nop && !*first_stroke) append_nop(); regular_key_down = 0; first_release_stroke = 1; if (all_keys && is_bidirectional) { // create a duplicate for bidirectional translations (=, ~) stroke *s = *press_first_stroke; first_stroke = release_first_stroke; while (s) { if (s->keysym) { append_stroke(s->keysym, s->press); } else if (s->shift) { append_shift(s->shift); } else if (!s->status) { append_nop(); } else { append_midi(s->status, s->data, s->step, s->n_steps, s->steps, s->swap, s->change, s->incr, s->recursive, s->feedback); } s = s->next; } } } void add_keystroke(char *keySymName, int press_release) { KeySym sym; if (is_keystroke && !strncmp(keySymName, "RELEASE", 8)) { add_release(0); return; } sym = string_to_KeySym(keySymName); if (sym != 0) { add_keysym(sym, press_release); } else { fprintf(stderr, "unrecognized keysym: %s\n", keySymName); } } void add_string(char *str) { while (str && *str) { if (*str >= ' ' && *str <= '~') { add_keysym((KeySym)(*str), PRESS_RELEASE); } str++; } } void add_midi(char *tok) { int status, data, step, n_steps, *steps, incr, dir = 0, mod = 0, swap = 0, change = 0; int recursive = *tok == '$', fb = *tok == '!', fb2 = *tok == '^'; char buf[100]; if (fb2 && mode != 1) { fprintf(stderr, "shift feedback only allowed in key translations: %s\n", tok); return; } if (parse_midi(tok+recursive+fb+fb2, buf, 0, mode, recursive, &status, &data, &step, &n_steps, &steps, &incr, &dir, &mod, &swap, &change)) { if (status == 0) { // 'ch' token; this doesn't actually generate any output, it just sets // the default MIDI channel midi_channel = data; if (recursive) fprintf(stderr, "invalid macro call: %s\n", tok); } else { append_midi(status, data, step, n_steps, steps, swap, change, incr!=0, recursive, fb2?2:fb); } } else { // inspect the token that was actually recognized (if any) to give some // useful error message here if (strcmp(buf, "ch")) fprintf(stderr, "syntax error: %s\n", tok); else fprintf(stderr, "invalid MIDI channel: %s\n", tok); } } void finish_translation(void) { //printf("finish_translation()\n"); if (is_keystroke) { add_release(0); } add_release(1); if (debug_strokes) { if (is_keystroke) { print_stroke_sequence(key_name, "D", *press_first_stroke, 0, 0, 0, 0, 0); print_stroke_sequence(key_name, "U", *release_first_stroke, 0, 0, 0, 0, 0); } else { print_stroke_sequence(key_name, "", *first_stroke, 0, 0, 0, 0, 0); } printf("\n"); } } int read_config_file(void) { struct stat buf; char *home; char *line; char *s; char *name = NULL; char *regex; char *tok; char *which_key; char *updown; char delim; translation *tr = NULL; FILE *f; int config_file_default = 0; static int errors = 0; if (config_file_name == NULL) { config_file_name = getenv("MIDIZAP_CONFIG_FILE"); if (config_file_name == NULL) { home = getenv("HOME"); config_file_name = alloc_strcat(home, "/.midizaprc"); config_file_default = 1; } else { config_file_name = alloc_strcat(config_file_name, NULL); } config_file_modification_time = 0; } if (stat(config_file_name, &buf) < 0) { // AG: Fall back to the system-wide configuration file. if (!config_file_default && !errors) { perror(config_file_name); errors++; } config_file_name = "/etc/midizaprc"; config_file_modification_time = 0; } if (stat(config_file_name, &buf) < 0) { if (!errors) { perror(config_file_name); errors++; } return 0; } if (buf.st_mtime == 0) { buf.st_mtime = 1; } if (buf.st_mtime > config_file_modification_time) { config_file_modification_time = buf.st_mtime; if (default_debug_regex || default_debug_strokes || default_debug_keys || default_debug_midi) { printf("Loading configuration: %s\n", config_file_name); } f = fopen(config_file_name, "r"); if (f == NULL) { if (!errors) { perror(config_file_name); errors++; } return 0; } free_all_translations(); reload_callback(); debug_regex = default_debug_regex; debug_strokes = default_debug_strokes; debug_keys = default_debug_keys; debug_midi = default_debug_midi; midi_octave = 0; while ((line=read_line(f, config_file_name)) != NULL) { //printf("line: %s", line); s = line; while (*s && isspace(*s)) { s++; } if (*s == '#') { continue; } if (*s == '[') { // [name] regex\n int mode = 0; name = ++s; while (*s && *s != ']') { s++; } regex = NULL; if (*s) { *s = '\0'; s++; while (*s && isspace(*s)) { s++; } if (!strncmp(s, "TITLE", 5)) { mode = 1; s += 5; } else if (!strncmp(s, "CLASS", 5)) { mode = 2; s += 5; } while (*s && isspace(*s)) { s++; } regex = s; while (*s) { s++; } s--; while (s > regex && isspace(*s)) { s--; } s[1] = '\0'; } finish_translation_section(tr); tr = new_translation_section(name, mode, regex); continue; } tok = token(s, &delim); if (tok == NULL) { continue; } if (!strcmp(tok, "DEBUG_REGEX")) { debug_regex = 1; // -dr continue; } if (!strcmp(tok, "DEBUG_STROKES")) { debug_strokes = 1; // -ds continue; } if (!strcmp(tok, "DEBUG_KEYS")) { debug_keys = 1; // -dk continue; } if (!strcmp(tok, "DEBUG_MIDI")) { debug_midi = 1; // -dm continue; } if (!strcmp(tok, "NO_FEEDBACK")) { auto_feedback = 0; // -n continue; } if (!strcmp(tok, "JACK_NAME")) { char *a = token(NULL, &delim); if (!jack_client_name) { static char buf[100]; strncpy(buf, a, 100); buf[99] = 0; // just in case... jack_client_name = buf; // -j } continue; } if (!strcmp(tok, "JACK_PORTS")) { char *a = token(NULL, &delim); int k, n; if (!jack_num_outputs) { if (sscanf(a, "%d%n", &k, &n) == 1 && !a[n] && k>=0 && k<=2) { jack_num_outputs = k; // -o } else { fprintf(stderr, "invalid port number: %s, must be 0, 1 or 2\n", a); } } continue; } if (!strncmp(tok, "JACK_", 5)) { // JACK_IN/OUT. The port number follows (default: 1), then a regex // (taken verbatim from the rest of the line). char *s = tok+5, *regex; int is_input = strncmp(s, "IN", 2) == 0; if (is_input) s += 2; else if (strncmp(s, "OUT", 3) == 0) s += 3; else { fprintf(stderr, "invalid token: %s, must be JACK_IN or JACK_OUT\n", tok); continue; } int portno = !*s||*s=='1'?0:*s=='2'?1:-1; if (portno < 0) { fprintf(stderr, "invalid port number: %s, must be 1 or 2\n", s); continue; } if (*s && *++s) { // trailing garbage fprintf(stderr, "invalid token: %s, must be JACK_IN or JACK_OUT\n", tok); continue; } s = token_src; while (*s && isspace(*s)) { s++; } regex = s; while (*s) { s++; } s--; while (s > regex && isspace(*s)) { s--; } s[1] = '\0'; char **jack_regex = is_input?jack_in_regex:jack_out_regex; if (jack_regex[portno]) { if (strcmp(jack_regex[portno], regex)) fprintf(stderr, "error: attempt to redefine %s as '%s'\n(is already defined as '%s')\n", tok, regex, jack_regex[portno]); } else { jack_regex[portno] = strdup(regex); } continue; } if (!strcmp(tok, "PASSTHROUGH")) { // -t char *a = token(NULL, &delim); int k, n; if (a && *a && *a != '#') { if (sscanf(a, "%d%n", &k, &n) == 1 && !a[n] && k>=0 && k<=2) { if (passthrough[0] < 0) passthrough[0] = k==1; if (passthrough[1] < 0) passthrough[1] = k==2; } else { fprintf(stderr, "invalid port number: %s, must be 0, 1 or 2\n", a); } } else { if (passthrough[0] < 0) passthrough[0] = 1; if (passthrough[1] < 0) passthrough[1] = 1; } continue; } if (!strcmp(tok, "SYSTEM_PASSTHROUGH")) { // -s char *a = token(NULL, &delim); int k, n; if (a && *a && *a != '#') { if (sscanf(a, "%d%n", &k, &n) == 1 && !a[n] && k>=0 && k<=2) { if (system_passthrough[0] < 0) system_passthrough[0] = k==1; if (system_passthrough[1] < 0) system_passthrough[1] = k==2; } else { fprintf(stderr, "invalid port number: %s, must be 0, 1 or 2\n", a); } } else { if (system_passthrough[0] < 0) system_passthrough[0] = 1; if (system_passthrough[1] < 0) system_passthrough[1] = 1; } continue; } if (!strncmp(tok, "MIDI_OCTAVE", 11)) { char *a = tok+11; int k, n; if (!*a) // look for the offset in the next token a = token(NULL, &delim); if (sscanf(a, "%d%n", &k, &n) == 1 && !a[n]) { midi_octave = k; } else { fprintf(stderr, "invalid octave offset: %s\n", a); } continue; } which_key = tok; if (start_translation(tr, which_key)) { continue; } tok = token(NULL, &delim); while (tok != NULL) { if (delim != '"' && tok[0] == '#') { break; // skip rest as comment } //printf("token: [%s] delim [%d]\n", tok, delim); switch (delim) { case ' ': case '\t': case '\n': case '\0': // no newline at eof if (!strcmp(tok, "RELEASE")) { // Suppress the default MIDI release sequence if there's an // explicit release sequence. explicit_release = 1; add_keystroke(tok, PRESS_RELEASE); } else if (!strncmp(tok, "SHIFT", 5)) { int shift = isdigit(tok[5])?tok[5]-'0':1; if ((tok[5] == 0 || (isdigit(tok[5]) && tok[6] == 0)) && shift >= 1 && shift <= N_SHIFTS) append_shift(shift); else fprintf(stderr, "invalid shift key: [%s]%s\n", name, tok); } else if (!strcmp(tok, "NOP")) append_nop(); else if (strncmp(tok, "XK", 2)) add_midi(tok); else add_keystroke(tok, PRESS_RELEASE); break; case '"': add_string(tok); break; default: // should be slash updown = token(NULL, &delim); if (updown != NULL) { switch (updown[0]) { case 'U': add_keystroke(tok, RELEASE); break; case 'D': add_keystroke(tok, PRESS); break; case 'H': add_keystroke(tok, HOLD); break; default: fprintf(stderr, "invalid up/down modifier [%s]%s: %s\n", name, which_key, updown); add_keystroke(tok, PRESS); break; } } } tok = token(NULL, &delim); } finish_translation(); } finish_translation_section(tr); fclose(f); return 1; } else { return 0; } } translation * get_translation(char *win_title, char *win_class) { translation *tr; read_config_file(); tr = first_translation_section; while (tr != NULL) { if (!tr->is_default) { // AG: We first try to match the class name, since it usually provides // better identification clues. if ((tr->mode == 0 || tr->mode == 2) && win_class && *win_class && regexec(&tr->regex, win_class, 0, NULL, 0) == 0) { return tr; } if ((tr->mode == 0 || tr->mode == 1) && win_title && *win_title && regexec(&tr->regex, win_title, 0, NULL, 0) == 0) { return tr; } } tr = tr->next; } return NULL; }