diff --git a/src/apps/terminal/Jamfile b/src/apps/terminal/Jamfile index 1efea6d9b6..0da6f422f3 100644 --- a/src/apps/terminal/Jamfile +++ b/src/apps/terminal/Jamfile @@ -4,7 +4,7 @@ SetSubDirSupportedPlatformsBeOSCompatible ; UseHeaders [ FDirName $(HAIKU_TOP) src kits tracker ] ; -UsePrivateHeaders libroot kernel system ; +UsePrivateHeaders libroot kernel shared system ; Application Terminal : ActiveProcessInfo.cpp @@ -27,6 +27,7 @@ Application Terminal : TermApp.cpp TerminalBuffer.cpp TerminalCharClassifier.cpp + TerminalRoster.cpp TermConst.cpp TermParse.cpp TermScrollView.cpp diff --git a/src/apps/terminal/TermApp.cpp b/src/apps/terminal/TermApp.cpp index fc1958a320..09852d0a0e 100644 --- a/src/apps/terminal/TermApp.cpp +++ b/src/apps/terminal/TermApp.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2009, Haiku, Inc. All rights reserved. + * Copyright 2001-2010, Haiku, Inc. All rights reserved. * Copyright (c) 2003-2004 Kian Duffy * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. * @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -28,7 +27,6 @@ #include #include - #include "Arguments.h" #include "Globals.h" #include "PrefHandler.h" @@ -40,9 +38,6 @@ static bool sUsageRequested = false; //static bool sGeometryRequested = false; -const ulong MSG_ACTIVATE_TERM = 'msat'; -const ulong MSG_TERM_WINDOW_INFO = 'mtwi'; - int main() @@ -59,29 +54,10 @@ main() TermApp::TermApp() : BApplication(TERM_SIGNATURE), fStartFullscreen(false), - fWindowTitleUserDefined(false), - fWindowNumber(-1), fTermWindow(NULL), fArgs(NULL) { - fArgs = new Arguments(0, NULL); - - fWindowTitle = B_TRANSLATE("Terminal"); - _RegisterTerminal(); - - if (fWindowNumber > 0) - fWindowTitle << " " << fWindowNumber; - - if (_LoadWindowPosition(&fTermFrame, &fTermWorkspaces) != B_OK) { - int i = fWindowNumber / 16; - int j = fWindowNumber % 16; - int k = (j * 16) + (i * 64) + 50; - int l = (j * 16) + 50; - - fTermFrame.Set(k, l, k + 50, k + 50); - fTermWorkspaces = B_CURRENT_WORKSPACE; - } } @@ -120,7 +96,7 @@ TermApp::ReadyToRun() // init the mouse copy'n'paste clipboard gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true); - status_t status = _MakeTermWindow(fTermFrame, fTermWorkspaces); + status_t status = _MakeTermWindow(); // failed spawn, print stdout and open alert panel // TODO: This alert does never show up. @@ -161,9 +137,6 @@ TermApp::QuitRequested() void TermApp::Quit() { - if (!sUsageRequested) - _UnregisterTerminal(); - BApplication::Quit(); } @@ -179,27 +152,10 @@ void TermApp::MessageReceived(BMessage* message) { switch (message->what) { - case MENU_SWITCH_TERM: - _SwitchTerm(); - break; - case MSG_ACTIVATE_TERM: fTermWindow->Activate(); break; - case MSG_TERM_WINDOW_INFO: - { - BMessage reply(B_REPLY); - reply.AddBool("minimized", fTermWindow->IsMinimized()); - reply.AddInt32("workspaces", fTermWindow->Workspaces()); - message->SendReply(&reply); - break; - } - - case MSG_SAVE_WINDOW_POSITION: - _SaveWindowPosition(message); - break; - case MSG_CHECK_CHILDREN: _HandleChildCleanup(); break; @@ -223,10 +179,8 @@ TermApp::ArgvReceived(int32 argc, char **argv) return; } - if (fArgs->Title() != NULL) { + if (fArgs->Title() != NULL) fWindowTitle = fArgs->Title(); - fWindowTitleUserDefined = true; - } fStartFullscreen = fArgs->FullScreen(); } @@ -270,102 +224,10 @@ TermApp::RefsReceived(BMessage* message) status_t -TermApp::_GetWindowPositionFile(BFile* file, uint32 openMode) -{ - BPath path; - status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); - if (status != B_OK) - return status; - - status = path.Append("Terminal_windows"); - if (status != B_OK) - return status; - - return file->SetTo(path.Path(), openMode); -} - - -status_t -TermApp::_LoadWindowPosition(BRect* frame, uint32* workspaces) -{ - status_t status; - BMessage position; - - BFile file; - status = _GetWindowPositionFile(&file, B_READ_ONLY); - if (status != B_OK) - return status; - - status = position.Unflatten(&file); - - file.Unset(); - - if (status != B_OK) - return status; - - status = position.FindRect("rect", fWindowNumber - 1, frame); - if (status != B_OK) - return status; - - int32 _workspaces; - status = position.FindInt32("workspaces", fWindowNumber - 1, &_workspaces); - if (status != B_OK) - return status; - if (modifiers() & B_SHIFT_KEY) - *workspaces = _workspaces; - else - *workspaces = B_CURRENT_WORKSPACE; - - return B_OK; -} - - -status_t -TermApp::_SaveWindowPosition(BMessage* position) -{ - BFile file; - BMessage originalSettings; - - // We append ourself to the existing settings file - // So we have to read it, insert our BMessage, and rewrite it. - - status_t status = _GetWindowPositionFile(&file, B_READ_ONLY); - if (status == B_OK) { - originalSettings.Unflatten(&file); - // No error checking on that : it fails if the settings - // file is missing, but we can create it. - - file.Unset(); - } - - // Append the new settings - BRect rect; - position->FindRect("rect", &rect); - if (originalSettings.ReplaceRect("rect", fWindowNumber - 1, rect) != B_OK) - originalSettings.AddRect("rect", rect); - - int32 workspaces; - position->FindInt32("workspaces", &workspaces); - if (originalSettings.ReplaceInt32("workspaces", fWindowNumber - 1, workspaces) - != B_OK) - originalSettings.AddInt32("workspaces", workspaces); - - // Resave the whole thing - status = _GetWindowPositionFile (&file, - B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); - if (status != B_OK) - return status; - - return originalSettings.Flatten(&file); -} - - -status_t -TermApp::_MakeTermWindow(BRect &frame, uint32 workspaces) +TermApp::_MakeTermWindow() { try { - fTermWindow = new TermWindow(frame, fWindowTitle, - fWindowTitleUserDefined, fWindowNumber, workspaces, fArgs); + fTermWindow = new TermWindow(fWindowTitle, fArgs); } catch (int error) { return (status_t)error; } catch (...) { @@ -378,240 +240,6 @@ TermApp::_MakeTermWindow(BRect &frame, uint32 workspaces) } -void -TermApp::_ActivateTermWindow(team_id id) -{ - BMessenger app(TERM_SIGNATURE, id); - if (app.IsTargetLocal()) - fTermWindow->Activate(); - else - app.SendMessage(MSG_ACTIVATE_TERM); -} - - -void -TermApp::_SwitchTerm() -{ - team_id myId = be_app->Team(); - BList teams; - be_roster->GetAppList(TERM_SIGNATURE, &teams); - - int32 numTerms = teams.CountItems(); - if (numTerms <= 1) - return; - - // Find our position in the app teams. - int32 i; - - for (i = 0; i < numTerms; i++) { - if (myId == reinterpret_cast(teams.ItemAt(i))) - break; - } - - do { - if (--i < 0) - i = numTerms - 1; - } while (!_IsSwitchTarget(reinterpret_cast(teams.ItemAt(i)))); - - // Activate switched terminal. - _ActivateTermWindow(reinterpret_cast(teams.ItemAt(i))); -} - - -bool -TermApp::_IsSwitchTarget(team_id id) -{ - uint32 currentWorkspace = 1L << current_workspace(); - - BMessenger app(TERM_SIGNATURE, id); - if (app.IsTargetLocal()) { - return !fTermWindow->IsMinimized() - && (fTermWindow->Workspaces() & currentWorkspace) != 0; - } - - BMessage reply; - if (app.SendMessage(MSG_TERM_WINDOW_INFO, &reply) != B_OK) - return false; - - bool minimized; - int32 workspaces; - if (reply.FindBool("minimized", &minimized) != B_OK - || reply.FindInt32("workspaces", &workspaces) != B_OK) - return false; - - return !minimized && (workspaces & currentWorkspace) != 0; -} - - -/*! Checks if all teams that have an ID-to-team mapping in the message - are still running. - The IDs for teams that are gone will be made available again, and - their mapping is removed from the message. -*/ -void -TermApp::_SanitizeIDs(BMessage* data, uint8* windows, ssize_t length) -{ - BList teams; - be_roster->GetAppList(TERM_SIGNATURE, &teams); - - for (int32 i = 0; i < length; i++) { - if (!windows[i]) - continue; - - BString id("id-"); - id << i + 1; - - team_id team; - if (data->FindInt32(id.String(), &team) != B_OK) - continue; - - if (!teams.HasItem((void*)team)) { - windows[i] = false; - data->RemoveName(id.String()); - } - } -} - - -/*! - Removes the current fWindowNumber (ID) from the supplied array, or - finds a free ID in it, and sets fWindowNumber accordingly. -*/ -bool -TermApp::_UpdateIDs(bool set, uint8* windows, ssize_t maxLength, - ssize_t* _length) -{ - ssize_t length = *_length; - - if (set) { - int32 i; - for (i = 0; i < length; i++) { - if (!windows[i]) { - windows[i] = true; - fWindowNumber = i + 1; - break; - } - } - - if (i == length) { - if (length >= maxLength) - return false; - - windows[length] = true; - length++; - fWindowNumber = length; - } - } else { - // update information and write it back - windows[fWindowNumber - 1] = false; - } - - *_length = length; - return true; -} - - -void -TermApp::_UpdateRegistration(bool set) -{ - if (set) - fWindowNumber = -1; - else if (fWindowNumber < 0) - return; - -#ifdef __HAIKU__ - // use BClipboard - it supports atomic access in Haiku - BClipboard clipboard(TERM_SIGNATURE); - - while (true) { - if (!clipboard.Lock()) - return; - - BMessage* data = clipboard.Data(); - - const uint8* windowsData; - uint8 windows[512]; - ssize_t length; - if (data->FindData("ids", B_RAW_TYPE, - (const void**)&windowsData, &length) != B_OK) - length = 0; - - if (length > (ssize_t)sizeof(windows)) - length = sizeof(windows); - if (length > 0) - memcpy(windows, windowsData, length); - - _SanitizeIDs(data, windows, length); - - status_t status = B_OK; - if (_UpdateIDs(set, windows, sizeof(windows), &length)) { - // add/remove our ID-to-team mapping - BString id("id-"); - id << fWindowNumber; - - if (set) - data->AddInt32(id.String(), Team()); - else - data->RemoveName(id.String()); - - data->RemoveName("ids"); - //if (data->ReplaceData("ids", B_RAW_TYPE, windows, length) != B_OK) - data->AddData("ids", B_RAW_TYPE, windows, length); - - status = clipboard.Commit(true); - } - - clipboard.Unlock(); - - if (status == B_OK) - break; - } -#else // !__HAIKU__ - // use a file to store the IDs - unfortunately, locking - // doesn't work on BeOS either here - int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT); - if (fd < 0) - return; - - struct flock lock; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_CUR; - lock.l_start = 0; - lock.l_len = -1; - fcntl(fd, F_SETLKW, &lock); - - uint8 windows[512]; - ssize_t length = read_pos(fd, 0, windows, sizeof(windows)); - if (length < 0) { - close(fd); - return; - } - - if (length > (ssize_t)sizeof(windows)) - length = sizeof(windows); - - if (_UpdateIDs(set, windows, sizeof(windows), &length)) - write_pos(fd, 0, windows, length); - - close(fd); -#endif // !__HAIKU__ -} - - -void -TermApp::_UnregisterTerminal() -{ - _UpdateRegistration(false); -} - - -void -TermApp::_RegisterTerminal() -{ - _UpdateRegistration(true); -} - - //#ifndef B_NETPOSITIVE_APP_SIGNATURE //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS" //#endif diff --git a/src/apps/terminal/TermApp.h b/src/apps/terminal/TermApp.h index f0b9abe117..f7275a97e4 100644 --- a/src/apps/terminal/TermApp.h +++ b/src/apps/terminal/TermApp.h @@ -58,23 +58,7 @@ protected: virtual void ArgvReceived(int32 argc, char** argv); private: - status_t _MakeTermWindow(BRect& frame, - uint32 workspaces); - status_t _GetWindowPositionFile(BFile* file, - uint32 openMode); - status_t _LoadWindowPosition(BRect* frame, - uint32* workspaces); - status_t _SaveWindowPosition(BMessage* message); - void _SwitchTerm(); - void _ActivateTermWindow(team_id id); - bool _IsSwitchTarget(team_id id); - void _SanitizeIDs(BMessage* data, uint8* windows, - ssize_t length); - bool _UpdateIDs(bool set, uint8* windows, - ssize_t maxLength, ssize_t* _length); - void _UpdateRegistration(bool set); - void _UnregisterTerminal(); - void _RegisterTerminal(); + status_t _MakeTermWindow(); void _HandleChildCleanup(); static void _SigChildHandler(int signal, void* data); @@ -85,12 +69,8 @@ private: private: bool fStartFullscreen; BString fWindowTitle; - bool fWindowTitleUserDefined; - int32 fWindowNumber; BWindow* fTermWindow; - BRect fTermFrame; - uint32 fTermWorkspaces; Arguments* fArgs; }; diff --git a/src/apps/terminal/TermConst.h b/src/apps/terminal/TermConst.h index 04fd34a8b4..2e759f4405 100644 --- a/src/apps/terminal/TermConst.h +++ b/src/apps/terminal/TermConst.h @@ -93,6 +93,8 @@ static const uint32 MSG_REPORT_MOUSE_EVENT = 'mous'; static const uint32 MSG_SAVE_WINDOW_POSITION = 'swps'; static const uint32 MSG_MOVE_TAB_LEFT = 'mvtl'; static const uint32 MSG_MOVE_TAB_RIGHT = 'mvtr'; +static const uint32 MSG_ACTIVATE_TERM = 'msat'; + // Preference Read/Write Keys static const char* const PREF_HALF_FONT_FAMILY = "Half Font Family"; diff --git a/src/apps/terminal/TermWindow.cpp b/src/apps/terminal/TermWindow.cpp index ed7f67e6ff..2e011d5d5a 100644 --- a/src/apps/terminal/TermWindow.cpp +++ b/src/apps/terminal/TermWindow.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,6 +36,8 @@ #include #include +#include + #include "ActiveProcessInfo.h" #include "Arguments.h" #include "AppearPrefView.h" @@ -64,6 +68,7 @@ static const uint32 kEditTabTitle = 'ETti'; static const uint32 kEditWindowTitle = 'EWti'; static const uint32 kTabTitleChanged = 'TTch'; static const uint32 kWindowTitleChanged = 'WTch'; +static const uint32 kUpdateSwitchTerminalsMenuItem = 'Ustm'; #undef B_TRANSLATE_CONTEXT @@ -150,13 +155,10 @@ struct TermWindow::Session { // #pragma mark - TermWindow -TermWindow::TermWindow(BRect frame, const BString& title, - bool isUserDefinedTitle, int32 windowIndex, uint32 workspaces, - Arguments* args) +TermWindow::TermWindow(const BString& title, Arguments* args) : - BWindow(frame, title, B_DOCUMENT_WINDOW, - B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE, workspaces), - fWindowIndex(windowIndex), + BWindow(BRect(0, 0, 0, 0), title, B_DOCUMENT_WINDOW, + B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE), fTitleUpdateRunner(this, BMessage(kUpdateTitles), 1000000), fNextSessionID(0), fTabView(NULL), @@ -178,27 +180,55 @@ TermWindow::TermWindow(BRect frame, const BString& title, fMatchWord(false), fFullScreen(false) { + // register this terminal + fTerminalRoster.Register(Team(), this); + fTerminalRoster.SetListener(this); + int32 id = fTerminalRoster.ID(); + // apply the title settings - fTitle.title = title; fTitle.pattern = title; - fTitle.patternUserDefined = isUserDefinedTitle; + if (fTitle.pattern.Length() == 0) { + fTitle.pattern = B_TRANSLATE("Terminal"); + + if (id >= 0) + fTitle.pattern << " " << id + 1; + + fTitle.patternUserDefined = false; + } else + fTitle.patternUserDefined = true; + + fTitle.title = fTitle.pattern; + fTitle.pattern = title; + _TitleSettingsChanged(); + // get the saved window position and workspaces + BRect frame; + uint32 workspaces; + if (_LoadWindowPosition(&frame, &workspaces) == B_OK) { + // apply + MoveTo(frame.LeftTop()); + ResizeTo(frame.Width(), frame.Height()); + SetWorkspaces(workspaces); + } else { + // use computed defaults + int i = id / 16; + int j = id % 16; + int k = (j * 16) + (i * 64) + 50; + int l = (j * 16) + 50; + + MoveTo(k, l); + } + + // init the GUI and add a tab _InitWindow(); _AddTab(args); - - // register as an application roster listener -- we want to know when other - // terminals are started and quit, so we can update the our - // "Switch Terminals" menu item. - be_roster->StartWatching(this); - - _UpdateSwitchTerminalsMenuItem(); } TermWindow::~TermWindow() { - be_roster->StopWatching(this); + fTerminalRoster.Unregister(); _FinishTitleDialog(); @@ -301,10 +331,7 @@ TermWindow::QuitRequested() if (!_CanClose(-1)) return false; - BMessage position = BMessage(MSG_SAVE_WINDOW_POSITION); - position.AddRect("rect", Frame()); - position.AddInt32("workspaces", Workspaces()); - be_app->PostMessage(&position); + _SaveWindowPosition(); return BWindow::QuitRequested(); } @@ -423,6 +450,98 @@ TermWindow::_SetupMenu() ; AddChild(fMenuBar); + + _UpdateSwitchTerminalsMenuItem(); +} + + +status_t +TermWindow::_GetWindowPositionFile(BFile* file, uint32 openMode) +{ + BPath path; + status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); + if (status != B_OK) + return status; + + status = path.Append("Terminal_windows"); + if (status != B_OK) + return status; + + return file->SetTo(path.Path(), openMode); +} + + +status_t +TermWindow::_LoadWindowPosition(BRect* frame, uint32* workspaces) +{ + status_t status; + BMessage position; + + BFile file; + status = _GetWindowPositionFile(&file, B_READ_ONLY); + if (status != B_OK) + return status; + + status = position.Unflatten(&file); + + file.Unset(); + + if (status != B_OK) + return status; + + int32 id = fTerminalRoster.ID(); + status = position.FindRect("rect", id, frame); + if (status != B_OK) + return status; + + int32 _workspaces; + status = position.FindInt32("workspaces", id, &_workspaces); + if (status != B_OK) + return status; + if (modifiers() & B_SHIFT_KEY) + *workspaces = _workspaces; + else + *workspaces = B_CURRENT_WORKSPACE; + + return B_OK; +} + + +status_t +TermWindow::_SaveWindowPosition() +{ + BFile file; + BMessage originalSettings; + + // We append ourself to the existing settings file + // So we have to read it, insert our BMessage, and rewrite it. + + status_t status = _GetWindowPositionFile(&file, B_READ_ONLY); + if (status == B_OK) { + originalSettings.Unflatten(&file); + // No error checking on that : it fails if the settings + // file is missing, but we can create it. + + file.Unset(); + } + + // Append the new settings + int32 id = fTerminalRoster.ID(); + BRect rect(Frame()); + if (originalSettings.ReplaceRect("rect", id, rect) != B_OK) + originalSettings.AddRect("rect", rect); + + int32 workspaces = Workspaces(); + if (originalSettings.ReplaceInt32("workspaces", id, workspaces) != B_OK) + originalSettings.AddInt32("workspaces", workspaces); + + // Resave the whole thing + status = _GetWindowPositionFile (&file, + B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + if (status != B_OK) + return status; + + return originalSettings.Flatten(&file); } @@ -472,7 +591,7 @@ TermWindow::MessageReceived(BMessage *message) break; case MENU_SWITCH_TERM: - be_app->PostMessage(MENU_SWITCH_TERM); + _SwitchTerminal(); break; case MENU_NEW_TERM: @@ -809,16 +928,9 @@ TermWindow::MessageReceived(BMessage *message) _OpenSetWindowTitleDialog(); break; - case B_SOME_APP_LAUNCHED: - case B_SOME_APP_QUIT: - { - BString signature; - if (message->FindString("be:signature", &signature) == B_OK - && signature == TERM_SIGNATURE) { - _UpdateSwitchTerminalsMenuItem(); - } + case kUpdateSwitchTerminalsMenuItem: + _UpdateSwitchTerminalsMenuItem(); break; - } default: BWindow::MessageReceived(message); @@ -830,7 +942,8 @@ TermWindow::MessageReceived(BMessage *message) void TermWindow::WindowActivated(bool activated) { - BWindow::WindowActivated(activated); + if (activated) + _UpdateSwitchTerminalsMenuItem(); } @@ -1164,6 +1277,28 @@ TermWindow::FrameResized(float newWidth, float newHeight) } +void +TermWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces) +{ + fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); +} + + +void +TermWindow::WorkspaceActivated(int32 workspace, bool state) +{ + fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); +} + + +void +TermWindow::Minimize(bool minimize) +{ + BWindow::Minimize(minimize); + fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); +} + + void TermWindow::TabSelected(SmartTabView* tabView, int32 index) { @@ -1290,6 +1425,13 @@ TermWindow::SetTitleDialogDone(SetTitleDialog* dialog) } +void +TermWindow::TerminalInfosUpdated(TerminalRoster* roster) +{ + PostMessage(kUpdateSwitchTerminalsMenuItem); +} + + void TermWindow::PreviousTermView(TermView* view) { @@ -1372,12 +1514,7 @@ TermWindow::_MakeWindowSizeMenu() void TermWindow::_UpdateSwitchTerminalsMenuItem() { - // get the running Terminal teams - BList teams; - be_roster->GetAppList(TERM_SIGNATURE, &teams); - - // update the menu item - fSwitchTerminalsMenuItem->SetEnabled(teams.CountItems() > 1); + fSwitchTerminalsMenuItem->SetEnabled(_FindSwitchTerminalTarget() >= 0); } @@ -1435,8 +1572,8 @@ TermWindow::_UpdateSessionTitle(int32 index) return; // evaluate the window title pattern - WindowTitlePlaceholderMapper windowMapper(activeProcessInfo, fWindowIndex, - sessionTitle); + WindowTitlePlaceholderMapper windowMapper(activeProcessInfo, + fTerminalRoster.ID() + 1, sessionTitle); const BString& windowTitle = PatternEvaluator::Evaluate(fTitle.pattern, windowMapper); @@ -1530,6 +1667,60 @@ TermWindow::_FinishTitleDialog() } +void +TermWindow::_SwitchTerminal() +{ + team_id teamID = _FindSwitchTerminalTarget(); + if (teamID < 0) + return; + + BMessenger app(TERM_SIGNATURE, teamID); + app.SendMessage(MSG_ACTIVATE_TERM); +} + + +team_id +TermWindow::_FindSwitchTerminalTarget() +{ + AutoLocker rosterLocker(fTerminalRoster); + + team_id myTeamID = Team(); + + int32 numTerms = fTerminalRoster.CountTerminals(); + if (numTerms <= 1) + return -1; + + // Find our position in the Terminal teams. + int32 i; + + for (i = 0; i < numTerms; i++) { + if (myTeamID == fTerminalRoster.TerminalAt(i)->team) + break; + } + + if (i == numTerms) { + // we didn't find ourselves -- that shouldn't happen + return -1; + } + + uint32 currentWorkspace = 1L << current_workspace(); + + while (true) { + if (--i < 0) + i = numTerms - 1; + + const TerminalRoster::Info* info = fTerminalRoster.TerminalAt(i); + if (info->team == myTeamID) { + // That's ourselves again. We've run through the complete list. + return -1; + } + + if (!info->minimized && (info->workspaces & currentWorkspace) != 0) + return info->team; + } +} + + TermWindow::SessionID TermWindow::_NewSessionID() { diff --git a/src/apps/terminal/TermWindow.h b/src/apps/terminal/TermWindow.h index 436a4dbf92..1f9d9891cb 100644 --- a/src/apps/terminal/TermWindow.h +++ b/src/apps/terminal/TermWindow.h @@ -38,10 +38,12 @@ #include "SmartTabView.h" #include "SetTitleDialog.h" +#include "TerminalRoster.h" #include "TermView.h" class Arguments; +class BFile; class BFont; class BMenu; class BMenuBar; @@ -51,11 +53,11 @@ class TermViewContainerView; class TermWindow : public BWindow, private SmartTabView::Listener, - private TermView::Listener, private SetTitleDialog::Listener { + private TermView::Listener, private SetTitleDialog::Listener, + private TerminalRoster::Listener { public: - TermWindow(BRect frame, const BString& title, - bool isUserDefinedTitle, int32 windowIndex, - uint32 workspaces, Arguments* args); + TermWindow(const BString& title, + Arguments* args); virtual ~TermWindow(); void SessionChanged(); @@ -63,10 +65,15 @@ public: protected: virtual bool QuitRequested(); virtual void MessageReceived(BMessage* message); - virtual void WindowActivated(bool); + virtual void WindowActivated(bool activated); virtual void MenusBeginning(); virtual void Zoom(BPoint leftTop, float width, float height); virtual void FrameResized(float newWidth, float newHeight); + virtual void WorkspacesChanged(uint32 oldWorkspaces, + uint32 newWorkspaces); + virtual void WorkspaceActivated(int32 workspace, + bool state); + virtual void Minimize(bool minimize); private: // SmartTabView::Listener @@ -92,6 +99,9 @@ private: bool titleUserDefined); virtual void SetTitleDialogDone(SetTitleDialog* dialog); + // TerminalRoster::Listener + virtual void TerminalInfosUpdated(TerminalRoster* roster); + private: struct Title { BString title; @@ -120,6 +130,7 @@ private: struct Session; +private: void _SetTermColors(TermViewContainerView* termView); void _InitWindow(); void _SetupMenu(); @@ -127,6 +138,12 @@ private: static BMenu* _MakeWindowSizeMenu(); void _UpdateSwitchTerminalsMenuItem(); + status_t _GetWindowPositionFile(BFile* file, + uint32 openMode); + status_t _LoadWindowPosition(BRect* frame, + uint32* workspaces); + status_t _SaveWindowPosition(); + void _GetPreferredFont(BFont &font); status_t _DoPageSetup(); void _DoPrint(); @@ -160,13 +177,17 @@ private: void _OpenSetWindowTitleDialog(); void _FinishTitleDialog(); + void _SwitchTerminal(); + team_id _FindSwitchTerminalTarget(); + SessionID _NewSessionID(); int32 _NewSessionIndex(); private: + TerminalRoster fTerminalRoster; + Title fTitle; BString fSessionTitlePattern; - int32 fWindowIndex; BMessageRunner fTitleUpdateRunner; BList fSessions; diff --git a/src/apps/terminal/TerminalRoster.cpp b/src/apps/terminal/TerminalRoster.cpp new file mode 100644 index 0000000000..3a9ffa2797 --- /dev/null +++ b/src/apps/terminal/TerminalRoster.cpp @@ -0,0 +1,520 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TerminalRoster.h" + +#include + +#include + +#include +#include +#include + +#include + +#include "TermConst.h" + + +static const bigtime_t kAppsRunningCheckInterval = 1000000; + + +// #pragma mark - Info + + +/*! Creates an Info with the given \a id and \a team ID. + \c workspaces is set to 0 and \c minimized to \c true. +*/ +TerminalRoster::Info::Info(int32 id, team_id team) + : + id(id), + team(team), + workspaces(0), + minimized(true) +{ +} + + +/*! Create an Info and initializes its data from \a archive. +*/ +TerminalRoster::Info::Info(const BMessage& archive) +{ + if (archive.FindInt32("id", &id) != B_OK) + id = -1; + if (archive.FindInt32("team", &team) != B_OK) + team = -1; + if (archive.FindUInt32("workspaces", &workspaces) != B_OK) + workspaces = 0; + if (archive.FindBool("minimized", &minimized) != B_OK) + minimized = true; +} + + +/*! Writes the Info's data into fields of \a archive. + The const BMessage& constructor can restore an identical Info from it. +*/ +status_t +TerminalRoster::Info::Archive(BMessage& archive) const +{ + status_t error; + if ((error = archive.AddInt32("id", id)) != B_OK + || (error = archive.AddInt32("team", team)) != B_OK + || (error = archive.AddUInt32("workspaces", workspaces)) != B_OK + || (error = archive.AddBool("minimized", minimized)) != B_OK) { + return error; + } + + return B_OK; +} + + +/*! Compares two Infos. + Infos are considered equal, iff all data members are. +*/ +bool +TerminalRoster::Info::operator==(const Info& other) const +{ + return id == other.id && team == other.team + && workspaces == other.workspaces && minimized == other.minimized; +} + + +// #pragma mark - TerminalRoster + + +/*! Creates a TerminalRoster. + Most methods cannot be used until Register() has been invoked. +*/ +TerminalRoster::TerminalRoster() + : + BHandler("terminal roster"), + fLock("terminal roster"), + fClipboard(TERM_SIGNATURE), + fInfos(10, true), + fOurInfo(NULL), + fLastCheckedTime(0), + fListener(NULL), + fInfosUpdated(false) +{ +} + + +/*! Locks the object. + Also makes sure the roster list is reasonably up-to-date. +*/ +bool +TerminalRoster::Lock() +{ + // lock + bool locked = fLock.Lock(); + if (!locked) + return false; + + // make sure we're registered + if (fOurInfo == NULL) { + fLock.Unlock(); + return false; + } + + // If the check interval has passed, make sure all infos still have running + // teams. + bigtime_t now = system_time(); + if (fLastCheckedTime + kAppsRunningCheckInterval) { + bool needsUpdate = false; + for (int32 i = 0; const Info* info = TerminalAt(i); i++) { + if (!_TeamIsRunning(info->team)) { + needsUpdate = true; + break; + } + } + + if (needsUpdate) { + AutoLocker clipboardLocker(fClipboard); + if (clipboardLocker.IsLocked()) { + if (_UpdateInfos(true) == B_OK) + _UpdateClipboard(); + } + } else + fLastCheckedTime = now; + } + + return true; +} + + +/*! Unlocks the object. + As a side effect the listener will be notified, if the terminal list has + changed in any way. +*/ +void +TerminalRoster::Unlock() +{ + if (fOurInfo != NULL && fInfosUpdated) { + // the infos have changed -- notify our listener + _NotifyListener(); + } + + fLock.Unlock(); +} + + +/*! Registers a terminal with the roster and establishes a link. + + The object attaches itself to the supplied \a looper and will receive + updates via messaging (obviously the looper must run (not necessarily + right now) for this to work). + + \param teamID The team ID of this team. + \param looper A looper the object can attach itself to. + \return \c B_OK, if successful, another error code otherwise. +*/ +status_t +TerminalRoster::Register(team_id teamID, BLooper* looper) +{ + AutoLocker locker(fLock); + + if (fOurInfo != NULL) { + // already registered + return B_BAD_VALUE; + } + + // lock the clipboard + AutoLocker clipboardLocker(fClipboard); + if (!clipboardLocker.IsLocked()) + return B_BAD_VALUE; + + // get the current infos from the clipboard + status_t error = _UpdateInfos(true); + if (error != B_OK) + return error; + + // find an unused ID + int32 id = 0; + for (int32 i = 0; const Info* info = TerminalAt(i); i++) { + if (info->id > id) + break; + id++; + } + + // create our own info + fOurInfo = new(std::nothrow) Info(id, teamID); + if (fOurInfo == NULL) + return B_NO_MEMORY; + + // insert it + if (!fInfos.BinaryInsert(fOurInfo, &_CompareInfos)) { + delete fOurInfo; + fOurInfo = NULL; + return B_NO_MEMORY; + } + + // update the clipboard + error = _UpdateClipboard(); + if (error != B_OK) { + fInfos.MakeEmpty(true); + fOurInfo = NULL; + return error; + } + + // add ourselves to the looper and start watching + looper->AddHandler(this); + + be_roster->StartWatching(this, B_REQUEST_QUIT); + fClipboard.StartWatching(this); + + // Update again in case we've missed a update message sent before we were + // listening. + _UpdateInfos(false); + + return B_OK; +} + + +/*! Unregisters the terminal from the roster and closes the link. + + Basically undoes all effects of Register(). +*/ +void +TerminalRoster::Unregister() +{ + AutoLocker locker(fLock); + if (!locker.IsLocked()) + return; + + // stop watching and remove ourselves from the looper + be_roster->StartWatching(this); + fClipboard.StartWatching(this); + + Looper()->RemoveHandler(this); + + // lock the clipboard and get the current infos + AutoLocker clipboardLocker(fClipboard); + if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK) + return; + + // remove our info and update the clipboard + fInfos.RemoveItem(fOurInfo); + delete fOurInfo; + fOurInfo = NULL; + + _UpdateClipboard(); +} + + +/*! Returns the ID assigned to this terminal when it was registered. +*/ +int32 +TerminalRoster::ID() const +{ + return fOurInfo != NULL ? fOurInfo->id : -1; +} + + +/*! Updates this terminal's window status. + All other running terminals will be notified, if the status changed. + + \param minimized \c true, if the window is minimized. + \param workspaces The window's workspaces mask. +*/ +void +TerminalRoster::SetWindowInfo(bool minimized, uint32 workspaces) +{ + AutoLocker locker(this); + if (!locker.IsLocked()) + return; + + if (minimized == fOurInfo->minimized && workspaces == fOurInfo->workspaces) + return; + + fOurInfo->minimized = minimized; + fOurInfo->workspaces = workspaces; + fInfosUpdated = true; + + // lock the clipboard and get the current infos + AutoLocker clipboardLocker(fClipboard); + if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK) + return; + + // update the clipboard to make our change known to the others + _UpdateClipboard(); +} + + +/*! Overriden to handle update messages. +*/ +void +TerminalRoster::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_SOME_APP_QUIT: + { + BString signature; + if (message->FindString("be:signature", &signature) != B_OK + || signature != TERM_SIGNATURE) { + break; + } + // fall through + } + + case B_CLIPBOARD_CHANGED: + { + // lock ourselves and the clipboard and update the infos + AutoLocker locker(this); + AutoLocker clipboardLocker(fClipboard); + if (clipboardLocker.IsLocked()) { + _UpdateInfos(false); + + if (fInfosUpdated) + _NotifyListener(); + } + + break; + } + + default: + BHandler::MessageReceived(message); + break; + } +} + + +/*! Updates the terminal info list from the clipboard. + + \param checkApps If \c true, it is checked for each found info whether the + respective team is still running. If not, the info is removed from the + list (though not from the clipboard). + \return \c B_OK, if the update went fine, another error code otherwise. When + an error occurs the object state will still be consistent, but might no + longer be up-to-date. +*/ +status_t +TerminalRoster::_UpdateInfos(bool checkApps) +{ + BMessage* data = fClipboard.Data(); + + // find out how many infos we can expect + type_code type; + int32 count; + status_t error = data->GetInfo("teams", &type, &count); + if (error != B_OK) + count = 0; + + // create an info list from the message + InfoList infos(10, true); + for (int32 i = 0; i < count; i++) { + // get the team's message + BMessage teamData; + error = data->FindMessage("teams", i, &teamData); + if (error != B_OK) + return error; + + // create the info + Info* info = new(std::nothrow) Info(teamData); + if (info == NULL) + return B_NO_MEMORY; + if (info->id < 0 || info->team < 0 + || infos.BinarySearchByKey(info->id, &_CompareIDInfo) != NULL + || (checkApps && !_TeamIsRunning(info->team))) { + // invalid/duplicate info -- skip + delete info; + fInfosUpdated = true; + continue; + } + + // add it to the list + if (!infos.BinaryInsert(info, &_CompareInfos)) { + delete info; + return B_NO_MEMORY; + } + } + + // update the current info list from the infos we just read + int32 oldIndex = 0; + int32 newIndex = 0; + while (oldIndex < fInfos.CountItems() || newIndex < infos.CountItems()) { + Info* oldInfo = fInfos.ItemAt(oldIndex); + Info* newInfo = infos.ItemAt(newIndex); + + if (oldInfo == NULL || (newInfo != NULL && oldInfo->id > newInfo->id)) { + // new info is not in old list -- transfer it + if (!fInfos.AddItem(newInfo, oldIndex++)) + return B_NO_MEMORY; + infos.RemoveItemAt(newIndex); + fInfosUpdated = true; + } else if (newInfo == NULL || oldInfo->id < newInfo->id) { + // old info is not in new list -- delete it, unless it's our own + if (oldInfo == fOurInfo) { + oldIndex++; + } else { + delete fInfos.RemoveItemAt(oldIndex); + fInfosUpdated = true; + } + } else { + // info is in both lists -- update the old info, unless it's our own + if (oldInfo != fOurInfo) { + if (*oldInfo != *newInfo) { + *oldInfo = *newInfo; + fInfosUpdated = true; + } + } + + oldIndex++; + newIndex++; + } + } + + if (checkApps) + fLastCheckedTime = system_time(); + + return B_OK; +} + + +/*! Updates the clipboard with the object's terminal info list. + + \return \c B_OK, if the update went fine, another error code otherwise. When + an error occurs the object state will still be consistent, but might no + longer be in sync with the clipboard. +*/ +status_t +TerminalRoster::_UpdateClipboard() +{ + // get the clipboard data message + BMessage* data = fClipboard.Data(); + if (data == NULL) + return B_BAD_VALUE; + + // clear the message and add all infos + data->MakeEmpty(); + + for (int32 i = 0; const Info* info = TerminalAt(i); i++) { + BMessage teamData; + status_t error = info->Archive(teamData); + if (error != B_OK + || (error = data->AddMessage("teams", &teamData)) != B_OK) { + fClipboard.Revert(); + return error; + } + } + + // commit the changes + status_t error = fClipboard.Commit(); + if (error != B_OK) { + fClipboard.Revert(); + return error; + } + + return B_OK; +} + + +/*! Notifies the listener, if something has changed. +*/ +void +TerminalRoster::_NotifyListener() +{ + if (!fInfosUpdated) + return; + + if (fListener != NULL) + fListener->TerminalInfosUpdated(this); + + fInfosUpdated = false; +} + + +/*static*/ int +TerminalRoster::_CompareInfos(const Info* a, const Info* b) +{ + return a->id - b->id; +} + + +/*static*/ int +TerminalRoster::_CompareIDInfo(const int32* id, const Info* info) +{ + return *id - info->id; +} + + +bool +TerminalRoster::_TeamIsRunning(team_id teamID) +{ + // we are running for sure + if (fOurInfo != NULL && fOurInfo->team == teamID) + return true; + + team_info info; + return get_team_info(teamID, &info) == B_OK; +} + + +// #pragma mark - Listener + + +TerminalRoster::Listener::~Listener() +{ +} diff --git a/src/apps/terminal/TerminalRoster.h b/src/apps/terminal/TerminalRoster.h new file mode 100644 index 0000000000..26ad8a4034 --- /dev/null +++ b/src/apps/terminal/TerminalRoster.h @@ -0,0 +1,113 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef TERMINAL_ROSTER_H +#define TERMINAL_ROSTER_H + + +#include +#include +#include + +#include + + +/*! Provides access to the (virtual) global terminal roster. + + Register() registers our terminal with the roster and establishes the link + to the roster. From then on CountTerminals() and TerminalAt() provide + access to the terminal list and allows to post our window status via + SetWindowInfo(). The list is automatically kept up to date. Unregister() + will remove our terminal from the roster and terminate the connection. + + A single Listener can be set. It is notified whenever the terminal list + has changed. +*/ +class TerminalRoster : private BHandler { +public: + class Listener; + +public: + struct Info { + int32 id; + team_id team; + uint32 workspaces; + bool minimized; + + public: + Info(int32 id, team_id team); + Info(const BMessage& archive); + + status_t Archive(BMessage& archive) const; + + bool operator==(const Info& other) const; + bool operator!=(const Info& other) const + { return !(*this == other); } + }; + +public: + TerminalRoster(); + + bool Lock(); + void Unlock(); + + status_t Register(team_id teamID, BLooper* looper); + void Unregister(); + bool IsRegistered() const + { return fOurInfo != NULL; } + + int32 ID() const; + + void SetWindowInfo(bool minimized, + uint32 workspaces); + + // roster must be locked + int32 CountTerminals() const + { return fInfos.CountItems(); } + const Info* TerminalAt(int32 index) const + { return fInfos.ItemAt(index); } + + void SetListener(Listener* listener) + { fListener = listener; } + +private: + // BHandler + virtual void MessageReceived(BMessage* message); + +private: + typedef BObjectList InfoList; + +private: + status_t _UpdateInfos(bool checkApps); + status_t _UpdateClipboard(); + + void _NotifyListener(); + + static int _CompareInfos(const Info* a, const Info* b); + static int _CompareIDInfo(const int32* id, + const Info* info); + + bool _TeamIsRunning(team_id teamID); + +private: + BLocker fLock; + BClipboard fClipboard; + InfoList fInfos; + Info* fOurInfo; + bigtime_t fLastCheckedTime; + Listener* fListener; + bool fInfosUpdated; +}; + + +class TerminalRoster::Listener { +public: + virtual ~Listener(); + + virtual void TerminalInfosUpdated(TerminalRoster* roster) + = 0; +}; + + +#endif // TERMINAL_ROSTER_H