/* nClock for TI-Nspire Copyright (C) 2014 Levak This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "charmap_8x12.h" typedef unsigned long int uint32_t; #define HOOK_PATH "/nclock.hook" #define CONTRAST_MIN 0x6B #define CONTRASTOFF_MIN 0x60 unsigned char* SCREEN_BASE_ADDRESS = 0; #define SCREEN_BYTES_SIZE (has_colors?SCREEN_WIDTH*SCREEN_HEIGHT*2:SCREEN_WIDTH*SCREEN_HEIGHT/2) static char * config_path = "/documents/config.nclock"; static char * title = "nClock says"; static int revertedScreen = 0; unsigned char screen_type = SCR_320x240_565; static const unsigned read_nand_31_addrs[] = { 0x10071F5C, 0x10071EC4, 0x10071658, 0X100715E8, 0x1006E0AC, 0x1006E03C}; #define read_nand_31 SYSCALL_CUSTOM(read_nand_31_addrs ,void, void* dest, int size, int offset, int, int percent_max, void *progress_cb ) void bc_read_nand(void* dest, int size, int offset, int unknown, int percent_max, void *progress_cb) { if (nl_ndless_rev() < 989) // Ndless 3.1 read_nand_31(dest, size, offset, unknown, percent_max, progress_cb); else read_nand(dest, size, offset, unknown, percent_max, progress_cb); } uint8_t getScreenType() { if(has_colors) { if(nl_ndless_rev() >= 2004) { // Ndless 4.2+ if(lcd_type()==SCR_240x320_565) return SCR_240x320_565; } else { // Ndless < 4.2 char f=0; bc_read_nand(&f, 1, 0x81d, 0, 0, 0); if(f==1) return SCR_240x320_565; } } return SCR_320x240_565; } inline void setPixel(int x, int y, int r, int g, int b) { if(revertedScreen) x = 319-x, y = 239-y; if(has_colors) { /* Inverted color for CX since it is ugly and sharpen */ unsigned short* p; if(screen_type==SCR_320x240_565) p = (unsigned short*)(SCREEN_BASE_ADDRESS + (x << 1) + (y << 9) + (y << 7)); else p = (unsigned short*)(SCREEN_BASE_ADDRESS + (y << 1) + (x << 8) + (x << 7) + (x << 6) + (x << 5)); *p = (((255-r) >> 3) << 11) | (((255-g) >> 2) << 5) | ((255-b) >> 3); } else { unsigned char* p = (unsigned char*)(SCREEN_BASE_ADDRESS + ((x >> 1) + (y << 7) + (y << 5))); char c = (r>>4)/3 + (g>>4)/3 + (b>>4)/3; *p = (x & 1) ? ((*p & 0xF0) | c) : ((*p & 0x0F) | (c << 4)); } } void clrscr(void) { if(lcd_isincolor()) memset(SCREEN_BASE_ADDRESS, 0x00, SCREEN_BYTES_SIZE); else memset(SCREEN_BASE_ADDRESS, 0xFF, SCREEN_BYTES_SIZE); } void putChar(int x, int y, char ch, int c, int size) { int i, j, k1, k2; for(i = 0; i < CHAR_HEIGHT; ++i) { unsigned char code = charMap_ascii[(unsigned char)ch][i]; for(j = CHAR_WIDTH; j >= 0; --j, code >>= 1) if (code & 1) for(k1 = 0; k1 < size; ++k1) for(k2 = 0; k2 < size; ++k2) setPixel(x+j*size + k1, y+i*size + k2, 0, c, c); } } void fillRect(int x1, int y1, int x2, int y2, int c) { for(; x1 < x2; ++x1) for(; y1 < y2; ++y1) setPixel(x1, y1, c, c, c); } void drawRect(int x, int y, int w, int h, int s, int c) { int i, j; for(i = x; i < x+w; ++i) for(j = y; j < y+s; ++j) setPixel(i, j, c, c, c); for(i = x; i < x+w; ++i) for(j = y+h; j > y+h-s; --j) setPixel(i, j, c, c, c); for(j = y; j < y+h; ++j) for(i = x; i < x+s; ++i) setPixel(i, j, c, c, c); for(j = y; j < y+h; ++j) for(i = x+w; i > x+w-s; --i) setPixel(i, j, c, c, c); } void drawString(char * string, int xpos, int ypos, int c, int size) { int i; for(i = 0; string[i]; ++i) putChar(xpos+(CHAR_WIDTH+2)*size*i, ypos, string[i], c, size); } void drawBase(int x, int y) { int c = 0; setPixel(x, y, c, c, c); setPixel(x+1, y, c, c, c); setPixel(x+2, y, c, c, c); setPixel(x, y+1, c, c, c); setPixel(x+2, y+1, c, c, c); setPixel(x, y+2, c, c, c); setPixel(x+2, y+2, c, c, c); setPixel(x, y+3, c, c, c); } void drawM(int x, int y) { drawBase(x, y); setPixel(x+2, y+3, 0, 0, 0); } void drawP(int x, int y) { drawBase(x, y); setPixel(x+1, y+2, 0, 0, 0); } void drawA(int x, int y) { drawP(x, y); setPixel(x+2, y+3, 0, 0, 0); } void drawSegment(int x, int y, int x1, int y1, int x2, int y2, int size, int c[3]) { int i, j; for(i = x+x1*size; i < x+(x2+1)*size; ++i) for(j = y+y1*size; j < y+(y2+1)*size; ++j) setPixel(i, j, c[0], c[1], c[2]); } char segments[] = {119, 18, 61, 59, 90, 107, 111, 50, 127, 123}; void sevenSegments(int x, int y, int n, int size, int c) { char code = segments[n]; int color[] = {0, c, c}; int grey[] = {200, 200, 200}; drawSegment(x, y, 1, 9, 3, 9, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 4, 5, 4, 8, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 0, 5, 0, 8, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 1, 4, 3, 4, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 4, 1, 4, 3, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 1, 0, 3, 0, size, (code&1)?color:grey); code >>= 1; drawSegment(x, y, 0, 1, 0, 3, size, (code&1)?color:grey); } static unsigned int* p_contrast = (unsigned int*) 0x900F0020; static unsigned int contrast; inline int isScreenOn() { return *p_contrast > CONTRASTOFF_MIN; } int fadeStep(int step, int to) { int sens = step < 0; if((sens && (int)*p_contrast + step > to) || (!sens && (int)*p_contrast + step < to)) { *p_contrast = *p_contrast + step; return step; } else { *p_contrast = to; return 0; } } /* Non-bloking fadeDown of the backlight */ int fadeDown(unsigned step) { printf("%x fadeDown\n", *p_contrast); int to = CONTRAST_MIN; return fadeStep(-step, to); } /* Non-bloking fadeUp of the backlight */ int fadeUp(unsigned step) { printf("%x fadeUp\n", *p_contrast); return fadeStep(step, contrast); } void fadeFunc(int (*fade)(unsigned), int ms) { int div = 4; int minimum = CONTRAST_MIN; int diff = (contrast - minimum); printf("contrast: %x\nmin: %x\ndiff: %d\n", contrast, minimum, diff); if(diff) { int inc = (diff >> div) ? (diff >> div) : (diff > 0) ? 1 : -1; int waittime = ms / (1 << div); printf("ms: %d\nwait : %d\n", ms, waittime); while(fade(inc)) sleep(waittime); } } /* Blocking fadeOut of the backlight */ inline void fadeOut(int ms) { fadeFunc(fadeDown, ms); } /* Blocking fadeIn of the backlight */ inline void fadeIn(int ms) { fadeFunc(fadeUp, ms); } inline void resetCalc(void) { *((int*)0x900A0008) = 2; /* reboot the calc */ } inline int fwrite_long(unsigned long n, FILE * f) { return fprintf(f, "%ld ", n); } inline int fwrite_long_ptr(unsigned long * p, FILE * f) { fwrite_long((unsigned long)p, f); return fwrite_long(*p, f); } unsigned long fread_long(FILE * f) { if(f) { char c[1]; unsigned long n = 0; while(fread(c, 1, sizeof(char), f) && *c != ' ' && *c != '\n' && *c != '\t') { n = n*10 + (*c - '0'); } return n; } else return 0; } void fread_long_ptr(unsigned long ** pp, FILE * f) { unsigned long * p = (unsigned long *) fread_long(f); unsigned long v = fread_long(f); if(!*pp) *pp = p; else **pp = v; //printf("%x %x %d\n", pp, *pp, **pp); } unsigned long * const p_RTC = (unsigned long*)0x90090000; /* Shared data between hook and nclock */ static unsigned long * display12hrs = 0; static unsigned long * displaySecs = 0; static unsigned long * countdown = 0; static unsigned long * mini_clock_posx = 0; static unsigned long * mini_clock_posy = 0; static unsigned long * resetCalcOnCountdown = 0; static unsigned long * checkTimeOnStartup = 0; static unsigned long * noMiniClock = 0; static unsigned long timeout = 180; int writeConfig(char * config_path) { FILE * config = fopen(config_path, "w"); if(!config) return 0; fwrite_long(*p_RTC, config); fwrite_long_ptr(display12hrs, config); fwrite_long_ptr(displaySecs, config); fwrite_long_ptr(countdown, config); fwrite_long_ptr(mini_clock_posx, config); fwrite_long_ptr(mini_clock_posy, config); fwrite_long_ptr(resetCalcOnCountdown, config); fwrite_long_ptr(checkTimeOnStartup, config); fwrite_long_ptr(noMiniClock, config); fwrite_long(timeout, config); //printf("wrote %d %x(%d) %x(%d) %x(%d) %x(%d)\n", *p_RTC, display12hrs, *display12hrs, displaySecs, *displaySecs, countdown, *countdown, resetCalcOnCountdown, *resetCalcOnCountdown); fclose(config); return 1; } int readConfig(unsigned long * ts, char * config_path) { FILE * config = fopen(config_path, "r"); if(!config) return 0; unsigned long t = fread_long(config); if(ts) *ts = t; fread_long_ptr(&display12hrs, config); fread_long_ptr(&displaySecs, config); fread_long_ptr(&countdown, config); if(*countdown < *p_RTC) *countdown = 0; fread_long_ptr(&mini_clock_posx, config); fread_long_ptr(&mini_clock_posy, config); *mini_clock_posx%=320; *mini_clock_posy%=240; fread_long_ptr(&resetCalcOnCountdown, config); fread_long_ptr(&checkTimeOnStartup, config); fread_long_ptr(&noMiniClock, config); t = fread_long(config); if(t) timeout = t; //printf("read %d %x(%d) %x(%d) %x(%d) %x(%d)\n", *p_RTC, display12hrs, *display12hrs, displaySecs, *displaySecs, countdown, *countdown, resetCalcOnCountdown, *resetCalcOnCountdown); fclose(config); return 1; } void malloc_shared_data(void) { display12hrs = malloc(sizeof(unsigned long)); *display12hrs = 0; displaySecs = malloc(sizeof(unsigned long)); *displaySecs = 0; countdown = malloc(sizeof(unsigned long)); *countdown = 0; mini_clock_posx = malloc(sizeof(unsigned long)); *mini_clock_posx = 210; mini_clock_posy = malloc(sizeof(unsigned long)); *mini_clock_posy = 0; resetCalcOnCountdown = malloc(sizeof(unsigned long)); *resetCalcOnCountdown = 0; checkTimeOnStartup = malloc(sizeof(unsigned long)); *checkTimeOnStartup = 0; noMiniClock = malloc(sizeof(unsigned long)); *noMiniClock = 0; } void free_shared_data(void) { free(display12hrs); free(displaySecs); free(countdown); free(mini_clock_posx); free(mini_clock_posy); free(resetCalcOnCountdown); free(checkTimeOnStartup); free(noMiniClock); } int maxDayInMonth(unsigned month, int leap) { if(month == 4 || month == 6 || month == 9 || month == 11) return 30; else if(month == 2) return (leap) ? 29 : 28; else return 31; } /* Optimized LeapYear Detection (with optimized modulos) */ int isLeapYear(unsigned year) { if(!(year&3)) { // mod 4 = 0 int r2= (year>>2)/25; // century-1 int r3= (r2>>1)/5; // millennium-1 r2= year - ((r2*25) << 2); // 2-digits year r3= year - ((r3*125) << 3); // 3-digits year if(!r3 || (!(r2&3) && r2)) return 1; else return 0; } else return 0; } static const double nb_days_in_year = 5113/14.; unsigned long time2timestamp(int hr, int min, int sec) { return sec + min*60 + hr*3600; } unsigned long date2timestamp(int year, int month, int day, int hr, int min, int sec) { int cyear; sec = time2timestamp(hr, min, sec); int ly = isLeapYear(year); for(--month; month > 0; --month) day += maxDayInMonth(month, ly); day--; day+=365*(year-1970); for(cyear=1972;cyear> 2) / 15; *sec = t - ((tt * 15) << 2); t = tt; tt = (tt >> 2) / 15; *min = t - ((tt * 15) << 2); t = tt; tt = (tt >> 3) / 3; *hr = t - ((tt * 3) << 3); return tt; } void timestamp2day(unsigned long t, int * year, int * month, int * day) { *year=1970; int leap_year=0; while(t>=365+leap_year) { (*year)++; t-=365+leap_year; leap_year=isLeapYear(*year); } *month = 1; int max_day = maxDayInMonth(*month, leap_year); while(t >= max_day) { t -= max_day; ++*month; max_day = maxDayInMonth(*month, leap_year); } *day=t+1; } void timestamp2date(unsigned long t, int * year, int * month, int * day, int * hr, int * min, int * sec) { t = timestamp2time(t, hr, min, sec); timestamp2day(t, year, month, day); } /* Fully optimized clock that only displays current time * It runs only when the calc is ON (notice that the hook remains * even if the calc is OFF) */ void mini_nclock(int clearBg) { /* if calc is ON (screen not OFF) */ int blink = 0; int blink2 = 0; int screen_on = isScreenOn(); unsigned long t = 0; if (*countdown || screen_on) { t = *p_RTC; if(*countdown > t) t = *countdown - t; } /* if we are in countdown mode */ if(*countdown) { if(*countdown > t) { blink = (t < 60) ? (t&1)*255 : 0; blink2 = (t > 60 && t < 300) ? (t&1)*255 : 0; } else { //*p_contrast = contrast; //*((int*)0x900B0008) = 0xFFFFFFFF; //*((short*)0x900B0004) = 0x2000; //fadeIn(0); *countdown = 0; char * s = "Time's over !"; int i, j; for(i = 60; i < 260; ++i) for(j = 60; j < 180; ++j) setPixel(i, j, 255, 255, 255); for(i = 0; i < 30 && s[i]; ++i) putChar(100+(CHAR_WIDTH+2)*i, 115, s[i], 0, 1); if(*resetCalcOnCountdown) resetCalc(); } } if(screen_on) { int hr, min, sec; timestamp2time(t, &hr, &min, &sec); int offset = (*display12hrs && !*countdown) ? 5: 0; int xpos = *mini_clock_posx - offset; int xend = xpos + offset + ((*displaySecs) ? 49 : 31); int ypos = *mini_clock_posy; int yend = ypos + 12; int i, j; if(clearBg&1) for(i = xpos; i < xend; ++i) for(j = ypos; j < yend; ++j) setPixel(i, j, 255, 255, 255); else if(clearBg&2) { int bx = (xpos > 2) ? xpos-2 : (xpos > 1) ? xpos-1 : xpos; int by = (ypos > 2) ? ypos-2 : (ypos > 1) ? ypos-1 : ypos; for(i = bx; i < xend+2 && i < 320; ++i) for(j = by; j < yend+2 && j < 240; ++j) setPixel(i, j, 255, 255, 255); } if(ypos-1 > 0) for(i = xpos; i < xend; ++i) setPixel(i, ypos-1, 50, 50, 50); if(yend < 240) for(i = xpos; i < xend; ++i) setPixel(i, yend, 50, 50, 50); if(xpos - 1 > 0) for(j = ypos; j < yend; ++j) setPixel(xpos - 1, j, 50, 50, 50); if(xend < 320) for(j = ypos; j < yend; ++j) setPixel(xend, j, 50, 50, 50); if(*display12hrs && !*countdown) { if(hr >= 12) { if(hr > 12) hr -= 12; drawP(xpos + 1, ypos + 1); } else drawA(xpos + 1, ypos + 1); drawM(xpos + 1, ypos + 6); } xpos += offset; ypos++; xpos++; sevenSegments(xpos, ypos, hr/10, 1, blink); sevenSegments(xpos+7, ypos, hr%10, 1, blink); putChar(xpos+8, ypos, ':', blink2, 1); sevenSegments(xpos+17, ypos, min/10, 1, blink); sevenSegments(xpos+24, ypos, min%10, 1, blink); if(*displaySecs) { putChar(xpos+26, ypos, ':', blink2, 1); sevenSegments(xpos+34, ypos, sec/10, 1, blink); sevenSegments(xpos+41, ypos, sec%10, 1, blink); } } } // CX/CM : // search : 30 10 9F E5 04 E0 2D E5 34 D0 4D E2 2C 30 8D E2 00 00 A0 E3 04 10 8D E5 28 30 8D E5 08 00 8D E5 70 FE FF EB 04 10 8D E2 // mask : FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F0 FF FF FF FF FF FF FF static const int hook_addrs[] = {0x100B66C8, 0x100B6988, // Clickpad / Touchpad 3.1 0x100EAAAC, 0x100EADC4, // CX 3.1 0x100E72CC, 0x100E75E4, // CM 3.1 0x101122b8, 0x100eb288, // Clickpad / Touchpad 3.6 0x10111cfc, 0x1011201C, // CX 3.6 0x101184C0, 0, // ClickPad / Touchpad 3.9.0 0, 0, // ClickPad / Touchpad 3.9.1 0x10117EF8, 0x10117D24, // CX 3.9.0 0x10118684, 0x101184B0, // CX 3.9.1 - low miniclock refresh rate issue, new hook location needed 0, 0, // CX 4.0.0 0x1011EF68, 0x1011EDB8, // CX 4.0.3 - low miniclock refresh rate 0x10122E04, 0x10122C5C, // CX 4.2 - non working hook anymore 0x10127CD0, 0x10127B28, // CX 4.3 - non working hook anymore 0x1012AB14, 0x1012A960, // CX 4.4 - non working hook anymore 0x1012C4BC, 0x1012C354, // CX 4.5 - non working hook anymore 0x1012C9A4, 0x1012C8C8, // CX 4.5.1 - non working hook anymore }; static uint32_t lcd_mirror_ptr[] = { 0, 0, 0, 0, 0, 0, // classic 3.1 + CX 3.1 + CM 3.1 0, 0, 0, 0, // classic 3.6 + CX 3.6 0, 0, 0, 0, // classic 3.9.0 + classic 3.9.1 0, 0, 0, 0, // CX 3.9.0 + CX 3.9.1 0, 0, // CX 4.0.0 0, 0, // CX 4.0.3 0x110ED6D4, 0x111516D4, // CX 4.2 0x110FD6DC, 0x111616DC, // CX 4.3 0x113356DC, 0x113996DC, // CX 4.4 0x113496E4, 0x113B16E4, // CX 4.5 0, 0, // CX 4.5.1 }; #define HOOK_ADDR (nl_osvalue((int*)hook_addrs, sizeof(hook_addrs)/sizeof(hook_addrs[0]))) HOOK_DEFINE(hook_nclock) { if(!*noMiniClock) mini_nclock(1); HOOK_RESTORE_RETURN(hook_nclock); } static short year_codes[] = {6, 4, 2, 0}; static short month_codes[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; static char months[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static char weekdays[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; // for example, see http://blog.artofmemory.com/how-to-calculate-the-day-of-the-week-4203.html char * getWeekDay(int year, int month, int day) { int d = (year>>2)/25; // century int r = year - (d<<2)*25; // year with 2 digits // (Century Code + Year Code + Month Code + Date Number – Leap Year Code) mod 7 return weekdays[(year_codes[d&3] // century code + r + (r>>2) // year code + month_codes[month-1] // month code + day // date number (day of month) - (((month<=2)&&isLeapYear(year))?1:0) // leap year code ) % 7]; } void display(int xpos, int ypos, int size, int blink1, int blink2, int hr, int min, int sec) { int sizeSeg = size * 7; sevenSegments(xpos, ypos, hr/10, size, blink1); sevenSegments(xpos+sizeSeg, ypos, hr%10, size, blink1); putChar(xpos+sizeSeg*2-size*2, ypos+sizeSeg/2, ':', blink2, 1); sevenSegments(xpos+sizeSeg*3-size*4, ypos, min/10, size, blink1); sevenSegments(xpos+sizeSeg*4-size*4, ypos, min%10, size, blink1); putChar(xpos+sizeSeg*5-size*6, ypos+sizeSeg/2, ':', blink2, 1); sevenSegments(xpos+sizeSeg*6-size*8, ypos, sec/10, size, blink1); sevenSegments(xpos+sizeSeg*7-size*8, ypos, sec%10, size, blink1); } int nclock(int forcedDisplay) { int dayChanged = 0; int xpos = 45; int ypos = 100; int size = 4; if(*countdown) { unsigned long t = *p_RTC; if(*countdown > t) { t = *countdown - t; int hr, min, sec; if(t > 86400 && forcedDisplay) { int day, month, year; timestamp2date(t, &year, &month, &day, &hr, &min, &sec); char s_CD[50]; year -= 1970; /* timestamp2date adds 1970 */ --month; /* 1 indexed mounths */ if(year > 0) sprintf(s_CD, "%d year(s) %d month(s) %d day(s)", year, month, day); else if(month > 0) sprintf(s_CD, "%d month(s) %d day(s)", month, day); else sprintf(s_CD, "%d day(s)", day); drawString(s_CD, (320-strlen(s_CD)*(CHAR_WIDTH+2))>>1, ypos-13, 0, 1); } timestamp2time(t, &hr, &min, &sec); if(isScreenOn()) { char * str_desc = (*resetCalcOnCountdown) ? "Reset in:" : "Countdown:"; drawString(str_desc, xpos+65, ypos-60, 0, 1); int blink = (t < 60) ? (t&1)*255 : 0; int blink2 = (t > 60 && t < 300) ? (t&1)*255 : 0; display(xpos+45, ypos-45, 3, blink, blink2, hr, min, sec); } if(t > 297 && t < 300) { fadeIn(100); show_msgbox(title, "5 minutes left !"); wait_no_key_pressed(); dayChanged = 1; } } else { fadeIn(100); *countdown = 0; show_msgbox(title, "Time's up !"); wait_no_key_pressed(); dayChanged = 1; if(*resetCalcOnCountdown) resetCalc(); } } if(isScreenOn() || forcedDisplay) { int hr, min, sec; unsigned long t = timestamp2time(*p_RTC, &hr, &min, &sec); dayChanged |= (hr == 0 || hr == 24 || ((*display12hrs) && hr == 12)) && min == 0 && sec == 0; if(*display12hrs) { if(hr >= 12) { if(hr > 12) hr -= 12; putChar(xpos - 15, ypos + 2*size, 'P', 0, 1); } else putChar(xpos - 15, ypos + 2*size, 'A', 0, 1); putChar(xpos - 15, ypos + 6*size, 'M', 0, 1); } display(xpos, ypos, 5, 0, 0, hr, min, sec); if(dayChanged || forcedDisplay) { int day, month, year; char s_RTC[30]; timestamp2day(t, &year, &month, &day); char * weekday = getWeekDay(year, month, day); sprintf(s_RTC, "%s %02d %s %04d", weekday, day, months[month-1], year); drawString(s_RTC, xpos+40, 100 + size*18, 0, 1); } } return dayChanged; } int askTime(int * hour, int * min, int * sec) { char * subtitle = "Set the time"; return (show_1numeric_input(title, subtitle, "Hour (0-23)", hour, 0, 23) && show_1numeric_input(title, subtitle, "Min (0-59)", min, 0, 59) && show_1numeric_input(title, subtitle, "Sec (0-59)", sec, 0, 59)); } int askDay(int * year, int * month, int * day) { char * subtitle = "Set the day"; return (show_1numeric_input(title, subtitle, "Year (1970-?)", year, 1970, 0x9001) && show_1numeric_input(title, subtitle, "Month (1-12)", month, 1, 12) && show_1numeric_input(title, subtitle, "Day (1-31)", day, 1, maxDayInMonth(*month, isLeapYear(*year)))); } int askDate(int * year, int * month, int * day, int * hour, int * min, int * sec) { return askDay(year, month, day) && askTime(hour, min, sec); } int askTimeout(void) { char * subtitle = "Delay before the screen fades out :"; return (show_1numeric_input(title, subtitle, "Seconds (5-?)", (int*)&timeout, 5, 0x9001)); } int askDisplay12hrs(void) { int result = show_msgbox_3b(title, "Select hour format", "12hrs", "24hrs", "Cancel"); *display12hrs = (result == 1) ? 1 : (result == 2) ? 0 : *display12hrs; return result != 3; } int askDisplaySecs(void) { int result = show_msgbox_3b(title, "Do you want to display seconds ?", "Yes", "No", "Cancel"); *displaySecs = (result == 1) ? 1 : (result == 2) ? 0 : *displaySecs; return result != 3; } int askResetCalcOnCountdown(void) { int result = show_msgbox_3b(title, "Do you want the calc to reboot when the countdown reaches 0 ?", "Yes", "No", "Cancel"); *resetCalcOnCountdown = (result == 1) ? 1 : (result == 2) ? 0 : *resetCalcOnCountdown; return result; } int askNoMiniClock(void) { int result = show_msgbox_3b(title, "You feel that the mini-clock is\ntoo mainstream ?", "Yes", "No", "Cancel"); *noMiniClock = (result == 1) ? 1 : (result == 2) ? 0 : *noMiniClock; if(result == 1) refresh_osscr(); return result; } static char * selfName; static char * selfFolder; static char * selfPath; int askStartup(void) { char * startupFolder = "/documents/ndless/startup/"; if(strcmp(selfFolder, startupFolder) == 0) { show_msgbox(title, "nClock is already a start-up program !\nIf you want to remove this option, move nClock in a \\1keyword different folder than /documents/ndless/startup/"); } else { if(show_msgbox_2b(title, "Do you want nClock to start when the calc boots ?", "Yes", "Cancel") == 1) { char newPath[strlen(startupFolder) + strlen(selfName) + 1]; strcpy(newPath, startupFolder); strcat(newPath, selfName); if(!rename(selfPath, newPath) || (!mkdir(startupFolder, 0) && !rename(selfPath, newPath))) { char * caption = "nClock was moved to :\n"; char msg[strlen(caption) + strlen(newPath) + 1]; strcpy(msg, caption); strcat(msg, newPath); show_msgbox(title, msg); refresh_osscr(); } else show_msgbox(title, "Something went wrong, sorry =("); } } return 0; } int askCheckTimeOnStartup(void) { int result = show_msgbox_3b(title, "Do you want to check if the time is correct when you launch nClock after a reboot ?", "Yes", "No", "Cancel"); *checkTimeOnStartup = (result == 1) ? 1 : (result == 2) ? 0 : *checkTimeOnStartup; return result; } int getEvt() { /*ON Left Right Down Up Enter Esc x x x x x x x x 80 40 20 10 8 4 2 1*/ int evt = 0; if(isKeyPressed(KEY_NSPIRE_ESC)) evt |= 0x1; if(isKeyPressed(KEY_NSPIRE_ENTER)) evt |= 0x2; if(isKeyPressed(KEY_NSPIRE_CLICK)) evt |= 0x2; if(isKeyPressed(KEY_NSPIRE_UP)) evt |= 0x4; if(isKeyPressed(KEY_NSPIRE_LEFTUP)) evt |= 0x4; if(isKeyPressed(KEY_NSPIRE_UPRIGHT)) evt |= 0x4; if(isKeyPressed(KEY_NSPIRE_DOWN)) evt |= 0x8; if(isKeyPressed(KEY_NSPIRE_DOWNLEFT)) evt |= 0x8; if(isKeyPressed(KEY_NSPIRE_RIGHTDOWN)) evt |= 0x8; if(isKeyPressed(KEY_NSPIRE_RIGHT)) evt |= 0x10; if(isKeyPressed(KEY_NSPIRE_LEFT)) evt |= 0x20; if(on_key_pressed()) evt |= 0x80; //if(evt) printf("%x\n", evt); return evt; } void wait_no_key_pressed_repeat(int * repeatKey) { unsigned int counter = 0xFFF; unsigned int counter2 = (*repeatKey) ? 0x7FFF : 0x1FFFF; while(any_key_pressed() && (--counter > 0 || (counter = 0xFFF && --counter2 > 0))); *repeatKey = counter2 == 0; } void ScreenBG(char * Title) { clrscr(); int xlen = strlen(Title)*(CHAR_WIDTH+2)*2; int xpos = (320 - xlen) / 2; drawString(Title, xpos, 5, 0, 2); } void Screen(char table[][24], unsigned tablelength, char * Title, int (*handleEnter)(unsigned), void (*handlePaint)(char [][24], unsigned, unsigned), int (*personalInvalidate)(int)) { int evt = 0; unsigned option = 0; int repeatKey = 0; int invalidate = 1; int idle_mode = 0; unsigned long last_timer = *p_RTC; /* reset timer */ wait_no_key_pressed(); fadeOut(100); while(!(evt&1)) { if(invalidate) { ScreenBG(Title); handlePaint(table, tablelength, option); if(personalInvalidate) invalidate = personalInvalidate(1); else invalidate = 0; fadeIn(100); } else if(personalInvalidate) invalidate |= personalInvalidate(0); int prev_evt = evt; evt = getEvt(); if(prev_evt != evt) repeatKey = 0; if(evt) { last_timer = *p_RTC; /* reset timer */ if(idle_mode) { fadeIn(100); idle_mode = 0; wait_no_key_pressed(); evt = 0; /* Cancel the event*/ } } else idle(); /* if timer has expired, bright down the screen */ if(!idle_mode && *p_RTC >= last_timer + timeout) { idle_mode = !fadeDown(1); sleep(100); } else if(idle_mode) { sleep(500); } if(evt & 0x2) { wait_no_key_pressed(); evt = handleEnter(option); invalidate = 1; wait_no_key_pressed(); last_timer = *p_RTC; /* reset timer */ fadeOut(100); } if(evt & 0x24) { if(option > 0) --option; else option = tablelength - 1; invalidate = 1; wait_no_key_pressed_repeat(&repeatKey); } if(evt & 0x18) { if(option < tablelength - 1) ++option; else option = 0; invalidate = 1; wait_no_key_pressed_repeat(&repeatKey); } } } void handlePaintV(char table[][24], unsigned tablelength, unsigned option) { int marginy = 50; int yinc = (240 - marginy) / tablelength; int ypos = marginy; unsigned i; for(i = 0; i < tablelength; ++i, ypos += yinc) { int xlen = strlen(table[i])*(CHAR_WIDTH+2); int xpos = (320 - xlen) >> 1; drawString(table[i], xpos, ypos, 0, 1); if(i == option) drawRect(xpos-5, ypos-5, xlen+10, CHAR_HEIGHT+10, 2, 0); } } void handlePaintH(char table[][24], unsigned tablelength, unsigned option) { int xinc = 320 / tablelength; int xpos = 0; int ypos = 220; unsigned i; for(i = 0; i < tablelength; ++i, xpos += xinc) { int xlen = strlen(table[i])*(CHAR_WIDTH+2); int xpos2 = xpos + (xinc - xlen) / 2; drawString(table[i], xpos2, ypos, 0, 1); if(i == option) drawRect(xpos2-5, ypos-5, xlen+10, CHAR_HEIGHT+10, 2, 0); } } int handleEnterQuickCountdowns(unsigned option) { switch(option) { case 0: *countdown = *p_RTC + 180; return 1; case 1: *countdown = *p_RTC + 300; return 1; case 2: *countdown = *p_RTC + 900; return 1; case 3: *countdown = *p_RTC + 1800; return 1; case 4: *countdown = *p_RTC + 3600; return 1; case 5: *countdown = *p_RTC + 7200; return 1; case 6: *countdown = *p_RTC + 9600; return 1; default: return 0; } } void quickCountdownsScreen(void) { char table[][24] = {"3 min (eggs)", "5 min", "15 min (rice)", "30 min", "1 hour (essay)", "2 hours (test)", "3 hours (biiig test)", "Back"}; unsigned tablelength = sizeof(table) / sizeof(table[0]); Screen(table, tablelength, "Quick Countdowns", handleEnterQuickCountdowns, handlePaintV, 0); } int handleEnterCountdown(unsigned option) { int year = 0, month = 0, day = 0, hr = 0, min = 0, sec = 0; switch(option) { case 0: if(askTime(&hr, &min, &sec)) { *countdown = *p_RTC + time2timestamp(hr, min, sec); } return 1; case 1: timestamp2date(*p_RTC, &year, &month, &day, &hr, &min, &sec); if(askDate(&year, &month, &day, &hr, &min, &sec)) { *countdown = date2timestamp(year, month, day, hr, min, sec); timestamp2date(*countdown, &year, &month, &day, &hr, &min, &sec); } return 1; case 2: quickCountdownsScreen(); return 1; case 3: *countdown = 0; show_msgbox(title, "Countdown removed."); return 0; default: return 1; } } void countdownScreen(void) { char table[][24] = {"...delay", "...from date", "Quick countdowns...", "Stop current countdown", "Back"}; unsigned tablelength = sizeof(table) / sizeof(table[0]); Screen(table, tablelength, "Countdown", handleEnterCountdown, handlePaintV, 0); } void askMiniClockPosition(void) { int evt = 0; int invalidate = 1; wait_no_key_pressed(); clrscr(); int saved_x = *mini_clock_posx; int saved_y = *mini_clock_posy; int offset = (*display12hrs && !*countdown) ? 5: 0; int xend = ((*displaySecs) ? 49 : 31); int yend = 12; while(!(evt&1)) { if(invalidate) { mini_nclock(2); invalidate = 0; } sleep(10); evt = getEvt(); if(evt & 0x2) { wait_no_key_pressed(); break; } if(evt & 0x4) { if(*mini_clock_posy > 0) (*mini_clock_posy)--; invalidate = 1; } if(evt & 0x8) { if(*mini_clock_posy < 240 - yend) (*mini_clock_posy)++; invalidate = 1; } if(evt & 0x10) { if(*mini_clock_posx < 320 - xend) (*mini_clock_posx)++; invalidate = 1; } if(evt & 0x20) { if(*mini_clock_posx > offset) (*mini_clock_posx)--; invalidate = 1; } } if(evt&1) { *mini_clock_posx = saved_x; *mini_clock_posy = saved_y; } else refresh_osscr(); } int handleEnterOptions(unsigned option) { int year, month, day, hr, min, sec; switch(option) { case 0: timestamp2date(*p_RTC, &year, &month, &day, &hr, &min, &sec); if(askTime(&hr, &min, &sec)) { *(p_RTC+2) = date2timestamp(year, month, day, hr, min, sec); } break; case 1: timestamp2date(*p_RTC, &year, &month, &day, &hr, &min, &sec); if(askDay(&year, &month, &day)) { timestamp2time(*p_RTC, &hr, &min, &sec); /* We lost some seconds on the popup */ *(p_RTC+2) = date2timestamp(year, month, day, hr, min, sec); } break; case 2: askDisplay12hrs(); break; case 3: askDisplaySecs(); break; case 4: askMiniClockPosition(); break; case 5: askNoMiniClock(); break; case 6: askTimeout(); break; case 7: askCheckTimeOnStartup(); break; default: return 1; break; } return 0; } void optionScreen(void) { char table[][24] = {"Change Time", "Change Date", "12/24 hrs", "h:m:s / h:m", "Mini clock position", "Don't use the miniclock", "Fade Out delay", "Ask time on first load", "Back"}; unsigned tablelength = sizeof(table) / sizeof(table[0]); Screen(table, tablelength, "Options", handleEnterOptions, handlePaintV, 0); } int handleEnterMoar(unsigned option) { switch(option) { case 0: revertedScreen = !revertedScreen; return 1; break; case 1: askResetCalcOnCountdown(); break; case 2: askStartup(); break; case 3: show_msgbox(title, " nClock\nby Levak & Critor - 18/2/2016\nDisplay the time everywhere !\nnClock comes with two clocks : One is a tiny little clock hooked in the Real Time Clock of the TI-Nspire, and the other one let's you see more things like the actual date. nClock is a whole and options are synchronised in real time and saved in a config file.\nEverything (especially the mini-clock) has been designed to consume the minimal power and don't be energy intensive.\n\nAlso, many many thanks to Lionel Debroux and ExtendeD who helped me a lot in Nspire C programming.\n\nMore TI-Nspire programs on http://tiplanet.org !"); break; default: break; } return 0; } void moarScreen(void) { char table[][24] = {"Reverse the screen", "Reset Calc on countdown", "Load nClock on startup", "About"}; unsigned tablelength = sizeof(table) / sizeof(table[0]); Screen(table, tablelength, "Moar!", handleEnterMoar, handlePaintV, 0); } int handleEnterMenu(unsigned option) { switch(option) { case 0: optionScreen(); break; case 1: countdownScreen(); break; case 2: moarScreen(); default: break; } return 0; } void menuScreen(void) { char table[][24] = {"Options", "Countdown", "Moar !"}; unsigned tablelength = sizeof(table) / sizeof(table[0]); Screen(table, tablelength, "nClock", handleEnterMenu, handlePaintH, nclock); } void saveHook(uint32_t hook) { FILE* h=fopen(HOOK_PATH,"wb"); if(h) { fwrite(&hook,sizeof(uint32_t),1,h); fclose(h); } } int loadHook(uint32_t* hook) { FILE* h=fopen(HOOK_PATH,"rb"); if(h) { fread(hook,sizeof(uint32_t),1,h); fclose(h); return 1; } return 0; } int main(int argc, char* argv[]) { if(argc < 1) return 0; SCREEN_BASE_ADDRESS =*(unsigned char**)0xC0000010 ; screen_type = getScreenType(); /* Save user contrast */ contrast = *p_contrast; int len = strlen(argv[0]); char path[len]; strcpy(path, argv[0]); char *p = strrchr(path, '/'); char self_name[len - ((p) ? p - path + 1: 0) + 1]; strcpy(self_name, (p) ? p + 1 : path); if(p) *(p + 1) = 0; selfName = self_name; selfFolder = path; selfPath = argv[0]; if(strcmp(self_name, "uninstall.tns") == 0) { if(!nl_isstartup()) { if (show_msgbox_2b(title, "Do you want to unsinstall nClock ?", "Yes", "No") == 2) return 0; if(remove(config_path) == 0) { show_msgbox(title, "nClock has successfuly deleted the config file."); free_shared_data(); if(show_msgbox_2b(title, "To completly remove nclock, you need to reboot.\nReboot now ?", "Yes", "No") == 1) { resetCalc(); } else return 0; } else show_msgbox(title, "nClock is not installed."); return 0; } else { char newName[] = "nclock.tns"; char newPath[strlen(selfFolder)+strlen(newName)+1]; strcpy(newPath, selfFolder); strcat(newPath, newName); if(rename(selfPath, newPath) != 0) return 0; } } uint32_t current_hook = *(volatile uint32_t*)HOOK_ADDR; uint32_t orig_hook; int r = loadHook(&orig_hook); if(!r || nl_isstartup()) { if(!r || orig_hook!=current_hook) saveHook(current_hook); orig_hook=current_hook; } /* If hook isn't already installed */ if(orig_hook==current_hook) { /* if(screen_type==SCR_240x320_565) { SCREEN_BASE_ADDRESS=*(unsigned char**)nl_osvalue((int*)lcd_mirror_ptr, sizeof(lcd_mirror_ptr)/sizeof(lcd_mirror_ptr[0])); screen_type==SCR_320x240_565; }*/ malloc_shared_data(); /* Install hook */ HOOK_INSTALL(HOOK_ADDR, hook_nclock); nl_set_resident(); } if(!nl_isstartup()) { /* Extra screen buffer */ char *sbuffer = malloc(SCREEN_BYTES_SIZE); memcpy(sbuffer, SCREEN_BASE_ADDRESS, SCREEN_BYTES_SIZE); /* Event loop */ readConfig(0, config_path); menuScreen(); fadeOut(100); memcpy(SCREEN_BASE_ADDRESS, sbuffer, SCREEN_BYTES_SIZE); fadeIn(100); free(sbuffer); //printf("%d %x(%d) %x(%d) %x(%d)\n", *p_RTC, display12hrs, *display12hrs, displaySecs, *displaySecs, countdown, *countdown); } /* Restore user defined contrast */ *p_contrast = contrast; writeConfig(config_path); return 0; }