#!/usr/bin/perl
# ^^^ You may need to change this to the location of your perl interpreter
# RemoteBox v1.5 (c) 2010-2012 Ian Chapman. Licenced under the terms of the GPL
use strict;
use warnings;
use FindBin qw($Bin);
use POSIX qw(ceil);
use File::Basename;
use File::Spec;
use MIME::Base64;
use lib "$Bin/share/remotebox";
use vboxService qw($endpoint $fault :all);
require 'vboxserializers.pl';
require 'rbox_lists.pl';
require 'rbox_prefs.pl';
require 'rbox_gui_init.pl';
require 'rbox_newguest.pl';
require 'rbox_gui_edit.pl';
require 'rbox_gui_vmm.pl';
require 'rbox_err.pl';
$|=1;
our (%gui, %hostspec, %osfamily, %osversion, %prefs);

# Medium Variant
our %EMedVar = (Standard            => 0,
                VmdkSplit2G         => 1,
                VmdkStreamOptimized => 4,
                VmdkESX             => 8,
                Fixed               => 65536,
                Diff                => 131072);

# Useful for determining truth (as far as GTK is concerned) based on strings returned
# from the VBox API. Eg for setting a checkbox
our %bool = (Null  => 0,
             null  => 0,
             false => 0,
             False => 0,
             true  => 1,
             True  => 1);

$endpoint = 'http://localhost:18083';
$fault = sub{}; # Do nothing with faults until connected
&rbprefs_get();
$gui{expanderMessages}->set_expanded($prefs{MSGLOGEXPAND});
&restore_window_pos('windowMain');
&addrow_log("Welcome to $gui{appname} $gui{appver}.");
Gtk2->main;


sub quit_remotebox() {
    &save_window_pos('windowMain');
    &virtualbox_logoff();
    # These should be reset on exit to help garbage collection
    $gui{menuitemFloppy}->set_submenu(undef);
    $gui{menuitemDVD}->set_submenu(undef);
    $gui{menuitemUSB}->set_submenu(undef);
    $gui{menuitemFloppy}->set_submenu($gui{menutmp1});
    $gui{menuitemDVD}->set_submenu($gui{menutmp2});
    $gui{menuitemUSB}->set_submenu($gui{menutmp3});
    Gtk2->main_quit; # Supposedly deprecated but segfaults on newer systems if just exit is used
}

sub virtualbox_logon() {
    my ($url, $user, $password) = @_;

    &virtualbox_logoff(); # Ensure we disconnect from an existing connection
    $endpoint = $url;
    eval { $gui{websn} = IWebsessionManager_logon($user, $password); };
    if ($gui{websn}) {
        $fault = \&vboxerror; # Install the fault capture
        $gui{heartbeat} = Glib::Timeout->add(58000, \&heartbeat) if ($prefs{HEARTBEAT}); # Install heartbeat if requested
        return 1;
    }
    else {
        &show_err_msg('connect', $gui{messagedialogError}, " ($url)");
        return 0;
    }
}

# Logoff and perform various necessary cleanups
sub virtualbox_logoff() {
    my ($closewin) = @_;
    $fault = sub{}; # Disable fault capture. Protection against infinite loops
    Glib::Source->remove($gui{heartbeat}) if ($gui{heartbeat}); # Remove any heartbeat timers

    # Force close any open windows - usually because we've hit a fault. This will cause them to
    # execute their cancel operations
    if ($closewin) {
        foreach my $win (Gtk2::Window->list_toplevels()) {
            next if ($win eq $gui{windowMain});
            $win->hide;
        }
    }

    if ($gui{websn}) {
        eval{ IWebsessionManager_logoff($gui{websn}); };
        $gui{websn} = undef;
    }

    # Return GUI to a disconnected state
    &sens_unselected();
    &sens_connect(0);
    &clr_list_guest();
}

