#include #include #include #include #define _GNU_SOURCE #include #include #include #include #include #include #include #include "timezone_map.h" Display *display; Window window; GC gc; int screen; int selected_zone; int selected_place; Pixmap pixmap; XColor ocean; XColor land[8]; XColor border; XColor selected_border; XColor selected_land; XColor name_colour; XColor location_dot; XColor lat_lon; XColor tux_yellow; Dimension screen_width = 1920; Dimension screen_height = 1080; Dimension width = 1920; Dimension height = 1080; Position x = 0; Position y = 0; XFontStruct *font; XPoint xpoints[1000]; int radius; double aspect_correction = 1; double target_lat; double target_lon; double displayed_lat; double displayed_lon; double lon_rotate[2] = {1, 0}; double lat_rotate[2] = {1, 0}; double acceleration = 0.01; char input_keys[200]; int guessing_timezone = FALSE; time_t guess_timeout = 0; int guess_failed = FALSE; time_t guess_failed_timeout = 0; int show_information = FALSE; int info_displayed = FALSE; void select_place (int place) { target_lon = atan2 (zone_data[selected_zone].place_info[place].v.y, zone_data[selected_zone].place_info[place].v.x); target_lat = acos (zone_data[selected_zone].place_info[place].v.z /2); selected_place = place; } void default_zone (char *arg_zone) { int found_zone = FALSE; for (int zone = 0; zone < NUM_ZONES; zone++) { for (int place = 0; place < zone_data[zone].num_places; place++) { if (strstr (zone_data[zone].place_info[place].zonename, arg_zone[0] ? arg_zone : "Los_Angeles")) { selected_zone = zone; select_place (place); found_zone = TRUE; } } } if (!found_zone) { default_zone (""); } } int translate_point (vect_type *v, XPoint *p) { vect_type v2; vect_type v3; v2.x = v->x * lon_rotate[0] + v->y * lon_rotate[1]; v2.y = v->x * -1.0 * lon_rotate[1] + v->y * lon_rotate[0]; v2.z = v->z; v3.x = v2.x * lat_rotate[0] + v2.z * lat_rotate[1]; v3.y = v2.y * radius; v3.z = v2.x * -1.0 * lat_rotate[1] + v2.z * lat_rotate[0]; // If the point is behind the face of the globe, project it to the edge. if (v3.x < 0) { p->x = width /2 + radius * sin (atan2 (v3.y,v3.z)) * aspect_correction; p->y = height/2 - radius * cos (atan2 (v3.y,v3.z)); } else { p->x = width /2 + lrint (v3.y) * aspect_correction; p->y = height/2 - lrint (v3.z); } // Return a value indicating if it is on the front of the world. return (v3.x >= 0); } void draw_zone ( Drawable d, int zone, int fill, int selected) { int boundary = 0; int num_points = 0; int all_on_back = 1; if (selected && fill) XSetForeground (display, gc, selected_land.pixel); while (boundary < zone_data[zone].num_boundary_points) { if (zone_data[zone].boundary_points[boundary].x < -9) { if ((num_points > 1) && !all_on_back) { if (fill) { if (!selected) { if (zone == NUM_ZONES - 1) { if (boundary < 380) XSetForeground (display, gc, WhitePixel (display, screen)); else if (boundary < 470) XSetForeground (display, gc, tux_yellow.pixel); else if (boundary < 1120) XSetForeground (display, gc, BlackPixel (display, screen)); else XSetForeground (display, gc, WhitePixel (display, screen)); } else { XSetForeground (display, gc, land[boundary % XtNumber (land)].pixel); } } XFillPolygon ( display, d, gc, xpoints, num_points, Complex, CoordModeOrigin); } else XDrawLines ( display, d, gc, xpoints, num_points, CoordModeOrigin); } boundary++; num_points = 0; all_on_back = 1; } else { if (translate_point (&zone_data[zone].boundary_points[boundary], &xpoints[num_points])) all_on_back = 0; num_points++; boundary++; } } } void draw_string (int x, int y, const char*s, unsigned long colour) { XSetForeground (display, gc, BlackPixel (display, screen)); for (int x_pos = x - 3; x_pos <= x + 3; x_pos++) for (int y_pos = y - 3; y_pos <= y + 3; y_pos++) XDrawString (display, pixmap, gc, x_pos, y_pos, s, strlen (s)); XSetForeground (display, gc, colour); XDrawString (display, pixmap, gc, x, y, s, strlen (s)); } void draw_point (XPoint p, char *s) { int text_width; int dot_size = ceil (sqrt (height) / 6.0); if (s) dot_size += 2; XSetForeground (display, gc, s ? WhitePixel (display, screen) : location_dot.pixel); XFillRectangle (display, pixmap, gc, p.x - dot_size / 2, p.y - dot_size / 2, dot_size, dot_size); XSetForeground (display, gc, BlackPixel (display, screen)); XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound); XDrawRectangle (display, pixmap, gc, p.x - dot_size / 2, p.y - dot_size / 2, dot_size, dot_size); if (s) { int text_x; int text_y; text_width = XTextWidth (font, s, strlen (s)); if (p.x + text_width + 30 > width) { text_x = p.x - text_width - 6; text_y = p.y + 10; } else { text_x = p.x + 6; text_y = p.y + 10; } draw_string (text_x, text_y, s, name_colour.pixel); } } void redraw_map () { XPoint selected_point; char selected_name[100]; lon_rotate[0] = cos (displayed_lon); lon_rotate[1] = sin (displayed_lon); lat_rotate[1] = cos (displayed_lat) * radius; lat_rotate[0] = sin (displayed_lat) * radius; /* Draw the map into the pixmap. */ XSetForeground (display, gc, BlackPixel (display, screen)); XFillRectangle (display, pixmap, gc, 0, 0, width, height); XSetForeground (display, gc, ocean.pixel); XFillArc (display, pixmap, gc, width / 2 - radius * aspect_correction, height / 2 - radius, radius * 2 * aspect_correction, radius * 2, 0, 360 * 64); XSetForeground (display, gc, lat_lon.pixel); XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound); for (int lon = 0; lon < 24; lon++) for (int lat = 0; lat < 12 * 3; lat++) { XPoint line[2]; vect_type v; v.x = cos (lon * 15 * M_PI / 180) * cos ((90 - lat * 5) * M_PI / 180); v.y = sin (lon * 15 * M_PI / 180) * cos ((90 - lat * 5) * M_PI / 180); v.z = sin ((90 - lat * 5) / 180.0 * M_PI); line[0] = line[1]; if (translate_point (&v, &line[1]) && (lat > 0)) XDrawLines (display, pixmap, gc, line, 2, CoordModeOrigin); } for (int lat = 0; lat < 12; lat++) for (int lon = 0; lon <= 24 * 3; lon++) { XPoint line[2]; vect_type v; v.x = cos (lon * 5 * M_PI / 180) * cos ((90 - lat * 15) * M_PI / 180); v.y = sin (lon * 5 * M_PI / 180) * cos ((90 - lat * 15) * M_PI / 180); v.z = sin ((90 - lat * 15) / 180.0 * M_PI); line[0] = line[1]; if (translate_point (&v, &line[1]) && (lon > 0)) XDrawLines (display, pixmap, gc, line, 2, CoordModeOrigin); } for (int zone = 0; zone < NUM_ZONES; zone++) draw_zone (pixmap, zone, 1, 0); XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound); XSetForeground (display, gc, border.pixel); for (int zone = 0; zone < NUM_ZONES - 1; zone++) draw_zone (pixmap, zone, 0, 0); if (show_information) { info_displayed = TRUE; char *s[4] = {"Left/Right: Change Offset", "Up/Down: Change Location", "Enter: Select Location", "Esc: Abort"}; int num = XtNumber(s); for (int i = 0; i < num; i++) { int text_width = XTextWidth (font, s[i], strlen (s[i])); draw_string (width/2 - text_width/2, height/2 + (-2 + i) * (font->ascent + font->descent), s[i], name_colour.pixel); } } else if (guessing_timezone || (info_displayed = FALSE)) { char *s = "Guessing Timezone"; int text_width = XTextWidth (font, s, strlen (s)); draw_string (width/2 - text_width/2, height/2, s, name_colour.pixel); } else if (guess_failed) { char *s = "Unable to Guess Timezone"; char *s2 = "Please Select Timezone Manually"; int text_width = XTextWidth (font, s, strlen (s)); draw_string (width/2 - text_width/2, height/2, s, name_colour.pixel); text_width = XTextWidth (font, s2, strlen (s2)); draw_string (width/2 - text_width/2, height/2 + font->ascent + font->descent, s2, name_colour.pixel); } else { draw_zone (pixmap, selected_zone, 1, 1); XSetForeground (display, gc, selected_border.pixel); draw_zone (pixmap, selected_zone, 0, 1); for (int place = 0; place < zone_data[selected_zone].num_places; place++) { XPoint point; translate_point (&zone_data[selected_zone].place_info[place].v, &point); draw_point (point, NULL); if ((place == selected_place) && (!guessing_timezone)) { char *underscore; selected_point = point; strcpy (selected_name, strchr (zone_data[selected_zone].place_info[place].zonename, '/') + 1); while (underscore = strchr (selected_name, '_')) underscore[0] = ' '; } } draw_point (selected_point, selected_name); { char zone_offset[100]; sprintf (zone_offset, "UTC%s", zone_data[selected_zone].offset); draw_string ( ((width * 0.95 - XTextWidth (font, "UTC+88", strlen ("UTC+88"))) + ((width / 2) + radius / sqrt (2))) / 2, ((lrint (height * 0.05) + font->ascent) + (height / 2 - radius / sqrt (2))) / 2, zone_offset, name_colour.pixel); } } if (!guessing_timezone && !guess_failed) { const char *title = "Select Your Time Zone"; const char *help = "Help = i"; draw_string (width / 2 - XTextWidth (font, title, strlen (title)) / 2, ((lrint (height * 0.05) + font->ascent) + (height / 2 - radius / sqrt (2))) / 2, title, WhitePixel (display,screen)); draw_string (width * 0.05, height * 0.95 - font->descent - font->ascent, help, name_colour.pixel); } XCopyArea (display, pixmap, window, gc, 0, 0, width, height, 0, 0); } int nearest_z (double near_z) { double min_delta = fabs (near_z - zone_data[selected_zone].place_info[0].v.z); int min_delta_place = 0; for (int place = 1; place < zone_data[selected_zone].num_places; place++) { double delta = fabs (near_z - zone_data[selected_zone].place_info[place].v.z); if (delta < min_delta) { min_delta_place = place; min_delta = delta; } } return min_delta_place; } void handle_key (char key) { double selected_z; selected_z = zone_data[selected_zone].place_info[selected_place].v.z; if (show_information) { show_information = FALSE; return; } switch (key) { case 'U': select_place ((selected_place + zone_data[selected_zone].num_places - 1) % zone_data[selected_zone].num_places); break; case 'D': select_place ((selected_place + 1) % zone_data[selected_zone].num_places); break; case 'L': selected_zone = (selected_zone + NUM_ZONES - 1) % NUM_ZONES; select_place (nearest_z (selected_z)); break; case 'R': selected_zone = (selected_zone + 1) % NUM_ZONES; select_place (nearest_z (selected_z)); break; case 'I': show_information = !show_information; break; case 'X': printf ("%s\n", zone_data[selected_zone].place_info[selected_place].zonename); exit (0); case 'E': exit(1); } } void handle_event (XEvent *xevent) { switch (xevent->type) { case Expose: redraw_map (); break; case KeyPress: switch (XLookupKeysym (&xevent->xkey, 0)) { case XK_Up: handle_key ('U'); break; case XK_Down: handle_key ('D'); break; case XK_Left: handle_key ('L'); break; case XK_Right: handle_key ('R'); break; case XK_Return: handle_key ('X'); break; case XK_Escape: handle_key ('E'); break; case XK_I: case XK_i: handle_key ('I'); break; } break; } } Bool event_predicate (Display *display, XEvent *xevent, XPointer unused) { return (xevent->type == KeyPress) || (xevent->type == Expose); } void next_view () { static double step_size = 0.01; double error_total; double lat_error = target_lat - displayed_lat; double lon_error = target_lon - displayed_lon; if (guessing_timezone) { displayed_lon += 1 / 180.0 * M_PI; } else { lat_error = atan2 (sin (lat_error), cos( lat_error)); lon_error = atan2 (sin (lon_error), cos (lon_error)); error_total = sqrt (lat_error * lat_error + lon_error * lon_error); if (error_total < acceleration) { displayed_lat = target_lat; displayed_lon = target_lon; } else { if (error_total > (step_size + acceleration) * (step_size + acceleration) / acceleration / 2) step_size += acceleration; else step_size -= acceleration; if (step_size < acceleration) step_size = acceleration; displayed_lat += step_size * (lat_error / error_total); displayed_lon += step_size * (lon_error / error_total); } } } void *lirc_thread (void *unused) { int lirc_fd; struct lirc_config *lirc_config; char *lirc_code; if ((lirc_fd = lirc_init ("mythtv",0)) == -1) fprintf (stderr,"Error initialising lirc\n"); else { int readc_status; if (readc_status = lirc_readconfig (NULL, &lirc_config, NULL)) { fprintf (stderr,"Error loading lirc config file %d %p\n", readc_status, lirc_config); } } while ((lirc_nextcode (&lirc_code) == 0) && (lirc_code != NULL)) { char *action; while ((lirc_code2char (lirc_config, lirc_code, &action) == 0) && (action != NULL)) { if (strcasecmp (action, "down") == 0) strcat (input_keys, "D"); if (strcasecmp (action, "up") == 0) strcat (input_keys, "U"); if (strcasecmp (action, "left") == 0) strcat (input_keys, "L"); if (strcasecmp (action, "right") == 0) strcat (input_keys, "R"); if (strcasecmp (action, "return") == 0) strcat (input_keys, "X"); if (strcasecmp (action, "Esc") == 0) strcat (input_keys, "E"); } free (lirc_code); lirc_code = NULL; } } void get_value (FILE *input, char*output, int out_len) { const char *value_pattern = "value=\""; char *value; output[0] = 0; fgets (output, out_len, input); if (value = strcasestr (output, value_pattern)) { memmove (output, value + strlen (value_pattern), strlen (value + strlen (value_pattern)) + 1); } if (value = strchr (output, '"')) value[0] = 0; } void *timezone_guess (void *unused) { const double invalid = 99999; FILE *guess_data; char line_in[1000]; double guess_latitude = invalid; double guess_longitude = invalid; char guess_zone[1000] = {0}; char *timezone_info_command; if (getenv ("FAKE_GEOBYTES")) { timezone_info_command = "sleep 2 ; " "echo Latitude ; " "echo \" res_name = appname; classHint->res_class = "MoonRoot"; } XSetClassHint (display, window, classHint); XFree (classHint); typedef struct { CARD32 flags; CARD32 functions; CARD32 decorations; INT32 input_mode; CARD32 status; } MotifWmHints, MwmHints; #define MWM_HINTS_DECORATIONS (1L << 1) Atom XA_MOTIF_WM_HINTS = XInternAtom (display, "_MOTIF_WM_HINTS", False); MotifWmHints mwm_hints; mwm_hints.flags = MWM_HINTS_DECORATIONS; mwm_hints.decorations = 0; XChangeProperty ( display, window, XA_MOTIF_WM_HINTS, XA_MOTIF_WM_HINTS, 32, PropModeReplace, (char *) &mwm_hints, 5); } void set_window_position (int x, int y) { XSizeHints hints; hints.flags = USPosition | PPosition; hints.x = x; hints.y = y; XSetWMNormalHints(display, window, &hints); } int main (int argc, char *argv[]) { XEvent xevent; Colormap cmap; XColor color, colorrgb; pthread_t tid; int opt; int arg_width = -1; int arg_height = -1; char arg_zone[100] = {0}; if (getenv ("ACCEL")) acceleration = atof (getenv ("ACCEL")); while ((opt = getopt (argc, argv, "z:w:h:a:")) != -1) { switch (opt) { case 'z': strncpy (arg_zone, optarg, sizeof (arg_zone) - 1); if (strcasecmp (arg_zone, "guess") == 0) { guessing_timezone = TRUE; arg_zone[0]=0; } break; case 'w': arg_width = atoi (optarg); break; case 'h': arg_height = atoi (optarg); break; case 'a': acceleration = atof (optarg); break; case '?': printf ("usage: %s [-a accelleration] [-z timezone] " "[-w width] [-h height]\n" "e.g. linhes_timezone -w 1920 -h 1090 -z Australia/Adelaide\n" "specify a timezone of 'guess' to determine the initial " "timezone from\n" "your ip address using geocache.\n", argv[0]); exit (0); break; } } pthread_create (&tid, NULL, lirc_thread, NULL); /* Connect to the X server. */ display = XOpenDisplay (""); if (display == NULL) { fprintf (stderr, "cannot connect to server\n"); exit (EXIT_FAILURE); } /* Get default screen. */ screen = DefaultScreen (display); screen_width = XWidthOfScreen (DefaultScreenOfDisplay (display)); screen_height = XHeightOfScreen (DefaultScreenOfDisplay (display)); if ((arg_width <= 0) && (arg_height > 0)) arg_width == arg_height; if (arg_width > 0) { width = arg_width; if (arg_height <= 0) height = width; else height = arg_height; x = (screen_width - width) / 2; y = (screen_height - height) / 2; } else { width = screen_width; height = screen_height; x = 0; y = 0; } if (width < height) radius = width; else radius = height; radius = 0.96 * radius / 2; if ((screen_height == 0) || (XWidthMMOfScreen (DefaultScreenOfDisplay (display)) == 0)) aspect_correction = 1; else aspect_correction = sqrt (screen_width * XHeightMMOfScreen (DefaultScreenOfDisplay (display)) * 1.0 / screen_height / XWidthMMOfScreen (DefaultScreenOfDisplay (display))); // Protect against very wierd aspect corrections from bogus // screen dimensions. if ((aspect_correction < 0.5) || (aspect_correction > 2)) aspect_correction = 1; window = XCreateSimpleWindow (display, DefaultRootWindow(display), x, y, width, height, 0, land[0].pixel, ocean.pixel); remove_titlebar_and_borders (); set_window_position (x, y); if (!window) { fprintf (stderr, "cannot open window\n"); exit (EXIT_FAILURE); } /* set graphics context of rectangle to red */ gc= XCreateGC (display, window, 0, 0); cmap = DefaultColormap (display, screen); // Load the font. font = XLoadQueryFont (display, (width > 800) ? "-*-lucida-bold-r-*-*-34-*-*-*-*-*-*-*" : "-*-lucida-bold-r-*-*-20-*-*-*-*-*-*-*"); if (!font) fprintf (stderr,"error loading font\n"); XSetFont (display, gc, font->fid); pixmap = XCreatePixmap (display, window, width, height, DefaultDepth (display, DefaultScreen (display))); ocean.flags = DoRed | DoGreen | DoBlue; ocean.red = 40 * 256; ocean.green = 41 * 256; ocean.blue = 72 * 256; if (XAllocColor (display, cmap, &ocean) == 0) printf ("Cant allocate color\n"); lat_lon.flags = DoRed | DoGreen | DoBlue; lat_lon.red = 0 * 256; lat_lon.green = 0 * 256; lat_lon.blue = 86 * 256; if (XAllocColor (display, cmap, &lat_lon) == 0) printf ("Cant allocate color\n"); tux_yellow.flags = DoRed | DoGreen | DoBlue; tux_yellow.red = 248 * 256; tux_yellow.green = 191 * 256; tux_yellow.blue = 17 * 256; if (XAllocColor (display, cmap, &tux_yellow) == 0) printf ("Cant allocate color\n"); for (int land_col = 0; land_col < XtNumber (land); land_col++) { land[land_col].flags = DoRed | DoGreen | DoBlue; land[land_col].red = (120 + 9 * land_col) * 256; land[land_col].green = (40 + 3 * land_col) * 256; land[land_col].blue = 0 * 256; if (XAllocColor (display, cmap, &land[land_col]) == 0) printf ("Cant allocate color\n"); } selected_land.flags = DoRed | DoGreen | DoBlue; selected_land.red = 3 * 256; selected_land.green = 40 * 256; selected_land.blue = 13 * 256; if (XAllocColor (display, cmap, &selected_land) == 0) printf ("Cant allocate color\n"); border.flags = DoRed | DoGreen | DoBlue; border.red = 0 * 256; border.green = 0 * 256; border.blue = 0 * 256; if (XAllocColor (display, cmap, &border) == 0) printf ("Cant allocate color\n"); selected_border.flags = DoRed | DoGreen | DoBlue; selected_border.red = 80 * 256; selected_border.green = 255 * 256; selected_border.blue = 80 * 256; if (XAllocColor (display, cmap, &selected_border) == 0) printf ("Cant allocate color\n"); name_colour.flags = DoRed | DoGreen | DoBlue; name_colour.red = 255 * 256; name_colour.green = 255 * 256; name_colour.blue = 0 * 256; if (XAllocColor (display, cmap, &name_colour) == 0) printf ("Cant allocate color\n"); location_dot.flags = DoRed | DoGreen | DoBlue; location_dot.red = 255 * 256; location_dot.green = 55 * 256; location_dot.blue = 200 * 256; if (XAllocColor (display, cmap, &location_dot) == 0) printf ("Cant allocate color\n"); // Find the selected timezone (or LA if a timezone was not selected) and // make that the selected zone and place. default_zone (arg_zone); if (guessing_timezone) { pthread_create (&tid, NULL, timezone_guess, NULL); guess_timeout = time (NULL) + 10; displayed_lat = M_PI/2; displayed_lon = 0; } else { displayed_lat = target_lat; displayed_lon = target_lon; } /* ask for exposure event and keyboard events */ XSelectInput(display, window, KeymapNotify | ExposureMask); /* pop this window up on the screen */ XMapRaised (display, window); redraw_map (); while (1) { if (guessing_timezone && (time (NULL) > guess_timeout)) { guessing_timezone = FALSE; guess_failed_timeout = time (NULL) + 3; guess_failed = TRUE; } if ((displayed_lat != target_lat) || (displayed_lon != target_lon) || guessing_timezone || guess_failed || (info_displayed ^ show_information)) { if (guess_failed && (time (NULL) > guess_failed_timeout)) { guess_failed = FALSE; } next_view (); redraw_map (); } else { usleep (1000); } while (strlen (input_keys)) { handle_key (input_keys[0]); memmove (&input_keys[0], &input_keys[1], strlen (input_keys)); } // If there is an event pending, go on to process it. if (XCheckIfEvent (display, &xevent, event_predicate, NULL)) handle_event (&xevent); } return 0; }