[TypeScript] 01 Electronアプリの改変 ショートカット変更

[Mac M2 Pro 12CPU, Sonoma 14.5]

最近は麻雀よりも将棋の方に興味が向いています。

AI対局や名局の棋譜並べに最適なShogiHomeというElectronアプリをGitHubから入手して使ってみたのですが、ショートカットで右矢印キーがNEXTではなくLASTになるなど仕様が気になるので、自分用に改変しました。

左矢印がBACK、右矢印はNEXT、上矢印がFIRST、下矢印がLASTになるように割り付けました。

Electronアプリは久しぶりに扱ったため時間を要しました。GUIに特化したフレームワークであるVue.jsについては全くの初見で少々手こずりました。

4ファイル6箇所を書き換えてビルド
ShogiHomeのFork

[Xcode] iOS 18 新仕様への対応 アプリアイコン

[Mac M2 Pro 12CPU, Sonoma 14.5, Xcode 16 beta]

今秋リリース予定のiOS 18からiOSアプリのアイコンが3種類(Light, Dark, Tinted)必要になります。

早速、Xcode 16 beta版で登録してみました。

watchOSアプリを手掛けているデベロッパーであれば文字盤のアクセントカラー対応で洗礼を受けているので、特に混乱はないと思います。

ダークモード用のアイコン(背景が透過度100%)はそのままTintedアイコン(モノトーン)としても使えそうな感じがしますが、多色デザインであれば灰色の濃さで表現するため作り直しということになりそうです。

[Python] 363 カレンダーicsファイルをjsonファイルに変換

[Mac M2 Pro 12CPU, Ventura 13.6, Python 3.10.4]

Googleカレンダーから入手したicsファイルの内容をgpt-4oに見せて、jsonファイルの作成スクリプトを考えさせました。

これまではGitHubなどから使えそうなコードを引っ張ってきたりしていましたが、AIに作らせる方が手っ取り早いです。

今回の場合はicsファイルに記念日が混在していたので、結局自分で手を入れて仕上げることになりました。

import json
from icalendar import Calendar
from datetime import datetime

# アメリカの祝日リスト
us_holidays = {
    "New Year's Day",
    "Martin Luther King Jr. Day",
    "Presidents' Day",
    "Memorial Day",
    "Juneteenth",
    "Independence Day",
    "Labor Day",
    "Columbus Day",
    "Veterans Day",
    "Thanksgiving Day",
    "Christmas Day"
}

def parse_ics(file_path):
    with open(file_path, 'r') as f:
        ics_content = f.read()

    calendar = Calendar.from_ical(ics_content)
    holidays = {}

    for component in calendar.walk():
        if component.name == "VEVENT":
            summary = str(component.get('summary'))
            dtstart = component.get('dtstart').dt
            if isinstance(dtstart, datetime):
                dtstart = dtstart.date()
            dtstart_str = dtstart.strftime('%Y-%m-%d')
            
             # アメリカの祝日リストに含まれているか確認
            if any(holiday in summary for holiday in us_holidays):
                holidays[dtstart_str] = summary

    return holidays

def save_to_json(data, output_file):
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