sub show_dialog_connect() {
    $gui{liststoreConnectURL}->clear();
    $gui{liststoreConnectUser}->clear();
    $gui{checkbuttonConnectSave}->set_active($prefs{SAVEURLUSER});
    $gui{checkbuttonConnectSSL}->set_active($prefs{SSLVERIFY});

    foreach (keys (%{$prefs{URL}})) {
        my $iter = $gui{liststoreConnectURL}->append();
        $gui{liststoreConnectURL}->set($iter, 0, $_);
    }

    foreach (keys (%{$prefs{USER}})) {
        my $iter = $gui{liststoreConnectUser}->append();
        $gui{liststoreConnectUser}->set($iter, 0, $_);
    }

    my $response = $gui{dialogConnect}->run;
    $gui{dialogConnect}->hide;
    $gui{dialogConnect}->get_display->flush;

    if ($response eq 'ok') {
        my $url = $gui{comboboxentryConnectURL}->get_active_text();
        my $user = $gui{comboboxentryConnectUser}->get_active_text();
        $url = $endpoint if (!$url);
        $url = "http://$url" if ($url !~ m/^.+:\/\//);
        $url = "$url:18083" if ($url !~ m/:\d+$/);

        if ($gui{checkbuttonConnectSave}->get_active()) {
            $prefs{URL}{$url} = 'URL' if ($url);
            $prefs{USER}{$user} = 'USER' if ($user);
            &rbprefs_save();
        }

        # If we got a successful logon
        if (&virtualbox_logon($url, $user, $gui{entryConnectPassword}->get_text())) {
            my $ver = IVirtualBox_getVersion($gui{websn});

            if (!$ver) {
                &show_err_msg('auth', $gui{messagedialogError}, " ($url)");
                &virtualbox_logoff();
            }
            else {
                &addrow_log("Logged onto $endpoint running VirtualBox $ver.");
                &show_err_msg('vboxver', $gui{messagedialogWarning}, "\nDetected VirtualBox Version: $ver") if ($ver !~ m/^4.2/);
                &populate_hostspec();
                &populate_ostypes();
                &fill_list_guest();
                &show_err_msg('noextensions', $gui{messagedialogWarning}) if ($hostspec{vrdeextpack} !~ m/Oracle VM VirtualBox Extension Pack/i);
                &sens_connect(1);
            }
        }
    }
}

sub show_dialog_about() {
    $gui{aboutdialog}->run;
    $gui{aboutdialog}->hide;
}

sub show_dialog_customvideo() {
    $gui{spinbuttonCustomVideoW}->set_value(640);
    $gui{spinbuttonCustomVideoH}->set_value(480);
    $gui{dialogCustomVideo}->run;
    $gui{dialogCustomVideo}->hide;

    my %res = (w => int($gui{spinbuttonCustomVideoW}->get_value()),
               h => int($gui{spinbuttonCustomVideoH}->get_value()),
               d => &getsel_combo($gui{comboboxCustomVideoD}, 1));

    return %res;
}

# Shows a dialog with basic information about the VirtualBox server
sub show_dialog_serverinfo() {
    &fill_list_serverinfo();
    $gui{dialogInfo}->set_title('Server Information');
    $gui{dialogInfo}->set_transient_for($gui{windowMain});
    $gui{dialogInfo}->run;
    $gui{dialogInfo}->hide;
}

# Displays a dialog with the contents of the logs for a specific guest
sub show_dialog_log() {
    my ($widget) = @_;
    my $gref = &getsel_list_guest();

    if ($widget eq $gui{menuitemLog0}) {
        &fill_list_log($$gref{IMachine}, 0);
        $gui{dialogInfo}->set_title("$$gref{Name} : Log 0");
    }
    elsif ($widget eq $gui{menuitemLog1}) {
        &fill_list_log($$gref{IMachine}, 1);
        $gui{dialogInfo}->set_title("$$gref{Name} : Log 1");
    }
    elsif ($widget eq $gui{menuitemLog2}) {
        &fill_list_log($$gref{IMachine}, 2);
        $gui{dialogInfo}->set_title("$$gref{Name} : Log 2");
    }
    elsif ($widget eq $gui{menuitemLog3}) {
        &fill_list_log($$gref{IMachine}, 3);
        $gui{dialogInfo}->set_title("$$gref{Name} : Log 3");
    }

    $gui{dialogInfo}->set_transient_for($gui{windowMain});
    $gui{dialogInfo}->run;
    $gui{dialogInfo}->hide;
}


sub show_dialog_vminfo() {
    &fill_list_vminfo();
    my $gref = &getsel_list_guest();
    $gui{dialogInfo}->set_title("$$gref{Name} : More Details");
    $gui{dialogInfo}->set_transient_for($gui{windowMain});
    $gui{dialogInfo}->run;
    $gui{dialogInfo}->hide;
}

sub fill_summarydetails() {
    my $gref = &getsel_list_guest();
    &addrow_log("Retrieving details summary for $$gref{Name}...");
    $gui{labelDetailsName}->set_markup('<b>Name:</b> ' . $$gref{Name});
    $gui{labelDetailsOSType}->set_markup('<b>OS Type:</b> ' . $$gref{Os});
    $gui{labelDetailsBaseMem}->set_markup('<b>Base Memory:</b> ' . IMachine_getMemorySize($$gref{IMachine}) . ' MB');
    $gui{labelDetailsVidMem}->set_markup('<b>Video Memory:</b> ' . IMachine_getVRAMSize($$gref{IMachine}) . ' MB');

    if (IMachine_getHWVirtExProperty($$gref{IMachine}, 'Enabled') eq 'true') { $gui{labelDetailsVTX}->set_markup('<b>VT-x/AMD-V:</b> Enabled'); }
    else { $gui{labelDetailsVTX}->set_markup('<b>VT-x/AMD-V:</b> Disabled'); }

    my $desc = IMachine_getDescription($$gref{IMachine});
    $desc = '<No Description>' if (!$desc);
    $gui{labelDetailsDescription}->set_text($desc);
    &addrow_log("Details summary for $$gref{Name} retrieved.");
}

sub clr_summarydetails() {
    $gui{labelDetailsName}->set_text('');
    $gui{labelDetailsOSType}->set_text('');
    $gui{labelDetailsBaseMem}->set_text('');
    $gui{labelDetailsVidMem}->set_text('');
    $gui{labelDetailsVTX}->set_text('');
    $gui{labelDetailsDescription}->set_text('');
}


sub show_dialog_snapshotdetails() {
    my $snapref = &getsel_list_snapshots();
    $gui{entrySnapshotName}->set_text(ISnapshot_getName($$snapref{ISnapshot}));
    $gui{textbufferSnapshotDescription}->set_text(ISnapshot_getDescription($$snapref{ISnapshot}));
    my $response = $gui{dialogSnapshot}->run;
    $gui{dialogSnapshot}->hide;

    if ($response eq 'ok') {
        my $iter_s = $gui{textbufferSnapshotDescription}->get_start_iter();
        my $iter_e = $gui{textbufferSnapshotDescription}->get_end_iter();
        ISnapshot_setDescription($$snapref{ISnapshot}, $gui{textbufferSnapshotDescription}->get_text($iter_s, $iter_e, 0));
        ISnapshot_setName($$snapref{ISnapshot}, $gui{entrySnapshotName}->get_text());
        &fill_list_snapshots();
    }
}

sub show_dialog_snapshot() {
    my ($widget) = @_; # Need to reuse dialog for snapshot details
    $gui{entrySnapshotName}->set_text('Snapshot');
    $gui{textbufferSnapshotDescription}->set_text('');
    my $response = $gui{dialogSnapshot}->run();
    $gui{dialogSnapshot}->hide();

    if ($response eq 'ok') {
        my $iter_s = $gui{textbufferSnapshotDescription}->get_start_iter();
        my $iter_e = $gui{textbufferSnapshotDescription}->get_end_iter();
        &take_snapshot($gui{entrySnapshotName}->get_text(), $gui{textbufferSnapshotDescription}->get_text($iter_s, $iter_e, 0));
    }
}

# Performs a reset of the guest
sub reset_guest() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        IConsole_reset(ISession_getConsole($$sref{ISession}));
        &addrow_log("Sent reset signal to $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Stops the guest
sub stop_guest() {
    if ($prefs{STOPTYPE} eq 'ACPI') { &stop_guest_acpi(); }
    elsif ($prefs{STOPTYPE} eq 'STATE') { &stop_guest_savestate(); }
    else { &stop_guest_poweroff(); }
}

# Stops a guest by issuing a hard poweroff
sub stop_guest_poweroff() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IConsole = ISession_getConsole($$sref{ISession});

        if (my $IProgress = IConsole_powerDown($IConsole)) {
            &show_progress_window2($IProgress, "Powering off $$gref{Name}...", $gui{windowMain}, $$gref{IMachine}, 'PoweredOff');
            &fill_list_guest();
            &addrow_log("Sent power off signal to $$gref{Name}.");
        }
        else { &addrow_log("Warning: Could not send power off signal to $$gref{Name}."); }
    }

    # At this point the guest may have save its state and the session lock automatically released
    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub stop_guest_acpi() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        IConsole_powerButton(ISession_getConsole($$sref{ISession}));
        &fill_list_guest();
        &addrow_log("Sent ACPI shutdown to $$gref{Name} which may be prompting you to shutdown.");
    }

    # At this point the guest may have powered off and the session lock automatically released.
    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Stops a guest and save it's execution state
