diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e0a4b12..3bc99945 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: run: | mkdir build cd build - cmake .. -DENABLE_IBUS=ON -DENABLE_FCITX=ON + cmake .. -DENABLE_BOTH=ON make build-macos: @@ -44,7 +44,7 @@ jobs: submodules: recursive - name: install-deps - run: brew install cmake qt@5 zstd + run: brew install cmake qt@5 zstd ninja - name: setup-qt run: echo "$(brew --prefix qt@5)/bin" >> $GITHUB_PATH @@ -53,6 +53,6 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix zstd)" - make + cmake .. -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix zstd)" -GNinja + ninja diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 76b49c5c..28e6b704 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,23 +11,17 @@ jobs: strategy: matrix: # container: [ "ubuntu:18.04", "ubuntu:20.04", "ubuntu:22.04", "ubuntu:23.04", "debian:11", "fedora:37", "fedora:38", "archlinux:latest" ] - container: [ "ubuntu:20.04", "ubuntu:22.04", "ubuntu:24.04", "debian:11", "debian:12", "fedora:40", "fedora:41", "opensuse/tumbleweed" ] + container: [ "ubuntu:22.04", "ubuntu:24.04", "debian:11", "debian:12", "fedora:40", "fedora:41", "opensuse/tumbleweed" ] # this list should be updated from time to time by consulting these pages: # https://releases.ubuntu.com/ # https://wiki.debian.org/DebianReleases#Production_Releases # https://fedoraproject.org/wiki/Releases # https://en.wikipedia.org/wiki/OpenSUSE#Version_history - ime: [ "ibus", "fcitx" ] - # Some distributions doesn't have the required version of fcitx library, so we exclude them. - exclude: - - container: "ubuntu:20.04" - ime: "fcitx" runs-on: "ubuntu-latest" container: image: ${{ matrix.container }} env: DIST: ${{ matrix.container }} - IME: ${{ matrix.ime }} DEBIAN_FRONTEND: noninteractive steps: - name: install-git @@ -64,7 +58,7 @@ jobs: - name: upload-artifacts uses: actions/upload-artifact@v4 with: - name: pkg-${{ steps.sanitizer.outputs.sanitized_container }}-${{ matrix.ime }} + name: pkg-${{ steps.sanitizer.outputs.sanitized_container }} path: artifact release: diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index beac2882..2cea3c9f 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -8,6 +8,7 @@ "${workspaceRoot}" ], "defines": [], + "compileCommands": "build/compile_commands.json", "intelliSenseMode": "clang-x64", "browse": { "path": [ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d3419de..eab316d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.18...3.29) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" VERSION_STRING LIMIT_COUNT 1 REGEX "^.+") @@ -17,6 +17,20 @@ if(NOT WIN32 AND NOT APPLE) include(CMakeCargo) endif() +if(APPLE) + enable_language(Swift) + + if("${CMAKE_Swift_COMPILER_VERSION}" VERSION_LESS 5.9) + message(FATAL_ERROR "Bidirectional C++ Interop requires Swift 5.9 or greater. Have ${CMAKE_Swift_COMPILER_VERSION}") + endif() + + # Set up swiftrt.o and runtime library search paths + include(InitializeSwift) + # cmake/modules/AddSwift.cmake provides the function for creating the Swift to + # C++ bridging header + include(AddSwift) +endif() + find_package(Qt5 COMPONENTS Widgets Network REQUIRED) message(STATUS "Qt5 framework version: ${Qt5Core_VERSION}") @@ -66,10 +80,14 @@ if(OS_LINUX) option(ENABLE_IBUS "Enable IBus support" OFF) option(ENABLE_FCITX "Enable Fcitx support" OFF) + option(ENABLE_BOTH "Enable both IBus and Fcitx support" OFF) - if((NOT ENABLE_IBUS) AND (NOT ENABLE_FCITX)) - message(FATAL_ERROR "Please set atleast ENABLE_IBUS or ENABLE_FCITX to be ON to build a backend") + if((NOT ENABLE_IBUS) AND (NOT ENABLE_FCITX) AND (NOT ENABLE_BOTH)) + message(FATAL_ERROR "Please set atleast ENABLE_IBUS or ENABLE_FCITX or ENABLE_BOTH to be ON to build a backend") endif() + + ## Find zstd ## + pkg_check_modules(ZSTD REQUIRED libzstd) ## Find iBus ## if(ENABLE_IBUS) @@ -81,13 +99,16 @@ if(OS_LINUX) find_package(Fcitx5Core 5.0.5 REQUIRED) endif() - ## Find zstd ## - pkg_check_modules(ZSTD REQUIRED libzstd) + if(ENABLE_BOTH) + pkg_check_modules(IBUS REQUIRED ibus-1.0) + find_package(Fcitx5Core 5.0.5 REQUIRED) + endif() endif() # For Windows if(WIN32) find_package(zstd CONFIG REQUIRED) + set(PROJECT_DATADIR "$ENV{LOCALAPPDATA}\\OpenBangla" CACHE PATH "Path to Data Directory") set(BIN_DIR "${PROJECT_DATADIR}/bin") add_definitions(-DPROJECT_DATADIR="${PROJECT_DATADIR}") @@ -100,6 +121,14 @@ if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) set(CMAKE_INSTALL_PREFIX "/") + + # Determine host architectures for the .pkg installer distribution. + # Without hostArchitectures, macOS assumes x86_64 and prompts for Rosetta. + if(CMAKE_OSX_ARCHITECTURES) + string(REPLACE ";" "," CPACK_HOST_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}") + else() + set(CPACK_HOST_ARCHITECTURES "${CMAKE_SYSTEM_PROCESSOR}") + endif() endif() add_subdirectory(src/engine) @@ -124,16 +153,21 @@ set(CPACK_DEBIAN_PACKAGE_MAINTAINER "OpenBangla Team " set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/data/Readme.txt") set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/data/Readme.txt") if(APPLE) + set(CPACK_PACKAGE_NAME "OpenBangla Keyboard") # macOS productbuild requires license files with .txt, .rtf, .rtfd, or .html extension configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${CMAKE_BINARY_DIR}/LICENSE.txt" COPYONLY) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/LICENSE.txt") else() + set(CPACK_PACKAGE_NAME "openbangla-keyboard") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") + + # Stripping on macOS invalidates ad-hoc code signatures, causing + # installed apps to be killed by the system. + set(CPACK_STRIP_FILES TRUE) endif() set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") -set(CPACK_STRIP_FILES TRUE) set(CPACK_PRODUCTBUILD_IDENTITY_NAME "" CACHE STRING "Code signing identity for productbuild") set(CPACK_PRODUCTBUILD_KEYCHAIN_PATH "" CACHE STRING "Keychain path for code signing") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://openbangla.org/") @@ -142,6 +176,9 @@ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") set(CPACK_RPM_PACKAGE_URL "https://openbangla.org/") set(CPACK_RPM_PACKAGE_RELEASE_DIST ON) +configure_file("${CMAKE_SOURCE_DIR}/data/linux/postinst.in" "${CMAKE_BINARY_DIR}/postinst" @ONLY) +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst") +set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_BINARY_DIR}/postinst") # Prevents CPack from generating file conflicts set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/applications" "/usr/share/metainfo" "/usr/share/pixmaps" "/usr/share/icons" "/usr/share/icons/hicolor" @@ -155,18 +192,24 @@ set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${PROJECT_VERSION}-$ENV{DIST} # Configure CPack on the basis of which backend we are building for. if(ENABLE_IBUS) ## IBUS - set(CPACK_PACKAGE_NAME "ibus-openbangla") + # set(CPACK_PACKAGE_NAME "ibus-openbangla") set(CPACK_DEBIAN_PACKAGE_DEPENDS "ibus (>= 1.5.1)") set(CPACK_RPM_PACKAGE_REQUIRES "qt5-qtbase >= 5.9.0, ibus >= 1.5.1, ibus-libs >= 1.5.1, libzstd >= 1.3.3") list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/ibus" "/usr/share/ibus/component") elseif(ENABLE_FCITX) ## FCITX - set(CPACK_PACKAGE_NAME "fcitx-openbangla") + # set(CPACK_PACKAGE_NAME "fcitx-openbangla") set(CPACK_DEBIAN_PACKAGE_DEPENDS "fcitx5 (>= 5.0.5)") set(CPACK_RPM_PACKAGE_REQUIRES "qt5-qtbase >= 5.9.0, libzstd >= 1.3.3, fcitx5 >= 5.0.5") list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/lib64/fcitx5" "/usr/share/fcitx5" "/usr/share/fcitx5/inputmethod" "/usr/share/fcitx5/addon") +elseif(ENABLE_BOTH) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "fcitx5 (>= 5.0.5) | ibus (>= 1.5.1)") + set(CPACK_RPM_PACKAGE_REQUIRES + "(ibus >= 1.5.1 if gnome-shell else fcitx5 >= 5.0.5), (gnome-shell-extension-appindicator if gnome-shell), qt5-qtbase >= 5.15.0, libzstd >= 1.3.3" + ) + list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/ibus" "/usr/share/ibus/component" "/usr/lib64/fcitx5" + "/usr/share/fcitx5" "/usr/share/fcitx5/inputmethod" "/usr/share/fcitx5/addon") elseif(APPLE) - set(CPACK_PACKAGE_NAME "OpenBangla Keyboard") set(CPACK_PACKAGE_VENDOR "OpenBangla") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_FILE_NAME "OpenBangla-Keyboard-${PROJECT_VERSION}") @@ -175,7 +218,9 @@ elseif(APPLE) set(CPACK_COMPONENTS_ALL IME GUI) set(CPACK_COMPONENT_IME_REQUIRED TRUE) set(CPACK_COMPONENT_GUI_REQUIRED TRUE) + set(CPACK_COMPONENT_GUI_DEPENDS IME) set(CPACK_COMPONENT_UNSPECIFIED_REQUIRED "FALSE") + set(CPACK_POSTFLIGHT_GUI_SCRIPT "${CMAKE_SOURCE_DIR}/data/productbuild/postinstall") set(CPACK_PRODUCTBUILD_RESOURCES_DIR "${CMAKE_SOURCE_DIR}/data/productbuild") set(CPACK_PRODUCTBUILD_BACKGROUND "installer.png") set(CPACK_PRODUCTBUILD_BACKGROUND_ALIGNMENT "left") diff --git a/cmake/AddSwift.cmake b/cmake/AddSwift.cmake new file mode 100644 index 00000000..099273fd --- /dev/null +++ b/cmake/AddSwift.cmake @@ -0,0 +1,78 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2023 Apple Inc. and the Swift project authors. +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See https://swift.org/LICENSE.txt for license information + + +# Generate the bridging header from Swift to C++ +# +# target: the name of the target to generate headers for. +# This target must build swift source files. +# header: the name of the header file to generate. +# +# NOTE: This logic will eventually be unstreamed into CMake. +function(_swift_generate_cxx_header target header) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Target ${target} not defined.") + endif() + + if(NOT DEFINED CMAKE_Swift_COMPILER) + message(WARNING "Swift not enabled in project. Cannot generate headers for Swift files.") + return() + endif() + + cmake_parse_arguments(ARG "" "" "SEARCH_PATHS;MODULE_NAME" ${ARGN}) + + if(NOT ARG_MODULE_NAME) + set(target_module_name $) + set(ARG_MODULE_NAME $,${target_module_name},${target}>) + endif() + + if(ARG_SEARCH_PATHS) + list(TRANSFORM ARG_SEARCH_PATHS PREPEND "-I") + endif() + + if(APPLE AND CMAKE_OSX_SYSROOT) + set(SDK_FLAGS "-sdk" "${CMAKE_OSX_SYSROOT}") + elseif(WIN32) + set(SDK_FLAGS "-sdk" "$ENV{SDKROOT}") + elseif(CMAKE_SYSROOT) + set(SDK_FLAGS "-sdk" "${CMAKE_SYSROOT}") + endif() + + cmake_path(APPEND CMAKE_CURRENT_BINARY_DIR include + OUTPUT_VARIABLE base_path) + + cmake_path(APPEND base_path ${header} + OUTPUT_VARIABLE header_path) + + cmake_path(APPEND CMAKE_CURRENT_BINARY_DIR "${ARG_MODULE_NAME}.emit-module.d" OUTPUT_VARIABLE depfile_path) + + set(_AllSources $,${CMAKE_CURRENT_SOURCE_DIR}>) + set(_SwiftSources $) + add_custom_command(OUTPUT ${header_path} + DEPENDS ${_SwiftSources} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND + ${CMAKE_Swift_COMPILER} -typecheck + ${ARG_SEARCH_PATHS} + ${_SwiftSources} + ${SDK_FLAGS} + -module-name "${ARG_MODULE_NAME}" + -cxx-interoperability-mode=default + -emit-clang-header-path ${header_path} + -emit-dependencies + DEPFILE "${depfile_path}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT + "Generating '${header_path}'" + COMMAND_EXPAND_LISTS) + + # Added to public interface for dependees to find. + target_include_directories(${target} PUBLIC ${base_path}) + # Added to the target to ensure target rebuilds if header changes and is used + # by sources in the target. + target_sources(${target} PRIVATE ${header_path}) +endfunction() diff --git a/cmake/CPack.distribution.dist.in b/cmake/CPack.distribution.dist.in new file mode 100644 index 00000000..c14d89ef --- /dev/null +++ b/cmake/CPack.distribution.dist.in @@ -0,0 +1,24 @@ + + + @CPACK_PACKAGE_NAME@ + + + + + + + + + + + + + + file:./Contents/Packages/@CPACK_PACKAGE_FILE_NAME@-GUI.pkg + + + + file:./Contents/Packages/@CPACK_PACKAGE_FILE_NAME@-IME.pkg + + + diff --git a/cmake/InitializeSwift.cmake b/cmake/InitializeSwift.cmake new file mode 100644 index 00000000..c4fa2ea2 --- /dev/null +++ b/cmake/InitializeSwift.cmake @@ -0,0 +1,89 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2023 Apple Inc. and the Swift project authors. +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See https://swift.org/LICENSE.txt for license information + +# Compute the name of the architecture directory on Windows from the CMake +# system processor name. +function(_swift_windows_arch_name output_variable_name target_arch) + if(NOT WIN32) + return() + endif() + + if("${target_arch}" STREQUAL "AMD64") + set("${output_variable_name}" "x86_64" PARENT_SCOPE) + elseif("${target_arch}" STREQUAL "ARM64") + set("${output_variable_name}" "aarch64" PARENT_SCOPE) + else() + message(FATAL_ERROR "Unknown windows architecture: ${target_arch}") + endif() +endfunction() + +# Compute flags and search paths +# NOTE: This logic will eventually move to CMake +function(_setup_swift_paths) + # If we haven't set the swift library search paths, do that now + if(NOT SWIFT_LIBRARY_SEARCH_PATHS) + if(CMAKE_OSX_SYSROOT) + set(SDK_FLAGS "-sdk" "${CMAKE_OSX_SYSROOT}") + endif() + + # Note: This does not handle cross-compiling correctly. + # To handle it correctly, we would need to pass the target triple and + # flags to this compiler invocation. + execute_process( + COMMAND ${CMAKE_Swift_COMPILER} ${SDK_FLAGS} -print-target-info + OUTPUT_VARIABLE SWIFT_TARGET_INFO + ) + + # extract search paths from swift driver response + string(JSON SWIFT_TARGET_PATHS GET ${SWIFT_TARGET_INFO} "paths") + + string(JSON SWIFT_TARGET_LIBRARY_PATHS GET ${SWIFT_TARGET_PATHS} "runtimeLibraryPaths") + string(JSON SWIFT_TARGET_LIBRARY_PATHS_LENGTH LENGTH ${SWIFT_TARGET_LIBRARY_PATHS}) + math(EXPR SWIFT_TARGET_LIBRARY_PATHS_LENGTH "${SWIFT_TARGET_LIBRARY_PATHS_LENGTH} - 1 ") + + string(JSON SWIFT_TARGET_LIBRARY_IMPORT_PATHS GET ${SWIFT_TARGET_PATHS} "runtimeLibraryImportPaths") + string(JSON SWIFT_TARGET_LIBRARY_IMPORT_PATHS_LENGTH LENGTH ${SWIFT_TARGET_LIBRARY_IMPORT_PATHS}) + math(EXPR SWIFT_TARGET_LIBRARY_IMPORT_PATHS_LENGTH "${SWIFT_TARGET_LIBRARY_IMPORT_PATHS_LENGTH} - 1 ") + + string(JSON SWIFT_SDK_IMPORT_PATH ERROR_VARIABLE errno GET ${SWIFT_TARGET_PATHS} "sdkPath") + + foreach(JSON_ARG_IDX RANGE ${SWIFT_TARGET_LIBRARY_PATHS_LENGTH}) + string(JSON SWIFT_LIB GET ${SWIFT_TARGET_LIBRARY_PATHS} ${JSON_ARG_IDX}) + list(APPEND SWIFT_SEARCH_PATHS ${SWIFT_LIB}) + endforeach() + + foreach(JSON_ARG_IDX RANGE ${SWIFT_TARGET_LIBRARY_IMPORT_PATHS_LENGTH}) + string(JSON SWIFT_LIB GET ${SWIFT_TARGET_LIBRARY_IMPORT_PATHS} ${JSON_ARG_IDX}) + list(APPEND SWIFT_SEARCH_PATHS ${SWIFT_LIB}) + endforeach() + + if(SWIFT_SDK_IMPORT_PATH) + list(APPEND SWIFT_SEARCH_PATHS ${SWIFT_SDK_IMPORT_PATH}) + endif() + + # Save the swift library search paths + set(SWIFT_LIBRARY_SEARCH_PATHS ${SWIFT_SEARCH_PATHS} CACHE FILEPATH "Swift driver search paths") + endif() + + link_directories(${SWIFT_LIBRARY_SEARCH_PATHS}) + + if(WIN32) + _swift_windows_arch_name(SWIFT_WIN_ARCH_DIR "${CMAKE_SYSTEM_PROCESSOR}") + set(SWIFT_SWIFTRT_FILE "$ENV{SDKROOT}/usr/lib/swift/windows/${SWIFT_WIN_ARCH_DIR}/swiftrt.obj") + add_link_options("$<$:${SWIFT_SWIFTRT_FILE}>") + elseif(NOT APPLE) + find_file(SWIFT_SWIFTRT_FILE + swiftrt.o + PATHS ${SWIFT_LIBRARY_SEARCH_PATHS} + NO_CACHE + REQUIRED + NO_DEFAULT_PATH) + add_link_options("$<$:${SWIFT_SWIFTRT_FILE}>") + endif() +endfunction() + +_setup_swift_paths() diff --git a/data/linux/postinst.in b/data/linux/postinst.in new file mode 100644 index 00000000..f4266589 --- /dev/null +++ b/data/linux/postinst.in @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +@BIN_DIR@/openbangla-gui --setup-system + +exit 0 diff --git a/data/productbuild/postinstall b/data/productbuild/postinstall new file mode 100755 index 00000000..b3c3162c --- /dev/null +++ b/data/productbuild/postinstall @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +/Applications/OpenBangla\ Keyboard.app/Contents/MacOS/OpenBangla\ Keyboard --setup-system + +exit 0 \ No newline at end of file diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index e8de981b..78a0f5d4 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -12,6 +12,11 @@ if (OS_LINUX) add_subdirectory(fcitx) endif() + if(ENABLE_BOTH) + add_subdirectory(ibus) + add_subdirectory(fcitx) + endif() + ## Include subdirectories add_subdirectory(riti) endif (OS_LINUX) diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index b919f7e5..f2686485 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -1,4 +1,4 @@ -include_directories(../shared 3rdParty) +include_directories(../shared 3rdParty macOS) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) @@ -18,7 +18,8 @@ set(SRC_MAIN main.cpp LayoutConverter.cpp LayoutConverter.h SingleInstance.cpp - AvroPhonetic.cpp) + AvroPhonetic.cpp + PlatformConfig.cpp) set(LINK_LIBS libShared Qt5::Widgets Qt5::Network) @@ -27,13 +28,15 @@ if(OS_LINUX) endif(OS_LINUX) if(WIN32) - list(APPEND LINK_LIBS zstd::libzstd) set(CMAKE_WIN32_EXECUTABLE ON) + + list(APPEND LINK_LIBS zstd::libzstd) endif(WIN32) if(APPLE) - list(APPEND LINK_LIBS zstd::libzstd) - list(APPEND LINK_LIBS "-framework Foundation") + add_subdirectory(macOS) + + list(APPEND LINK_LIBS macOS "-framework Foundation" zstd::libzstd) endif(APPLE) if(APPLE) @@ -121,8 +124,12 @@ if(OS_LINUX) endif() if(APPLE) - install(TARGETS openbangla-gui - BUNDLE DESTINATION "Applications" + # Install the bundle as a directory rather than a target to prevent + # install_name_tool from modifying the binary and invalidating the + # code signature applied after macdeployqt. + install(DIRECTORY "$" + DESTINATION "Applications" + USE_SOURCE_PERMISSIONS COMPONENT GUI ) endif() diff --git a/src/frontend/PlatformConfig.cpp b/src/frontend/PlatformConfig.cpp new file mode 100644 index 00000000..b1549fc5 --- /dev/null +++ b/src/frontend/PlatformConfig.cpp @@ -0,0 +1,559 @@ +/* + * OpenBangla Keyboard + * Copyright (C) 2025 Muhammad Mominul Huque + * + * 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 3 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PlatformConfig.h" +#include "Log.h" + +#ifdef Q_OS_MACOS +#include "macOS.h" +#endif + +DesktopEnvironment detectDesktopEnvironment() { + const QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + + // Detect Windows/macOS first + #ifdef Q_OS_WIN + return DesktopEnvironment::Windows; + #endif + + #ifdef Q_OS_MACOS + return DesktopEnvironment::macOS; + #endif + + // Linux/UNIX detection + QStringList desktopEnvs { + env.value("XDG_CURRENT_DESKTOP"), + env.value("DESKTOP_SESSION"), + env.value("GDMSESSION"), + env.value("XDG_SESSION_DESKTOP") + }; + + const QString de = desktopEnvs.join(';').toLower(); + + if (de.contains("zorin")) return DesktopEnvironment::ZorinOS; + if (de.contains("gnome")) return DesktopEnvironment::GNOME; + if (de.contains("kde")) return DesktopEnvironment::KDE; + if (de.contains("xfce")) return DesktopEnvironment::XFCE; + if (de.contains("lxde")) return DesktopEnvironment::LXDE; + if (de.contains("lxqt")) return DesktopEnvironment::LXQt; + if (de.contains("mate")) return DesktopEnvironment::MATE; + if (de.contains("cinnamon")) return DesktopEnvironment::Cinnamon; + if (de.contains("budgie")) return DesktopEnvironment::Budgie; + if (de.contains("unity")) return DesktopEnvironment::Unity; + if (de.contains("pantheon")) return DesktopEnvironment::Pantheon; + if (de.contains("deepin")) return DesktopEnvironment::Deepin; + + // Fallback session type detection + if (!env.value("WAYLAND_DISPLAY").isEmpty()) + return DesktopEnvironment::Wayland; + if (env.value("XDG_SESSION_TYPE").contains("x11")) + return DesktopEnvironment::X11; + + return DesktopEnvironment::Unknown; +} + +QString desktopEnvironmentToString(DesktopEnvironment de) { + switch(de) { + case DesktopEnvironment::Windows: return "Windows"; + case DesktopEnvironment::macOS: return "macOS"; + case DesktopEnvironment::GNOME: return "GNOME"; + case DesktopEnvironment::KDE: return "KDE"; + case DesktopEnvironment::XFCE: return "XFCE"; + case DesktopEnvironment::LXDE: return "LXDE"; + case DesktopEnvironment::LXQt: return "LXQt"; + case DesktopEnvironment::MATE: return "MATE"; + case DesktopEnvironment::Cinnamon: return "Cinnamon"; + case DesktopEnvironment::Budgie: return "Budgie"; + case DesktopEnvironment::Unity: return "Unity"; + case DesktopEnvironment::Pantheon: return "Pantheon"; + case DesktopEnvironment::Deepin: return "Deepin"; + case DesktopEnvironment::ZorinOS: return "ZorinOS"; + case DesktopEnvironment::Wayland: return "Wayland"; + case DesktopEnvironment::X11: return "X11"; + default: return "Unknown"; + } +} + +bool isAppIndicatorEnabled() { + QProcess process; + process.setProgram("gnome-extensions"); + process.setArguments(QStringList() << "list" << "--enabled"); + + process.start(); + if (!process.waitForFinished()) { + qWarning() << "Failed to execute gnome-extensions command"; + return false; + } + + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + qWarning() << "Command execution failed"; + return false; + } + + const QString output = QString::fromUtf8(process.readAllStandardOutput()); + const QString targetUUID = "appindicatorsupport@rgcjonas.gmail.com"; + const QString targetUUID2 = "ubuntu-appindicators@ubuntu.com"; + + // Check each line for exact match + const QStringList extensions = output.split('\n', Qt::SkipEmptyParts); + for (const QString &ext : extensions) { + auto trimmedExt = ext.trimmed(); + if (trimmedExt == targetUUID || trimmedExt == targetUUID2) { + return true; + } + } + + return false; +} + +bool shouldShowTrayIcon() { + DesktopEnvironment de = detectDesktopEnvironment(); + if (de == DesktopEnvironment::GNOME || de == DesktopEnvironment::ZorinOS) { + return isAppIndicatorEnabled(); + } else if(de == DesktopEnvironment::Pantheon) { + // Pantheon (Elementary OS) does not support AppIndicator + return false; + } + + return true; +} + +QString readDConfKey(const QString &key) { + QProcess process; + process.start("dconf", {"read", key}); + process.waitForFinished(); + + if (process.exitCode() == 0) { + return process.readAllStandardOutput().trimmed(); + } else { + LOG_ERROR("readDConfKey(%s) Error: %s\n", key.toStdString().c_str(), process.readAllStandardError().toStdString().c_str()); + return ""; + } +} + +void writeDConfSetting(QString key, QString value) { + // Execute: dconf write "" + QProcess process; + process.start("dconf", {"write", key, value}); + process.waitForFinished(); + + if (process.exitCode() == 0) { + LOG_DEBUG("Successfully updated DConf settings!\n"); + } else { + LOG_ERROR("Error updating DConf settings: %s\n", process.readAllStandardError().toStdString().c_str()); + } +} + +QStringList parseDConfInputSources(const QString &raw) { + QStringList sourcesList; + + // It comes in an array of tuples like this: + // [('xkb', 'us'), ('ibus', 'OpenBangla')] + // So we need to do some preprocessing. + auto string = QString(raw).replace("[", "").replace("]", ""); + auto index = string.indexOf("),"); + + while(index != -1) { + auto sub = string.mid(0, index + 1).trimmed(); + sourcesList.append(sub); + string.remove(0, index + 2); + index = string.indexOf("),"); + } + + if(!string.isEmpty()) { + sourcesList.append(string.trimmed()); + } + + for (const auto &source : sourcesList) { + LOG_DEBUG("Source: %s\n", source.toStdString().c_str()); + } + + return sourcesList; +} + +void setupGnomeIME() { + QString sources = readDConfKey("/org/gnome/desktop/input-sources/sources"); + QStringList sourcesList = parseDConfInputSources(sources); + + QString firstSource = sourcesList.first(); + + // Check if OpenBangla exists in the sources + bool found = sourcesList.contains("('ibus', 'OpenBangla')"); + + if (found) { + LOG_DEBUG("OpenBangla found in sources\n"); + } else { + LOG_DEBUG("OpenBangla not found in sources\n"); + // Add OpenBangla to the sources + sourcesList.append("('ibus', 'OpenBangla')"); + auto sourcesString = QString("[%1]").arg(sourcesList.join(", ")); + writeDConfSetting("/org/gnome/desktop/input-sources/sources", sourcesString); + writeDConfSetting("/org/gnome/desktop/input-sources/mru-sources", QString("[%1]").arg(firstSource)); + LOG_DEBUG("Added OpenBangla to sources\n"); + } +} + +void setupCinnamonIME() { + QString sources = readDConfKey("/org/cinnamon/desktop/input-sources/sources"); + QStringList sourcesList = parseDConfInputSources(sources); + + bool found = sourcesList.contains("('ibus', 'OpenBangla')"); + + if (found) { + LOG_DEBUG("[Cinnamon] OpenBangla found in sources\n"); + } else { + LOG_DEBUG("[Cinnamon] OpenBangla not found in sources\n"); + sourcesList.append("('ibus', 'OpenBangla')"); + auto sourcesString = QString("[%1]").arg(sourcesList.join(", ")); + writeDConfSetting("/org/cinnamon/desktop/input-sources/sources", sourcesString); + LOG_DEBUG("[Cinnamon] Added OpenBangla to sources\n"); + } +} + + +QString findFcitx5WaylandLauncher() { + QStringList candidates = { + "/usr/share/applications/fcitx5-wayland-launcher.desktop", + "/usr/local/share/applications/fcitx5-wayland-launcher.desktop", + }; + + // Also check XDG data dirs + QString xdgDataDirs = QProcessEnvironment::systemEnvironment().value("XDG_DATA_DIRS", "/usr/share:/usr/local/share"); + for (const auto &dir : xdgDataDirs.split(':')) { + QString path = dir + "/applications/fcitx5-wayland-launcher.desktop"; + if (!candidates.contains(path)) { + candidates.append(path); + } + } + + for (const auto &path : candidates) { + if (QFile::exists(path)) { + return path; + } + } + + return ""; +} + +void setupKdeVirtualKeyboard() { + QString launcherPath = findFcitx5WaylandLauncher(); + if (launcherPath.isEmpty()) { + LOG_DEBUG("Fcitx5 Wayland launcher desktop file not found\n"); + return; + } + + // Read current virtual keyboard setting + QProcess readProcess; + readProcess.start("kreadconfig6", {"--file", "kwinrc", "--group", "Wayland", "--key", "InputMethod"}); + readProcess.waitForFinished(); + + QString currentValue = readProcess.readAllStandardOutput().trimmed(); + + if (currentValue == launcherPath) { + LOG_DEBUG("KDE virtual keyboard already set to Fcitx5\n"); + return; + } + + // Set Fcitx5 as the virtual keyboard + QProcess writeProcess; + writeProcess.start("kwriteconfig6", {"--file", "kwinrc", "--group", "Wayland", "--key", "InputMethod", launcherPath}); + writeProcess.waitForFinished(); + + if (writeProcess.exitCode() == 0) { + LOG_DEBUG("Set KDE virtual keyboard to Fcitx5: %s\n", launcherPath.toStdString().c_str()); + } else { + LOG_ERROR("Failed to set KDE virtual keyboard: %s\n", writeProcess.readAllStandardError().toStdString().c_str()); + } +} + +bool addOpenBanglaToFcitx5ViaDBus() { + // Get current input method group info + QProcess getProcess; + getProcess.start("gdbus", {"call", "--session", + "--dest", "org.fcitx.Fcitx5", + "--object-path", "/controller", + "--method", "org.fcitx.Fcitx.Controller1.InputMethodGroupInfo", + "Default"}); + getProcess.waitForFinished(); + + if (getProcess.exitCode() != 0) { + return false; + } + + // Output format: ('us', [('keyboard-us', ''), ('openbangla', '')]) + QString output = getProcess.readAllStandardOutput().trimmed(); + + if (output.contains("'openbangla'")) { + LOG_DEBUG("OpenBangla already in Fcitx5 input method group\n"); + return true; + } + + // Parse the default layout from the output + // Format: ('layout', [...]) + QString defaultLayout = "us"; + int firstQuote = output.indexOf('\''); + int secondQuote = output.indexOf('\'', firstQuote + 1); + if (firstQuote >= 0 && secondQuote > firstQuote) { + defaultLayout = output.mid(firstQuote + 1, secondQuote - firstQuote - 1); + } + + // Parse the existing input methods array + // We need to reconstruct the array with openbangla added + int arrayStart = output.indexOf('['); + int arrayEnd = output.lastIndexOf(']'); + QString existingArray; + if (arrayStart >= 0 && arrayEnd > arrayStart) { + existingArray = output.mid(arrayStart, arrayEnd - arrayStart + 1); + } + + // Build new array: insert openbangla before the closing bracket + QString newArray; + if (existingArray.isEmpty() || existingArray == "[]") { + newArray = "[('keyboard-us', ''), ('openbangla', '')]"; + } else { + // Remove trailing ] and add openbangla + newArray = existingArray.left(existingArray.length() - 1) + ", ('openbangla', '')]"; + } + + QProcess setProcess; + setProcess.start("gdbus", {"call", "--session", + "--dest", "org.fcitx.Fcitx5", + "--object-path", "/controller", + "--method", "org.fcitx.Fcitx.Controller1.SetInputMethodGroupInfo", + "Default", defaultLayout, newArray}); + setProcess.waitForFinished(); + + if (setProcess.exitCode() == 0) { + LOG_DEBUG("Added OpenBangla to Fcitx5 input method group via DBus\n"); + return true; + } + + LOG_ERROR("Failed to add OpenBangla via DBus: %s\n", setProcess.readAllStandardError().toStdString().c_str()); + return false; +} + +void addOpenBanglaToFcitx5Profile() { + QString profilePath = QDir::homePath() + "/.config/fcitx5/profile"; + + // Ensure the directory exists + QDir().mkpath(QDir::homePath() + "/.config/fcitx5"); + + if (QFile::exists(profilePath)) { + // Read existing profile and check if openbangla is already present + QSettings profile(profilePath, QSettings::IniFormat); + + // Scan existing groups for openbangla + int itemCount = 0; + bool found = false; + + for (const auto &group : profile.childGroups()) { + if (group.startsWith("Groups/0/Items/")) { + itemCount++; + profile.beginGroup(group); + if (profile.value("Name").toString() == "openbangla") { + found = true; + } + profile.endGroup(); + } + } + + if (found) { + LOG_DEBUG("OpenBangla already in Fcitx5 profile\n"); + return; + } + + // Add openbangla as the next item + QString newGroup = QString("Groups/0/Items/%1").arg(itemCount); + profile.beginGroup(newGroup); + profile.setValue("Name", "openbangla"); + profile.setValue("Layout", ""); + profile.endGroup(); + profile.sync(); + + LOG_DEBUG("Added OpenBangla to existing Fcitx5 profile\n"); + } else { + // Create a new profile with keyboard-us and openbangla + QSettings profile(profilePath, QSettings::IniFormat); + + profile.beginGroup("Groups/0"); + profile.setValue("Name", "Default"); + profile.setValue("Default Layout", "us"); + profile.setValue("DefaultIM", "openbangla"); + profile.endGroup(); + + profile.beginGroup("Groups/0/Items/0"); + profile.setValue("Name", "keyboard-us"); + profile.setValue("Layout", ""); + profile.endGroup(); + + profile.beginGroup("Groups/0/Items/1"); + profile.setValue("Name", "openbangla"); + profile.setValue("Layout", ""); + profile.endGroup(); + + profile.beginGroup("GroupOrder"); + profile.setValue("0", "Default"); + profile.endGroup(); + + profile.sync(); + + LOG_DEBUG("Created new Fcitx5 profile with OpenBangla\n"); + } +} + +void setupFcitx5InputMethod() { + // Try DBus first (works when Fcitx5 is already running) + if (!addOpenBanglaToFcitx5ViaDBus()) { + LOG_DEBUG("Fcitx5 DBus not available, writing profile directly\n"); + addOpenBanglaToFcitx5Profile(); + } +} + +void setupKdeIME() { + setupKdeVirtualKeyboard(); + setupFcitx5InputMethod(); +} + +void configureImConfigForFcitx5() { + QString xinputrcPath = QDir::homePath() + "/.xinputrc"; + + // Check if already configured for fcitx5 + if (QFile::exists(xinputrcPath)) { + QFile file(xinputrcPath); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString content = file.readAll(); + file.close(); + if (content.contains("run_im fcitx5")) { + LOG_DEBUG("[Fcitx5] im-config already set to fcitx5\n"); + return; + } + } + } + + // Use im-config to set fcitx5 as the input method + QProcess process; + process.start("im-config", {"-n", "fcitx5"}); + process.waitForFinished(); + + if (process.exitCode() == 0) { + LOG_DEBUG("[Fcitx5] Configured im-config to use fcitx5\n"); + } else { + LOG_ERROR("[Fcitx5] Failed to configure im-config: %s\n", process.readAllStandardError().toStdString().c_str()); + } +} + +void setFcitx5EnvVarsForSession() { + // Set IM environment variables for the current DBus session so that + // applications launched after this point will use fcitx5. + QProcess process; + process.start("dbus-update-activation-environment", { + "GTK_IM_MODULE=fcitx", + "QT_IM_MODULE=fcitx", + "XMODIFIERS=@im=fcitx", + "SDL_IM_MODULE=fcitx" + }); + process.waitForFinished(); + + if (process.exitCode() == 0) { + LOG_DEBUG("[Fcitx5] Updated DBus activation environment for fcitx5\n"); + } else { + LOG_ERROR("[Fcitx5] Failed to update DBus environment: %s\n", process.readAllStandardError().toStdString().c_str()); + } +} + +void startFcitx5IfNeeded() { + // Check if fcitx5 is already running + QProcess pgrep; + pgrep.start("pgrep", {"-x", "fcitx5"}); + pgrep.waitForFinished(); + + if (pgrep.exitCode() == 0) { + LOG_DEBUG("[Fcitx5] fcitx5 is already running\n"); + return; + } + + // Start fcitx5 as a daemon + QProcess::startDetached("fcitx5", {"-d"}); + LOG_DEBUG("[Fcitx5] Started fcitx5 daemon\n"); +} + +void setupXfceIME() { + configureImConfigForFcitx5(); + setFcitx5EnvVarsForSession(); + startFcitx5IfNeeded(); + setupFcitx5InputMethod(); +} + +void setupMateIME() { + configureImConfigForFcitx5(); + setFcitx5EnvVarsForSession(); + startFcitx5IfNeeded(); + setupFcitx5InputMethod(); +} + +void setupBudgieIME() { + configureImConfigForFcitx5(); + setFcitx5EnvVarsForSession(); + startFcitx5IfNeeded(); + setupFcitx5InputMethod(); +} + +void setupLXQtIME() { + configureImConfigForFcitx5(); + setFcitx5EnvVarsForSession(); + startFcitx5IfNeeded(); + setupFcitx5InputMethod(); +} + +void setupInputSources() { + auto de = detectDesktopEnvironment(); + + if(de == DesktopEnvironment::GNOME || de == DesktopEnvironment::ZorinOS) { + setupGnomeIME(); + } else if(de == DesktopEnvironment::KDE) { + setupKdeIME(); + } else if(de == DesktopEnvironment::Cinnamon) { + setupCinnamonIME(); + } else if(de == DesktopEnvironment::Deepin) { + setupFcitx5InputMethod(); + } else if(de == DesktopEnvironment::XFCE) { + setupXfceIME(); + } else if(de == DesktopEnvironment::MATE) { + setupMateIME(); + } else if(de == DesktopEnvironment::Budgie) { + setupBudgieIME(); + } else if(de == DesktopEnvironment::LXQt) { + setupLXQtIME(); + } else if(de == DesktopEnvironment::macOS) { + #ifdef Q_OS_MACOS + macOS::setupOpenBanglaInputSource(); + #endif + } else { + LOG_DEBUG("Desktop Environment not supported for input source setup\n"); + } +} diff --git a/src/frontend/PlatformConfig.h b/src/frontend/PlatformConfig.h new file mode 100644 index 00000000..79ac6b82 --- /dev/null +++ b/src/frontend/PlatformConfig.h @@ -0,0 +1,44 @@ +/* + * OpenBangla Keyboard + * Copyright (C) 2025 Muhammad Mominul Huque + * + * 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 3 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 . + */ + + enum class DesktopEnvironment { + Unknown, + Windows, + macOS, + GNOME, + KDE, + XFCE, + LXDE, + LXQt, + MATE, + Cinnamon, + Budgie, + Unity, + Pantheon, + Deepin, + ZorinOS, + Wayland, + X11 +}; + +DesktopEnvironment detectDesktopEnvironment(); +QString desktopEnvironmentToString(DesktopEnvironment de); +bool isAppIndicatorEnabled(); +bool shouldShowTrayIcon(); + +void setupInputSources(); diff --git a/src/frontend/TopBar.cpp b/src/frontend/TopBar.cpp index 342b14e9..edf33ef2 100644 --- a/src/frontend/TopBar.cpp +++ b/src/frontend/TopBar.cpp @@ -33,6 +33,8 @@ #include "AboutDialog.h" #include "SettingsDialog.h" #include "LayoutConverter.h" +#include "AutoCorrectDialog.h" +#include "PlatformConfig.h" #include "FileSystem.h" #include "ui_TopBar.h" @@ -151,7 +153,10 @@ void TopBar::SetupPopupMenus() { connect(iconMenuAbout, SIGNAL(triggered()), this, SLOT(iconMenuAbout_clicked())); iconMenu = new QMenu(this); - iconMenu->addAction(iconMenuHide); + // Gnome Shell and Pantheon doesn't support tray icon natively. + if(shouldShowTrayIcon()) { + iconMenu->addAction(iconMenuHide); + } iconMenu->addAction(iconMenuLayout); iconMenu->addAction(iconMenuAbout); } diff --git a/src/frontend/macOS/CMakeLists.txt b/src/frontend/macOS/CMakeLists.txt new file mode 100644 index 00000000..fe643028 --- /dev/null +++ b/src/frontend/macOS/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_library(macOS STATIC InputSource.swift) +set_target_properties(macOS PROPERTIES Swift_MODULE_NAME "macOS") +target_compile_options(macOS PUBLIC + "$<$:-cxx-interoperability-mode=default>") + +_swift_generate_cxx_header(macOS + macOS.h + SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/frontend/macOS/InputSource.swift b/src/frontend/macOS/InputSource.swift new file mode 100644 index 00000000..da92bf40 --- /dev/null +++ b/src/frontend/macOS/InputSource.swift @@ -0,0 +1,71 @@ +import Carbon +import Foundation + +private let bundleID = "org.openbangla.inputmethod.keyboard" +private let imeName = "OpenBangla" + +// MARK: - IME location + +/// Returns the URL of the installed IME bundle, checking system-wide and +/// per-user Input Methods directories in that order. +private func locateIME() -> URL? { + let candidates = [ + URL(fileURLWithPath: "/Library/Input Methods/\(imeName).app"), + FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent("Library/Input Methods/\(imeName).app"), + ] + return candidates.first { FileManager.default.fileExists(atPath: $0.path) } +} + +// MARK: - TIS helpers + +/// Find the OpenBangla input source, **including disabled / not-yet-enabled +/// sources** (`includeAllInstalled: true`). +private func findInputSource() -> TISInputSource? { + let props = [kTISPropertyBundleID: bundleID] as CFDictionary + guard + let list = TISCreateInputSourceList(props, true)?.takeRetainedValue() as? [TISInputSource] + else { return nil } + return list.first +} + +private func getBool(_ source: TISInputSource, _ key: CFString) -> Bool { + guard let ref = TISGetInputSourceProperty(source, key) else { return false } + return CFBooleanGetValue(unsafeBitCast(ref, to: CFBoolean.self)) +} + +// MARK: - Public API + +/// Returns `true` when the OpenBangla input source is installed and enabled. +public func getInputSourceEnabled() -> Bool { + guard let source = findInputSource() else { return false } + return getBool(source, kTISPropertyInputSourceIsEnabled) +} + +/// Registers and enables the OpenBangla input source. +public func setupOpenBanglaInputSource() { + if getInputSourceEnabled() { + NSLog("OpenBangla input source is already enabled.") + return + } + + guard let imeURL = locateIME() else { + NSLog("Error: OpenBangla.app not found in any Input Methods directory.") + return + } + + // Register the bundle so TIS can see it even before it is enabled. + TISRegisterInputSource(imeURL as CFURL) + + guard let source = findInputSource() else { + NSLog("Error: OpenBangla input source not found after registration (\(imeURL.path)).") + return + } + + let status = TISEnableInputSource(source) + if status == noErr { + NSLog("OpenBangla IME enabled.") + } else { + NSLog("Error: TISEnableInputSource failed with status \(status).") + } +} diff --git a/src/frontend/main.cpp b/src/frontend/main.cpp index dbbfaf7d..8dd51a86 100644 --- a/src/frontend/main.cpp +++ b/src/frontend/main.cpp @@ -23,8 +23,14 @@ #include "TopBar.h" #include "SingleInstance.h" #include "Settings.h" +#include "PlatformConfig.h" +#include "Log.h" #include "FileSystem.h" +#ifdef Q_OS_MACOS + #include "macOS.h" +#endif + int main(int argc, char *argv[]) { QApplication app(argc, argv); gUserFolders = new UserFolders(); @@ -39,9 +45,18 @@ int main(int argc, char *argv[]) { parser.addVersionOption(); QCommandLineOption darkIcon("dark","Enable dark theme support"); QCommandLineOption startInTray("tray","Start in tray"); + QCommandLineOption setupSystem("setup-system","Setup OpenBangla input source"); parser.addOption(darkIcon); parser.addOption(startInTray); + parser.addOption(setupSystem); parser.process(app); + + LOG_INFO("Detected Desktop Environment: %s\n", desktopEnvironmentToString(detectDesktopEnvironment()).toStdString().c_str()); + + if(parser.isSet(setupSystem)) { + setupInputSources(); + return 0; + } // Prevent many instances of the app to be launched QString name = "com.openbangla.keyboard"; diff --git a/tools/build.sh b/tools/build.sh index 9fb12c6e..aff20702 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -1,19 +1,14 @@ #! /bin/bash RELEASE_VERSION=$(cat version.txt | head -n1) # follow cmake PACKAGE_FILE_NAME directive in main repo -RELEASE_STUB="${IME}-openbangla_${RELEASE_VERSION}-" +RELEASE_STUB="openbangla-keyboard_${RELEASE_VERSION}-" makeDeb () { RELEASE_FILENAME="${RELEASE_STUB}${DIST}.deb" - apt-get -y install build-essential pkg-config libibus-1.0-dev cmake libzstd-dev ninja-build curl qtbase5-dev qtbase5-dev-tools file + apt-get -y install build-essential pkg-config libibus-1.0-dev libfcitx5core-dev cmake libzstd-dev ninja-build curl qtbase5-dev qtbase5-dev-tools file curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain stable - if [[ "${IME}" == "ibus" ]]; then - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_IBUS=ON -DCPACK_GENERATOR=DEB - else - apt-get -y install libfcitx5core-dev - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_FCITX=ON -DCPACK_GENERATOR=DEB - fi + cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_BOTH=ON -DCPACK_GENERATOR=DEB ninja package -C /build RELEASE_FILE="/build/${RELEASE_FILENAME}" @@ -24,11 +19,7 @@ makeRpmFedora () { dnf install -y --allowerasing @buildsys-build cmake ibus-devel fcitx5-devel libzstd-devel qt5-qtdeclarative-devel ninja-build curl curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain stable - if [[ "${IME}" == "ibus" ]]; then - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_IBUS=ON -DCPACK_GENERATOR=RPM - else - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_FCITX=ON -DCPACK_GENERATOR=RPM - fi + cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_BOTH=ON -DCPACK_GENERATOR=RPM ninja package -C /build RELEASE_FILE="/build/${RELEASE_FILENAME}" @@ -42,11 +33,7 @@ makeRpmOpenSuse () { zypper install -y libQt5Core-devel libQt5Widgets-devel libQt5Network-devel libzstd-devel cmake ninja ibus-devel fcitx5-devel gcc curl rpm-build curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain stable - if [[ "${IME}" == "ibus" ]]; then - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_IBUS=ON -DCPACK_GENERATOR=RPM - else - cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_FCITX=ON -DCPACK_GENERATOR=RPM - fi + cmake -H"$GITHUB_WORKSPACE" -B/build -GNinja -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_BOTH=ON -DCPACK_GENERATOR=RPM ninja package -C /build RELEASE_FILE="/build/${RELEASE_FILENAME}"