xref: /third_party/gptfdisk/gptcurses.cc (revision cf200d32)
1/*
2 *    Implementation of GPTData class derivative with curses-based text-mode
3 *    interaction
4 *    Copyright (C) 2011-2024 Roderick W. Smith
5 *
6 *    This program is free software; you can redistribute it and/or modify
7 *    it under the terms of the GNU General Public License as published by
8 *    the Free Software Foundation; either version 2 of the License, or
9 *    (at your option) any later version.
10 *
11 *    This program is distributed in the hope that it will be useful,
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *    GNU General Public License for more details.
15 *
16 *    You should have received a copy of the GNU General Public License along
17 *    with this program; if not, write to the Free Software Foundation, Inc.,
18 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 */
21
22#include <clocale>
23#include <iostream>
24#include <string>
25#include <sstream>
26#if defined (__APPLE__) || (__FreeBSD__)
27#include <ncurses.h>
28#else
29#include <ncursesw/ncurses.h>
30#endif
31#include "gptcurses.h"
32#include "support.h"
33
34using namespace std;
35
36// # of lines to reserve for general information and headers (RESERVED_TOP)
37// and for options and messages (RESERVED_BOTTOM)
38#define RESERVED_TOP 7
39#define RESERVED_BOTTOM 5
40
41int GPTDataCurses::numInstances = 0;
42
43GPTDataCurses::GPTDataCurses(void) {
44   if (numInstances > 0) {
45      refresh();
46   } else {
47      setlocale( LC_ALL , "" );
48      initscr();
49      cbreak();
50      noecho();
51      intrflush(stdscr, false);
52      keypad(stdscr, true);
53      nonl();
54      numInstances++;
55   } // if/else
56   firstSpace = NULL;
57   lastSpace = NULL;
58   currentSpace = NULL;
59   currentSpaceNum = -1;
60   whichOptions = ""; // current set of options
61   currentKey = 'b'; // currently selected option
62   displayType = USE_CURSES;
63} // GPTDataCurses constructor
64
65GPTDataCurses::~GPTDataCurses(void) {
66   numInstances--;
67   if ((numInstances == 0) && !isendwin())
68      endwin();
69} // GPTDataCurses destructor
70
71/************************************************
72 *                                              *
73 * Functions relating to Spaces data structures *
74 *                                              *
75 ************************************************/
76
77void GPTDataCurses::EmptySpaces(void) {
78   Space *trash;
79
80   while (firstSpace != NULL) {
81      trash = firstSpace;
82      firstSpace = firstSpace->nextSpace;
83      delete trash;
84   } // if
85   numSpaces = 0;
86   lastSpace = NULL;
87} // GPTDataCurses::EmptySpaces()
88
89// Create Spaces from partitions. Does NOT creates Spaces to represent
90// unpartitioned space on the disk.
91// Returns the number of Spaces created.
92int GPTDataCurses::MakeSpacesFromParts(void) {
93   uint32_t i;
94   Space *tempSpace;
95
96   EmptySpaces();
97   for (i = 0; i < numParts; i++) {
98      if (partitions[i].IsUsed()) {
99         tempSpace = new Space;
100         tempSpace->firstLBA = partitions[i].GetFirstLBA();
101         tempSpace->lastLBA = partitions[i].GetLastLBA();
102         tempSpace->origPart = &partitions[i];
103         tempSpace->partNum = (int) i;
104         LinkToEnd(tempSpace);
105      } // if
106   } // for
107   return numSpaces;
108} // GPTDataCurses::MakeSpacesFromParts()
109
110// Add a single empty Space to the current Spaces linked list and sort the result....
111void GPTDataCurses::AddEmptySpace(uint64_t firstLBA, uint64_t lastLBA) {
112   Space *tempSpace;
113
114   tempSpace = new Space;
115   tempSpace->firstLBA = firstLBA;
116   tempSpace->lastLBA = lastLBA;
117   tempSpace->origPart = &emptySpace;
118   tempSpace->partNum = -1;
119   LinkToEnd(tempSpace);
120   SortSpaces();
121} // GPTDataCurses::AddEmptySpace();
122
123// Add Spaces to represent the unallocated parts of the partition table.
124// Returns the number of Spaces added.
125int GPTDataCurses::AddEmptySpaces(void) {
126   int numAdded = 0;
127   Space *current;
128
129   SortSpaces();
130   if (firstSpace == NULL) {
131      AddEmptySpace(GetFirstUsableLBA(), GetLastUsableLBA());
132      numAdded++;
133   } else {
134      current = firstSpace;
135      while ((current != NULL) /* && (current->partNum != -1) */ ) {
136         if ((current == firstSpace) && (current->firstLBA > GetFirstUsableLBA())) {
137            AddEmptySpace(GetFirstUsableLBA(), current->firstLBA - 1);
138            numAdded++;
139         } // if
140         if ((current == lastSpace) && (current->lastLBA < GetLastUsableLBA())) {
141            AddEmptySpace(current->lastLBA + 1, GetLastUsableLBA());
142            numAdded++;
143         } // if
144         if ((current->prevSpace != NULL) && (current->prevSpace->lastLBA < (current->firstLBA - 1))) {
145            AddEmptySpace(current->prevSpace->lastLBA + 1, current->firstLBA - 1);
146            numAdded++;
147         } // if
148         current = current->nextSpace;
149      } // while
150   } // if/else
151   return numAdded;
152} // GPTDataCurses::AddEmptySpaces()
153
154// Remove the specified Space from the linked list and set its previous and
155// next pointers to NULL.
156void GPTDataCurses::UnlinkSpace(Space *theSpace) {
157   if (theSpace != NULL) {
158      if (theSpace->prevSpace != NULL)
159         theSpace->prevSpace->nextSpace = theSpace->nextSpace;
160      if (theSpace->nextSpace != NULL)
161         theSpace->nextSpace->prevSpace = theSpace->prevSpace;
162      if (theSpace == firstSpace)
163         firstSpace = theSpace->nextSpace;
164      if (theSpace == lastSpace)
165         lastSpace = theSpace->prevSpace;
166      theSpace->nextSpace = NULL;
167      theSpace->prevSpace = NULL;
168      numSpaces--;
169   } // if
170} // GPTDataCurses::UnlinkSpace
171
172// Link theSpace to the end of the current linked list.
173void GPTDataCurses::LinkToEnd(Space *theSpace) {
174   if (lastSpace == NULL) {
175      firstSpace = lastSpace = theSpace;
176      theSpace->nextSpace = NULL;
177      theSpace->prevSpace = NULL;
178   } else {
179      theSpace->prevSpace = lastSpace;
180      theSpace->nextSpace = NULL;
181      lastSpace->nextSpace = theSpace;
182      lastSpace = theSpace;
183   } // if/else
184   numSpaces++;
185} // GPTDataCurses::LinkToEnd()
186
187// Sort spaces into ascending order by on-disk position.
188void GPTDataCurses::SortSpaces(void) {
189   Space *oldFirst, *oldLast, *earliest = NULL, *current = NULL;
190
191   oldFirst = firstSpace;
192   oldLast = lastSpace;
193   firstSpace = lastSpace = NULL;
194   while (oldFirst != NULL) {
195      current = earliest = oldFirst;
196      while (current != NULL) {
197         if (current->firstLBA < earliest->firstLBA)
198            earliest = current;
199         current = current->nextSpace;
200      } // while
201      if (oldFirst == earliest)
202         oldFirst = earliest->nextSpace;
203      if (oldLast == earliest)
204         oldLast = earliest->prevSpace;
205      UnlinkSpace(earliest);
206      LinkToEnd(earliest);
207   } // while
208} // GPTDataCurses::SortSpaces()
209
210// Identify the spaces on the disk, a "space" being defined as a partition
211// or an empty gap between, before, or after partitions. The spaces are
212// presented to users in the main menu display.
213void GPTDataCurses::IdentifySpaces(void) {
214   MakeSpacesFromParts();
215   AddEmptySpaces();
216} // GPTDataCurses::IdentifySpaces()
217
218/**************************
219 *                        *
220 * Data display functions *
221 *                        *
222 **************************/
223
224// Display a single Space on line # lineNum.
225// Returns a pointer to the space being displayed
226Space* GPTDataCurses::ShowSpace(int spaceNum, int lineNum) {
227   Space *space;
228   int i = 0;
229#ifdef USE_UTF16
230   char temp[40];
231#endif
232
233   space = firstSpace;
234   while ((space != NULL) && (i < spaceNum)) {
235      space = space->nextSpace;
236      i++;
237   } // while
238   if ((space != NULL) && (lineNum < (LINES - 5))) {
239      ClearLine(lineNum);
240      if (space->partNum == -1) { // space is empty
241         move(lineNum, 12);
242         printw("%s", BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
243         move(lineNum, 24);
244         printw("free space");
245      } else { // space holds a partition
246         move(lineNum, 3);
247         printw("%d", space->partNum + 1);
248         move(lineNum, 12);
249         printw("%s", BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
250         move(lineNum, 24);
251         printw("%s", space->origPart->GetTypeName().c_str());
252         move(lineNum, 50);
253         #ifdef USE_UTF16
254         space->origPart->GetDescription().extract(0, 39, temp, 39);
255         printw(temp);
256         #else
257         printw("%s", space->origPart->GetDescription().c_str());
258         #endif
259      } // if/else
260   } // if
261   return space;
262} // GPTDataCurses::ShowSpace
263
264// Display the partitions, being sure that the space #selected is displayed
265// and highlighting that space.
266// Returns the number of the space being shown (should be selected, but will
267// be -1 if something weird happens)
268int GPTDataCurses::DisplayParts(int selected) {
269   int lineNum = 5, i = 0, retval = -1, numToShow, pageNum;
270   string theLine;
271
272   move(lineNum++, 0);
273   theLine = "Part. #     Size        Partition Type            Partition Name";
274   printw("%s", theLine.c_str());
275   move(lineNum++, 0);
276   theLine = "----------------------------------------------------------------";
277   printw("%s", theLine.c_str());
278   numToShow = LINES - RESERVED_TOP - RESERVED_BOTTOM;
279   pageNum = selected / numToShow;
280   for (i = pageNum * numToShow; i <= (pageNum + 1) * numToShow - 1; i++) {
281      if (i < numSpaces) { // real space; show it
282         if (i == selected) {
283            currentSpaceNum = i;
284            if (displayType == USE_CURSES) {
285               attron(A_REVERSE);
286               currentSpace = ShowSpace(i, lineNum++);
287               attroff(A_REVERSE);
288            } else {
289               currentSpace = ShowSpace(i, lineNum);
290               move(lineNum++, 0);
291               printw(">");
292            }
293            DisplayOptions(i);
294            retval = selected;
295         } else {
296            ShowSpace(i, lineNum++);
297         }
298      } else { // blank in display
299         ClearLine(lineNum++);
300      } // if/else
301   } // for
302   refresh();
303   return retval;
304} // GPTDataCurses::DisplayParts()
305
306/**********************************************
307 *                                            *
308 * Functions corresponding to main menu items *
309 *                                            *
310 **********************************************/
311
312// Delete the specified partition and re-detect partitions and spaces....
313void GPTDataCurses::DeletePartition(int partNum) {
314   if (!GPTData::DeletePartition(partNum))
315      Report("Could not delete partition!");
316   IdentifySpaces();
317   if (currentSpaceNum >= numSpaces) {
318      currentSpaceNum = numSpaces - 1;
319      currentSpace = lastSpace;
320   } // if
321} // GPTDataCurses::DeletePartition()
322
323// Displays information on the specified partition
324void GPTDataCurses::ShowInfo(int partNum) {
325   uint64_t size;
326#ifdef USE_UTF16
327   char temp[NAME_SIZE + 1];
328#endif
329
330   clear();
331   move(2, (COLS - 29) / 2);
332   printw("Information for partition #%d\n\n", partNum + 1);
333   printw("Partition GUID code: %s (%s)\n", partitions[partNum].GetType().AsString().c_str(),
334          partitions[partNum].GetTypeName().c_str());
335   printw("Partition unique GUID: %s\n", partitions[partNum].GetUniqueGUID().AsString().c_str());
336   printw("First sector: %llu (at %s)\n", (long long unsigned int) partitions[partNum].GetFirstLBA(),
337          BytesToIeee(partitions[partNum].GetFirstLBA(), blockSize).c_str());
338   printw("Last sector: %llu (at %s)\n", (long long unsigned int) partitions[partNum].GetLastLBA(),
339          BytesToIeee(partitions[partNum].GetLastLBA(), blockSize).c_str());
340   size = partitions[partNum].GetLastLBA() - partitions[partNum].GetFirstLBA() + 1;
341   printw("Partition size: %llu sectors (%s)\n", (long long unsigned int) size, BytesToIeee(size, blockSize).c_str());
342   printw("Attribute flags: %016llx\n", (long long unsigned int) partitions[partNum].GetAttributes().GetAttributes());
343   #ifdef USE_UTF16
344   partitions[partNum].GetDescription().extract(0, NAME_SIZE , temp, NAME_SIZE );
345   printw("Partition name: '%s'\n", temp);
346   #else
347   printw("Partition name: '%s'\n", partitions[partNum].GetDescription().c_str());
348   #endif
349   PromptToContinue();
350} // GPTDataCurses::ShowInfo()
351
352// Prompt for and change a partition's name....
353void GPTDataCurses::ChangeName(int partNum) {
354   char temp[NAME_SIZE + 1];
355
356   if (ValidPartNum(partNum)) {
357      move(LINES - 4, 0);
358      clrtobot();
359      move(LINES - 4, 0);
360      #ifdef USE_UTF16
361      partitions[partNum].GetDescription().extract(0, NAME_SIZE , temp, NAME_SIZE );
362      printw("Current partition name is '%s'\n", temp);
363      #else
364      printw("Current partition name is '%s'\n", partitions[partNum].GetDescription().c_str());
365      #endif
366      printw("Enter new partition name, or <Enter> to use the current name:\n");
367      echo();
368      getnstr(temp, NAME_SIZE );
369      partitions[partNum].SetName((string) temp);
370      noecho();
371   } // if
372} // GPTDataCurses::ChangeName()
373
374// Change the partition's type code....
375void GPTDataCurses::ChangeType(int partNum) {
376   char temp[80] = "L\0";
377   PartType tempType;
378
379   echo();
380   do {
381      move(LINES - 4, 0);
382      clrtobot();
383      move(LINES - 4, 0);
384      printw("Current type is %04x (%s)\n", partitions[partNum].GetType().GetHexType(), partitions[partNum].GetTypeName().c_str());
385      printw("Hex code or GUID (L to show codes, Enter = %04x): ", partitions[partNum].GetType().GetHexType());
386      getnstr(temp, 79);
387      if ((temp[0] == 'L') || (temp[0] == 'l')) {
388         ShowTypes();
389      } else {
390         if (temp[0] == '\0')
391            tempType = partitions[partNum].GetType().GetHexType();
392         tempType = temp;
393         partitions[partNum].SetType(tempType);
394      } // if
395   } while ((temp[0] == 'L') || (temp[0] == 'l') || (partitions[partNum].GetType() == (GUIDData) "0x0000"));
396   noecho();
397} // GPTDataCurses::ChangeType
398
399// Sets the partition alignment value
400void GPTDataCurses::SetAlignment(void) {
401   int alignment;
402   char conversion_specifier[] = "%d";
403
404   move(LINES - 4, 0);
405   clrtobot();
406   printw("Current partition alignment, in sectors, is %d.", GetAlignment());
407   do {
408      move(LINES - 3, 0);
409      printw("Type new alignment value, in sectors: ");
410      echo();
411      scanw(conversion_specifier, &alignment);
412      noecho();
413   } while ((alignment == 0) || (alignment > MAX_ALIGNMENT));
414   GPTData::SetAlignment(alignment);
415} // GPTDataCurses::SetAlignment()
416
417// Verify the data structures. Note that this function leaves curses mode and
418// relies on the underlying GPTData::Verify() function to report on problems
419void GPTDataCurses::Verify(void) {
420   char junk;
421
422   def_prog_mode();
423   endwin();
424   GPTData::Verify();
425   cout << "\nPress the <Enter> key to continue: ";
426   cin.get(junk);
427   reset_prog_mode();
428   refresh();
429} // GPTDataCurses::Verify()
430
431// Create a new partition in the space pointed to by currentSpace.
432void GPTDataCurses::MakeNewPart(void) {
433   uint64_t size, newFirstLBA = 0, newLastLBA = 0, lastAligned;
434   int partNum;
435   char inLine[80];
436
437   move(LINES - 4, 0);
438   clrtobot();
439   lastAligned = currentSpace->lastLBA + 1;
440   Align(&lastAligned);
441   lastAligned--;
442   // Discard end-alignment attempt if it's giving us an invalid end point....
443   if (!IsFree(lastAligned))
444       lastAligned = currentSpace->lastLBA;
445   while ((newFirstLBA < currentSpace->firstLBA) || (newFirstLBA > currentSpace->lastLBA)) {
446      move(LINES - 4, 0);
447      clrtoeol();
448      newFirstLBA = currentSpace->firstLBA;
449      Align(&newFirstLBA);
450      printw("First sector (%llu-%llu, default = %llu): ", (long long unsigned int) newFirstLBA,
451              (long long unsigned int) currentSpace->lastLBA, (long long unsigned int) newFirstLBA);
452      echo();
453      getnstr(inLine, 79);
454      noecho();
455      newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, sectorAlignment, newFirstLBA);
456      Align(&newFirstLBA);
457   } // while
458   if (newFirstLBA > lastAligned)
459      size = currentSpace->lastLBA - newFirstLBA + 1;
460   else
461      size = lastAligned - newFirstLBA + 1;
462   while ((newLastLBA > currentSpace->lastLBA) || (newLastLBA < newFirstLBA)) {
463      move(LINES - 3, 0);
464      clrtoeol();
465      printw("Size in sectors or {KMGTP} (default = %llu): ", (long long unsigned int) size);
466      echo();
467      getnstr(inLine, 79);
468      noecho();
469      newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, sectorAlignment, size) - 1;
470   } // while
471   partNum = FindFirstFreePart();
472   if (CreatePartition(partNum, newFirstLBA, newLastLBA)) { // created OK; set type code & name....
473      ChangeType(partNum);
474      ChangeName(partNum);
475   } else {
476      Report("Error creating partition!");
477   } // if/else
478} // GPTDataCurses::MakeNewPart()
479
480// Prompt user for permission to save data and, if it's given, do so!
481void GPTDataCurses::SaveData(void) {
482   string answer = "";
483   char inLine[80];
484
485   move(LINES - 4, 0);
486   clrtobot();
487   move (LINES - 2, 14);
488   printw("Warning!! This may destroy data on your disk!");
489   echo();
490   while ((answer != "yes") && (answer != "no")) {
491      move (LINES - 4, 2);
492      printw("Are you sure you want to write the partition table to disk? (yes or no): ");
493      getnstr(inLine, 79);
494      answer = inLine;
495      if ((answer != "yes") && (answer != "no")) {
496         move(LINES - 2, 0);
497         clrtoeol();
498         move(LINES - 2, 14);
499         printw("Please enter 'yes' or 'no'");
500      } // if
501   } // while()
502   noecho();
503   if (answer == "yes") {
504      if (SaveGPTData(1)) {
505         if (!myDisk.DiskSync())
506            Report("The kernel may be using the old partition table. Reboot to use the new\npartition table!");
507      } else {
508         Report("Problem saving data! Your partition table may be damaged!");
509      }
510   }
511} // GPTDataCurses::SaveData()
512
513// Back up the partition table, prompting user for a filename....
514void GPTDataCurses::Backup(void) {
515   char inLine[80];
516
517   ClearBottom();
518   move(LINES - 3, 0);
519   printw("Enter backup filename to save: ");
520   echo();
521   getnstr(inLine, 79);
522   noecho();
523   SaveGPTBackup(inLine);
524} // GPTDataCurses::Backup()
525
526// Load a GPT backup from a file
527void GPTDataCurses::LoadBackup(void) {
528   char inLine[80];
529
530   ClearBottom();
531   move(LINES - 3, 0);
532   printw("Enter backup filename to load: ");
533   echo();
534   getnstr(inLine, 79);
535   noecho();
536   if (!LoadGPTBackup(inLine))
537      Report("Restoration failed!");
538   IdentifySpaces();
539} // GPTDataCurses::LoadBackup()
540
541// Display some basic help information
542void GPTDataCurses::ShowHelp(void) {
543   int i = 0;
544
545   clear();
546   move(0, (COLS - 22) / 2);
547   printw("Help screen for cgdisk");
548   move(2, 0);
549   printw("This is cgdisk, a curses-based disk partitioning program. You can use it\n");
550   printw("to create, delete, and modify partitions on your hard disk.\n\n");
551   attron(A_BOLD);
552   printw("Use cgdisk only on GUID Partition Table (GPT) disks!\n");
553   attroff(A_BOLD);
554   printw("Use cfdisk on Master Boot Record (MBR) disks.\n\n");
555   printw("Command      Meaning\n");
556   printw("-------      -------\n");
557   while (menuMain[i].key != 0) {
558      printw("   %c         %s\n", menuMain[i].key, menuMain[i].desc.c_str());
559      i++;
560   } // while()
561   PromptToContinue();
562} // GPTDataCurses::ShowHelp()
563
564/************************************
565 *                                  *
566 * User input and menuing functions *
567 *                                  *
568 ************************************/
569
570// Change the currently-selected space....
571void GPTDataCurses::ChangeSpaceSelection(int delta) {
572   if (currentSpace != NULL) {
573      while ((delta > 0) && (currentSpace->nextSpace != NULL)) {
574         currentSpace = currentSpace->nextSpace;
575         delta--;
576         currentSpaceNum++;
577      } // while
578      while ((delta < 0) && (currentSpace->prevSpace != NULL)) {
579         currentSpace = currentSpace->prevSpace;
580         delta++;
581         currentSpaceNum--;
582      } // while
583   } // if
584   // Below will hopefully never be true; bad counting error (bug), so reset to
585   // the first Space as a failsafe....
586   if (DisplayParts(currentSpaceNum) != currentSpaceNum) {
587      currentSpaceNum = 0;
588      currentSpace = firstSpace;
589      DisplayParts(currentSpaceNum);
590   } // if
591} // GPTDataCurses
592
593// Move option selection left or right....
594void GPTDataCurses::MoveSelection(int delta) {
595   int newKeyNum;
596
597   // Begin with a sanity check to ensure a valid key is selected....
598   if (whichOptions.find(currentKey) == string::npos)
599      currentKey = 'n';
600   newKeyNum = whichOptions.find(currentKey);
601   newKeyNum += delta;
602   if (newKeyNum < 0)
603      newKeyNum = whichOptions.length() - 1;
604   newKeyNum %= whichOptions.length();
605   currentKey = whichOptions[newKeyNum];
606   DisplayOptions(currentKey);
607} // GPTDataCurses::MoveSelection()
608
609// Show user's options. Refers to currentSpace to determine which options to show.
610// Highlights the option with the key selectedKey; or a default if that's invalid.
611void GPTDataCurses::DisplayOptions(char selectedKey) {
612   uint64_t i, j = 0, firstLine, numPerLine;
613   string optionName, optionDesc = "";
614
615   if (currentSpace != NULL) {
616      if (currentSpace->partNum == -1) { // empty space is selected
617         whichOptions = EMPTY_SPACE_OPTIONS;
618         if (whichOptions.find(selectedKey) == string::npos)
619            selectedKey = 'n';
620      } else { // a partition is selected
621         whichOptions = PARTITION_OPTIONS;
622         if (whichOptions.find(selectedKey) == string::npos)
623            selectedKey = 't';
624      } // if/else
625
626      firstLine = LINES - 4;
627      numPerLine = (COLS - 8) / 12;
628      ClearBottom();
629      move(firstLine, 0);
630      for (i = 0; i < whichOptions.length(); i++) {
631         optionName = "";
632         for (j = 0; menuMain[j].key; j++) {
633            if (menuMain[j].key == whichOptions[i]) {
634               optionName = menuMain[j].name;
635               if (whichOptions[i] == selectedKey)
636                  optionDesc = menuMain[j].desc;
637            } // if
638         } // for
639         move(firstLine + i / numPerLine, (i % numPerLine) * 12 + 4);
640         if (whichOptions[i] == selectedKey) {
641            attron(A_REVERSE);
642            printw("[ %s ]", optionName.c_str());
643            attroff(A_REVERSE);
644         } else {
645            printw("[ %s ]", optionName.c_str());
646         } // if/else
647      } // for
648      move(LINES - 1, (COLS - optionDesc.length()) / 2);
649      printw("%s", optionDesc.c_str());
650      currentKey = selectedKey;
651   } // if
652} // GPTDataCurses::DisplayOptions()
653
654// Accept user input and process it. Returns when the program should terminate.
655void GPTDataCurses::AcceptInput() {
656   int inputKey, exitNow = 0;
657
658   do {
659      refresh();
660      inputKey = getch();
661      switch (inputKey) {
662         case KEY_UP:
663            ChangeSpaceSelection(-1);
664            break;
665         case KEY_DOWN:
666            ChangeSpaceSelection(+1);
667            break;
668         case 339: // page up key
669            ChangeSpaceSelection(RESERVED_TOP + RESERVED_BOTTOM - LINES);
670            break;
671         case 338: // page down key
672            ChangeSpaceSelection(LINES - RESERVED_TOP - RESERVED_BOTTOM);
673            break;
674         case KEY_LEFT:
675            MoveSelection(-1);
676            break;
677         case KEY_RIGHT:
678            MoveSelection(+1);
679            break;
680         case KEY_ENTER: case 13:
681            exitNow = Dispatch(currentKey);
682            break;
683         case 27: // escape key
684            exitNow = 1;
685            break;
686         default:
687            exitNow = Dispatch(inputKey);
688            break;
689      } // switch()
690   } while (!exitNow);
691} // GPTDataCurses::AcceptInput()
692
693// Operation has been selected, so do it. Returns 1 if the program should
694// terminate on return from this program, 0 otherwise.
695int GPTDataCurses::Dispatch(char operation) {
696   int exitNow = 0;
697
698   switch (operation) {
699      case 'a': case 'A':
700         SetAlignment();
701         break;
702      case 'b': case 'B':
703         Backup();
704         break;
705      case 'd': case 'D':
706         if (ValidPartNum(currentSpace->partNum))
707            DeletePartition(currentSpace->partNum);
708         break;
709      case 'h': case 'H':
710         ShowHelp();
711         break;
712      case 'i': case 'I':
713         if (ValidPartNum(currentSpace->partNum))
714            ShowInfo(currentSpace->partNum);
715         break;
716      case 'l': case 'L':
717         LoadBackup();
718         break;
719      case 'm': case 'M':
720         if (ValidPartNum(currentSpace->partNum))
721            ChangeName(currentSpace->partNum);
722         break;
723      case 'n': case 'N':
724         if (currentSpace->partNum < 0) {
725            MakeNewPart();
726            IdentifySpaces();
727         } // if
728         break;
729      case 'q': case 'Q':
730         exitNow = 1;
731         break;
732      case 't': case 'T':
733         if (ValidPartNum(currentSpace->partNum))
734            ChangeType(currentSpace->partNum);
735         break;
736      case 'v': case 'V':
737         Verify();
738         break;
739      case 'w': case 'W':
740         SaveData();
741         break;
742      default:
743         break;
744   } // switch()
745   DrawMenu();
746   return exitNow;
747} // GPTDataCurses::Dispatch()
748
749// Draws the main menu
750void GPTDataCurses::DrawMenu(void) {
751   string title="cgdisk ";
752   title += GPTFDISK_VERSION;
753   string drive="Disk Drive: ";
754   drive += device;
755   ostringstream size;
756
757   size << "Size: " << diskSize << ", " << BytesToIeee(diskSize, blockSize);
758
759   clear();
760   move(0, (COLS - title.length()) / 2);
761   printw("%s", title.c_str());
762   move(2, (COLS - drive.length()) / 2);
763   printw("%s", drive.c_str());
764   move(3, (COLS - size.str().length()) / 2);
765   printw("%s", size.str().c_str());
766   DisplayParts(currentSpaceNum);
767} // DrawMenu
768
769int GPTDataCurses::MainMenu(void) {
770   if (((LINES - RESERVED_TOP - RESERVED_BOTTOM) < 2) || (COLS < 80)) {
771      Report("Display is too small; it must be at least 80 x 14 characters!");
772   } else {
773      if (GPTData::Verify() > 0)
774         Report("Warning! Problems found on disk! Use the Verify function to learn more.\n"
775                "Using gdisk or some other program may be necessary to repair the problems.");
776      IdentifySpaces();
777      currentSpaceNum = 0;
778      DrawMenu();
779      AcceptInput();
780   } // if/else
781   endwin();
782   return 0;
783} // GPTDataCurses::MainMenu
784
785/***********************************************************
786 *                                                         *
787 * Non-class support functions (mostly related to ncurses) *
788 *                                                         *
789 ***********************************************************/
790
791// Clears the specified line of all data....
792void ClearLine(int lineNum) {
793   move(lineNum, 0);
794   clrtoeol();
795} // ClearLine()
796
797// Clear the last few lines of the display
798void ClearBottom(void) {
799   move(LINES - RESERVED_BOTTOM, 0);
800   clrtobot();
801} // ClearBottom()
802
803void PromptToContinue(void) {
804   ClearBottom();
805   move(LINES - 2, (COLS - 29) / 2);
806   printw("Press any key to continue....");
807   cbreak();
808   getch();
809} // PromptToContinue()
810
811// Display one line of text on the screen and prompt to press any key to continue.
812void Report(string theText) {
813   clear();
814   move(0, 0);
815   printw("%s", theText.c_str());
816   move(LINES - 2, (COLS - 29) / 2);
817   printw("Press any key to continue....");
818   cbreak();
819   getch();
820} // Report()
821
822// Displays all the partition type codes and then prompts to continue....
823// NOTE: This function temporarily exits curses mode as a matter of
824// convenience.
825void ShowTypes(void) {
826   PartType tempType;
827   char junk;
828
829   def_prog_mode();
830   endwin();
831   tempType.ShowAllTypes(LINES - 3);
832   cout << "\nPress the <Enter> key to continue: ";
833   cin.get(junk);
834   reset_prog_mode();
835   refresh();
836} // ShowTypes()
837