sub stop_guest_savestate() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IConsole = ISession_getConsole($$sref{ISession});

        if (my $IProgress = IConsole_saveState($IConsole)) {
            &show_progress_window2($IProgress, "Saving the execution state of $$gref{Name}...", $gui{windowMain}, $$gref{IMachine}, 'Saved');
            &fill_list_guest();
            &addrow_log("Saved the execution state of $$gref{Name}.");
        }
        else { &addrow_log("Warning: Could not save the execution state of $$gref{Name}."); }
    }

    # At this point the guest may have save its state and the session lock automatically released
    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Pauses the execution of the guest
sub pause_guest() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        IConsole_pause(ISession_getConsole($$sref{ISession}));
        &fill_list_guest();
        &addrow_log("Paused the execution of $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub resume_guest() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        IConsole_resume(ISession_getConsole($$sref{ISession}));
        &fill_list_guest();
        &addrow_log("Resumed the execution state of $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub start_guest() {
    my $gref = &getsel_list_guest();
    my $ISession = IWebsessionManager_getSessionObject($gui{websn});
    my $IProgress = IMachine_launchVMProcess($$gref{IMachine}, $ISession, 'headless', "");
    my $started = 0;

    if ($IProgress) {
        my $resultcode = &show_progress_window($IProgress, "Starting $$gref{Name}...", 'cancel', $gui{windowMain});

        if ( $resultcode != 0) {
           my $IVirtualBoxErrorInfo = IProgress_getErrorInfo($IProgress);
           &show_err_msg('startguest', $gui{messagedialogError},
                         "Guest: $$gref{Name}\nCode: $resultcode\nError:\n" . IVirtualBoxErrorInfo_getText($IVirtualBoxErrorInfo));
        }
        else {
            $started = 1; # Most likely the guest started
            &addrow_log("Start signal sent to $$gref{Name}.");
        }

    }
    else { &show_err_msg('sessionopen', $gui{messagedialogError}, " ($$gref{Name})"); }

    ISession_unlockMachine($ISession) if (ISession_getState($ISession) ne 'Unlocked');
    if ($prefs{RDPAUTOOPEN} and $started) { &open_remote_display(); }
    &fill_list_guest();
}

# Removes a guest, optionally deleting associated files
sub remove_guest() {
    my $gref = &getsel_list_guest();
    my $response = $gui{dialogRemoveGuest}->run;
    $gui{dialogRemoveGuest}->hide;

    if ($response eq '1') { # Remove Only
        IMachine_unregister($$gref{IMachine}, 'DetachAllReturnNone');
        unlink("$gui{THUMBDIR}/$$gref{Uuid}.png") if (-e "$gui{THUMBDIR}/$$gref{Uuid}.png"); # Remove screenshot icon
        &addrow_log("Removed $$gref{Name}.");
        &fill_list_guest();
    }
    elsif ($response eq '2') { # Delete all
        my @IMedium = IMachine_unregister($$gref{IMachine}, 'DetachAllReturnHardDisksOnly');

        foreach my $medium (@IMedium) {
            my $IProgress = IMachine_delete($$gref{IMachine}, $medium);
            &show_progress_window($IProgress, "Deleting a disk image from $$gref{Name}...", 0, $gui{windowMain});
        }

        unlink("$gui{THUMBDIR}/$$gref{Uuid}.png") if (-e "$gui{THUMBDIR}/$$gref{Uuid}.png"); # Remove screenshot icon
        &addrow_log("Removed and deleted $$gref{Name}.");
        &fill_list_guest();
    }
}

sub recurse_snapshot() {
    my ($ISnapshot, $iter, $ISnapshot_current) = @_;
    my $citer = $gui{treestoreSnapshots}->append($iter);
    my $snapname = ISnapshot_getName($ISnapshot);
    my $date = scalar(localtime((ISnapshot_getTimeStamp($ISnapshot))/1000)); # VBox returns msecs so / 1000
    if ($ISnapshot_current and $ISnapshot eq $ISnapshot_current) { $snapname = "$snapname (Current State)"; }
    $gui{treestoreSnapshots}->set($citer, 0, $snapname, 1, $date, 2, $ISnapshot);
    my @snapshots = ISnapshot_getChildren($ISnapshot);
    if (@snapshots > 0) { &recurse_snapshot($_, $citer, $ISnapshot_current) foreach (@snapshots); }
}

# Restores a guest to a snapshot state
sub restore_snapshot() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Type} eq 'WriteLock') {
        my $snapref = &getsel_list_snapshots();
        my $IProgress = IConsole_restoreSnapshot(ISession_getConsole($$sref{ISession}), $$snapref{ISnapshot});
        &show_progress_window($IProgress, "Restoring $$gref{Name} to $$snapref{Name}...", 0, $gui{windowMain});
        &fill_list_snapshots();
        &addrow_log("Snapshot of $$gref{Name} restored.");
    }
    else { &show_err_msg('restorefail', $gui{messagedialogError}, " ($$gref{Name})"); }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Deletes a guest snapshot
sub delete_snapshot() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} ne 'None') {
        my $snapref = &getsel_list_snapshots();

        if (ISnapshot_getChildrenCount($$snapref{ISnapshot}) > 1) { &show_err_msg('snapdelchild', $gui{messagedialogError}, " ($$gref{Name})"); }
        else {
            my $snapuuid = ISnapshot_getId($$snapref{ISnapshot});
            my $IProgress = IConsole_deleteSnapshot(ISession_getConsole($$sref{ISession}), $snapuuid);
            &show_progress_window($IProgress, "Deleting snapshot of $$gref{Name}...", 0, $gui{windowMain}) if ($IProgress);
            &fill_list_snapshots();
            &addrow_log("Snapshot of $$gref{Name} deleted.");
        }
    }
    else { &show_err_msg('snapdelete', $gui{messagedialogError}, " ($$gref{Name})"); }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Takes a snapshot of a guest
sub take_snapshot() {
    my ($name, $description) = @_;
    $name = 'Snapshot' if (!$name);
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} ne 'None') {
        my $IProgress = IConsole_takeSnapshot(ISession_getConsole($$sref{ISession}), $name, $description);
        &show_progress_window($IProgress, "Taking snapshot of $$gref{Name}...", 0, $gui{windowMain}) if ($IProgress);
        &fill_list_snapshots();
        &addrow_log("Created a new snapshot of $$gref{Name}.");
    }
    else { &show_err_msg('snapshotfail', $gui{messagedialogError}, " ($$gref{Name})"); }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Attempts to open the remote display by calling the RDP client
