aboutsummaryrefslogtreecommitdiff
path: root/users
diff options
context:
space:
mode:
authorAjax <doogle999@users.noreply.github.com>2018-10-23 15:44:48 -0400
committerDrashna Jaelre <drashna@live.com>2018-10-23 12:44:48 -0700
commitb0a021c07aa38905090058bd57b9304425594557 (patch)
tree6f4523df1269052759a94a579d9ea184873cf9fc /users
parentc8267d9fea5e8b0dbdeb8521238c922ff4a83a99 (diff)
downloadqmk_firmware-b0a021c07aa38905090058bd57b9304425594557.tar.gz
qmk_firmware-b0a021c07aa38905090058bd57b9304425594557.zip
Made a userspace that allows you to use your keyboard as an order of operations based calculator (#2864)
* Made DZ60 layout with calculator * Cleaned up and commented, preparing to fix bug with negative in front of open parenthesis as first character * Fixed bug where negative sign infront of parenthesis as first character was parsed incorrectly * Made a better solution for the bug from the previous commit * Modularized and added a userfile so that this code can be used on various keyboards, found in doogle999 * Removed commented code from keymap * Made the layer that is used for calculations a define so that it can be changed per keyboard * Made the readme * Made the readme in the correct place * Revert "Made the readme in the correct place" This reverts commit 7f8b59ed9e59c77401a48be3a7ac1e8fd8e84e32. * Manually synced with qmk upstream * Stopped repeat, made keys print character that they are defined as rather than what the keyboard wants them to do * Added support for numpad, might make all keycodes custom so that there is no need to change doogle999.c if you want to change the keycode that is associated with a function, also made numpad automatically activating an option * Fixed some bugs with backspacing, updated the readme * Fixed some bugs with numlock turning on at the wrong times when a shift key was down * Made the return to layer work automatically instead of just forcing it to layer 0 * fixes and style changes, 20% decreased binary size * Fixed some bugs with double printing and compilation errors on my side * Fixed bug with exceeding the buffer size * Removed changes that added const-ness * Made changes so that backspace does not repeat to remove backspace bugs, still some bugs with recalculating without having typed anything * Fixed obo error with calc main loop * Made includes more accurate in keymap for dz60 * Moved flags to user makefile
Diffstat (limited to 'users')
-rw-r--r--users/doogle999/doogle999.c460
-rw-r--r--users/doogle999/doogle999.h99
-rw-r--r--users/doogle999/readme.md41
-rw-r--r--users/doogle999/rules.mk14
4 files changed, 614 insertions, 0 deletions
diff --git a/users/doogle999/doogle999.c b/users/doogle999/doogle999.c
new file mode 100644
index 000000000..320de7cff
--- /dev/null
+++ b/users/doogle999/doogle999.c
@@ -0,0 +1,460 @@
1#include "doogle999.h"
2
3static unsigned char inputLocation = 0; // Current index in text input
4
5static double calc(const char input[CALC_BUFFER_SIZE +1]) // Finds value of input char array, relatively small and fast I think
6{
7 char inputToken[CALC_BUFFER_SIZE + 1]; // Input buffer, used when a single token (generally a number) takes up more
8 unsigned char inputTokenLocation = 0, inputLocation = 0; // Keep track of indices
9
10 struct Token tokens[CALC_BUFFER_SIZE + 1]; // Input, converted to tokens, one extra large to accomodate for possible negative sign then open parenthesis as first character
11 unsigned char tokenCount = 0; // Keep track of index
12
13 bool dashAsMinus = false; // Kind of a hacky solution to determining whether to treat a dash as a minus sign or a negative sign
14
15 while(inputLocation < CALC_BUFFER_SIZE + 1)
16 {
17 char digit = input[inputLocation];
18
19 if(inputLocation == 0 && input[inputLocation] == CALC_CHAR_SUB && input[inputLocation + 1] == CALC_CHAR_BEG)
20 {
21 tokens[tokenCount].raw.num = 0;
22 tokens[tokenCount].isNum = true;
23
24 tokenCount++;
25 dashAsMinus = true;
26 }
27
28 if ((digit >= '0' && digit <= '9') || /* valid digit */
29 (inputTokenLocation != 0 && input[inputLocation] == CALC_CHAR_DEC) || /* valid floating point */
30 (!dashAsMinus && inputTokenLocation == 0 && input[inputLocation] == CALC_CHAR_SUB)) /* - is negative sign */
31 {
32 inputToken[inputTokenLocation] = input[inputLocation];
33 inputTokenLocation++;
34 inputLocation++;
35 continue;
36 }
37
38 if(inputTokenLocation != 0)
39 {
40 // sscanf(inputToken, "%lf", &tokens[tokenCount].raw.num); // I would like to use sscanf here, but the small version of stdio.h on the chip doesn't allow sscanf or its sister functions to be used to process floats
41 tokens[tokenCount].raw.num = atof(inputToken);
42 tokens[tokenCount].isNum = true;
43 for(unsigned char i = 0; i < inputTokenLocation + 1; i++)
44 {
45 inputToken[i] = '\0';
46 }
47 inputTokenLocation = 0;
48 tokenCount++;
49 dashAsMinus = true;
50 continue;
51 }
52
53 /* inputTokenLocation == 0 */
54 tokens[tokenCount].isNum = false;
55 tokens[tokenCount].raw.op.c = input[inputLocation];
56 tokens[tokenCount].raw.op.priority = 0;
57 tokens[tokenCount].raw.op.ltr = true;
58 dashAsMinus = false;
59
60 switch(input[inputLocation])
61 {
62 case CALC_CHAR_BEG:
63 break;
64 case CALC_CHAR_END:
65 dashAsMinus = true;
66 break;
67 case CALC_CHAR_ADD:
68 tokens[tokenCount].raw.op.priority = CALC_PRIO_ADD;
69 break;
70 case CALC_CHAR_SUB:
71 tokens[tokenCount].raw.op.priority = CALC_PRIO_SUB;
72 break;
73 case CALC_CHAR_MUL:
74 tokens[tokenCount].raw.op.priority = CALC_PRIO_MUL;
75 break;
76 case CALC_CHAR_DIV:
77 tokens[tokenCount].raw.op.priority = CALC_PRIO_DIV;
78 break;
79 case CALC_CHAR_EXP:
80 tokens[tokenCount].raw.op.priority = CALC_PRIO_EXP;
81 tokens[tokenCount].raw.op.ltr = false;
82 break;
83 case CALC_CHAR_SIN:
84 case CALC_CHAR_COS:
85 case CALC_CHAR_TAN:
86 case CALC_CHAR_ASN:
87 case CALC_CHAR_ACS:
88 case CALC_CHAR_ATN:
89 case CALC_CHAR_LGE:
90 case CALC_CHAR_LOG:
91 case CALC_CHAR_SQT:
92 break;
93 case CALC_CHAR_EUL:
94 tokens[tokenCount].isNum = true;
95 tokens[tokenCount].raw.num = CALC_VALU_EUL;
96 dashAsMinus = true;
97 break;
98 case CALC_CHAR_PI:
99 tokens[tokenCount].isNum = true;
100 tokens[tokenCount].raw.num = CALC_VALU_PI;
101 dashAsMinus = true;
102 break;
103 case '\0':
104 tokenCount--;
105 inputLocation = CALC_BUFFER_SIZE;
106 break;
107 default:
108 tokenCount--;
109 break;
110 }
111 tokenCount++;
112 inputLocation++;
113 }
114
115 struct Token output[CALC_BUFFER_SIZE + 1]; // Final output tokens before evaluation
116 struct Token opstack[CALC_BUFFER_SIZE + 1]; // Stack of operators
117 unsigned char outputLocation = 0, opstackLocation = 0; // Keep track of indices
118
119 unsigned char numBrackets = 0; // The number of parenthesis
120
121 for(unsigned char i = 0; i < tokenCount; i++)
122 {
123 if(tokens[i].isNum)
124 {
125 output[outputLocation] = tokens[i];
126 outputLocation++;
127 }
128 else if(tokens[i].raw.op.c == CALC_CHAR_BEG)
129 {
130 opstack[opstackLocation] = tokens[i];
131 opstackLocation++;
132 }
133 else if(tokens[i].raw.op.c == CALC_CHAR_END)
134 {
135 while(opstack[opstackLocation - 1].raw.op.c != CALC_CHAR_BEG)
136 {
137 output[outputLocation] = opstack[opstackLocation - 1];
138 outputLocation++;
139 opstackLocation--;
140 }
141 opstackLocation--;
142
143 numBrackets += 2;
144 }
145 else if(tokens[i].raw.op.priority == 0)
146 {
147 opstack[opstackLocation] = tokens[i];
148 opstackLocation++;
149 }
150 else
151 {
152 while(opstackLocation != 0
153 && (opstack[opstackLocation - 1].raw.op.priority == 0
154 || tokens[i].raw.op.priority < opstack[opstackLocation - 1].raw.op.priority
155 || (tokens[i].raw.op.priority == opstack[opstackLocation - 1].raw.op.priority && opstack[opstackLocation - 1].raw.op.ltr))
156 && opstack[opstackLocation - 1].raw.op.c != CALC_CHAR_BEG)
157 {
158 output[outputLocation] = opstack[opstackLocation - 1];
159 outputLocation++;
160 opstackLocation--;
161 }
162 opstack[opstackLocation] = tokens[i];
163 opstackLocation++;
164 }
165 }
166
167 tokenCount -= numBrackets;
168
169 for(signed char i = opstackLocation - 1; i >= 0; i--)
170 {
171 output[outputLocation] = opstack[i];
172 outputLocation++;
173 opstackLocation--;
174 }
175
176 double answer[CALC_BUFFER_SIZE];
177 unsigned char answerLocation = 0;
178
179 for(unsigned char i = 0; i < tokenCount; i++)
180 {
181 if(output[i].isNum)
182 {
183 answer[answerLocation] = output[i].raw.num;
184 answerLocation++;
185 continue;
186 }
187
188 if(output[i].raw.op.priority == 0)
189 {
190 if (answerLocation < 1) { /* not handled here -- ERROR? */ } else
191 if(answerLocation >= 1)
192 {
193 double (*op)(double);
194 switch(output[i].raw.op.c)
195 {
196 case CALC_CHAR_SIN:
197 op = sin;
198 break;
199 case CALC_CHAR_COS:
200 op = cos;
201 break;
202 case CALC_CHAR_TAN:
203 op = tan;
204 break;
205 case CALC_CHAR_ASN:
206 op = asin;
207 break;
208 case CALC_CHAR_ACS:
209 op = acos;
210 break;
211 case CALC_CHAR_ATN:
212 op = atan;
213 break;
214 case CALC_CHAR_LGE:
215 op = log;
216 break;
217 case CALC_CHAR_LOG:
218 op = log10;
219 break;
220 case CALC_CHAR_SQT:
221 op = sqrt;
222 break;
223 default:
224 continue; /* invalid input */
225 }
226 answer[answerLocation - 1] = op(answer[answerLocation - 1]);
227 }
228 }
229 /* priority != 0 */
230 else if(answerLocation >= 2)
231 {
232 switch(output[i].raw.op.c)
233 {
234 case CALC_CHAR_ADD:
235 answer[answerLocation - 2] += answer[answerLocation - 1];
236 break;
237 case CALC_CHAR_SUB:
238 answer[answerLocation - 2] -= answer[answerLocation - 1];
239 break;
240 case CALC_CHAR_MUL:
241 answer[answerLocation - 2] *= answer[answerLocation - 1];
242 break;
243 case CALC_CHAR_DIV:
244 answer[answerLocation - 2] /= answer[answerLocation - 1];
245 break;
246 case CALC_CHAR_EXP:
247 answer[answerLocation - 2] = pow(answer[answerLocation - 2], answer[answerLocation - 1]);
248 break;
249 }
250
251 answerLocation--;
252 }
253 }
254
255 return answer[0];
256}
257
258/*
259 * @returns 0 when nothing should happen and QMK should work as usual
260 * @returns -1 when invalid input was given, QMK should ignore it
261 * @returns -2 when BSP should be done
262 * @returns -3 when CALC should be done
263 * @returns -4 when ENDCALC should be done
264 * @returns positive value of CALC_* when normal input was processed
265 */
266static int process_input(const uint16_t keycode, const uint8_t mods, const keyevent_t event)
267{
268 /* handle even when no key was pressed */
269 if(!event.pressed)
270 {
271 switch(keycode)
272 {
273 /* QMK should handle those */
274 case KC_RSFT:
275 case KC_LSFT:
276 return 0;
277 break;
278 }
279 /* ??? ignore */
280 return -1;
281 }
282
283 /* when shift key is pressed handle characters differently */
284 char characterPressed;
285 if((get_mods() & MODS_SHIFT_MASK))
286 {
287 switch(keycode)
288 {
289 case KC_9:
290 characterPressed = CALC_CHAR_BEG;
291 break;
292 case KC_0:
293 characterPressed = CALC_CHAR_END;
294 break;
295 case KC_EQUAL:
296 characterPressed = CALC_CHAR_ADD;
297 break;
298 case KC_KP_PLUS:
299 characterPressed = CALC_CHAR_ADD;
300 break;
301 case KC_6:
302 characterPressed = CALC_CHAR_EXP;
303 break;
304 case KC_8:
305 characterPressed = CALC_CHAR_MUL;
306 break;
307 case KC_KP_ASTERISK:
308 characterPressed = CALC_CHAR_MUL;
309 break;
310 case KC_S:
311 characterPressed = CALC_CHAR_ASN;
312 break;
313 case KC_C:
314 characterPressed = CALC_CHAR_ACS;
315 break;
316 case KC_T:
317 characterPressed = CALC_CHAR_ATN;
318 break;
319 case KC_L:
320 characterPressed = CALC_CHAR_LOG;
321 break;
322 default:
323 return -1;
324 break;
325 }
326 return characterPressed;
327 }
328
329 /* normal key handling: shift not pressed */
330
331 /* digits */
332 if (keycode == KC_KP_0 || keycode == KC_0) {
333 return '0';
334 } else if (keycode >= KC_KP_1 && keycode <= KC_KP_9) {
335 return keycode - KC_KP_1 +1 + '0';
336 } else if (keycode >= KC_1 && keycode <= KC_9) {
337 return keycode - KC_1 +1 + '0';
338 }
339
340 /* other tokens */
341 switch (keycode) {
342 case KC_MINUS:
343 case KC_KP_MINUS:
344 return characterPressed = CALC_CHAR_SUB;
345 case KC_SLASH:
346 case KC_KP_SLASH:
347 return characterPressed = CALC_CHAR_DIV;
348 case KC_S:
349 return characterPressed = CALC_CHAR_SIN;
350 case KC_C:
351 return characterPressed = CALC_CHAR_COS;
352 case KC_T:
353 return characterPressed = CALC_CHAR_TAN;
354 case KC_Q:
355 return characterPressed = CALC_CHAR_SQT;
356 case KC_L:
357 return characterPressed = CALC_CHAR_LGE;
358 case KC_DOT:
359 case KC_KP_DOT:
360 return characterPressed = CALC_CHAR_DEC;
361 case KC_P:
362 return characterPressed = CALC_CHAR_PI;
363 case KC_E:
364 return characterPressed = CALC_CHAR_EUL;
365 case KC_BSPC:
366 return -2;
367 case KC_RSFT:
368 return 0;
369 case KC_LSFT:
370 return 0;
371 case CALC:
372 return -3;
373 case ENDCALC:
374 return -4;
375 default:
376 return -1;
377 }
378}
379
380bool process_record_user(uint16_t keycode, keyrecord_t* record)
381{
382 static char text[CALC_BUFFER_SIZE + 1]; // Used to store input and then output when ready to print
383 static char backspaceText[CALC_BUFFER_SIZE + 1]; // Pretty dumb waste of memory because only backspace characters, used with send_string to backspace and remove input
384
385 if((biton32(layer_state) == CALC_LAYER && CALC_FORCE_NUM_LOCK_INSIDE_CALC) || (biton32(layer_state) != CALC_LAYER && CALC_FORCE_NUM_LOCK_OUTSIDE_CALC))
386 {
387 bool numpadKeyPressed = record->event.pressed &&
388 !(get_mods() & MODS_SHIFT_MASK) &&
389 /* KC_KP_1, KC_KP_2, ..., KC_KP_0, KC_KP_DOT */
390 (keycode >= KC_KP_1 && keycode <= KC_KP_DOT);
391
392 if(numpadKeyPressed && !(host_keyboard_leds() & (1 << USB_LED_NUM_LOCK)))
393 {
394 add_key(KC_NLCK);
395 send_keyboard_report();
396 del_key(KC_NLCK);
397 }
398 }
399
400 if(biton32(layer_state) != CALC_LAYER) { return true; }
401
402 int action = process_input(keycode, get_mods(), record->event);
403 switch(action)
404 {
405 case 0:
406 return true;
407 case -1:
408 return false;
409 case -2:
410 if(inputLocation > 0)
411 {
412 inputLocation--;
413 text[inputLocation] = '\0';
414 backspaceText[0] = (char)8;
415 backspaceText[1] = '\0';
416 send_string(backspaceText);
417 }
418 return false;
419 case -3:
420 for(int i = 0; i < inputLocation; i++)
421 {
422 backspaceText[i] = (char)8;
423 }
424 send_string(backspaceText);
425 dtostrf(calc(text), CALC_PRINT_SIZE, CALC_PRINT_SIZE, text);
426 send_string(text);
427 for(unsigned char i = 0; i < CALC_BUFFER_SIZE; i++)
428 {
429 text[i] = '\0';
430 backspaceText[i] = '\0';
431 }
432 inputLocation = 0;
433 return false;
434 case -4:
435 for(unsigned char i = 0; i < CALC_BUFFER_SIZE; i++)
436 {
437 text[i] = '\0';
438 backspaceText[i] = '\0';
439 }
440 inputLocation = 0;
441 layer_off(CALC_LAYER);
442 return false;
443 default:
444 break;
445 }
446 char characterPressed = (char)action;
447
448 if(inputLocation < CALC_BUFFER_SIZE)
449 {
450 text[inputLocation] = characterPressed;
451 inputLocation++;
452
453 char characterToSend[2];
454 characterToSend[0] = characterPressed;
455 characterToSend[1] = '\0';
456
457 send_string(characterToSend);
458 }
459 return false;
460} \ No newline at end of file
diff --git a/users/doogle999/doogle999.h b/users/doogle999/doogle999.h
new file mode 100644
index 000000000..7ed38fbcc
--- /dev/null
+++ b/users/doogle999/doogle999.h
@@ -0,0 +1,99 @@
1#ifndef USERSPACE
2#define USERSPACE
3
4#include "quantum.h"
5
6#define NO_ACTION_ONESHOT
7#define NO_ACTION_MACRO
8
9#define MODS_SHIFT_MASK (MOD_BIT(KC_LSHIFT)|MOD_BIT(KC_RSHIFT))
10
11// Layer the calculator is on
12#define CALC_LAYER 2
13
14// Inside is whether when you are in calc mode it should automatically force numlock, outside is whether it should do it outside of calculator mode
15#define CALC_FORCE_NUM_LOCK_INSIDE_CALC true
16#define CALC_FORCE_NUM_LOCK_OUTSIDE_CALC true
17
18// Maximum number of characters the calculator can have
19#define CALC_BUFFER_SIZE 32
20
21// Minimum width of the printed text / the number of decimal places
22#define CALC_PRINT_SIZE 6
23
24/*-----
25 Special
26-----*/
27#define CALC_CHAR_BEG '('
28#define CALC_CHAR_END ')'
29#define CALC_CHAR_DEC '.'
30
31/*-----
32 Operators - Can add more here such as modulo %, factorial !
33-----*/
34#define CALC_CHAR_ADD '+'
35#define CALC_PRIO_ADD 1
36
37#define CALC_CHAR_SUB '-'
38#define CALC_PRIO_SUB 1
39
40#define CALC_CHAR_MUL '*'
41#define CALC_PRIO_MUL 2
42
43#define CALC_CHAR_DIV '/'
44#define CALC_PRIO_DIV 2
45
46#define CALC_CHAR_EXP '^'
47#define CALC_PRIO_EXP 3
48
49/*-----
50 Functions
51-----*/
52#define CALC_CHAR_SIN 's'
53#define CALC_CHAR_COS 'c'
54#define CALC_CHAR_TAN 't'
55
56#define CALC_CHAR_ASN 'S'
57#define CALC_CHAR_ACS 'C'
58#define CALC_CHAR_ATN 'T'
59
60#define CALC_CHAR_LGE 'l'
61#define CALC_CHAR_LOG 'L'
62
63#define CALC_CHAR_SQT 'q'
64
65/*-----
66 Constants
67-----*/
68#define CALC_CHAR_EUL 'e'
69#define CALC_VALU_EUL 2.71828182845904523536
70
71#define CALC_CHAR_PI 'p'
72#define CALC_VALU_PI 3.14159265358979323846
73
74struct OP // Operator/function
75{
76 char c;
77 unsigned char priority;
78 bool ltr;
79};
80
81union TokenRaw // A token after the input has been processed, can either be a number or an operator/function
82{
83 double num;
84 struct OP op;
85};
86
87struct Token // Encapsulator
88{
89 bool isNum;
90 union TokenRaw raw;
91};
92
93enum CalcFunctions // Hardware calculator key functionality
94{
95 CALC = SAFE_RANGE,
96 ENDCALC
97};
98
99#endif
diff --git a/users/doogle999/readme.md b/users/doogle999/readme.md
new file mode 100644
index 000000000..e108555b9
--- /dev/null
+++ b/users/doogle999/readme.md
@@ -0,0 +1,41 @@
1Copyright 2018 <name> <email> @doogle999
2
3Overview
4========
5
6This folder is just for some calculator code for my keyboards.
7
8Making Your Keyboard Into A Calculator
9--------------------------------------
10
11If you'd like to make your keyboard into a calculator, you can do it with this userspace (hopefully!)
12
13You can make a keymap for your keyboard of choice named doogle999 and then you can make it regularly.
14
15You should make one layer that is just the functionality for your calculator, so it just has the keys you need (numbers, symbols, some letters for functions). It should also have END_CALC and CALC somewhere. END_CALC gets you out of calculator mode, and CALC evaluates the calculation.
16
17On one of your other keymaps you should make a key that is TO(layer of calculator). This is how you will activate the calculator. You should also define the layer your calculator is on with the define CALC_LAYER in doogle999.h (this means that for all your keyboards, your calculator layer has to be the same layer).
18
19You can change what characters coorespond to what operators and functions and you can add more functions in doogle999.h and doogle999.c, you can also change which characters are sued for which keys. However, as of now standard keys should be used for operations. By that I mean if you want your multiplication sign to be an x, you should change the CALC_CHAR_MUL to an x but you should keep your multiplication keycode on the keymap either as KC_8 (shifted) or KC_KP_ASTERISK. This might be changed in the future so there are custom keycodes so this is less of a pain and more intuitive.
20
21You can look at my dz60 keymap doogle999 for an example.
22
23Issues
24------
25Unfortunately the chip onboard my dz60 only does single precision floating point numbers, but I have everything set up as double so if your chip supports doubles it should work for you.
26
27This Was Here When I Made The ReadMe
28------------------------------------
29
30This program is free software: you can redistribute it and/or modify
31it under the terms of the GNU General Public License as published by
32the Free Software Foundation, either version 2 of the License, or
33(at your option) any later version.
34
35This program is distributed in the hope that it will be useful,
36but WITHOUT ANY WARRANTY; without even the implied warranty of
37MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38GNU General Public License for more details.
39
40You should have received a copy of the GNU General Public License
41along with this program. If not, see <http://www.gnu.org/licenses/>. \ No newline at end of file
diff --git a/users/doogle999/rules.mk b/users/doogle999/rules.mk
new file mode 100644
index 000000000..12698a27e
--- /dev/null
+++ b/users/doogle999/rules.mk
@@ -0,0 +1,14 @@
1SRC += doogle999.c
2
3CFLAGS += -fstrict-aliasing -ftree-vrp
4
5BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration(+1000)
6MOUSEKEY_ENABLE = no # Mouse keys(+4700)
7EXTRAKEY_ENABLE = yes # Audio control and System control(+450)
8CONSOLE_ENABLE = no # Console for debug(+400)
9COMMAND_ENABLE = no # Commands for debug and configuration
10SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
11NKRO_ENABLE = yes # USB Nkey Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work
12BACKLIGHT_ENABLE = yes # Enable keyboard backlight functionality
13AUDIO_ENABLE = no
14RGBLIGHT_ENABLE = yes