From c1ea9e77016257aa14dcdb0714bb73f35547de41 Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:09:54 -0400 Subject: [PATCH 1/9] Adjust instance layout and display name handling Added another column within the Instances widget to show the BlueStacks instance display_name from the config file to go with the unique name. This makes it much easier to confirm which instance is referenced. --- main.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 515afdc..8d3c301 100644 --- a/main.py +++ b/main.py @@ -116,10 +116,11 @@ def init_ui(self) -> None: self.instance_group = QGroupBox("Instances") self.instance_layout = QGridLayout() - self.instance_layout.setColumnStretch(0, 4) - self.instance_layout.setColumnStretch(1, 1) + self.instance_layout.setColumnStretch(0, 3) + self.instance_layout.setColumnStretch(1, 4) self.instance_layout.setColumnStretch(2, 1) - self.instance_layout.setHorizontalSpacing(15) + self.instance_layout.setColumnStretch(3, 1) + self.instance_layout.setHorizontalSpacing(10) self.instance_group.setLayout(self.instance_layout) main_layout.addWidget(self.instance_group) @@ -251,6 +252,7 @@ def update_instance_data(self) -> None: patch_mode = inst.get("patch_mode", False) # 5.22.150.1014+ uses the patches root_info = config_handler.get_complete_root_statuses(config_path) instance_root_statuses = root_info['instance_statuses'] + display_names = root_info['display_names'] disk_instances = {entry for entry in (os.listdir(data_path) if os.path.isdir(data_path) else []) if os.path.isdir(os.path.join(data_path, entry))} all_instance_names = set(instance_root_statuses.keys()) | disk_instances @@ -266,6 +268,8 @@ def update_instance_data(self) -> None: elif is_readonly is False: rw_mode = constants.MODE_READWRITE individual_root_on = instance_root_statuses.get(name, False) + display_name = display_names.get(name, name) + if patch_mode: # 5.22.150.1014+: app root = the guest su binary actually patched # (tracked by the Data.vhdx sidecar). enable_root_access only makes @@ -290,6 +294,7 @@ def update_instance_data(self) -> None: "rw_mode": rw_mode, "root_enabled": effective_root_status, "individual_root_status": individual_root_on, + "display_name": display_name, "patch_mode": patch_mode, } @@ -321,8 +326,9 @@ def update_instance_checkboxes(self, preserve_selection: bool = True): rw_text = "On" if data["rw_mode"] == constants.MODE_READWRITE else "Off" self.instance_layout.addWidget(checkbox, row, 0) - self.instance_layout.addWidget(QLabel(f"Root: {root_text}"), row, 1) - self.instance_layout.addWidget(QLabel(f"R/W: {rw_text}"), row, 2) + self.instance_layout.addWidget(QLabel(data["display_name"]), row, 1) + self.instance_layout.addWidget(QLabel(f"Root: {root_text}"), row, 2) + self.instance_layout.addWidget(QLabel(f"R/W: {rw_text}"), row, 3) self.instance_checkboxes[unique_id] = {"checkbox": checkbox} def _toggle_single_instance_root(self, unique_id, progress=None): From a5e5f14a3cc90e8c6556e8cc5df5864166ea069c Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:19:10 -0400 Subject: [PATCH 2/9] Add DISPLAY_NAME_KEY constant to constants.py --- constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/constants.py b/constants.py index d2bf89a..14686db 100644 --- a/constants.py +++ b/constants.py @@ -5,6 +5,7 @@ INSTANCE_PREFIX = "bst.instance." ENABLE_ROOT_KEY = ".enable_root_access" +DISPLAY_NAME_KEY = ".display_name" FEATURE_ROOTING_KEY = "bst.feature.rooting" BLUESTACKS_CONF_FILENAME = "bluestacks.conf" From 00d65fe806442634a7567d07614e79458ca49927 Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:19:45 -0400 Subject: [PATCH 3/9] Include display names in config return values Added a new dictionary to return instance display names alongside global and instance statuses. --- config_handler.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/config_handler.py b/config_handler.py index a01b03f..ca276b2 100644 --- a/config_handler.py +++ b/config_handler.py @@ -94,6 +94,8 @@ def get_complete_root_statuses(config_path: str) -> dict[str, Any]: A dictionary like: {'global_status': bool, 'instance_statuses': {name: bool}} """ instance_statuses: dict[str, bool] = {} + display_names: dict[str, bool] = {} + global_status: bool = False if not os.path.isfile(config_path): @@ -111,6 +113,16 @@ def get_complete_root_statuses(config_path: str) -> dict[str, Any]: + r'\s*=\s*"([^"]*)"', re.IGNORECASE, ) + + instance_pattern_name = re.compile( + r"^" + + re.escape(constants.INSTANCE_PREFIX) + + r"([^.]+)" + + re.escape(constants.DISPLAY_NAME_KEY) + + r'\s*=\s*"([^"]*)"', + re.IGNORECASE, + ) + global_pattern = re.compile( r"^" + re.escape(constants.FEATURE_ROOTING_KEY) + r'\s*=\s*"1"', re.IGNORECASE ) @@ -131,8 +143,15 @@ def get_complete_root_statuses(config_path: str) -> dict[str, Any]: instance_name, value = match.group(1), match.group(2) is_enabled = value == "1" instance_statuses[instance_name] = is_enabled + + # Check for instance display name key + match_name = instance_pattern_name.match(stripped_line) + if match_name: + instance_name, value = match_name.group(1), match_name.group(2) + display_names[instance_name] = value + except Exception: logger.exception(f"Error reading config file {config_path} for root statuses.") return {"global_status": False, "instance_statuses": {}} - return {"global_status": global_status, "instance_statuses": instance_statuses} + return {"global_status": global_status, "instance_statuses": instance_statuses, "display_names": display_names} From ade6e376144dac92931957a3ef6ce95f89a296e9 Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:48:05 -0400 Subject: [PATCH 4/9] Add instance color highlighting if root is turned on --- main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 8d3c301..1ecf87f 100644 --- a/main.py +++ b/main.py @@ -323,11 +323,16 @@ def update_instance_checkboxes(self, preserve_selection: bool = True): checkbox.setChecked(unique_id in previous_selection) root_text = "On" if data["root_enabled"] else "Off" + Qlabel_root = QLabel(f"Root: {root_text}") + if root_text == "On": + Qlabel_root.setStyleSheet("""QLabel { background-color: green; color: white; padding: 2px;}""") + else: + Qlabel_root.setStyleSheet("""QLabel { padding: 2px;}""") rw_text = "On" if data["rw_mode"] == constants.MODE_READWRITE else "Off" self.instance_layout.addWidget(checkbox, row, 0) self.instance_layout.addWidget(QLabel(data["display_name"]), row, 1) - self.instance_layout.addWidget(QLabel(f"Root: {root_text}"), row, 2) + self.instance_layout.addWidget(Qlabel_root, row, 2) self.instance_layout.addWidget(QLabel(f"R/W: {rw_text}"), row, 3) self.instance_checkboxes[unique_id] = {"checkbox": checkbox} From e534ecb3c81e05d711db75a45e531088be6b4774 Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:20:59 -0400 Subject: [PATCH 5/9] Change display_names retrieval to use get method --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 1ecf87f..51cc668 100644 --- a/main.py +++ b/main.py @@ -252,7 +252,7 @@ def update_instance_data(self) -> None: patch_mode = inst.get("patch_mode", False) # 5.22.150.1014+ uses the patches root_info = config_handler.get_complete_root_statuses(config_path) instance_root_statuses = root_info['instance_statuses'] - display_names = root_info['display_names'] + display_names = root_info.get('display_names', {}) disk_instances = {entry for entry in (os.listdir(data_path) if os.path.isdir(data_path) else []) if os.path.isdir(os.path.join(data_path, entry))} all_instance_names = set(instance_root_statuses.keys()) | disk_instances From f899b0d21d2700ec583312aeb9c1c5df36c8adcb Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:21:23 -0400 Subject: [PATCH 6/9] Update display_names type in config_handler.py Changed display_names type from bool to str. --- config_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_handler.py b/config_handler.py index ca276b2..f3d0acc 100644 --- a/config_handler.py +++ b/config_handler.py @@ -94,7 +94,7 @@ def get_complete_root_statuses(config_path: str) -> dict[str, Any]: A dictionary like: {'global_status': bool, 'instance_statuses': {name: bool}} """ instance_statuses: dict[str, bool] = {} - display_names: dict[str, bool] = {} + display_names: dict[str, str] = {} global_status: bool = False From 72abfe8c9c38c897fc1e4e436bf2a583845c9c2f Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:28:28 -0400 Subject: [PATCH 7/9] Fix variable naming for QLabel instance --- main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 51cc668..d54e64b 100644 --- a/main.py +++ b/main.py @@ -323,16 +323,16 @@ def update_instance_checkboxes(self, preserve_selection: bool = True): checkbox.setChecked(unique_id in previous_selection) root_text = "On" if data["root_enabled"] else "Off" - Qlabel_root = QLabel(f"Root: {root_text}") + qlabel_root = QLabel(f"Root: {root_text}") if root_text == "On": - Qlabel_root.setStyleSheet("""QLabel { background-color: green; color: white; padding: 2px;}""") + qlabel_root.setStyleSheet("background-color: green; color: white; padding: 2px;") else: - Qlabel_root.setStyleSheet("""QLabel { padding: 2px;}""") + qlabel_root.setStyleSheet("padding: 2px;") rw_text = "On" if data["rw_mode"] == constants.MODE_READWRITE else "Off" self.instance_layout.addWidget(checkbox, row, 0) self.instance_layout.addWidget(QLabel(data["display_name"]), row, 1) - self.instance_layout.addWidget(Qlabel_root, row, 2) + self.instance_layout.addWidget(qlabel_root, row, 2) self.instance_layout.addWidget(QLabel(f"R/W: {rw_text}"), row, 3) self.instance_checkboxes[unique_id] = {"checkbox": checkbox} From dd2451e724cf4dc6d58aa0f0f6efff12f1b9ad5f Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:28:54 -0400 Subject: [PATCH 8/9] Include display_names in error return value Added 'display_names' to the returned dictionary in case of an error. --- config_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_handler.py b/config_handler.py index f3d0acc..f1eb9c1 100644 --- a/config_handler.py +++ b/config_handler.py @@ -152,6 +152,6 @@ def get_complete_root_statuses(config_path: str) -> dict[str, Any]: except Exception: logger.exception(f"Error reading config file {config_path} for root statuses.") - return {"global_status": False, "instance_statuses": {}} + return {"global_status": False, "instance_statuses": {}, "display_names": {}} return {"global_status": global_status, "instance_statuses": instance_statuses, "display_names": display_names} From 890f4a73c66371de639f39c58337ec49d3aa8621 Mon Sep 17 00:00:00 2001 From: Chris Borneman <75288580+Hobbes4Pres@users.noreply.github.com> Date: Fri, 17 Jul 2026 02:25:04 -0400 Subject: [PATCH 9/9] Add scroll area for instance management in UI --- main.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index d54e64b..e3188fe 100644 --- a/main.py +++ b/main.py @@ -19,6 +19,7 @@ QCheckBox, QMessageBox, QFileDialog, + QScrollArea, ) from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, pyqtSlot from PyQt5.QtGui import QIcon @@ -114,16 +115,38 @@ def init_ui(self) -> None: self.path_label.setWordWrap(True) main_layout.addWidget(self.path_label) + # Instances group box self.instance_group = QGroupBox("Instances") - self.instance_layout = QGridLayout() + instance_group_layout = QVBoxLayout(self.instance_group) + + # Scroll area inside the group box + self.instance_scroll_area = QScrollArea() + self.instance_scroll_area.setWidgetResizable(True) + self.instance_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.instance_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.instance_scroll_area.setMaximumHeight(1600) + #self.instance_scroll_area.setMinimumHeight(800) + #self.instance_scroll_area.setMinimumWidth(1000) + + # Widget that contains the QGridLayout + self.instance_container = QWidget() + self.instance_layout = QGridLayout(self.instance_container) + + # define our columns in the grid (instance ID, display name, Root, R/W) self.instance_layout.setColumnStretch(0, 3) self.instance_layout.setColumnStretch(1, 4) self.instance_layout.setColumnStretch(2, 1) self.instance_layout.setColumnStretch(3, 1) self.instance_layout.setHorizontalSpacing(10) - self.instance_group.setLayout(self.instance_layout) + + # Put the grid container inside the scroll area + self.instance_scroll_area.setWidget(self.instance_container) + + # Put the scroll area inside the group box + instance_group_layout.addWidget(self.instance_scroll_area) main_layout.addWidget(self.instance_group) + # Bottom buttons remain outside the scroll area button_layout = QHBoxLayout() self.root_toggle_button = QPushButton("Toggle Root") self.root_toggle_button.clicked.connect(self.handle_toggle_root) @@ -164,7 +187,8 @@ def init_ui(self) -> None: main_layout.addWidget(self.status_label) self.setLayout(main_layout) - self.setMinimumWidth(550) + self.setMinimumSize(650, 450) + self.resize(1000, 800) def initialize_paths_and_instances(self) -> None: logger.info("Initializing BlueStacks paths and instances...")