sub open_remote_display() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IConsole = ISession_getConsole($$sref{ISession});
        my $IVRDEServerInfo = IConsole_getVRDEServerInfo($IConsole);

        for (1..5) { # Wait up to 5 seconds for the VRDE server to start
            last if ($$IVRDEServerInfo{port} != -1);
            sleep 1;
            $IVRDEServerInfo = IConsole_getVRDEServerInfo($IConsole);
        }

        if ($$IVRDEServerInfo{port} > 0) {
            my $rdpcmd = $prefs{RDPCLIENT};
            my ($user, $pass) = ($gui{comboboxentryConnectUser}->get_active_text(), $gui{entryConnectPassword}->get_text());
            my $dst = $endpoint;
            $dst =~ s/^.*:\/\///;
            $dst =~ s/:\d+$//;
            $rdpcmd =~ s/%h/$dst/g;
            $rdpcmd =~ s/%p/$$IVRDEServerInfo{port}/g;
            $rdpcmd =~ s/%n/$$gref{Name}/g;
            $rdpcmd =~ s/%o/$$gref{Os}/g;
            $rdpcmd =~ s/%U/$user/g;
            $rdpcmd =~ s/%P/$pass/g;
            system("$rdpcmd &");
            &addrow_log("Sent request to open remote display for $$gref{Name} at address $dst:$$IVRDEServerInfo{port}");
        }
        else { &show_err_msg('remotedisplay', $gui{messagedialogError}, " ($$gref{Name})"); }
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub populate_hostspec() {
    my $IHost = IVirtualBox_getHost($gui{websn});
    my $ISystemProperties = IVirtualBox_getSystemProperties($gui{websn});
    %hostspec = (vbver        => IVirtualBox_getVersion($gui{websn}),
                 buildrev     => IVirtualBox_getRevision($gui{websn}),
                 pkgtype      => IVirtualBox_getPackageType($gui{websn}),
                 settingsfile => IVirtualBox_getSettingsFilePath($gui{websn}),
                 os           => IHost_getOperatingSystem($IHost),
                 osver        => IHost_getOSVersion($IHost),
                 maxhostcpuon => IHost_getProcessorOnlineCount($IHost),
                 cpudesc      => IHost_getProcessorDescription($IHost),
                 cpuspeed     => IHost_getProcessorSpeed($IHost),
                 memsize      => IHost_getMemorySize($IHost),
                 machinedir   => ISystemProperties_getDefaultMachineFolder($ISystemProperties),
                 maxhdsize    => ISystemProperties_getInfoVDSize($ISystemProperties),
                 maxnet       => ISystemProperties_getMaxNetworkAdapters($ISystemProperties, 'PIIX3'),
                 maxser       => ISystemProperties_getSerialPortCount($ISystemProperties),
                 minguestcpu  => ISystemProperties_getMinGuestCPUCount($ISystemProperties),
                 maxguestcpu  => ISystemProperties_getMaxGuestCPUCount($ISystemProperties),
                 minguestram  => ISystemProperties_getMinGuestRAM($ISystemProperties),
                 maxguestram  => ISystemProperties_getMaxGuestRAM($ISystemProperties),
                 minguestvram => ISystemProperties_getMinGuestVRAM($ISystemProperties),
                 maxguestvram => ISystemProperties_getMaxGuestVRAM($ISystemProperties),
                 maxbootpos   => ISystemProperties_getMaxBootPosition($ISystemProperties),
                 maxmonitors  => ISystemProperties_getMaxGuestMonitors($ISystemProperties),
                 vrdeextpack  => ISystemProperties_getDefaultVRDEExtPack($ISystemProperties));

    # Obtain any physical DVD drives
    my @dvd = IHost_getDVDDrives($IHost);
    $hostspec{dvd} = \@dvd;

    # Ontain any physical floppy drivers
    my @floppy = IHost_getFloppyDrives($IHost);
    $hostspec{floppy} = \@floppy;
}

sub populate_ostypes() {
    my @IGuestOSType = IVirtualBox_getGuestOSTypes($gui{websn});
    %osfamily=();
    %osversion=();

    foreach my $type (@IGuestOSType) {
        if (!defined($osfamily{$$type{familyId}})) {
            $osfamily{$$type{familyId}} = {};
            $osfamily{$$type{familyId}}{verids} = ();
            $osfamily{$$type{familyId}}{icon} = Gtk2::Gdk::Pixbuf->new_from_file("$Bin/share/remotebox/icons/os/$$type{familyId}.png");
        }

        $osfamily{$$type{familyId}}{description} = $$type{familyDescription};
        push @{ $osfamily{$$type{familyId}}{verids} }, $$type{id};
        $osversion{$$type{id}} = {} if (!defined($osversion{$$type{id}}));
        $osversion{$$type{id}}{description} = $$type{description};
        $osversion{$$type{id}}{adapterType} = $$type{adapterType};
        $osversion{$$type{id}}{recommendedHDD} = $$type{recommendedHDD};
        $osversion{$$type{id}}{recommendedFloppy} = $$type{recommendedFloppy};
        $osversion{$$type{id}}{is64Bit} = $$type{is64Bit};
        $osversion{$$type{id}}{recommendedVirtEx} = $$type{recommendedVirtEx};
        $osversion{$$type{id}}{recommendedIOAPIC} = $$type{recommendedIOAPIC};
        $osversion{$$type{id}}{recommendedVRAM} = $$type{recommendedVRAM};
        $osversion{$$type{id}}{recommendedRAM} = $$type{recommendedRAM};
        $osversion{$$type{id}}{recommendedHPET} = $$type{recommendedHPET};
        $osversion{$$type{id}}{recommendedUSB} = $$type{recommendedUSB};
        $osversion{$$type{id}}{recommendedUSBHID} = $$type{recommendedUSBHID};
        $osversion{$$type{id}}{recommendedVirtEx} = $$type{recommendedVirtEx};
        $osversion{$$type{id}}{recommendedPAE} = $$type{recommendedPAE};
        $osversion{$$type{id}}{recommendedUSBTablet} = $$type{recommendedUSBTablet};
        $osversion{$$type{id}}{recommendedHDStorageBus} = $$type{recommendedHDStorageBus};
        $osversion{$$type{id}}{recommendedChipset} = $$type{recommendedChipset};
        $osversion{$$type{id}}{recommendedFirmware} = $$type{recommendedFirmware};
        $osversion{$$type{id}}{recommendedDVDStorageBus} = $$type{recommendedDVDStorageBus};
        $osversion{$$type{id}}{recommendedHDStorageController} = $$type{recommendedHDStorageController};
        $osversion{$$type{id}}{recommendedDVDStorageController} = $$type{recommendedDVDStorageController};
        $osversion{$$type{id}}{recommendedRTCUseUTC} = $$type{recommendedRTCUseUTC};
        $osversion{$$type{id}}{recommended2DVideoAcceleration} = $$type{recommended2DVideoAcceleration};
        $osversion{$$type{id}}{recommendedAudioController} = $$type{recommendedAudioController};
        $osversion{$$type{id}}{familyId} = $$type{familyId};
        if (-e "$Bin/share/remotebox/icons/os/$$type{id}.png") { $osversion{$$type{id}}{icon} = Gtk2::Gdk::Pixbuf->new_from_file("$Bin/share/remotebox/icons/os/$$type{id}.png"); }
        else { $osversion{$$type{id}}{icon} = $gui{img}{OtherOS}; }
    }
}

