// flit.cpp -- Flit is a "tray" with clock, sound, and battery applets // Version 1.2.0 Rounded up version number for public release // Version 1.1.7 Made size scaling work with floating-point factor // Version 1.1.6 Support size scaling, fixed hide/show bug // Version 1.1.5 Compile-time option for OLPC hardware battery support // Version 1.1.0 ALSA sound control support // Version 1.0.0 Let's call this major level done! No real code changes. // Version 0.9.9 Handle case where battery rate is "unknown" (or some other non-numeric) // by allowing calc to proceed using derived rate (as introduced in ver 0.9.3/0.9.4) // Version 0.9.8 Made the automatic pop-open of menu with Ctrl optional, // Start in unfocused input state // Version 0.9.7 Removed Esc keybinding to open menu, added '%' to batt. icon // Version 0.9.6 Changed format of % statements in tooltips and diag. output // Changed Alt+(key) bindings to Ctrl+(key) // Version 0.9.5 Prevent unhiding sound control if no sound system available, // Alt key automatically opens menu (cheap way to improve key-binding) // Surpress charge/discharge time estimates for 3 minutes to build up data // Version 0.9.4 Fixed scaling factors used in effective charging/discharging rate // Version 0.9.3 Added effective charging/discharging rate calculation in cases // when ACPI does not provide one. // Version 0.9.2 added support for keyboard shortcuts to open right-click menu // and to control sound using arror keys, or +/- keys, or through menu. // Also, the Pause key will mute/unmute sound. // Version 0.9.1 added support for all (hopefully) likely OSS volume parameter types // and a new .flit.conf param: oss_control_name to favor a specific // OSS control parameter, (e.g. pcm, vol, or vmix0-outvol) // Note: vmix controls will not work unless the vmix device is fully // "attached" to your hardware. // Version 0.9.0 added support for MIXT_STEREODB OSS parameter type // Version 0.8.9 rearranged some code to make transparent style work better. // Version 0.8.8 switches to direct OSS API (ioctl() stuff) for sound management. // Hopefully, it will support a wider variety of sound hardware. // Also, this version peeks under the actual intended location // of the flit window to determine the "transparent" color. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "soundcard.h" // UNCOMMENT TO SUPPORT OLPC-1 BATTERY MONITOR #define OLPC // UNCOMMENT TO PRINT DIAGNOSTIC MESSAGES //#define DIAG // This adds a bit of gloss to the icons #define VANITY 1 #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a):(b)) #endif #define APP_VER "1.2.0" // Last update 2010-05-04 #define METER_W 28 #define SOUND_W 28 #define CLOCK_W 64 #define SIZE_H 20 #define SIZE_W (METER_W + SOUND_W + CLOCK_W) #define SECONDS_PER_HOUR 3600 #define SAMPLES_PER_HOUR 720 #define MIN_SAMPLES_BEFORE_ESTIMATION 36 #define BATTERY_BASE_DIR "/proc/acpi/battery/" #define PATH_TO_FLIT_HELP "/usr/share/doc/tc/flit_help.htm" const char About_text[] = "Flit version %s\n" "copyright 2010 by Michael A. Losh\n" "\n" "Flit is an applet 'tray', with clock, sound control, and battery monitor.\n" "\n" "Flit is free software: you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation, version 3.\n" "\n" "Flit is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "See http://www.gnu.org/licenses/ for more details."; int Base_t_sec; int Running = 1; enum { MI_ABOUT, MI_HELP, MI_CLOCK, MI_SHOW24HR, MI_SOUND, MI_SOUND_MUTE, MI_SOUND_UP, MI_SOUND_DOWN, MI_BATTERY, MI_HOTKEY_TOGGLE, MI_NORMAL_STYLE, MI_INVERSE_STYLE, MI_TRANSPARENT_STYLE, MI_SAVE_CONFIG, MI_QUIT}; static void MenuCB(Fl_Widget* window_p, void *userdata); Fl_Menu_Item right_click_menu[18] = { {"&About", FL_CTRL+'a', MenuCB, (void*)MI_ABOUT}, {"&Help", FL_CTRL+'h', MenuCB, (void*)MI_HELP,FL_MENU_DIVIDER}, {"Show/hide cloc&k", FL_CTRL+'k', MenuCB, (void*)MI_CLOCK}, {"&Toggle 12/24 hr. disp.", FL_CTRL+'t', MenuCB, (void*)MI_SHOW24HR, FL_MENU_DIVIDER}, {"Show/hide soun&d", FL_CTRL+'d', MenuCB, (void*)MI_SOUND}, {"&Mute/unmute", FL_CTRL+'m', MenuCB, (void*)MI_SOUND_MUTE}, {"&Up the sound", FL_CTRL+'u', MenuCB, (void*)MI_SOUND_UP}, {"&Lower the sound", FL_CTRL+'l', MenuCB, (void*)MI_SOUND_DOWN, FL_MENU_DIVIDER}, {"Show/hide &battery meter", FL_CTRL+'b', MenuCB, (void*)MI_BATTERY, FL_MENU_DIVIDER}, {"Toggle Ctrl key menu activation", FL_CTRL+'y', MenuCB, (void*)MI_HOTKEY_TOGGLE}, {"&Normal style", FL_CTRL+'n', MenuCB, (void*)MI_NORMAL_STYLE}, {"&Inverse style", FL_CTRL+'i', MenuCB, (void*)MI_INVERSE_STYLE}, {"Trans&parent style", FL_CTRL+'p', MenuCB, (void*)MI_TRANSPARENT_STYLE, FL_MENU_DIVIDER}, {"&Save configuration", FL_CTRL+'s', MenuCB, (void*)MI_SAVE_CONFIG, FL_MENU_DIVIDER}, {"&Quit", FL_CTRL+'q', MenuCB, (void*)MI_QUIT}, {0} }; float Z = 1.0; // Zoom - Size Scaling factor uchar Fg_r; // Normal forground color, typically black uchar Fg_g; uchar Fg_b; uchar Bg_r; // Normal background color, often gray uchar Bg_g; uchar Bg_b; uchar RW_r = 0x40; // X11 root window background color uchar RW_g = 0x50; uchar RW_b = 0x80; enum {BATT_STATE_UNK, BATT_STATE_CHARGED, BATT_STATE_CHARGING, BATT_STATE_DISCHARGING}; enum {NORMAL_STYLE = 0, INVERSE_STYLE = 1, TRANSPARENT_STYLE = 2}; enum {LOC_SE, LOC_SW, LOC_NW, LOC_NE, LOC_CUST}; #ifdef OLPC enum {BATT_SYSTEM_ACPI, BATT_SYSTEM_OLPC}; int BatterySystemType = BATT_SYSTEM_ACPI; #define OLPC_BATT_CAP_DEVFILE "/sys/devices/platform/olpc-battery.0/power_supply/olpc-battery/capacity" //#define OLPC_BATT_CAP_DEVFILE "/home/tc/projects/flit/capacity" #define OLPC_BATT_AC_DEVFILE "/sys/devices/platform/olpc-battery.0/power_supply/olpc-ac/online" //#define OLPC_BATT_AC_DEVFILE "/home/tc/projects/flit/online" #endif class Flit_Batt_Meter : public Fl_Box { int level; int toggle; public: int show_plug; Flit_Batt_Meter(int x, int y, int w, int h, const char * l = 0) : Fl_Box(x, y, w, h, l), show_plug(1), level(0), toggle(0) {}; int value(void) { return level;}; void value(int n) { level = n; toggle = !toggle; redraw(); } void draw() { fl_color(FL_FOREGROUND_COLOR); if (show_plug) { label(""); } Fl_Box::draw(); if (show_plug) { fl_rectf(4 * Z, 4 *Z, 10 * Z, 2 * Z); fl_polygon(12*Z,3*Z, 15*Z,2*Z, 15*Z,7*Z, 12*Z,6*Z ); fl_line(15*Z, 3*Z, 18*Z, 3*Z); fl_line(15*Z, 6*Z, 18*Z, 6*Z); } if (level < 13 && toggle % 2 == 0 && !show_plug) { fl_color(FL_RED); // outline in red every other update } fl_rectf(2*Z, 10*Z, 22*Z, 8*Z); fl_line (24*Z, 12*Z, 24*Z, 15*Z); fl_color(FL_BLUE); fl_rectf(3*Z, 11*Z, 20*Z, 6*Z); fl_line (23*Z, 13*Z, 23*Z, 14*Z); #ifdef VANITY // A concession to vanity: a bit of highlight fl_color(0x80, 0x80, 0xFF); // light blue fl_line(4*Z, 12*Z, 22*Z, 12*Z); #endif if (level > 40) { fl_color(FL_GREEN); } else if (level > 20) { fl_color(FL_YELLOW); } else { fl_color(FL_RED); } int charge_w = (level + 2) / 5; // + 2 does a little rounding fl_rectf(3*Z, 11*Z, charge_w*Z, 6*Z); #ifdef VANITY if (level > 40) { fl_color(0x80, 0xFF, 0x80); // light green } else if (level > 20) { fl_color(0xFF, 0xFF, 0x80); // light yellow } else { fl_color(0xFF, 0x80, 0x80); // light red } fl_line(3*Z, 12*Z, (2 + charge_w)*Z, 12*Z); #endif } }; class Flit_Sound_Control : public Fl_Box { int level; int toggle; char tip_txt[16]; public: int show_plug; Flit_Sound_Control(int x, int y, int w, int h, const char * l = 0) : Fl_Box(x, y, w, h, l), level(91) {}; int value(void) { return level;}; void value(int n) { level = n; redraw(); sprintf(tip_txt, "Vol. %d", value()); tooltip(tip_txt); #ifdef DIAG printf("Sound ctrl. new value is %d\n", level); #endif }; void draw() { Fl_Box::draw(); fl_color(0xA0, 0xA0, 0xB0); // Medium blue-gray fl_rectf(x() + 5*Z, 7*Z, 3*Z, 6*Z); // coil color fl_polygon(x()+8*Z,6*Z, x()+12*Z,2*Z, x()+12*Z,18*Z, x()+8*Z,14*Z ); // horn color fl_color(FL_FOREGROUND_COLOR); fl_rect(x() + 4*Z, 6*Z, 4*Z, 8*Z); // coil outline fl_loop(x()+8*Z,6*Z, x()+12*Z,2*Z, x()+12*Z,18*Z, x()+8*Z,14*Z ); // horn outline fl_loop(x()+13*Z,7*Z, x()+14*Z,9*Z, x()+14*Z,11*Z, x()+13*Z,13*Z ); // dome outline fl_color(0x40, 0x40, 0x40); // dark gray fl_loop(x()+13*Z,7*Z, x()+14*Z,9*Z, x()+14*Z,11*Z, x()+13*Z,13*Z ); // dome outline if (level < 1 ) { fl_color(FL_RED); fl_arc(x()+4*Z, 2*Z, 16*Z, 16*Z, 0.0, 359.9); fl_arc(x()+5*Z, 3*Z, 14*Z, 14*Z, 0.0, 359.9); fl_line(x()+7*Z, 5*Z, x()+16*Z, 14*Z); fl_line(x()+8*Z, 5*Z, x()+17*Z, 14*Z); } #ifdef VANITY // A concession to vanity: a bit of highlight fl_color(0xE0, 0xE0, 0xE0); // light gray fl_line(x()+5*Z, 9*Z, x()+7*Z, 9*Z); fl_line(x()+9*Z, 7*Z, x()+11*Z, 5*Z); #endif fl_color(Bg_r + Fg_r / 2, Bg_g + Fg_g / 2, Bg_b + Fg_b / 2); if (level > 0) { fl_color(FL_FOREGROUND_COLOR); fl_line(x() + 16*Z, 15*Z, x() + 18*Z, 17*Z); } if (level >= 10) { fl_line(x() + 18*Z, 17*Z, x() + 19*Z, 19*Z); } if (level >= 20) { fl_line(x() + 17*Z, 13*Z, x() + 19*Z, 14*Z); } if (level >= 30) { fl_line(x() + 19*Z, 14*Z, x() + 21*Z, 15*Z); } if (level >= 40) { fl_line(x() + 17*Z, 11*Z, x() + 19*Z, 11*Z); } if (level >= 50) { fl_line(x() + 19*Z, 11*Z, x() + 22*Z, 11*Z); } if (level >= 60) { fl_line(x() + 17*Z, 9*Z, x() + 19*Z, 8*Z); } if (level >= 70) { fl_line(x() + 19*Z, 8*Z, x() + 21*Z, 7*Z); } if (level >= 80) { fl_line(x() + 16*Z, 6*Z, x() + 18*Z, 4*Z); } if (level >= 90) { fl_line(x() + 18*Z, 4*Z, x() + 20*Z, 2*Z); } } }; enum { SOUND_TYPE_OSS, SOUND_TYPE_ALSA}; class Flit_Frame : public Fl_Window { protected: int size_w; int size_h; int win_loc; int style; int win_x; int win_y; char configfilename[256]; int show_sound; int sound_type; Flit_Sound_Control* sound_p; int prev_sound_vol; int mixer_fd; int mix_ctrl; int mix_min; int mix_max; char sound_control_name[64]; char alsa_amixer_path[128]; int show_clock; Fl_Box* clock_p; int show24hr; char timestr[16]; char ampm[4]; time_t last_update_time; int show_battery; Flit_Batt_Meter* meter_p; int samples; float capacity_mAh; float charge_mAh; float orig_charge_mAh; float avgcur_mA; float charge_pct; float remaining_hrs; int batt_state; char meter_str[8]; char meter_tip[64]; char time_tip[64]; char batt_state_filename[128]; public: int menu_hotkey_activation; void set_color(Fl_Color c) { if (meter_p) meter_p->labelcolor(c); if (sound_p) sound_p->labelcolor(c); if (clock_p) clock_p->labelcolor(c); } void set_boxtype(Fl_Boxtype boxtype) { if (meter_p) meter_p->box(boxtype); if (sound_p) sound_p->box(boxtype); if (clock_p) clock_p->box(boxtype); redraw(); } Flit_Frame(const char *title = 0) : Fl_Window(10, 10, 100, 20, title), show24hr(0), capacity_mAh(999999.0), charge_mAh(999999.0), orig_charge_mAh(99999.0), avgcur_mA(1), charge_pct(100.0), remaining_hrs(9999.0), batt_state(BATT_STATE_UNK), samples(0), sound_type(SOUND_TYPE_OSS), show_clock(1), show_sound(1), show_battery(1), meter_p(NULL), sound_p(NULL), clock_p(NULL), size_w(0), size_h(SIZE_H), win_loc(LOC_SE), style(NORMAL_STYLE), win_x(10), win_y(10), prev_sound_vol(-1), mixer_fd(0), mix_min(0), mix_max(100), mix_ctrl(-1), menu_hotkey_activation(1), last_update_time(0) { Base_t_sec = time(NULL); strcpy(sound_control_name, "autosel"); }; void get_time(void) { time_t now = time(NULL); struct tm* timep = localtime(&now); if (show24hr) { strcpy(ampm, ""); } else { if (timep->tm_hour>=12) { strcpy(ampm, " PM"); } else { strcpy(ampm, " AM"); } } int hour = timep->tm_hour; if (!show24hr) { hour = hour % 12; if (hour < 1) hour = 12; } sprintf(timestr, "%d:%02d%s", hour, timep->tm_min, ampm); clock_p->label(timestr); strcpy(time_tip, ctime(&now)); clock_p->tooltip(time_tip); clock_p->redraw(); }; void get_sound_info(void) { int c; int success = 0; int autosel = 0; // Whether Flit should pick a control oss_mixext info; mix_ctrl = 0; autosel = 0; if (!strcmp(sound_control_name, "autosel") ) { printf("Will autoselect sound control parameter\n"); autosel = 1; } if(!mixer_fd) { mixer_fd = open("/dev/mixer", O_RDWR); if (-1 != mixer_fd) { oss_mixerinfo mi; mi.dev = -1; if (-1 != ioctl(mixer_fd, SNDCTL_MIXERINFO, &mi)) { #ifdef DIAG printf("Mixer device is '%s', priority %d\n", mi.name, mi.priority); #endif } else { perror("SNDCTL_MIXERINFO ioctl()"); } int mix_ctls = 0; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_NREXT, &mix_ctls)) { #ifdef DIAG printf("There are %d controls in the mixer\n", mix_ctls); #endif } for (c = 0 ; c < mix_ctls ; c++ ) { info.dev = 0; info.ctrl = c; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_EXTINFO, &info)) { #ifdef DIAG printf("--->\t%d\tConsidering volume control item: '%s', type %d, flags 0x%X\n", c, info.extname, info.type, info.flags); #endif if ( !success && ( !strcmp(info.extname, sound_control_name) // found the specified OSS control? || ( autosel == 1 && ( (info.flags & MIXF_MAINVOL) // OSS thinks it can control volume || (info.flags & MIXF_PCMVOL) // '' ) ) ) ) { success = 1; mix_ctrl = c; mix_max = info.maxvalue; mix_min = info.minvalue; #ifdef DIAG printf("===>\t%d\tSelected volume control item: '%s'\n", c, info.extname); printf("===>\t%d\tmin %d, max %d,\n", c, mix_min, mix_min); #else break; // quit after the first one that seems to match #endif } #ifdef DIAG else { printf("\t\t\t(not selected)\n"); } #endif } else perror("SNDCTL_MIX_EXTINFO ioctl()"); } // end for } #ifdef DIAG else { perror("/dev/mixer open()"); printf("\n"); } #endif } if (!success) { // Check for ALSA sound #ifdef DIAG printf("Looking for ALSA sound...\n"); #endif char potential_sound_control[48]; FILE* which_pf = 0; *alsa_amixer_path = '\0'; *potential_sound_control = '\0'; which_pf = popen("which amixer", "r"); if(fgets(alsa_amixer_path, 127, which_pf)) { #ifdef DIAG printf("AMIXER path is %s\n", alsa_amixer_path); #endif } fclose(which_pf); if (strlen(alsa_amixer_path) > 1) { sound_type = SOUND_TYPE_ALSA; FILE* amixerpf = NULL; char cmd[128]; char buf[512]; char *s; int need_autosel = autosel; int need_lims = 1; int newval; int minval = -1; int maxval = -1; if (need_autosel) { sprintf(cmd, "amixer", sound_control_name); } else { sprintf(cmd, "amixer sget %s", sound_control_name); } amixerpf = popen(cmd, "r"); if (amixerpf) { while(!feof(amixerpf) && fgets(buf, 511, amixerpf)) { // look for limits s = strstr(buf, "Limits:"); if (need_lims && s && 2 == sscanf(s, "Limits: Playback %d - %d", &minval, &maxval)) { mix_min = minval; mix_max = maxval; #ifdef DIAG printf("Sound volume ranges from %d to %d\n", mix_min, mix_max); #endif success = 1; need_lims = 0; continue; } s = strstr(buf, "Simple mixer control"); if (need_autosel && s && 1 == sscanf(s, "Simple mixer control '%[^']", potential_sound_control)) { #ifdef DIAG printf("Considering potential sound control '%s'\n", potential_sound_control); #endif continue; } s = strstr(buf, "pvolume"); if (need_autosel && s && strlen(potential_sound_control) > 0) { strcpy(sound_control_name, potential_sound_control); #ifdef DIAG printf("Accepted sound countrol '%s'\n", sound_control_name); #endif need_autosel = 0; continue; } else { *potential_sound_control = '\0'; // blank any current candidate out continue; } } fclose(amixerpf); } } else { #ifdef DIAG printf("ALSA amixer not found!\n"); #endif } } if (!success) { printf("No sound found!\n"); show_sound = 0; mixer_fd = 0; } } void get_sound_level(void) { oss_mixext info; oss_mixer_value vr; int pct; int rawval; if (sound_type == SOUND_TYPE_OSS && mixer_fd && mix_ctrl) { info.dev = 0; info.ctrl = mix_ctrl; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_EXTINFO, &info)) { vr.dev=info.dev; vr.ctrl=info.ctrl; vr.timestamp=info.timestamp; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_READ, &vr)) { switch(info.type) { case MIXT_MONOSLIDER: case MIXT_STEREOSLIDER: rawval = vr.value & 0xFF; break; case MIXT_MONOSLIDER16: case MIXT_STEREOSLIDER16: rawval = vr.value & 0x7FFF; break; case MIXT_MUTE: rawval = (vr.value) ? mix_max : mix_min; break; case MIXT_SLIDER: // 32-bit default: // Maybe MIXT_VALUE, MIXT_HEXVALUE, MIXT_MONODB, MIXT_STEREODB rawval = vr.value; break; } pct = (int)(100.0 * (float)(rawval - mix_min) / (float)mix_max); #ifdef DIAG printf("Read 0x%08X from control, raw volume is %d or %d %%\n", vr.value, rawval, pct); #endif sound_p->value(pct); } else perror("SNDCTL_MIX_READ ioctl()"); } else perror("SNDCTL_MIX_EXTINFO ioctl()"); } else if (sound_type = SOUND_TYPE_ALSA) { FILE* amixerpf = NULL; char buf[512]; char *s; int lvol = -1; int rvol = -1; int v; char cmd[128]; sprintf(cmd, "amixer sget %s", sound_control_name); amixerpf = popen(cmd, "r"); if (amixerpf) { while(!feof(amixerpf) && fgets(buf, 511, amixerpf)) { s = strstr(buf, "Front Left: Playback"); if (s && 1 == sscanf(s, "Front Left: Playback %d", & v)) { lvol = v; continue; } s = strstr(buf, "Front Right: Playback"); if (s && 1 == sscanf(s, "Front Right: Playback %d", & v)) { rvol = v; } if (lvol >= 0 && rvol >= 0 && (mix_max > mix_min)) { pct = (int)(((float)(rvol + lvol)) * 100.0 / (2.0 * (float)(mix_max - mix_min))); #ifdef DIAG printf("Sound level is %d %%\n", pct); #endif sound_p->value(pct); break; } } fclose(amixerpf); } } else { #ifdef DIAG printf("No sound device or suitable control\n"); #endif } }; void set_sound_level(int vol) { oss_mixext info; oss_mixer_value vr; int newvol; int intended; newvol = (int)((float)(vol * mix_max) / 100.0) - mix_min; if (sound_type = SOUND_TYPE_OSS && mixer_fd && mix_ctrl) { info.dev = 0; info.ctrl = mix_ctrl; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_EXTINFO, &info)) { vr.dev=info.dev; vr.ctrl=info.ctrl; vr.timestamp=info.timestamp; switch(info.type) { case MIXT_MONOSLIDER: case MIXT_STEREOSLIDER: vr.value = (newvol << 8) | newvol; break; case MIXT_MONOSLIDER16: case MIXT_STEREOSLIDER16: vr.value = (newvol << 16) | newvol; break; case MIXT_MUTE: vr.value = (newvol) ? mix_max : mix_min; break; case MIXT_SLIDER: // 32-bit default: // Maybe MIXT_VALUE, MIXT_HEXVALUE, MIXT_MONODB, MIXT_STEREODB vr.value = newvol; break; } intended = vr.value; if (-1 != ioctl(mixer_fd, SNDCTL_MIX_WRITE, &vr)) { #ifdef DIAG printf("Written value is 0x%08X, intended value 0x%08X (for %d %%)\n", vr.value, intended, vol); #endif if (vr.value == intended) { sound_p->value(vol); } else { // find out what the value is, actually get_sound_level(); } } else perror("SNDCTL_MIX_WRITE ioctl()"); } else perror("SNDCTL_MIX_EXTINFO ioctl()"); } else if (sound_type = SOUND_TYPE_ALSA) { FILE* amixerpf = NULL; char cmd[128]; char buf[512]; char *s; int minval, maxval; int lvol = -1; int rvol = -1; int v; int actual_pct; sprintf(cmd, "amixer sset %s %d", sound_control_name, newvol); #ifdef DIAG printf("********************\nCmd for intended %d pct: '%s'\n", vol, cmd); #endif amixerpf = popen(cmd, "r"); if (amixerpf) { while(!feof(amixerpf) && fgets(buf, 511, amixerpf)) { s = strstr(buf, "Front Left: Playback"); if (s && 1 == sscanf(s, "Front Left: Playback %d", & v)) { lvol = v; continue; } s = strstr(buf, "Front Right: Playback"); if (s && 1 == sscanf(s, "Front Right: Playback %d", & v)) { rvol = v; } if (lvol >= 0 && rvol >= 0 && (mix_max > mix_min)) { actual_pct = (rvol + lvol) * 100.0 / (2.0 * (mix_max - mix_min)); #ifdef DIAG printf("Sound level after change is actually %d %%\n", actual_pct); #endif sound_p->value(actual_pct); break; } } fclose(amixerpf); } } else { #ifdef DIAG printf("No sound device or suitable control\n"); #endif } }; void get_battery_info(void) { char dirname[32] = ""; char buf[80] = ""; DIR* bd = NULL; // batt. directory file ptr FILE* bif = NULL; // batt. info file ptr struct dirent* dp; #ifdef OLPC bif = fopen(OLPC_BATT_CAP_DEVFILE, "r"); if (bif) { BatterySystemType = BATT_SYSTEM_OLPC; fclose(bif); return; } else { BatterySystemType = BATT_SYSTEM_ACPI; } #endif bd = opendir(BATTERY_BASE_DIR); while (bd && (dp = readdir(bd))) { if (*(dp->d_name) == '.') continue; if (strcmp(dp->d_name, dirname) < 0 || strlen(dirname) == 0) { strcpy(dirname, dp->d_name); // keep first alphabetically if more than one } } closedir(bd); if (!strlen(dirname)) { meter_p->label("\nAC"); return; } sprintf(batt_state_filename, "%s%s/state", BATTERY_BASE_DIR, dirname); // save for later sprintf(buf, "%s%s/info", BATTERY_BASE_DIR, dirname); // format batt. info filename bif = fopen(buf, "r"); float cap_mAh = 0.0; if (bif) { while(fgets(buf, 79, bif)) { if (1 == sscanf(buf, "last full capacity: %f mAh\n", &cap_mAh)) { capacity_mAh = cap_mAh; #ifdef DIAG printf("Battery capacity is %1.1f mAh\n", capacity_mAh); #endif break; } } fclose(bif); } else { meter_p->label("\nAC"); meter_p->show_plug = 1; } }; void update_battery_meter(void) { int hours = (int)floor(remaining_hrs); int mins = (int)((remaining_hrs - hours)*60.0); sprintf(meter_str, "%1.f%%\n-",charge_pct); if (batt_state == BATT_STATE_CHARGED) { strcpy(meter_tip, "Fully charged"); } else if (samples < MIN_SAMPLES_BEFORE_ESTIMATION) { sprintf(meter_tip, "%1.f %%", charge_pct); } else { sprintf(meter_tip, "%1.f %%, about %dhr%s, %dmin%s %s", charge_pct, hours, (hours==1) ? "":"s", mins, (mins==1) ? "":"s", (batt_state == BATT_STATE_CHARGING) ? "to charge":"remaining" ); } meter_p->label(meter_str); meter_p->tooltip(meter_tip); meter_p->value((int)floor(charge_pct)); meter_p->redraw(); return; } void get_battery_state(void) { int need_state = 1; int need_charge = 1; int want_rate = 1; char buf[80]; char status[20]; float chg_mAh = 0.0; float rate_mA = 0.0; float mA = 1; float ratio = 0.0; FILE* bsf = NULL; FILE* bif = NULL; // batt. info file ptr struct dirent* dp; #ifdef OLPC // vvvvvvvvvvvvvvvvvvvvv static int cap_initialized = 0; static unsigned long int t_of_prev_pcnt = 0; // when the cap. percetage last changed; static int prev_cap_pcnt = 0; static float rate_pcnt = 100.0 / (3.0 * 3600.0); // just assume 3 hrs of charging/discharging now static int prev_ac_present = 0; int cap_pcnt = -1; int cap_pcnt_change = 0; int interval_t = 0; if (BatterySystemType == BATT_SYSTEM_OLPC) { bsf = fopen(OLPC_BATT_CAP_DEVFILE, "r"); if (bsf) { fscanf(bsf, "%d", &cap_pcnt); if (cap_pcnt >= 0) { if (cap_initialized) { cap_pcnt_change = cap_pcnt - prev_cap_pcnt; if (cap_pcnt_change != 0) { interval_t = time(NULL) - t_of_prev_pcnt; } } charge_pct = ((float) cap_pcnt ) * 100.0 / 97.0; if (charge_pct > 100.0) { charge_pct = 100.0; } #ifdef DIAG printf("OLPC raw charge percentage is %d%%, rescaled to %1.0f %%\n", cap_pcnt, charge_pct); #endif } samples++; fclose(bsf); } #ifdef DIAG else { printf("ERROR: Cannot check battery charge status!\n"); } #endif FILE* acif = NULL; // AC info file ptr int charging = -1; acif = fopen(OLPC_BATT_AC_DEVFILE, "r"); if (acif) { fscanf(acif, "%d", &charging); meter_p->show_plug = 0; // assume discharging if (charging == 1) { if (prev_ac_present != 1) { samples = 0; // state change, don't report time est. for a while rate_pcnt = 0.0; } prev_ac_present = 1; meter_p->show_plug = 1; batt_state = BATT_STATE_CHARGING; if (interval_t > 0) { // do some low-pass filtering on rate of change rate_pcnt = ((7.0 * rate_pcnt )+ ((float)cap_pcnt_change / (float)interval_t)) / 8.0; } if (fabs(rate_pcnt) > 0.00000) { remaining_hrs = (100.0 - charge_pct) / (3600.0 * rate_pcnt); } #ifdef DIAG printf("cap_pcnt_change is %d interval_t is %d, rate_pcnt is %1.3f, cap_pcnt is %d, remaining_hrs is %1.4f\n", cap_pcnt_change, interval_t, rate_pcnt, cap_pcnt, remaining_hrs); #endif if (charge_pct >= 99.0) { batt_state = BATT_STATE_CHARGED; remaining_hrs = 0.0; } } else if (charging == 0) { if (prev_ac_present != 0) { samples = 0; // state change, don't report time est. for a while rate_pcnt = 0.0; } prev_ac_present = 0; batt_state = BATT_STATE_DISCHARGING; if (interval_t > 0) { // Change rate slowly to match recent "instantaneous" rate rate_pcnt = ((7.0 * rate_pcnt )+ ((float)cap_pcnt_change / (float)interval_t)) / 8.0; } if (fabs(rate_pcnt) > 0.00000) { remaining_hrs = charge_pct / (-3600.0 * rate_pcnt); } #ifdef DIAG printf("cap_pcnt_change is %d interval_t is %d, rate_pcnt is %1.3f, cap_pcnt is %d, remaining_hrs is %1.4f\n", cap_pcnt_change, interval_t, rate_pcnt, cap_pcnt, remaining_hrs); #endif } else { batt_state = BATT_STATE_UNK; prev_ac_present = -1; } if (cap_initialized == 0 || cap_pcnt_change != 0) { t_of_prev_pcnt = time(NULL); prev_cap_pcnt = cap_pcnt; cap_initialized = 1; } fclose (acif); } update_battery_meter(); return; } #endif // OLPC ^^^^^^^^^^^^^ bsf = fopen(batt_state_filename, "r"); if (bsf) { while(fgets(buf, 79, bsf)) { if (need_state && 1 == sscanf(buf, "charging state: %s\n", status)) { need_state = 0; if (!strcmp(status, "discharging")) { if (batt_state != BATT_STATE_DISCHARGING) samples = 0; batt_state = BATT_STATE_DISCHARGING; } else if (!strcmp(status, "charging")) { if (batt_state != BATT_STATE_CHARGING) samples = 0; batt_state = BATT_STATE_CHARGING; } else if (!strcmp(status, "charged")) { batt_state = BATT_STATE_CHARGED; } else { batt_state = BATT_STATE_UNK; } } else if (want_rate && 1 == sscanf(buf, "present rate: %f mA\n", &rate_mA)) { want_rate = 0; if (rate_mA > 32767.0) { rate_mA = 65536.0 - rate_mA; // handle negative for charging } } else if (need_charge && 1 == sscanf(buf, "remaining capacity: %f mAh\n", &chg_mAh)) { need_charge = 0; charge_mAh = chg_mAh; charge_pct = 100.0 * charge_mAh / capacity_mAh; if (samples == 0) orig_charge_mAh = charge_mAh; break; } } fclose(bsf); if (!need_charge && !need_state) { samples++; #ifdef DIAG printf("Sample %d at time %d sec after start\n", samples, time(NULL) - Base_t_sec); #endif if (samples == 1) { #ifdef DIAG printf("Setting avg. current to just this sample\n"); #endif avgcur_mA = rate_mA; } else if (samples < 30) { // For about the first 3 mins, favor new data #ifdef DIAG printf("Favoring new data 2/3 to avg 1/3\n"); #endif avgcur_mA = ((2.0*rate_mA) + avgcur_mA ) / 3.0; } else { // Weighted average of past (15/16) and now (1/16), acts as a low-pass filter #ifdef DIAG printf("Favoring old data 15/16 to new 1/16\n"); #endif avgcur_mA = ((15.0 * avgcur_mA) + rate_mA) / 16.0; } meter_p->show_plug = 1; // assume we need to show it if (batt_state == BATT_STATE_CHARGING) { if (rate_mA < 1.0) { // Calculate charging rate since ACPI did not provide rate_mA = (charge_mAh - orig_charge_mAh) * 720.0 / (float)samples; if (rate_mA < 1) { rate_mA = 1; } } remaining_hrs = (capacity_mAh - charge_mAh) / rate_mA; // Charging time is not very accurate because charge // rate slows down as the battery fills, so to // provide a rough approximation, we use a couple // of correction factors // TODO: Replace with a self-calibrating function? if (charge_pct< 0.90) { remaining_hrs *= 2.6; } else { remaining_hrs *= 1.6; } } if (batt_state == BATT_STATE_DISCHARGING) { if (fabs(avgcur_mA) > 1.0) { remaining_hrs = charge_mAh / avgcur_mA; } else { // Calculate discharging rate since ACPI did not provide rate_mA = (orig_charge_mAh - charge_mAh) * 720.0 / (float)samples; if (rate_mA < 1) { rate_mA = 1; } remaining_hrs = charge_mAh / rate_mA; } meter_p->show_plug = 0; // don't show if we are discharging! } update_battery_meter(); #ifdef DIAG printf("Charge/discharge state is %s (flit code %d)\n", status, batt_state); printf("Current is %1.f mA, average current is %1.1f mA\n", rate_mA, avgcur_mA); printf("Charge level was %1.f mAh at sample zero\n", orig_charge_mAh); printf("Charge level is %1.f mAh or %1.1f %% of %1.f possible\n", charge_mAh, charge_pct, capacity_mAh); printf("Remaining hours: %1.2f\n", remaining_hrs); printf("---------------------------------------------------\n"); #endif } } else { meter_p->label("??"); } meter_p->redraw(); }; void toggle_show24hr(void) { show24hr = !show24hr; get_time(); redraw(); }; void do_menu(void) { const Fl_Menu_Item *m = right_click_menu->popup(Fl::event_x(), Fl::event_y(), "Flit", 0, 0); if ( m ) m->do_callback(this, m->user_data()); }; // -1 for quieter, +1 for louder, 0 for toggle void do_sound_adj(int direction) { int vol; vol = sound_p->value(); if (direction < 0) { prev_sound_vol = vol; vol -= 5; if (vol < 0) vol = 0; } else if (direction > 0) { prev_sound_vol = vol; vol += 5; if (vol > 100) vol = 100; } else { // direction is 0, so toggle if (sound_p->value() > 0) { prev_sound_vol = vol; vol = 0; } else { vol = prev_sound_vol; } } set_sound_level(vol); sound_p->redraw(); Fl_Tooltip::delay(0); // force tooltip on Fl_Tooltip::exit(sound_p); Fl_Tooltip::enter(sound_p); Fl_Tooltip::delay(1); } int handle(int e) { static int off_x = 0; static int off_y = 0; int key; switch ( e ) { case FL_KEYDOWN: key = Fl::event_key(); if (menu_hotkey_activation && (key == FL_Control_L || key == FL_Control_R)) { do_menu(); return 1; } break; case FL_KEYUP: key = Fl::event_key(); if (show_sound && (key == FL_Up || key == '=')) { // '=' is also + key do_sound_adj(+1); return 1; } else if (show_sound && (key == FL_Down || key == '-')) { do_sound_adj(-1); return 1; } else if (show_sound && key == FL_Pause) { do_sound_adj(0); return 1; } break; case FL_PUSH: if (Fl::event_button() == FL_LEFT_MOUSE) { if (show_sound && Fl::event_clicks() && Fl::event_x() > sound_p->x() && Fl::event_x() < (sound_p->x() + SOUND_W)) { do_sound_adj(0); // zero means toggle return 1; } else { off_x = Fl::event_x(); off_y = Fl::event_y(); } } else if (Fl::event_button() == FL_RIGHT_MOUSE ) { do_menu(); return(1); // (tells caller we handled this event) } break; case FL_DRAG: if ( Fl::event_button() == FL_LEFT_MOUSE ) { win_x = Fl::event_x_root() - off_x; win_y = Fl::event_y_root() - off_y; position(win_x, win_y); win_loc = LOC_CUST; redraw(); return 1; } case FL_MOUSEWHEEL: if (show_sound && Fl::event_x() > sound_p->x() && Fl::event_x() < (sound_p->x() + SOUND_W)) { do_sound_adj(-1 * Fl::event_dy()); return 1; } default: break; } return(Fl_Window::handle(e)); }; void update(void) { time_t now = time(NULL); if (now - last_update_time >= 5) { if (show_clock) get_time(); if (show_sound) get_sound_level(); if (show_battery) get_battery_state(); last_update_time = now; } if (show_sound && prev_sound_vol < 0) { prev_sound_vol = sound_p->value(); } redraw(); } void get_config() { char confline[128]; char s[64]; int n; float f; int xpos, ypos; char* env_home_p = getenv("HOME"); sprintf(configfilename, "%s/.flit.conf", env_home_p); FILE* cfgf = NULL; cfgf = fopen(configfilename, "r"); if (cfgf) { while (fgets(confline, 127, cfgf)) { if (1 == sscanf(confline, "show_clock = %d\n", &n)) { #ifdef DIAG printf("show_clock is %d\n", n); #endif show_clock = n; } else if (1 == sscanf(confline, "show_sound = %d\n", &n)) { #ifdef DIAG printf("show_sound is %d\n", n); #endif show_sound = n; } else if (1 == sscanf(confline, "show_battery = %d\n", &n)) { #ifdef DIAG printf("show_battery is %d\n", n); #endif show_battery = n; } else if (1 == sscanf(confline, "menu_hotkey_activation = %d\n", &n)) { #ifdef DIAG printf("menu_hotkey_activation is %d\n", n); #endif menu_hotkey_activation = n; } else if (1 == sscanf(confline, "style = %d\n", &n)) { #ifdef DIAG printf("style is %d\n", n); #endif style = n; } else if (1 == sscanf(confline, "zoom = %f\n", &f)) { #ifdef DIAG printf("zoom is %1.2f\n", f); #endif Z = f; } else if (1 == sscanf(confline, "sound_control_name = %s\n", s)) { if (strlen(s) > 0 && strlen(s) < 63) { strcpy(sound_control_name, s); } #ifdef DIAG printf("sound_control_name is %s\n", sound_control_name); #endif } else if (1 == sscanf(confline, "show24hr = %d\n", &n)) { #ifdef DIAG printf("show24hr is %d\n", n); #endif show24hr = n; } else if (1 == sscanf(confline, "location = %s\n", s)) { #ifdef DIAG printf("Location spec is %s\n", s); #endif if (!strcmp(s, "se")) { win_loc = LOC_SE; } else if (!strcmp(s, "sw")) { win_loc = LOC_SW; } else if (!strcmp(s, "nw")) { win_loc = LOC_NW; } else if (!strcmp(s, "ne")) { win_loc = LOC_NE; } else { if (2 == sscanf(s, "%d,%d", &xpos, &ypos)) { win_x = xpos; win_y = ypos; win_loc = LOC_CUST; } } } // end location } // end while more lines. } else { #ifdef DIAG printf("No config file found: using defaults.\n"); #endif } }; // end get_config() void arrange(void) { size_h = SIZE_H * Z; size_w = 0; begin(); if (show_battery) { if (!meter_p) { meter_p = new Flit_Batt_Meter(0, 0, METER_W * Z, SIZE_H * Z); get_battery_info(); meter_p->labelsize(8 * Z); } meter_p->position(size_w, 0); size_w += METER_W * Z; } else if (meter_p) { meter_p->position(size_w, 0); } if (show_sound) { if (!sound_p) { sound_p = new Flit_Sound_Control(0, 0, SOUND_W * Z, SIZE_H * Z); sound_p->labelsize(8 * Z); } sound_p->position(size_w, 0); size_w += SOUND_W * Z; } else if (sound_p) { sound_p->position(0, 0); } if (show_clock) { if (!clock_p) { clock_p = new Fl_Box(0, 0, CLOCK_W * Z, SIZE_H * Z); get_time(); clock_p->labelsize(12 * Z); } clock_p->position(size_w, 0); size_w += CLOCK_W * Z; } else if (clock_p) { clock_p->position(0, 0); } end(); switch(win_loc) { case LOC_SW: win_x = 0; win_y = Fl::h() - size_h; break; case LOC_NW: win_x = 0; win_y = 0; break; case LOC_NE: win_x = Fl::w() - size_w; win_y = 0; break; case LOC_SE: win_x = Fl::w() - size_w; win_y = Fl::h() - size_h; break; } resize(win_x, win_y, size_w, size_h); redraw(); }; void toggle_show_clock(void) { if (show_clock && (show_battery + show_sound)) // can't disable clock unless one other is showing { show_clock = 0; if (clock_p) clock_p->hide(); } else if (!show_clock) { // can always re-enable clock show_clock = 1; if (clock_p) clock_p->show(); } arrange(); set_style(style); }; void toggle_show_sound(void) { if (show_sound && (show_clock + show_battery)) // can't disable sound ctrl unless one other is showing { show_sound = 0; mixer_fd = 0; if (sound_p) sound_p->hide(); } else if (!show_sound) { // can always re-enable sound show_sound = 1; if (sound_p) sound_p->show(); get_sound_info(); // checks for availability if (!show_sound) { if (sound_p) sound_p->hide(); fl_alert("No sound system (e.g. OSS) found.\n"); } } arrange(); set_style(style); }; void toggle_show_batt(void) { if (show_battery && (show_sound + show_clock)) // can't disable battery meter unless one other is showing { show_battery = 0; if (meter_p) meter_p->hide(); } else if (!show_battery) { // can always re-enable battery show_battery = 1; if (meter_p) meter_p->show(); } arrange(); set_style(style); }; void set_style(int newstyle) { int bg_r, bg_g, bg_b, fg_r, fg_g, fg_b; style = newstyle; switch(style) { case NORMAL_STYLE: Fl::set_color(FL_BACKGROUND_COLOR, Bg_r, Bg_g, Bg_b); Fl::set_color(FL_FOREGROUND_COLOR, Fg_r, Fg_g, Fg_b); set_boxtype(FL_THIN_DOWN_BOX); break; case INVERSE_STYLE: Fl::set_color(FL_BACKGROUND_COLOR, Fg_r, Fg_g, Fg_b); Fl::set_color(FL_FOREGROUND_COLOR, Bg_r, Bg_g, Bg_b); set_boxtype(FL_THIN_DOWN_BOX); break; case TRANSPARENT_STYLE: bg_r = MIN(0xFF, RW_r + 50); bg_g = MIN(0xFF, RW_g + 50); bg_b = MIN(0xFF, RW_b + 50); fg_r = Fg_r; fg_g = Fg_g; fg_b = Fg_b; if (bg_r + bg_g + bg_b < 0xA0) { // we have a fairly dark background, so foreground color // needs to be fairly light fg_r = fg_g = fg_b = 0xA0; } Fl::set_color(FL_BACKGROUND_COLOR, bg_r, bg_g, bg_b); Fl::set_color(FL_FOREGROUND_COLOR, fg_r, fg_g, fg_b); set_boxtype(FL_FLAT_BOX); break; } redraw(); }; void get_background_color(void) { Window rootw; XImage * xip = NULL; rootw = DefaultRootWindow(fl_display); // Take a peek at the X11 root window, so we can find out it's color xip = XGetImage(fl_display, rootw, win_x, win_y, 1, 1, -1, ZPixmap); // 1x1 pixel screen grab if (xip) { RW_r = (uchar)xip->data[2]; // This may only work with true-color color modes RW_g = (uchar)xip->data[1]; RW_b = (uchar)xip->data[0]; #ifdef DIAG printf("Root RGB colors are %02X:%02X:%02x\n", RW_r, RW_g, RW_b); #endif } } void configure(void) { get_config(); clear_border(); box(FL_NO_BOX); if (show_sound) get_sound_info(); arrange(); get_background_color(); Fl::get_color(FL_FOREGROUND_COLOR, Fg_r, Fg_g, Fg_b); Fl::get_color(FL_BACKGROUND_COLOR, Bg_r, Bg_g, Bg_b); set_style(style); }; void save_config(void) { const char *loc_tag[] = {"se", "sw", "nw", "ne"}; FILE* cfgf = NULL; cfgf = fopen(configfilename, "w"); if (cfgf) { fprintf(cfgf, "# Configuration file for Flit \n\n"); fprintf(cfgf, "# Note: this is a machine-generated file; if you change any\n"); fprintf(cfgf, "# content, preserve spelling, capitalization, and spacing!\n\n"); fprintf(cfgf, "# Enable (1) or disable (0) individual applets\n"); fprintf(cfgf, "show_clock = %d\n", show_clock); fprintf(cfgf, "show_sound = %d\n", show_sound); fprintf(cfgf, "show_battery = %d\n", show_battery); fprintf(cfgf, "\n"); fprintf(cfgf, "# Show clock in 24 hour format (1 means yes, 0 means no - use AM/PM)\n"); fprintf(cfgf, "show24hr = %d\n", show24hr); fprintf(cfgf, "\n"); fprintf(cfgf, "# OSS/ALSA sound control name to use, autosel means 'try to pick for me'\n"); fprintf(cfgf, "sound_control_name = %s\n", sound_control_name); fprintf(cfgf, "\n"); fprintf(cfgf, "# Automatically pop up right-click menu when Ctrl key is first pressed (1 = do, 0 = don't)\n"); fprintf(cfgf, "menu_hotkey_activation = %d\n", menu_hotkey_activation); fprintf(cfgf, "\n"); fprintf(cfgf, "# Color style: 0 is normal, 1 is inverted, 2 is 'transparent' (based on root window color)\n"); fprintf(cfgf, "style = %d\n", style); fprintf(cfgf, "\n"); fprintf(cfgf, "# Zoom size: 1.0 is normal, 1.50 is %150, etc.\n"); fprintf(cfgf, "zoom = %1.2f\n", Z); fprintf(cfgf, "\n"); fprintf(cfgf, "# Location: se (Southeast, i.e. lower right), sw, nw, ne, or a custom X,Y like this example:\n"); fprintf(cfgf, "#location = %d,%d\n", win_x, win_y); if (win_loc == LOC_CUST) { fprintf(cfgf, "location = %d,%d\n", win_x, win_y); } else { fprintf(cfgf, "location = %s\n", loc_tag[win_loc]); } fprintf(cfgf, "\n"); fclose(cfgf); } } }; static void MenuCB(Fl_Widget* window_p, void *userdata) { Fl_Help_Dialog hd; long choice = (long)userdata; Flit_Frame* mainwnd_p = (Flit_Frame *)window_p; switch (choice) { case MI_ABOUT: fl_message(About_text, APP_VER); break; case MI_HELP: hd.load(PATH_TO_FLIT_HELP); hd.textsize(14); hd.show(); while (hd.visible()) { Fl::wait(1); } break; case MI_CLOCK: mainwnd_p->toggle_show_clock(); break; case MI_SHOW24HR: mainwnd_p->toggle_show24hr(); break; case MI_BATTERY: mainwnd_p->toggle_show_batt(); break; case MI_SOUND: mainwnd_p->toggle_show_sound(); break; case MI_SOUND_MUTE: mainwnd_p->do_sound_adj(0); // Toggle sound on/off break; case MI_SOUND_UP: mainwnd_p->do_sound_adj(+1); break; case MI_SOUND_DOWN: mainwnd_p->do_sound_adj(-1); break; case MI_HOTKEY_TOGGLE: mainwnd_p->menu_hotkey_activation = !(mainwnd_p->menu_hotkey_activation); break; case MI_NORMAL_STYLE: mainwnd_p->set_style(NORMAL_STYLE); break; case MI_INVERSE_STYLE: mainwnd_p->set_style(INVERSE_STYLE); break; case MI_TRANSPARENT_STYLE: mainwnd_p->set_style(TRANSPARENT_STYLE); break; case MI_SAVE_CONFIG: mainwnd_p->save_config(); break; case MI_QUIT: Running = 0; break; default: break; } } int main(int argc, char** argv) { int rc; fl_open_display(); fl_message_font(fl_font(), 12 * Z); Flit_Frame MainWnd("Flit"); MainWnd.configure(); MainWnd.update(); MainWnd.show(); //XSetInputFocus(fl_display, PointerRoot, RevertToPointerRoot, CurrentTime); while(Running) { Fl::wait(5.0); MainWnd.update(); } //MainWnd.save_config(); return 0; // Fl::run(); }