if __name__ == "__main__":
    ics_file_path = 'america.ics'  # ICSファイルのパスを指定
    output_json_file = 'holidayUS.json'  # 出力するJSONファイルの名前

    holidays = parse_ics(ics_file_path)
    
    # 日付の昇順で並べ替え
    sorted_holidays = dict(sorted(holidays.items()))

    save_to_json(sorted_holidays, output_json_file)
    print(f"祝日情報が {output_json_file} に保存されました。")
{
    "2019-01-01": "New Year's Day",
    "2019-01-21": "Martin Luther King Jr. Day",
    "2019-02-18": "Presidents' Day",
    "2019-05-27": "Memorial Day",
    "2019-06-19": "Juneteenth",
    "2019-07-04": "Independence Day",
    "2019-09-02": "Labor Day",
    "2019-11-11": "Veterans Day",
    "2019-11-28": "Thanksgiving Day",
    "2019-12-25": "Christmas Day",
<以下略>

[Swift] 73 SwiftUIの特異な仕様 / 選択したカレンダーのイベントをForEach文で表示

[Mac M2 Pro 12CPU, Sonoma 14.3.1]

画像はwatchOSアプリで”日本の祝日”カレンダーの向こう2ヶ月分を表示させたものです。

海の日だけが表示されている普通の画面のように見えますが、実は背景色が黒の空イベント2件分で海の日の下を埋めています。

そうしないと選択していない他のカレンダーの繰り返しイベントが表示されてしまいます。仕様というかOSの完全なバグでしょう。

便利だからSwiftUIを使ってはいるものの、こういった現象に遭遇するとげんなりしますね。コード自体は間違っていないですから、原因究明が大変です。

[Swift] 72 フォントサイズの自動調整

[Mac M2 Pro 12CPU, Sonoma 14.3.1]

watchOSアプリでフォントサイズを自動調整する場合は、lineLimitモディファイアやminimumScaleFactorモディファイアを使用します。

HStack(spacing:4){
    Text("White Background")
        .font(.body)
        .frame(maxWidth: .infinity, alignment: .leading)
        .lineLimit(1)
        .minimumScaleFactor(0.8)
    Spacer()
    Toggle(isOn: $whiteBack) {
        Text("")
    }
        .labelsHidden()
        .onChange(of: whiteBack) { newValue in
            defaults?.set(newValue, forKey: whiteBackKey)
        }
}

[Swift] 71 条件演算子を使ったリファクタリング

[Mac M2 Pro 12CPU, Sonoma 14.3.1]

SwiftUIアプリでトグルボタン2つと言語設定で分岐させたif文がメンテ不能な程にややこしくなったため、条件演算子を使ってリファクタリングしました。

条件 ? 真の場合の値 : 偽の場合の値

Clangではあり得なかったことですが、Xcodeでややこしいコードを書くと根を上げてエラーになりリファクタリングを強制されます。

[整理前]

VStack (spacing: -6){
    if bezelDisplay {
        if !coloredCMP {
            if languageCode!.contains("ja") {
                Text(getFormattedWeekday(entry.date).prefix(2))
                    .font(.system(size: 15))
                    .bold()
                    .foregroundColor(Color(hex:colorCircular1Hex))
                    .containerBackground(for: .widget){
                        Color.blue
                    }
                    .widgetLabel {
                        Text(getYear(entry.date) + " " + getFormattedYear(entry.date))
                            .font(.system(size: 20))
                    }
            } else {
                Text(getFormattedWeekday(entry.date).prefix(2))
                    .font(.system(size: 15))
                    .bold()
                    .foregroundColor(Color(hex:colorCircular1Hex))
                    .containerBackground(for: .widget){
                        Color.blue
                    }
                    .widgetLabel {
                        Text(getYear(entry.date))
                            .font(.system(size: 20))
                    }
            }
        } else {

<以下略>

[整理後]

VStack (spacing: -6){
    let fontSize: Font = coloredCMP ? .system(size: 18) : .body
    let yearText: String = bezelDisplay ? (languageCode!.contains("ja") ? getYear(entry.date) + " " + getFormattedYear(entry.date) : getYear(entry.date)) : ""

    Text(getFormattedWeekday(entry.date))
        .font(fontSize)
        .bold()
        .foregroundColor(Color(hex: colorCircular1Hex))
        .containerBackground(for: .widget) {
            Color.blue
        }
        .padding(.vertical, 2)
        .widgetLabel {
            Text(yearText)
                .font(.system(size: 20))
        }
        
    Text(getDay(entry.date))
        .font(.system(size: 20))
        .bold()
        .foregroundColor(Color(hex:colorCircular3Hex))
        .containerBackground(for: .widget){
            Color.blue
        }
}

[C++] 373 ChatAIアプリの製作 その51 LLMの序列変更 Perplexity API

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

Perplexity APIを使うことで最新情報も取得できるようになったので、第1選択LLMに昇格させました。

間違った情報が混ざっていることもありますが、ラフな情報収集としてはまずまずかと思います。

24/05/17追記
Perplexity APIは回答の言語が不安定(日本語 or 英語)なので、第1選択LLMをgtp-4oに戻しました。Webサービス(無料 or Pro)で使う方が良さそうです。

[C++] 372 ChatAIアプリの製作 その50 gpt-4oとPerplexity APIの導入

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

5月13日に発表されたgpt-4oを早速導入しました。Perplexity APIも導入しました。

gpt-4oについてはこれから検証していきます。

まず驚いたのがPerplexity API(llama-3-sonar-large-32k-online)のネット検索能力です。年度、日付などはいい加減ですが、プレミアリーグの最新データを取得してくれました。順位、勝敗、得失点は正確でした。

ただしPerplexity APIはgptのようにあまり行間を読んでくれないため、くどくどしくプロンプトを書かないと意図に沿った回答が得られないケースがあります。

またsystemで日本語で回答するように指示しても、英語で返す方が多かったりします。なので最初のプロンプトでも日本語指定のテンプレを自動追記するようにしました。それでも頑なに英語で返すことがあります。

アプリに登録しているLLM一覧(左上のプルダウンメニュー)

[C++] 371 ローマ字を英数モードで打った時のリカバリー その3 再変換までキー5回押下で実装 

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

かつてのATOKの様に、かなキー2回押下で再変換までできず、以下のように機能を3分割して実装しました。

英字からひらがな変換:かなキー2回押下、AppleScript
ひらがなを範囲選択:英数キー2回押下、 C++自製関数
再変換:F13キー1回押下、AppleScript

最終的にC++だけで実装できるよう、リファクタリングしていきます。

#include <ApplicationServices/ApplicationServices.h>

void pressShiftLeftArrow(int count) {
    // Shiftキーを押す
    CGEventRef shiftDown = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, true);
    CGEventPost(kCGHIDEventTap, shiftDown);
    CFRelease(shiftDown);

    // 文字数分だけ左矢印キーを押す
    for (int i = 0; i < count; ++i) {
        CGEventRef leftArrowDown = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)123, true);
        CGEventRef leftArrowUp = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)123, false);
        CGEventSetFlags(leftArrowDown, kCGEventFlagMaskShift);
        CGEventPost(kCGHIDEventTap, leftArrowDown);
        CGEventPost(kCGHIDEventTap, leftArrowUp);
        CFRelease(leftArrowDown);
        CFRelease(leftArrowUp);
        // イベント間にわずかな遅延を挿入
        usleep(10000); // 10ミリ秒待機
    }

    // Shiftキーを離す
    CGEventRef shiftUp = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, false);
    CGEventPost(kCGHIDEventTap, shiftUp);
    CFRelease(shiftUp);
}

