diff --git a/ui/xemu-snapshots.c b/ui/xemu-snapshots.c index ec719f99e9..5a695257c1 100644 --- a/ui/xemu-snapshots.c +++ b/ui/xemu-snapshots.c @@ -31,6 +31,7 @@ #include "migration/qemu-file.h" #include "migration/snapshot.h" #include "qapi/error.h" +#include "qapi/qapi-commands-block.h" #include "sysemu/runstate.h" #include "qemu-common.h" @@ -53,6 +54,7 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, QEMUSnapshotInfo *info, XemuSnapshotData *data, Error **err) { + data->disc_path = NULL; data->xbe_title_name = NULL; data->gl_thumbnail = 0; @@ -82,11 +84,28 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, return; } - const size_t xbe_title_name_size = buf[0]; - offset = 1; + assert(size >= 9); + + offset = 0; + + const size_t disc_path_size = be32_to_cpu(*(uint32_t *)&buf[offset]); + offset += 4; + + if (disc_path_size) { + data->disc_path = (char *)g_malloc(disc_path_size + 1); + assert(size >= (offset + disc_path_size)); + memcpy(data->disc_path, &buf[offset], disc_path_size); + data->disc_path[disc_path_size] = 0; + offset += disc_path_size; + } + + assert(size >= (offset + 4)); + const size_t xbe_title_name_size = buf[offset]; + offset += 1; if (xbe_title_name_size) { data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1); + assert(size >= (offset + xbe_title_name_size)); memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size); data->xbe_title_name[xbe_title_name_size] = 0; offset += xbe_title_name_size; @@ -98,6 +117,7 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, if (thumbnail_size) { GLuint thumbnail; glGenTextures(1, &thumbnail); + assert(size >= (offset + thumbnail_size)); if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset], thumbnail_size)) { data->gl_thumbnail = thumbnail; @@ -202,6 +222,30 @@ done: return xemu_snapshots_len; } +char *xemu_get_currently_loaded_disc_path(void) +{ + char *file = NULL; + BlockInfoList *block_list, *info; + + block_list = qmp_query_block(NULL); + + for (info = block_list; info; info = info->next) { + if (strcmp("ide0-cd1", info->value->device)) { + continue; + } + + if (info->value->has_inserted) { + BlockDeviceInfo *inserted = info->value->inserted; + if (inserted->has_node_name) { + file = g_strdup(inserted->file); + } + } + } + + qapi_free_BlockInfoList(block_list); + return file; +} + void xemu_snapshots_load(const char *vm_name, Error **err) { bool vm_running = runstate_is_running(); @@ -223,6 +267,9 @@ void xemu_snapshots_delete(const char *vm_name, Error **err) void xemu_snapshots_save_extra_data(QEMUFile *f) { + char *path = xemu_get_currently_loaded_disc_path(); + size_t path_size = path ? strlen(path) : 0; + size_t xbe_title_name_size = 0; char *xbe_title_name = NULL; struct xbe *xbe_data = xemu_get_xbe_info(); @@ -239,7 +286,13 @@ void xemu_snapshots_save_extra_data(QEMUFile *f) qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC); qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION); - qemu_put_be32(f, 1 + xbe_title_name_size + 4 + thumbnail_size); + qemu_put_be32(f, 4 + path_size + 1 + xbe_title_name_size + 4 + thumbnail_size); + + qemu_put_be32(f, path_size); + if (path_size) { + qemu_put_buffer(f, (const uint8_t *)path, path_size); + g_free(path); + } qemu_put_byte(f, xbe_title_name_size); if (xbe_title_name_size) { diff --git a/ui/xemu-snapshots.h b/ui/xemu-snapshots.h index 9a33e687b4..a9809117a2 100644 --- a/ui/xemu-snapshots.h +++ b/ui/xemu-snapshots.h @@ -37,11 +37,13 @@ extern "C" { extern const char **g_snapshot_shortcut_index_key_map[]; typedef struct XemuSnapshotData { + char *disc_path; char *xbe_title_name; GLuint gl_thumbnail; } XemuSnapshotData; // Implemented in xemu-snapshots.c +char *xemu_get_currently_loaded_disc_path(void); int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data, Error **err); void xemu_snapshots_load(const char *vm_name, Error **err); diff --git a/ui/xemu.c b/ui/xemu.c index 4cb9891b20..1f009b1104 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -55,6 +55,7 @@ #include "hw/xbox/smbus.h" // For eject, drive tray #include "hw/xbox/nv2a/nv2a.h" +#include "ui/xemu-notifications.h" #include @@ -1556,26 +1557,34 @@ int main(int argc, char **argv) // rcu_unregister_thread(); } -void xemu_eject_disc(void) +void xemu_eject_disc(Error **errp) { + Error *error = NULL; + xbox_smc_eject_button(); // Xbox software may request that the drive open, but do it now anyway - Error *err = NULL; - qmp_eject(true, "ide0-cd1", false, NULL, true, false, &err); + qmp_eject(true, "ide0-cd1", false, NULL, true, false, &error); + if (error) { + error_propagate(errp, error); + } xbox_smc_update_tray_state(); } -void xemu_load_disc(const char *path) +void xemu_load_disc(const char *path, Error **errp) { + Error *error = NULL; + // Ensure an eject sequence is always triggered so Xbox software reloads xbox_smc_eject_button(); - Error *err = NULL; qmp_blockdev_change_medium(true, "ide0-cd1", false, NULL, path, false, "", false, 0, - &err); + &error); + if (error) { + error_propagate(errp, error); + } xbox_smc_update_tray_state(); } diff --git a/ui/xui/actions.cc b/ui/xui/actions.cc index f38be7dcb5..f6e8cb29ec 100644 --- a/ui/xui/actions.cc +++ b/ui/xui/actions.cc @@ -17,19 +17,29 @@ // along with this program. If not, see . // #include "common.hh" +#include "actions.hh" #include "misc.hh" #include "xemu-hud.h" #include "../xemu-snapshots.h" #include "../xemu-notifications.h" +#include "snapshot-manager.hh" void ActionEjectDisc(void) { xemu_settings_set_string(&g_config.sys.files.dvd_path, ""); - xemu_eject_disc(); + + Error *err = NULL; + xemu_eject_disc(&err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } } void ActionLoadDisc(void) { + Error *err = NULL; + const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0"; const char *new_disc_path = PausedFileOpen(NOC_FILE_DIALOG_OPEN, iso_file_filters, @@ -39,7 +49,12 @@ void ActionLoadDisc(void) return; } xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path); - xemu_load_disc(new_disc_path); + + xemu_load_disc(new_disc_path, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } } void ActionTogglePause(void) @@ -81,7 +96,7 @@ void ActionActivateBoundSnapshot(int slot, bool save) if (save) { xemu_snapshots_save(snapshot_name, &err); } else { - xemu_snapshots_load(snapshot_name, &err); + ActionLoadSnapshotChecked(snapshot_name); } if (err) { @@ -89,3 +104,8 @@ void ActionActivateBoundSnapshot(int slot, bool save) error_free(err); } } + +void ActionLoadSnapshotChecked(const char *name) +{ + g_snapshot_mgr.LoadSnapshotChecked(name); +} diff --git a/ui/xui/actions.hh b/ui/xui/actions.hh index 60235bf373..08aedd3c9b 100644 --- a/ui/xui/actions.hh +++ b/ui/xui/actions.hh @@ -25,3 +25,4 @@ void ActionReset(); void ActionShutdown(); void ActionScreenshot(); void ActionActivateBoundSnapshot(int slot, bool save); +void ActionLoadSnapshotChecked(const char *name); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 3f7d3b6d1f..0d3ca34885 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -22,12 +22,14 @@ #include "main-menu.hh" #include "font-manager.hh" #include "input-manager.hh" +#include "snapshot-manager.hh" #include "viewport-manager.hh" #include "xemu-hud.h" #include "misc.hh" #include "gl-helpers.hh" #include "reporting.hh" #include "qapi/error.h" +#include "actions.hh" #include "../xemu-input.h" #include "../xemu-notifications.h" @@ -35,7 +37,6 @@ #include "../xemu-monitor.h" #include "../xemu-version.h" #include "../xemu-net.h" -#include "../xemu-snapshots.h" #include "../xemu-os-utils.h" #include "../xemu-xbe.h" @@ -641,63 +642,24 @@ void MainMenuNetworkView::DrawUdpOptions(bool appearing) ImGui::PopFont(); } -void MainMenuSnapshotsView::Load() -{ - Error *err = NULL; - - if (!m_load_failed) { - m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err); - } - - if (err) { - m_load_failed = true; - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - m_snapshots_len = 0; - } - - struct xbe *xbe = xemu_get_xbe_info(); - if (xbe && xbe->cert->m_titleid != m_current_title_id) { - g_free(m_current_title_name); - m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); - m_current_title_id = xbe->cert->m_titleid; - } -} - -MainMenuSnapshotsView::MainMenuSnapshotsView(): MainMenuTabView() +MainMenuSnapshotsView::MainMenuSnapshotsView() : MainMenuTabView() { xemu_snapshots_mark_dirty(); - m_load_failed = false; m_search_regex = NULL; m_current_title_name = NULL; m_current_title_id = 0; - } MainMenuSnapshotsView::~MainMenuSnapshotsView() { - g_free(m_snapshots); - g_free(m_extra_data); - xemu_snapshots_mark_dirty(); - g_free(m_current_title_name); g_free(m_search_regex); } -void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint thumbnail) +bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) { - Error *err = NULL; - int current_snapshot_binding = -1; - for (int i = 0; i < 4; ++i) { - if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { - assert(current_snapshot_binding == -1); - current_snapshot_binding = i; - } - } - ImGuiStyle &style = ImGui::GetStyle(); - ImVec2 pos = ImGui::GetCursorPos(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); ImGui::PushFont(g_font_mgr.m_menu_font_small); @@ -706,6 +668,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5))); + ImGui::PushFont(g_font_mgr.m_menu_font_medium); ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name); @@ -715,54 +678,36 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImVec2 title_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); ImVec2 date_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); ImVec2 binding_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 button_size(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3)); - bool load = ImGui::Button("###button", ImVec2(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3))); - if (load) { - xemu_snapshots_load(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - bool options_key_pressed = ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft); - bool show_snapshot_context_menu = ImGui::IsItemHovered() && - (ImGui::IsMouseReleased(ImGuiMouseButton_Right) || - options_key_pressed); + bool load = ImGui::Button("###button", button_size); - ImVec2 next_pos = ImGui::GetCursorPos(); - const ImVec2 p0 = ImGui::GetItemRectMin(); - const ImVec2 p1 = ImGui::GetItemRectMax(); ImGui::PopFont(); + const ImVec2 p0 = ImGui::GetItemRectMin(); + const ImVec2 p1 = ImGui::GetItemRectMax(); draw_list->PushClipRect(p0, p1, true); // Snapshot thumbnail - ImGui::SetItemAllowOverlap(); - ImGui::SetCursorPos(ImVec2(pos.x + thumbnail_pos.x, pos.y + thumbnail_pos.y)); - - if (!thumbnail) { - thumbnail = g_icon_tex; - } - + GLuint thumbnail = data->gl_thumbnail ? data->gl_thumbnail : g_icon_tex; + int thumbnail_width, thumbnail_height; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thumbnail); - int thumbnail_width, thumbnail_height; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); // Draw black background behind thumbnail - draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y), - ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), - IM_COL32_BLACK); + ImVec2 thumbnail_min(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y); + ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x, thumbnail_min.y + thumbnail_size.y); + draw_list->AddRectFilled(thumbnail_min, thumbnail_max, IM_COL32_BLACK); - // Draw centered thumbnail + // Draw centered thumbnail image int scaled_width, scaled_height; ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); - ImVec2 img_pos = ImGui::GetCursorPos(); - img_pos.x += (thumbnail_size.x - scaled_width) / 2; - img_pos.y += (thumbnail_size.y - scaled_height) / 2; - ImGui::SetCursorPos(img_pos); - ImGui::Image((ImTextureID)(uint64_t)thumbnail, ImVec2(scaled_width, scaled_height)); + ImVec2 img_min = ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2, + thumbnail_min.y + (thumbnail_size.y - scaled_height) / 2); + ImVec2 img_max = ImVec2(img_min.x + scaled_width, img_min.y + scaled_height); + draw_list->AddImage((ImTextureID)(uint64_t)thumbnail, img_min, img_max); // Snapshot title ImGui::PushFont(g_font_mgr.m_menu_font_medium); @@ -771,6 +716,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const // Snapshot XBE title name ImGui::PushFont(g_font_mgr.m_menu_font_small); + const char *title_name = data->xbe_title_name ? data->xbe_title_name : "(Unknown XBE Title Name)"; draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); // Snapshot date @@ -788,71 +734,9 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::PopFont(); draw_list->PopClipRect(); - - if (show_snapshot_context_menu) { - if (options_key_pressed) { - ImGui::SetNextWindowPos(p0); - } - ImGui::OpenPopup("Snapshot Options"); - } - - if (ImGui::BeginPopupContextItem("Snapshot Options")) { - if (ImGui::MenuItem("Load")) { - xemu_snapshots_load(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - - if (ImGui::BeginMenu("Keybinding")) { - for (int i = 0; i < 4; ++i) { - char *item_name = g_strdup_printf("Bind to F%d", i + 5); - - if (ImGui::MenuItem(item_name)) { - if (current_snapshot_binding >= 0) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); - } - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); - current_snapshot_binding = i; - - ImGui::CloseCurrentPopup(); - } - - g_free(item_name); - } - - if (current_snapshot_binding >= 0) { - if (ImGui::MenuItem("Unbind")) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); - current_snapshot_binding = -1; - } - } - ImGui::EndMenu(); - } - - ImGui::Separator(); - - if (ImGui::MenuItem("Replace")) { - xemu_snapshots_save(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - if (ImGui::MenuItem("Delete")) { - xemu_snapshots_delete(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - - ImGui::EndPopup(); - } - ImGui::PopStyleVar(2); - ImGui::SetCursorPos(next_pos); + + return load; } void MainMenuSnapshotsView::ClearSearch() @@ -892,18 +776,26 @@ static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data void MainMenuSnapshotsView::Draw() { - Load(); - SectionTitle("Snapshots"); + g_snapshot_mgr.Refresh(); - Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game); + struct xbe *xbe = xemu_get_xbe_info(); + if (xbe && xbe->cert->m_titleid != m_current_title_id) { + g_free(m_current_title_name); + m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); + m_current_title_id = xbe->cert->m_titleid; + } + + SectionTitle("Snapshots"); + Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game, + "Only display snapshots created while running the currently running XBE"); ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); bool snapshot_with_create_name_exists = false; - for (int i = 0; i < m_snapshots_len; ++i) { - if (g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0) { + for (int i = 0; i < g_snapshot_mgr.m_snapshots_len; ++i) { + if (g_strcmp0(m_search_buf.c_str(), g_snapshot_mgr.m_snapshots[i].name) == 0) { snapshot_with_create_name_exists = true; break; } @@ -919,9 +811,9 @@ void MainMenuSnapshotsView::Draw() ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } - for (int i = m_snapshots_len - 1; i >= 0; i--) { - if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && - (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { + for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { + if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && + (strcmp(m_current_title_name, g_snapshot_mgr.m_extra_data[i].xbe_title_name) != 0)) { continue; } @@ -929,12 +821,12 @@ void MainMenuSnapshotsView::Draw() GMatchInfo *match; bool keep_entry = false; - g_regex_match(m_search_regex, m_snapshots[i].name, (GRegexMatchFlags)0, &match); + g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name, (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_match_info_free(match); - if (m_extra_data[i].xbe_title_name) { - g_regex_match(m_search_regex, m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); + if (g_snapshot_mgr.m_extra_data[i].xbe_title_name) { + g_regex_match(m_search_regex, g_snapshot_mgr.m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_free(match); } @@ -944,16 +836,94 @@ void MainMenuSnapshotsView::Draw() } } + QEMUSnapshotInfo *snapshot = &g_snapshot_mgr.m_snapshots[i]; + XemuSnapshotData *data = &g_snapshot_mgr.m_extra_data[i]; + + int current_snapshot_binding = -1; + for (int i = 0; i < 4; ++i) { + if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { + assert(current_snapshot_binding == -1); + current_snapshot_binding = i; + } + } + ImGui::PushID(i); - SnapshotBigButton( - m_snapshots + i, - m_extra_data[i].xbe_title_name ? m_extra_data[i].xbe_title_name : "Unknown", - m_extra_data[i].gl_thumbnail - ); + + ImVec2 pos = ImGui::GetCursorScreenPos(); + bool load = BigSnapshotButton(snapshot, data, current_snapshot_binding); + + // FIXME: Provide context menu control annotation + if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) { + ImGui::SetNextWindowPos(pos); + ImGui::OpenPopup("Snapshot Options"); + } + + DrawSnapshotContextMenu(snapshot, data, current_snapshot_binding); + ImGui::PopID(); + + if (load) { + ActionLoadSnapshotChecked(snapshot->name); + } } } +void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) +{ + if (!ImGui::BeginPopupContextItem("Snapshot Options")) { + return; + } + + if (ImGui::MenuItem("Load")) { + ActionLoadSnapshotChecked(snapshot->name); + } + + if (ImGui::BeginMenu("Keybinding")) { + for (int i = 0; i < 4; ++i) { + char *item_name = g_strdup_printf("Bind to F%d", i + 5); + + if (ImGui::MenuItem(item_name)) { + if (current_snapshot_binding >= 0) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + } + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); + current_snapshot_binding = i; + + ImGui::CloseCurrentPopup(); + } + + g_free(item_name); + } + + if (current_snapshot_binding >= 0) { + if (ImGui::MenuItem("Unbind")) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + current_snapshot_binding = -1; + } + } + ImGui::EndMenu(); + } + + ImGui::Separator(); + + Error *err = NULL; + + if (ImGui::MenuItem("Replace")) { + xemu_snapshots_save(snapshot->name, &err); + } + + if (ImGui::MenuItem("Delete")) { + xemu_snapshots_delete(snapshot->name, &err); + } + + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + + ImGui::EndPopup(); +} + MainMenuSystemView::MainMenuSystemView() : m_dirty(false) { } diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index 7eb015e4d7..0c769a3c5b 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -104,24 +104,19 @@ public: class MainMenuSnapshotsView : public virtual MainMenuTabView { protected: - QEMUSnapshotInfo *m_snapshots; - XemuSnapshotData *m_extra_data; - int m_snapshots_len; uint32_t m_current_title_id; char *m_current_title_name; std::string m_search_buf; - bool m_load_failed; private: - void Load(); void ClearSearch(); + void DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); public: GRegex *m_search_regex; MainMenuSnapshotsView(); ~MainMenuSnapshotsView(); - void SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, - GLuint screenshot); + bool BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); void Draw() override; }; diff --git a/ui/xui/main.cc b/ui/xui/main.cc index e027c409a9..4680dad29b 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -37,6 +37,7 @@ #include "misc.hh" #include "gl-helpers.hh" #include "input-manager.hh" +#include "snapshot-manager.hh" #include "viewport-manager.hh" #include "font-manager.hh" #include "scene.hh" @@ -54,6 +55,8 @@ #endif bool g_screenshot_pending; +const char *g_snapshot_pending_load_name; + float g_main_menu_height; static ImGuiStyle g_base_style; @@ -298,6 +301,7 @@ void xemu_hud_render(void) #endif g_scene_mgr.Draw(); if (!first_boot_window.is_open) notification_manager.Draw(); + g_snapshot_mgr.Draw(); // static bool show_demo = true; // if (show_demo) ImGui::ShowDemoWindow(&show_demo); diff --git a/ui/xui/meson.build b/ui/xui/meson.build index 088e68c764..2fe5b2c64a 100644 --- a/ui/xui/meson.build +++ b/ui/xui/meson.build @@ -16,6 +16,7 @@ xemu_ss.add(files( 'scene-components.cc', 'scene-manager.cc', 'scene.cc', + 'snapshot-manager.cc', 'viewport-manager.cc', 'welcome.cc', 'widgets.cc', diff --git a/ui/xui/snapshot-manager.cc b/ui/xui/snapshot-manager.cc new file mode 100644 index 0000000000..5362e3f00f --- /dev/null +++ b/ui/xui/snapshot-manager.cc @@ -0,0 +1,163 @@ +// +// xemu User Interface +// +// Copyright (C) 2020-2022 Matt Borgerson +// +// 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. If not, see . +// + +#include "common.hh" +#include "notifications.hh" +#include "snapshot-manager.hh" +#include "xemu-hud.h" + +SnapshotManager g_snapshot_mgr; + +SnapshotManager::SnapshotManager() +{ + m_snapshots = NULL; + m_extra_data = NULL; + m_load_failed = false; + m_open_pending = false; + m_snapshots_len = 0; +} + +SnapshotManager::~SnapshotManager() +{ + g_free(m_snapshots); + g_free(m_extra_data); + xemu_snapshots_mark_dirty(); +} + +void SnapshotManager::Refresh() +{ + Error *err = NULL; + + if (!m_load_failed) { + m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err); + } + + if (err) { + m_load_failed = true; + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + m_snapshots_len = 0; + } +} + +void SnapshotManager::LoadSnapshotChecked(const char *name) +{ + Refresh(); + + XemuSnapshotData *data = NULL; + for (int i = 0; i < m_snapshots_len; i++) { + if (!strcmp(m_snapshots[i].name, name)) { + data = &m_extra_data[i]; + break; + } + } + + if (data == NULL) { + return; + } + + char *current_disc_path = xemu_get_currently_loaded_disc_path(); + if (data->disc_path && (!current_disc_path || strcmp(current_disc_path, data->disc_path))) { + if (current_disc_path) { + m_current_disc_path = current_disc_path; + } else { + m_current_disc_path.clear(); + } + m_target_disc_path = data->disc_path; + m_pending_load_name = name; + m_open_pending = true; + } else { + if (!data->disc_path) { + xemu_eject_disc(NULL); + } + LoadSnapshot(name); + } + + if (current_disc_path) { + g_free(current_disc_path); + } +} + +void SnapshotManager::LoadSnapshot(const char *name) +{ + Error *err = NULL; + + xemu_snapshots_load(name, &err); + + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } +} + +void SnapshotManager::Draw() +{ + DrawSnapshotDiscLoadDialog(); +} + +void SnapshotManager::DrawSnapshotDiscLoadDialog() +{ + if (m_open_pending) { + ImGui::OpenPopup("DVD Drive Image"); + m_open_pending = false; + } + + if (!ImGui::BeginPopupModal("DVD Drive Image", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + + ImGui::Text("The DVD drive disc image mounted when the snapshot was created does not appear to be loaded:"); + ImGui::Spacing(); + ImGui::Indent(); + ImGui::Text("Current Image: %s", m_current_disc_path.length() ? m_current_disc_path.c_str() : "(None)"); + ImGui::Text("Expected Image: %s", m_target_disc_path.length() ? m_target_disc_path.c_str() : "(None)"); + ImGui::Unindent(); + ImGui::Spacing(); + ImGui::Text("Would you like to load it now?"); + + ImGui::Dummy(ImVec2(0,16)); + + if (ImGui::Button("Yes", ImVec2(120, 0))) { + xemu_eject_disc(NULL); + + Error *err = NULL; + xemu_load_disc(m_target_disc_path.c_str(), &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } else { + LoadSnapshot(m_pending_load_name.c_str()); + } + + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("No", ImVec2(120, 0))) { + LoadSnapshot(m_pending_load_name.c_str()); + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); +} diff --git a/ui/xui/snapshot-manager.hh b/ui/xui/snapshot-manager.hh new file mode 100644 index 0000000000..6966d85f85 --- /dev/null +++ b/ui/xui/snapshot-manager.hh @@ -0,0 +1,47 @@ +// +// xemu User Interface +// +// Copyright (C) 2020-2022 Matt Borgerson +// +// 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. If not, see . +// + +#pragma once +#include +#include "../xemu-snapshots.h" + +class SnapshotManager +{ +public: + QEMUSnapshotInfo *m_snapshots; + XemuSnapshotData *m_extra_data; + bool m_load_failed; + bool m_open_pending; + int m_snapshots_len; + + std::string m_pending_load_name; + std::string m_current_disc_path; + std::string m_target_disc_path; + + SnapshotManager(); + ~SnapshotManager(); + void Refresh(); + void LoadSnapshot(const char *name); + void LoadSnapshotChecked(const char *name); + + void Draw(); + void DrawSnapshotDiscLoadDialog(); +}; + +extern SnapshotManager g_snapshot_mgr; diff --git a/ui/xui/xemu-hud.h b/ui/xui/xemu-hud.h index a510c85bf6..aa6e3d9acf 100644 --- a/ui/xui/xemu-hud.h +++ b/ui/xui/xemu-hud.h @@ -33,8 +33,8 @@ extern "C" { int xemu_is_fullscreen(void); void xemu_monitor_init(void); void xemu_toggle_fullscreen(void); -void xemu_eject_disc(void); -void xemu_load_disc(const char *path); +void xemu_eject_disc(Error **errp); +void xemu_load_disc(const char *path, Error **errp); // Implemented in xemu_hud.cc void xemu_hud_init(SDL_Window *window, void *sdl_gl_context);