|
| 1 | +import re |
| 2 | +from core.widgets.base import BaseWidget |
| 3 | +from core.validation.widgets.yasb.language import VALIDATION_SCHEMA |
| 4 | +from PyQt6.QtWidgets import QLabel, QHBoxLayout, QWidget |
| 5 | +from PyQt6.QtCore import Qt, QTimer |
| 6 | +import ctypes |
| 7 | +import ctypes |
| 8 | +from ctypes import wintypes |
| 9 | + |
| 10 | +# Constants |
| 11 | +LOCALE_NAME_MAX_LENGTH = 85 |
| 12 | +LOCALE_SISO639LANGNAME = 0x59 |
| 13 | +LOCALE_SISO3166CTRYNAME = 0x5A |
| 14 | +LOCALE_SLANGUAGE = 0x2 |
| 15 | +LOCALE_SCOUNTRY = 0x6 |
| 16 | + |
| 17 | +# Define necessary ctypes structures and functions |
| 18 | +user32 = ctypes.WinDLL('user32', use_last_error=True) |
| 19 | +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) |
| 20 | + |
| 21 | +class LanguageWidget(BaseWidget): |
| 22 | + validation_schema = VALIDATION_SCHEMA |
| 23 | + |
| 24 | + def __init__( |
| 25 | + self, |
| 26 | + label: str, |
| 27 | + label_alt: str, |
| 28 | + update_interval: int, |
| 29 | + callbacks: dict[str, str], |
| 30 | + ): |
| 31 | + super().__init__(int(update_interval * 1000), class_name="language-widget") |
| 32 | + |
| 33 | + self._show_alt_label = False |
| 34 | + self._label_content = label |
| 35 | + self._label_alt_content = label_alt |
| 36 | + |
| 37 | + # Construct container |
| 38 | + self._widget_container_layout: QHBoxLayout = QHBoxLayout() |
| 39 | + self._widget_container_layout.setSpacing(0) |
| 40 | + self._widget_container_layout.setContentsMargins(0, 0, 0, 0) |
| 41 | + # Initialize container |
| 42 | + self._widget_container: QWidget = QWidget() |
| 43 | + self._widget_container.setLayout(self._widget_container_layout) |
| 44 | + self._widget_container.setProperty("class", "widget-container") |
| 45 | + # Add the container to the main widget layout |
| 46 | + self.widget_layout.addWidget(self._widget_container) |
| 47 | + |
| 48 | + self._create_dynamically_label(self._label_content, self._label_alt_content) |
| 49 | + |
| 50 | + self.register_callback("toggle_label", self._toggle_label) |
| 51 | + self.register_callback("update_label", self._update_label) |
| 52 | + |
| 53 | + self.callback_left = callbacks["on_left"] |
| 54 | + self.callback_right = callbacks["on_right"] |
| 55 | + self.callback_middle = callbacks["on_middle"] |
| 56 | + self.callback_timer = "update_label" |
| 57 | + |
| 58 | + self.start_timer() |
| 59 | + |
| 60 | + def _toggle_label(self): |
| 61 | + self._show_alt_label = not self._show_alt_label |
| 62 | + for widget in self._widgets: |
| 63 | + widget.setVisible(not self._show_alt_label) |
| 64 | + for widget in self._widgets_alt: |
| 65 | + widget.setVisible(self._show_alt_label) |
| 66 | + self._update_label() |
| 67 | + |
| 68 | + |
| 69 | + def _create_dynamically_label(self, content: str, content_alt: str): |
| 70 | + def process_content(content, is_alt=False): |
| 71 | + label_parts = re.split('(<span.*?>.*?</span>)', content) #Filters out empty parts before entering the loop |
| 72 | + label_parts = [part for part in label_parts if part] |
| 73 | + widgets = [] |
| 74 | + for part in label_parts: |
| 75 | + part = part.strip() # Remove any leading/trailing whitespace |
| 76 | + if not part: |
| 77 | + continue |
| 78 | + if '<span' in part and '</span>' in part: |
| 79 | + class_name = re.search(r'class=(["\'])([^"\']+?)\1', part) |
| 80 | + class_result = class_name.group(2) if class_name else 'icon' |
| 81 | + icon = re.sub(r'<span.*?>|</span>', '', part).strip() |
| 82 | + label = QLabel(icon) |
| 83 | + label.setProperty("class", class_result) |
| 84 | + else: |
| 85 | + label = QLabel(part) |
| 86 | + label.setProperty("class", "label") |
| 87 | + label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| 88 | + self._widget_container_layout.addWidget(label) |
| 89 | + widgets.append(label) |
| 90 | + if is_alt: |
| 91 | + label.setProperty("class", "label alt") |
| 92 | + label.hide() |
| 93 | + else: |
| 94 | + label.show() |
| 95 | + return widgets |
| 96 | + self._widgets = process_content(content) |
| 97 | + self._widgets_alt = process_content(content_alt, is_alt=True) |
| 98 | + |
| 99 | + |
| 100 | + def _update_label(self): |
| 101 | + active_widgets = self._widgets_alt if self._show_alt_label else self._widgets |
| 102 | + active_label_content = self._label_alt_content if self._show_alt_label else self._label_content |
| 103 | + label_parts = re.split('(<span.*?>.*?</span>)', active_label_content) |
| 104 | + label_parts = [part for part in label_parts if part] |
| 105 | + widget_index = 0 |
| 106 | + try: |
| 107 | + lang = self._get_current_keyboard_language() |
| 108 | + except: |
| 109 | + lang = None |
| 110 | + |
| 111 | + for part in label_parts: |
| 112 | + part = part.strip() |
| 113 | + if part and widget_index < len(active_widgets) and isinstance(active_widgets[widget_index], QLabel): |
| 114 | + if '<span' in part and '</span>' in part: |
| 115 | + # Ensure the icon is correctly set |
| 116 | + icon = re.sub(r'<span.*?>|</span>', '', part).strip() |
| 117 | + active_widgets[widget_index].setText(icon) |
| 118 | + else: |
| 119 | + # Update label with formatted content |
| 120 | + formatted_text = part.format(lang=lang) if lang else part |
| 121 | + active_widgets[widget_index].setText(formatted_text) |
| 122 | + widget_index += 1 |
| 123 | + |
| 124 | + |
| 125 | + # Get the current keyboard layout |
| 126 | + def _get_current_keyboard_language(self): |
| 127 | + # Get the foreground window (the active window) |
| 128 | + hwnd = user32.GetForegroundWindow() |
| 129 | + # Get the thread id of the foreground window |
| 130 | + thread_id = user32.GetWindowThreadProcessId(hwnd, None) |
| 131 | + # Get the keyboard layout for the thread |
| 132 | + hkl = user32.GetKeyboardLayout(thread_id) |
| 133 | + # Get the language identifier from the HKL |
| 134 | + lang_id = hkl & 0xFFFF |
| 135 | + # Buffers for the language and country names |
| 136 | + lang_name = ctypes.create_unicode_buffer(LOCALE_NAME_MAX_LENGTH) |
| 137 | + country_name = ctypes.create_unicode_buffer(LOCALE_NAME_MAX_LENGTH) |
| 138 | + full_lang_name = ctypes.create_unicode_buffer(LOCALE_NAME_MAX_LENGTH) |
| 139 | + full_country_name = ctypes.create_unicode_buffer(LOCALE_NAME_MAX_LENGTH) |
| 140 | + # Get the ISO language name |
| 141 | + kernel32.GetLocaleInfoW(lang_id, LOCALE_SISO639LANGNAME, lang_name, LOCALE_NAME_MAX_LENGTH) |
| 142 | + # Get the ISO country name |
| 143 | + kernel32.GetLocaleInfoW(lang_id, LOCALE_SISO3166CTRYNAME, country_name, LOCALE_NAME_MAX_LENGTH) |
| 144 | + # Get the full language name |
| 145 | + kernel32.GetLocaleInfoW(lang_id, LOCALE_SLANGUAGE, full_lang_name, LOCALE_NAME_MAX_LENGTH) |
| 146 | + # Get the full country name |
| 147 | + kernel32.GetLocaleInfoW(lang_id, LOCALE_SCOUNTRY, full_country_name, LOCALE_NAME_MAX_LENGTH) |
| 148 | + |
| 149 | + short_code = f"{lang_name.value}-{country_name.value}" |
| 150 | + full_name = f"{full_lang_name.value}" |
| 151 | + return {'short': short_code, 'full': full_name} |
| 152 | + |
0 commit comments