/*
Introduction:
This sketch implements a USB-connected button pad on an Arduino Leonardo, complete with an SSD1306 OLED that displays 
the real-time clock synced from the PC. Each button emulates a keyboard keystroke
(macros, MIDI triggers, custom commands), making it a versatile controller for studio, workshop, or desktop shortcuts.
To simplify wiring, no external pull-down resistors are used on the buttons—each pin is configured as INPUT_PULLUP in the code.
Time is sent over USB by a companion Python script, so no external RTC hardware is needed.

by Gabriele De Cosimo
  created 11 May 2025

  This example code is in the public domain.
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Keyboard.h>

// --- Display configuration ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- Variables for serial reception (date and time) ---
String serialBuffer      = "";
String receivedDate      = "00-00-0000";  // format dd-mm-yyyy
String receivedTime      = "00:00:00";    // format HH:MM:SS

// Variables for non-blocking display update
unsigned long lastDisplayUpdate = 0;
const unsigned long updateInterval = 100;  // update every 100 ms

// Variables for debouncing
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 70;    // debounce time in milliseconds

// --- Keyboard command functions ---

void COPYACTION() {
  Keyboard.press(KEY_LEFT_CTRL);
  delay(2);
  Keyboard.press('c');  // 'c' = copy; change to desired key
  delay(2);
  Keyboard.release('c');
  delay(1);
  Keyboard.release(KEY_LEFT_CTRL);
  delay(600);
}

void PASTEACTION() {
  Keyboard.press(KEY_LEFT_CTRL);
  delay(2);
  Keyboard.press('v');  // 'v' = paste; change to desired key
  delay(2);
  Keyboard.release('v');
  delay(1);
  Keyboard.release(KEY_LEFT_CTRL);
  delay(600);
}

void SAVEACTION() {
  Keyboard.press(KEY_LEFT_CTRL);
  delay(2);
  Keyboard.press('s');  // 's' = save; change to desired key
  delay(2);
  Keyboard.release('s');
  delay(1);
  Keyboard.release(KEY_LEFT_CTRL);
}

void UNDOACTION() {
  Keyboard.press(KEY_LEFT_CTRL);
  delay(2);
  Keyboard.press('z');  // 'z' = undo; change to desired key
  delay(2);
  Keyboard.release('z');
  delay(1);
  Keyboard.release(KEY_LEFT_CTRL);
  delay(100);
}

void MINIMIZEALLWINDOWS() {
  Keyboard.press(KEY_LEFT_GUI);
  delay(20);
  Keyboard.press('d');  // 'd' = show desktop; change to desired key
  delay(20);
  Keyboard.release('d');
  delay(20);
  Keyboard.release(KEY_LEFT_GUI);
  delay(400);
}

// --- Function to update the display ---
void updateDisplay() {
  display.clearDisplay();

  // --- Top band (date: black background, white text, centered) ---
  display.fillRect(0, 0, SCREEN_WIDTH, 16, SSD1306_BLACK);
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  int textWidth = receivedDate.length() * 6;
  int x = (SCREEN_WIDTH - textWidth) / 2;
  int y = (16 - 8) / 2;
  display.setCursor(x, y);
  display.print(receivedDate);

  // --- Lower area (simulated "blue") ---
  display.fillRect(0, 16, SCREEN_WIDTH, SCREEN_HEIGHT - 16, SSD1306_BLACK);
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(2);
  int timeTextWidth = receivedTime.length() * 6 * 2;
  int xTime = (SCREEN_WIDTH - timeTextWidth) / 2;
  display.setCursor(xTime, 30);
  display.print(receivedTime);

  display.display();
}

void setup() {
  Serial.begin(9600);
  Keyboard.begin();

  // Set button pins as INPUT_PULLUP
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);

  // Initialize the display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    for (;;);  // Halt if display initialization fails
  }
  display.clearDisplay();
  display.display();
}

void loop() {
  // --- Handle serial reception ---
  while (Serial.available() > 0) {
    char c = Serial.read();
    serialBuffer += c;
  }

  int nlIndex = serialBuffer.indexOf('\n');
  if (nlIndex != -1) {
    String dateLine = serialBuffer.substring(0, nlIndex);
    serialBuffer = serialBuffer.substring(nlIndex + 1);
    nlIndex = serialBuffer.indexOf('\n');
    String timeLine = "";
    if (nlIndex != -1) {
      timeLine = serialBuffer.substring(0, nlIndex);
      serialBuffer = serialBuffer.substring(nlIndex + 1);
    }
    if (dateLine.length() > 0 && timeLine.length() > 0) {
      receivedDate = dateLine;
      receivedTime = timeLine;
    }
  }

  // --- Non-blocking display update ---
  unsigned long currentMillis = millis();
  if (currentMillis - lastDisplayUpdate >= updateInterval) {
    updateDisplay();
    lastDisplayUpdate = currentMillis;
  }

  // --- Button handling with debouncing ---
  int buttonState4 = digitalRead(4);
  int buttonState5 = digitalRead(5);
  int buttonState6 = digitalRead(6);
  int buttonState7 = digitalRead(7);
  int buttonState8 = digitalRead(8);

  if (buttonState4 == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    COPYACTION();
    lastDebounceTime = millis();
  }
  if (buttonState5 == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    PASTEACTION();
    lastDebounceTime = millis();
  }
  if (buttonState6 == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    SAVEACTION();
    lastDebounceTime = millis();
  }
  if (buttonState7 == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    UNDOACTION();
    lastDebounceTime = millis();
  }
  if (buttonState8 == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    MINIMIZEALLWINDOWS();
    lastDebounceTime = millis();
  }
}
