From: Abhinav Kannan Date: Fri, 21 Dec 2012 19:26:04 +0000 (-0600) Subject: Palacios GUI Added X-Git-Url: http://v3vee.org/palacios/gitweb/gitweb.cgi?a=commitdiff_plain;h=e30e35a15e2350e77f71e5b052b5a67a77cdf267;p=palacios.releases.git Palacios GUI Added --- diff --git a/linux_usr/gui/Palacios.pro b/linux_usr/gui/Palacios.pro new file mode 100644 index 0000000..1b75532 --- /dev/null +++ b/linux_usr/gui/Palacios.pro @@ -0,0 +1,31 @@ +TEMPLATE = app +TARGET = Palacios +QT += core \ + gui \ + xml +CONFIG += debug_and_release +DEFINES += QTONLY + +LIBS += -lvncclient -lgnutls -lX11 + + +HEADERS += palacios/vm_console_widget.h \ + palacios/vm_creation_wizard.h \ + palacios/newpalacios.h \ + palacios/defs.h \ + palacios/vnc_module/remoteview.h \ + palacios/vnc_module/vncclientthread.h \ + palacios/vnc_module/vncview.h +SOURCES += palacios/vm_view.cpp \ + palacios/vm_console_widget.cpp \ + palacios/vm_mode_dialog.cpp \ + palacios/vm_info_widget.cpp \ + palacios/vm_creation_wizard.cpp \ + palacios/main.cpp \ + palacios/newpalacios.cpp \ + palacios/vm_threads.cpp \ + palacios/vnc_module/remoteview.cpp \ + palacios/vnc_module/vncclientthread.cpp \ + palacios/vnc_module/vncview.cpp +FORMS += +RESOURCES += palacios_resources.qrc diff --git a/linux_usr/gui/Palacios.pro.locallibs b/linux_usr/gui/Palacios.pro.locallibs new file mode 100644 index 0000000..6704d76 --- /dev/null +++ b/linux_usr/gui/Palacios.pro.locallibs @@ -0,0 +1,34 @@ +TEMPLATE = app +TARGET = Palacios +QT += core \ + gui \ + xml +CONFIG += debug_and_release +DEFINES += QTONLY + +INCLUDEPATH += . \ + $$PWD/ext/include +DEPENDPATH += . \ + $$PWD/external_libs/include +LIBS += -L$$PWD/ext/lib -lvncclient -lgnutls + +HEADERS += palacios/vm_console_widget.h \ + palacios/vm_creation_wizard.h \ + palacios/newpalacios.h \ + palacios/defs.h \ + palacios/vnc_module/remoteview.h \ + palacios/vnc_module/vncclientthread.h \ + palacios/vnc_module/vncview.h +SOURCES += palacios/vm_view.cpp \ + palacios/vm_console_widget.cpp \ + palacios/vm_mode_dialog.cpp \ + palacios/vm_info_widget.cpp \ + palacios/vm_creation_wizard.cpp \ + palacios/main.cpp \ + palacios/newpalacios.cpp \ + palacios/vm_threads.cpp \ + palacios/vnc_module/remoteview.cpp \ + palacios/vnc_module/vncclientthread.cpp \ + palacios/vnc_module/vncview.cpp +FORMS += +RESOURCES += palacios_resources.qrc diff --git a/linux_usr/gui/README b/linux_usr/gui/README new file mode 100644 index 0000000..867c9b0 --- /dev/null +++ b/linux_usr/gui/README @@ -0,0 +1,75 @@ +This directory contains an implementation of a graphical user +interface for Palacios VMs, analogous to the "server console" tool in +VMware or VirtualBox, etc. The GUI builds directly on top of the +Palacios command-line utilities. + +The GUI was initially implemented by Abhinav Kannan and is: + +(c) 2012 Abhinav Kannan +(c) 2012 V3VEE Project + +As with other Linux interface components of Palacios, it is released +under GPL. Palacios itself is released under a BSD license. + + +PREREQUISITES +============= + +To build the GUI, you will need to install various libraries and +components. Specifically, you need: + +- QT4 +- QT4 development support and tools (qmake) +- LibVNCServer +- GNUTLS and GNUTLS development support + + +The easiest way to do this is to become root and then use +your package manager. + +For example, on a Red Hat or Fedora system: + +su - (become root) +yum install qt4 +yum install qt4-devel +yum install libvncsever +yum install libvncserver-devel +yum install gnutls +yum install gnutls-devel +exit (return to normal user) + +If you need to set up as a none-root user, please see the file +report.pdf. + + +BUILDING +======== + +1. Use qmake to build a Makefile for your machine: + +qmake Palacos.Pro + +2. make it + +make + +3. you should now have an executable file "Palacios" + this will become palacios/linux_usr/v3_x0gui + and will be wrapped by the script palacios/linux_usr/v3_gui + +If you would like to change things, please read report.pdf. +The "Palacios.pro" file mentioned in the report is +included here as "Palacios.pro.locallibs" + +RUNNING +======= + +To run the GUI, the Palacios command-line utilities and xterm need to +be on your path, and v3vee.ko kernel module must be inserted into +the running kernel. For example: + +export PATH=$PATH:/home/foo/palacios/linxu_usr:/path/to/xterm +insmod /home/foo/palacios/v3vee.ko +Palacios & + + diff --git a/linux_usr/gui/images/activate_vm.png b/linux_usr/gui/images/activate_vm.png new file mode 100644 index 0000000..d92f6f7 Binary files /dev/null and b/linux_usr/gui/images/activate_vm.png differ diff --git a/linux_usr/gui/images/exit.png b/linux_usr/gui/images/exit.png new file mode 100644 index 0000000..2feef30 Binary files /dev/null and b/linux_usr/gui/images/exit.png differ diff --git a/linux_usr/gui/images/icon_no_preview.png b/linux_usr/gui/images/icon_no_preview.png new file mode 100644 index 0000000..a9cd4f3 Binary files /dev/null and b/linux_usr/gui/images/icon_no_preview.png differ diff --git a/linux_usr/gui/images/new_vm.png b/linux_usr/gui/images/new_vm.png new file mode 100644 index 0000000..b0a8667 Binary files /dev/null and b/linux_usr/gui/images/new_vm.png differ diff --git a/linux_usr/gui/images/palacios.png b/linux_usr/gui/images/palacios.png new file mode 100755 index 0000000..2d1a539 Binary files /dev/null and b/linux_usr/gui/images/palacios.png differ diff --git a/linux_usr/gui/images/pause_vm.png b/linux_usr/gui/images/pause_vm.png new file mode 100644 index 0000000..a116757 Binary files /dev/null and b/linux_usr/gui/images/pause_vm.png differ diff --git a/linux_usr/gui/images/reload_vm.png b/linux_usr/gui/images/reload_vm.png new file mode 100644 index 0000000..541ffad Binary files /dev/null and b/linux_usr/gui/images/reload_vm.png differ diff --git a/linux_usr/gui/images/start_vm.png b/linux_usr/gui/images/start_vm.png new file mode 100644 index 0000000..32fb017 Binary files /dev/null and b/linux_usr/gui/images/start_vm.png differ diff --git a/linux_usr/gui/images/stop_vm.png b/linux_usr/gui/images/stop_vm.png new file mode 100644 index 0000000..8dd6826 Binary files /dev/null and b/linux_usr/gui/images/stop_vm.png differ diff --git a/linux_usr/gui/images/vm_pause.png b/linux_usr/gui/images/vm_pause.png new file mode 100644 index 0000000..8a8d4fa Binary files /dev/null and b/linux_usr/gui/images/vm_pause.png differ diff --git a/linux_usr/gui/images/vm_refresh.png b/linux_usr/gui/images/vm_refresh.png new file mode 100644 index 0000000..8ff580b Binary files /dev/null and b/linux_usr/gui/images/vm_refresh.png differ diff --git a/linux_usr/gui/images/vm_start.png b/linux_usr/gui/images/vm_start.png new file mode 100644 index 0000000..437811d Binary files /dev/null and b/linux_usr/gui/images/vm_start.png differ diff --git a/linux_usr/gui/images/vm_stop.png b/linux_usr/gui/images/vm_stop.png new file mode 100644 index 0000000..d8e7799 Binary files /dev/null and b/linux_usr/gui/images/vm_stop.png differ diff --git a/linux_usr/gui/palacios/defs.h b/linux_usr/gui/palacios/defs.h new file mode 100644 index 0000000..424f88b --- /dev/null +++ b/linux_usr/gui/palacios/defs.h @@ -0,0 +1,104 @@ +/* + * defs.h + * + * Created on: Oct 4, 2012 + * Author: Abhinav Kannan + */ + +#ifndef DEFS_H_ +#define DEFS_H_ + +const char* STATUS_BAR_MSG_READY = "Ready"; + +const char* TITLE_MAIN_WINDOW = "Palacios"; +const char* TITLE_DOCK_TELEMETRY = "Kernel messages"; +const char* TITLE_DOCK_VM_LIST = "List of VMs"; + +const char* MENU_FILE = "File"; +const char* MENU_VIEW = "View"; +const char* MENU_VM = "VM"; +const char* MENU_HELP = "Help"; + +const char* FILE_MENU_NEW_VM = "New VM"; +const char* NEW_VM_STATUS_TIP = "Create a new virtual machine"; +const char* FILE_MENU_EXIT = "Exit"; +const char* EXIT_STATUS_TIP = "Exit Palacios VMM"; + +const char* VM_MENU_START = "Start VM"; +const char* VM_MENU_STOP = "Stop VM"; +const char* VM_MENU_PAUSE = "Pause VM"; +const char* VM_MENU_RESTART = "Restart VM"; +const char* VM_MENU_REMOVE = "Delete VM"; +const char* VM_MENU_ACTIVATE = "Activate VM"; +const char* VM_MENU_RELOAD = "Reload VMs"; + +const char* VM_STOP_WARNING_MESSAGE = "You are about to stop a running virtual machine. Please stop all executing processes" + "within the virtual machine to insure safe termination of VM. Do you want to continue?"; +const char* VM_DELETE_WARNING_MESSAGE = "Are you sure you want to delete this VM?"; +const char* DELETE_RUNNING_VM_ERROR = "This VM is currently running! Please stop the VM before deleting"; + +const char* HELP_MENU_ABOUT = "About Palacios"; +const char* ABOUT_PALACIOS = + "Palacios is a virtual machine monitor (VMM) " + "that is available for public use as a community resource. Palacios is highly configurable " + "and designed to be embeddable into different host operating systems, such as Linux and the " + "Kitten lightweight kernel. Palacios is a non-paravirtualized VMM that makes extensive use of " + "the virtualization extensions in modern Intel and AMD x86 processors. " + "Palacios is a compact codebase that has been designed to be easy to understand and readily " + "configurable for different environments. It is unique in being designed to be embeddable into " + "other OSes instead of being implemented in the context of a specific OS. Palacios is distributed under the BSD license." + + "\nPalacios is part of the V3VEE Project"; + +const char* XTERM_CMD = "/usr/bin/xterm"; + +const char* FILE_VM_LIST = "virtual_machines_list.txt"; + +const char* TAG_VM = "vm"; + +const char* ERROR_TELEMETRY = "Telemetry information currently unavailable"; + +const char* LABEL_ACTIVE_INVENTORY = "Active Inventory"; +const char* LABEL_ACTIVE_NOT_INVENTORY = "Not in inventory"; +const char* LABEL_INACTIVE_INVENTORY = "Inactive Inventory"; + +const char* ERROR_SETUP_MODULE_INSTALL = "Kernel Module not installed"; +const char* ERROR_SETUP_MODULE_INSTALL_FAILED = "Kernel module not installed correctly"; +const char* ERROR_SETUP_MEMORY = "Memory not intialized"; + +const char* ERROR_APP_CLOSE = "There are running VMs in the current session. Stop or pause the VMs before exiting"; +const char* ERROR_VM_RUNNING = "VM is already running"; +const char* ERROR_UPDATE_VM_STATE = "Error could not update VM state"; +const char* ERROR_RUN_ACTIVE_NOT_INVENTORY = "VM instance exists on the system but has not been added to the inventory. Activate VM to proceed"; +const char* ERROR_RUN_INACTIVE_INVENTORY = "VM instance has not been activated"; +const char* ERROR_NO_DEVFILE_FOR_LAUNCH = "Could not find /dev/v3-vm# file to launch VM"; +const char* ERROR_STOP_VM = "VM is not running"; +const char* ERROR_VM_NOT_INVENTORY = "Cannot stop VM. VM is either inactive or not available in inventory"; +const char* ERROR_LAUNCH_VM_DEVICE_NOT_FOUND = "Error launching VM! Device file not found"; +const char* ERROR_LAUNCH_VM_IOCTL = "VM Launch: IOCTL error! Check kernel logs for details"; +const char* ERROR_STOP_VM_PATH = "Error executing stop command. Check path variable"; +const char* ERROR_STOP_VM_IOCTL = "VM Stop: IOCTL error! Check kernel logs for details"; +const char* ERROR_STOP_VM_DEVICE_NOT_FOUND = "Could not stop VM! Device file not found"; +const char* ERROR_PAUSE_VM_IOCTL = "VM Pause: IOCTL error! Check kernel logs for details"; +const char* ERROR_PAUSE_VM_DEVICE_NOT_FOUND = "Error pausing VM! Could not open device file"; +const char* ERROR_RESTART_VM_IOCTL = "VM Restart: IOCTL error! Check kernel logs for details"; +const char* ERROR_RESTART_VM_DEVICE_NOT_FOUND = "Error restarting VM! Device file not found."; + +const char* ERROR_VM_CREATE_PATH = "VM creation failed: Check PATH variable"; +const char* ERROR_VM_CREATE_IOCTL = "VM creation failed: Check kernel logs for more details"; +const char* ERROR_VM_CREATE_DB = "VM creation failed: Could not save VM. Error in database"; +const char* ERROR_VM_CREATE_PROC = "VM creation failed: Unable to get dev file"; +const char* ERROR_VM_CREATE_FAILED = "VM creation failed: Could not create dev file for new VM. Check kernel logs for more details"; +const char* SUCCESS_VM_ADDED = "VM added to inventory. Activate to use"; +const char* SUCCESS_VM_CREATE = "VM created successfully!"; + +const char* VM_TAB_TITLE = "VM Details"; + +const char* ERROR_VM_LAUNCH = "VM Launch failed: Check kernel logs for details"; + +const char* ERROR_VM_DELETE_PATH = "VM Deletion failed: Check PATH variable"; +const char* ERROR_VM_DELETE_IOCTL = "VM Deletion failed: Check kernel logs for more details"; +const char* ERROR_VM_DELETE_DB = "VM Deletion failed: Database error"; +const char* ERROR_VM_DELETE_INVALID_ARGUMENT = "VM Deletion failed: Could not find dev file!"; +const char* SUCCESS_VM_DELETE = "VM deleted successfully"; +#endif /* DEFS_H_ */ diff --git a/linux_usr/gui/palacios/main.cpp b/linux_usr/gui/palacios/main.cpp new file mode 100644 index 0000000..74cd084 --- /dev/null +++ b/linux_usr/gui/palacios/main.cpp @@ -0,0 +1,23 @@ +#include "newpalacios.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + NewPalacios w; + /*int checkSetup = w.checkPalacios(); + + if (checkSetup == -1) { + a.exit(); + return 0; + } else { + w.setMinimumSize(820, 640); + w.showMaximized(); + return a.exec(); + }*/ + + w.setMinimumSize(820, 640); + w.showMaximized(); + //w.checkPalacios(); + int eventLoop = a.exec(); + return eventLoop; +} diff --git a/linux_usr/gui/palacios/newpalacios.cpp b/linux_usr/gui/palacios/newpalacios.cpp new file mode 100644 index 0000000..35d14a4 --- /dev/null +++ b/linux_usr/gui/palacios/newpalacios.cpp @@ -0,0 +1,1450 @@ +#include +#include +#include +#include + +#include "newpalacios.h" +#include "defs.h" + +NewPalacios::NewPalacios(QWidget *parent) : + QMainWindow(parent) { + + // Call UI setup methods + createWizard(); + createCentralWidget(); + createActions(); + createMenus(); + createToolBars(); + createStatusBar(); + createDockWindows(); + readExistingVmsFile(); + + setWindowTitle(tr(TITLE_MAIN_WINDOW)); + + mExitAppFromMenu = false; +} + +NewPalacios::~NewPalacios() { + // Cleanup actions + delete mExitApp; + delete mVmNew; + delete mVmStop; + delete mVmPause; + delete mVmStart; + delete mVmActivate; + delete mReloadVms; + delete mAboutApp; + + // Clean up menu + delete mFileMenu; + delete mViewMenu; + delete mVmMenu; + delete mHelpMenu; + + // Clean up toolbar + delete mVmToolBar; + delete mVmCtrlToolBar; + + /*if (mLoadVmsThread != NULL) + delete mLoadVmsThread; + if (mAddVmThread != NULL) + delete mAddVmThread; + if (mDeleteVmThread != NULL) + delete mDeleteVmThread;*/ + + delete mVmTreeView; + delete mVmTelemetryView; + delete mVmWizard; + delete mVmInfoWidget; + + // Delete the central widget at the end + delete mVmControlPanel; +} + +void NewPalacios::createCentralWidget() { + // The VM View will be the central widget + // of this window. The List of VMs will + // be docked in the left corner + mVmControlPanel = new QTabWidget(); + mVmControlPanel->setTabsClosable(true); + connect(mVmControlPanel, SIGNAL(tabCloseRequested(int)), + this, SLOT(vmTabClosed(int))); + + mVmInfoWidget = new VmInfoWidget(mVmControlPanel); + + // Set the widget to the window + this->setCentralWidget(mVmControlPanel); +} + +void NewPalacios::createActions() { + mVmNew = new QAction(QIcon(":/images/images/new_vm.png"), + tr(FILE_MENU_NEW_VM), this); + connect(mVmNew, SIGNAL(triggered()), this, SLOT(createVmInstance())); + + mExitApp = new QAction(QIcon(":/images/images/exit.png"), + tr(FILE_MENU_EXIT), this); + connect(mExitApp, SIGNAL(triggered()), this, SLOT(exitApplication())); + + mVmStart = new QAction(QIcon(":/images/images/start_vm.png"), + tr(VM_MENU_START), this); + connect(mVmStart, SIGNAL(triggered()), this, SLOT(selectVmMode())); + + mVmStop = new QAction(QIcon(":/images/images/stop_vm.png"), + tr(VM_MENU_STOP), this); + connect(mVmStop, SIGNAL(triggered()), this, SLOT(stopVm())); + + mVmPause = new QAction(QIcon(":/images/images/pause_vm.png"), + tr(VM_MENU_PAUSE), this); + connect(mVmPause, SIGNAL(triggered()), this, SLOT(pauseVm())); + + mVmActivate = new QAction(QIcon(":/images/images/activate_vm.png"), + tr(VM_MENU_ACTIVATE), this); + connect(mVmActivate, SIGNAL(triggered()), this, SLOT(activateVm())); + + mAboutApp = new QAction(tr(HELP_MENU_ABOUT), this); + connect(mAboutApp, SIGNAL(triggered()), this, SLOT(aboutPalacios())); + + mReloadVms = new QAction(QIcon(":/images/images/reload_vm.png"), tr(VM_MENU_RELOAD), this); + connect(mReloadVms, SIGNAL(triggered()), this, SLOT(reloadVms())); + + connect(mVmWizard, SIGNAL(accepted()), this, SLOT(addNewVm())); +} + +void NewPalacios::createMenus() { + mFileMenu = menuBar()->addMenu(tr(MENU_FILE)); + mFileMenu->addAction(mVmNew); + mFileMenu->addSeparator(); + mFileMenu->addAction(mExitApp); + + mViewMenu = menuBar()->addMenu(tr(MENU_VIEW)); + + mVmMenu = menuBar()->addMenu(tr(MENU_VM)); + mVmMenu->addAction(mVmStart); + mVmMenu->addAction(mVmStop); + mVmMenu->addAction(mVmPause); + mVmMenu->addAction(mVmActivate); + mVmMenu->addAction(mReloadVms); + + mHelpMenu = menuBar()->addMenu(tr(MENU_HELP)); + mHelpMenu->addAction(mAboutApp); +} + +void NewPalacios::createToolBars() { + mVmToolBar = addToolBar(tr(MENU_FILE)); + mVmToolBar->addAction(mVmNew); + + mVmCtrlToolBar = addToolBar(tr(MENU_VM)); + mVmCtrlToolBar->addAction(mVmStart); + mVmCtrlToolBar->addAction(mVmStop); + mVmCtrlToolBar->addAction(mVmPause); + mVmCtrlToolBar->addAction(mVmActivate); + mVmCtrlToolBar->addAction(mReloadVms); +} + +void NewPalacios::createStatusBar() { + statusBar()->showMessage(tr(STATUS_BAR_MSG_READY)); +} + +void NewPalacios::createDockWindows() { + QDockWidget* dockVmList = new QDockWidget(tr(TITLE_DOCK_VM_LIST), this); + + // Setup VM instance tree view + mVmTreeView = new QTreeWidget(dockVmList); + mVmTreeView->setColumnCount(1); + mVmTreeView->headerItem()->setHidden(true); + // Header for active VMs + QTreeWidgetItem* activeVms = new QTreeWidgetItem(mVmTreeView); + activeVms->setText(0, tr(LABEL_ACTIVE_INVENTORY)); + mVmTreeView->addTopLevelItem(activeVms); + + // Header for inactive VMs in the inventory + QTreeWidgetItem* inactiveVms = new QTreeWidgetItem(mVmTreeView); + inactiveVms->setText(0, tr(LABEL_INACTIVE_INVENTORY)); + mVmTreeView->addTopLevelItem(inactiveVms); + + // Header for active VMs not in inventory + QTreeWidgetItem* activeNotInventoryVms = new QTreeWidgetItem(mVmTreeView); + activeNotInventoryVms->setText(0, tr(LABEL_ACTIVE_NOT_INVENTORY)); + mVmTreeView->addTopLevelItem(activeNotInventoryVms); + + mVmTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mVmTreeView, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(vmContextMenu(const QPoint &))); + connect(mVmTreeView, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, + SLOT(vmItemClickListener(QTreeWidgetItem*, int))); + + dockVmList->setAllowedAreas(Qt::LeftDockWidgetArea); + dockVmList->setFeatures(QDockWidget::DockWidgetClosable); + dockVmList->setWidget(mVmTreeView); + addDockWidget(Qt::LeftDockWidgetArea, dockVmList); + mViewMenu->addAction(dockVmList->toggleViewAction()); + + // Telemetry dock + QDockWidget* dockTelemetry = new QDockWidget(tr(TITLE_DOCK_TELEMETRY), this); + mVmTelemetryView = new QTextEdit(dockTelemetry); + mVmTelemetryView->setReadOnly(true); + dockTelemetry->setAllowedAreas(Qt::BottomDockWidgetArea); + dockTelemetry->setFeatures(QDockWidget::NoDockWidgetFeatures); + dockTelemetry->setWidget(mVmTelemetryView); + addDockWidget(Qt::BottomDockWidgetArea, dockTelemetry); + connect(dockTelemetry, SIGNAL(visibilityChanged(bool)), this, SLOT(updateTelemetry(bool))); +} + +void NewPalacios::updateTelemetry(bool visible) { + if (visible) { + mTelemProc = new QProcess(); + mTelemProc->setProcessChannelMode(QProcess::MergedChannels); + QStringList args; + args << "-c" << "tail -f /var/log/messages"; + + // Slots used for debugging + //connect(mTelemProc, SIGNAL(started()), this, SLOT(processStarted())); + //connect(mTelemProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processExit(int, QProcess::ExitStatus))); + //connect(mTelemProc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + + // Connect output of dmesg to text widget + connect(mTelemProc, SIGNAL(readyReadStandardOutput()), this, SLOT(updateTelemetryView())); + + mTelemProc->start("sh", args); + if (!mTelemProc->waitForStarted()) { + if (mVmTelemetryView != NULL) { + mVmTelemetryView->setText(tr(ERROR_TELEMETRY)); + } + } + } else { + /*if (mTelemProc != NULL) { + mTelemProc->close(); + mTelemProc->terminate(); + delete mTelemProc; + }*/ + } +} + +void NewPalacios::updateTelemetryView() { + if (mVmTelemetryView != NULL && mTelemProc != NULL) { + mVmTelemetryView->setText(mTelemProc->readAllStandardOutput()); + } +} + +void NewPalacios::createWizard() { + mVmWizard = new NewVmWizard(); +} + +// Listener for VM item clicks +void NewPalacios::vmItemClickListener(QTreeWidgetItem* item, int col) { + if (item->text(0).compare(LABEL_ACTIVE_INVENTORY) == 0 + || item->text(0).compare(LABEL_INACTIVE_INVENTORY) == 0 + || item->text(0).compare(LABEL_ACTIVE_NOT_INVENTORY) == 0) { + // If user clicks on the main headers + // do nothing + isHeaderClicked = true; + return; + } + + isHeaderClicked = false; + + QString vmName = item->text(col); + mVmName = vmName; + + // Locate the VM clicked in the list. This is inefficient because each time we need + // to search the list. If a better way is found. replace here + VmInfo* vm = NULL; + for (int i = 0; i < mVmList.size(); i++) { + if (vmName.compare(mVmList[i]->getVmName()) == 0) { + mVmPos = i; + vm = mVmList[i]; + break; + } + } + // Load the details of the seleted VM + if (vm != NULL) { + mVmInfoWidget->updateInfoView(vm); + } +} + +void NewPalacios::vmContextMenu(const QPoint &pos) { + //QPoint globalPos = mVmListView->mapToGlobal(pos); + QPoint globalPos = mVmTreeView->mapToGlobal(pos); + + QTreeWidgetItem* item = mVmTreeView->itemAt(pos); + if (item->text(0).compare(LABEL_ACTIVE_INVENTORY) == 0 + || item->text(0).compare(LABEL_INACTIVE_INVENTORY) == 0 + || item->text(0).compare(LABEL_ACTIVE_NOT_INVENTORY) == 0) { + // Clicked on the headers + // do nothing + isHeaderClicked = true; + return; + } + + isHeaderClicked = false; + + // Update VM pos, the global index locator for VMs + QString vmName = item->text(0); + // Update global VM name + mVmName = vmName; + for (int i=0; igetVmName()) == 0) { + mVmPos = i; + break; + } + } + + QMenu *menu = new QMenu(); + if (mVmList[mVmPos]->getCategory() == VmInfo::INACTIVE_INVENTORY + || mVmList[mVmPos]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + + menu->addAction(new QAction(tr(VM_MENU_ACTIVATE), this)); + } else { + if (mVmList[mVmPos]->getState() == VmInfo::RUNNING) { + menu->addAction(new QAction(tr(VM_MENU_PAUSE), this)); + + } else if (mVmList[mVmPos]->getState() == VmInfo::PAUSED) { + menu->addAction(new QAction(tr(VM_MENU_RESTART), this)); + + } else if (mVmList[mVmPos]->getState() == VmInfo::STOPPED) { + menu->addAction(new QAction(tr(VM_MENU_START), this)); + + } + + menu->addAction(new QAction(tr(VM_MENU_STOP), this)); + } + + menu->addAction(new QAction(tr(VM_MENU_REMOVE), this)); + + QAction* selectedAction = menu->exec(globalPos); + if (selectedAction == NULL) { + // User did not select any option + return; + } + + QString actionItem = selectedAction->text(); + if (actionItem.compare(tr(VM_MENU_START)) == 0 + || actionItem.compare(tr(VM_MENU_RESTART)) == 0) { + // If VM was stopped or paused + selectVmMode(); + + } else if (actionItem.compare(tr(VM_MENU_PAUSE)) == 0) { + // Pause VM + pauseVm(); + + } else if (actionItem.compare(tr(VM_MENU_RESTART)) == 0) { + // Restart VM + restartVm(); + + } else if (actionItem.compare(tr(VM_MENU_STOP)) == 0) { + // Stop the VM if possible + stopVm(); + + } else if (actionItem.compare(tr(VM_MENU_REMOVE)) == 0) { + bool isVmRunning = false; + for (int i=0; i < mRunningVms.size(); i++) { + if (mVmName.compare(mRunningVms[i]->getVmName()) == 0) { + isVmRunning = true; + break; + } + } + + QMessageBox vmDeleteWarningBox; + + if (isVmRunning) { + vmDeleteWarningBox.setText(DELETE_RUNNING_VM_ERROR); + vmDeleteWarningBox.setIcon(QMessageBox::Critical); + } else { + vmDeleteWarningBox.setText(VM_DELETE_WARNING_MESSAGE); + vmDeleteWarningBox.setIcon(QMessageBox::Warning); + } + + vmDeleteWarningBox.setStandardButtons( + QMessageBox::Ok | QMessageBox::Cancel); + vmDeleteWarningBox.setDefaultButton(QMessageBox::Cancel); + int retVal = vmDeleteWarningBox.exec(); + + switch (retVal) { + case QMessageBox::Ok: + if (!isVmRunning) { + deleteVm(mVmName); + } + break; + case QMessageBox::Cancel: + break; + } + + } else if (actionItem.compare(tr(VM_MENU_ACTIVATE)) == 0) { + activateVm(); + return; + } + +} + +void NewPalacios::aboutPalacios() { + QMessageBox::about(this, tr(HELP_MENU_ABOUT), tr(ABOUT_PALACIOS)); +} + +// This function checks for the necessary setup +// required by palacios to run. The setup for palacios +// includes: +// 1. Check for v3vee.ko module +// 2. Check if palacios has been allocated memory +void NewPalacios::checkPalacios() { + int err = 0; + QMessageBox setupError; + setupError.setStandardButtons(QMessageBox::Ok); + setupError.setIcon(QMessageBox::Critical); + + // v3vee.ko check + QProcess* v3 = new QProcess(); + v3->setProcessChannelMode(QProcess::MergedChannels); + v3->start("lsmod"); + v3->waitForFinished(); + QByteArray reply = v3->readAllStandardOutput(); + + if (reply.isEmpty() || !reply.contains("v3vee")) { + setupError.setText(tr(ERROR_SETUP_MODULE_INSTALL)); + err = -1; + } + + QFile file("/proc/v3vee/v3-mem"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + setupError.setText(tr(ERROR_SETUP_MODULE_INSTALL_FAILED)); + err = -1; + } + + QTextStream in(&file); + + while(in.atEnd()) { + QString line = in.readLine(); + if (line.contains("null")) { + setupError.setText(tr(ERROR_SETUP_MEMORY)); + err = -1; + break; + } + } + + if (err != 0) { + int ret = setupError.exec(); + + if (ret == QMessageBox::Ok) { + // Palacios not setup correctly, exit + this->close(); + } + } +} + +void NewPalacios::closeEvent(QCloseEvent* event) { + if (mExitAppFromMenu == true) { + event->accept(); + return; + } + + bool flag = false; + + for (int i=0; igetState() == VmInfo::RUNNING) { + flag = true; + break; + } + } + + if (!flag) { + if (mTelemProc != NULL) { + mTelemProc->close(); + mTelemProc->terminate(); + delete mTelemProc; + } + event->accept(); + } else { + + QMessageBox appCloseWarning; + appCloseWarning.setText(tr("There are still running VMs. It is suggested to close the running VMs tab before exiting. Click cancel to go back")); + appCloseWarning.setIcon(QMessageBox::Warning); + appCloseWarning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + + int ret = appCloseWarning.exec(); + switch (ret) { + case QMessageBox::Ok: + event->accept(); + break; + case QMessageBox::Cancel: + event->ignore(); + break; + } + } +} + +void NewPalacios::exitApplication() { + QMessageBox appCloseWarning; + appCloseWarning.setText(tr("There are still running VMs. It is suggested to close the running VMs tab before exiting. Click cancel to go back")); + appCloseWarning.setIcon(QMessageBox::Warning); + appCloseWarning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + + bool flag = false; + + // Check if there are running VMs + for (int i=0; igetState() == VmInfo::RUNNING) { + flag = true; + break; + } + } + + // If there are no running VMs + // exit + if (!flag) { + if (mTelemProc != NULL) { + mTelemProc->close(); + mTelemProc->terminate(); + delete mTelemProc; + } + mExitAppFromMenu = true; + this->close(); + } else { + // Inform user about running VMs and ask for consent + // before closing application + int ret = appCloseWarning.exec(); + switch (ret) { + case QMessageBox::Ok: + mExitAppFromMenu = true; + this->close(); + break; + case QMessageBox::Cancel: + mExitAppFromMenu = false; + break; + } + } +} + +// Convenience method for showing messages +void NewPalacios::showMessage(QString msg, bool err, bool warning) { + QMessageBox msgBox; + msgBox.setText(msg); + if (err == true) { + msgBox.setIcon(QMessageBox::Critical); + } else if (warning == true) { + msgBox.setIcon(QMessageBox::Warning); + } + + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); +} + +/*void NewPalacios::processStarted() { + //qDebug() << "Process started..."; +} + +void NewPalacios::processExit(int errorCode, QProcess::ExitStatus exitStatus) { + if (exitStatus == QProcess::CrashExit) { + //qDebug() << "The process crashed!!"; + } else { + //qDebug() << "The process exited normally.."; + } +} + +void NewPalacios::processError(QProcess::ProcessError error) { + //qDebug() << "There was an error in the process execution..."; + + switch (error) { + case QProcess::FailedToStart: + //qDebug() << "Process failed to start..."; + break; + case QProcess::Crashed: + //qDebug() << "Process crashed..."; + break; + case QProcess::Timedout: + //qDebug() << "Process timed out..."; + break; + case QProcess::WriteError: + //qDebug() << "Process had write error..."; + break; + case QProcess::ReadError: + //qDebug() << "Process had read error..."; + break; + case QProcess::UnknownError: + //qDebug() << "Process unknown error..."; + break; + + } +}*/ + +void NewPalacios::createVmInstance() { + if (mVmWizard != NULL) { + mVmWizard->restart(); + mVmWizard->show(); + } +} + +void NewPalacios::selectVmMode() { + if (isHeaderClicked) { + return; + } + + if (mVmList[mVmPos]->getCategory() != VmInfo::ACTIVE_INVENTORY) { + QMessageBox warning; + showMessage(tr("VM is not active!"), true); + return; + } + + + if (mVmList[mVmPos]->getState() == VmInfo::PAUSED) { + // If machine is paused, we just restart using the previous selected mode + restartVm(); + } else { + // Machine is started fresh + mVmModeDialog = new VmModeDialog(this); + connect(mVmModeDialog, SIGNAL(setMode(int, QString)), + this, SLOT(getVmMode(int, QString))); + mVmModeDialog->show(); + } +} + +void NewPalacios::getVmMode(int mode, QString streamName) { + mVmMode = mode; + mStreamName = streamName; + // Start VM + startVm(); +} + +void NewPalacios::startVm() { + if (mVmList.isEmpty()) { + // There is no VM in the list + return; + } + + // Check if we are trying to start an inactive VM + if (mVmList[mVmPos]->getCategory() == VmInfo::INACTIVE_INVENTORY) { + showMessage(tr(ERROR_RUN_INACTIVE_INVENTORY), true); + return; + } + + // Check if we are trying to start a VM not in the inventory + if (mVmList[mVmPos]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + showMessage(tr(ERROR_RUN_ACTIVE_NOT_INVENTORY), true); + return; + } + + int pos = 0; + // Check if the VM is already running + for (int i=0; i < mRunningVms.size(); i++) { + if (mVmName.compare(mRunningVms[i]->getVmName()) == 0 + && mVmMode != VmConsoleWidget::STREAM) { + // If we are running the VM in stream mode then we + // can have multiple streams open simultaneously + // For the other modes, only one instance can be running + showMessage(tr(ERROR_VM_RUNNING), true); + return; + } + } + + QString v3_devfile = mVmList[mVmPos]->getVmDevFile(); + if (v3_devfile == NULL) { + showMessage(tr(ERROR_NO_DEVFILE_FOR_LAUNCH), true); + return; + } + + int vm_status = mVmList[mVmPos]->getState(); + + QProcess* v3LaunchProc = NULL; + QStringList args; + bool flag = false; + QByteArray message; + + switch (vm_status) { + case VmInfo::STOPPED: + // If VM is stopped, launch it + v3LaunchProc = new QProcess(); + v3LaunchProc->setProcessChannelMode(QProcess::MergedChannels); + + // Connect debug slots + //connect(v3LaunchProc, SIGNAL(started()), this, SLOT(processStarted())); + //connect(v3LaunchProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processExit(int, QProcess::ExitStatus))); + //connect(v3LaunchProc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + + args << v3_devfile; + v3LaunchProc->start("v3_launch", args); + + flag = v3LaunchProc->waitForFinished(); + message = v3LaunchProc->readAllStandardOutput(); + + if (!flag) { + showMessage(tr(ERROR_VM_LAUNCH), true); + delete v3LaunchProc; + return; + + } else if (message.contains("Error opening V3Vee VM device")) { + showMessage(tr(ERROR_LAUNCH_VM_DEVICE_NOT_FOUND), true); + delete v3LaunchProc; + return; + + } + + break; + /*case VmInfo::PAUSED: + // If VM is paused, call + restartVm(); + break;*/ + case VmInfo::RUNNING: + // If VM is running, do nothing + // We will be just calling v3_cons_sc/v3_stream/vnc to + // connect to it + break; + } + + QString name = mVmList[mVmPos]->getVmName(); + // Create a new console instance and set the launch file + VmXConsoleParent* consoleParent = new VmXConsoleParent(name); + + // Add it to list of running VMs + mRunningVms.append(consoleParent); + pos = mRunningVms.indexOf(consoleParent); + + // Launch in new tab + // Disable updates in widgets to reduce screen flicker + // due to layouting in widgets + consoleParent->setUpdatesEnabled(false); + mVmControlPanel->setUpdatesEnabled(false); + if (mVmMode != VmConsoleWidget::STREAM) { + // If not in stream mode, header will be VM name + mVmControlPanel->insertTab(mVmControlPanel->count(), consoleParent, name); + } else { + // If stream mode, header will be stream name + mVmControlPanel->insertTab(mVmControlPanel->count(), consoleParent, mStreamName); + } + mVmControlPanel->setCurrentWidget(consoleParent); + + // Start VM + consoleParent->showWidget(mVmMode, v3_devfile, mStreamName); + // Re-enable updates to widgets + mVmControlPanel->setUpdatesEnabled(true); + consoleParent->setUpdatesEnabled(true); + + // Signal to tell the background xterm process to quit when console widget is closed + connect(mRunningVms[pos], SIGNAL(windowClosingWithId(QString)), this, + SLOT(consoleWindowClosed(QString))); + // Update VM state + updateVmState(VmInfo::RUNNING); + + for (int i=0; itopLevelItem(VmInfo::ACTIVE_INVENTORY)->childCount(); i++) { + QTreeWidgetItem* child = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY)->child(i); + if (mVmName.compare(child->text(0)) == 0) { + child->setIcon(0, QIcon(":/images/images/start_vm.png")); + break; + } + } +} + +void NewPalacios::updateVmState(int mode) { + // TODO: Move this to a background thread + QString vm_name = mVmList[mVmPos]->getVmName(); + + QFile file("virtual_machines_list.txt"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + showMessage(tr(ERROR_UPDATE_VM_STATE), true); + stopVm(); + return; + } + + QFile temp("temp.txt"); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + showMessage(tr(ERROR_UPDATE_VM_STATE), true); + stopVm(); + return; + } + + QTextStream in(&file); + QTextStream out(&temp); + + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.compare("\n") == 0) { + continue; + } + + QStringList vmDet = line.split(","); + QString nameStr = vmDet.at(0); + + if (nameStr.compare(vm_name) == 0) { + QString configStr = vmDet.at(1); + QString devfileStr = vmDet.at(2); + QString stateStr = vmDet.at(3); + QString imageStr = vmDet.at(4); + + out << nameStr << "," << configStr << "," << devfileStr << "," << QString::number(mode) <<"," << imageStr << endl; + } else { + out << line << endl; + } + } + + out.flush(); + file.remove("virtual_machines_list.txt"); + temp.rename("virtual_machines_list.txt"); + + file.close(); + temp.close(); + + // Update VM object + mVmList[mVmPos]->setState(mode); +} + +void NewPalacios::stopVm() { + if (mVmList.isEmpty() + || mVmList[mVmPos]->getState() == VmInfo::STOPPED) { + // If the VM list is empty or the VM is already stopped + // or if the VM is passive + // return + showMessage(tr(ERROR_STOP_VM), false, true); + return; + } + + if (mVmList[mVmPos]->getCategory() == VmInfo::INACTIVE_INVENTORY + || mVmList[mVmPos]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + // If the VM is inactive or active not inventory + showMessage(tr(ERROR_VM_NOT_INVENTORY), true); + return; + } + + QString name = NULL; + QMessageBox vmStopWarningBox; + vmStopWarningBox.setText(VM_STOP_WARNING_MESSAGE); + vmStopWarningBox.setIcon(QMessageBox::Warning); + vmStopWarningBox.setStandardButtons( + QMessageBox::Cancel | QMessageBox::Ok); + vmStopWarningBox.setDefaultButton(QMessageBox::Cancel); + int ret = vmStopWarningBox.exec(); + int posInTabWidget = -1; + QStringList args; + QProcess* v3StopProc = NULL; + + switch (ret) { + case QMessageBox::Ok: + + v3StopProc = new QProcess(); + v3StopProc->setProcessChannelMode(QProcess::MergedChannels); + args << mVmList[mVmPos]->getVmDevFile(); + v3StopProc->start("v3_stop", args); + if (!v3StopProc->waitForFinished()) { + showMessage(tr(ERROR_STOP_VM_PATH), true); + delete v3StopProc; + return; + + } else if (v3StopProc->readAllStandardOutput().contains("Error opening V3Vee VM device")) { + showMessage(tr(ERROR_STOP_VM_DEVICE_NOT_FOUND), true); + delete v3StopProc; + return; + } + + name = mVmList[mVmPos]->getVmName(); + for (int i=0; igetVmName()) == 0) { + // Remove widget from tab if placed there + posInTabWidget = mVmControlPanel->indexOf(mRunningVms[i]); + if (posInTabWidget != -1) { + // Console is present in tab + mVmControlPanel->removeTab(posInTabWidget); + } + // Close the console + mRunningVms[i]->close(); + // Remove it from list of running console windows + mRunningVms.removeAt(i); + break; + } + } + + // Update VM state + updateVmState(VmInfo::STOPPED); + + // Update icon + for (int i=0; itopLevelItem(VmInfo::ACTIVE_INVENTORY)->childCount(); i++) { + QTreeWidgetItem* child = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY)->child(i); + if (mVmName.compare(child->text(0)) == 0) { + child->setIcon(0, QIcon(":/images/images/stop_vm.png")); + break; + } + } + + break; + case QMessageBox::Cancel: + break; + } +} + +void NewPalacios::pauseVm() { + if (mVmList.isEmpty()) { + return; + } + + if (mVmList[mVmPos]->getCategory() == VmInfo::INACTIVE_INVENTORY + || mVmList[mVmPos]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + showMessage(tr(ERROR_VM_NOT_INVENTORY), false, true); + return; + } + + QString v3_devfile = mVmList[mVmPos]->getVmDevFile(); + if (v3_devfile == NULL) { + showMessage(tr("Device file not found"), true); + return; + } + + QProcess* v3Pauseproc = new QProcess(); + v3Pauseproc->setProcessChannelMode(QProcess::MergedChannels); + QStringList args; + args << v3_devfile; + v3Pauseproc->start("v3_pause", args); + if (!v3Pauseproc->waitForFinished()) { + showMessage(v3Pauseproc->errorString(), true); + } else if (v3Pauseproc->readAllStandardOutput().contains("Error opening V3Vee VM device")) { + showMessage(tr(ERROR_PAUSE_VM_DEVICE_NOT_FOUND), true); + } + + // Update VM state + updateVmState(VmInfo::PAUSED); + + // Update icon + for (int i=0; itopLevelItem(VmInfo::ACTIVE_INVENTORY)->childCount(); i++) { + QTreeWidgetItem* child = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY)->child(i); + if (mVmName.compare(child->text(0)) == 0) { + child->setIcon(0, QIcon(":/images/images/pause_vm.png")); + break; + } + } + + delete v3Pauseproc; +} + +void NewPalacios::restartVm() { + if (mVmList.isEmpty()) { + return; + } + + if (mVmList[mVmPos]->getCategory() == VmInfo::INACTIVE_INVENTORY + || mVmList[mVmPos]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + showMessage(tr(ERROR_VM_NOT_INVENTORY), false, true); + return; + } + + QString v3_devfile = mVmList[mVmPos]->getVmDevFile(); + if (v3_devfile == NULL) { + showMessage(tr("Device file not found"), true); + return; + } + + QProcess* v3Continueproc = new QProcess(); + v3Continueproc->setProcessChannelMode(QProcess::MergedChannels); + QStringList args; + args << v3_devfile; + v3Continueproc->start("v3_continue", args); + + if (!v3Continueproc->waitForFinished()) { + showMessage(v3Continueproc->errorString(), true); + } else if (v3Continueproc->readAllStandardOutput().contains("Error opening V3Vee VM device")) { + showMessage(tr(ERROR_RESTART_VM_IOCTL), true); + } + + // Update icon + for (int i=0; itopLevelItem(VmInfo::ACTIVE_INVENTORY)->childCount(); i++) { + QTreeWidgetItem* child = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY)->child(i); + if (mVmName.compare(child->text(0)) == 0) { + child->setIcon(0, QIcon(":/images/images/start_vm.png")); + break; + } + } + + delete v3Continueproc; +} + +// Method to create a VM from inactive list +int NewPalacios::createInactiveVm() { + QString vmImageFile = mVmList[mVmPos]->getImageFile(); + QString vmName = mVmList[mVmPos]->getVmName(); + + // Create the VM instance + QProcess* v3CreateProc = new QProcess(); + QStringList args; + args.clear(); + args << vmImageFile << vmName; + v3CreateProc->start("v3_create", args); + + if (v3CreateProc->waitForFinished()) { + if (v3CreateProc->readAllStandardOutput().contains("Error (-1)")) { + // v3_create has failed with IOCTL error + showMessage(tr(ERROR_VM_CREATE_IOCTL), true); + delete v3CreateProc; + return -1; + } + } else { + showMessage(tr(ERROR_VM_CREATE_PATH), true); + delete v3CreateProc; + return -1; + } + + // Cleanup + delete v3CreateProc; + + // Check the last line of /proc/v3vee/v3-guests + // to see the /dev/v3-vm# of the new VM + bool isCreated = false; + QProcess* proc = new QProcess(); + proc->setProcessChannelMode(QProcess::MergedChannels); + proc->start("cat /proc/v3vee/v3-guests"); + + if (!proc->waitForFinished()) { + showMessage(tr(ERROR_VM_CREATE_PROC), true); + delete proc; + return -1; + } + + //QByteArray temp = vmName.toAscii(); + //const char* vmEntry = temp.data(); + + // Read standard output of process + QByteArray val = proc->readAllStandardOutput(); + if (!val.isNull()) { + // The created VM can be defined anywhere in the /proc file + // we need to go over all entries till we find it + QList procs = val.split('\n'); + for (int i=0; i temp = procs[i].split('\t'); + QByteArray a = temp.at(0); + QString vmProcName(a); + if (vmName.compare(vmProcName) == 0) { + // We have found the VM, get dev file name + QByteArray b = temp.at(1); + QString vmDevFile(b); + vmDevFile.remove(QChar('\n'), Qt::CaseInsensitive); + mVmList[mVmPos]->setVmDevFile(vmDevFile); + // Make VM instance active + mVmList[mVmPos]->setCategory(VmInfo::ACTIVE_INVENTORY); + isCreated = true; + break; + } + } + } + + if (!isCreated) { + // We did not find an entry in + // /proc file so there must have + // been an error in creation + showMessage(tr(ERROR_VM_CREATE_FAILED), true); + delete proc; + return -1; + } + + if (proc != NULL) + delete proc; + + return 0; +} + +int NewPalacios::updateDb(int cat) { + QString vm_name = mVmList[mVmPos]->getVmName(); + + QFile file("virtual_machines_list.txt"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + showMessage("Error opening DB! Please restart application", true); + return -1; + } + + QFile temp("temp.txt"); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + showMessage("Error opening DB! Please restart application", true); + return -1; + } + + QTextStream in(&file); + QTextStream out(&temp); + + + if (cat == VmInfo::INACTIVE_INVENTORY) { + // If we are updating an inactive entry + // search the file and update + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.compare("\n") == 0) { + continue; + } + QStringList vmDet = line.split(","); + QString nameStr = vmDet.at(0); + + if (vm_name.compare(nameStr) != 0) { + // If the name does not match, then copy to new place + out << line << endl; + } else { + // Update DB state + VmInfo* v = mVmList[mVmPos]; + QString updateLine = v->getVmName() + + "," + v->getVmConfigFile() + + "," + v->getVmDevFile() + + "," + QString::number(VmInfo::STOPPED) + + "," + v->getImageFile(); + out << updateLine << endl; + } + } + } else if (cat == VmInfo::ACTIVE_NOT_INVENTORY) { + + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.compare("\n") == 0) { + continue; + } + + // Copy all the contents into new file + out << line << endl; + } + + // Copy the new VM info + VmInfo* vv = mVmList[mVmPos]; + QString updateLine = vv->getVmName() + + "," + vv->getVmConfigFile() + + "," + vv->getVmDevFile() + + "," + QString::number(VmInfo::STOPPED) + + "," + vv->getImageFile(); + out << updateLine << endl; + } + + out.flush(); + file.remove("virtual_machines_list.txt"); + temp.rename("virtual_machines_list.txt"); + + file.close(); + temp.close(); + + return 0; +} + +void NewPalacios::activateVm() { + // 1. Check VM category + // 1a. If VM is active but not in inventory, upgrade category + // 1b. If VM is inactive, create VM and upgrade category + QTreeWidgetItem* item = NULL; + + // Remove VM from inactive list + // The list could be from either the inactive inventory or + // active not inventory + int category = mVmList[mVmPos]->getCategory(); + + if (category == VmInfo::ACTIVE_NOT_INVENTORY) { + // The VM exists in the /proc file and + // is already created. We will only update + // VM category. + + // Ask user for Config file and Image file + QString configFile = QFileDialog::getOpenFileName(this, tr("Select config file"), ".", + "XML file (*.xml)"); + if (configFile == NULL) { + // User pressed cancel + return; + } + QString imageFile = QFileDialog::getOpenFileName(this, tr("Select image file"), ".", + "Image file (*.img *.bZ)"); + if (imageFile == NULL) { + // User pressed cancel + return; + } + + mVmList[mVmPos]->setVmConfigFile(configFile); + mVmList[mVmPos]->setImageFile(imageFile); + + // Remove from active not inventory list + for (int i=0; itopLevelItem(VmInfo::ACTIVE_NOT_INVENTORY)->childCount(); i++) { + item = mVmTreeView->topLevelItem(VmInfo::ACTIVE_NOT_INVENTORY)->child(i); + if (item->text(0).compare(mVmList[mVmPos]->getVmName()) == 0) { + item = mVmTreeView->topLevelItem(VmInfo::ACTIVE_NOT_INVENTORY)->takeChild(i); + break; + } + } + + // Add it to active list + QTreeWidgetItem* subParent = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY); + subParent->addChild(item); + + // Update Category + mVmList[mVmPos]->setCategory(VmInfo::ACTIVE_INVENTORY); + + // Update DB with newly created entry + updateDb(VmInfo::ACTIVE_NOT_INVENTORY); + return; + + } else if (category == VmInfo::INACTIVE_INVENTORY) { + // The VM is in the inventory but inactive. + // This means that we need to create it. + int ret = createInactiveVm(); + + if (ret != 0) { + // VM was not created due to some problem + // TODO: Decide if you want to delete the reference + // from the text file or keep it there for a while and + // try again + return; + } + + // Once VM is created, we need to first remove it from the + // inactive list and add it to active list + for (int i=0; itopLevelItem(VmInfo::INACTIVE_INVENTORY)->childCount(); i++) { + item = mVmTreeView->topLevelItem(VmInfo::INACTIVE_INVENTORY)->child(i); + if (item->text(0).compare(mVmList[mVmPos]->getVmName()) == 0) { + item = mVmTreeView->topLevelItem(VmInfo::INACTIVE_INVENTORY)->takeChild(i); + break; + } + } + + // Add it to active list + QTreeWidgetItem* subParent = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY); + subParent->addChild(item); + + // Update DB with newly created entry + updateDb(VmInfo::INACTIVE_INVENTORY); + } +} + +void NewPalacios::reloadVms() { + // Clear all previous entries + for (int i=0; itopLevelItemCount(); i++) { + qDeleteAll(mVmTreeView->topLevelItem(i)->takeChildren()); + } + + // Call load VM thread + readExistingVmsFile(); +} + +// This handler is used in serial mode when user clicks +// the close button of the floating window +void NewPalacios::consoleWindowClosed(QString name) { + // Remove the console from the list of + // running consoles + for (int i=0; i < mRunningVms.length(); i++) { + if (name.compare(mRunningVms[i]->getVmName()) == 0) { + mRunningVms.removeAt(i); + break; + } + } +} + +// This handler is used in v3_cons_sc mode when the user clicks +// the close button on the tab widget +void NewPalacios::vmTabClosed(int index) { + if (index == 0) { + // As of now do not remove the info tab + return; + } + + // Get the name of the VM tab which we need to close + // Need to cast the return value of TabWidget->widget as it returns an object + // of the base class. static_cast is used because the conversion is from base + // class object to derived class object + VmXConsoleParent* widget = static_cast(mVmControlPanel->widget(index)); + QString name = widget->getVmName(); + for (int i=0; igetVmName()) == 0) { + // Remove the tab + mVmControlPanel->removeTab(index); + // Remove the console from running vm instances + mRunningVms.removeAt(i); + // Send close event to widget + widget->close(); + break; + } + } +} + +/* This function reads from the vm list file */ +void NewPalacios::readExistingVmsFile() { + mThread = new QThread(); + mLoadVmsThread = new LoadVmsThread(); + mLoadVmsThread->moveToThread(mThread); + connect(mThread, SIGNAL(started()), mLoadVmsThread, SLOT(loadVms())); + connect(mLoadVmsThread, SIGNAL(finished()), mThread, SLOT(quit())); + // Action which listens to completion of initial VM loading + connect(mLoadVmsThread, SIGNAL(finished()), this, + SLOT(finishLoadingVmsFromFile())); + mThread->start(); +} + +// Update UI with VMs loaded from file +void NewPalacios::finishLoadingVmsFromFile() { + if (mLoadVmsThread->getStatus() == LoadVmsThread::STATUS_OK) { + mVmList.clear(); + mVmPos = 0; + + int category = -1; + + // Load VMs into memory + mVmList.append(mLoadVmsThread->getVmList()); + + QTreeWidgetItem* item = NULL; + for (int i = 0; i < mVmList.size(); i++) { + if (mVmList[i]->getCategory() == VmInfo::ACTIVE_INVENTORY) { + item = new QTreeWidgetItem(); + item->setText(0, mVmList[i]->getVmName()); + + category = mVmList[i]->getState(); + switch (category) { + case VmInfo::STOPPED: + item->setIcon(0, QIcon(":/images/images/stop_vm.png")); + break; + case VmInfo::PAUSED: + item->setIcon(0, QIcon(":/images/images/pause_vm.png")); + break; + case VmInfo::RUNNING: + item->setIcon(0, QIcon(":/images/images/start_vm.png")); + break; + } + + mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY)->addChild(item); + + } else if (mVmList[i]->getCategory() == VmInfo::INACTIVE_INVENTORY) { + item = new QTreeWidgetItem(); + item->setText(0, mVmList[i]->getVmName()); + mVmTreeView->topLevelItem(VmInfo::INACTIVE_INVENTORY)->addChild(item); + + } else if (mVmList[i]->getCategory() == VmInfo::ACTIVE_NOT_INVENTORY) { + item = new QTreeWidgetItem(); + item->setText(0, mVmList[i]->getVmName()); + mVmTreeView->topLevelItem(VmInfo::ACTIVE_NOT_INVENTORY)->addChild(item); + } + } + + mVmControlPanel->insertTab(0, mVmInfoWidget, tr(VM_TAB_TITLE)); + + if (mThread != NULL) + delete mThread; + + } +} + +/* This method is called after completion of the VM wizard + * It updates the backend vm file with new vm */ +void NewPalacios::addNewVm() { + QString name = mVmWizard->field("guestName").toString(); + QString file = mVmWizard->field("configLoc").toString(); + QFileInfo f(mVmWizard->field("imageLoc").toString()); + QString img = f.fileName(); + + mThread = new QThread(); + mAddVmThread = new AddVmThread(name, file, img); + mAddVmThread->moveToThread(mThread); + connect(mThread, SIGNAL(started()), mAddVmThread, SLOT(addVm())); + connect(mAddVmThread, SIGNAL(finished()), mThread, SLOT(quit())); + // Call method on window to update the vm list + connect(mAddVmThread, SIGNAL(finished()), this, SLOT(updateVmList())); + mThread->start(); +} + +/* Update UI with new VM added to list */ +void NewPalacios::updateVmList() { + if(mAddVmThread != NULL && mAddVmThread->getStatus() + == AddVmThread::ERROR_V3CREATE_DB) { + showMessage(tr(ERROR_VM_CREATE_DB), true); + + } else if (mAddVmThread != NULL && + mAddVmThread->getStatus() == AddVmThread::STATUS_OK) { + // Newly created VM is inactive but in inventory + showMessage(tr(SUCCESS_VM_ADDED), false); + mVmList.append(mAddVmThread->getNewVm()); + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, mAddVmThread->getName()); + + QTreeWidgetItem* subParent = mVmTreeView->topLevelItem(VmInfo::INACTIVE_INVENTORY); + subParent->addChild(item); + } + + if (mThread != NULL) + delete mThread; +} + +/* Handler to delete VM instance from list and backend file */ +void NewPalacios::deleteVm(QString item) { + int category = -1; + QString devfile; + + for (int i = 0; i < mVmList.size(); i++) { + if (item.compare(mVmList[i]->getVmName()) == 0) { + category = mVmList[i]->getCategory(); + devfile = mVmList[i]->getVmDevFile(); + break; + } + } + + mThread = new QThread(); + mDeleteVmThread = new DeleteVmThread(category, item, devfile); + mDeleteVmThread->moveToThread(mThread); + connect(mThread, SIGNAL(started()), mDeleteVmThread, SLOT(deleteVm())); + connect(mDeleteVmThread, SIGNAL(finished()), mThread, SLOT(quit())); + connect(mDeleteVmThread, SIGNAL(finished()), this, SLOT(handleVmDeletion())); + mThread->start(); +} + +void NewPalacios::handleVmDeletion() { + if (mDeleteVmThread->getStatus() + == DeleteVmThread::ERROR_V3FREE_PATH) { + showMessage(tr(ERROR_VM_DELETE_PATH), true); + + } else if (mDeleteVmThread->getStatus() + == DeleteVmThread::ERROR_V3FREE_IOCTL) { + showMessage(tr(ERROR_VM_DELETE_IOCTL), true); + + } else if (mDeleteVmThread->getStatus() + == DeleteVmThread::ERROR_V3FREE_DB) { + showMessage(tr(ERROR_VM_DELETE_DB), true); + + } else if (mDeleteVmThread->getStatus() + == DeleteVmThread::ERROR_V3FREE_INVALID_ARGUMENT) { + showMessage(tr(ERROR_VM_DELETE_INVALID_ARGUMENT), true); + + } else if (mDeleteVmThread->getStatus() + == DeleteVmThread::STATUS_OK) { + // Delete VM from list + for (int i = 0; i < mVmList.size(); i++) { + if (mVmName.compare(mVmList[i]->getVmName()) == 0) { + // Deletion is a little tricky + // 1. Find the VM to be deleted and get its name + // 2. Remove VM from list + // 3. Determine the category + // 4. Search the appropriate category for the VM + // 5. Remove from UI widget and delete + int cat = mVmList[i]->getCategory(); + mVmList.removeAt(i); + + if (cat == VmInfo::ACTIVE_INVENTORY) { + QTreeWidgetItem* activeItems = mVmTreeView->topLevelItem(VmInfo::ACTIVE_INVENTORY); + for (int j=0; jchildCount(); j++) { + QString n = activeItems->child(j)->text(0); + if (mVmName.compare(n) == 0) { + QTreeWidgetItem* achild = activeItems->takeChild(j); + delete achild; + break; + } + + } + } else if (cat == VmInfo::INACTIVE_INVENTORY) { + QTreeWidgetItem* inactiveItems = mVmTreeView->topLevelItem(VmInfo::INACTIVE_INVENTORY); + for (int j=0; jchildCount(); j++) { + QString in = inactiveItems->child(j)->text(0); + if (mVmName.compare(in) == 0) { + QTreeWidgetItem* ichild = inactiveItems->takeChild(j); + delete ichild; + break; + } + } + } else if (cat == VmInfo::ACTIVE_NOT_INVENTORY) { + QTreeWidgetItem* activeNotInvItems = mVmTreeView->topLevelItem(VmInfo::ACTIVE_NOT_INVENTORY); + for (int j=0; jchildCount(); j++) { + QString an = activeNotInvItems->child(j)->text(0); + if (mVmName.compare(an) == 0) { + QTreeWidgetItem* aichild = activeNotInvItems->takeChild(j); + delete aichild; + break; + } + } + + } + + // Clear VM from Info widget view + mVmInfoWidget->deleteVm(); + break; + } + } + + showMessage(tr(SUCCESS_VM_DELETE), false); + } + + if (mThread != NULL) + delete mThread; +} diff --git a/linux_usr/gui/palacios/newpalacios.h b/linux_usr/gui/palacios/newpalacios.h new file mode 100644 index 0000000..47c23b6 --- /dev/null +++ b/linux_usr/gui/palacios/newpalacios.h @@ -0,0 +1,425 @@ +#ifndef NEWPALACIOS_H +#define NEWPALACIOS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vm_creation_wizard.h" +#include "vm_console_widget.h" + +class VmInfo; +class VmInfoWidget; +class VmXConsoleParent; +class VmVncWidget; +class LoadVmsThread; +class AddVmThread; +class DeleteVmThread; +class VmModeDialog; + +class NewPalacios: public QMainWindow { +Q_OBJECT + +public: + NewPalacios(QWidget *parent = 0); + ~NewPalacios(); + +protected: + void closeEvent(QCloseEvent*); + +private slots: + void createVmInstance(); + void aboutPalacios(); + void exitApplication(); + void startVm(); + void stopVm(); + void pauseVm(); + void restartVm(); + void activateVm(); + void reloadVms(); + // Item click listener for tree items + void vmItemClickListener(QTreeWidgetItem*, int); + void finishLoadingVmsFromFile(); + void addNewVm(); + void updateVmList(); + void deleteVm(QString); + void handleVmDeletion(); + // Context menu for VMs + void vmContextMenu(const QPoint &p); + void consoleWindowClosed(QString); + void vmTabClosed(int); + // This slot will be triggered when play/menu start + // button is pressed. This will launch a dialog which + // will allow the user to select operating mode + void selectVmMode(); + // This slot will be triggered from the mode selection + // dialog when the user has finished selection. + void getVmMode(int, QString); + void updateTelemetry(bool); + void updateTelemetryView(); + +private slots: + // These are used for debugging QProcess commands + //void processStarted(); + //void processExit(int, QProcess::ExitStatus); + //void processError(QProcess::ProcessError); + +signals: + void vmLoadingComplete(); + +public: + void readExistingVmsFile(); + // Check for Palacios setup + void checkPalacios(); + +private: + // Functions to setup different components of the window + void createCentralWidget(); + void createActions(); + void createMenus(); + void createToolBars(); + void createStatusBar(); + void createDockWindows(); + void createWizard(); + void createEventHandler(); + // Show error message boxes + void showMessage(QString err, bool error, bool warning=false); + // Update state of VM + void updateVmState(int); + // Create inactive VM + int createInactiveVm(); + // Update DB + int updateDb(int); + +private: + // Main view of the application. + QTabWidget* mVmControlPanel; + VmInfoWidget* mVmInfoWidget; + // This is used to represent the classes of VM + QTreeWidget* mVmTreeView; + QTextEdit* mVmTelemetryView; + + // Progress dialog to inform user about background thread processing + //QProgressDialog* mProgress; + + // Runs Palacios in terminal mode + QList mRunningVms; + + bool mExitAppFromMenu; + // Used to identify VMs inside list + int mVmPos; + QString mVmName; + + // Action variables used to handle events such as menu clicks, button clicks etc. + QAction* mExitApp; + QAction* mVmNew; + QAction* mVmStop; + QAction* mVmPause; + QAction* mVmStart; + QAction* mVmActivate; + QAction* mReloadVms; + QAction* mAboutApp; + + // Menu variables + QMenu* mFileMenu; + QMenu* mViewMenu; + QMenu* mVmMenu; + QMenu* mHelpMenu; + + // Toolbar variables + QToolBar* mVmToolBar; + QToolBar* mVmCtrlToolBar; + + // This dialog will be used to give option + // to user to select from three modes of + // operation + VmModeDialog* mVmModeDialog; + int mVmMode; + QString mStreamName; + bool isHeaderClicked; + + // Process to read kernel logs + QProcess* mLogProc; + + // Wizard to help in vm creation + NewVmWizard* mVmWizard; + + // List of created VMs. Each VM object contains information parsed from + // the config files provided as part of VM creation + QList mVmList; + //QList mVmList; + + /* We save the information about VMs in a text file. We store the name of the VM instance + * and the path of the configuration file used to create the VM. Every time we create/add/delete + * a VM instance, this file is edited. This is simplest way as of now to store this information. + * If in future we need more information to be stored we could use a database and Qt's + * model-view system */ + + QThread* mThread; + + // Thread to load existing VM intances + LoadVmsThread* mLoadVmsThread; + // Thread to add a new VM instance + AddVmThread* mAddVmThread; + // Thread to delete a VM instance + DeleteVmThread* mDeleteVmThread; + + // Telemetry process + QProcess* mTelemProc; +}; + +class VmModeDialog : public QWidget { +Q_OBJECT +public: + VmModeDialog(QWidget* parent = 0); + +private slots: + void selectMode(bool); + void okButton(); + void cancelButton(); + +signals: + // This signal will be caught in the main window + // and the mode will be set accordingly + void setMode(int, QString); + +private: + void setupDialog(); + +private: + bool isV3Cons; + bool isV3Stream; + bool isV3Vnc; + int mode; + QGroupBox* v3_modes; + QRadioButton* v3_stream; + QRadioButton* v3_cons; + QRadioButton* v3_vnc; + QWidget* v3_stream_info; + QLineEdit* v3_stream_name; +}; + +// Class to hold information about a VM instance +class VmInfo { +public: + VmInfo() { + } + + ~VmInfo() { + } + + // Tells us about the state of the VM + enum { + STOPPED, PAUSED, RUNNING + }; + + // Tells about the category of VM + enum { + ACTIVE_INVENTORY, INACTIVE_INVENTORY, ACTIVE_NOT_INVENTORY + }; + + // Return state of VM + int getState() { + return mVmState; + } + + // Ser state of VM + void setState(int state) { + this->mVmState = state; + } + + void setCategory(int cat) { + this->mVmCategory = cat; + } + + int getCategory() { + return mVmCategory; + } + + void setImageFile(QString img) { + this->mVmImageFile = img; + } + + QString getImageFile() { + return mVmImageFile; + } + + QString getVmName() { + return mVmName; + } + + void setVmName(QString name) { + this->mVmName = name; + } + + QString getVmDevFile() { + return mVmDevFile; + } + + void setVmDevFile(QString name) { + this->mVmDevFile = name; + } + + QString getVmConfigFile() { + return this->mVmConfigFile; + } + + void setVmConfigFile(QString file) { + this->mVmConfigFile = file; + } + +private: + int mVmState; + int mVmCategory; + QString mVmName; + QString mVmDevFile; + QString mVmImageFile; + QString mVmConfigFile; +}; + +class VmInfoWidget: public QWidget { +Q_OBJECT +public: + VmInfoWidget(QWidget* parent = 0); + ~VmInfoWidget(); + +private: + void setInfoView(); + void setupUi(); + +public: + void parseElement(const QDomElement&, QTreeWidgetItem*); + void parseAttribute(const QDomElement&, QTreeWidgetItem*); + void parseText(const QDomElement&, QTreeWidgetItem*); + void updateInfoView(VmInfo* vm); + void deleteVm(); + +private: + QTreeWidget* mVmInfoView; +}; + +class VmXConsoleParent: public QWidget { +Q_OBJECT +public: + VmXConsoleParent(QString name, QWidget* parent = 0); + void showWidget(int mode, QString devfile, QString streamName); + void showTelemetryInfo(); + bool isRunning(); + QString getVmName(); + +signals: + void windowClosingWithId(QString name); + void windowClosing(); + +protected: + void closeEvent(QCloseEvent* event); + +private: + bool mIsConsoleRunning; + QString mVmName; + VmConsoleWidget* mConsole; +}; + +class LoadVmsThread: public QObject { +Q_OBJECT +public: + static const int STATUS_OK = 0; + static const int ERROR_FILE_CANNOT_BE_OPENED = -1; + + int getStatus(); + QList getVmList(); + +signals: + void finished(); + +public slots: + void loadVms(); +private: + int status; + QList list; +}; + +class AddVmThread: public QObject { +Q_OBJECT +public: + static const int STATUS_OK = 0; + static const int ERROR_V3CREATE_PATH = -1; + static const int ERROR_V3CREATE_IOCTL = -2; + static const int ERROR_V3CREATE_PROC = -3; + static const int ERROR_V3CREATE_DEV = -4; + static const int ERROR_V3CREATE_DB = -5; + + AddVmThread(QString name, QString conf, QString img); + int getStatus(); + QString getName(); + VmInfo* getNewVm(); + +signals: + void finished(); + +public slots: + void addVm(); + +private: + int status; + QString vmName; + QString vmDevFile; + QString vmConfigFile; + QString vmImageFile; + VmInfo* vm; +}; + +class DeleteVmThread: public QObject { +Q_OBJECT +public: + static const int STATUS_OK = 0; + static const int ERROR_V3FREE_PATH = -1; + static const int ERROR_V3FREE_IOCTL = -2; + static const int ERROR_V3FREE_DB = -3; + static const int ERROR_V3FREE_INVALID_ARGUMENT = -4; + + DeleteVmThread(int, QString, QString); + int getStatus(); + +signals: + void finished(); + +public slots: + void deleteVm(); + +private slots: + // These are used for debugging QProcess commands + //void processStarted(); + //void processExit(int, QProcess::ExitStatus); + //void processError(QProcess::ProcessError); + +private: + int status; + int vmCategory; + QString vmToDelete; + QString vmDevfile; +}; + +#endif // NEWPALACIOS_H diff --git a/linux_usr/gui/palacios/vm_console_widget.cpp b/linux_usr/gui/palacios/vm_console_widget.cpp new file mode 100644 index 0000000..ec0081b --- /dev/null +++ b/linux_usr/gui/palacios/vm_console_widget.cpp @@ -0,0 +1,272 @@ +/* + * vm_console_widget.cpp + * + * Created on: Oct 4, 2012 + * Author: abhinav + */ + +#include "vm_console_widget.h" + +#include +#include +#include +#include + +VmConsoleWidget::VmConsoleWidget(QWidget* parent) : + QWidget(parent), mCols(100), mRows(25), mTermProcess(0) { + mTermProcess = NULL; + mVncServer = NULL; +} + +VmConsoleWidget::~VmConsoleWidget() { + +} + +bool VmConsoleWidget::tryTerminate() { + if (mTermProcess == NULL) { + return true; + } + + if (mTermProcess != NULL && mTermProcess->state() == QProcess::Running) { + mTermProcess->terminate(); + bool xwindow_closed = mTermProcess->waitForFinished(); + delete mTermProcess; + return xwindow_closed; + + } else if (mVncView != NULL) { + mVncServer->terminate(); + bool vncserver_exited = mVncServer->waitForFinished(); + delete mVncView; + delete mVncServer; + return vncserver_exited; + } + + return true; +} + +void VmConsoleWidget::mainWindowClosing() { + close(); +} + +void VmConsoleWidget::closeEvent(QCloseEvent* e) { + if (!tryTerminate()) + qDebug() << "Warning: could not terminate process...there could be a leak"; + else + qDebug() << "Process terminated"; + + e->accept(); +} + +void VmConsoleWidget::resizeEvent(QResizeEvent* re) { + QWidget::resizeEvent(re); + + if (mTermProcess == NULL) + return; + + // Search for xterm window and update its size + Display *dsp = XOpenDisplay(NULL); + Window wnd = winId(); + + bool childFound = false; + while (!childFound && mTermProcess->state() == QProcess::Running) { + Window root, parent, *children; + uint numwin; + XQueryTree(dsp, wnd, &root, &parent, &children, &numwin); + childFound = (children != NULL); + + if (childFound) { + XResizeWindow(dsp, *children, width(), height()); + XFree(children); + } + } + + XCloseDisplay(dsp); +} + +bool VmConsoleWidget::isRunning() { + if (mTermProcess == NULL) { + return false; + } + + // FIXME: This function is currently very unreliable + // The QProcess->state() function should return the correct + // state of the process but it is currently not working as expected + // Debugging with GDB also does not reveal the problem + // Maybe the way this method is being called is incorrect + return (mTermProcess->state() == QProcess::Running) ? true : false; +} + +bool VmConsoleWidget::start(int mode, QString devfile, QString streamName) { + qDebug() << "VmConsoleWidget : start() [" << streamName << "]"; + + // Start VM in correct mode + if (mode == VmConsoleWidget::STREAM) { + mVmMode = "v3_stream"; + } else if (mode == VmConsoleWidget::CONSOLE) { + mVmMode = "v3_cons_sc"; + } else { + mVmMode = "v3_vncclient"; + } + + qDebug() << "Vm Mode: " << mVmMode; + qDebug() << "Vm dev file: " << devfile; + + QStringList args; + + if (streamName.compare("") != 0) { + // This is v3_stream mode + args << "-sb" << "-geometry" << QString("%1x%2").arg(mCols).arg(mRows) << "-j" + << "-into" << QString::number(winId()) << "-e" << mVmMode << devfile << streamName; + + // Start the xterm process + qDebug() << "Starting terminal with arguments '" << args.join(" "); + mTermProcess = new QProcess(); + + // Connect signals and slots + connect(mTermProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(termProcessExited(int, QProcess::ExitStatus))); + + connect(mTermProcess, SIGNAL(error(QProcess::ProcessError)), this, + SLOT(errorMessage(QProcess::ProcessError))); + + mTermProcess->start("/usr/bin/xterm", args); + + } else { + // This is non-stream mode + + if (mVmMode.compare("v3_cons_sc") == 0) { + // Console mode + args << "-sb" << "-geometry" << QString("%1x%2").arg(mCols).arg(mRows) << "-j" + << "-into" << QString::number(winId()) << "-e" << mVmMode << devfile; + + + qDebug() << "Starting terminal with arguments '" << args.join(" "); + mTermProcess = new QProcess(); + + // Connect signals and slots + connect(mTermProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(termProcessExited(int, QProcess::ExitStatus))); + + connect(mTermProcess, SIGNAL(error(QProcess::ProcessError)), this, + SLOT(errorMessage(QProcess::ProcessError))); + + mTermProcess->start("/usr/bin/xterm", args); + + } else { + // VNC mode + mVncServer = new QProcess(); + mVncServer->setProcessChannelMode(QProcess::MergedChannels); + + connect(mVncServer, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(termProcessExited(int, QProcess::ExitStatus))); + + connect(mVncServer, SIGNAL(error(QProcess::ProcessError)), this, + SLOT(errorMessage(QProcess::ProcessError))); + + QStringList args; + args << "--port=5951" << "--password=test123" << devfile; + qDebug() << "arguments: " << args.join(" "); + mVncServer->start("v3_vncserver", args); + + if (!mVncServer->waitForFinished()) { + qDebug() << "Error with VNC server"; + QMessageBox msg; + msg.setText("Error with VNC server!"); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Critical); + msg.exec(); + return false; + } + + //args << "-sb" << "-geometry" << QString("%1x%2").arg(mCols).arg(mRows) << "-j" + // << "-into" << QString::number(winId()) << "-e" << "vncviewer localhost:5951"; + + QVBoxLayout* vncBox = new QVBoxLayout(); + mVncView = new VncView(this, QUrl("vnc://localhost:5951"), KConfigGroup()); + //mVncView = new VncView(this, QUrl("vnc://localhost:5901"), KConfigGroup()); + vncBox->addWidget(mVncView); + this->setLayout(vncBox); + + mVncView->show(); + mVncView->start(); + } + } + + // Flag to indicate success and failure + bool status = false; + + if (mVmMode.compare("v3_vncclient") == 0) { + // This is VNC mode + status = true; + + } else { + int success; + + // This is v3_stream or v3_cons_sc mode + if (mTermProcess != NULL && mTermProcess->waitForStarted()) { + success = 0; + qDebug() << "Process started"; + } else { + success = -1; + qDebug() << "Process did not start"; + } + + if (success == 0) { + /* Wait for the xterm window to be opened and resize it + * to our own widget size. + */ + Display *dsp = XOpenDisplay(NULL); + Window wnd = winId(); + + bool childFound = false; + while (!childFound && mTermProcess->state() == QProcess::Running) { + Window root, parent, *children; + uint numwin; + XQueryTree(dsp, wnd, &root, &parent, &children, &numwin); + childFound = (children != NULL); + + if (childFound) { + XResizeWindow(dsp, *children, width(), height()); + } + } + + XCloseDisplay(dsp); + + if (!childFound) + success = -2; + } + + if (success < 0) { + qDebug() << (success == -1 ? "Starting the process failed" + : "Process started, but exited before opening a terminal"); + + if (success < -1) + tryTerminate(); + } + + status = (success == 0); + } + + return status; +} + +void VmConsoleWidget::termProcessExited(int a, QProcess::ExitStatus b) { + qDebug() << "Term Process Exited: " << a << " : " << b; + + if (mTermProcess != NULL) { + delete mTermProcess; + } + + if (mVncServer != NULL) { + delete mVncServer; + } + + mTermProcess = NULL; + mVncServer = NULL; + + emit exited(); +} + +void VmConsoleWidget::errorMessage(QProcess::ProcessError err) { + qDebug() << "Process error: " << err; +} diff --git a/linux_usr/gui/palacios/vm_console_widget.h b/linux_usr/gui/palacios/vm_console_widget.h new file mode 100644 index 0000000..6871d6e --- /dev/null +++ b/linux_usr/gui/palacios/vm_console_widget.h @@ -0,0 +1,59 @@ +/* + * vm_console_widget.h + * + * Created on: Oct 5, 2012 + * Author: abhinav + */ + +#ifndef VM_CONSOLE_WIDGET_H_ +#define VM_CONSOLE_WIDGET_H_ + +#include "vnc_module/vncview.h" + +#include +#include +#include + +class VmConsoleWidget: public QWidget { +Q_OBJECT + +public: + // Different operation modes of VM + enum { + STREAM, CONSOLE, VNC + }; + + VmConsoleWidget(QWidget* parent = 0); + virtual ~VmConsoleWidget(); + bool isRunning(); + +public slots: + bool start(int mode, QString devfile, QString streamName); + bool tryTerminate(); + void mainWindowClosing(); + +protected slots: + void termProcessExited(int, QProcess::ExitStatus); + void errorMessage(QProcess::ProcessError); + +signals: + void exited(); + +protected: + void closeEvent(QCloseEvent *); + void resizeEvent(QResizeEvent *); + +private: + int mCols; + int mRows; + QString mVmMode; + // Process for xterm + QProcess* mTermProcess; + // VNC + QProcess* mVncServer; + VncView* mVncView; + +}; + + +#endif /* VM_CONSOLE_WIDGET_H_ */ diff --git a/linux_usr/gui/palacios/vm_creation_wizard.cpp b/linux_usr/gui/palacios/vm_creation_wizard.cpp new file mode 100644 index 0000000..200fb5c --- /dev/null +++ b/linux_usr/gui/palacios/vm_creation_wizard.cpp @@ -0,0 +1,177 @@ +/* + * newvmwizard.cpp + * + * Created on: Sep 20, 2012 + * Author: abhinav + */ + +#include +#include "vm_creation_wizard.h" + +NewVmWizard::NewVmWizard(QWidget* parent) : + QWizard(parent) { + + setPage(Page_Intro, new IntroPage); + setPage(Page_Image_File, new GuestImagePage); + setPage(Page_Final, new FinalPage); + setStartId(Page_Intro); + + setOptions(QWizard::NoBackButtonOnLastPage); + setWindowTitle(tr("New Virtual Machine")); +} + +NewVmWizard::~NewVmWizard() { +} + +IntroPage::IntroPage(QWidget* parent) : + QWizardPage(parent) { + setTitle(tr("Introduction")); + setPixmap(QWizard::WatermarkPixmap, QPixmap(":/images/images/palacios.png")); + + topLabel = new QLabel( + tr("This wizard will help you to create a new virtual machine")); + topLabel->setWordWrap(true); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(topLabel); + setLayout(layout); +} + +IntroPage::~IntroPage() { + delete topLabel; +} + +int IntroPage::nextId() const { + return NewVmWizard::Page_Image_File; +} + +GuestImagePage::GuestImagePage(QWidget* parent) : + QWizardPage(parent) { + setTitle(tr("VM Creation")); + setSubTitle( + tr( + "Please fill all the fields. Make sure all file paths are correct")); + + guestNameLabel = new QLabel(tr("Enter a name for the guest")); + guestName = new QLineEdit(); + guestNameLabel->setBuddy(guestName); + + configLocLabel = new QLabel(tr("Enter path of config file")); + configLoc = new QLineEdit(); + configLocLabel->setBuddy(configLoc); + + imageLocLabel = new QLabel(tr("Enter path of image file")); + imageLoc = new QLineEdit(); + imageLocLabel->setBuddy(imageLoc); + + browseConfig = new QPushButton(tr("Browse")); + browseImage = new QPushButton(tr("Browse")); + + registerField("guestName*", guestName); + registerField("configLoc*", configLoc); + registerField("imageLoc*", imageLoc); + //registerField("memory*", memory); + + connect(browseConfig, SIGNAL(clicked()), this, SLOT(locateConfigFile())); + connect(browseImage, SIGNAL(clicked()), this, SLOT(locateImageFile())); + + QGridLayout* layout = new QGridLayout(); + layout->addWidget(guestNameLabel, 0, 0); + layout->addWidget(guestName, 0, 1); + layout->addWidget(configLocLabel, 1, 0); + layout->addWidget(configLoc, 1, 1); + layout->addWidget(browseConfig, 1, 2); + layout->addWidget(imageLocLabel, 2, 0); + layout->addWidget(imageLoc, 2, 1); + layout->addWidget(browseImage, 2, 2); + + setLayout(layout); +} + +GuestImagePage::~GuestImagePage() { + delete guestNameLabel; + delete guestName; + delete configLocLabel; + delete configLoc; + delete imageLocLabel; + delete imageLoc; + delete browseConfig; + delete browseImage; +} + +void GuestImagePage::locateConfigFile() { + QString configFile = QFileDialog::getOpenFileName(this, tr("Open file"), ".", + "XML file (*.xml)"); + configLoc->setText(configFile); +} + +void GuestImagePage::locateImageFile() { + QString imageFile = QFileDialog::getOpenFileName(this, tr("Open file"), ".", + "Image file (*.img *.bZ)"); + imageLoc->setText(imageFile); +} + +int GuestImagePage::nextId() const { + return NewVmWizard::Page_Final; +} + +bool GuestImagePage::validatePage() { + v3Proc = new QProcess(); + v3Proc->setProcessChannelMode(QProcess::MergedChannels); + v3Proc->start("cat /proc/v3vee/v3-guests"); + v3Proc->waitForFinished(); + qDebug() << "Reply from proc to check for new VM creation: " << v3Proc->readAllStandardOutput(); + QString name = field("guestName").toString(); + QByteArray temp = name.toLocal8Bit(); + const char* vmEntry = temp.data(); + bool exists = false; + + QByteArray val = v3Proc->readAllStandardOutput(); + if (!val.isNull()) { + QList procLine = val.split('\n'); + if (procLine.size() > 0) { + for (int i=0; isetCommitPage(true); + this->setFinalPage(true); + setTitle(tr("Status")); + setPixmap(QWizard::WatermarkPixmap, QPixmap(":/images/images/palacios.png")); + + finalLabel = new QLabel("VM Creation Successful"); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->addWidget(finalLabel); + + setLayout(layout); +} + +FinalPage::~FinalPage() { + delete finalLabel; +} + +int FinalPage::nextId() const { + return -1; +} diff --git a/linux_usr/gui/palacios/vm_creation_wizard.h b/linux_usr/gui/palacios/vm_creation_wizard.h new file mode 100644 index 0000000..7be86b4 --- /dev/null +++ b/linux_usr/gui/palacios/vm_creation_wizard.h @@ -0,0 +1,88 @@ +/* + * newvmwizard.h + * + * Created on: Sep 20, 2012 + * Author: abhinav + */ + +#ifndef NEWVMWIZARD_H_ +#define NEWVMWIZARD_H_ + +#include +#include + +class IntroPage; +class GuestImagePage; +class FinalPage; + +class NewVmWizard: public QWizard { +Q_OBJECT +public: + enum { + Page_Intro, Page_Image_File, Page_Final + }; + NewVmWizard(QWidget* parent = 0); + ~NewVmWizard(); + +signals: + void finishedWizard(QString guestName); + +private: + IntroPage* mIntroPage; + GuestImagePage* mImagePage; + FinalPage* mFinalPage; +}; + +class IntroPage: public QWizardPage { +Q_OBJECT + +public: + IntroPage(QWidget* parent = 0); + ~IntroPage(); + int nextId() const; + +private: + QLabel* topLabel; +}; + +class GuestImagePage: public QWizardPage { +Q_OBJECT + +public: + GuestImagePage(QWidget* parent = 0); + ~GuestImagePage(); + bool validatePage(); + int nextId() const; + +private slots: + void locateConfigFile(); + void locateImageFile(); + +private: + QLabel* guestNameLabel; + QLabel* configLocLabel; + QLabel* imageLocLabel; + + QLineEdit* guestName; + QLineEdit* configLoc; + QLineEdit* imageLoc; + + QPushButton* browseConfig; + QPushButton* browseImage; + + QProcess* v3Proc; +}; + +class FinalPage: public QWizardPage { +Q_OBJECT + +public: + FinalPage(QWidget* parent = 0); + ~FinalPage(); + int nextId() const; + +private: + QLabel* finalLabel; +}; + +#endif /* NEWVMWIZARD_H_ */ diff --git a/linux_usr/gui/palacios/vm_info_widget.cpp b/linux_usr/gui/palacios/vm_info_widget.cpp new file mode 100644 index 0000000..a1c1845 --- /dev/null +++ b/linux_usr/gui/palacios/vm_info_widget.cpp @@ -0,0 +1,356 @@ +#include "newpalacios.h" + +VmInfoWidget::VmInfoWidget(QWidget* parent) : + QWidget(parent) { + setInfoView(); + setupUi(); +} + +VmInfoWidget::~VmInfoWidget() { + delete mVmInfoView; +} + +void VmInfoWidget::setInfoView() { + // Create the main tree widget + mVmInfoView = new QTreeWidget(); + mVmInfoView->setColumnCount(2); + QStringList headerLabels; + headerLabels << "Property" << "Value"; + mVmInfoView->setHeaderLabels(headerLabels); +} + +void VmInfoWidget::setupUi() { + QVBoxLayout* vmDetailsLayout = new QVBoxLayout(this); + vmDetailsLayout->addWidget(mVmInfoView); + setLayout(vmDetailsLayout); +} + +void VmInfoWidget::parseAttribute(const QDomElement &element, + QTreeWidgetItem* parent) { + QDomNamedNodeMap atts = element.attributes(); + + for (unsigned int i=0; isetText(0, "[" + atts.item(i).nodeName() +"]"); + item->setText(1, "[" + atts.item(i).nodeValue() +"]"); + } +} + +void VmInfoWidget::parseText(const QDomElement &element, + QTreeWidgetItem* parent) { + QString value = element.text(); + parent->setText(1, value); +} + +void VmInfoWidget::parseElement(const QDomElement &element, + QTreeWidgetItem* parent) { + + QTreeWidgetItem* item = new QTreeWidgetItem(parent); + + if (element.tagName().compare("memory") == 0) { + // Memory tag + item->setText(0, "memory"); + parseAttribute(element, item); + parseText(element, item); + + } else if (element.tagName().compare("telemetry") == 0) { + // Telemetry tag + item->setText(0, "telemetry"); + parseText(element, item); + + } else if (element.tagName().compare("extensions") == 0) { + // Extensions tag + item->setText(0, "extensions"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("extension") == 0) { + // Specific Extension + item->setText(0, "extension"); + parseAttribute(element, item); + parseText(element, item); + + } else if (element.tagName().compare("paging") == 0) { + // Paging tag + item->setText(0, "paging"); + parseAttribute(element, item); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("strategy") == 0) { + item->setText(0, "strategy"); + parseText(element, item); + + } else if (element.tagName().compare("large_pages") == 0) { + item->setText(0, "large_pages"); + parseText(element, item); + + } else if (element.tagName().compare("schedule_hz") == 0) { + // Schedule Hz + item->setText(0, "schedule_hz"); + parseText(element, item); + + } else if (element.tagName().compare("cores") == 0) { + // Cores + item->setText(0, "cores"); + parseAttribute(element, item); + + } else if (element.tagName().compare("core") == 0) { + item->setText(0, "core"); + + } else if (element.tagName().compare("memmap") == 0) { + item->setText(0, "memmap"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("region") == 0) { + item->setText(0, "region"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("start") == 0) { + item->setText(0, "start"); + parseText(element, item); + + } else if (element.tagName().compare("end") == 0) { + item->setText(0, "end"); + parseText(element, item); + + } else if (element.tagName().compare("host_addr") == 0) { + item->setText(0, "host_addr"); + parseText(element, item); + + } else if (element.tagName().compare("files") == 0) { + item->setText(0, "files"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("file") == 0) { + item->setText(0, "file"); + parseAttribute(element, item); + + } else if (element.tagName().compare("devices") == 0) { + // Devices + item->setText(0, "devices"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("device") == 0) { + // Device + item->setText(0, "device"); + parseAttribute(element, item); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("storage") == 0) { + item->setText(0, "storage"); + parseText(element, item); + + } else if (element.tagName().compare("apic") == 0) { + item->setText(0, "apic"); + parseText(element, item); + + } else if (element.tagName().compare("bus") == 0) { + // Bus tag, is seen in many device tags + + } else if (element.tagName().compare("vendor_id") == 0) { + item->setText(0, "vendor_id"); + parseText(element, item); + + } else if (element.tagName().compare("device_id") == 0) { + item->setText(0, "device_id"); + parseText(element, item); + + } else if (element.tagName().compare("irq") == 0) { + item->setText(0, "irq"); + parseText(element, item); + + } else if (element.tagName().compare("controller") == 0) { + // Controller tag, is also seen in some device files + item->setText(0, "controller"); + parseText(element, item); + + } else if (element.tagName().compare("file") == 0) { + item->setText(0, "file"); + parseText(element, item); + + } else if (element.tagName().compare("frontend") == 0) { + item->setText(0, "frontend"); + parseAttribute(element, item); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("model") == 0) { + item->setText(0, "model"); + parseText(element, item); + + } else if (element.tagName().compare("type") == 0) { + item->setText(0, "type"); + parseText(element, item); + + } else if (element.tagName().compare("bus_num") == 0) { + item->setText(0, "bus_num"); + parseText(element, item); + + } else if (element.tagName().compare("drive_num") == 0) { + item->setText(0, "drive_num"); + parseText(element, item); + + } else if (element.tagName().compare("path") == 0) { + item->setText(0, "path"); + parseText(element, item); + + } else if (element.tagName().compare("ip") == 0) { + item->setText(0, "ip"); + parseText(element, item); + + } else if (element.tagName().compare("port") == 0) { + item->setText(0, "port"); + parseText(element, item); + + } else if (element.tagName().compare("tag") == 0) { + item->setText(0, "tag"); + parseText(element, item); + + } else if (element.tagName().compare("size") == 0) { + item->setText(0, "size"); + parseText(element, item); + + } else if (element.tagName().compare("mac") == 0) { + item->setText(0, "mac"); + parseText(element, item); + + } else if (element.tagName().compare("name") == 0) { + item->setText(0, "name"); + parseText(element, item); + + } else if (element.tagName().compare("ports") == 0) { + item->setText(0, "ports"); + QDomNode child = element.firstChild(); + while (!child.isNull()) { + if (!child.isElement()) { + child = child.nextSibling(); + continue; + } + + parseElement(child.toElement(), item); + child = child.nextSibling(); + } + + } else if (element.tagName().compare("mode") == 0) { + item->setText(0, "mode"); + parseText(element, item); + + } +} + +void VmInfoWidget::updateInfoView(VmInfo* vm) { + // Open config file + QFile file(vm->getVmConfigFile()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "File cannot be opened!"; + return; + } + + // Setup DOM parser + QDomDocument doc("vm_config"); + if (!doc.setContent(&file)) { + qDebug() << "Error setting content"; + return; + } + + file.close(); + + // Root element + QTreeWidgetItem* rootItem = new QTreeWidgetItem(); + QDomElement root = doc.documentElement(); + + rootItem->setText(0, root.tagName()); + parseAttribute(root, rootItem); + mVmInfoView->takeTopLevelItem(0); + mVmInfoView->addTopLevelItem(rootItem); + + // Elements + QDomNode n = root.firstChild(); + while (!n.isNull()) { + + if (n.isElement()) { + parseElement(n.toElement(), rootItem); + } + + n = n.nextSibling(); + } + + mVmInfoView->expandAll(); +} + +void VmInfoWidget::deleteVm() { + QTreeWidgetItem* item = mVmInfoView->takeTopLevelItem(0); + delete item; +} diff --git a/linux_usr/gui/palacios/vm_mode_dialog.cpp b/linux_usr/gui/palacios/vm_mode_dialog.cpp new file mode 100644 index 0000000..8d8ae70 --- /dev/null +++ b/linux_usr/gui/palacios/vm_mode_dialog.cpp @@ -0,0 +1,120 @@ +#include + +#include "newpalacios.h" + +VmModeDialog::VmModeDialog(QWidget* parent) { + setupDialog(); +} + +void VmModeDialog::setupDialog() { + isV3Cons = false; + isV3Stream = false; + isV3Vnc = false; + + // Group box to hold radio buttons + v3_modes = new QGroupBox(tr("Select VM mode")); + // Widget to give information about stream name + // for v3_stream + v3_stream_info = new QWidget(v3_modes); + + v3_stream = new QRadioButton(tr("Stream Mode")); + v3_cons = new QRadioButton(tr("Console Mode")); + v3_vnc = new QRadioButton(tr("VNC mode")); + + // Setup stream info widget + QLabel* v3_stream_label = new QLabel(tr("Stream name")); + v3_stream_name = new QLineEdit(); + + QGridLayout* grid = new QGridLayout(); + grid->addWidget(v3_stream_label, 0, 0); + grid->addWidget(v3_stream_name, 0, 1); + v3_stream_info->setLayout(grid); + v3_stream_info->setVisible(false); + + // Setup group box + QVBoxLayout* box = new QVBoxLayout(); + box->addWidget(v3_cons); + box->addWidget(v3_stream); + box->addWidget(v3_stream_info); + box->addWidget(v3_vnc); + v3_modes->setLayout(box); + + // Setup main layout for dialog + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->addWidget(v3_modes); + QHBoxLayout* actionLayout = new QHBoxLayout(); + QPushButton* ok = new QPushButton(tr("OK")); + QPushButton* cancel = new QPushButton(tr("Cancel")); + actionLayout->addWidget(ok); + actionLayout->addWidget(cancel); + mainLayout->addLayout(actionLayout); + + setLayout(mainLayout); + resize(300, 200); + move(200, 200); + setWindowTitle("Select VM mode"); + + // Connect signals and slots + connect(v3_cons, SIGNAL(toggled(bool)), this, SLOT(selectMode(bool))); + connect(v3_stream, SIGNAL(toggled(bool)), this, SLOT(selectMode(bool))); + connect(v3_vnc, SIGNAL(toggled(bool)), this, SLOT(selectMode(bool))); + connect(ok, SIGNAL(clicked()), this, SLOT(okButton())); + connect(cancel, SIGNAL(clicked()), this, SLOT(cancelButton())); +} + +void VmModeDialog::okButton() { + if (isV3Cons == false + && isV3Stream == false + && isV3Vnc == false) { + + // Do not emit anything + close(); + return; + } + + QString name = ""; + + if (isV3Stream) { + name = v3_stream_name->text(); + } + + emit setMode(mode, name); + close(); +} + +void VmModeDialog::cancelButton() { + close(); +} + +void VmModeDialog::selectMode(bool checked) { + if (checked == true) { + if (v3_cons->isChecked()) { + // If console is checked, then set + // mode to v3_cons + mode = VmConsoleWidget::CONSOLE; + isV3Cons = true; + isV3Stream = false; + isV3Vnc = false; + v3_stream_info->setVisible(false); + + } else if (v3_stream->isChecked()) { + mode = VmConsoleWidget::STREAM; + isV3Cons = false; + isV3Stream = true; + isV3Vnc = false; + v3_stream_info->setVisible(true); + + } else if (v3_vnc->isChecked()) { + mode = VmConsoleWidget::VNC; + isV3Cons = false; + isV3Stream = false; + isV3Vnc = true; + v3_stream_info->setVisible(false); + + } else { + isV3Cons = false; + isV3Stream = false; + isV3Vnc = false; + } + } +} diff --git a/linux_usr/gui/palacios/vm_threads.cpp b/linux_usr/gui/palacios/vm_threads.cpp new file mode 100644 index 0000000..2acedd8 --- /dev/null +++ b/linux_usr/gui/palacios/vm_threads.cpp @@ -0,0 +1,446 @@ +#include + +#include "newpalacios.h" + +class ProcVm { +public: + QString name; + QString devFile; +}; + +class DbVm { +public: + QString name; + QString config; + QString image; + int state; + QString dev; +}; + +int LoadVmsThread::getStatus() { + return status; +} + +QList LoadVmsThread::getVmList() { + return list; +} + +void LoadVmsThread::loadVms() { + bool doesProcExist = false; + bool isDbPresent = false; + + // Check if /proc file exists + QFileInfo procInfo("/proc/v3vee/v3-guests"); + if (procInfo.exists()) { + // Proc file exists + doesProcExist = true; + } + + // Read proc file + //QStringList procVms; + QList procVms; + + if (doesProcExist) { + QProcess* p = new QProcess(); + p->setProcessChannelMode(QProcess::MergedChannels); + QStringList args; + args << "-c" << "cat /proc/v3vee/v3-guests"; + p->start("sh", args); + if (!p->waitForFinished()) { + // Since we are unable to open the proc file + // for whatever some reason, the only way + // to identify this state would be to mark /proc + // as not existing + doesProcExist = false; + + } else { + QByteArray procFile = p->readAllStandardOutput(); + if (!procFile.isNull()) { + if (procFile.isEmpty()) { + doesProcExist = false; + + } else { + QList procContents = procFile.split('\n'); + + for (int i=0; i dbVms; + + if (isDbPresent) { + // Read the DB file + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.compare("\n") == 0) { + continue; + } + + QStringList vmDet = line.split(","); + DbVm dbVm; + dbVm.name = vmDet.at(0); + dbVm.config = vmDet.at(1); + dbVm.dev = vmDet.at(2); + dbVm.state = vmDet.at(3).toInt(); + dbVm.image = vmDet.at(4); + + dbVms.append(dbVm); + } + } + + if (!doesProcExist && isDbPresent) { + // If /proc does not exist but DB does + // then all VMs are inactive + for (int i=0; isetVmName(dbVms[i].name); + vmDb->setVmConfigFile(dbVms[i].config); + vmDb->setVmDevFile(dbVms[i].dev); + vmDb->setState(dbVms[i].state); + vmDb->setImageFile(dbVms[i].image); + vmDb->setCategory(VmInfo::INACTIVE_INVENTORY); + // Add VM to loading list + list.append(vmDb); + } + + } else if (doesProcExist && !isDbPresent) { + // If /proc exists but DB does not + for (int i=0; isetVmName(procVms[i].name); + vmProc->setVmConfigFile(""); + vmProc->setVmDevFile(procVms[i].devFile); + vmProc->setState(VmInfo::STOPPED); + vmProc->setImageFile(""); + vmProc->setCategory(VmInfo::ACTIVE_NOT_INVENTORY); + // Add VM to loading list + list.append(vmProc); + } + + } else if (doesProcExist && isDbPresent) { + // Both files exist + // Compare entries in /proc and text file, mark non-matching entries in /proc as active not inventory + QList procIndices; + QList dbIndices; + + int found = -1; + + for (int i=0; i= 0) { + VmInfo* vm = new VmInfo(); + vm->setVmName(dbVms[found].name); + vm->setVmConfigFile(dbVms[found].config); + vm->setVmDevFile(dbVms[found].dev); + vm->setState(dbVms[found].state); + vm->setImageFile(dbVms[found].image); + + vm->setCategory(VmInfo::ACTIVE_INVENTORY); + // Add VM to loading list + list.append(vm); + found = -1; + } + } + + // Once we have compared /proc with the DB and marked matching indices + // we mark the rest of the entries in /proc as active not inventory + for (int j=0; jsetVmName(procVms[j].name); + vmProcOnly->setVmConfigFile(""); + vmProcOnly->setVmDevFile(procVms[j].devFile); + vmProcOnly->setState(VmInfo::STOPPED); + vmProcOnly->setImageFile(""); + vmProcOnly->setCategory(VmInfo::ACTIVE_NOT_INVENTORY); + list.append(vmProcOnly); + } + } + + // Once we have compared /proc with the DB and marked matching indices + // we mark the rest of the entries in dbVms as inactive + for (int j=0; jsetVmName(dbVms[j].name); + vmDbOnly->setVmConfigFile(dbVms[j].config); + vmDbOnly->setVmDevFile(""); + vmDbOnly->setState(dbVms[j].state); + vmDbOnly->setImageFile(dbVms[j].image); + vmDbOnly->setCategory(VmInfo::INACTIVE_INVENTORY); + list.append(vmDbOnly); + } + } + } + + status = STATUS_OK; + file.close(); + + emit finished(); +} + +AddVmThread::AddVmThread(QString name, QString conf, QString img) { + this->status = STATUS_OK; + this->vmName = name; + this->vmConfigFile = conf; + this->vmImageFile = img; +} + +int AddVmThread::getStatus() { + return status; +} + +VmInfo* AddVmThread::getNewVm() { + return vm; +} + +QString AddVmThread::getName() { + return vmName; +} + +void AddVmThread::addVm() { + QFile file("virtual_machines_list.txt"); + if (!file.open(QIODevice::Append | QIODevice::Text)) { + status = ERROR_V3CREATE_DB; + emit finished(); + return; + } + + // Add VM to the inventory (database). The VM still needs to be + // activated to run + // Create new VM instance + // The dev file for this VM will be set when it is activated + vm = new VmInfo(); + vm->setVmName(vmName); + vm->setVmConfigFile(vmConfigFile); + vm->setImageFile(vmImageFile); + vm->setCategory(VmInfo::INACTIVE_INVENTORY); + + QTextStream out(&file); + + out << vmName; + out << ","; + out << vmConfigFile; + out << ","; + out << vmDevFile; + out << ","; + out << QString::number(VmInfo::STOPPED); + out << ","; + out << vmImageFile; + out << endl; + + out.flush(); + file.close(); + + status = STATUS_OK; + + emit finished(); +} + +DeleteVmThread::DeleteVmThread(int category, QString name, QString devfile) { + this->vmCategory = category; + this->vmToDelete = name; + this->status = -2; + this->vmDevfile = devfile; +} + +int DeleteVmThread::getStatus() { + return status; +} + +/*void DeleteVmThread::processStarted() { + //qDebug() << "V3 Free process started"; +} + +void DeleteVmThread::processExit(int errorCode, QProcess::ExitStatus exitStatus) { + if (exitStatus == QProcess::CrashExit) { + //qDebug() << "v3_free process crashed!!"; + } else { + //qDebug() << "v3_free process exited normally.."; + } +} + +void DeleteVmThread::processError(QProcess::ProcessError error) { + //qDebug() << "There was an error in the v3_free process execution..."; + + switch (error) { + case QProcess::FailedToStart: + //qDebug() << "Process failed to start..."; + break; + case QProcess::Crashed: + //qDebug() << "Process crashed..."; + break; + case QProcess::Timedout: + //qDebug() << "Process timed out..."; + break; + case QProcess::WriteError: + //qDebug() << "Process had write error..."; + break; + case QProcess::ReadError: + //qDebug() << "Process had read error..."; + break; + case QProcess::UnknownError: + //qDebug() << "Process unknown error..."; + break; + + } +}*/ + +void DeleteVmThread::deleteVm() { + + // Check for the category of VM being deleted + // If Active or Active not inventory then only + // invoke v3_free since we have an instance of the + // VM in proc. If it is Inactive and we are deleting + // then we need to just remove it from the DB + + // Create variables + // C++ complains if we create variables inside switch statement + QProcess* v3Freeproc; + QStringList args; + QByteArray procOutput; + + switch (vmCategory) { + case VmInfo::ACTIVE_INVENTORY: + case VmInfo::ACTIVE_NOT_INVENTORY: + if (vmDevfile == NULL) { + // Cannot delete VM without dev file + status = ERROR_V3FREE_INVALID_ARGUMENT; + emit finished(); + return; + } + + v3Freeproc = new QProcess(); + v3Freeproc->setProcessChannelMode(QProcess::MergedChannels); + + // Connect debug slots + connect(v3Freeproc, SIGNAL(started()), this, SLOT(processStarted())); + connect(v3Freeproc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processExit(int, QProcess::ExitStatus))); + connect(v3Freeproc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + + args << vmDevfile; + v3Freeproc->start("v3_free", args); + + if (!v3Freeproc->waitForFinished()) { + // Reinsert data into database since v3_free was not successful + status = ERROR_V3FREE_PATH; + delete v3Freeproc; + emit finished(); + return; + } else { + procOutput = v3Freeproc->readAllStandardOutput(); + + if (procOutput.contains("IOCTL error")) { + status = ERROR_V3FREE_IOCTL; + delete v3Freeproc; + emit finished(); + return; + } + } + + // V3_free success + if (v3Freeproc != NULL) { + delete v3Freeproc; + } + + break; + case VmInfo::INACTIVE_INVENTORY: + default: + break; + } + + QFile file("virtual_machines_list.txt"); + QFile tempFile("temp.txt"); + + bool oldFile = file.open(QIODevice::ReadOnly | QIODevice::Text); + bool newFile = tempFile.open(QIODevice::WriteOnly | QIODevice::Text); + + if (!oldFile || !newFile) { + status = ERROR_V3FREE_DB; + emit finished(); + return; + } + + QTextStream in(&file); + QTextStream out(&tempFile); + + while (!in.atEnd()) { + QString line = in.readLine(); + QStringList vm = line.split(","); + if (vm.at(0).compare(vmToDelete) != 0) { + out << line << endl; + } + } + + out.flush(); + file.remove("virtual_machines_list.txt"); + tempFile.rename("virtual_machines_list.txt"); + + file.close(); + tempFile.close(); + status = STATUS_OK; + + emit finished(); +} diff --git a/linux_usr/gui/palacios/vm_view.cpp b/linux_usr/gui/palacios/vm_view.cpp new file mode 100644 index 0000000..48e5a3d --- /dev/null +++ b/linux_usr/gui/palacios/vm_view.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include "newpalacios.h" + +VmXConsoleParent::VmXConsoleParent(QString name, QWidget* parent) + : QWidget(parent) { + + mVmName = name; + + QVBoxLayout* l = new QVBoxLayout; + mConsole = new VmConsoleWidget(this); + l->addWidget(mConsole); + l->setStretchFactor(mConsole, 1000); + + this->setLayout(l); + this->setWindowTitle(name); + + connect(this, SIGNAL(windowClosing()), mConsole, + SLOT(mainWindowClosing())); +} + +void VmXConsoleParent::showWidget(int mode, QString devfile, QString streamName) { + mConsole->start(mode, devfile, streamName); + + // Workaround for QProcess error + mIsConsoleRunning = true; +} + +bool VmXConsoleParent::isRunning() { + // FIXME: For some reason QProcess does not seem to return the correct state + // Qprocess->state() should give the correct state of the running process + // but currently there is a problem with geting the right state. + // For the time being a boolean flag is used to represent the state (running/not running) + + //return mConsole->isRunning(); + return mIsConsoleRunning; +} + +QString VmXConsoleParent::getVmName() { + return mVmName; +} + +void VmXConsoleParent::closeEvent(QCloseEvent* event) { + // Workaround for QProcess error + /*if (!tryTerminate()) + qDebug() << "Warning: could not terminate process...there could be a leak"; + else + qDebug() << "Process terminated";*/ + + mIsConsoleRunning = false; + emit windowClosingWithId(mVmName); + emit windowClosing(); + event->accept(); +} diff --git a/linux_usr/gui/palacios/vnc_module/remoteview.cpp b/linux_usr/gui/palacios/vnc_module/remoteview.cpp new file mode 100644 index 0000000..e45a1a1 --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/remoteview.cpp @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** Copyright (C) 2002-2003 Tim Jansen +** Copyright (C) 2007-2008 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#include "remoteview.h" + +#ifndef QTONLY + #include + #include +#endif + +#include + +RemoteView::RemoteView(QWidget *parent) + : QWidget(parent), + m_status(Disconnected), + m_host(QString()), + m_port(0), + m_viewOnly(false), + m_grabAllKeys(false), + m_scale(false), + m_keyboardIsGrabbed(false), +#ifndef QTONLY + m_wallet(0), +#endif + m_dotCursorState(CursorOff) +{ +} + +RemoteView::~RemoteView() +{ +#ifndef QTONLY + delete m_wallet; +#endif +} + +RemoteView::RemoteStatus RemoteView::status() +{ + return m_status; +} + +void RemoteView::setStatus(RemoteView::RemoteStatus s) +{ + if (m_status == s) + return; + + if (((1+ m_status) != s) && (s != Disconnected)) { + // follow state transition rules + + if (s == Disconnecting) { + if (m_status == Disconnected) + return; + } else { + Q_ASSERT(((int) s) >= 0); + if (m_status > s) { + m_status = Disconnected; + emit statusChanged(Disconnected); + } + // smooth state transition + RemoteStatus origState = m_status; + for (int i = origState; i < s; ++i) { + m_status = (RemoteStatus) i; + emit statusChanged((RemoteStatus) i); + } + } + } + m_status = s; + emit statusChanged(m_status); +} + +bool RemoteView::supportsScaling() const +{ + return false; +} + +bool RemoteView::supportsLocalCursor() const +{ + return false; +} + +QString RemoteView::host() +{ + return m_host; +} + +QSize RemoteView::framebufferSize() +{ + return QSize(0, 0); +} + +void RemoteView::startQuitting() +{ +} + +bool RemoteView::isQuitting() +{ + return false; +} + +int RemoteView::port() +{ + return m_port; +} + +void RemoteView::updateConfiguration() +{ +} + +void RemoteView::keyEvent(QKeyEvent *) +{ +} + +bool RemoteView::viewOnly() +{ + return m_viewOnly; +} + +void RemoteView::setViewOnly(bool viewOnly) +{ + m_viewOnly = viewOnly; +} + +bool RemoteView::grabAllKeys() +{ + return m_grabAllKeys; +} + +void RemoteView::setGrabAllKeys(bool grabAllKeys) +{ + m_grabAllKeys = grabAllKeys; + + if (grabAllKeys) { + m_keyboardIsGrabbed = true; + grabKeyboard(); + } else if (m_keyboardIsGrabbed) { + releaseKeyboard(); + } +} + +QPixmap RemoteView::takeScreenshot() +{ + return QPixmap::grabWidget(this); +} + +void RemoteView::showDotCursor(DotCursorState state) +{ + m_dotCursorState = state; +} + +RemoteView::DotCursorState RemoteView::dotCursorState() const +{ + return m_dotCursorState; +} + +bool RemoteView::scaling() const +{ + return m_scale; +} + +void RemoteView::enableScaling(bool scale) +{ + m_scale = scale; +} + +void RemoteView::switchFullscreen(bool) +{ +} + +void RemoteView::scaleResize(int, int) +{ +} + +KUrl RemoteView::url() +{ + return m_url; +} + +#ifndef QTONLY +QString RemoteView::readWalletPassword(bool fromUserNameOnly) +{ + const QString KRDCFOLDER = "KRDC"; + + window()->setDisabled(true); // WORKAROUND: disable inputs so users cannot close the current tab (see #181230) + m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), window()->winId()); + window()->setDisabled(false); + + if (m_wallet) { + bool walletOK = m_wallet->hasFolder(KRDCFOLDER); + if (!walletOK) { + walletOK = m_wallet->createFolder(KRDCFOLDER); + kDebug(5010) << "Wallet folder created"; + } + if (walletOK) { + kDebug(5010) << "Wallet OK"; + m_wallet->setFolder(KRDCFOLDER); + QString password; + + QString key; + if (fromUserNameOnly) + key = m_url.userName(); + else + key = m_url.prettyUrl(KUrl::RemoveTrailingSlash); + + if (m_wallet->hasEntry(key) && + !m_wallet->readPassword(key, password)) { + kDebug(5010) << "Password read OK"; + + return password; + } + } + } + return QString(); +} + +void RemoteView::saveWalletPassword(const QString &password, bool fromUserNameOnly) +{ + QString key; + if (fromUserNameOnly) + key = m_url.userName(); + else + key = m_url.prettyUrl(KUrl::RemoveTrailingSlash); + + if (m_wallet && m_wallet->isOpen()) { + kDebug(5010) << "Write wallet password"; + m_wallet->writePassword(key, password); + } +} +#endif + +QCursor RemoteView::localDotCursor() const +{ +#ifdef QTONLY + return QCursor(); //TODO +#else + QBitmap cursorBitmap(KGlobal::dirs()->findResource("appdata", + "pics/pointcursor.png")); + QBitmap cursorMask(KGlobal::dirs()->findResource("appdata", + "pics/pointcursormask.png")); + return QCursor(cursorBitmap, cursorMask); +#endif +} + +void RemoteView::focusInEvent(QFocusEvent *event) +{ + if (m_grabAllKeys) { + m_keyboardIsGrabbed = true; + grabKeyboard(); + } + + QWidget::focusInEvent(event); +} + +void RemoteView::focusOutEvent(QFocusEvent *event) +{ + if (m_grabAllKeys || m_keyboardIsGrabbed) { + m_keyboardIsGrabbed = false; + releaseKeyboard(); + } + + QWidget::focusOutEvent(event); +} + +#include "moc_remoteview.cpp" diff --git a/linux_usr/gui/palacios/vnc_module/remoteview.h b/linux_usr/gui/palacios/vnc_module/remoteview.h new file mode 100644 index 0000000..10d22e0 --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/remoteview.h @@ -0,0 +1,415 @@ +/**************************************************************************** +** +** Copyright (C) 2002-2003 Tim Jansen +** Copyright (C) 2007-2008 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#ifndef REMOTEVIEW_H +#define REMOTEVIEW_H + +#ifdef QTONLY + #include + #define KUrl QUrl + #define KRDCCORE_EXPORT +#else + #include + #include + #include +#endif + +#include + +class HostPreferences; + +/** + * Generic widget that displays a remote framebuffer. + * Implement this if you want to add another backend. + * + * Things to take care of: + * @li The RemoteView is responsible for its size. In + * non-scaling mode, set the fixed size of the widget + * to the remote resolution. In scaling mode, set the + * maximum size to the remote size and minimum size to the + * smallest resolution that your scaler can handle. + * @li if you override mouseMoveEvent() + * you must ignore the QEvent, because the KRDC widget will + * need it for stuff like toolbar auto-hide and bump + * scrolling. If you use x11Event(), make sure that + * MotionNotify events will be forwarded. + * + */ +class KRDCCORE_EXPORT RemoteView : public QWidget +{ + Q_OBJECT + +public: + + Q_ENUMS(Quality) + + enum Quality { + Unknown, + High, + Medium, + Low + }; + + /** + * Describes the state of a local cursor, if there is such a concept in the backend. + * With local cursors, there are two cursors: the cursor on the local machine (client), + * and the cursor on the remote machine (server). Because there is usually some lag, + * some backends show both cursors simultanously. In the VNC backend the local cursor + * is a dot and the remote cursor is the 'real' cursor, usually an arrow. + */ + + Q_ENUMS(DotCursorState) + + enum DotCursorState { + CursorOn, ///< Always show local cursor (and the remote one). + CursorOff, ///< Never show local cursor, only the remote one. + /// Try to measure the lag and enable the local cursor if the latency is too high. + CursorAuto + }; + + /** + * State of the connection. The state of the connection is returned + * by @ref RemoteView::status(). + * + * Not every state transition is allowed. You are only allowed to transition + * a state to the following state, with three exceptions: + * @li You can move from every state directly to Disconnected + * @li You can move from every state except Disconnected to + * Disconnecting + * @li You can move from Disconnected to Connecting + * + * @ref RemoteView::setStatus() will follow this rules for you. + * (If you add/remove a state here, you must adapt it) + */ + + Q_ENUMS(RemoteStatus) + + enum RemoteStatus { + Connecting = 0, + Authenticating = 1, + Preparing = 2, + Connected = 3, + Disconnecting = -1, + Disconnected = -2 + }; + + Q_ENUMS(ErrorCode) + + enum ErrorCode { + Nonne = 0, + Internal, + Connection, + Protocol, + IO, + Name, + NoServer, + ServerBlocked, + Authentication + }; + + virtual ~RemoteView(); + + /** + * Checks whether the backend supports scaling. The + * default implementation returns false. + * @return true if scaling is supported + * @see scaling() + */ + virtual bool supportsScaling() const; + + /** + * Checks whether the widget is in scale mode. The + * default implementation always returns false. + * @return true if scaling is activated. Must always be + * false if @ref supportsScaling() returns false + * @see supportsScaling() + */ + virtual bool scaling() const; + + /** + * Checks whether the backend supports the concept of local cursors. The + * default implementation returns false. + * @return true if local cursors are supported/known + * @see DotCursorState + * @see showDotCursor() + * @see dotCursorState() + */ + virtual bool supportsLocalCursor() const; + + /** + * Sets the state of the dot cursor, if supported by the backend. + * The default implementation does nothing. + * @param state the new state (CursorOn, CursorOff or + * CursorAuto) + * @see dotCursorState() + * @see supportsLocalCursor() + */ + virtual void showDotCursor(DotCursorState state); + + /** + * Returns the state of the local cursor. The default implementation returns + * always CursorOff. + * @return true if local cursors are supported/known + * @see showDotCursor() + * @see supportsLocalCursor() + */ + virtual DotCursorState dotCursorState() const; + + /** + * Checks whether the view is in view-only mode. This means + * that all input is ignored. + */ + virtual bool viewOnly(); + + /** + * Checks whether grabbing all possible keys is enabled. + */ + virtual bool grabAllKeys(); + + /** + * Returns the resolution of the remote framebuffer. + * It should return a null @ref QSize when the size + * is not known. + * The backend must also emit a @ref framebufferSizeChanged() + * when the size of the framebuffer becomes available + * for the first time or the size changed. + * @return the remote framebuffer size, a null QSize + * if unknown + */ + virtual QSize framebufferSize(); + + /** + * Initiate the disconnection. This doesn't need to happen + * immediately. The call must not block. + * @see isQuitting() + */ + virtual void startQuitting(); + + /** + * Checks whether the view is currently quitting. + * @return true if it is quitting + * @see startQuitting() + * @see setStatus() + */ + virtual bool isQuitting(); + + /** + * @return the host the view is connected to + */ + virtual QString host(); + + /** + * @return the port the view is connected to + */ + virtual int port(); + + /** + * Initialize the view (for example by showing configuration + * dialogs to the user) and start connecting. Should not block + * without running the event loop (so displaying a dialog is ok). + * When the view starts connecting the application must call + * @ref setStatus() with the status Connecting. + * @return true if successful (so far), false + * otherwise + * @see connected() + * @see disconnected() + * @see disconnectedError() + * @see statusChanged() + */ + virtual bool start() = 0; + + /** + * Called when the configuration is changed. + * The default implementation does nothing. + */ + virtual void updateConfiguration(); + + /** + * @return screenshot of the view + */ + virtual QPixmap takeScreenshot(); + +#ifndef QTONLY + /** + * Returns the current host preferences of this view. + */ + virtual HostPreferences* hostPreferences() = 0; +#endif + + /** + * Returns the current status of the connection. + * @return the status of the connection + * @see setStatus() + */ + RemoteStatus status(); + + /** + * @return the current url + */ + KUrl url(); + +public slots: + /** + * Called to enable or disable scaling. + * Ignored if @ref supportsScaling() is false. + * The default implementation does nothing. + * @param s true to enable, false to disable. + * @see supportsScaling() + * @see scaling() + */ + virtual void enableScaling(bool scale); + + /** + * Enables/disables the view-only mode. + * Ignored if @ref supportsScaling() is false. + * The default implementation does nothing. + * @param viewOnly true to enable, false to disable. + * @see supportsScaling() + * @see viewOnly() + */ + virtual void setViewOnly(bool viewOnly); + + /** + * Enables/disables grabbing all possible keys. + * @param grabAllKeys true to enable, false to disable. + * Default is false. + * @see grabAllKeys() + */ + virtual void setGrabAllKeys(bool grabAllKeys); + + /** + * Called to let the backend know it when + * we switch from/to fullscreen. + * @param on true when switching to fullscreen, + * false when switching from fullscreen. + */ + virtual void switchFullscreen(bool on); + + /** + * Sends a QKeyEvent to the remote server. + * @param event the key to send + */ + virtual void keyEvent(QKeyEvent *event); + + /** + * Called when the visible place changed so remote + * view can resize itself. + */ + virtual void scaleResize(int w, int h); + +signals: + /** + * Emitted when the size of the remote screen changes. Also + * called when the size is known for the first time. + * @param x the width of the screen + * @param y the height of the screen + */ + void framebufferSizeChanged(int w, int h); + + /** + * Emitted when the view connected successfully. + */ + void connected(); + + /** + * Emitted when the view disconnected without error. + */ + void disconnected(); + + /** + * Emitted when the view disconnected with error. + */ + void disconnectedError(); + + /** + * Emitted when the view has a specific error. + */ + void errorMessage(const QString &title, const QString &message); + + /** + * Emitted when the status of the view changed. + * @param s the new status + */ + void statusChanged(RemoteView::RemoteStatus s); + + /** + * Emitted when the password dialog is shown or hidden. + * @param b true when the dialog is shown, false when it has been hidden + */ + void showingPasswordDialog(bool b); + + /** + * Emitted when the mouse on the remote side has been moved. + * @param x the new x coordinate + * @param y the new y coordinate + * @param buttonMask the mask of mouse buttons (bit 0 for first mouse + * button, 1 for second button etc)a + */ + void mouseStateChanged(int x, int y, int buttonMask); + +protected: + RemoteView(QWidget *parent = 0); + + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + + /** + * The status of the remote view. + */ + RemoteStatus m_status; + + /** + * Set the status of the connection. + * Emits a statusChanged() signal. + * Note that the states need to be set in a certain order, + * see @ref Status. setStatus() will try to do this + * transition automatically, so if you are in Connecting + * and call setStatus(Preparing), setStatus() will + * emit a Authenticating and then Preparing. + * If you transition backwards, it will emit a + * Disconnected before doing the transition. + * @param s the new status + */ + virtual void setStatus(RemoteStatus s); + + QCursor localDotCursor() const; + + QString m_host; + int m_port; + bool m_viewOnly; + bool m_grabAllKeys; + bool m_scale; + bool m_keyboardIsGrabbed; + KUrl m_url; + +#ifndef QTONLY + QString readWalletPassword(bool fromUserNameOnly = false); + void saveWalletPassword(const QString &password, bool fromUserNameOnly = false); + KWallet::Wallet *m_wallet; +#endif + + DotCursorState m_dotCursorState; +}; + +#endif diff --git a/linux_usr/gui/palacios/vnc_module/vncclientthread.cpp b/linux_usr/gui/palacios/vnc_module/vncclientthread.cpp new file mode 100644 index 0000000..bb43b90 --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/vncclientthread.cpp @@ -0,0 +1,454 @@ +/**************************************************************************** +** +** Copyright (C) 2007-2008 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#include "vncclientthread.h" + +#include +#include + +//for detecting intel AMT KVM vnc server +static const QString INTEL_AMT_KVM_STRING= "Intel(r) AMT KVM"; +static QString outputErrorMessageString; + +QVector VncClientThread::m_colorTable; + +void VncClientThread::setClientColorDepth(rfbClient* cl, VncClientThread::ColorDepth cd) +{ + switch(cd) { + case bpp8: + if (m_colorTable.isEmpty()) { + m_colorTable.resize(256); + int r,g,b; + for (int i = 0; i < 256; ++i) { + //pick out the red (3 bits), green (3 bits) and blue (2 bits) bits and make them maximum significant in 8bits + //this gives a colortable for 8bit true colors + r= (i & 0x07) << 5; + g= (i & 0x38) << 2; + b= i & 0xc0; + m_colorTable[i] = qRgb(r, g, b); + } + } + cl->format.depth = 8; + cl->format.bitsPerPixel = 8; + cl->format.redShift = 0; + cl->format.greenShift = 3; + cl->format.blueShift = 6; + cl->format.redMax = 7; + cl->format.greenMax = 7; + cl->format.blueMax = 3; + break; + case bpp16: + cl->format.depth = 16; + cl->format.bitsPerPixel = 16; + cl->format.redShift = 11; + cl->format.greenShift = 5; + cl->format.blueShift = 0; + cl->format.redMax = 0x1f; + cl->format.greenMax = 0x3f; + cl->format.blueMax = 0x1f; + break; + case bpp32: + default: + cl->format.depth = 24; + cl->format.bitsPerPixel = 32; + cl->format.redShift = 16; + cl->format.greenShift = 8; + cl->format.blueShift = 0; + cl->format.redMax = 0xff; + cl->format.greenMax = 0xff; + cl->format.blueMax = 0xff; + } +} + +rfbBool VncClientThread::newclient(rfbClient *cl) +{ + VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0); + Q_ASSERT(t); + + //8bit color hack for Intel(r) AMT KVM "classic vnc" = vnc server built in in Intel Vpro chipsets. + if (INTEL_AMT_KVM_STRING == cl->desktopName) { + kDebug(5011) << "Intel(R) AMT KVM: switching to 8 bit color depth (workaround, recent libvncserver needed)"; + t->setColorDepth(bpp8); + } + setClientColorDepth(cl, t->colorDepth()); + + const int width = cl->width, height = cl->height, depth = cl->format.bitsPerPixel; + const int size = width * height * (depth / 8); + if (t->frameBuffer) + delete [] t->frameBuffer; // do not leak if we get a new framebuffer size + t->frameBuffer = new uint8_t[size]; + cl->frameBuffer = t->frameBuffer; + memset(cl->frameBuffer, '\0', size); + + switch (t->quality()) { + case RemoteView::High: + cl->appData.encodingsString = "copyrect zlib hextile raw"; + cl->appData.compressLevel = 0; + cl->appData.qualityLevel = 9; + break; + case RemoteView::Medium: + cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; + cl->appData.compressLevel = 5; + cl->appData.qualityLevel = 7; + break; + case RemoteView::Low: + case RemoteView::Unknown: + default: + cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; + cl->appData.compressLevel = 9; + cl->appData.qualityLevel = 1; + } + + SetFormatAndEncodings(cl); + kDebug(5011) << "Client created"; + return true; +} + +void VncClientThread::updatefb(rfbClient* cl, int x, int y, int w, int h) +{ +// kDebug(5011) << "updated client: x: " << x << ", y: " << y << ", w: " << w << ", h: " << h; + VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0); + Q_ASSERT(t); + + const int width = cl->width, height = cl->height; + QImage img; + switch(t->colorDepth()) { + case bpp8: + img = QImage(cl->frameBuffer, width, height, QImage::Format_Indexed8); + img.setColorTable(m_colorTable); + break; + case bpp16: + img = QImage(cl->frameBuffer, width, height, QImage::Format_RGB16); + break; + case bpp32: + img = QImage(cl->frameBuffer, width, height, QImage::Format_RGB32); + break; + } + + if (img.isNull()) { + kDebug(5011) << "image not loaded"; + } + + t->setImage(img); + + t->emitUpdated(x, y, w, h); +} + +void VncClientThread::cuttext(rfbClient* cl, const char *text, int textlen) +{ + const QString cutText = QString::fromUtf8(text, textlen); + kDebug(5011) << cutText; + + if (!cutText.isEmpty()) { + VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0); + Q_ASSERT(t); + + t->emitGotCut(cutText); + } +} + +char *VncClientThread::passwdHandler(rfbClient *cl) +{ + kDebug(5011) << "password request" << kBacktrace(); + + VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0); + Q_ASSERT(t); + + t->passwordRequest(); + t->m_passwordError = true; + + return strdup(t->password().toLocal8Bit()); +} + +void VncClientThread::outputHandler(const char *format, ...) +{ + va_list args; + va_start(args, format); + + QString message; + message.vsprintf(format, args); + + va_end(args); + + message = message.trimmed(); + + kDebug(5011) << message; + + if ((message.contains("Couldn't convert ")) || + (message.contains("Unable to connect to VNC server"))) + outputErrorMessageString = i18n("Server not found."); + + if ((message.contains("VNC connection failed: Authentication failed, too many tries")) || + (message.contains("VNC connection failed: Too many authentication failures"))) + outputErrorMessageString = i18n("VNC authentication failed because of too many authentication tries."); + + if (message.contains("VNC connection failed: Authentication failed")) + outputErrorMessageString = i18n("VNC authentication failed."); + + if (message.contains("VNC server closed connection")) + outputErrorMessageString = i18n("VNC server closed connection."); + + // internal messages, not displayed to user + if (message.contains("VNC server supports protocol version 3.889")) // see http://bugs.kde.org/162640 + outputErrorMessageString = "INTERNAL:APPLE_VNC_COMPATIBILTY"; +} + +VncClientThread::VncClientThread(QObject *parent) + : QThread(parent) + , frameBuffer(0) + , cl(0) + , m_stopped(false) +{ + QMutexLocker locker(&mutex); + + QTimer *outputErrorMessagesCheckTimer = new QTimer(this); + outputErrorMessagesCheckTimer->setInterval(500); + connect(outputErrorMessagesCheckTimer, SIGNAL(timeout()), this, SLOT(checkOutputErrorMessage())); + outputErrorMessagesCheckTimer->start(); +} + +VncClientThread::~VncClientThread() +{ + if(isRunning()) { + stop(); + terminate(); + const bool quitSuccess = wait(1000); + kDebug(5011) << "Attempting to stop in deconstructor, will crash if this fails:" << quitSuccess; + } + + if (cl) { + // Disconnect from vnc server & cleanup allocated resources + rfbClientCleanup(cl); + } + + delete [] frameBuffer; +} + +void VncClientThread::checkOutputErrorMessage() +{ + if (!outputErrorMessageString.isEmpty()) { + kDebug(5011) << outputErrorMessageString; + QString errorMessage = outputErrorMessageString; + outputErrorMessageString.clear(); + // show authentication failure error only after the 3rd unsuccessful try + if ((errorMessage != i18n("VNC authentication failed.")) || m_passwordError) + outputErrorMessage(errorMessage); + } +} + +void VncClientThread::setHost(const QString &host) +{ + QMutexLocker locker(&mutex); + m_host = host; +} + +void VncClientThread::setPort(int port) +{ + QMutexLocker locker(&mutex); + m_port = port; +} + +void VncClientThread::setQuality(RemoteView::Quality quality) +{ + m_quality = quality; + //set color depth dependent on quality + switch(quality) { + case RemoteView::Low: + setColorDepth(bpp8); + break; + case RemoteView::High: + setColorDepth(bpp32); + break; + case RemoteView::Medium: + default: + setColorDepth(bpp16); + } +} + +void VncClientThread::setColorDepth(ColorDepth colorDepth) +{ + m_colorDepth= colorDepth; +} + +RemoteView::Quality VncClientThread::quality() const +{ + return m_quality; +} + +VncClientThread::ColorDepth VncClientThread::colorDepth() const +{ + return m_colorDepth; +} + +void VncClientThread::setImage(const QImage &img) +{ + QMutexLocker locker(&mutex); + m_image = img; +} + +const QImage VncClientThread::image(int x, int y, int w, int h) +{ + QMutexLocker locker(&mutex); + + if (w == 0) // full image requested + return m_image; + else + return m_image.copy(x, y, w, h); +} + +void VncClientThread::emitUpdated(int x, int y, int w, int h) +{ + emit imageUpdated(x, y, w, h); +} + +void VncClientThread::emitGotCut(const QString &text) +{ + emit gotCut(text); +} + +void VncClientThread::stop() +{ + QMutexLocker locker(&mutex); + m_stopped = true; +} + +void VncClientThread::run() +{ + QMutexLocker locker(&mutex); + + while (!m_stopped) { // try to connect as long as the server allows + locker.relock(); + m_passwordError = false; + locker.unlock(); + + rfbClientLog = outputHandler; + rfbClientErr = outputHandler; + //24bit color dept in 32 bits per pixel = default. Will change colordepth and bpp later if needed + cl = rfbGetClient(8, 3, 4); + setClientColorDepth(cl, this->colorDepth()); + cl->MallocFrameBuffer = newclient; + cl->canHandleNewFBSize = true; + cl->GetPassword = passwdHandler; + cl->GotFrameBufferUpdate = updatefb; + cl->GotXCutText = cuttext; + rfbClientSetClientData(cl, 0, this); + + locker.relock(); + cl->serverHost = strdup(m_host.toUtf8().constData()); + + if (m_port < 0 || !m_port) // port is invalid or empty... + m_port = 5900; // fallback: try an often used VNC port + + if (m_port >= 0 && m_port < 100) // the user most likely used the short form (e.g. :1) + m_port += 5900; + cl->serverPort = m_port; + locker.unlock(); + + kDebug(5011) << "--------------------- trying init ---------------------"; + + if (rfbInitClient(cl, 0, 0)) + break; + else + cl = 0; + + locker.relock(); + if (m_passwordError) + continue; + + return; + } + + locker.relock(); + kDebug(5011) << "--------------------- Starting main VNC event loop ---------------------"; + while (!m_stopped) { + locker.unlock(); + const int i = WaitForMessage(cl, 500); + if (i < 0) { + break; + } + if (i) { + if (!HandleRFBServerMessage(cl)) { + break; + } + } + + locker.relock(); + while (!m_eventQueue.isEmpty()) { + ClientEvent* clientEvent = m_eventQueue.dequeue(); + locker.unlock(); + clientEvent->fire(cl); + delete clientEvent; + locker.relock(); + } + } + + m_stopped = true; +} + +ClientEvent::~ClientEvent() +{ +} + +void PointerClientEvent::fire(rfbClient* cl) +{ + SendPointerEvent(cl, m_x, m_y, m_buttonMask); +} + +void KeyClientEvent::fire(rfbClient* cl) +{ + SendKeyEvent(cl, m_key, m_pressed); +} + +void ClientCutEvent::fire(rfbClient* cl) +{ + SendClientCutText(cl, text.toUtf8().data(), text.size()); +} + +void VncClientThread::mouseEvent(int x, int y, int buttonMask) +{ + QMutexLocker lock(&mutex); + if (m_stopped) + return; + + m_eventQueue.enqueue(new PointerClientEvent(x, y, buttonMask)); +} + +void VncClientThread::keyEvent(int key, bool pressed) +{ + QMutexLocker lock(&mutex); + if (m_stopped) + return; + + m_eventQueue.enqueue(new KeyClientEvent(key, pressed)); +} + +void VncClientThread::clientCut(const QString &text) +{ + QMutexLocker lock(&mutex); + if (m_stopped) + return; + + m_eventQueue.enqueue(new ClientCutEvent(text)); +} + +#include "moc_vncclientthread.cpp" diff --git a/linux_usr/gui/palacios/vnc_module/vncclientthread.h b/linux_usr/gui/palacios/vnc_module/vncclientthread.h new file mode 100644 index 0000000..0c7c172 --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/vncclientthread.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2007-2008 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#ifndef VNCCLIENTTHREAD_H +#define VNCCLIENTTHREAD_H + +#ifdef QTONLY + #include + #define kDebug(n) qDebug() + #define kBacktrace() "" + #define i18n tr +#else + #include + #include +#endif + +#include "remoteview.h" + +#include +#include +#include +#include + +extern "C" { +#include +} + +class ClientEvent +{ +public: + virtual ~ClientEvent(); + + virtual void fire(rfbClient*) = 0; +}; + +class KeyClientEvent : public ClientEvent +{ +public: + KeyClientEvent(int key, int pressed) + : m_key(key), m_pressed(pressed) {} + + void fire(rfbClient*); + +private: + int m_key; + int m_pressed; +}; + +class PointerClientEvent : public ClientEvent +{ +public: + PointerClientEvent(int x, int y, int buttonMask) + : m_x(x), m_y(y), m_buttonMask(buttonMask) {} + + void fire(rfbClient*); + +private: + int m_x; + int m_y; + int m_buttonMask; +}; + +class ClientCutEvent : public ClientEvent +{ +public: + ClientCutEvent(const QString &text) + : text(text) {} + + void fire(rfbClient*); + +private: + QString text; +}; + +class VncClientThread: public QThread +{ + Q_OBJECT + +public: + Q_ENUMS(ColorDepth) + + enum ColorDepth { + bpp32, + bpp16, + bpp8 + }; + + explicit VncClientThread(QObject *parent = 0); + ~VncClientThread(); + const QImage image(int x = 0, int y = 0, int w = 0, int h = 0); + void setImage(const QImage &img); + void emitUpdated(int x, int y, int w, int h); + void emitGotCut(const QString &text); + void stop(); + void setHost(const QString &host); + void setPort(int port); + void setQuality(RemoteView::Quality quality); + void setPassword(const QString &password) { + m_password = password; + } + const QString password() const { + return m_password; + } + + RemoteView::Quality quality() const; + ColorDepth colorDepth() const; + uint8_t *frameBuffer; + +signals: + void imageUpdated(int x, int y, int w, int h); + void gotCut(const QString &text); + void passwordRequest(); + void outputErrorMessage(const QString &message); + +public slots: + void mouseEvent(int x, int y, int buttonMask); + void keyEvent(int key, bool pressed); + void clientCut(const QString &text); + +protected: + void run(); + +private: + static void setClientColorDepth(rfbClient *cl, ColorDepth cd); + void setColorDepth(ColorDepth colorDepth); + //these static methods are callback functions for libvncclient + static rfbBool newclient(rfbClient *cl); + static void updatefb(rfbClient *cl, int x, int y, int w, int h); + static void cuttext(rfbClient *cl, const char *text, int textlen); + static char* passwdHandler(rfbClient *cl); + static void outputHandler(const char *format, ...); + + QImage m_image; + rfbClient *cl; + QString m_host; + QString m_password; + int m_port; + QMutex mutex; + RemoteView::Quality m_quality; + ColorDepth m_colorDepth; + QQueue m_eventQueue; + //color table for 8bit indexed colors + static QVector m_colorTable; + + volatile bool m_stopped; + volatile bool m_passwordError; + +private slots: + void checkOutputErrorMessage(); +}; + +#endif diff --git a/linux_usr/gui/palacios/vnc_module/vncview.cpp b/linux_usr/gui/palacios/vnc_module/vncview.cpp new file mode 100644 index 0000000..223359b --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/vncview.cpp @@ -0,0 +1,597 @@ +/**************************************************************************** +** +** Copyright (C) 2007-2012 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#include "vncview.h" + +#ifdef QTONLY + #include + #include + #define KMessageBox QMessageBox + /*#define error(parent, message, caption) \ + critical(parent, caption, message)*/ +#else + #include "settings.h" + #include + #include + #include + #include + #include +#endif + +#include +#include +#include +#include + +// Definition of key modifier mask constants +#define KMOD_Alt_R 0x01 +#define KMOD_Alt_L 0x02 +#define KMOD_Meta_L 0x04 +#define KMOD_Control_L 0x08 +#define KMOD_Shift_L 0x10 + +VncView::VncView(QWidget *parent, const KUrl &url, KConfigGroup configGroup) + : RemoteView(parent), + m_initDone(false), + m_buttonMask(0), + m_repaint(false), + m_quitFlag(false), + m_firstPasswordTry(true), + m_dontSendClipboard(false), + m_horizontalFactor(1.0), + m_verticalFactor(1.0), + m_forceLocalCursor(false) +{ + m_url = url; + m_host = url.host(); + m_port = url.port(); + + connect(&vncThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int)), Qt::BlockingQueuedConnection); + connect(&vncThread, SIGNAL(gotCut(QString)), this, SLOT(setCut(QString)), Qt::BlockingQueuedConnection); + connect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword()), Qt::BlockingQueuedConnection); + connect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString))); + + m_clipboard = QApplication::clipboard(); + connect(m_clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged())); + +#ifndef QTONLY + m_hostPreferences = new VncHostPreferences(configGroup, this); +#else + Q_UNUSED(configGroup); +#endif +} + +VncView::~VncView() +{ + startQuitting(); +} + +bool VncView::eventFilter(QObject *obj, QEvent *event) +{ + if (m_viewOnly) { + if (event->type() == QEvent::KeyPress || + event->type() == QEvent::KeyRelease || + event->type() == QEvent::MouseButtonDblClick || + event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::Wheel || + event->type() == QEvent::MouseMove) + return true; + } + + return RemoteView::eventFilter(obj, event); +} + +QSize VncView::framebufferSize() +{ + return m_frame.size(); +} + +QSize VncView::sizeHint() const +{ + return size(); +} + +QSize VncView::minimumSizeHint() const +{ + return size(); +} + +void VncView::scaleResize(int w, int h) +{ + RemoteView::scaleResize(w, h); + + kDebug(5011) << w << h; + if (m_scale) { + m_verticalFactor = (qreal) h / m_frame.height(); + m_horizontalFactor = (qreal) w / m_frame.width(); + +#ifndef QTONLY + if (Settings::keepAspectRatio()) { + m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor); + } +#else + m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor); +#endif + + const qreal newW = m_frame.width() * m_horizontalFactor; + const qreal newH = m_frame.height() * m_verticalFactor; + setMaximumSize(newW, newH); //This is a hack to force Qt to center the view in the scroll area + resize(newW, newH); + } +} + +void VncView::updateConfiguration() +{ + RemoteView::updateConfiguration(); + + // Update the scaling mode in case KeepAspectRatio changed + scaleResize(parentWidget()->width(), parentWidget()->height()); +} + +void VncView::startQuitting() +{ + kDebug(5011) << "about to quit"; + + setStatus(Disconnecting); + + m_quitFlag = true; + + vncThread.stop(); + + unpressModifiers(); + + // Disconnect all signals so that we don't get any more callbacks from the client thread + bool imageUpdatedDisconnect = disconnect(&vncThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int))); + bool gotCutDisconnect = disconnect(&vncThread, SIGNAL(gotCut(QString)), this, SLOT(setCut(QString))); + bool passwordRequestDisconnect = disconnect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword())); + bool outputErrorMessageDisconnect = disconnect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString))); + + kDebug(5011) << "Signals disconnected: imageUpdated: " << imageUpdatedDisconnect << "gotCut: " << gotCutDisconnect << "passwordRequest: " << passwordRequestDisconnect << "outputErrorMessage: " << outputErrorMessageDisconnect; + + vncThread.quit(); + + const bool quitSuccess = vncThread.wait(1000); + + kDebug(5011) << "Quit VNC thread success:" << quitSuccess; + + setStatus(Disconnected); +} + +bool VncView::isQuitting() +{ + return m_quitFlag; +} + +bool VncView::start() +{ + vncThread.setHost(m_host); + vncThread.setPort(m_port); + RemoteView::Quality quality; +#ifdef QTONLY + quality = (RemoteView::Quality)((QCoreApplication::arguments().count() > 2) ? + QCoreApplication::arguments().at(2).toInt() : 2); +#else + quality = m_hostPreferences->quality(); +#endif + + vncThread.setQuality(quality); + + kDebug(5011) << "Host: " << m_host; + kDebug(5011) << "Port: " << m_port; + kDebug(5011) << "Quality: " << quality; + + // set local cursor on by default because low quality mostly means slow internet connection + if (quality == RemoteView::Low) { + showDotCursor(RemoteView::CursorOn); +#ifndef QTONLY + // KRDC does always just have one main window, so at(0) is safe + KXMLGUIClient *mainWindow = dynamic_cast(KMainWindow::memberList().at(0)); + if (mainWindow) + mainWindow->actionCollection()->action("show_local_cursor")->setChecked(true); +#endif + } + + setStatus(Connecting); + + vncThread.start(); + return true; +} + +bool VncView::supportsScaling() const +{ + return true; +} + +bool VncView::supportsLocalCursor() const +{ + return true; +} + +void VncView::requestPassword() +{ + kDebug(5011) << "request password"; + + setStatus(Authenticating); + + kDebug(5011) << "After setting authentication state"; +/*#ifndef QTONLY + // just try to get the passwort from the wallet the first time, otherwise it will loop (see issue #226283) + if (m_firstPasswordTry && m_hostPreferences->walletSupport()) { + QString walletPassword = readWalletPassword(); + + if (!walletPassword.isNull()) { + vncThread.setPassword(walletPassword); + m_firstPasswordTry = false; + return; + } + } +#endif + + if (m_firstPasswordTry && !m_url.password().isNull()) { + vncThread.setPassword(m_url.password()); + m_firstPasswordTry = false; + return; + }*/ + +#ifdef QTONLY + bool ok = true; + QString password = QInputDialog::getText(this, //krazy:exclude=qclasses + tr("Password required"), + tr("Please enter the password for the remote desktop:"), + QLineEdit::Password, QString(), &ok); + m_firstPasswordTry = false; + if (ok) + vncThread.setPassword(password); + else + startQuitting(); +#else + KPasswordDialog dialog(this); + dialog.setPrompt(m_firstPasswordTry ? i18n("Access to the system requires a password.") + : i18n("Authentication failed. Please try again.")); + if (dialog.exec() == KPasswordDialog::Accepted) { + m_firstPasswordTry = false; + vncThread.setPassword(dialog.password()); + } else { + kDebug(5011) << "password dialog not accepted"; + startQuitting(); + } +#endif +} + +void VncView::outputErrorMessage(const QString &message) +{ + kDebug(5011) << message; + + if (message == "INTERNAL:APPLE_VNC_COMPATIBILTY") { + setCursor(localDotCursor()); + m_forceLocalCursor = true; + return; + } + + startQuitting(); + +#ifndef QTONLY + KMessageBox::error(this, message, i18n("VNC failure")); +#endif + emit errorMessage(i18n("VNC failure"), message); +} + +#ifndef QTONLY +HostPreferences* VncView::hostPreferences() +{ + return m_hostPreferences; +} +#endif + +void VncView::updateImage(int x, int y, int w, int h) +{ +// kDebug(5011) << "got update" << width() << height(); + + m_x = x; + m_y = y; + m_w = w; + m_h = h; + + if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) { + // If the view is scaled, grow the update rectangle to avoid artifacts + m_x-=1; + m_y-=1; + m_w+=2; + m_h+=2; + } + + m_frame = vncThread.image(); + + if (!m_initDone) { + setAttribute(Qt::WA_StaticContents); + setAttribute(Qt::WA_OpaquePaintEvent); + installEventFilter(this); + + setCursor(((m_dotCursorState == CursorOn) || m_forceLocalCursor) ? localDotCursor() : Qt::BlankCursor); + + setMouseTracking(true); // get mouse events even when there is no mousebutton pressed + setFocusPolicy(Qt::WheelFocus); + setStatus(Connected); + emit connected(); + + if (m_scale) { +#ifndef QTONLY + kDebug(5011) << "Setting initial size w:" <width() << " h:" << m_hostPreferences->height(); + emit framebufferSizeChanged(m_hostPreferences->width(), m_hostPreferences->height()); + scaleResize(m_hostPreferences->width(), m_hostPreferences->height()); + kDebug() << "m_frame.size():" << m_frame.size() << "size()" << size(); +#else +//TODO: qtonly alternative +#endif + } + + m_initDone = true; + +#ifndef QTONLY + if (m_hostPreferences->walletSupport()) { + saveWalletPassword(vncThread.password()); + } +#endif + } + + if ((y == 0 && x == 0) && (m_frame.size() != size())) { + kDebug(5011) << "Updating framebuffer size"; + if (m_scale) { + setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + if (parentWidget()) + scaleResize(parentWidget()->width(), parentWidget()->height()); + } else { + kDebug(5011) << "Resizing: " << m_frame.width() << m_frame.height(); + resize(m_frame.width(), m_frame.height()); + setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area + setMinimumSize(m_frame.width(), m_frame.height()); + emit framebufferSizeChanged(m_frame.width(), m_frame.height()); + } + } + + m_repaint = true; + repaint(qRound(m_x * m_horizontalFactor), qRound(m_y * m_verticalFactor), qRound(m_w * m_horizontalFactor), qRound(m_h * m_verticalFactor)); + m_repaint = false; +} + +void VncView::setViewOnly(bool viewOnly) +{ + RemoteView::setViewOnly(viewOnly); + + m_dontSendClipboard = viewOnly; + + if (viewOnly) + setCursor(Qt::ArrowCursor); + else + setCursor(m_dotCursorState == CursorOn ? localDotCursor() : Qt::BlankCursor); +} + +void VncView::showDotCursor(DotCursorState state) +{ + RemoteView::showDotCursor(state); + + setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor); +} + +void VncView::enableScaling(bool scale) +{ + RemoteView::enableScaling(scale); + + if (scale) { + setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + setMinimumSize(1, 1); + if (parentWidget()) + scaleResize(parentWidget()->width(), parentWidget()->height()); + } else { + m_verticalFactor = 1.0; + m_horizontalFactor = 1.0; + + setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area + setMinimumSize(m_frame.width(), m_frame.height()); + resize(m_frame.width(), m_frame.height()); + } +} + +void VncView::setCut(const QString &text) +{ + m_dontSendClipboard = true; + m_clipboard->setText(text, QClipboard::Clipboard); + m_dontSendClipboard = false; +} + +void VncView::paintEvent(QPaintEvent *event) +{ +// kDebug(5011) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h; + if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) { + kDebug(5011) << "no valid image to paint"; + RemoteView::paintEvent(event); + return; + } + + event->accept(); + + QPainter painter(this); + + if (m_repaint) { +// kDebug(5011) << "normal repaint"; + painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor), + qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), + m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), + qRound(m_h*m_verticalFactor), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { +// kDebug(5011) << "resize repaint"; + QRect rect = event->rect(); + if (rect.width() != width() || rect.height() != height()) { +// kDebug(5011) << "Partial repaint"; + const int sx = rect.x()/m_horizontalFactor; + const int sy = rect.y()/m_verticalFactor; + const int sw = rect.width()/m_horizontalFactor; + const int sh = rect.height()/m_verticalFactor; + painter.drawImage(rect, + m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { +// kDebug(5011) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height(); + painter.drawImage(QRect(0, 0, width(), height()), + m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor, + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + RemoteView::paintEvent(event); +} + +void VncView::resizeEvent(QResizeEvent *event) +{ + RemoteView::resizeEvent(event); + update(); +} + +bool VncView::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: +// kDebug(5011) << "keyEvent"; + keyEventHandler(static_cast(event)); + return true; + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: +// kDebug(5011) << "mouseEvent"; + mouseEventHandler(static_cast(event)); + return true; + break; + case QEvent::Wheel: +// kDebug(5011) << "wheelEvent"; + wheelEventHandler(static_cast(event)); + return true; + break; + default: + return RemoteView::event(event); + } +} + +void VncView::mouseEventHandler(QMouseEvent *e) +{ + if (e->type() != QEvent::MouseMove) { + if ((e->type() == QEvent::MouseButtonPress) || + (e->type() == QEvent::MouseButtonDblClick)) { + if (e->button() & Qt::LeftButton) + m_buttonMask |= 0x01; + if (e->button() & Qt::MidButton) + m_buttonMask |= 0x02; + if (e->button() & Qt::RightButton) + m_buttonMask |= 0x04; + } else if (e->type() == QEvent::MouseButtonRelease) { + if (e->button() & Qt::LeftButton) + m_buttonMask &= 0xfe; + if (e->button() & Qt::MidButton) + m_buttonMask &= 0xfd; + if (e->button() & Qt::RightButton) + m_buttonMask &= 0xfb; + } + } + + vncThread.mouseEvent(qRound(e->x() / m_horizontalFactor), qRound(e->y() / m_verticalFactor), m_buttonMask); +} + +void VncView::wheelEventHandler(QWheelEvent *event) +{ + int eb = 0; + if (event->delta() < 0) + eb |= 0x10; + else + eb |= 0x8; + + const int x = qRound(event->x() / m_horizontalFactor); + const int y = qRound(event->y() / m_verticalFactor); + + vncThread.mouseEvent(x, y, eb | m_buttonMask); + vncThread.mouseEvent(x, y, m_buttonMask); +} + +void VncView::keyEventHandler(QKeyEvent *e) +{ + // strip away autorepeating KeyRelease; see bug #206598 + if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease)) + return; + +// parts of this code are based on http://italc.sourcearchive.com/documentation/1.0.9.1/vncview_8cpp-source.html + rfbKeySym k = e->nativeVirtualKey(); + + // we do not handle Key_Backtab separately as the Shift-modifier + // is already enabled + if (e->key() == Qt::Key_Backtab) { + k = XK_Tab; + } + + const bool pressed = (e->type() == QEvent::KeyPress); + + // handle modifiers + if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L) { + if (pressed) { + m_mods[k] = true; + } else if (m_mods.contains(k)) { + m_mods.remove(k); + } else { + unpressModifiers(); + } + } + + if (k) { + vncThread.keyEvent(k, pressed); + } +} + +void VncView::unpressModifiers() +{ + const QList keys = m_mods.keys(); + QList::const_iterator it = keys.constBegin(); + while (it != keys.end()) { + vncThread.keyEvent(*it, false); + it++; + } + m_mods.clear(); +} + +void VncView::clipboardDataChanged() +{ + kDebug(5011); + + if (m_status != Connected) + return; + + if (m_clipboard->ownsClipboard() || m_dontSendClipboard) + return; + + const QString text = m_clipboard->text(QClipboard::Clipboard); + + vncThread.clientCut(text); +} + +#include "moc_vncview.cpp" diff --git a/linux_usr/gui/palacios/vnc_module/vncview.h b/linux_usr/gui/palacios/vnc_module/vncview.h new file mode 100644 index 0000000..241d5dc --- /dev/null +++ b/linux_usr/gui/palacios/vnc_module/vncview.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2007-2012 Urs Wolfer +** +** This file is part of KDE. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; see the file COPYING. If not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +** Boston, MA 02110-1301, USA. +** +****************************************************************************/ + +#ifndef VNCVIEW_H +#define VNCVIEW_H + +#include "remoteview.h" +#include "vncclientthread.h" + +#ifdef QTONLY + class KConfigGroup{}; +#else + #include "vnchostpreferences.h" +#endif + +#include + +extern "C" { +#include +} + +class VncView: public RemoteView +{ + Q_OBJECT + +public: + explicit VncView(QWidget *parent = 0, const KUrl &url = KUrl(), KConfigGroup configGroup = KConfigGroup()); + ~VncView(); + + QSize framebufferSize(); + QSize sizeHint() const; + QSize minimumSizeHint() const; + void startQuitting(); + bool isQuitting(); + bool start(); + bool supportsScaling() const; + bool supportsLocalCursor() const; + +#ifndef QTONLY + HostPreferences* hostPreferences(); +#endif + + void setViewOnly(bool viewOnly); + void showDotCursor(DotCursorState state); + void enableScaling(bool scale); + + virtual void updateConfiguration(); + +public slots: + void scaleResize(int w, int h); + +protected: + void paintEvent(QPaintEvent *event); + bool event(QEvent *event); + void resizeEvent(QResizeEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + +private: + VncClientThread vncThread; + QClipboard *m_clipboard; + bool m_initDone; + int m_buttonMask; + QMap m_mods; + int m_x, m_y, m_w, m_h; + bool m_repaint; + bool m_quitFlag; + bool m_firstPasswordTry; + bool m_dontSendClipboard; + qreal m_horizontalFactor; + qreal m_verticalFactor; +#ifndef QTONLY + VncHostPreferences *m_hostPreferences; +#endif + QImage m_frame; + bool m_forceLocalCursor; + + void keyEventHandler(QKeyEvent *e); + void unpressModifiers(); + void wheelEventHandler(QWheelEvent *event); + void mouseEventHandler(QMouseEvent *event); + +private slots: + void updateImage(int x, int y, int w, int h); + void setCut(const QString &text); + void requestPassword(); + void outputErrorMessage(const QString &message); + void clipboardDataChanged(); +}; + +#endif diff --git a/linux_usr/gui/palacios_resources.qrc b/linux_usr/gui/palacios_resources.qrc new file mode 100644 index 0000000..a9dfad1 --- /dev/null +++ b/linux_usr/gui/palacios_resources.qrc @@ -0,0 +1,17 @@ + + + images/exit.png + images/icon_no_preview.png + images/new_vm.png + images/palacios.png + images/vm_pause.png + images/vm_refresh.png + images/vm_start.png + images/vm_stop.png + images/start_vm.png + images/stop_vm.png + images/activate_vm.png + images/pause_vm.png + images/reload_vm.png + + diff --git a/linux_usr/gui/report.pdf b/linux_usr/gui/report.pdf new file mode 100644 index 0000000..3e10189 Binary files /dev/null and b/linux_usr/gui/report.pdf differ