# Displays a popup machine menu on the guest list when the right mouse button is pressed
sub show_rmb_menu() {
    my ($widget, $event) = @_;

    # Check if it's the RMB otherwise do nothing
    if ($event->button == 3) {
        # This code is needed because if the user just presses the RMB, then GTK has not updated the
        # location of the cursor until AFTER this routine is complete meaning will be referencing the
        # wrong VM. We need to force a cursor update first.
        my $path = $gui{treeviewGuest}->get_path_at_pos(int($event->x), int($event->y));
        if ($path) {
            $gui{treeviewGuest}->grab_focus();
            $gui{treeviewGuest}->set_cursor($path);
        }

        $gui{menuMachine}->popup(undef, undef, undef, undef, 0, $event->time);
        return 1;
    }

    return 0;
}

# Called when parent DVD menu is highlighted. Cheaper than calling each time main menu is opened
sub fill_menu_dvd() {
    my $dvdmenu = Gtk2::Menu->new();
    $gui{menuitemDVD}->set_submenu(undef); # Help garbage collection
    $gui{menuitemDVD}->set_submenu($dvdmenu); # Hijack the temporary submenu (restored on exit)
    my $IMediumRef = &get_all_media('DVD');
    my $item = Gtk2::MenuItem->new_with_label('<Empty Drive>');
    $dvdmenu->append($item);
    $item->show();
    $item->signal_connect(activate => \&mount_dvd_online, '');
    my $sep = Gtk2::SeparatorMenuItem->new();
    $dvdmenu->append($sep);
    $sep->show();

    if ($hostspec{dvd}) {
        foreach my $pdvd (@{$hostspec{dvd}}) {
            my $item = Gtk2::MenuItem->new_with_label('<Server Drive> ' . IMedium_getLocation($pdvd));
            $dvdmenu->append($item);
            $item->show();
            $item->signal_connect(activate => \&mount_dvd_online, $pdvd);
        }

        my $pdvdsep = Gtk2::SeparatorMenuItem->new();
        $dvdmenu->append($pdvdsep);
        $pdvdsep->show();
    }

    foreach (sort { lc($a) cmp lc($b) } (keys %$IMediumRef)) {
        my $item = Gtk2::MenuItem->new_with_label($_);
        $dvdmenu->append($item);
        $item->show();
        $item->signal_connect(activate => \&mount_dvd_online, $$IMediumRef{$_});
    }
}

