|  | @@ -41,7 +41,12 @@ static EXT_RAM_ATTR struct {
 | 
	
		
			
				|  |  |  	int offset, boundary;
 | 
	
		
			
				|  |  |  	char *metadata_config;
 | 
	
		
			
				|  |  |  	bool timer, refresh;
 | 
	
		
			
				|  |  | -	uint32_t elapsed, duration;
 | 
	
		
			
				|  |  | +	uint32_t elapsed;
 | 
	
		
			
				|  |  | +	struct {
 | 
	
		
			
				|  |  | +		uint32_t value;
 | 
	
		
			
				|  |  | +		char string[8]; // H:MM:SS
 | 
	
		
			
				|  |  | +		bool visible;
 | 
	
		
			
				|  |  | +	} duration;
 | 
	
		
			
				|  |  |  	TickType_t tick;
 | 
	
		
			
				|  |  |  } displayer;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -126,7 +131,7 @@ void display_init(char *welcome) {
 | 
	
		
			
				|  |  |  		static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
 | 
	
		
			
				|  |  |  		static EXT_RAM_ATTR StackType_t xStack[DISPLAYER_STACK_SIZE] __attribute__ ((aligned (4)));
 | 
	
		
			
				|  |  |  		
 | 
	
		
			
				|  |  | -		GDS_SetLayout( display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
 | 
	
		
			
				|  |  | +		GDS_SetLayout(display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
 | 
	
		
			
				|  |  |  		GDS_SetFont(display, &Font_droid_sans_fallback_15x17 );
 | 
	
		
			
				|  |  |  		GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, welcome);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -193,23 +198,34 @@ static void displayer_task(void *args) {
 | 
	
		
			
				|  |  |  		
 | 
	
		
			
				|  |  |  		// handler elapsed track time
 | 
	
		
			
				|  |  |  		if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) {
 | 
	
		
			
				|  |  | -			char line[20], duration[12] = "";
 | 
	
		
			
				|  |  | +			char line[19] = "-", *_line = line + 1; // [-]H:MM:SS / H:MM:SS
 | 
	
		
			
				|  |  |  			TickType_t tick = xTaskGetTickCount();
 | 
	
		
			
				|  |  |  			uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS;
 | 
	
		
			
				|  |  | -			
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  			if (elapsed >= 1000) {
 | 
	
		
			
				|  |  |  				xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 | 
	
		
			
				|  |  |  				displayer.tick = tick;
 | 
	
		
			
				|  |  | -				displayer.elapsed += elapsed / 1000;
 | 
	
		
			
				|  |  | -				xSemaphoreGive(displayer.mutex);				
 | 
	
		
			
				|  |  | -				if (displayer.duration > 0) {
 | 
	
		
			
				|  |  | -					if (displayer.duration < 3600) snprintf(duration, sizeof(duration), " / %u:%02u", displayer.duration / 60, displayer.duration % 60);
 | 
	
		
			
				|  |  | -					else snprintf(duration, sizeof(duration), " / %u:%02u:%02u", (displayer.duration / 3600) % 100, (displayer.duration % 3600) / 60, displayer.duration % 60);
 | 
	
		
			
				|  |  | -				}			
 | 
	
		
			
				|  |  | -				if (displayer.elapsed < 3600) snprintf(line, sizeof(line), "%*u:%02u", sizeof(line) - 1 - strlen(duration) - 3, displayer.elapsed / 60, displayer.elapsed % 60);
 | 
	
		
			
				|  |  | -				else snprintf(line, sizeof(line), "%*u:%02u:%02u", sizeof(line) - 1 - strlen(duration) - 6, (displayer.elapsed / 3600) % 100, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
 | 
	
		
			
				|  |  | -				strcat(line, duration);
 | 
	
		
			
				|  |  | -				GDS_TextLine(display, 1, GDS_TEXT_RIGHT, (GDS_TEXT_CLEAR | GDS_TEXT_CLEAR_EOL) | GDS_TEXT_UPDATE, line);
 | 
	
		
			
				|  |  | +				elapsed = displayer.elapsed += elapsed / 1000;
 | 
	
		
			
				|  |  | +				xSemaphoreGive(displayer.mutex);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// when we have duration but no space, display remaining time
 | 
	
		
			
				|  |  | +				if (displayer.duration.value && !displayer.duration.visible) elapsed = displayer.duration.value - elapsed;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if (elapsed < 3600) sprintf(_line, "%u:%02u", elapsed / 60, elapsed % 60);
 | 
	
		
			
				|  |  | +				else sprintf(_line, "%u:%02u:%02u", (elapsed / 3600) % 100, (elapsed % 3600) / 60, elapsed % 60);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// concatenate if we have room for elapsed / duration
 | 
	
		
			
				|  |  | +				if (displayer.duration.visible) {
 | 
	
		
			
				|  |  | +					strcat(_line, "/");
 | 
	
		
			
				|  |  | +					strcat(_line, displayer.duration.string);
 | 
	
		
			
				|  |  | +				} else if (displayer.duration.value) {
 | 
	
		
			
				|  |  | +					_line--;
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// just re-write the whole line it's easier
 | 
	
		
			
				|  |  | +				GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR, displayer.header);	
 | 
	
		
			
				|  |  | +				GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  				timer_sleep = 1000;
 | 
	
		
			
				|  |  |  			} else timer_sleep = max(1000 - elapsed, 0);	
 | 
	
		
			
				|  |  |  		} else timer_sleep = DEFAULT_SLEEP;
 | 
	
	
		
			
				|  | @@ -323,9 +339,27 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
 | 
	
		
			
				|  |  |  	
 | 
	
		
			
				|  |  |  	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (elapsed >= 0) displayer.elapsed = elapsed / 1000;	
 | 
	
		
			
				|  |  | -	if (duration >= 0) displayer.duration = duration / 1000;
 | 
	
		
			
				|  |  |  	if (displayer.timer) displayer.tick = xTaskGetTickCount();
 | 
	
		
			
				|  |  | +	if (elapsed >= 0) displayer.elapsed = elapsed / 1000;	
 | 
	
		
			
				|  |  | +	if (duration > 0) {
 | 
	
		
			
				|  |  | +		displayer.duration.visible = true;
 | 
	
		
			
				|  |  | +		displayer.duration.value = duration / 1000;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		if (displayer.duration.value > 3600) sprintf(displayer.duration.string, "%u:%02u:%02u", (displayer.duration.value / 3600) % 10,
 | 
	
		
			
				|  |  | +													(displayer.duration.value % 3600) / 60, displayer.duration.value % 60);
 | 
	
		
			
				|  |  | +		else sprintf(displayer.duration.string, "%u:%02u", displayer.duration.value / 60, displayer.duration.value % 60);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		char *buf;
 | 
	
		
			
				|  |  | +		asprintf(&buf, "%s %s/%s", displayer.header, displayer.duration.string, displayer.duration.string);
 | 
	
		
			
				|  |  | +		if (GDS_GetTextWidth(display, 1, 0, buf) > GDS_GetWidth(display)) {
 | 
	
		
			
				|  |  | +			ESP_LOGW(TAG, "Can't fit duration %s (%d) on screen using elapsed only", buf, GDS_GetTextWidth(display, 1, 0, buf));
 | 
	
		
			
				|  |  | +			displayer.duration.visible = false;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		free(buf);
 | 
	
		
			
				|  |  | +	} else if (!duration) {
 | 
	
		
			
				|  |  | +		displayer.duration.visible = false;
 | 
	
		
			
				|  |  | +		displayer.duration.value = 0;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  		
 | 
	
		
			
				|  |  |  	xSemaphoreGive(displayer.mutex);
 | 
	
		
			
				|  |  |  }	
 | 
	
	
		
			
				|  | @@ -350,7 +384,8 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 | 
	
		
			
				|  |  |  		displayer.timer = false;
 | 
	
		
			
				|  |  |  		displayer.refresh = true;
 | 
	
		
			
				|  |  |  		displayer.string[0] = '\0';
 | 
	
		
			
				|  |  | -		displayer.elapsed = displayer.duration = 0;
 | 
	
		
			
				|  |  | +		displayer.elapsed = displayer.duration.value = 0;
 | 
	
		
			
				|  |  | +		displayer.duration.visible = false;
 | 
	
		
			
				|  |  |  		displayer.offset = displayer.boundary = 0;
 | 
	
		
			
				|  |  |  		display_bus(&displayer, DISPLAY_BUS_TAKE);
 | 
	
		
			
				|  |  |  		vTaskResume(displayer.task);
 |