/** * @file main * */ /********************* * INCLUDES *********************/ #define _DEFAULT_SOURCE /* needed for usleep() */ #include #include #define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/ #include #include "lvgl/lvgl.h" //#include "lvgl/examples/lv_examples.h" //#include "lv_demos/lv_demo.h" #include "lv_drivers/display/monitor.h" #include "lv_drivers/indev/mouse.h" #include "lv_drivers/indev/keyboard.h" #include "lv_drivers/indev/mousewheel.h" // get PineTime header #include "displayapp/lv_pinetime_theme.h" #include #include #include "BootloaderVersion.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" #include "components/brightness/BrightnessController.h" #include "components/motor/MotorController.h" #include "components/datetime/DateTimeController.h" #include "components/heartrate/HeartRateController.h" #include "components/fs/FS.h" #include "drivers/Spi.h" #include "drivers/SpiMaster.h" #include "drivers/SpiNorFlash.h" #include "drivers/St7789.h" #include "drivers/TwiMaster.h" #include "drivers/Cst816s.h" #include "drivers/PinMap.h" #include "systemtask/SystemTask.h" #include "drivers/PinMap.h" #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" // get the simulator-headers #include "displayapp/DisplayApp.h" #include "displayapp/LittleVgl.h" #include #include #include #include #include // std::pow /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static void hal_init(void); static int tick_thread(void *data); /********************** * STATIC VARIABLES **********************/ lv_indev_t *kb_indev; lv_indev_t *mouse_indev = nullptr; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {} /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * VARIABLES **********************/ /********************** * STATIC PROTOTYPES **********************/ /********************** * GLOBAL FUNCTIONS **********************/ constexpr NRF_TWIM_Type *NRF_TWIM1 = nullptr; static constexpr uint8_t touchPanelTwiAddress = 0x15; static constexpr uint8_t motionSensorTwiAddress = 0x18; static constexpr uint8_t heartRateSensorTwiAddress = 0x44; Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0, {Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb, Pinetime::Drivers::SpiMaster::Modes::Mode3, Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz, Pinetime::PinMap::SpiSck, Pinetime::PinMap::SpiMosi, Pinetime::PinMap::SpiMiso}}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand}; Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; // The TWI device should work @ up to 400Khz but there is a HW bug which prevent it from // respecting correct timings. According to erratas heet, this magic value makes it run // at ~390Khz with correct timings. static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug {0x06200000}; Pinetime::Drivers::TwiMaster twiMaster {NRF_TWIM1, MaxTwiFrequencyWithoutHardwareBug, Pinetime::PinMap::TwiSda, Pinetime::PinMap::TwiScl}; Pinetime::Drivers::Cst816S touchPanel; // {twiMaster, touchPanelTwiAddress}; //#ifdef PINETIME_IS_RECOVERY // #include "displayapp/DummyLittleVgl.h" // #include "displayapp/DisplayAppRecovery.h" //#else // #include "displayapp/LittleVgl.h" // #include "displayapp/DisplayApp.h" //#endif Pinetime::Components::LittleVgl lvgl {lcd, touchPanel}; Pinetime::Drivers::Bma421 motionSensor {twiMaster, motionSensorTwiAddress}; Pinetime::Drivers::Hrs3300 heartRateSensor {twiMaster, heartRateSensorTwiAddress}; TimerHandle_t debounceTimer; TimerHandle_t debounceChargeTimer; Pinetime::Controllers::Battery batteryController; Pinetime::Controllers::Ble bleController; Pinetime::Controllers::HeartRateController heartRateController; Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController); Pinetime::Controllers::FS fs; // {spiNorFlash}; Pinetime::Controllers::Settings settingsController {fs}; Pinetime::Controllers::MotorController motorController {}; Pinetime::Controllers::DateTime dateTimeController {settingsController}; Pinetime::Drivers::Watchdog watchdog; Pinetime::Drivers::WatchdogView watchdogView(watchdog); Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::TimerController timerController; Pinetime::Controllers::AlarmController alarmController {dateTimeController}; Pinetime::Controllers::TouchHandler touchHandler(touchPanel, lvgl); Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; Pinetime::Applications::DisplayApp displayApp(lcd, lvgl, touchPanel, batteryController, bleController, dateTimeController, watchdogView, notificationManager, heartRateController, settingsController, motorController, motionController, timerController, alarmController, brightnessController, touchHandler); Pinetime::System::SystemTask systemTask(spi, lcd, spiNorFlash, twiMaster, touchPanel, lvgl, batteryController, bleController, dateTimeController, timerController, alarmController, watchdog, notificationManager, motorController, heartRateSensor, motionController, motionSensor, settingsController, heartRateController, displayApp, heartRateApp, fs, touchHandler, buttonHandler); // variable used in SystemTask.cpp Work loop std::chrono::time_point NoInit_BackUpTime; class Framework { public: // Contructor which initialize the parameters. Framework(bool visible_, int height_, int width_) : visible(visible_), height(height_), width(width_) { if (visible) { //SDL_Init(SDL_INIT_VIDEO); // Initializing SDL as Video SDL_CreateWindowAndRenderer(width, height, 0, &window, &renderer); SDL_SetWindowTitle(window, "LV Simulator Status"); { // move window a bit to the right, to not be over the PineTime Screen int x,y; SDL_GetWindowPosition(window, &x, &y); SDL_SetWindowPosition(window, x+LV_HOR_RES_MAX, y); } SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); // setting draw color SDL_RenderClear(renderer); // Clear the newly created window SDL_RenderPresent(renderer); // Reflects the changes done in the // window. } motorController.Init(); settingsController.Init(); lvgl.Init(); lv_mem_monitor(&mem_mon); printf("initial free_size = %u\n", mem_mon.free_size); // update time to current system time once on startup dateTimeController.SetCurrentTime(std::chrono::system_clock::now()); systemTask.Start(); // initialize the first LVGL screen //const auto clockface = settingsController.GetClockFace(); //switch_to_screen(1+clockface); } // Destructor ~Framework(){ SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); } void draw_circle_red(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_green(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_blue(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_yellow(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_grey(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_white(int center_x, int center_y, int radius_){ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); draw_circle_(center_x, center_y, radius_); } void draw_circle_(int center_x, int center_y, int radius_){ // Drawing circle for(int x=center_x-radius_; x<=center_x+radius_; x++){ for(int y=center_y-radius_; y<=center_y+radius_; y++){ if((std::pow(center_y-y,2)+std::pow(center_x-x,2)) <= std::pow(radius_,2)){ SDL_RenderDrawPoint(renderer, x, y); } } } } void refresh() { // always refresh the LVGL screen this->refresh_screen(); if (!visible) { return; } SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); { // motorController.is_ringing constexpr const int center_x = 15; constexpr const int center_y = 15; if (motorController.is_ringing) { draw_circle_red(center_x, center_y, 15); } else { draw_circle_grey(center_x, center_y, 15); } } { // motorController.motor_is_running constexpr const int center_x = 45; constexpr const int center_y = 15; if (motorController.motor_is_running) { draw_circle_red(center_x, center_y, 15); } else { draw_circle_grey(center_x, center_y, 15); } } { // ble.motor_is_running constexpr const int center_x = 75; constexpr const int center_y = 15; if (bleController.IsConnected()) { draw_circle_blue(center_x, center_y, 15); } else { draw_circle_grey(center_x, center_y, 15); } } // batteryController.percentRemaining for (uint8_t percent=0; percent<=10; percent++) { const int center_x = 15+15*percent; const int center_y = 60; if (batteryController.percentRemaining < percent*10) { draw_circle_grey(center_x, center_y, 15); } else { draw_circle_green(center_x, center_y, 15); } } { // batteryController.isCharging constexpr const int center_x = 15; constexpr const int center_y = 90; if (batteryController.isCharging) { draw_circle_yellow(center_x, center_y, 15); } else { draw_circle_grey(center_x, center_y, 15); } } { // brightnessController.Level constexpr const int center_y = 15; const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level(); uint8_t level_idx = 0; if (level == Pinetime::Controllers::BrightnessController::Levels::Low) { level_idx = 1; } else if (level == Pinetime::Controllers::BrightnessController::Levels::Medium) { level_idx = 2; } else if (level == Pinetime::Controllers::BrightnessController::Levels::High) { level_idx = 3; } for (uint8_t i=0; i<4; i++) { const int center_x = 115+15*i; if (i <= level_idx) { draw_circle_white(center_x, center_y, 15); } else { draw_circle_grey(center_x, center_y, 15); } } } // Show the change on the screen SDL_RenderPresent(renderer); } // prepared notficitions, one per message category const std::vector notification_messages { "category:\nUnknown", "Lorem ipsum\ndolor sit amet,\nconsectetur adipiscing elit,\n", "SimpleAlert", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Email:", "Vitae aliquet nec ullamcorper sit amet.", "News:", "Id\naliquet\nrisus\nfeugiat\nin\nante\nmetus\ndictum\nat.", "IncomingCall:", "Ut porttitor leo a diam sollicitudin.", "MissedCall:", "Ultrices tincidunt arcu non sodales neque sodales ut etiam sit.", "Sms:", "Pellentesque dignissim enim sit amet.", "VoiceMail:", "Urna nec tincidunt praesent semper feugiat nibh sed pulvinar proin.", "Schedule:", "Tellus id interdum velit laoreet id donec ultrices tincidunt.", "HighProriotyAlert:", "Viverra maecenas accumsan lacus vel facilisis volutpat est velit egestas.", "InstantMessage:", "Volutpat consequat mauris nunc congue.", }; size_t notification_idx = 0; // which message to send next void send_notification() { Pinetime::Controllers::NotificationManager::Notification notif; const std::string &title = notification_messages.at(notification_idx*2); const std::string &message = notification_messages.at(notification_idx*2+1); std::copy(title.begin(), title.end(), notif.message.data()); notif.message[title.size()] = '\0'; // title and message is \0 separated std::copy(message.begin(), message.end(), notif.message.data()+title.size()+1); notif.message[title.size() + 1 + message.size()] = '\0'; // zero terminate the message notif.size = title.size() + 1 + message.size(); notif.category = static_cast(notification_idx % 11); notificationManager.Push(std::move(notif)); // send next message the next time notification_idx++; if (notification_idx >= notification_messages.size()) { notification_idx = 0; } } // can't use SDL_PollEvent, as those are fed to lvgl // implement a non-descructive key-pressed handler (as not consuming SDL_Events) void handle_keys() { const Uint8 *state = SDL_GetKeyboardState(NULL); const bool key_shift = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT]; auto debounce = [&] (const char key_low, const char key_capital, const bool scancode, bool &key_handled) { if (scancode && !key_handled) { if (key_shift) { this->handle_key(key_capital); } else { this->handle_key(key_low); } key_handled = true; } else if (scancode && key_handled) { // ignore, already handled } else { key_handled = false; } }; debounce('r', 'R', state[SDL_SCANCODE_R], key_handled_r); debounce('n', 'N', state[SDL_SCANCODE_N], key_handled_n); debounce('m', 'M', state[SDL_SCANCODE_M], key_handled_m); debounce('b', 'B', state[SDL_SCANCODE_B], key_handled_b); debounce('v', 'V', state[SDL_SCANCODE_V], key_handled_v); debounce('c', 'C', state[SDL_SCANCODE_C], key_handled_c); debounce('l', 'L', state[SDL_SCANCODE_L], key_handled_l); debounce('p', 'P', state[SDL_SCANCODE_P], key_handled_p); debounce('s', 'S', state[SDL_SCANCODE_S], key_handled_s); debounce('h', 'H', state[SDL_SCANCODE_H], key_handled_h); // screen switcher buttons debounce('1', '!'+1, state[SDL_SCANCODE_1], key_handled_1); debounce('2', '!'+2, state[SDL_SCANCODE_2], key_handled_2); debounce('3', '!'+3, state[SDL_SCANCODE_3], key_handled_3); debounce('4', '!'+4, state[SDL_SCANCODE_4], key_handled_4); debounce('5', '!'+5, state[SDL_SCANCODE_5], key_handled_5); debounce('6', '!'+6, state[SDL_SCANCODE_6], key_handled_6); debounce('7', '!'+7, state[SDL_SCANCODE_7], key_handled_7); debounce('8', '!'+8, state[SDL_SCANCODE_8], key_handled_8); debounce('9', '!'+9, state[SDL_SCANCODE_9], key_handled_9); debounce('0', '!'+0, state[SDL_SCANCODE_0], key_handled_0); } // modify the simulated controller depending on the pressed key void handle_key(SDL_Keycode key) { if (key == 'r') { motorController.StartRinging(); } else if (key == 'R') { motorController.StopRinging(); } else if (key == 'm') { motorController.RunForDuration(100); } else if (key == 'M') { motorController.RunForDuration(255); } else if (key == 'n') { send_notification(); } else if (key == 'N') { notificationManager.ClearNewNotificationFlag(); } else if (key == 'b') { bleController.Connect(); } else if (key == 'B') { bleController.Disconnect(); } else if (key == 'v') { if (batteryController.percentRemaining >= 90) { batteryController.percentRemaining = 100; } else { batteryController.percentRemaining += 10; } } else if (key == 'V') { if (batteryController.percentRemaining <= 10) { batteryController.percentRemaining = 0; } else { batteryController.percentRemaining -= 10; } } else if (key == 'c') { batteryController.isCharging = true; batteryController.isPowerPresent = true; } else if (key == 'C') { batteryController.isCharging = false; batteryController.isPowerPresent = false; } else if (key == 'l') { brightnessController.Higher(); } else if (key == 'L') { brightnessController.Lower(); } else if (key == 'p') { this->print_memory_usage = true; } else if (key == 'P') { this->print_memory_usage = false; } else if (key == 's') { motionSensor.steps += 500; } else if (key == 'S') { if (motionSensor.steps > 500) { motionSensor.steps -= 500; } else { motionSensor.steps = 0; } } else if (key == 'h') { if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::Stopped) { heartRateController.Start(); } else if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::NotEnoughData) { heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, 10); } else { uint8_t heartrate = heartRateController.HeartRate(); heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, heartrate + 10); } } else if (key == 'H') { heartRateController.Stop(); } else if (key >= '0' && key <= '9') { this->switch_to_screen(key-'0'); } else if (key >= '!'+0 && key <= '!'+9) { this->switch_to_screen(key-'!'+10); } batteryController.voltage = batteryController.percentRemaining * 50; } void handle_touch_and_button() { int x, y; uint32_t buttons = SDL_GetMouseState(&x, &y); const bool left_click = (buttons & SDL_BUTTON_LMASK) != 0; const bool right_click = (buttons & SDL_BUTTON_RMASK) != 0; if (left_click) { left_release_sent = false; systemTask.OnTouchEvent(); return; } else { if (!left_release_sent) { left_release_sent = true; systemTask.OnTouchEvent(); return; } } if (right_click != right_last_state) { right_last_state =right_click; systemTask.PushMessage(Pinetime::System::Messages::HandleButtonEvent); return; } } // helper function to switch between screens void switch_to_screen(uint8_t screen_idx) { if (screen_idx == 1) { settingsController.SetClockFace(0); displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 2) { settingsController.SetClockFace(1); displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 3) { settingsController.SetClockFace(2); displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 4) { displayApp.StartApp(Pinetime::Applications::Apps::Paddle, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 5) { displayApp.StartApp(Pinetime::Applications::Apps::Twos, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 6) { displayApp.StartApp(Pinetime::Applications::Apps::Metronome, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 7) { displayApp.StartApp(Pinetime::Applications::Apps::FirmwareUpdate, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 8) { displayApp.StartApp(Pinetime::Applications::Apps::BatteryInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 9) { displayApp.StartApp(Pinetime::Applications::Apps::FlashLight, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 0) { displayApp.StartApp(Pinetime::Applications::Apps::QuickSettings, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 11) { displayApp.StartApp(Pinetime::Applications::Apps::Music, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 12) { displayApp.StartApp(Pinetime::Applications::Apps::Paint, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 13) { displayApp.StartApp(Pinetime::Applications::Apps::SysInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 14) { displayApp.StartApp(Pinetime::Applications::Apps::Steps, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 15) { displayApp.StartApp(Pinetime::Applications::Apps::Error, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else if (screen_idx == 17) { displayApp.StartApp(Pinetime::Applications::Apps::PassKey, Pinetime::Applications::DisplayApp::FullRefreshDirections::None); } else { std::cout << "unhandled screen_idx: " << int(screen_idx) << std::endl; } } static void screen_off_delete_cb(lv_obj_t *obj, lv_event_t event) { if (event == LV_EVENT_DELETE) { auto* fw = static_cast(obj->user_data); if (obj == fw->screen_off_bg) { // on delete make sure to not double free the screen_off objects fw->screen_off_created = false; } } } // render the current status of the simulated controller void refresh_screen() { const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level(); if (level == Pinetime::Controllers::BrightnessController::Levels::Off) { if (!screen_off_created) { screen_off_created = true; screen_off_bg = lv_obj_create(lv_scr_act(), nullptr); lv_obj_set_size(screen_off_bg, 240, 240); lv_obj_set_pos(screen_off_bg, 0, 0); lv_obj_set_style_local_bg_color(screen_off_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); screen_off_bg->user_data = this; // add callback to prevent double free through Alarm Screen lv_obj_set_event_cb(screen_off_bg, screen_off_delete_cb); screen_off_label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(screen_off_label, "Screen is OFF"); lv_obj_align(screen_off_label, nullptr, LV_ALIGN_CENTER, 0, -20); } /* Periodically call the lv_task handler. * It could be done in a timer interrupt or an OS task too.*/ // only call the task handler if the screen is off, // when the screen is enabled the call is done in the SystemTask class lv_task_handler(); } else { if (screen_off_created) { screen_off_created = false; lv_obj_del(screen_off_bg); lv_obj_del(screen_off_label); } } if (print_memory_usage) { // print free memory with the knowledge that 14KiB RAM is the actual PineTime-Memory lv_mem_monitor(&mem_mon); printf("actual free_size = %d\n", int64_t(mem_mon.free_size) - (LV_MEM_SIZE - 14U*1024U)); } } bool print_memory_usage = false; lv_mem_monitor_t mem_mon; // variables to create and destroy an lvgl overlay to indicate a turned off screen bool screen_off_created = false; lv_obj_t *screen_off_bg; lv_obj_t *screen_off_label; private: bool key_handled_r = false; // r ... enable ringing, R ... disable ringing bool key_handled_m = false; // m ... let motor run, M ... stop motor bool key_handled_n = false; // n ... send notification, N ... clear all notifications bool key_handled_b = false; // b ... connect Bluetooth, B ... disconnect Bluetooth bool key_handled_v = false; // battery Voltage and percentage, v ... increase, V ... decrease bool key_handled_c = false; // c ... charging, C ... not charging bool key_handled_l = false; // l ... increase brightness level, L ... lower brightness level bool key_handled_p = false; // p ... enable print memory usage, P ... disable print memory usage bool key_handled_s = false; // s ... increase step count, S ... decrease step count bool key_handled_h = false; // h ... set heartrate running, H ... stop heartrate // numbers from 0 to 9 to switch between screens bool key_handled_1 = false; bool key_handled_2 = false; bool key_handled_3 = false; bool key_handled_4 = false; bool key_handled_5 = false; bool key_handled_6 = false; bool key_handled_7 = false; bool key_handled_8 = false; bool key_handled_9 = false; bool key_handled_0 = false; bool visible; // show Simulator window int height; // Height of the window int width; // Width of the window SDL_Renderer *renderer = NULL; // Pointer for the renderer SDL_Window *window = NULL; // Pointer for the window bool left_release_sent = true; // make sure to send one mouse button release event bool right_last_state = false; // varable used to send message only on changing state }; int main(int argc, char **argv) { // parse arguments bool fw_status_window_visible = true; bool arg_help = false; for (int i=1; i