# Called when parent USB menu is highlighted. Cheaper than calling each time machine menu is opened
sub fill_menu_usb() {
    my %connected;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $usbmenu = Gtk2::Menu->new();
        $gui{menuitemUSB}->set_submenu(undef); # Help garbage collection
        $gui{menuitemUSB}->set_submenu($usbmenu); # Hijack the temporary submenu (restored on exit)
        my @IHostUSBDevices = IHost_getUSBDevices(IVirtualBox_getHost($gui{websn}));
        my @USBDevices = IConsole_getUSBDevices(ISession_getConsole($$sref{ISession}));

        foreach my $IUSBDevice (@USBDevices) { $connected{IUSBDevice_getId($IUSBDevice)} = 1; }

        foreach my $usb (@IHostUSBDevices) {
            my $label = &usb_makelabel(IUSBDevice_getManufacturer($usb),
                                       IUSBDevice_getProduct($usb),
                                       sprintf('%04x', IUSBDevice_getRevision($usb)));

            my $item = Gtk2::CheckMenuItem->new_with_label($label);
            my $usbid = IUSBDevice_getId($usb);
            $item->set_active(1) if $connected{$usbid};
            $usbmenu->append($item);
            $item->show();
            $item->signal_connect(activate => \&mount_usb_online, [$usbid, $label]);
        }
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Fills the menu with the number of hot pluggable CPUs
sub fill_menu_cpu() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $cpumenu = Gtk2::Menu->new();
        $gui{menuitemHotPlugCPU}->set_submenu(undef); # Help garbage collection
        $gui{menuitemHotPlugCPU}->set_submenu($cpumenu); # Hijack the temporary submenu (restored on exit)
        my $cpucount = IMachine_getCPUCount($$sref{IMachine});

        # CPU 0 is special - it can never be detached.
        my $item = Gtk2::CheckMenuItem->new_with_label('vCPU 0');
        $item->set_active(1);
        $item->set_sensitive(0);
        $item->set_tooltip_text('vCPU 0 is never hot pluggabe');
        $cpumenu->append($item);
        $item->show();

        foreach my $cpunum (1..($cpucount - 1)) {
            my $item = Gtk2::CheckMenuItem->new_with_label("vCPU $cpunum");
            $item->set_active(1) if ($bool{IMachine_getCPUStatus($$sref{IMachine}, $cpunum)});
            $cpumenu->append($item);
            $item->show();
            $item->signal_connect(activate => \&mount_cpu_online, $cpunum);
        }
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Attaches or detaches a processor whilst the guest is online
sub mount_cpu_online() {
    my ($widget, $cpunum) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        if ($widget->get_active()) {
            IMachine_hotPlugCPU($$sref{IMachine}, $cpunum);
            &addrow_log("Attempt to hot plug vCPU $cpunum for $$gref{Name}.");
        }
        else {
            IMachine_hotUnplugCPU($$sref{IMachine}, $cpunum);
            &addrow_log("Attempt to hot unplug vCPU $cpunum for $$gref{Name}.");
        }

        IMachine_saveSettings($$sref{IMachine});
    }
    else { &addrow_log("Error: Could not change the status of CPU $cpunum for $$gref{Name}."); }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Attaches or detaches a USB device whilst the guest is online
sub mount_usb_online() {
    my ($widget, $dataref) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IConsole = ISession_getConsole($$sref{ISession});

        if ($widget->get_active()) {
            IConsole_attachUSBDevice($IConsole, $$dataref[0]);
            &addrow_log("Attached USB device '$$dataref[1]' to $$gref{Name}.");
        }
        else {
            IConsole_detachUSBDevice($IConsole, $$dataref[0]);
            &addrow_log("Detached USB device '$$dataref[1]' from $$gref{Name}.");
        }

        IMachine_saveSettings($$sref{IMachine});
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Called when parent Floppy menu is highlighted. Cheaper than calling each time machine menu is opened
sub fill_menu_floppy() {
    my $floppymenu = Gtk2::Menu->new();
    $gui{menuitemFloppy}->set_submenu(undef); # Help garbage collection
    $gui{menuitemFloppy}->set_submenu($floppymenu); # Hijack the temporary submenu (restored on exit)
    my $IMediumRef = &get_all_media('Floppy');
    my $item = Gtk2::MenuItem->new_with_label('<Empty Drive>');
    $floppymenu->append($item);
    $item->show();
    $item->signal_connect(activate => \&mount_floppy_online, '');
    my $sep = Gtk2::SeparatorMenuItem->new();
    $floppymenu->append($sep);
    $sep->show();

    if ($hostspec{floppy}) {
        foreach my $pfloppy (@{$hostspec{floppy}}) {
            my $item = Gtk2::MenuItem->new_with_label('<Server Drive> ' . IMedium_getLocation($pfloppy));
            $floppymenu->append($item);
            $item->show();
            $item->signal_connect(activate => \&mount_floppy_online, $pfloppy);
        }

        my $pfloppysep = Gtk2::SeparatorMenuItem->new();
        $floppymenu->append($pfloppysep);
        $pfloppysep->show();
    }

    foreach (sort { lc($a) cmp lc($b) } (keys %$IMediumRef)) {
        my $item = Gtk2::MenuItem->new_with_label($_);
        $floppymenu->append($item);
        $item->show();
        $item->signal_connect(activate => \&mount_floppy_online, $$IMediumRef{$_});
    }
}

# Inserts a DVD/CD whilst the guest is online and running
sub mount_dvd_online() {
    my ($widget, $IMedium) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my @IMediumAttachment = IMachine_getMediumAttachments($$sref{IMachine});
        foreach my $attach (@IMediumAttachment) {
            next if ($$attach{type} ne 'DVD');
            IMachine_mountMedium($$sref{IMachine}, $$attach{controller}, $$attach{port}, $$attach{device}, $IMedium, 0);
            last;
        }
        IMachine_saveSettings($$sref{IMachine});
        &addrow_log("Changed CD/DVD medium for $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Inserts a floppy disk image whilst the guest is online
sub mount_floppy_online() {
    my ($widget, $IMedium) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my @IMediumAttachment = IMachine_getMediumAttachments($$sref{IMachine});
        foreach my $attach (@IMediumAttachment) {
            next if ($$attach{type} ne 'Floppy');
            IMachine_mountMedium($$sref{IMachine}, $$attach{controller}, $$attach{port}, $$attach{device}, $IMedium, 0);
            last;
        }
        IMachine_saveSettings($$sref{IMachine});
        &addrow_log("Changed floppy medium for $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub keyboard_CAD() {
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IKeyboard = IConsole_getKeyboard(ISession_getConsole($$sref{ISession}));
        IKeyboard_putCAD($IKeyboard);
        &addrow_log("Keyboard sequence Ctrl-Alt-Delete sent to $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub keyboard_send() {
    my ($widget) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});
    my $sequence;
    my @scancodes;

    if ($widget eq $gui{menuitemKeyboardCAF1}) {
        $sequence = 'Ctrl-Alt-F1';
        @scancodes = (29, 56, 59, 157, 184, 187);
    }
    elsif ($widget eq $gui{menuitemKeyboardCAF2}) {
        $sequence = 'Ctrl-Alt-F2';
        @scancodes = (29, 56, 60, 157, 184, 188);
    }
    elsif ($widget eq $gui{menuitemKeyboardCAF3}) {
        $sequence = 'Ctrl-Alt-F3';
        @scancodes = (29, 56, 61, 157, 184, 189);
    }
    elsif ($widget eq $gui{menuitemKeyboardCAF7}) {
        $sequence = 'Ctrl-Alt-F7';
        @scancodes = (29, 56, 65, 157, 184, 193);
    }
    elsif ($widget eq $gui{menuitemKeyboardCAF12}) {
        $sequence = 'Ctrl-Alt-F12';
        @scancodes = (29, 56, 88, 157, 184, 216);
    }
    elsif ($widget eq $gui{menuitemKeyboardASF1}) {
        $sequence = 'Alt-SysRq+F1';
        @scancodes = (56, 84, 184, 212, 59, 187); # Actually sends Alt-SysRq THEN F1
    }
    elsif ($widget eq $gui{menuitemKeyboardASF2}) {
        $sequence = 'Alt-SysRq+F2';
        @scancodes = (56, 84, 184, 212, 60, 188); # Actually sends Alt-SysRq THEN F2
    }
    elsif ($widget eq $gui{menuitemKeyboardASF3}) {
        $sequence = 'Alt-SysRq+F3';
        @scancodes = (56, 84, 184, 212, 61, 189); # Actually sends Alt-SysRq THEN F3
    }
    elsif ($widget eq $gui{menuitemKeyboardASF4}) {
        $sequence = 'Alt-SysRq+F4';
        @scancodes = (56, 84, 184, 212, 62, 190); # Actually sends Alt-SysRq THEN F4
    }
    elsif ($widget eq $gui{menuitemKeyboardASF5}) {
        $sequence = 'Alt-SysRq+F5';
        @scancodes = (56, 84, 184, 212, 63, 191); # Actually sends Alt-SysRq THEN F5
    }
    elsif ($widget eq $gui{menuitemKeyboardASF6}) {
        $sequence = 'Alt-SysRq+F6';
        @scancodes = (56, 84, 184, 212, 64, 192); # Actually sends Alt-SysRq THEN F6
    }
    elsif ($widget eq $gui{menuitemKeyboardASF7}) {
        $sequence = 'Alt-SysRq+F7';
        @scancodes = (56, 84, 184, 212, 65, 193); # Actually sends Alt-SysRq THEN F7
    }
    elsif ($widget eq $gui{menuitemKeyboardASF8}) {
        $sequence = 'Alt-SysRq+F8';
        @scancodes = (56, 84, 184, 212, 66, 194); # Actually sends Alt-SysRq THEN F8
    }
    elsif ($widget eq $gui{menuitemKeyboardASH}) {
        $sequence = 'Alt-SysRq+H';
        @scancodes = (56, 84, 184, 212, 35, 163); # Actually sends Alt-SysRq THEN H
    }
    elsif ($widget eq $gui{menuitemKeyboardCABS}) {
        $sequence = 'Ctrl-Alt-Backspace';
        @scancodes = (29, 56, 14, 157, 184, 142);
    }
    elsif ($widget eq $gui{menuitemKeyboardCTRLC}) {
        $sequence = 'Ctrl-c';
        @scancodes = (29, 46, 157, 174);
    }
    elsif ($widget eq $gui{menuitemKeyboardCTRLD}) {
        $sequence = 'Ctrl-d';
        @scancodes = (29, 32, 157, 160);
    }

    if ($$sref{IMachine}) {
        my $IKeyboard = IConsole_getKeyboard(ISession_getConsole($$sref{ISession}));
        IKeyboard_putScancode($IKeyboard, $_) foreach (@scancodes);
        &addrow_log("Keyboard sequence $sequence sent to $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

sub update_vidmeminfo() {
    my $w = int($gui{spinbuttonCustomVideoW}->get_value());
    my $h = int($gui{spinbuttonCustomVideoH}->get_value());
    my $d = &getsel_combo($gui{comboboxCustomVideoD}, 1);

    my $vidmem = ceil(($w * $h * $d) / 8388608);
    $gui{labelCustomVideoInfo}->set_text("Ensure the guest has at least $vidmem MB of Video RAM for this mode");
}

# Sends a video mode hint to the guest
sub send_video_hint() {
    my ($widget) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});
    my %res = (w => 640,
               h => 480,
               d => 32);

    if ($widget eq $gui{menuitemSetVideo1}) { %res = (w => 800, h => 600, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo2}) { %res = (w => 1024, h => 768, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo3}) { %res = (w => 1280, h => 1024, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo4}) { %res = (w => 1400, h => 1050, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo5}) { %res = (w => 1600, h => 1200, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo6}) { %res = (w => 1440, h => 900, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo7}) { %res = (w => 1680, h => 1050, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideo8}) { %res = (w => 1920, h => 1200, d => 32); }
    elsif ($widget eq $gui{menuitemSetVideoCustom}) { %res = &show_dialog_customvideo(); }

    if ($$sref{IMachine}) {
        my $IConsole = ISession_getConsole($$sref{ISession});
        IDisplay_setVideoModeHint(IConsole_getDisplay($IConsole), 0, 1, 0, 0, 0, $res{w}, $res{h}, $res{d});
        &addrow_log("Sent video hint ($res{w}x$res{h}:$res{d}) to $$gref{Name}.");
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}

# Return a hash reference with name as key. Useful for sorting
sub get_all_media() {
    my ($type) = @_;
    my @IMedium;
    my %media;

    if ($type eq 'DVD') { @IMedium = IVirtualBox_getDVDImages($gui{websn}); }
    elsif ($type eq 'Floppy') { @IMedium = IVirtualBox_getFloppyImages($gui{websn}); }
    else { @IMedium = IVirtualBox_getHardDisks($gui{websn}); }

    $media{IMedium_getName($_)} = $_ foreach (@IMedium);
    return \%media;
}

# Return an appropriate session
sub get_session() {
    my ($IMachine) = @_;
    my $ISession = IWebsessionManager_getSessionObject($gui{websn});
    my %state = (Lock     => 'None',
                 Type     => 'None',
                 IMachine => 0,
                 ISession => 0);

    if (IMachine_getSessionState($IMachine)  eq 'Unlocked') {
        IMachine_lockMachine($IMachine, $ISession, 'VM');
        $state{Lock} = 'VM';
        $state{Type} = ISession_getType($ISession);
        $state{IMachine} = ISession_getMachine($ISession);
        $state{ISession} = $ISession;
    }
    elsif (IMachine_getSessionState($IMachine)  eq 'Locked') {
        # Theoretically we shouldn't return a writelock here because it should have already been locked
        IMachine_lockMachine($IMachine, $ISession, 'Shared');
        $state{Lock} = 'Shared';
        $state{Type} = ISession_getType($ISession);
        $state{IMachine} = ISession_getMachine($ISession);
        $state{ISession} = $ISession;
    }

    return \%state;
}

# Simple sub which returns only the path component, based upon what the server is.
sub get_path_only() {
    my ($location) = @_;

    if ($hostspec{os} =~ m/Windows/i) {
        $location =~ m!^(.*)\\.*$!;
        $location = $1;
    }
    else { # All other operating systems use / as delim
        $location =~ m!^(.*)/.*$!;
        $location = $1;
    }

    return $location
}

# Expects a hash reference as an input and populates the hash with IMedium
# attributes, if that attribute has already been defined. The second argument
# is the IMedium virtualbox reference
sub get_imedium_attrs() {
    my ($href, $IMedium) = @_;
    return unless $IMedium;
    $$href{IMedium} = $IMedium; # For convenience
    $$href{refresh} = IMedium_refreshState($IMedium) if ($$href{refresh}); # Tell VM to get latest info on media (ie file size)
    $$href{accesserr} = IMedium_getLastAccessError($IMedium) if ($$href{accesserr});
    $$href{name} = IMedium_getName($IMedium) if ($$href{name});
    $$href{size} = IMedium_getSize($IMedium) if ($$href{size}); # Physical size in bytes
    $$href{logsize} = IMedium_getLogicalSize($IMedium) if ($$href{logsize}); # Logical size in bytes
    $$href{machineids} = [IMedium_getMachineIds($IMedium)] if ($$href{machineids}); # Machine IDs associated with media
    $$href{children} = [IMedium_getChildren($IMedium)] if ($$href{children}); # Children of media
    $$href{location} = IMedium_getLocation($IMedium) if ($$href{location}); # Disk location of medium
    $$href{type} = IMedium_getType($IMedium) if ($$href{type}); # Get the medium type
}

# Expects a hash reference as an input and populates the hash with IStorageController
# attributes, if that attribute has already been defined. The second argument
# is the IStorageController virtualbox reference
sub get_icontroller_attrs() {
    my ($href, $IStorageController) = @_;
    return unless $IStorageController;
    $$href{IStorageController} = $IStorageController; # For Convenience
    $$href{name} = IStorageController_getName($IStorageController) if ($$href{name});
    $$href{bus} = IStorageController_getBus($IStorageController) if ($$href{bus});
    $$href{cache} = $bool{IStorageController_getUseHostIOCache($IStorageController)} if ($$href{cache});
}

# Set sensitivity based on connection state
sub sens_connect() {
    my ($state) = @_;
    $gui{menuitemNew}->set_sensitive($state);
    $gui{menuitemAdd}->set_sensitive($state);
    $gui{menuitemVMM}->set_sensitive($state);
    $gui{menuitemServerInfo}->set_sensitive($state);
    $gui{menuitemVBPrefs}->set_sensitive($state);
    $gui{toolbuttonNew}->set_sensitive($state);
    $gui{toolbuttonRefresh}->set_sensitive($state);
}

# Sets the sensitivity when no guest is selected
sub sens_unselected() {
    $gui{menuitemAction}->set_sensitive(0);
    $gui{menuitemStart}->set_sensitive(0);
    $gui{menuitemStop}->set_sensitive(0);
    $gui{menuitemPause}->set_sensitive(0);
    $gui{menuitemResume}->set_sensitive(0);
    $gui{menuitemSettings}->set_sensitive(0);
    $gui{menuitemClone}->set_sensitive(0);
    $gui{menuitemReset}->set_sensitive(0);
    $gui{menuitemRemove}->set_sensitive(0);
    $gui{menuitemKeyboard}->set_sensitive(0);
    $gui{menuitemDisplay}->set_sensitive(0);
    $gui{menuitemHotPlugCPU}->set_sensitive(0);
    $gui{menuitemDVD}->set_sensitive(0);
    $gui{menuitemFloppy}->set_sensitive(0);
    $gui{menuitemUSB}->set_sensitive(0);
    $gui{menuitemScreenshot}->set_sensitive(0);
    $gui{menuitemLogs}->set_sensitive(0);
    $gui{toolbuttonStart}->set_sensitive(0);
    $gui{toolbuttonStop}->set_sensitive(0);
    $gui{toolbuttonSettings}->set_sensitive(0);
    $gui{toolbuttonRemoteDisplay}->set_sensitive(0);
    $gui{toolbuttonReset}->set_sensitive(0);
    $gui{buttonShowDetails}->set_sensitive(0);
    $gui{buttonRefreshSnapshots}->set_sensitive(0);
    $gui{buttonTakeSnapshot}->set_sensitive(0);
}

# Set sensitivity when a snapshot is selected
sub sens_snapshots() {
    $gui{buttonRestoreSnapshot}->set_sensitive(1);
    $gui{buttonDeleteSnapshot}->set_sensitive(1);
    $gui{buttonDetailsSnapshot}->set_sensitive(1);
}

# Resets the guest icon back to the default
sub reset_icon() {
    my $gref = &getsel_list_guest();
    unlink("$gui{THUMBDIR}/$$gref{Uuid}.png") if (-e "$gui{THUMBDIR}/$$gref{Uuid}.png");
    &fill_list_guest();
}

# Takes a PNG screenshot of the guest
sub screenshot() {
    my ($widget) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});
    $gui{filechooserscreenshot}->set_current_name("$$gref{Name}_screenshot.png");
    $gui{filechooserscreenshot}->set_current_folder($ENV{HOME}) if ($ENV{HOME});
    my $response = $gui{filechooserscreenshot}->run();
    $gui{filechooserscreenshot}->hide();
    my $filename = $gui{filechooserscreenshot}->get_filename();

    if ($response eq 'ok' and $filename) {

        if ($$sref{Lock} eq 'Shared') {
            my $IConsole = ISession_getConsole($$sref{ISession});
            my $IDisplay = IConsole_getDisplay($IConsole);
            my ($w, $h, $d) = IDisplay_getScreenResolution($IDisplay, 0);

            if (my $rawscreenshot = IDisplay_takeScreenShotPNGToArray($IDisplay, 0, $w, $h)) {
                my $screenshot = decode_base64($rawscreenshot);
                if (open(SHOT, '>', $filename)) {
                    binmode SHOT; # Not normally needed for UNIX, but more portable
                    print SHOT $screenshot;
                    close(SHOT);
                    &fill_list_guest();
                    &addrow_log("Saved screenshot of $$gref{Name} as $filename.");
                }
                else { &addrow_log("Failed to save screenshot of $$gref{Name} as $filename."); }
            }
            else { &show_err_msg('noscreenshot', $gui{messagedialogWarning}); }
        }
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}


# Sets the guest icon to a screenshot of the guest
sub screenshot_to_icon() {
    my ($widget) = @_;
    my $gref = &getsel_list_guest();
    my $sref = &get_session($$gref{IMachine});

    if ($$sref{Lock} eq 'Shared') {
        my $IConsole = ISession_getConsole($$sref{ISession});
        my $IDisplay = IConsole_getDisplay($IConsole);

        if (my $rawicon = IDisplay_takeScreenShotPNGToArray($IDisplay, 0, 32, 32)) {
            mkdir($gui{THUMBDIR}, 0755) unless (-e $gui{THUMBDIR});
            my $icon = decode_base64($rawicon);
            if (open(ICON, '>', "$gui{THUMBDIR}/$$gref{Uuid}.png")) {
                binmode ICON; # Not normally needed for UNIX, but more portable
                print ICON $icon;
                close(ICON);
                &fill_list_guest();
                &addrow_log("Configured screenshot as icon for $$gref{Name}.");
            }
            else { &addrow_log("Warning: Could not save icon for $$gref{Name}."); }
        }
        else { &show_err_msg('noscreenshot', $gui{messagedialogWarning}); }
    }

    ISession_unlockMachine($$sref{ISession}) if (ISession_getState($$sref{ISession}) eq 'Locked');
}


# Shows the PDF manual when the option is selected. Uses the user's default
# PDF reader via xdg-open on UNIX/BSD/Linux or open on Mac OS X
sub show_manual() {
    if ($^O =~ m/darwin/i) { system(qq[open "$Bin/docs/remotebox.pdf" &]); }
    else { system(qq[xdg-open "$Bin/docs/remotebox.pdf" &]); }
}

# Converts bytes into a human readable format with unit
sub bytesToX() {
    my ($bytes) = @_;
    my $unit;
    my $val;

    if ($bytes < 1024) {
        $unit = 'Bytes';
        $val = $bytes;
    }
    elsif ($bytes < 1048576) {
        $unit = 'KB';
        $val = $bytes / 1024;
    }
    elsif ($bytes < 1073741824) {
        $unit = 'MB';
        $val = $bytes / 1048576;
    }
    elsif ($bytes < 1099511627776) {
        $unit = 'GB';
        $val = $bytes / 1073741824;
    }
    else {
        $unit = 'TB';
        $val = $bytes / 1099511627776;
    }

    $val = sprintf('%0.2f', $val) if ($unit ne 'Bytes');
    return "$val $unit";
}