[C++] 370 ローマ字を英数モードで打った時のリカバリー その2 かなキー連続押下対応

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

前回のコードでは、かなキーを何となく押し、英字を入力して無意識に2回目を押したりすると不要なひらがなが一気に出現します。

これではまずいので1秒以内に2回目を押した時だけひらがな変換するようにしました。

あとはひらがな変換した文字列を範囲選択して再変換するという処理ですが、流石にC++でもハードルは高そうです。

Carbonフレームワークが今も現役なことに少々驚きました。

#include <Carbon/Carbon.h>
#include <iostream>
#include <string>
#include <unordered_map>
#include <chrono>

std::unordered_map<CGKeyCode, std::string> keyCodeMap = {
    {0, "a"}, {11, "b"}, {8, "c"}, {2, "d"}, {14, "e"}, {3, "f"},
    {5, "g"}, {4, "h"}, {34, "i"}, {38, "j"}, {40, "k"}, {37, "l"},
    {46, "m"}, {45, "n"}, {31, "o"}, {35, "p"}, {12, "q"}, {15, "r"},
    {1, "s"}, {17, "t"}, {32, "u"}, {9, "v"}, {13, "w"}, {7, "x"},
    {16, "y"}, {6, "z"}
};

std::string inputBuffer;
int kanaKeyPressCount = 0;
std::chrono::steady_clock::time_point firstKanaKeyPressTime;

CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
    if (type == kCGEventKeyDown) {
        CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

        if (keyCode == 104) { // かなキー(104)が押された場合
            std::cout << "かなキーを押しました" << std::endl;
            auto now = std::chrono::steady_clock::now();

            if (kanaKeyPressCount == 0) {
                firstKanaKeyPressTime = now;
                kanaKeyPressCount = 1;
            } else if (kanaKeyPressCount == 1) {
                auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - firstKanaKeyPressTime).count();
                if (duration <= 1) { // 1秒以内に2回目が押された場合
                    std::string command = "osascript \"LatinToHiragana.applescript\" " + inputBuffer;
                    std::cout << "command: " << command << std::endl;
                    system(command.c_str()); // コマンドを実行

                    inputBuffer.clear();
                    std::cout << "AppleScript実行。Current Bufferをリセット" << std::endl;
                } else {
                    std::cout << "1秒以上経過のため、カウントをリセット" << std::endl;
                }
                kanaKeyPressCount = 0; // カウントをリセット
            }
        } else {
            auto it = keyCodeMap.find(keyCode);
            if (it != keyCodeMap.end()) {
                inputBuffer += it->second;
                std::cout << "Current Buffer: " << inputBuffer << std::endl;
            } else {
                inputBuffer.clear();
                std::cout << "Current Bufferをリセット" << std::endl;
            }
        }
    }

    return event;
}

int main() {
    CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyDown), eventCallback, nullptr);
    if (!eventTap) {
        std::cerr << "Failed to create event tap" << std::endl;
        return 1;
    }

    CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
    CGEventTapEnable(eventTap, true);
    CFRunLoopRun();

    return 0;
}