Skip to content

Commit 156eb0b

Browse files
committed
[CONFIG] Added KDE propertiesplugin integration
1 parent 75c0edc commit 156eb0b

7 files changed

Lines changed: 614 additions & 4 deletions

CMakeLists.txt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ option(BOX32_BINFMT "Also setup binfmt integration for box32" ${BOX32_BINFMT})
4949
option(LARCH64_ABI_1 "Set to ON if building for Loongarch64 ABI 1.0 system" ${LARCH64_ABI_1})
5050
option(GDBJIT "Set to ON to enable GDB JIT support" ${GDBJIT})
5151
option(WOW64 "Set to ON if you want a WOW64 PE build in addition")
52+
option(BOX64_KDE_PROFILE_PLUGIN "Build the Dolphin/KIO Box64 profile context menu plugin when KF6 is available" ON)
5253

5354
if(TERMUX)
5455
set(TERMUX_PATH "/data/data/com.termux/files")
@@ -1424,6 +1425,56 @@ if(NOT _x86 AND NOT _x86_64)
14241425
add_custom_target(box64-configurator ALL DEPENDS ${BOX64_CONFIGURATOR_OUTPUT})
14251426
install(TARGETS ${BOX64} RUNTIME DESTINATION bin)
14261427
install(PROGRAMS ${BOX64_CONFIGURATOR_OUTPUT} DESTINATION bin)
1428+
install(FILES ${CMAKE_SOURCE_DIR}/system/box64-configurator.desktop DESTINATION share/applications)
1429+
if(BOX64_KDE_PROFILE_PLUGIN)
1430+
include(CheckLanguage)
1431+
check_language(CXX)
1432+
if(CMAKE_CXX_COMPILER)
1433+
enable_language(CXX)
1434+
set(QT_DEFAULT_MAJOR_VERSION 6)
1435+
find_package(ECM QUIET NO_MODULE)
1436+
if(ECM_FOUND)
1437+
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
1438+
include(KDEInstallDirs6 OPTIONAL)
1439+
find_package(Qt6 QUIET COMPONENTS Core Widgets)
1440+
find_package(KF6CoreAddons QUIET)
1441+
find_package(KF6KIO QUIET)
1442+
if(Qt6_FOUND AND Qt6Widgets_FOUND AND KF6CoreAddons_FOUND AND KF6KIO_FOUND)
1443+
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
1444+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
1445+
endif()
1446+
if(DEFINED QT6_INSTALL_PREFIX AND DEFINED QT6_INSTALL_PLUGINS
1447+
AND NOT QT6_INSTALL_PREFIX STREQUAL "" AND NOT QT6_INSTALL_PLUGINS STREQUAL "")
1448+
set(KDE_INSTALL_PLUGINDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}")
1449+
elseif(DEFINED QT6_INSTALL_PLUGINS AND NOT QT6_INSTALL_PLUGINS STREQUAL "")
1450+
set(KDE_INSTALL_PLUGINDIR "${QT6_INSTALL_PLUGINS}")
1451+
elseif(NOT DEFINED KDE_INSTALL_PLUGINDIR OR KDE_INSTALL_PLUGINDIR STREQUAL "")
1452+
set(KDE_INSTALL_PLUGINDIR "lib/qt6/plugins")
1453+
endif()
1454+
set(_BOX64_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
1455+
set(BUILD_SHARED_LIBS ON)
1456+
kcoreaddons_add_plugin(box64_profile_propertiesplugin
1457+
SOURCES ${CMAKE_SOURCE_DIR}/system/kde/box64_profile_propertiesplugin.cpp
1458+
INSTALL_NAMESPACE "kf6/propertiesdialog")
1459+
set(BUILD_SHARED_LIBS ${_BOX64_BUILD_SHARED_LIBS})
1460+
target_link_libraries(box64_profile_propertiesplugin PRIVATE KF6::KIOWidgets Qt6::Widgets)
1461+
set_target_properties(box64_profile_propertiesplugin PROPERTIES
1462+
AUTOMOC ON
1463+
PREFIX ""
1464+
CXX_STANDARD 17
1465+
CXX_STANDARD_REQUIRED ON
1466+
CXX_EXTENSIONS OFF)
1467+
message(STATUS "Building Box64 profile properties dialog plugin")
1468+
else()
1469+
message(STATUS "Dolphin/KIO Box64 profile plugin disabled: missing Qt6/KF6 KIO development packages")
1470+
endif()
1471+
else()
1472+
message(STATUS "Dolphin/KIO Box64 profile plugin disabled: ECM not found")
1473+
endif()
1474+
else()
1475+
message(STATUS "Dolphin/KIO Box64 profile plugin disabled: C++ compiler not found")
1476+
endif()
1477+
endif()
14271478
if(NOT NO_LIB_INSTALL)
14281479
install(PROGRAMS ${CMAKE_SOURCE_DIR}/tests/box64-bash DESTINATION bin)
14291480
install(PROGRAMS ${CMAKE_SOURCE_DIR}/system/box64-python DESTINATION bin)

configurator/configurator.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@
2020
from gi.repository import Gio, Gdk, GLib, Gtk, Pango
2121

2222
try:
23+
from .box64rc import SectionKey
2324
from .model import ConfigStore, EntryRecord, ValueState, box64_arch_from_machine
2425
from .usage import EnvOption, load_default_usage_catalog, load_usage_catalog
2526
except ImportError:
27+
from box64rc import SectionKey
2628
from model import ConfigStore, EntryRecord, ValueState, box64_arch_from_machine
2729
from usage import EnvOption, load_default_usage_catalog, load_usage_catalog
2830

2931

3032
APP_ID = "org.box64.Configurator"
33+
PROFILE_CHOICES = ("default", "safest", "safe", "fast", "fastest")
34+
PROFILE_OPTION_NAME = "BOX64_PROFILE"
3135

3236

3337
_EMBEDDED_TEXTS: Optional[Dict[str, Dict[str, str]]] = globals().get("_EMBEDDED_TEXTS")
@@ -248,6 +252,66 @@ def dropped_executable_entry(path: Path) -> str:
248252
return resolved.name
249253

250254

255+
def profile_menu_entry(path: Path) -> str:
256+
resolved = path.expanduser().resolve(strict=True)
257+
if not resolved.is_file():
258+
raise ValueError(f"Not a file: {path}")
259+
name = resolved.name
260+
if not name or any(char in name for char in "\r\n[]#"):
261+
raise ValueError(f"Invalid executable name for rcfile section: {name}")
262+
return name
263+
264+
265+
def entry_for_profile_menu(store: ConfigStore, name: str) -> Optional[EntryRecord]:
266+
normalized = name.lower()
267+
matches = [
268+
entry
269+
for entry in store.entries()
270+
if entry.key.kind == "exact" and entry.key.name == normalized
271+
]
272+
return next((entry for entry in matches if not entry.key.arch), matches[0] if matches else None)
273+
274+
275+
def get_profile_from_menu(store: ConfigStore, executable: Path) -> str:
276+
name = profile_menu_entry(executable)
277+
entry = entry_for_profile_menu(store, name)
278+
if entry is None:
279+
entry = EntryRecord(SectionKey("exact", name.lower()), name, None, None)
280+
281+
active, _often_used, _defaults = store.effective_values(entry)
282+
profile = next(
283+
(state.value.strip().lower() for state in active if state.option.name == PROFILE_OPTION_NAME),
284+
"default",
285+
)
286+
return profile if profile in PROFILE_CHOICES else "default"
287+
288+
289+
def profile_only_user_entry(entry: EntryRecord) -> bool:
290+
return (
291+
entry.user_section is not None
292+
and entry.system_section is None
293+
and all(assignment.key == PROFILE_OPTION_NAME for assignment in entry.user_section.assignments)
294+
)
295+
296+
297+
def set_profile_from_menu(store: ConfigStore, executable: Path, profile: str) -> None:
298+
name = profile_menu_entry(executable)
299+
entry = entry_for_profile_menu(store, name)
300+
if entry is None:
301+
if profile == "default":
302+
return
303+
new_key = store.create_entry(name)
304+
entry = store.entry_for_key(new_key)
305+
if entry is None:
306+
raise KeyError(new_key)
307+
308+
remove_profile_only_section = profile == "default" and profile_only_user_entry(entry)
309+
updated_key, _forked = store.set_option(entry, PROFILE_OPTION_NAME, profile)
310+
if remove_profile_only_section:
311+
store.delete_user_entry(updated_key)
312+
store.save()
313+
314+
251315
class EntryRow(Gtk.ListBoxRow):
252316
def __init__(self, entry: EntryRecord, language: str) -> None:
253317
super().__init__()
@@ -576,6 +640,25 @@ def _on_drag_data_received(
576640
elif errors:
577641
self._show_error(ValueError("\n".join(errors)))
578642

643+
def open_executable(self, path: Path) -> None:
644+
try:
645+
name = dropped_executable_entry(path)
646+
except Exception as error:
647+
self._show_error(error)
648+
return
649+
650+
try:
651+
new_key = self.store.create_entry(name)
652+
except Exception as error:
653+
self._show_error(error)
654+
return
655+
656+
self.selected_option_name = None
657+
self.current_key = new_key
658+
self._refresh_view(new_key)
659+
if self.store.dirty:
660+
self._push_status(tr(self.language, "dirty"))
661+
579662
def _on_entry_list_button_press(self, _listbox: Gtk.ListBox, event) -> bool:
580663
if event.button != 3:
581664
return False
@@ -1217,11 +1300,15 @@ def __init__(self, store: ConfigStore) -> None:
12171300
super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
12181301
self.store = store
12191302
self.window: Optional[ConfiguratorWindow] = None
1303+
self.open_executable: Optional[Path] = None
12201304

12211305
def do_activate(self) -> None:
12221306
if self.window is None:
12231307
self.window = ConfiguratorWindow(self, self.store)
12241308
self.window.present()
1309+
if self.open_executable is not None:
1310+
self.window.open_executable(self.open_executable)
1311+
self.open_executable = None
12251312

12261313

12271314
def parse_args(argv: Optional[Sequence[str]]) -> argparse.Namespace:
@@ -1230,7 +1317,16 @@ def parse_args(argv: Optional[Sequence[str]]) -> argparse.Namespace:
12301317
parser.add_argument("--usage-cn", type=Path)
12311318
parser.add_argument("--system-rc", type=Path, default=default_system_rc())
12321319
parser.add_argument("--user-rc", type=Path, default=default_user_rc())
1233-
return parser.parse_args(argv)
1320+
parser.add_argument("--set-profile", choices=PROFILE_CHOICES)
1321+
parser.add_argument("--get-profile", action="store_true")
1322+
parser.add_argument("executable", nargs="?", type=Path)
1323+
args = parser.parse_args(argv)
1324+
profile_mode_count = int(args.set_profile is not None) + int(args.get_profile)
1325+
if profile_mode_count > 1:
1326+
parser.error("--set-profile and --get-profile cannot be used together")
1327+
if profile_mode_count > 0 and args.executable is None:
1328+
parser.error("--set-profile and --get-profile require an executable")
1329+
return args
12341330

12351331

12361332
def main(argv: Optional[Sequence[str]] = None) -> int:
@@ -1242,7 +1338,15 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
12421338
else:
12431339
catalog = load_default_usage_catalog()
12441340
store = ConfigStore(args.system_rc, args.user_rc, catalog, box64_arch_from_machine(platform.machine()))
1341+
if args.set_profile is not None:
1342+
set_profile_from_menu(store, args.executable, args.set_profile)
1343+
return 0
1344+
if args.get_profile:
1345+
print(get_profile_from_menu(store, args.executable))
1346+
return 0
12451347
app = ConfiguratorApplication(store)
1348+
if args.executable is not None:
1349+
app.open_executable = args.executable
12461350
return app.run([sys.argv[0]])
12471351

12481352

docs/gen/usage_cn.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -647,10 +647,10 @@
647647
"BOX64_PROFILE": {
648648
"description": "预定义的环境变量集合,以兼容性或性能为导向。",
649649
"options": {
650-
"safest": "禁用所有不安全的 DynaRec 优化的配置文件",
651-
"safe": "比 safest 略不安全",
650+
"safest": "禁用所有不安全的优化",
651+
"safe": "仅启用小部分不安全的优化",
652652
"default": "大多数程序都能正常运行且性能适中的默认设置。",
653-
"fast": "启用许多不安全的优化,但同时启用 强内存模型 模拟",
653+
"fast": "启用许多不安全的优化,但同时启用强内存模型模拟",
654654
"fastest": "启用许多不安全的优化以获得更好的性能。"
655655
}
656656
},

system/box64-configurator.desktop

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Desktop Entry]
2+
Type=Application
3+
Name=Box64 Configurator
4+
Comment=Configure Box64 rcfile entries
5+
Exec=box64-configurator
6+
Icon=application-x-executable
7+
Terminal=false
8+
Categories=Settings;

0 commit comments

Comments
 (0)