-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmain.cpp
708 lines (610 loc) · 23.9 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
/**
* @filename : main.cpp
* @brief : 4.2inch e-paper display (B) demo
* @author : Yehui from Waveshare
*
* Copyright (C) Waveshare August 15 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documnetation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <string>
#include <math.h>
#include <ctime>
#include <unistd.h>
#include <pango/pangocairo.h>
#include "screen.h"
#include "gcal.h"
#include "secrets.h"
#include "../lib/json.hpp"
using json = nlohmann::json;
const char *GOOGLE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z";
const char *GOOGLE_DATE_FORMAT = "%Y-%m-%d";
const char *HEADPHONES_PNG = "/home/pi/upNext/code/headphones.png";
const char *PARTY_PNG = "/home/pi/upNext/code/party.png";
const char *TITLE_FONT = "Proxima Nova Regular 40";
const char *SUBTITLE_FONT = "Proxima Nova Regular 24";
const char *SMALL_TEXT_BOLD_FONT = "Proxima Nova Bold 16";
const char *SMALL_TEXT_REGULAR_FONT = "Proxima Nova Regular 16";
long int datediff(struct tm * a, struct tm * b);
void convert_event_time_to_time(json eventTime, tm* time);
int get_event_status_code(json event);
bool is_more_important_event(json eventA, json eventB);
json get_events(GoogleCalendar* gcal);
void draw_clock(cairo_t *cr);
void draw_events(cairo_t *cr, json events);
void draw_message_with_headphones(cairo_t *cr, string message);
void draw_no_more_meetings(cairo_t *cr);
void draw_time_tagline(cairo_t *cr, string c_str);
string time_remaining_tagline(tm* endTime);
string time_till_tagline(int delta_min);
void draw_while_until_next_event(cairo_t *cr, int delta_min);
void draw_secondary_event_line(cairo_t *cr, json event);
void draw_main_event(cairo_t *cr, json event);
void print_event(json event);
/* Returns time in seconds between two datetimes. Long can handle +/-60 years
* Does not use mktime, because of issues with daylight savings
* See https://stackoverflow.com/questions/12122084/confusing-behaviour-of-mktime-function-increasing-tm-hour-count-by-one
*/
long int datediff(struct tm * from, struct tm * to) {
return to->tm_sec - from->tm_sec +
60l * (to->tm_min - from->tm_min +
60l * (to->tm_hour - from->tm_hour +
24l * (to->tm_yday - from->tm_yday +
365l * (to->tm_year - from->tm_year)
)
)
);
}
void convert_event_time_to_time(json eventTime, tm* time) {
if (eventTime["dateTime"].is_null()) {
// All day events
strptime(eventTime["date"].get<string>().c_str(), GOOGLE_DATE_FORMAT, time);
} else {
strptime(eventTime["dateTime"].get<string>().c_str(), GOOGLE_TIME_FORMAT, time);
}
}
int get_event_status_code(json event) {
// 0 == "accepted"
// 1 == own event, no status
// 2 == "tentative"
// 3 == "needsAction"
// 4 == "declined"
if (event["attendees"].is_null()){
return 1;
}
string status = event["attendees"][0]["responseStatus"];
if (status == "accepted") {
return 0;
} else if (status == "tentative") {
return 2;
} else if (status == "needsAction") {
return 3;
} else if (status == "declined") {
return 4;
}
return 99;
}
json get_events(GoogleCalendar* gcal) {
char buffer [80];
time_t t = time(0); // get time now
struct tm * today = localtime( & t );
today->tm_hour = 0;
today->tm_min = 0;
today->tm_sec = 0;
mktime(today);
strftime(buffer, 80, GOOGLE_TIME_FORMAT, today);
string today_str(buffer);
// We go until tomorrow morning to pick up tomorrow's all day events
struct tm * tomorrow = localtime( & t );
tomorrow->tm_hour = 0;
tomorrow->tm_min = 0;
tomorrow->tm_sec = 1;
tomorrow->tm_mday += 1;
mktime(tomorrow);
strftime(buffer, 80, GOOGLE_TIME_FORMAT, tomorrow);
string tomorrow_str(buffer);
json events = gcal->GetEventsBetween("primary", today_str, tomorrow_str);
//cout << events.dump(4) << endl;
return events;
}
void print_event(json event) {
if (event.is_null()) {
cout << "Null" << endl;
return;
}
cout << "Event: " << event["summary"] << " | " << event["status"] << " | ";
if (event["start"]["dateTime"].is_null()) {
cout << "All day: " << event["start"]["date"] << endl;
} else {
cout << event["start"]["dateTime"] << endl;
}
}
bool is_more_important_event(json eventA, json eventB) {
// Returns true if eventA is "better" than eventB. In a tie, returns false.
//
// We prioritize accepted events of other people over own events,
// and priortize these over tentative over unanswered over declined.
// We prioritize events that started last, then events that end first.
// Therefore:
// If A is 1-5pm, B is 2-6pm, and C is 3-4pm, then:
// -At 1, we'll only show A
// -At 2, we'll show B as primary, A as secondary
// -At 3, we'll show C as primary, B as secondary
// -At 4, we'll show B as primary, A as secondary (B started later)
// -At 5, we'll only show B
// If A is 1-3pm, B is 1-2pm, and C is 2-3pm, then:
// -At 1, we'll show B as primary, A as secondary (B ends first)
// -At 2, we'll show C as primary, A as secondary (C started later)
// Finally, we prioritize non-recurring events over recurring events.
// Something is always better than nothing! We check A first, because tie returns false
if (eventA.is_null()) {
return false;
}
if (eventB.is_null()) {
return true;
}
// Checking response status - lower is better
int eventAStatus = get_event_status_code(eventA);
int eventBStatus = get_event_status_code(eventB);
if (eventAStatus < eventBStatus) {
return true;
} else if (eventAStatus > eventBStatus) {
return false;
}
struct tm eventAStart = {};
struct tm eventAEnd = {};
struct tm eventBStart = {};
struct tm eventBEnd = {};
convert_event_time_to_time(eventA["start"], &eventAStart);
convert_event_time_to_time(eventA["end"], &eventAEnd);
convert_event_time_to_time(eventB["start"], &eventBStart);
convert_event_time_to_time(eventB["end"], &eventBEnd);
// dateDiff(X,Y) is Y - X
long int deltaStart = datediff(&eventAStart, &eventBStart);
if (deltaStart < 0) {
// Y - X < 0 , so X > Y, so A started later, and is better
return true;
} else if (deltaStart > 0) {
return false;
}
long int deltaEnd = datediff(&eventAEnd, &eventBEnd);
if (deltaEnd < 0) {
// Y - X < 0 , so X > Y, so A ends later, and is worse
return false;
} else if (deltaEnd > 0) {
return true;
}
if (eventA["recurringEventId"].is_null() && !eventB["recurringEventId"].is_null()) {
return true;
} else if (!eventA["recurringEventId"].is_null() && eventB["recurringEventId"].is_null()) {
return false;
}
// They're basically the same, so in a tie return false
return false;
}
void draw_events(cairo_t *cr, json events) {
int num_events = events.size();
json best_current_event;
json second_current_event;
json best_next_event;
json second_next_event;
json today_all_day_event;
json tomorrow_all_day_event;
json first_event_of_day;
time_t currentTime = time(0);
struct tm * now = localtime( ¤tTime );
struct tm event_start_time = {};
struct tm event_end_time = {};
struct tm best_next_start_time = {};
struct tm second_next_start_time = {};
cout << "Processing events: " << endl;
json event;
for (int i = 0; i < num_events; i++) {
event = events[i];
print_event(event);
convert_event_time_to_time(event["start"], &event_start_time);
convert_event_time_to_time(event["end"], &event_end_time);
if (event["start"]["dateTime"].is_null()) {
// All day event
if (datediff(now, &event_start_time) < 0) {
today_all_day_event = event;
} else {
tomorrow_all_day_event = event;
}
continue;
}
if (first_event_of_day.is_null()) {
// First real event!
first_event_of_day = event;
}
if (datediff(now, &event_end_time) < 0) {
continue;
}
if (datediff(now, &event_start_time) < 0) {
// Pick the best two current events
if (is_more_important_event(event, best_current_event)) {
// By definition, we know best_current_event is always better than second_current_event
second_current_event = best_current_event;
best_current_event = event;
} else if (is_more_important_event(event, second_current_event)) {
second_current_event = event;
}
} else {
// For upcoming events, only 4 reasons why we'd care about this event:
// we don't have a first or a second, or this starts at the same time
// as one of those and is more important
if (best_next_event.is_null()) {
best_next_event = event;
best_next_start_time = event_start_time;
} else if (datediff(&best_next_start_time, &event_start_time) == 0 &&
is_more_important_event(event, best_next_event)) {
second_next_event = best_next_event;
second_next_start_time = best_next_start_time;
best_next_event = event;
best_next_start_time = event_start_time;
} else if (second_next_event.is_null()) {
second_next_event = event;
second_next_start_time = event_start_time;
} else if (datediff(&second_next_start_time, &event_start_time) == 0 &&
is_more_important_event(event, second_next_event)) {
second_next_event = event;
second_next_start_time = event_start_time;
}
}
}
cout << "Best curr: ";
print_event(best_current_event);
cout << "Second curr: ";
print_event(second_current_event);
cout << "Best next: ";
print_event(best_next_event);
cout << "Second next: ";
print_event(second_next_event);
int delta_min = 0;
if (!best_next_event.is_null()) {
convert_event_time_to_time(best_next_event["start"], &event_start_time);
long int delta_seconds = datediff(now, &event_start_time);
delta_min = (int) round(delta_seconds / 60.0);
cout << "Time until next event: " << delta_min << " minutes" << endl;
}
// We now have all of our event information and can decide
// what to show on the screen.
// The screen is designed with the following components:
// ___________________________________
// |[Time tagline] [clock]|
// | |
// |[Primary event details] |
// | |
// | |
// | |
// |[Secondary event tagline] |
// ⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻⎻
// [Time tagline] is things like "Now:", "In 5 minutes", or "Until 10am", and describes
// the primary event.
// [clock] is a clock, and shows things like "9:41pm"
//
// The logic for what event to show when/where is somewhat complex, and is tuned based
// on what felt right to me as a frequent user:
//
// [Primary event]
// * If we're supposed to be in a meeting right now, show that as the primary event.
// * If we're in a meeting, but there's another one in < 5 minutes, show that as the primary event
// instead of the meeting we're currently in, so we can prepare to go to that one.
// * If we're not in a meeting and there's an event in < 30 minutes, show that as the primary event.
// * If there isn't a meeting for > 30 minutes, don't show a primary event and instead say "heads down time"
// * If there's an all-day event and we're > 30 minutes before the first meeting of the day, show the all-day event
// as the primary event
// * If we're done with meetings for the day, yay! Don't show a primary event and instead say
// "Done with meetings for the day"
//
// [Time tagline]
// * If the primary event is our current meeting, show "Until <end time>:"
// * If the primary event is an upcoming event, show "In <delta> minutes:"
// * If the primary event is an all day meeting, show "All day:"
// * If we don't have a primary event (no more meetings or far away), don't show a tagline
//
// [Secondary event tagline]
// * If we're in a meeting, show the next one in the form of "Then - <time>: <summary>"
// * If we've got a next event coming up (in the "primary event" slot), show the one after that (the "following" event)
// if there is one.
// * If we've got a next event coming up but it's a ways out (not in the "primary event" slot), show _that_ event's
// details (i.e. *not* the "following" event)
// * If we're at the last event(s) of the day and so don't anything to put in the secondary event slot, see
// if we have an all day event tomorrow. If we do, show that, otherwise leave it blank
json primary_event;
json secondary_event;
bool in_meeting = !best_current_event.is_null();
bool have_next_event = !best_next_event.is_null();
cout << "In meeting: " << in_meeting << endl;
cout << "Have next: " << have_next_event << endl;
if (in_meeting) {
if (have_next_event && delta_min <= 5) {
primary_event = best_next_event;
} else {
primary_event = best_current_event;
}
} else if (have_next_event && delta_min <= 30) {
primary_event = best_next_event;
} else if (!today_all_day_event.is_null() && best_next_event == first_event_of_day) {
primary_event = today_all_day_event;
} else {
// making this explicit that we _don't_ want a primary event in this case
}
if (primary_event == best_next_event) {
secondary_event = second_next_event;
} else if (!second_current_event.is_null()) {
secondary_event = second_current_event;
} else {
secondary_event = best_next_event;
}
if (secondary_event.is_null() && !tomorrow_all_day_event.is_null()) {
secondary_event = tomorrow_all_day_event;
}
cout << "Primary event: ";
print_event(primary_event);
cout << "Secondary event: ";
print_event(secondary_event);
// Now, we draw it
if (primary_event.is_null()) {
if (have_next_event) {
draw_while_until_next_event(cr, delta_min);
} else {
draw_no_more_meetings(cr);
}
draw_secondary_event_line(cr, secondary_event);
} else {
draw_main_event(cr, primary_event);
draw_secondary_event_line(cr, secondary_event);
// Drawing tagline
string tagline;
if (primary_event == best_current_event) {
convert_event_time_to_time(primary_event["end"], &event_end_time);
tagline = time_remaining_tagline(&event_end_time);
} else if (primary_event == best_next_event) {
tagline = time_till_tagline(delta_min);
} else if (primary_event == today_all_day_event) {
tagline = "All day:";
}
draw_time_tagline(cr, tagline);
}
}
void draw_clock(cairo_t *cr) {
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
PangoLayout *layout = pango_cairo_create_layout (cr);
PangoFontDescription *font_description = pango_font_description_from_string (SMALL_TEXT_BOLD_FONT);
pango_layout_set_font_description (layout, font_description);
// Top right alignment
int width = 100;
int margin = 10;
cairo_move_to (cr, 400 - (width + margin), margin);
pango_layout_set_width (layout, width * PANGO_SCALE);
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
static char outstr[8];
time_t t = time(0); // get time now
struct tm * now = localtime( & t );
//sprintf(outstr, "%d:%02d", now->tm_hour, now->tm_min);
strftime(outstr, 8 * sizeof(char), "%-I:%M%P", now);
pango_layout_set_text (layout, outstr, -1);
pango_cairo_show_layout(cr, layout);
pango_font_description_free (font_description);
g_object_unref (layout);
}
void draw_message_with_image(cairo_t *cr, string message, const char* filepath) {
int margin = 10;
PangoLayout *layout = pango_cairo_create_layout (cr);
PangoFontDescription *font_description = pango_font_description_from_string (SUBTITLE_FONT);
pango_layout_set_font_description (layout, font_description);
// Center, slightly below center
cairo_move_to (cr, margin, 200);
pango_layout_set_width (layout, (400 - 2 * margin) * PANGO_SCALE);
// Only draw one line
pango_layout_set_height (layout, -1);
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
pango_layout_set_text (layout, message.c_str(), -1);
pango_cairo_show_layout(cr, layout);
// Draw image, restore source
cairo_surface_t *image = cairo_image_surface_create_from_png(filepath);
if (cairo_surface_status(image) == CAIRO_STATUS_SUCCESS) {
cairo_set_source_surface (cr, image, 125, 50);
cairo_paint (cr);
}
cairo_surface_destroy (image);
pango_font_description_free (font_description);
g_object_unref (layout);
}
void draw_no_more_meetings(cairo_t *cr) {
time_t t = time(0); // get time now
struct tm * today = localtime( & t );
// Weekend (or heading into it)
if (today->tm_wday == 5) {
draw_message_with_image(cr, "No more meetings - have a great weekend!", PARTY_PNG);
} else if (today->tm_wday == 6 || today->tm_wday == 0) {
draw_message_with_image(cr, "Hope you're enjoying your weekend", PARTY_PNG);
} else {
draw_message_with_image(cr, "No more meetings today", HEADPHONES_PNG);
}
}
void draw_time_tagline(cairo_t *cr, string tagline) {
if (tagline.empty()) {
return;
}
PangoLayout *layout = pango_cairo_create_layout (cr);
PangoFontDescription *font_description = pango_font_description_from_string (SMALL_TEXT_BOLD_FONT);
pango_layout_set_font_description (layout, font_description);
// Top left alignment
int margin = 10;
cairo_move_to (cr, margin, margin);
pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
pango_layout_set_text (layout, tagline.c_str(), -1);
pango_cairo_show_layout(cr, layout);
pango_font_description_free (font_description);
g_object_unref (layout);
}
string time_remaining_tagline(tm* endTime) {
static char timestr[20];
strftime(timestr, 20 * sizeof(char), "Until %-I:%M%P:", endTime);
return string(timestr);
}
string time_till_tagline(int delta_min) {
ostringstream os;
if (delta_min <= 0) {
os << "Now:";
} else if (delta_min == 1) {
os << "In 1 minute:";
} else {
os << "In " << delta_min << " minutes:";
}
return os.str();
}
void draw_while_until_next_event(cairo_t *cr, int delta_min) {
ostringstream os;
// e.g. "1.5 hours meeting free", "50 minutes meeting free"
if (delta_min > 75) {
// Nearest half hour
os.precision(2);
os << round(delta_min / 30.0) / 2 << " hours";
} else if (delta_min >= 57) {
os << "1 hour";
} else {
os << round(delta_min / 5.0) * 5 << " minutes";
}
os << " meeting free";
draw_message_with_image(cr, os.str(), HEADPHONES_PNG);
}
void draw_main_event(cairo_t *cr, json event) {
int margin = 10;
int startY = 50;
int title_width;
int title_height;
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
PangoLayout *layout = pango_cairo_create_layout (cr);
PangoFontDescription *title_font = pango_font_description_from_string (TITLE_FONT);
pango_layout_set_font_description (layout, title_font);
string summary = event["summary"];
pango_layout_set_text (layout, summary.c_str(), -1);
// 2 lines, ellipsize after that
pango_layout_set_width (layout, (400 - 2 * margin) * PANGO_SCALE);
int max_lines = event["location"].is_null() ? 3 : 2;
// Handy, but strange: if height is negative, it will be the (negative of) maximum number of lines per paragraph.
pango_layout_set_height (layout, -max_lines);
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
pango_layout_get_pixel_size(layout, &title_width, &title_height);
cairo_move_to (cr, margin, startY);
pango_cairo_show_layout(cr, layout);
if (!event["location"].is_null()) {
PangoFontDescription *subtitle_font = pango_font_description_from_string (SUBTITLE_FONT);
pango_layout_set_font_description (layout, subtitle_font);
string location = event["location"];
pango_layout_set_text (layout, location.c_str(), -1);
pango_layout_set_width (layout, (400 - 2 * margin) * PANGO_SCALE);
pango_layout_set_height (layout, -1);
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
cairo_move_to (cr, margin, startY + title_height + margin);
pango_cairo_show_layout(cr, layout);
pango_font_description_free (subtitle_font);
}
pango_font_description_free (title_font);
g_object_unref (layout);
}
void draw_secondary_event_line(cairo_t *cr, json event) {
if (event.is_null()) {
return;
}
string time_str;
if (event["start"]["dateTime"].is_null()) {
// All day event -- in current logic, this is always tomorrow
time_str = "Tomorrow:";
} else {
ostringstream os;
struct tm startTime = {};
convert_event_time_to_time(event["start"], &startTime);
static char timestr[10];
time_t currentTime = time(0);
struct tm * now = localtime( ¤tTime );
if (datediff(now, &startTime) < 0) {
// Another concurrent event
struct tm endTime = {};
convert_event_time_to_time(event["end"], &endTime);
strftime(timestr, 10 * sizeof(char), "%-I:%M%P", &endTime);
os << "Also - until " << timestr << ":";
time_str = os.str();
} else {
strftime(timestr, 10 * sizeof(char), "%-I:%M%P", &startTime);
os << "Then - " << timestr << ":";
time_str = os.str();
}
}
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
PangoLayout *layout = pango_cairo_create_layout (cr);
PangoFontDescription *bold_font = pango_font_description_from_string (SMALL_TEXT_BOLD_FONT);
pango_layout_set_font_description (layout, bold_font);
pango_layout_set_text (layout, time_str.c_str(), -1);
int text_width;
int text_height;
pango_layout_get_pixel_size(layout, &text_width, &text_height);
cairo_move_to (cr, 10, 300 - text_height - 10);
pango_cairo_show_layout(cr, layout);
PangoFontDescription *regular_font = pango_font_description_from_string (SMALL_TEXT_REGULAR_FONT);
pango_layout_set_font_description (layout, regular_font);
string summary = event["summary"];
pango_layout_set_text (layout, summary.c_str(), -1);
pango_layout_set_width (layout, (400 - text_width - 25) * PANGO_SCALE);
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
cairo_move_to (cr, text_width + 15, 300 - text_height - 10);
pango_cairo_show_layout(cr, layout);
pango_font_description_free (bold_font);
pango_font_description_free (regular_font);
g_object_unref (layout);
}
int main(void)
{
Screen screen;
if (screen.Init() != 0) {
printf("Screen initialization failed\n");
return -1;
}
//screen.Clear();
screen.HardWipe();
GoogleCalendar* gcal = new GoogleCalendar();
gcal->SetCredentials(GCAL_CLIENT_ID, GCAL_CLIENT_SECRET);
//gcal->RequestInstalledAppToken();
gcal->SetAuthToken(GCAL_AUTH_TOKEN, GCAL_REFRESH_TOKEN);
cairo_surface_t *surface = screen.GetCairoSurface();
cairo_t *cr = cairo_create (surface);
// Every 10 seconds, fetch events and re-render
while(true) {
json events = get_events(gcal);
draw_clock(cr);
draw_events(cr, events);
screen.Render();
// clear cairo context
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
sleep(10);
}
cairo_destroy (cr);
screen.Cleanup();
return 0;
}