diff --git a/config_handler.py b/config_handler.py index a01b03f..f1eb9c1 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, str] = {} + 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": False, "instance_statuses": {}, "display_names": {}} - return {"global_status": global_status, "instance_statuses": instance_statuses} + return {"global_status": global_status, "instance_statuses": instance_statuses, "display_names": display_names} 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" diff --git a/main.py b/main.py index 515afdc..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,15 +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() - self.instance_layout.setColumnStretch(0, 4) - self.instance_layout.setColumnStretch(1, 1) + 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.setHorizontalSpacing(15) - self.instance_group.setLayout(self.instance_layout) + self.instance_layout.setColumnStretch(3, 1) + self.instance_layout.setHorizontalSpacing(10) + + # 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) @@ -163,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...") @@ -251,6 +276,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.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 @@ -266,6 +292,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 +318,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, } @@ -318,11 +347,17 @@ 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("background-color: green; color: white; padding: 2px;") + else: + 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(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_root, 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):