/* vim: tabstop=4 shiftwidth=4 noexpandtab * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2013-2018 K. Lange * * Yutani Panel * * Provides an applications menu, a window list, status widgets, * and a clock, manages the user session, and provides alt-tab * window switching and alt-f2 app runner. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PANEL_HEIGHT 28 #define FONT_SIZE 14 #define TIME_LEFT 108 #define DATE_WIDTH 70 #define GRADIENT_HEIGHT 24 #define APP_OFFSET 140 #define TEXT_Y_OFFSET 2 #define ICON_PADDING 2 #define MAX_TEXT_WIDTH 180 #define MIN_TEXT_WIDTH 50 #define HILIGHT_COLOR rgb(142,216,255) #define FOCUS_COLOR rgb(255,255,255) #define TEXT_COLOR rgb(230,230,230) #define ICON_COLOR rgb(230,230,230) #define GRADIENT_AT(y) premultiply(rgba(72, 167, 255, ((24-(y))*160)/24)) #define ALTTAB_WIDTH 250 #define ALTTAB_HEIGHT 100 #define ALTTAB_BACKGROUND premultiply(rgba(0,0,0,150)) #define ALTTAB_OFFSET 10 #define ALTF2_WIDTH 400 #define ALTF2_HEIGHT 200 #define MAX_WINDOW_COUNT 100 #define TOTAL_CELL_WIDTH (title_width) #define LEFT_BOUND (width - TIME_LEFT - DATE_WIDTH - ICON_PADDING - widgets_width) #define WIDGET_WIDTH 24 #define WIDGET_RIGHT (width - TIME_LEFT - DATE_WIDTH) #define WIDGET_POSITION(i) (WIDGET_RIGHT - WIDGET_WIDTH * (i+1)) static yutani_t * yctx; static gfx_context_t * ctx = NULL; static yutani_window_t * panel = NULL; static gfx_context_t * actx = NULL; static yutani_window_t * alttab = NULL; static gfx_context_t * a2ctx = NULL; static yutani_window_t * alt_f2 = NULL; static list_t * window_list = NULL; static volatile int lock = 0; static volatile int drawlock = 0; static size_t bg_size; static char * bg_blob; static int width; static int height; static int widgets_width = 0; static int widgets_volume_enabled = 0; static int widgets_network_enabled = 0; static int widgets_weather_enabled = 0; static int network_status = 0; static sprite_t * sprite_panel; static sprite_t * sprite_logout; static sprite_t * sprite_volume_mute; static sprite_t * sprite_volume_low; static sprite_t * sprite_volume_med; static sprite_t * sprite_volume_high; static sprite_t * sprite_net_active; static sprite_t * sprite_net_disabled; struct MenuList * appmenu; struct MenuList * window_menu; struct MenuList * logout_menu; struct MenuList * netstat; struct MenuList * calmenu; struct MenuList * weather; static yutani_wid_t _window_menu_wid = 0; static int _close_enough(struct yutani_msg_window_mouse_event * me) { if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) { return 1; } return 0; } static int center_x(int x) { return (width - x) / 2; } static int center_y(int y) { return (height - y) / 2; } static int center_x_a(int x) { return (ALTTAB_WIDTH - x) / 2; } static int center_x_a2(int x) { return (ALTF2_WIDTH - x) / 2; } static void redraw(void); static volatile int _continue = 1; struct window_ad { yutani_wid_t wid; uint32_t flags; char * name; char * icon; char * strings; int left; }; typedef struct { char * icon; char * appname; char * title; } application_t; /* Windows, indexed by list order */ static struct window_ad * ads_by_l[MAX_WINDOW_COUNT+1] = {NULL}; /* Windows, indexed by z-order */ static struct window_ad * ads_by_z[MAX_WINDOW_COUNT+1] = {NULL}; static int focused_app = -1; static int active_window = -1; static int was_tabbing = 0; static int new_focused = -1; static int title_width = 0; static void toggle_hide_panel(void) { static int panel_hidden = 0; if (panel_hidden) { /* Unhide the panel */ for (int i = PANEL_HEIGHT-1; i >= 0; i--) { yutani_window_move(yctx, panel, 0, -i); usleep(10000); } panel_hidden = 0; } else { /* Hide the panel */ for (int i = 1; i <= PANEL_HEIGHT-1; i++) { yutani_window_move(yctx, panel, 0, -i); usleep(10000); } panel_hidden = 1; } } /* Handle SIGINT by telling other threads (clock) to shut down */ static void sig_int(int sig) { printf("Received shutdown signal in panel!\n"); _continue = 0; signal(SIGINT, sig_int); } static void launch_application(char * app) { if (!fork()) { printf("Starting %s\n", app); char * args[] = {"/bin/sh", "-c", app, NULL}; execvp(args[0], args); exit(1); } } /* Update the hover-focus window */ static void set_focused(int i) { if (focused_app != i) { focused_app = i; redraw(); } } static void _window_menu_start_move(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_focus_window(yctx, _window_menu_wid); yutani_window_drag_start_wid(yctx, _window_menu_wid); } static void _window_menu_start_maximize(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_MAXIMIZE); yutani_focus_window(yctx, _window_menu_wid); } static void _window_menu_close(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_focus_window(yctx, _window_menu_wid); yutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE); } static void window_show_menu(yutani_wid_t wid, int y, int x) { if (window_menu->window) return; _window_menu_wid = wid; menu_show(window_menu, yctx); yutani_window_move(yctx, window_menu->window, y, x); } #define VOLUME_DEVICE_ID 0 #define VOLUME_KNOB_ID 0 static uint32_t volume_level = 0; static int mixer = -1; static void update_volume_level(void) { if (mixer == -1) { mixer = open("/dev/mixer", O_RDONLY); } snd_knob_value_t value = {0}; value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */ value.id = VOLUME_KNOB_ID; /* TODO this too */ ioctl(mixer, SND_MIXER_READ_KNOB, &value); volume_level = value.val; } static void volume_raise(void) { if (volume_level > 0xE0000000) volume_level = 0xF0000000; else volume_level += 0x10000000; snd_knob_value_t value = {0}; value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */ value.id = VOLUME_KNOB_ID; /* TODO this too */ value.val = volume_level; ioctl(mixer, SND_MIXER_WRITE_KNOB, &value); redraw(); } static void volume_lower(void) { if (volume_level < 0x20000000) volume_level = 0x0; else volume_level -= 0x10000000; snd_knob_value_t value = {0}; value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */ value.id = VOLUME_KNOB_ID; /* TODO this too */ value.val = volume_level; ioctl(mixer, SND_MIXER_WRITE_KNOB, &value); redraw(); } static int weather_left = 0; static struct MenuEntry_Normal * weather_title_entry; static struct MenuEntry_Normal * weather_updated_entry; static struct MenuEntry_Normal * weather_conditions_entry; static struct MenuEntry_Normal * weather_humidity_entry; static struct MenuEntry_Normal * weather_clouds_entry; static char * weather_title_str; static char * weather_updated_str; static char * weather_conditions_str; static char * weather_humidity_str; static char * weather_clouds_str; static char * weather_temp_str; static int weather_status_valid = 0; static hashmap_t * weather_icons = NULL; static sprite_t * weather_icon = NULL; static void update_weather_status(void) { FILE * f = fopen("/tmp/weather-parsed.conf","r"); if (!f) { weather_status_valid = 0; if (widgets_weather_enabled) { widgets_weather_enabled = 0; /* Unshow */ widgets_width -= WIDGET_WIDTH; } return; } weather_status_valid = 1; if (!widgets_weather_enabled) { widgets_weather_enabled = 1; widgets_width += WIDGET_WIDTH; } if (weather_title_str) free(weather_title_str); if (weather_updated_str) free(weather_updated_str); if (weather_conditions_str) free(weather_conditions_str); if (weather_humidity_str) free(weather_humidity_str); if (weather_clouds_str) free(weather_clouds_str); if (weather_temp_str) free(weather_temp_str); /* read the entire status file */ fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); char * data = malloc(size + 1); fread(data, size, 1, f); data[size] = 0; fclose(f); /* Find relevant pieces */ char * t = data; char * temp = t; t = strstr(t, "\n"); *t = '\0'; t++; char * temp_r = t; t = strstr(t, "\n"); *t = '\0'; t++; char * conditions = t; t = strstr(t, "\n"); *t = '\0'; t++; char * icon = t; t = strstr(t, "\n"); *t = '\0'; t++; char * humidity = t; t = strstr(t, "\n"); *t = '\0'; t++; char * clouds = t; t = strstr(t, "\n"); *t = '\0'; t++; char * city = t; t = strstr(t, "\n"); *t = '\0'; t++; char * updated = t; if (!weather_icons) { weather_icons = hashmap_create(10); } if (!hashmap_has(weather_icons, icon)) { sprite_t * tmp = malloc(sizeof(sprite_t)); char path[512]; sprintf(path,"/usr/share/icons/weather/%s.bmp", icon); load_sprite(tmp, path); tmp->alpha = ALPHA_FORCE_SLOW_EMBEDDED; hashmap_set(weather_icons, icon, tmp); } weather_icon = hashmap_get(weather_icons, icon); char tmp[300]; sprintf(tmp, "Weather for %s", city); weather_title_str = strdup(tmp); sprintf(tmp, "%s", updated); weather_updated_str = strdup(tmp); sprintf(tmp, "%s° - %s", temp, conditions); weather_conditions_str = strdup(tmp); sprintf(tmp, "Humidity: %s%%", humidity); weather_humidity_str = strdup(tmp); sprintf(tmp, "Clouds: %s%%", clouds); weather_clouds_str = strdup(tmp); sprintf(tmp, "%s°", temp_r); weather_temp_str = strdup(tmp); free(data); } static int netstat_left = 0; static struct MenuEntry_Normal * netstat_ip_entry; static char * netstat_ip = NULL; static struct MenuEntry_Normal * netstat_device_entry; static char * netstat_device = NULL; static struct MenuEntry_Normal * netstat_gateway_entry; static char * netstat_gateway = NULL; static struct MenuEntry_Normal * netstat_dns_entry; static char * netstat_dns = NULL; static struct MenuEntry_Normal * netstat_mac_entry; static char * netstat_mac = NULL; static void update_network_status(void) { FILE * net = fopen("/proc/netif","r"); if (!net) return; char line[256]; do { memset(line, 0, 256); fgets(line, 256, net); if (!*line) break; if (strstr(line,"no network") != NULL) { network_status = 0; break; } else if (strstr(line,"ip:") != NULL) { network_status = 1; if (netstat_ip) { free(netstat_ip); } char tmp[512]; sprintf(tmp, "IP: %s", &line[strlen("ip: ")]); char * lf = strstr(tmp,"\n"); if (lf) *lf = '\0'; netstat_ip = strdup(tmp); } else if (strstr(line,"device:") != NULL) { network_status = 1; if (netstat_device) { free(netstat_device); } char tmp[512]; sprintf(tmp, "Device: %s", &line[strlen("device: ")]); char * lf = strstr(tmp,"\n"); if (lf) *lf = '\0'; netstat_device = strdup(tmp); } else if (strstr(line,"gateway:") != NULL) { network_status = 1; if (netstat_gateway) { free(netstat_gateway); } char tmp[512]; sprintf(tmp, "Gateway: %s", &line[strlen("gateway: ")]); char * lf = strstr(tmp,"\n"); if (lf) *lf = '\0'; netstat_gateway = strdup(tmp); } else if (strstr(line,"dns:") != NULL) { network_status = 1; if (netstat_dns) { free(netstat_dns); } char tmp[512]; sprintf(tmp, "Primary DNS: %s", &line[strlen("dns: ")]); char * lf = strstr(tmp,"\n"); if (lf) *lf = '\0'; netstat_dns = strdup(tmp); } else if (strstr(line,"mac:") != NULL) { network_status = 1; if (netstat_mac) { free(netstat_mac); } char tmp[512]; sprintf(tmp, "MAC: %s", &line[strlen("mac: ")]); char * lf = strstr(tmp,"\n"); if (lf) *lf = '\0'; netstat_mac = strdup(tmp); } } while (1); fclose(net); } static void show_logout_menu(void) { if (!logout_menu->window) { menu_show(logout_menu, yctx); if (logout_menu->window) { yutani_window_move(yctx, logout_menu->window, width - logout_menu->window->width, PANEL_HEIGHT); } } } static void show_app_menu(void) { if (!appmenu->window) { menu_show(appmenu, yctx); if (appmenu->window) { yutani_window_move(yctx, appmenu->window, 0, PANEL_HEIGHT); } } } static void show_cal_menu(void) { if (!calmenu->window) { menu_show(calmenu, yctx); if (calmenu->window) { yutani_window_move(yctx, calmenu->window, width - 24 - calmenu->window->width, PANEL_HEIGHT); } } } static void weather_refresh(struct MenuEntry * self) { (void)self; system("weather-tool &"); } static void weather_configure(struct MenuEntry * self) { (void)self; system("terminal sh -c \"sudo weather-configurator; weather-tool\" &"); } static void show_weather_status(void) { if (!weather) { weather = menu_create(); weather_title_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_title_entry); weather_updated_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_updated_entry); menu_insert(weather, menu_create_separator()); weather_conditions_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_conditions_entry); weather_humidity_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_humidity_entry); weather_clouds_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_clouds_entry); menu_insert(weather, menu_create_separator()); menu_insert(weather, menu_create_normal("refresh", NULL, "Refresh...", weather_refresh)); menu_insert(weather, menu_create_normal("config", NULL, "Configure...", weather_configure)); menu_insert(weather, menu_create_separator()); menu_insert(weather, menu_create_normal(NULL, NULL, "Weather data provided by", NULL)); menu_insert(weather, menu_create_normal(NULL, NULL, "OpenWeatherMap.org", NULL)); } if (weather_status_valid) { menu_update_title(weather_title_entry, weather_title_str); menu_update_title(weather_updated_entry, weather_updated_str); menu_update_title(weather_conditions_entry, weather_conditions_str); menu_update_title(weather_humidity_entry, weather_humidity_str); menu_update_title(weather_clouds_entry, weather_clouds_str); } if (!weather->window) { menu_show(weather, yctx); if (weather->window) { if (weather_left + weather->window->width > (unsigned int)width) { yutani_window_move(yctx, weather->window, width - weather->window->width, PANEL_HEIGHT); } else { yutani_window_move(yctx, weather->window, weather_left, PANEL_HEIGHT); } } } } static void show_network_status(void) { if (!netstat) { netstat = menu_create(); menu_insert(netstat, menu_create_normal(NULL, NULL, "Network Status", NULL)); menu_insert(netstat, menu_create_separator()); netstat_ip_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(netstat, netstat_ip_entry); netstat_dns_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(netstat, netstat_dns_entry); netstat_gateway_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(netstat, netstat_gateway_entry); netstat_mac_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(netstat, netstat_mac_entry); netstat_device_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(netstat, netstat_device_entry); } if (network_status) { menu_update_title(netstat_ip_entry, netstat_ip); menu_update_title(netstat_device_entry, netstat_device ? netstat_device : "(?)"); menu_update_title(netstat_dns_entry, netstat_dns ? netstat_dns : "(?)"); menu_update_title(netstat_gateway_entry, netstat_gateway ? netstat_gateway : "(?)"); menu_update_title(netstat_mac_entry, netstat_mac ? netstat_mac : "(?)"); } else { menu_update_title(netstat_ip_entry, "No network."); menu_update_title(netstat_device_entry, ""); menu_update_title(netstat_dns_entry, ""); menu_update_title(netstat_gateway_entry, ""); menu_update_title(netstat_mac_entry, ""); } if (!netstat->window) { menu_show(netstat, yctx); if (netstat->window) { if (netstat_left + netstat->window->width > (unsigned int)width) { yutani_window_move(yctx, netstat->window, width - netstat->window->width, PANEL_HEIGHT); } else { yutani_window_move(yctx, netstat->window, netstat_left, PANEL_HEIGHT); } } } } /* Callback for mouse events */ static void panel_check_click(struct yutani_msg_window_mouse_event * evt) { if (evt->wid == panel->wid) { if (evt->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(evt)) { /* Up-down click */ if (evt->new_x >= width - 24 ) { show_logout_menu(); } else if (evt->new_x < APP_OFFSET) { show_app_menu(); } else if (evt->new_x >= width - TIME_LEFT) { show_cal_menu(); } else if (evt->new_x >= APP_OFFSET && evt->new_x < LEFT_BOUND) { for (int i = 0; i < MAX_WINDOW_COUNT; ++i) { if (ads_by_l[i] == NULL) break; if (evt->new_x >= ads_by_l[i]->left && evt->new_x < ads_by_l[i]->left + TOTAL_CELL_WIDTH) { yutani_focus_window(yctx, ads_by_l[i]->wid); break; } } } int widget = 0; if (widgets_weather_enabled) { if (evt->new_x > WIDGET_POSITION(widget+1) && evt->new_x < WIDGET_POSITION(widget-1)) { weather_left = WIDGET_POSITION(widget); show_weather_status(); } widget += 2; } if (widgets_network_enabled) { if (evt->new_x > WIDGET_POSITION(widget) && evt->new_x < WIDGET_POSITION(widget-1)) { netstat_left = WIDGET_POSITION(widget); show_network_status(); } widget++; } if (widgets_volume_enabled) { if (evt->new_x > WIDGET_POSITION(widget) && evt->new_x < WIDGET_POSITION(widget-1)) { /* TODO: Show the volume manager */ } widget++; } } else if (evt->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { if (evt->new_x >= APP_OFFSET && evt->new_x < LEFT_BOUND) { for (int i = 0; i < MAX_WINDOW_COUNT; ++i) { if (ads_by_l[i] == NULL) break; if (evt->new_x >= ads_by_l[i]->left && evt->new_x < ads_by_l[i]->left + TOTAL_CELL_WIDTH) { window_show_menu(ads_by_l[i]->wid, evt->new_x, PANEL_HEIGHT); } } } } else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) { /* Movement, or mouse entered window */ if (evt->new_y < PANEL_HEIGHT) { for (int i = 0; i < MAX_WINDOW_COUNT; ++i) { if (ads_by_l[i] == NULL) { set_focused(-1); break; } if (evt->new_x >= ads_by_l[i]->left && evt->new_x < ads_by_l[i]->left + TOTAL_CELL_WIDTH) { set_focused(i); break; } } } else { set_focused(-1); } int scroll_direction = 0; if (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1; else if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1; if (scroll_direction) { int widget = 0; if (widgets_weather_enabled) { if (evt->new_x > WIDGET_POSITION(widget+1) && evt->new_x < WIDGET_POSITION(widget-1)) { /* Ignore */ } widget += 2; } if (widgets_network_enabled) { if (evt->new_x > WIDGET_POSITION(widget) && evt->new_x < WIDGET_POSITION(widget-1)) { /* Ignore */ } widget++; } if (widgets_volume_enabled) { if (evt->new_x > WIDGET_POSITION(widget) && evt->new_x < WIDGET_POSITION(widget-1)) { if (scroll_direction == 1) { volume_lower(); } else if (scroll_direction == -1) { volume_raise(); } } widget++; } if (evt->new_x >= APP_OFFSET && evt->new_x < LEFT_BOUND) { if (scroll_direction != 0) { struct window_ad * last = window_list->tail ? window_list->tail->value : NULL; int focus_next = 0; foreach(node, window_list) { struct window_ad * ad = node->value; if (focus_next) { yutani_focus_window(yctx, ad->wid); return; } if (ad->flags & 1) { if (scroll_direction == -1) { yutani_focus_window(yctx, last->wid); return; } if (scroll_direction == 1) { focus_next = 1; } } last = ad; } if (focus_next && window_list->head) { struct window_ad * ad = window_list->head->value; yutani_focus_window(yctx, ad->wid); return; } } } } } else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) { /* Mouse left panel window */ set_focused(-1); } } } static char altf2_buffer[1024] = {0}; static unsigned int altf2_collected = 0; #if 0 static list_t * altf2_apps = NULL; struct altf2_app { char * name; sprite_t * icon; }; static sprite_t * find_icon(char * name) { struct { char * name; char * icon; } special[] = { {"about", "star"}, {"help-browser", "help"}, {"terminal", "utilities-terminal"}, {NULL,NULL}, }; int i = 0; while (special[i].name) { if (!strcmp(special[i].name, name)) { return icon_get_48(special[i].icon); } i++; } return icon_get_48(name); } #endif static void close_altf2(void) { free(a2ctx->backbuffer); free(a2ctx); altf2_buffer[0] = 0; altf2_collected = 0; yutani_close(yctx, alt_f2); alt_f2 = NULL; } static void redraw_altf2(void) { #if 0 if (!altf2_apps) { /* initialize */ } #endif draw_fill(a2ctx, 0); draw_rounded_rectangle(a2ctx,0,0, ALTF2_WIDTH, ALTF2_HEIGHT, 10, ALTTAB_BACKGROUND); int t = draw_sdf_string_width(altf2_buffer, 22, SDF_FONT_THIN); draw_sdf_string(a2ctx, center_x_a2(t), 60, altf2_buffer, 22, rgb(255,255,255), SDF_FONT_THIN); flip(a2ctx); yutani_flip(yctx, alt_f2); } static void redraw_alttab(void) { /* Draw the background, right now just a dark semi-transparent box */ draw_fill(actx, 0); draw_rounded_rectangle(actx,0,0, ALTTAB_WIDTH, ALTTAB_HEIGHT, 10, ALTTAB_BACKGROUND); if (ads_by_z[new_focused]) { struct window_ad * ad = ads_by_z[new_focused]; sprite_t * icon = icon_get_48(ad->icon); /* Draw it, scaled if necessary */ if (icon->width == 48) { draw_sprite(actx, icon, center_x_a(48), ALTTAB_OFFSET); } else { draw_sprite_scaled(actx, icon, center_x_a(48), ALTTAB_OFFSET, 48, 48); } int t = draw_sdf_string_width(ad->name, 18, SDF_FONT_THIN); draw_sdf_string(actx, center_x_a(t), 12+ALTTAB_OFFSET+40, ad->name, 18, rgb(255,255,255), SDF_FONT_THIN); } flip(actx); yutani_flip(yctx, alttab); } static void launch_application_menu(struct MenuEntry * self) { struct MenuEntry_Normal * _self = (void *)self; if (!strcmp((char *)_self->action,"log-out")) { if (system("showdialog \"Log Out\" /usr/share/icons/48/exit.bmp \"Are you sure you want to log out?\"") == 0) { yutani_session_end(yctx); _continue = 0; } } else { launch_application((char *)_self->action); } } static void handle_key_event(struct yutani_msg_key_event * ke) { if (alt_f2 && ke->wid == alt_f2->wid) { if (ke->event.action == KEY_ACTION_DOWN) { if (ke->event.keycode == KEY_ESCAPE) { close_altf2(); return; } if (ke->event.key == '\b') { if (altf2_collected) { altf2_buffer[altf2_collected-1] = '\0'; altf2_collected--; redraw_altf2(); } return; } if (ke->event.key == '\n') { /* execute */ launch_application(altf2_buffer); close_altf2(); return; } if (!ke->event.key) { return; } /* Try to add it */ if (altf2_collected < sizeof(altf2_buffer) - 1) { altf2_buffer[altf2_collected] = ke->event.key; altf2_collected++; altf2_buffer[altf2_collected] = 0; redraw_altf2(); } } } if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == 't') && (ke->event.action == KEY_ACTION_DOWN)) { launch_application("exec terminal"); return; } if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.keycode == KEY_F11) && (ke->event.action == KEY_ACTION_DOWN)) { fprintf(stderr, "[panel] Toggling visibility.\n"); toggle_hide_panel(); return; } if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F1) && (ke->event.action == KEY_ACTION_DOWN)) { /* show menu */ show_app_menu(); } if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F2) && (ke->event.action == KEY_ACTION_DOWN)) { /* show menu */ if (!alt_f2) { alt_f2 = yutani_window_create(yctx, ALTF2_WIDTH, ALTF2_HEIGHT); yutani_window_move(yctx, alt_f2, center_x(ALTF2_WIDTH), center_y(ALTF2_HEIGHT)); a2ctx = init_graphics_yutani_double_buffer(alt_f2); redraw_altf2(); } } if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F3) && (ke->event.action == KEY_ACTION_DOWN)) { for (int i = 0; i < MAX_WINDOW_COUNT; ++i) { if (ads_by_l[i] == NULL) break; if (ads_by_l[i]->flags & 1) { window_show_menu(ads_by_l[i]->wid, ads_by_l[i]->left, PANEL_HEIGHT); } } } if ((was_tabbing) && (ke->event.keycode == 0 || ke->event.keycode == KEY_LEFT_ALT) && (ke->event.modifiers == 0) && (ke->event.action == KEY_ACTION_UP)) { fprintf(stderr, "[panel] Stopping focus new_focused = %d\n", new_focused); struct window_ad * ad = ads_by_z[new_focused]; if (!ad) return; yutani_focus_window(yctx, ad->wid); was_tabbing = 0; new_focused = -1; free(actx->backbuffer); free(actx); yutani_close(yctx, alttab); return; } if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == '\t') && (ke->event.action == KEY_ACTION_DOWN)) { int direction = (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) ? 1 : -1; if (window_list->length < 1) return; if (was_tabbing) { new_focused = new_focused + direction; } else { new_focused = active_window + direction; /* Create tab window */ alttab = yutani_window_create(yctx, ALTTAB_WIDTH, ALTTAB_HEIGHT); /* Center window */ yutani_window_move(yctx, alttab, center_x(ALTTAB_WIDTH), center_y(ALTTAB_HEIGHT)); /* Initialize graphics context against the window */ actx = init_graphics_yutani_double_buffer(alttab); } if (new_focused < 0) { new_focused = 0; for (int i = 0; i < MAX_WINDOW_COUNT; i++) { if (ads_by_z[i+1] == NULL) { new_focused = i; break; } } } else if (ads_by_z[new_focused] == NULL) { new_focused = 0; } was_tabbing = 1; redraw_alttab(); } } static void redraw(void) { spin_lock(&drawlock); struct timeval now; struct tm * timeinfo; char buffer[80]; uint32_t txt_color = TEXT_COLOR; int t = 0; /* Redraw the background */ memcpy(ctx->backbuffer, bg_blob, bg_size); /* Get the current time for the clock */ gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); /* Hours : Minutes : Seconds */ strftime(buffer, 80, "%H:%M:%S", timeinfo); draw_sdf_string(ctx, width - TIME_LEFT, 3, buffer, 20, calmenu->window ? HILIGHT_COLOR : txt_color, SDF_FONT_THIN); /* Day-of-week */ strftime(buffer, 80, "%A", timeinfo); t = draw_sdf_string_width(buffer, 12, SDF_FONT_THIN); t = (DATE_WIDTH - t) / 2; draw_sdf_string(ctx, width - TIME_LEFT - DATE_WIDTH + t, 2, buffer, 12, txt_color, SDF_FONT_THIN); /* Month Day */ strftime(buffer, 80, "%h %e", timeinfo); t = draw_sdf_string_width(buffer, 12, SDF_FONT_BOLD); t = (DATE_WIDTH - t) / 2; draw_sdf_string(ctx, width - TIME_LEFT - DATE_WIDTH + t, 12, buffer, 12, txt_color, SDF_FONT_BOLD); /* Applications menu */ draw_sdf_string(ctx, 8, 3, "Applications", 20, appmenu->window ? HILIGHT_COLOR : txt_color, SDF_FONT_THIN); /* Draw each widget */ int widget = 0; /* Weather */ if (widgets_weather_enabled) { uint32_t color = (weather && weather->window) ? HILIGHT_COLOR : ICON_COLOR; int t = draw_sdf_string_width(weather_temp_str, 15, SDF_FONT_THIN); draw_sdf_string(ctx, WIDGET_POSITION(widget) + (WIDGET_WIDTH - t) / 2, 5, weather_temp_str, 15, color, SDF_FONT_THIN); draw_sprite_alpha_paint(ctx, weather_icon, WIDGET_POSITION(widget+1), 0, 1.0, color); widget += 2; } /* - Network */ if (widgets_network_enabled) { uint32_t color = (netstat && netstat->window) ? HILIGHT_COLOR : ICON_COLOR; if (network_status == 1) { draw_sprite_alpha_paint(ctx, sprite_net_active, WIDGET_POSITION(widget), 0, 1.0, color); } else { draw_sprite_alpha_paint(ctx, sprite_net_disabled, WIDGET_POSITION(widget), 0, 1.0, color); } widget++; } /* - Volume */ if (widgets_volume_enabled) { if (volume_level < 10) { draw_sprite_alpha_paint(ctx, sprite_volume_mute, WIDGET_POSITION(widget), 0, 1.0, ICON_COLOR); } else if (volume_level < 0x547ae147) { draw_sprite_alpha_paint(ctx, sprite_volume_low, WIDGET_POSITION(widget), 0, 1.0, ICON_COLOR); } else if (volume_level < 0xa8f5c28e) { draw_sprite_alpha_paint(ctx, sprite_volume_med, WIDGET_POSITION(widget), 0, 1.0, ICON_COLOR); } else { draw_sprite_alpha_paint(ctx, sprite_volume_high, WIDGET_POSITION(widget), 0, 1.0, ICON_COLOR); } widget++; } /* Now draw the window list */ int i = 0, j = 0; spin_lock(&lock); if (window_list) { foreach(node, window_list) { struct window_ad * ad = node->value; char * s = ""; char tmp_title[50]; int w = 0; if (APP_OFFSET + i + w > LEFT_BOUND) { break; } if (title_width > MIN_TEXT_WIDTH) { memset(tmp_title, 0x0, 50); int t_l = strlen(ad->name); if (t_l > 45) { t_l = 45; } for (int i = 0; i < t_l; ++i) { tmp_title[i] = ad->name[i]; if (!ad->name[i]) break; } while (draw_sdf_string_width(tmp_title, 16, SDF_FONT_THIN) > title_width - ICON_PADDING) { t_l--; tmp_title[t_l] = '.'; tmp_title[t_l+1] = '.'; tmp_title[t_l+2] = '.'; tmp_title[t_l+3] = '\0'; } w += title_width; s = tmp_title; } /* Hilight the focused window */ if (ad->flags & 1) { /* This is the focused window */ for (int y = 0; y < GRADIENT_HEIGHT; ++y) { for (int x = APP_OFFSET + i; x < APP_OFFSET + i + w; ++x) { GFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), GRADIENT_AT(y)); } } } /* Get the icon for this window */ sprite_t * icon = icon_get_48(ad->icon); { sprite_t * _tmp_s = create_sprite(48, PANEL_HEIGHT-2, ALPHA_EMBEDDED); gfx_context_t * _tmp = init_graphics_sprite(_tmp_s); draw_fill(_tmp, rgba(0,0,0,0)); /* Draw it, scaled if necessary */ if (icon->width == 48) { draw_sprite(_tmp, icon, 0, 0); } else { draw_sprite_scaled(_tmp, icon, 0, 0, 48, 48); } free(_tmp); draw_sprite_alpha(ctx, _tmp_s, APP_OFFSET + i + w - 48 - 2, 0, 0.7); sprite_free(_tmp_s); } { sprite_t * _tmp_s = create_sprite(w, PANEL_HEIGHT, ALPHA_EMBEDDED); gfx_context_t * _tmp = init_graphics_sprite(_tmp_s); draw_fill(_tmp, rgba(0,0,0,0)); draw_sdf_string(_tmp, 0, 0, s, 16, rgb(0,0,0), SDF_FONT_THIN); blur_context_box(_tmp, 4); free(_tmp); draw_sprite(ctx, _tmp_s, APP_OFFSET + i + 2, TEXT_Y_OFFSET + 2); sprite_free(_tmp_s); } if (title_width > MIN_TEXT_WIDTH) { /* Then draw the window title, with appropriate color */ if (j == focused_app) { /* Current hilighted - title should be a light blue */ draw_sdf_string(ctx, APP_OFFSET + i + 2, TEXT_Y_OFFSET + 2, s, 16, HILIGHT_COLOR, SDF_FONT_THIN); } else { if (ad->flags & 1) { /* Top window should be white */ draw_sdf_string(ctx, APP_OFFSET + i + 2, TEXT_Y_OFFSET + 2, s, 16, FOCUS_COLOR, SDF_FONT_THIN); } else { /* Otherwise, off white */ draw_sdf_string(ctx, APP_OFFSET + i + 2, TEXT_Y_OFFSET + 2, s, 16, txt_color, SDF_FONT_THIN); } } } /* XXX This keeps track of how far left each window list item is * so we can map clicks up in the mouse callback. */ if (j < MAX_WINDOW_COUNT) { if (ads_by_l[j]) { ads_by_l[j]->left = APP_OFFSET + i; } } j++; i += w; } } spin_unlock(&lock); /* Draw the logout button; XXX This should probably have some sort of focus hilight */ draw_sprite_alpha_paint(ctx, sprite_logout, width - 23, 1, 1.0, (logout_menu->window ? HILIGHT_COLOR : ICON_COLOR)); /* Logout button */ /* Flip */ flip(ctx); yutani_flip(yctx, panel); spin_unlock(&drawlock); } static void update_window_list(void) { yutani_query_windows(yctx); list_t * new_window_list = list_create(); int i = 0; while (1) { /* We wait for a series of WINDOW_ADVERTISE messsages */ yutani_msg_t * m = yutani_wait_for(yctx, YUTANI_MSG_WINDOW_ADVERTISE); struct yutani_msg_window_advertise * wa = (void*)m->data; if (wa->size == 0) { /* A sentinal at the end will have a size of 0 */ free(m); break; } /* Store each window advertisement */ struct window_ad * ad = malloc(sizeof(struct window_ad)); char * s = malloc(wa->size); memcpy(s, wa->strings, wa->size); ad->name = &s[wa->offsets[0]]; ad->icon = &s[wa->offsets[1]]; ad->strings = s; ad->flags = wa->flags; ad->wid = wa->wid; ads_by_z[i] = ad; i++; ads_by_z[i] = NULL; node_t * next = NULL; /* And insert it, ordered by wid, into the window list */ foreach(node, new_window_list) { struct window_ad * n = node->value; if (n->wid > ad->wid) { next = node; break; } } if (next) { list_insert_before(new_window_list, next, ad); } else { list_insert(new_window_list, ad); } free(m); } active_window = i-1; i = 0; /* * Update each of the wid entries in our array so we can map * clicks to window focus events for each window */ foreach(node, new_window_list) { struct window_ad * ad = node->value; if (i < MAX_WINDOW_COUNT) { ads_by_l[i] = ad; ads_by_l[i+1] = NULL; } i++; } /* Then free up the old list and replace it with the new list */ spin_lock(&lock); if (new_window_list->length) { int tmp = LEFT_BOUND; tmp -= APP_OFFSET; if (tmp < 0) { title_width = 0; } else { title_width = tmp / new_window_list->length; if (title_width > MAX_TEXT_WIDTH) { title_width = MAX_TEXT_WIDTH; } if (title_width < MIN_TEXT_WIDTH) { title_width = 0; } } } else { title_width = 0; } if (window_list) { foreach(node, window_list) { struct window_ad * ad = (void*)node->value; free(ad->strings); free(ad); } list_free(window_list); free(window_list); } window_list = new_window_list; spin_unlock(&lock); /* And redraw the panel */ redraw(); } static void resize_finish(int xwidth, int xheight) { yutani_window_resize_accept(yctx, panel, xwidth, xheight); reinit_graphics_yutani(ctx, panel); yutani_window_resize_done(yctx, panel); width = xwidth; /* Draw the background */ draw_fill(ctx, rgba(0,0,0,0)); for (int i = 0; i < xwidth; i += sprite_panel->width) { draw_sprite(ctx, sprite_panel, i, 0); } /* Copy the prerendered background so we can redraw it quickly */ bg_size = panel->width * panel->height * sizeof(uint32_t); bg_blob = realloc(bg_blob, bg_size); memcpy(bg_blob, ctx->backbuffer, bg_size); update_window_list(); redraw(); } static void bind_keys(void) { /* Cltr-Alt-T = launch terminal */ yutani_key_bind(yctx, 't', KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* Alt+Tab = app switcher*/ yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT, YUTANI_BIND_STEAL); /* Ctrl-F11 = toggle panel visibility */ yutani_key_bind(yctx, KEY_F11, KEY_MOD_LEFT_CTRL, YUTANI_BIND_STEAL); /* Alt+F1 = show menu */ yutani_key_bind(yctx, KEY_F1, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* Alt+F2 = show app runner */ yutani_key_bind(yctx, KEY_F2, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* Alt+F3 = window context menu */ yutani_key_bind(yctx, KEY_F3, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* This lets us receive all just-modifier key releases */ yutani_key_bind(yctx, KEY_LEFT_ALT, 0, YUTANI_BIND_PASSTHROUGH); } static void sig_usr2(int sig) { yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP); yutani_flip(yctx, panel); bind_keys(); signal(SIGUSR2, sig_usr2); } const char * month_names[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; int days_in_months[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, }; void _menu_draw_MenuEntry_Calendar(gfx_context_t * ctx, struct MenuEntry * self, int offset) { self->offset = offset; char lines[9][22]; memset(lines, 0, sizeof(lines)); struct timeval now; gettimeofday(&now, NULL); struct tm actual; struct tm * timeinfo; timeinfo = localtime((time_t *)&now.tv_sec); memcpy(&actual, timeinfo, sizeof(struct tm)); timeinfo = &actual; char month[20]; sprintf(month, "%s %d", month_names[timeinfo->tm_mon], timeinfo->tm_year + 1900); int len = (20 - strlen(month)) / 2; while (len > 0) { strcat(lines[0]," "); len--; } strcat(lines[0],month); /* Days of week */ strcat(lines[1],"Su Mo Tu We Th Fr Sa"); int days_in_month = days_in_months[timeinfo->tm_mon]; if (days_in_month == 0) { /* How many days in February? */ struct tm tmp; memcpy(&tmp, timeinfo, sizeof(struct tm)); tmp.tm_mday = 29; tmp.tm_hour = 12; time_t tmp3 = mktime(&tmp); struct tm * tmp2 = localtime(&tmp3); if (tmp2->tm_mday == 29) { days_in_month = 29; } else { days_in_month = 28; } } int mday = timeinfo->tm_mday; int wday = timeinfo->tm_wday; /* 0 == sunday */ while (mday > 1) { mday--; wday = (wday + 6) % 7; } for (int i = 0; i < wday; ++i) { strcat(lines[2]," "); } int line = 2; while (mday <= days_in_month) { /* TODO Bold text? */ char tmp[5]; sprintf(tmp, "%2d ", mday); strcat(lines[line], tmp); if (wday == 6) line++; mday++; wday = (wday + 1) % 7; } self->height = 16 * (line+1) + 8; /* Go through each and draw with monospace font */ for (int i = 0; i < 9; ++i) { if (lines[i][0] != 0) { draw_sdf_string(ctx, 10, 4 + i * 17, lines[i], 16, rgb(0,0,0), i == 0 ? SDF_FONT_MONO_BOLD : SDF_FONT_MONO); } } } /* * Special menu entry to display a calendar */ struct MenuEntry * menu_create_calendar(void) { struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */ out->_type = -1; /* Special */ out->height = 16 * 9 + 8; out->rwidth = draw_sdf_string_width("XX XX XX XX XX XX XX", 16, SDF_FONT_MONO) + 20; out->renderer = _menu_draw_MenuEntry_Calendar; return out; } int main (int argc, char ** argv) { if (argc < 2 || strcmp(argv[1],"--really")) { fprintf(stderr, "%s: Desktop environment panel / dock\n" "\n" " Renders the application menu, window list, widgets,\n" " alt-tab window switcher, clock, etc.\n" " You probably don't want to run this directly - it is\n" " started automatically by the session manager.\n", argv[0]); return 1; } /* Connect to window server */ yctx = yutani_init(); /* For convenience, store the display size */ width = yctx->display_width; height = yctx->display_height; /* Create the panel window */ panel = yutani_window_create_flags(yctx, width, PANEL_HEIGHT, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS); /* And move it to the top layer */ yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP); /* Initialize graphics context against the window */ ctx = init_graphics_yutani_double_buffer(panel); /* Clear it out (the compositor should initialize it cleared anyway */ draw_fill(ctx, rgba(0,0,0,0)); flip(ctx); yutani_flip(yctx, panel); /* Load textures for the background and logout button */ sprite_panel = malloc(sizeof(sprite_t)); sprite_logout = malloc(sizeof(sprite_t)); load_sprite(sprite_panel, "/usr/share/panel.bmp"); sprite_panel->alpha = ALPHA_EMBEDDED; load_sprite(sprite_logout, "/usr/share/icons/panel-shutdown.bmp"); sprite_logout->alpha = ALPHA_FORCE_SLOW_EMBEDDED; struct stat stat_tmp; if (!stat("/dev/dsp",&stat_tmp)) { widgets_volume_enabled = 1; widgets_width += WIDGET_WIDTH; sprite_volume_mute = malloc(sizeof(sprite_t)); sprite_volume_low = malloc(sizeof(sprite_t)); sprite_volume_med = malloc(sizeof(sprite_t)); sprite_volume_high = malloc(sizeof(sprite_t)); load_sprite(sprite_volume_mute, "/usr/share/icons/24/volume-mute.bmp"); sprite_volume_mute->alpha = ALPHA_FORCE_SLOW_EMBEDDED; load_sprite(sprite_volume_low, "/usr/share/icons/24/volume-low.bmp"); sprite_volume_low->alpha = ALPHA_FORCE_SLOW_EMBEDDED; load_sprite(sprite_volume_med, "/usr/share/icons/24/volume-medium.bmp"); sprite_volume_med->alpha = ALPHA_FORCE_SLOW_EMBEDDED; load_sprite(sprite_volume_high, "/usr/share/icons/24/volume-full.bmp"); sprite_volume_high->alpha = ALPHA_FORCE_SLOW_EMBEDDED; /* XXX store current volume */ } { widgets_network_enabled = 1; widgets_width += WIDGET_WIDTH; sprite_net_active = malloc(sizeof(sprite_t)); load_sprite(sprite_net_active, "/usr/share/icons/24/net-active.bmp"); sprite_net_active->alpha = ALPHA_FORCE_SLOW_EMBEDDED; sprite_net_disabled = malloc(sizeof(sprite_t)); load_sprite(sprite_net_disabled, "/usr/share/icons/24/net-disconnected.bmp"); sprite_net_disabled->alpha = ALPHA_FORCE_SLOW_EMBEDDED; } /* TODO Probably should use the app launch shortcut */ system("sh -c \"sleep 4; weather-tool\" &"); /* Draw the background */ for (int i = 0; i < width; i += sprite_panel->width) { draw_sprite(ctx, sprite_panel, i, 0); } /* Copy the prerendered background so we can redraw it quickly */ bg_size = panel->width * panel->height * sizeof(uint32_t); bg_blob = malloc(bg_size); memcpy(bg_blob, ctx->backbuffer, bg_size); /* Catch SIGINT */ signal(SIGINT, sig_int); signal(SIGUSR2, sig_usr2); appmenu = menu_set_get_root(menu_set_from_description("/etc/panel.menu", launch_application_menu)); calmenu = menu_create(); menu_insert(calmenu, menu_create_calendar()); window_menu = menu_create(); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Maximize", _window_menu_start_maximize)); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Move", _window_menu_start_move)); menu_insert(window_menu, menu_create_separator()); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Close", _window_menu_close)); logout_menu = menu_create(); menu_insert(logout_menu, menu_create_normal("exit", "log-out", "Log Out", launch_application_menu)); /* Subscribe to window updates */ yutani_subscribe_windows(yctx); /* Ask compositor for window list */ update_window_list(); /* Key bindings */ bind_keys(); time_t last_tick = 0; int fds[1] = {fileno(yctx->sock)}; while (_continue) { int index = fswait2(1,fds,200); if (index == 0) { /* Respond to Yutani events */ yutani_msg_t * m = yutani_poll(yctx); while (m) { menu_process_event(yctx, m); switch (m->type) { /* New window information is available */ case YUTANI_MSG_NOTIFY: update_window_list(); break; /* Mouse movement / click */ case YUTANI_MSG_WINDOW_MOUSE_EVENT: panel_check_click((struct yutani_msg_window_mouse_event *)m->data); break; case YUTANI_MSG_KEY_EVENT: handle_key_event((struct yutani_msg_key_event *)m->data); break; case YUTANI_MSG_WELCOME: { struct yutani_msg_welcome * mw = (void*)m->data; width = mw->display_width; height = mw->display_height; yutani_window_resize(yctx, panel, mw->display_width, PANEL_HEIGHT); } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; default: break; } free(m); m = yutani_poll_async(yctx); } } else { struct timeval now; gettimeofday(&now, NULL); if (now.tv_sec != last_tick) { last_tick = now.tv_sec; waitpid(-1, NULL, WNOHANG); update_volume_level(); update_network_status(); update_weather_status(); redraw(); } } } /* Close the panel window */ yutani_close(yctx, panel); /* Stop notifying us of window changes */ yutani_unsubscribe_windows(yctx); return 0; }