1717#include < fstream>
1818#include " json.hpp"
1919#include " filesystem.hpp"
20+ #include < mutex>
21+ #include < atomic>
2022
2123#define VERSION " 1.0.0B" // Version of the app.
2224#define BETA false // If the app is in beta.
@@ -51,12 +53,13 @@ std::vector<std::string> output; // Logs
5153std::deque<int16_t > signalRssis; // Signal strengths recorded (for graphs)
5254int positionAway = 0 ; // How far we have scrolled up in the command line widget
5355int logScrolledLeft = 0 ; // How far we've scrolled right in the command line
54- bool running = false ; // If the UI update thread should run
56+ std::atomic< bool > running{ true } ; // If the UI update thread should run
5557bool showSaveSettingsPrompt = true ; // If we should ask to save a settings file (if it doesn't already exist)
5658std::thread refresher; // The UI update thread
5759ghc::filesystem::path exec; // Parent directory of the executable
5860ghc::filesystem::path settingsfile; // The file path containing our settings (potentially)
5961json settings; // Our global settings
62+ std::mutex mutex;
6063
6164enum rssi_stage {
6265 rssi_stage_excellent,
@@ -91,10 +94,10 @@ std::string rssiStageToString(rssi_stage stage) {
9194Color rssiStageToColor (rssi_stage stage) {
9295 switch (stage) {
9396 case rssi_stage_excellent: return Color::Green;
94- case rssi_stage_good: return Color::Green ;
95- case rssi_stage_fair: return Color::Green ;
96- case rssi_stage_poor: return Color::Green ;
97- case rssi_stage_unavailable: return Color::Green ;
97+ case rssi_stage_good: return Color::Yellow ;
98+ case rssi_stage_fair: return Color::Orange ;
99+ case rssi_stage_poor: return Color::Red ;
100+ case rssi_stage_unavailable: return Color::White ;
98101 }
99102}
100103
@@ -133,6 +136,7 @@ void debug(const std::string& input, Args&&... args) {
133136
134137// Add to command line widget logs ('output')
135138void log (std::string input) {
139+ std::lock_guard<std::mutex> lock (mutex);
136140 if (positionAway != 0 ) positionAway++; // If we're not following the logs, then scroll even farther away from them to stay where we are now
137141 output.push_back (input);
138142}
@@ -240,7 +244,7 @@ void usage(std::string command = "") {
240244 } else if (command == " save" || command == " unsave" ) {
241245 log (" save/unsave Usage:" );
242246 log (1 , " save/unsave help Print this help message." );
243- log (1 , " save/unsace password [SSID] [password] Save a password for a WiFi network, for use later." );
247+ log (1 , " save/unsave password [SSID] [password] Save a password for a WiFi network, for use later." );
244248 } else if (command.empty ()) {
245249 log (" Usage:" );
246250 log (1 , " help [command] Print this help message, or optionally the help message of a different command." );
@@ -260,6 +264,7 @@ void usage(std::string command = "") {
260264
261265bool processCommand (std::string input) {
262266 trim (input);
267+ if (input.empty ()) return true ;
263268 auto command = parseCommand (input); // The full list of arguments
264269 auto action = command[0 ]; // The thing the user is trying to do, the first argument
265270
@@ -279,7 +284,7 @@ bool processCommand(std::string input) {
279284 } else if (action == " echo" ) { // Debug command, solely for command parsing tests; won't be listed to the user
280285 log (fmt::format (" Received command of '{}' with {} extra arguments" , action, command.size () - 1 ));
281286 } else if (action == " power" ) {
282- if (command.size () < = 2 ) {
287+ if (command.size () > = 2 ) {
283288 const std::string status = command[1 ];
284289 int result;
285290
@@ -340,7 +345,11 @@ bool processCommand(std::string input) {
340345 return true ;
341346 }
342347
343- settings[" savedPasswords" ][*ssid] = pswd;
348+ {
349+ std::lock_guard<std::mutex> lock (mutex);
350+ settings[" savedPasswords" ][*ssid] = pswd;
351+ }
352+
344353 bool saved = saveSettings (settings);
345354 log (fmt::format (" Saved SSID '{}' with password '{}'!" , ssid.value_or (" <unknown>" ), pswd.value_or (" <unknown>" )));
346355 } else if (subcommand == std::nullopt ) {
@@ -357,7 +366,7 @@ bool processCommand(std::string input) {
357366 std::optional<std::string> ssid = atOrNull (command, 2 );
358367
359368 if (ssid == std::nullopt ) {
360- log (" Please provide ab SSID." );
369+ log (" Please provide an SSID." );
361370 return true ;
362371 }
363372
@@ -385,7 +394,7 @@ bool processCommand(std::string input) {
385394 showSaveSettingsPrompt = false ;
386395 _saveSettings (settings);
387396 log (" Saved settings!" );
388- } else if (status == " decline " ) {
397+ } else if (status == " deny " ) {
389398 showSaveSettingsPrompt = false ;
390399 log (" Declined to save settings." );
391400 } else {
@@ -425,7 +434,7 @@ int main(int argc, char* argv[]) {
425434 debug (" Starting ItlwmCLI version {} {}" , VERSION, versionTypeString);
426435
427436 exec = ghc::filesystem::absolute (argv[0 ]).parent_path ();
428- settingsfile = exec / " ItlwmCLI.settings.json" ; // File for settings
437+ settingsfile = exec / " ItlwmCLI.settings.json" ; // File for settings, obviously
429438
430439 // We finally get to the good stuff
431440 debug (" Loading application..." );
@@ -441,7 +450,7 @@ int main(int argc, char* argv[]) {
441450 uint32_t current80211State = 0 ;
442451
443452 unsigned long iteration = 0 ; // How many times the refresher thread has iterated
444- unsigned long pastIteration = -1 ; // Keeping track of "have we already recorded for this iteration?"
453+ signed long pastIteration = -1 ; // Keeping track of "have we already recorded for this iteration?"
445454 int minRssi = 0 ; // Minimum RSSI of the graph
446455 int maxRssi = 0 ; // Maximum RSSI of the graph
447456 std::string input_str; // What the user has inputted in the command line widget
@@ -458,26 +467,51 @@ int main(int argc, char* argv[]) {
458467 auto input = Input (&input_str, " Type 'help' for available commands. Use up/down, left/right to scroll." , style); // The input provider for the command line widget
459468
460469 auto renderer = Renderer ([&] {
470+ std::vector<std::string> localOutput;
471+ std::deque<int16_t > localSignalRssis;
472+ int localPositionAway;
473+ int localLogScrolledLeft;
474+ unsigned long localIteration;
475+
476+ {
477+ // Gotta lock stuff
478+ std::lock_guard<std::mutex> lock (mutex);
479+
480+ localOutput = output;
481+ localSignalRssis = signalRssis;
482+ localPositionAway = positionAway;
483+ localLogScrolledLeft = logScrolledLeft;
484+ localIteration = iteration;
485+ }
486+
461487 Elements output_elements;
462488 Elements networks_elements;
463489
464- size_t start = (output .size () > VISIBLE_LOG_LINES) ? (output .size () - VISIBLE_LOG_LINES) : 0 ; // Where should we start rendering command logs?
490+ size_t start = (localOutput .size () > VISIBLE_LOG_LINES) ? (localOutput .size () - VISIBLE_LOG_LINES) : 0 ; // Where should we start rendering command logs?
465491 bool foundConnected = false ; // If one of the networks returned from itlwm is the one we're connected to (it doesn't seem to do this in my testing)
466492
467- for (size_t i = start - positionAway; i < output.size () - positionAway; ++i) {
493+ int newStart = start - localPositionAway;
494+ if (newStart < 0 ) newStart = 0 ;
495+
496+ int toRender = localOutput.size () - localPositionAway;
497+ if (toRender < 0 ) toRender = 0 ;
498+
499+ for (size_t i = newStart; i < toRender; ++i) {
468500 std::string index = std::to_string (i + 1 );
469501 std::string spaces = " " ;
470502 while (index.size () + spaces.size () < LOG_INDEX_PADDING) spaces += " " ; // Pad so the line numbers line up correctly
471- output_elements.push_back (text (fmt::format (" {}{}. {}" , spaces, index, output [i].size () > logScrolledLeft ? output [i].substr (logScrolledLeft ) : " " )));
503+ output_elements.push_back (text (fmt::format (" {}{}. {}" , spaces, index, localOutput [i].size () > localLogScrolledLeft ? localOutput [i].substr (localLogScrolledLeft ) : " " )));
472504 }
473505
474506 while (output_elements.size () < VISIBLE_LOG_LINES) { // Pad with blanks to keep FTXUI consistent
475507 output_elements.insert (output_elements.begin (), text (" " ));
476508 }
477509
478- // Make sure to clear each time, in case it changes
510+ // Make sure to clear each time, in case it changes.
511+ // We also clear stationInfo so we know it's at least initialized.
479512 std::memset (currentSsid, 0 , sizeof (currentSsid));
480513 std::memset (currentBssid, 0 , sizeof (currentBssid));
514+ std::memset (stationInfo, 0 , sizeof (*stationInfo));
481515
482516 // Query itlwm
483517 bool network_ssid_available = get_network_ssid (currentSsid);
@@ -489,8 +523,8 @@ int main(int argc, char* argv[]) {
489523 bool station_info_available = get_station_info (stationInfo);
490524
491525 // So uh, station_info_available is always false in my experience for some reason, so we're using a different method
492- station_info_available = station_info_available || stationInfo ? true : false ;
493- bool rssi_available = station_info_available && stationInfo-> rssi < 0 && stationInfo-> rssi > RSSI_UNAVAILABLE_THRESHOLD ;
526+ station_info_available = station_info_available && stationInfo-> rssi < 0 && stationInfo-> rssi > RSSI_UNAVAILABLE_THRESHOLD ;
527+ bool rssi_available = station_info_available;
494528
495529 // If the WiFi is off, then everything should be off
496530 if (network_power_state_available == false || currentPowerState == false ) {
@@ -531,31 +565,30 @@ int main(int argc, char* argv[]) {
531565 networks_elements.push_back (text (" " ));
532566 }
533567
534- if (rssi_available && iteration % RSSI_RECORD_INTERVAL == 0 && pastIteration != iteration) { // Every X iterations
535- signalRssis.push_back (rssi_available ? stationInfo->rssi : RSSI_UNAVAILABLE_THRESHOLD);
536- if (signalRssis.size () > MAX_RSSI_RECORD_LENGTH) signalRssis.pop_front ();
537- pastIteration = iteration;
538- }
539-
540568 // Setup stuff for the graph
541- minRssi = *std::min_element (signalRssis.begin (), signalRssis.end ()); // Minimum graph point (based on the entire dataset)
542- maxRssi = *std::max_element (signalRssis.begin (), signalRssis.end ()); // Maximum graph point (based on the entire dataset)
543- if (minRssi == maxRssi) maxRssi = minRssi + 1 ;
569+ if (localSignalRssis.empty ()) {
570+ minRssi = 0 ;
571+ maxRssi = 0 ;
572+ } else {
573+ minRssi = *std::min_element (localSignalRssis.begin (), localSignalRssis.end ()); // Minimum graph point (based on the entire dataset)
574+ maxRssi = *std::max_element (localSignalRssis.begin (), localSignalRssis.end ()); // Maximum graph point (based on the entire dataset)
575+ if (minRssi == maxRssi) maxRssi = minRssi + 1 ;
576+ }
544577
545578 // Fancy duplication stuff
546- int lastIndexLength = output .size ();
579+ int lastIndexLength = localOutput .size ();
547580 std::stringstream hashtagStream;
548- hashtagStream << std::setw (LOG_INDEX_PADDING) << std::setfill (' ' ) << std::string (std::to_string (output .size ()).size (), ' #' );
581+ hashtagStream << std::setw (LOG_INDEX_PADDING) << std::setfill (' ' ) << std::string (std::to_string (localOutput .size ()).size (), ' #' );
549582
550583 // Get average RSSI
551584 long long rssiSum = 0 ;
552- for (int n : signalRssis ) rssiSum += abs (n);
553- int rssiAverage = signalRssis .empty () ? 0 : -static_cast <int >(rssiSum / signalRssis .size ());
585+ for (int n : localSignalRssis ) rssiSum += abs (n);
586+ int rssiAverage = localSignalRssis .empty () ? 0 : -static_cast <int >(rssiSum / localSignalRssis .size ());
554587
555- auto makeGraph = [rssi_available, minRssi, maxRssi](int width, int height) -> std::vector<int > {
588+ auto makeGraph = [rssi_available, minRssi, maxRssi, localSignalRssis ](int width, int height) -> std::vector<int > {
556589 std::vector<int > scaled (width, 0 );
557- if (signalRssis .empty ()) return scaled; // Empty, we don't have data yet
558- std::deque<int16_t > data (signalRssis ); // Duplicate the list
590+ if (localSignalRssis .empty ()) return scaled; // Empty, we don't have data yet
591+ std::deque<int16_t > data (localSignalRssis ); // Duplicate the list
559592 int i;
560593
561594 if (data.size () * BAR_WIDTH > width) { // Truncate the copied list
@@ -626,6 +659,9 @@ int main(int argc, char* argv[]) {
626659 });
627660
628661 auto interactive = CatchEvent (renderer, [&](Event event) { // Catch events, like keystrokes
662+ std::lock_guard<std::mutex> lock (mutex);
663+ int maxScroll = output.size () > VISIBLE_LOG_LINES ? static_cast <int >(output.size () - VISIBLE_LOG_LINES) : 0 ;
664+
629665 if (event == Event::Return) { // User tried to enter a command
630666 trim (input_str);
631667 if (input_str.empty ()) return true ;
@@ -643,7 +679,7 @@ int main(int argc, char* argv[]) {
643679
644680 return true ;
645681 } else if (event == Event::ArrowUp) { // Scroll up
646- if (positionAway < output. size () - VISIBLE_LOG_LINES ) {
682+ if (positionAway < maxScroll ) {
647683 positionAway++;
648684 }
649685 } else if (event == Event::ArrowDown) { // Scroll down
@@ -671,16 +707,36 @@ int main(int argc, char* argv[]) {
671707 refresher = std::thread ([&] {
672708 while (running) {
673709 std::this_thread::sleep_for (std::chrono::milliseconds (CONSTANT_REFRESH_INTERVAL));
710+ {
711+ std::lock_guard<std::mutex> lock (mutex);
712+ iteration++;
713+
714+ if (iteration % RSSI_RECORD_INTERVAL == 0 ) {
715+ int16_t rssiCopy = 0 ;
716+ bool availableCopy = false ;
717+
718+ {
719+ std::lock_guard<std::mutex> lock (mutex);
720+ availableCopy = station_info_available && stationInfo != nullptr ;
721+ if (availableCopy) rssiCopy = stationInfo->rssi ;
722+
723+ if (availableCopy) {
724+ signalRssis.push_back (rssiCopy);
725+ if (signalRssis.size () > MAX_RSSI_RECORD_LENGTH) signalRssis.pop_front ();
726+ }
727+ }
728+ }
729+ }
730+
674731 screen.PostEvent (Event::Custom);
675- iteration++;
676732 }
677733 });
678734 }
679735
680736 debug (" Starting application..." );
681- if (refresher.joinable ()) refresher.detach ();
682737 screen.Loop (interactive);
683738 running = false ;
739+ if (refresher.joinable ()) refresher.join ();
684740 api_terminate ();
685741 return 0 ;
686742}
0 commit comments