diff --git a/src/apps/Jamfile b/src/apps/Jamfile index 096eff4084..eccbc19284 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -36,6 +36,7 @@ HaikuSubInclude mediaconverter ; HaikuSubInclude mediaplayer ; HaikuSubInclude midiplayer ; HaikuSubInclude networkstatus ; +HaikuSubInclude networktime ; HaikuSubInclude overlayimage ; HaikuSubInclude packageinstaller ; HaikuSubInclude packagemanager ; diff --git a/src/apps/networktime/EditServerListWindow.cpp b/src/apps/networktime/EditServerListWindow.cpp new file mode 100644 index 0000000000..df65f96dad --- /dev/null +++ b/src/apps/networktime/EditServerListWindow.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ + + +#include "EditServerListWindow.h" +#include "NetworkTime.h" + +#include +#include +#include +#include +#include + +#include +#include + + +static const uint32 kMsgServersEdited = 'sedt'; + +static const char *kServerDelimiters = ", \n\t"; + + +struct tokens { + char *buffer; + char *next; + const char *delimiters; +}; + + +static void +put_tokens(tokens &tokens) +{ + free(tokens.buffer); + tokens.buffer = NULL; +} + + +static status_t +prepare_tokens(tokens &tokens, const char *text, const char *delimiters) +{ + tokens.buffer = strdup(text); + if (tokens.buffer == NULL) + return B_NO_MEMORY; + + tokens.delimiters = delimiters; + tokens.next = tokens.buffer; + return B_OK; +} + + +static const char * +next_token(tokens &tokens) +{ + char *token = strtok(tokens.next, tokens.delimiters); + if (tokens.next != NULL) + tokens.next = NULL; + if (token == NULL) + put_tokens(tokens); + + return token; +} + + +// #pragma mark - + + +EditServerListWindow::EditServerListWindow(BRect position, const BMessage &settings) + : BWindow(position, "Edit Server List", B_TITLED_WINDOW, + B_ASYNCHRONOUS_CONTROLS), + fSettings(settings) +{ + BRect rect = Bounds(); + BView *view = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW); + view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(view); + + rect = view->Bounds().InsetByCopy(5, 5); + + BButton *button = new BButton(rect, "defaults", "Reset To Defaults", + new BMessage(kMsgResetServerList), B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); + float width, height; + button->GetPreferredSize(&width, &height); + button->ResizeTo(rect.Width(), height); + button->MoveBy(0, rect.Height() - height); + button->SetTarget(be_app); + view->AddChild(button); + + rect.bottom -= 5 + height; + rect.InsetBy(2, 2); + rect.right -= B_V_SCROLL_BAR_WIDTH; + + fServerView = new BTextView(rect, "list", + rect.OffsetToCopy(B_ORIGIN).InsetByCopy(3, 3), B_FOLLOW_ALL); + fServerView->SetStylable(true); + + BScrollView *scrollView = new BScrollView("scroller", fServerView, B_FOLLOW_ALL, B_WILL_DRAW, false, true); + view->AddChild(scrollView); + + UpdateServerView(settings); + + SetSizeLimits(200, 16384, 200, 16384); + + if (Frame().LeftTop() == B_ORIGIN) { + // center on screen + BScreen screen; + MoveTo((screen.Frame().Width() - Bounds().Width()) / 2, + (screen.Frame().Height() - Bounds().Height()) / 2); + } +} + + +EditServerListWindow::~EditServerListWindow() +{ +} + + +void +EditServerListWindow::UpdateDefaultServerView(const BMessage &message) +{ + int32 defaultServer; + if (message.FindInt32("default server", &defaultServer) != B_OK) + return; + + BFont font(be_plain_font); + fServerView->SetFontAndColor(0, fServerView->TextLength(), + &font, B_FONT_FAMILY_AND_STYLE); + + struct tokens tokens; + const char *server; + if (fSettings.FindString("server", defaultServer, &server) == B_OK + && prepare_tokens(tokens, fServerView->Text(), kServerDelimiters) == B_OK) { + const char *token; + while ((token = next_token(tokens)) != NULL) { + if (strcasecmp(token, server)) + continue; + + int32 start = int32(token - tokens.buffer); + + BFont font; + font.SetFace(B_BOLD_FACE); + fServerView->SetFontAndColor(start, start + strlen(server), + &font, B_FONT_FAMILY_AND_STYLE); + + break; + } + put_tokens(tokens); + } +} + + +void +EditServerListWindow::UpdateServerView(const BMessage &message) +{ + if (message.HasString("server") || message.HasBool("reset servers")) + fServerView->SetText(""); + + int32 defaultServer; + if (message.FindInt32("default server", &defaultServer) != B_OK) + defaultServer = 0; + + const char *server; + int32 index = 0; + for (; message.FindString("server", index, &server) == B_OK; index++) { + int32 start, end; + fServerView->GetSelection(&start, &end); + + fServerView->Insert(server); + fServerView->Insert("\n"); + } + + UpdateDefaultServerView(message); +} + + +void +EditServerListWindow::UpdateServerList() +{ + // get old default server + + int32 defaultServer; + if (fSettings.FindInt32("default server", &defaultServer) != B_OK) + defaultServer = 0; + const char *server; + if (fSettings.FindString("server", defaultServer, &server) != B_OK) + server = NULL; + + // take over server list + + struct tokens tokens; + if (prepare_tokens(tokens, fServerView->Text(), kServerDelimiters) == B_OK) { + BMessage servers(kMsgUpdateSettings); + + int32 count = 0, newDefaultServer = -1; + + const char *token; + while ((token = next_token(tokens)) != NULL) { + servers.AddString("server", token); + if (!strcasecmp(token, server)) + newDefaultServer = count; + + count++; + } + + if (count != 0) { + if (newDefaultServer == -1) + newDefaultServer = 0; + + servers.AddInt32("default server", newDefaultServer); + be_app->PostMessage(&servers); + } else + be_app->PostMessage(kMsgResetServerList); + } +} + + +bool +EditServerListWindow::QuitRequested() +{ + be_app->PostMessage(kMsgEditServerListWindowClosed); + + BMessage update(kMsgUpdateSettings); + update.AddRect("edit servers frame", Frame()); + be_app->PostMessage(&update); + + UpdateServerList(); + return true; +} + + +void +EditServerListWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kMsgServersEdited: + UpdateServerList(); + break; + + case kMsgSettingsUpdated: + if (message->HasString("server")) + UpdateServerView(*message); + else + UpdateDefaultServerView(*message); + break; + } +} + diff --git a/src/apps/networktime/EditServerListWindow.h b/src/apps/networktime/EditServerListWindow.h new file mode 100644 index 0000000000..7b5b37b06c --- /dev/null +++ b/src/apps/networktime/EditServerListWindow.h @@ -0,0 +1,32 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ +#ifndef EDIT_SERVER_LIST_WINDOW_H +#define EDIT_SERVER_LIST_WINDOW_H + + +#include + +class BTextView; + + +class EditServerListWindow : public BWindow { + public: + EditServerListWindow(BRect position, const BMessage &settings); + virtual ~EditServerListWindow(); + + virtual bool QuitRequested(); + virtual void MessageReceived(BMessage *message); + + private: + void UpdateDefaultServerView(const BMessage &message); + void UpdateServerView(const BMessage &message); + + void UpdateServerList(); + + const BMessage &fSettings; + BTextView *fServerView; +}; + +#endif /* EDIT_SERVER_LIST_WINDOW_H */ diff --git a/src/apps/networktime/Jamfile b/src/apps/networktime/Jamfile new file mode 100644 index 0000000000..c87c225f6b --- /dev/null +++ b/src/apps/networktime/Jamfile @@ -0,0 +1,21 @@ +SubDir HAIKU_TOP src apps networktime ; + +Application NetworkTime : + EditServerListWindow.cpp + NetworkTime.cpp + ntp.cpp + UpdateWindow.cpp + $(DRIVER_SETTINGS) + + : be $(TARGET_LIBSTDC++) $(HAIKU_LOCALE_LIBS) $(HAIKU_NETWORK_LIBS) + : NetworkTime.rdef + ; + +DoCatalogs NetworkTime : + x-vnd.Haiku-NetworkTime + : + EditServerListWindow.cpp + NetworkTime.cpp + ntp.cpp + UpdateWindow.cpp +; diff --git a/src/apps/networktime/Monitor.h b/src/apps/networktime/Monitor.h new file mode 100644 index 0000000000..86f396d734 --- /dev/null +++ b/src/apps/networktime/Monitor.h @@ -0,0 +1,54 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ +#ifndef MONITOR_H +#define MONITOR_H + + +#include +#include + + +class Monitor { + public: + virtual ~Monitor() {} + + virtual void Update(const char *text, ...); + virtual void Update(float progress, const char *text = NULL, ...); + + virtual void Done(status_t status); + + private: + virtual void update(float progress, const char *text, va_list args) = 0; +}; + + +inline void +Monitor::Update(const char *text, ...) +{ + va_list args; + + va_start(args, text); + update(-1.f, text, args); + va_end(args); +} + + +inline void +Monitor::Update(float progress, const char *text, ...) +{ + va_list args; + + va_start(args, text); + update(progress, text, args); + va_end(args); +} + + +inline void +Monitor::Done(status_t /*status*/) +{ +} + +#endif /* MONITOR_H */ diff --git a/src/apps/networktime/NetworkTime.cpp b/src/apps/networktime/NetworkTime.cpp new file mode 100644 index 0000000000..2ce2fa920c --- /dev/null +++ b/src/apps/networktime/NetworkTime.cpp @@ -0,0 +1,670 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ + + +#include "NetworkTime.h" +#include "UpdateWindow.h" +#include "EditServerListWindow.h" +#include "ntp.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +status_t update_time(const BMessage &settings, Monitor *monitor); +status_t update_time(const BMessage &server, Monitor *monitor, thread_id *_asyncThread); + + +static const uint32 kMsgNetworkTimeSettings = 'NtpS'; + +extern const char *__progname; +static const char *sProgramName = __progname; + + +class Settings { + public: + Settings(); + ~Settings(); + + void CopyMessage(BMessage &message) const; + const BMessage &Message() const; + void UpdateFrom(BMessage &message); + + void ResetServersToDefaults(); + void ResetToDefaults(); + status_t Load(); + status_t Save(); + + private: + status_t GetPath(BPath &path); + + BMessage fMessage; + bool fWasUpdated; +}; + + +class PrintMonitor : public Monitor { + private: + virtual void + update(float progress, const char *text, va_list args) + { + if (progress != -1.f) + printf("%3g%% ", progress); + else + printf(" "); + + if (text != NULL) + vprintf(text, args); + + putchar('\n'); + } +}; + + +class MessengerMonitor : public Monitor { + public: + MessengerMonitor(BMessenger messenger) + : + fMessenger(messenger) + { + } + + virtual void + Done(status_t status) + { + BMessage message(kMsgStatusUpdate); + message.AddInt32("status", status); + + fMessenger.SendMessage(&message); + fMessenger.SendMessage(kMsgStopTimeUpdate); + + // This deadlocks Dano + //be_app_messenger.SendMessage(kMsgStopTimeUpdate); + } + + private: + virtual void + update(float progress, const char *text, va_list args) + { + BMessage message(kMsgStatusUpdate); + if (progress != -1.f) + message.AddFloat("progress", progress); + + if (text != NULL) { + char buffer[2048]; + vsprintf(buffer, text, args); + message.AddString("message", buffer); + } + + fMessenger.SendMessage(&message); + } + + BMessenger fMessenger; +}; + + +class NetworkTimeApplication : public BApplication { + public: + NetworkTimeApplication(Settings &settings); + virtual ~NetworkTimeApplication(); + + virtual void ReadyToRun(); + virtual void MessageReceived(BMessage *message); + virtual void AboutRequested(); + + void BroadcastSettingsChanges(BMessage &message); + + private: + Settings &fSettings; + thread_id fUpdateThread; + BWindow *fServerListWindow; +}; + + +status_t +adopt_int32(const BMessage &from, BMessage &to, const char *name, bool &updated) +{ + int32 value; + if (from.FindInt32(name, &value) != B_OK) + return B_ENTRY_NOT_FOUND; + + int32 original; + if (to.FindInt32(name, &original) == B_OK) { + if (value == original) + return B_OK; + + updated = true; + return to.ReplaceInt32(name, value); + } + + updated = true; + return to.AddInt32(name, value); +} + + +status_t +adopt_bool(const BMessage &from, BMessage &to, const char *name, bool &updated) +{ + bool value; + if (from.FindBool(name, &value) != B_OK) + return B_ENTRY_NOT_FOUND; + + bool original; + if (to.FindBool(name, &original) == B_OK) { + if (value == original) + return B_OK; + + updated = true; + return to.ReplaceBool(name, value); + } + + updated = true; + return to.AddBool(name, value); +} + + +status_t +adopt_rect(const BMessage &from, BMessage &to, const char *name, bool &updated) +{ + BRect rect; + if (from.FindRect(name, &rect) != B_OK) + return B_ENTRY_NOT_FOUND; + + BRect original; + if (to.FindRect(name, &original) == B_OK) { + if (rect == original) + return B_OK; + + updated = true; + return to.ReplaceRect(name, rect); + } + + updated = true; + return to.AddRect(name, rect); +} + + +// #pragma mark - + + +class UpdateLooper : public BLooper { + public: + UpdateLooper(const BMessage &settings, Monitor *monitor); + + void MessageReceived(BMessage *message); + + private: + const BMessage &fSettings; + Monitor *fMonitor; +}; + + +UpdateLooper::UpdateLooper(const BMessage &settings, Monitor *monitor) + : BLooper("update looper"), + fSettings(settings), + fMonitor(monitor) +{ + PostMessage(kMsgUpdateTime); +} + + +void +UpdateLooper::MessageReceived(BMessage *message) +{ + if (message->what != kMsgUpdateTime) + return; + + update_time(fSettings, fMonitor); +} + + +// #pragma mark - + + +NetworkTimeApplication::NetworkTimeApplication(Settings &settings) + : BApplication("application/x-vnd.Haiku-NetworkTime"), + fSettings(settings), + fUpdateThread(-1), + fServerListWindow(NULL) +{ +} + + +NetworkTimeApplication::~NetworkTimeApplication() +{ +} + + +void +NetworkTimeApplication::ReadyToRun() +{ + BRect rect; + if (fSettings.Message().FindRect("status frame", &rect) != B_OK) + rect.Set(0, 0, 300, 150); + + UpdateWindow *window = new UpdateWindow(rect, fSettings.Message()); + window->Show(); +} + + +void +NetworkTimeApplication::BroadcastSettingsChanges(BMessage &message) +{ + BWindow *window; + int32 index = 0; + while ((window = WindowAt(index++)) != NULL) + window->PostMessage(&message); +} + + +void +NetworkTimeApplication::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kMsgUpdateSettings: + { + fSettings.UpdateFrom(*message); + + message->what = kMsgSettingsUpdated; + BroadcastSettingsChanges(*message); + break; + } + + case kMsgResetServerList: + { + fSettings.ResetServersToDefaults(); + + BMessage updated = fSettings.Message(); + updated.what = kMsgSettingsUpdated; + + BroadcastSettingsChanges(updated); + break; + } + + case kMsgStopTimeUpdate: + if (fUpdateThread >= B_OK) + kill_thread(fUpdateThread); + + fUpdateThread = -1; + break; + + case kMsgUpdateTime: + { + if (fUpdateThread >= B_OK) + break; + + MessengerMonitor *monitor = NULL; + BMessenger messenger; + if (message->FindMessenger("monitor", &messenger) == B_OK) + monitor = new MessengerMonitor(messenger); + + update_time(fSettings.Message(), monitor, &fUpdateThread); + break; + } + + case kMsgEditServerList: + if (fServerListWindow == NULL) { + BRect rect; + if (fSettings.Message().FindRect("edit servers frame", &rect) != B_OK) + rect.Set(0, 0, 250, 250); + fServerListWindow = new EditServerListWindow(rect, fSettings.Message()); + } + + fServerListWindow->Show(); + break; + + case kMsgEditServerListWindowClosed: + fServerListWindow = NULL; + break; + } +} + + +void +NetworkTimeApplication::AboutRequested() +{ + BAlert *alert = new BAlert("about", "Network Time\n" + "\twritten by Axel Dörfler\n\n" + "\tCopyright " B_UTF8_COPYRIGHT "2004, pinc Software.\n" + "\tAll Rights Reserved.\n", "Ok"); + BTextView *view = alert->TextView(); + BFont font; + + view->SetStylable(true); + + view->GetFont(&font); + font.SetSize(18); + font.SetFace(B_BOLD_FACE); + view->SetFontAndColor(0, 12, &font); + + alert->Go(); +} + + +// #pragma mark - + + +Settings::Settings() + : + fMessage(kMsgNetworkTimeSettings), + fWasUpdated(false) +{ + ResetToDefaults(); + Load(); +} + + +Settings::~Settings() +{ + if (fWasUpdated) + Save(); +} + + +void +Settings::CopyMessage(BMessage &message) const +{ + message = fMessage; +} + + +const BMessage & +Settings::Message() const +{ + return fMessage; +} + + +void +Settings::UpdateFrom(BMessage &message) +{ + if (message.HasBool("reset servers")) { + fMessage.RemoveName("server"); + fWasUpdated = true; + } + + if (message.HasString("server")) { + // remove old servers + fMessage.RemoveName("server"); + + const char *server; + int32 index = 0; + while (message.FindString("server", index++, &server) == B_OK) { + fMessage.AddString("server", server); + } + + fWasUpdated = true; + } + + adopt_int32(message, fMessage, "default server", fWasUpdated); + + adopt_bool(message, fMessage, "auto mode", fWasUpdated); + adopt_bool(message, fMessage, "knows auto mode", fWasUpdated); + adopt_bool(message, fMessage, "try all servers", fWasUpdated); + adopt_bool(message, fMessage, "choose default server", fWasUpdated); + + adopt_rect(message, fMessage, "status frame", fWasUpdated); + adopt_rect(message, fMessage, "edit servers frame", fWasUpdated); +} + + +void +Settings::ResetServersToDefaults() +{ + fMessage.RemoveName("server"); + + fMessage.AddString("server", "pool.ntp.org"); + fMessage.AddString("server", "de.pool.ntp.org"); + fMessage.AddString("server", "time.nist.gov"); + + if (fMessage.ReplaceInt32("default server", 0) != B_OK) + fMessage.AddInt32("default server", 0); +} + + +void +Settings::ResetToDefaults() +{ + fMessage.MakeEmpty(); + ResetServersToDefaults(); + + fMessage.AddBool("knows auto mode", false); + fMessage.AddBool("auto mode", false); + fMessage.AddBool("try all servers", true); + fMessage.AddBool("choose default server", true); + + fMessage.AddRect("status frame", BRect(0, 0, 300, 150)); + fMessage.AddRect("edit servers frame", BRect(0, 0, 250, 250)); +} + + +status_t +Settings::Load() +{ + status_t status; + + BPath path; + if ((status = GetPath(path)) != B_OK) + return status; + + BFile file(path.Path(), B_READ_ONLY); + if ((status = file.InitCheck()) != B_OK) + return status; + + BMessage load; + if ((status = load.Unflatten(&file)) != B_OK) + return status; + + if (load.what != kMsgNetworkTimeSettings) + return B_BAD_TYPE; + + fMessage = load; + return B_OK; +} + + +status_t +Settings::Save() +{ + status_t status; + + BPath path; + if ((status = GetPath(path)) != B_OK) + return status; + + BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + if ((status = file.InitCheck()) != B_OK) + return status; + + file.SetSize(0); + + return fMessage.Flatten(&file); +} + + +status_t +Settings::GetPath(BPath &path) +{ + status_t status; + if ((status = find_directory(B_USER_SETTINGS_DIRECTORY, &path)) != B_OK) + return status; + + path.Append("pinc.networktime settings"); + return B_OK; +} + + +// #pragma mark - + + +status_t +update_time(const BMessage &settings, Monitor *monitor) +{ + int32 defaultServer; + if (settings.FindInt32("default server", &defaultServer) != B_OK) + defaultServer = 0; + + status_t status = B_ENTRY_NOT_FOUND; + const char *server; + if (settings.FindString("server", defaultServer, &server) == B_OK) + status = ntp_update_time(server, monitor); + + // try other servers if we should + if (status != B_OK && settings.FindBool("try all servers")) { + for (int32 index = 0; ; index++) { + if (index == defaultServer) + index++; + if (settings.FindString("server", index, &server) != B_OK) + break; + + status = ntp_update_time(server, monitor); + if (status == B_OK) { + if (be_app != NULL && settings.FindBool("choose default server")) { + BMessage update(kMsgUpdateSettings); + update.AddInt32("default server", index); + be_app->PostMessage(&update); + } + break; + } + } + } + + if (monitor != NULL) + monitor->Done(status); + + delete monitor; + return status; +} + + +status_t +update_time(const BMessage &settings, Monitor *monitor, thread_id *_asyncThread) +{ + if (_asyncThread != NULL) { + BLooper *looper = new UpdateLooper(settings, monitor); + *_asyncThread = looper->Run(); + return B_OK; + } + + return update_time(settings, monitor); +} + + +bool +parse_time(char *arg, int32 *_seconds) +{ + char *unit; + + if (isdigit(arg[0])) + *_seconds = strtoul(arg, &unit, 10); + else + return false; + + if (unit[0] == '\0' || !strcmp(unit, "m")) { + *_seconds *= 60; + return true; + } + if (!strcmp(unit, "h")) { + *_seconds *= 60 * 60; + return true; + } + if (!strcmp(unit, "s")) + return true; + + return false; +} + + +void +usage(void) +{ + fprintf(stderr, "usage: %s [-s|--server ] [-p|--periodic ] [-n|--silent] [-g|--nogui]\n" + " -s, --server\t\tContact \"host\" instead of saved list\n" + " -p, --periodic\tPeriodically query for the time, interval specified in minutes\n" + " -n, --silent\t\tDo not print progress\n" + " -g, --nogui\t\tDo not show graphical user interface (implied by other options)\n", + sProgramName); + exit(0); +} + + +int +main(int argc, char **argv) +{ + Settings settings; + + if (argc > 1) { + // when there are any command line options, we will switch to command line mode + + BMessage message; + settings.CopyMessage(message); + // we may want to override the global settings with command line options + + static struct option const longopts[] = { + {"server", required_argument, NULL, 's'}, + {"periodic", required_argument, NULL, 'p'}, + {"silent", no_argument, NULL, 'n'}, + {"nogui", no_argument, NULL, 'g'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + int32 interval = 0; + bool silent = false; + + char option; + while ((option = getopt_long(argc, argv, "s:p:h", longopts, NULL)) != -1) { + switch (option) { + case 's': + message.RemoveName("server"); + message.AddString("server", optarg); + message.ReplaceInt32("default server", 0); + break; + case 'n': + silent = true; + break; + case 'g': + // default in command line mode + break; + case 'p': + if (parse_time(optarg, &interval)) + break; + // supposed to fall through + case 'h': + default: + usage(); + } + } + + while (true) { + Monitor *monitor = NULL; + if (!silent) + monitor = new PrintMonitor(); + + update_time(message, monitor, NULL); + + if (interval == 0) + break; + + snooze(interval * 1000000LL); + } + } else { + NetworkTimeApplication app(settings); + app.Run(); + } + + return 0; +} diff --git a/src/apps/networktime/NetworkTime.h b/src/apps/networktime/NetworkTime.h new file mode 100644 index 0000000000..c3938c0a1e --- /dev/null +++ b/src/apps/networktime/NetworkTime.h @@ -0,0 +1,22 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ +#ifndef NETWORK_TIME_H +#define NETWORK_TIME_H + + +#include + + +static const uint32 kMsgUpdateTime = 'uptm'; +static const uint32 kMsgStopTimeUpdate = 'stpt'; +static const uint32 kMsgStatusUpdate = 'stup'; +static const uint32 kMsgUpdateSettings = 'upse'; +static const uint32 kMsgSettingsUpdated = 'seUp'; +static const uint32 kMsgResetServerList = 'rtsl'; + +static const uint32 kMsgEditServerList = 'edsl'; +static const uint32 kMsgEditServerListWindowClosed = 'eswc'; + +#endif /* NETWORK_TIME_H */ diff --git a/src/apps/networktime/NetworkTime.rdef b/src/apps/networktime/NetworkTime.rdef new file mode 100644 index 0000000000..1caba787e8 --- /dev/null +++ b/src/apps/networktime/NetworkTime.rdef @@ -0,0 +1,19 @@ + +resource app_signature "application/x-vnd.Haiku-NetworkTime"; + +resource app_version { + major = 1, + middle = 0, + minor = 0, + + /* 0 = development 1 = alpha 2 = beta + 3 = gamma 4 = golden master 5 = final */ + variety = 2, + + internal = 0, + + short_info = "NetworkTime", + long_info = "NetworkTime ©2004 pinc Software" +}; + +resource app_flags B_SINGLE_LAUNCH; diff --git a/src/apps/networktime/UpdateWindow.cpp b/src/apps/networktime/UpdateWindow.cpp new file mode 100644 index 0000000000..d0cabb62f2 --- /dev/null +++ b/src/apps/networktime/UpdateWindow.cpp @@ -0,0 +1,303 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ + + +#include "UpdateWindow.h" +#include "NetworkTime.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +static const uint32 kMsgAutoMode = 'aumd'; +static const uint32 kMsgTryAllServers = 'tasr'; +static const uint32 kMsgChooseDefaultServer = 'cdsr'; +static const uint32 kMsgDefaultServer = 'dsrv'; + + +UpdateWindow::UpdateWindow(BRect position, const BMessage &settings) + : BWindow(position, "Network Time", B_TITLED_WINDOW, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS), + fSettings(settings) +{ + bool autoMode; + if (settings.FindBool("auto mode", &autoMode) != B_OK) + autoMode = false; + + // pressing the control key will avoid auto quit + fAutoQuit = modifiers() & B_CONTROL_KEY ? false : autoMode; + bool showMenu = !fAutoQuit; + + BMenuBar *bar = new BMenuBar(Bounds(), ""); + BMenu *menu; + BMenuItem *item; + + menu = new BMenu("Project"); + + menu->AddItem(item = new BMenuItem("About" B_UTF8_ELLIPSIS, new BMessage(B_ABOUT_REQUESTED), '?')); + item->SetTarget(be_app); + menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q')); + bar->AddItem(menu); + + fServerMenu = new BMenu("Servers"); + fServerMenu->SetRadioMode(true); + BuildServerMenu(); + + menu = new BMenu("Options"); + menu->AddItem(fTryAllServersItem = new BMenuItem("Try All Servers", + new BMessage(kMsgTryAllServers))); + menu->AddItem(fChooseDefaultServerItem = new BMenuItem("Choose Default Server Automatically", + new BMessage(kMsgChooseDefaultServer))); + menu->AddItem(fAutoModeItem = new BMenuItem("Auto Mode", new BMessage(kMsgAutoMode))); + menu->AddSeparatorItem(); + menu->AddItem(fServerMenu); + menu->AddItem(new BMenuItem("Edit Server List" B_UTF8_ELLIPSIS, new BMessage(kMsgEditServerList))); + bar->AddItem(menu); + + if (showMenu) + AddChild(bar); + + BRect rect = Bounds(); + if (showMenu) + rect.top = bar->Bounds().Height(); + + BView *view = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW); + view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(view); + + rect = view->Bounds().InsetByCopy(5, 5); + fStatusBar = new BStatusBar(rect, "status", NULL, NULL); + float width, height; + fStatusBar->GetPreferredSize(&width, &height); + fStatusBar->ResizeTo(rect.Width(), height); + fStatusBar->SetResizingMode(B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT); + view->AddChild(fStatusBar); + + BMessage *buttonMessage = new BMessage(fAutoQuit ? kMsgStopTimeUpdate : kMsgUpdateTime); + buttonMessage->AddMessenger("monitor", this); + + if (showMenu) { + rect.top += height + 4; + fButton = new BButton(rect, NULL, "Update", buttonMessage, + B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT); + fButton->ResizeToPreferred(); + if (autoMode) + fButton->SetLabel("Stop"); + fButton->MoveBy((rect.Width() - fButton->Bounds().Width()) / 2, 0); + view->AddChild(fButton); + + ResizeTo(Bounds().Width(), + bar->Bounds().Height() + fButton->Frame().bottom + 5); + } else { + fButton = NULL; + ResizeTo(Bounds().Width(), height + 9); + } + + SetSizeLimits(200, 16384, Bounds().Height(), Bounds().Height()); + + if (Frame().LeftTop() == B_ORIGIN) { + // center on screen + BScreen screen; + MoveTo((screen.Frame().Width() - Bounds().Width()) / 2, + (screen.Frame().Height() - Bounds().Height()) / 2); + } + + // Update views + BMessage update = fSettings; + update.what = kMsgSettingsUpdated; + PostMessage(&update); + + // Automatically start update in "auto mode" + if (fAutoQuit) { + BMessage update(kMsgUpdateTime); + update.AddMessenger("monitor", this); + + be_app->PostMessage(&update); + } +} + + +UpdateWindow::~UpdateWindow() +{ +} + + +bool +UpdateWindow::QuitRequested() +{ + BMessage update(kMsgUpdateSettings); + update.AddRect("status frame", Frame()); + be_app_messenger.SendMessage(&update); + + be_app->PostMessage(B_QUIT_REQUESTED); + return true; +} + + +void +UpdateWindow::BuildServerMenu() +{ + // remove old entries + BMenuItem *item; + while ((item = fServerMenu->RemoveItem(0L)) != NULL) + delete item; + + int32 defaultServer; + if (fSettings.FindInt32("default server", &defaultServer) != B_OK) + defaultServer = 0; + + const char *server; + for (int32 i = 0; fSettings.FindString("server", i, &server) == B_OK; i++) { + fServerMenu->AddItem(item = new BMenuItem(server, new BMessage(kMsgDefaultServer))); + if (i == defaultServer) + item->SetMarked(true); + } +} + + +void +UpdateWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kMsgSettingsUpdated: + { + if (message->HasString("server")) + BuildServerMenu(); + else { + int32 index; + if (message->FindInt32("default server", &index) == B_OK) { + BMenuItem *item = fServerMenu->ItemAt(index); + if (item != NULL) + item->SetMarked(true); + } + } + + bool boolean; + if (message->FindBool("auto mode", &boolean) == B_OK) + fAutoModeItem->SetMarked(boolean); + if (message->FindBool("try all servers", &boolean) == B_OK) + fTryAllServersItem->SetMarked(boolean); + if (message->FindBool("choose default server", &boolean) == B_OK) + fChooseDefaultServerItem->SetMarked(boolean); + break; + } + + case kMsgAutoMode: + { + fAutoModeItem->SetMarked(!fAutoModeItem->IsMarked()); + + if (!fSettings.FindBool("knows auto mode")) { + // we should alert the user about the changed behaviour + (new BAlert("notice", + "Attention!\n\n" + "Activating this mode let's the user interface only show the " + "bare minimum, and you will not be able to reach the menu " + "anymore.\n" + "Also, the application will quit immediately after the time has " + "been updated successfully; it will only stay if updating fails, " + "so that you can catch the reason for the error.\n\n" + "If you want to get the full interface again, you have to " + "press the control key while starting this application.\n\n" + "This warning will appear only the first time you select this mode.", + "Ok"))->Go(); + } + + BMessage update(kMsgUpdateSettings); + update.AddBool("auto mode", fAutoModeItem->IsMarked()); + update.AddBool("knows auto mode", true); + be_app->PostMessage(&update); + break; + } + + case kMsgTryAllServers: + { + fTryAllServersItem->SetMarked(!fTryAllServersItem->IsMarked()); + + BMessage update(kMsgUpdateSettings); + update.AddBool("try all servers", fTryAllServersItem->IsMarked()); + be_app->PostMessage(&update); + break; + } + + case kMsgChooseDefaultServer: + { + fChooseDefaultServerItem->SetMarked(!fChooseDefaultServerItem->IsMarked()); + + BMessage update(kMsgUpdateSettings); + update.AddBool("choose default server", fChooseDefaultServerItem->IsMarked()); + be_app->PostMessage(&update); + break; + } + + case kMsgDefaultServer: + { + int32 index; + if (message->FindInt32("index", &index) != B_OK) + break; + + BMessage update(kMsgUpdateSettings); + update.AddInt32("default server", index); + be_app->PostMessage(&update); + break; + } + + case kMsgEditServerList: + be_app->PostMessage(kMsgEditServerList); + break; + + case kMsgStopTimeUpdate: + if (fButton != NULL) { + fButton->SetLabel("Update"); + fButton->Message()->what = kMsgUpdateTime; + } + + BButton *source; + if (message->FindPointer("source", (void **)&source) == B_OK + && fButton == source) + fStatusBar->SetText("Stopped."); + + be_app->PostMessage(message); + break; + + case kMsgUpdateTime: + if (fButton != NULL) { + fButton->SetLabel("Stop"); + fButton->Message()->what = kMsgStopTimeUpdate; + } + + be_app->PostMessage(message); + break; + + case kMsgStatusUpdate: + float progress; + if (message->FindFloat("progress", &progress) == B_OK) + fStatusBar->Update(progress - fStatusBar->CurrentValue()); + + const char *text; + if (message->FindString("message", &text) == B_OK) + fStatusBar->SetText(text); + + status_t status; + if (message->FindInt32("status", (int32 *)&status) == B_OK) { + if (status == B_OK && fAutoQuit) + QuitRequested(); + else if (fButton != NULL) { + fButton->SetLabel("Update"); + fButton->Message()->what = kMsgUpdateTime; + } + } + break; + + default: + BWindow::MessageReceived(message); + } +} + diff --git a/src/apps/networktime/UpdateWindow.h b/src/apps/networktime/UpdateWindow.h new file mode 100644 index 0000000000..fd9b45424f --- /dev/null +++ b/src/apps/networktime/UpdateWindow.h @@ -0,0 +1,36 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ +#ifndef UPDATE_WINDOW_H +#define UPDATE_WINDOW_H + + +#include + +class BStatusBar; +class BButton; +class BMenuItem; +class BMenu; + + +class UpdateWindow : public BWindow { + public: + UpdateWindow(BRect position, const BMessage &settings); + virtual ~UpdateWindow(); + + virtual bool QuitRequested(); + virtual void MessageReceived(BMessage *message); + + void BuildServerMenu(); + + private: + const BMessage &fSettings; + BStatusBar *fStatusBar; + BButton *fButton; + BMenuItem *fAutoModeItem, *fTryAllServersItem, *fChooseDefaultServerItem; + BMenu *fServerMenu; + bool fAutoQuit; +}; + +#endif /* UPDATE_WINDOW_H */ diff --git a/src/apps/networktime/ntp.cpp b/src/apps/networktime/ntp.cpp new file mode 100644 index 0000000000..2dc5a8775b --- /dev/null +++ b/src/apps/networktime/ntp.cpp @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2009, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ + + +#include "ntp.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + + +/* This structure and its data fields are described in RFC 1305 + * "Network Time Protocol (Version 3)" in appendix A. + */ + +struct fixed32 { + int16 integer; + uint16 fraction; + + void + SetTo(int16 integer, uint16 fraction = 0) + { + this->integer = htons(integer); + this->fraction = htons(fraction); + } + + int16 Integer() { return htons(integer); } + uint16 Fraction() { return htons(fraction); } +}; + +struct ufixed64 { + uint32 integer; + uint32 fraction; + + void + SetTo(uint32 integer, uint32 fraction = 0) + { + this->integer = htonl(integer); + this->fraction = htonl(fraction); + } + + uint32 Integer() { return htonl(integer); } + uint32 Fraction() { return htonl(fraction); } +}; + +struct ntp_data { + uint8 mode : 3; + uint8 version : 3; + uint8 leap_indicator : 2; + + uint8 stratum; + int8 poll; + int8 precision; /* in seconds of the nearest power of two */ + + fixed32 root_delay; + fixed32 root_dispersion; + uint32 root_identifier; + + ufixed64 reference_timestamp; + ufixed64 originate_timestamp; + ufixed64 receive_timestamp; + ufixed64 transmit_timestamp; + + /* optional authenticator follows (96 bits) */ +}; + +#define NTP_PORT 123 +#define NTP_VERSION_3 3 + +enum ntp_leap_warnings { + LEAP_NO_WARNING = 0, + LEAP_LAST_MINUTE_61_SECONDS, + LEAP_LAST_MINUTE_59_SECONDS, + LEAP_CLOCK_NOT_IN_SYNC, +}; + +enum ntp_modes { + MODE_RESERVED = 0, + MODE_SYMMETRIC_ACTIVE, + MODE_SYMMETRIC_PASSIVE, + MODE_CLIENT, + MODE_SERVER, + MODE_BROADCAST, + MODE_NTP_CONTROL_MESSAGE, +}; + + +const uint32 kUTCtoGMT = 12 * 60 * 60; + + +uint32 +seconds_system_difference(void) +{ + const uint32 kYear2000 = 3155713200UL; + // that many seconds from year 1900 to 2000. + + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_mday = 1; + tm.tm_year = 100; + + return kYear2000 - mktime(&tm); +} + + +uint32 +seconds_since_1900(void) +{ + return seconds_system_difference() + real_time_clock(); +} + + +status_t +ntp_update_time(const char *hostname, Monitor *monitor) +{ + if (monitor) + monitor->Update(0, "Contacting server \"%s\"...", hostname); + + hostent *server = gethostbyname(hostname); + if (server == NULL) { + if (monitor) + monitor->Update("Could not contact server."); + return B_ENTRY_NOT_FOUND; + } + + if (monitor) + monitor->Update(25, "Sending request..."); + + ntp_data message; + memset(&message, 0, sizeof(ntp_data)); + + message.leap_indicator = LEAP_CLOCK_NOT_IN_SYNC; + message.version = NTP_VERSION_3; + message.mode = MODE_CLIENT; + + message.stratum = 1; // primary reference + message.precision = -5; // 2^-5 ~ 32-64 Hz precision + + message.root_delay.SetTo(1); // 1 sec + message.root_dispersion.SetTo(1); + + message.transmit_timestamp.SetTo(seconds_since_1900()); + + int connection = socket(AF_INET, SOCK_DGRAM, 0); + if (connection < 0) { + if (monitor) + monitor->Update("Could not create socket: %s", strerror(errno)); + return errno; + } + + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_port = htons(NTP_PORT); + address.sin_addr.s_addr = *(uint32 *)server->h_addr_list[0]; + + if (sendto(connection, (char *)&message, sizeof(ntp_data), + 0, (struct sockaddr *)&address, sizeof(address)) < 0) { + if (monitor) + monitor->Update("Sending request failed: %s", strerror(errno)); + return errno; + } + + // message is sent, now just wait for something to be returned... + + if (monitor) + monitor->Update(50, "Waiting for answer..."); + + fd_set waitForReceived; + FD_ZERO(&waitForReceived); + FD_SET(connection, &waitForReceived); + + struct timeval timeout; + timeout.tv_sec = 3; + timeout.tv_usec = 0; + // we'll wait 3 seconds for the answer + + if (select(connection + 1, &waitForReceived, NULL, NULL, &timeout) <= 0) { + if (monitor) + monitor->Update("Waiting for answer failed: %s", strerror(errno)); + return errno; + } + + message.transmit_timestamp.SetTo(0); + + socklen_t addressSize = sizeof(address); + if (recvfrom(connection, (char *)&message, sizeof(ntp_data), 0, + (sockaddr *)&address, &addressSize) < (ssize_t)sizeof(ntp_data)) { + if (monitor) + monitor->Update("Message receiving failed: %s", strerror(errno)); + close(connection); + return errno; + } + + close(connection); + + if (message.transmit_timestamp.Integer() == 0) { + if (monitor) + monitor->Update("Received invalid time."); + return B_BAD_VALUE; + } + + time_t now = message.transmit_timestamp.Integer() - seconds_system_difference() + kUTCtoGMT; + + if (monitor) { + char buffer[64]; + strftime(buffer, sizeof(buffer), "%a %b %T %Y", localtime(&now)); + monitor->Update(100, "Message received: %s", buffer); + } + + set_real_time_clock(now); + return B_OK; +} diff --git a/src/apps/networktime/ntp.h b/src/apps/networktime/ntp.h new file mode 100644 index 0000000000..601676dd45 --- /dev/null +++ b/src/apps/networktime/ntp.h @@ -0,0 +1,15 @@ +/* + * Copyright 2004, pinc Software. All Rights Reserved. + * Distributed under the terms of the MIT license. + */ +#ifndef NTP_H +#define NTP_H + + +#include "Monitor.h" +#include + + +extern status_t ntp_update_time(const char *host, Monitor *monitor = NULL); + +#endif /* NTP_H */