diff --git a/Lib/RabbitCommon.ahk b/Lib/RabbitCommon.ahk index dfae2c1..3992ce1 100644 --- a/Lib/RabbitCommon.ahk +++ b/Lib/RabbitCommon.ahk @@ -50,6 +50,6 @@ OnMessage(context_object, session_id, message_type, message_value) { TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME) } } else { - ; TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME) + TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME) } } diff --git a/Lib/RabbitMonitors.ahk b/Lib/RabbitMonitors.ahk new file mode 100644 index 0000000..29e2014 --- /dev/null +++ b/Lib/RabbitMonitors.ahk @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2023 Xuesong Peng + * + * 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 . + * + */ + +WCHAR_SIZE() { + return 2 +} + +global CCHDEVICENAME := 32 +global MONITOR_DEFAULTTONULL := 0 +global MONITOR_DEFAULTTOPRIMARY := 1 +global MONITOR_DEFAULTTONEAREST := 2 + +class Point extends Class { + __New(x := 0, y := 0) { + this.buff := Buffer(Point.struct_size(), 0) + this.x := x + this.y := y + } + + static x_offset := (*) => 0 + static y_offset := (*) => Point.x_offset() + INT_SIZE() + static struct_size := (*) => Point.y_offset() + INT_SIZE() + + struct_ptr := (*) => this.buff.Ptr + + x { + get => NumGet(this.struct_ptr(), Point.x_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Point.x_offset()) + } + y { + get => NumGet(this.struct_ptr(), Point.y_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Point.y_offset()) + } +} + +class Rect extends Class { + __New(left := 0, top := 0, right := 0, bottom := 0) { + this.buff := Buffer(Rect.struct_size(), 0) + this.left := left + this.top := top + this.right := right + this.bottom := bottom + } + + static left_offset := (*) => 0 + static top_offset := (*) => Rect.left_offset() + INT_SIZE() + static right_offset := (*) => Rect.top_offset() + INT_SIZE() + static bottom_offset := (*) => Rect.right_offset() + INT_SIZE() + static struct_size := (*) => Rect.bottom_offset() + INT_SIZE() + + struct_ptr := (*) => this.buff.Ptr + + left { + get => NumGet(this.struct_ptr(), Rect.left_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Rect.left_offset()) + } + top { + get => NumGet(this.struct_ptr(), Rect.top_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Rect.top_offset()) + } + right { + get => NumGet(this.struct_ptr(), Rect.right_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Rect.right_offset()) + } + bottom { + get => NumGet(this.struct_ptr(), Rect.bottom_offset(), "Int") + set => NumPut("Int", Value, this.struct_ptr(), Rect.bottom_offset()) + } + + width() { + return this.right - this.left + } + height() { + return this.bottom - this.top + } +} ; Rect + +class MonitorInfo extends Class { + __New() { + this.buff := Buffer(MonitorInfo.struct_size(), 0) + NumPut("Int", MonitorInfo.struct_size(), this.struct_ptr()) + } + + static size_offset := (*) => 0 + static monitor_offset := (*) => MonitorInfo.size_offset() + INT_SIZE() + static work_offset := (*) => MonitorInfo.monitor_offset() + Rect.struct_size() + static flags_offset := (*) => MonitorInfo.work_offset() + Rect.struct_size() + static struct_size := (*) => MonitorInfo.flags_offset() + INT_SIZE() + + struct_ptr := (*) => this.buff.Ptr + + size { + get => NumGet(this.struct_ptr(), MonitorInfo.size_offset(), "Int") + } + monitor { + get => Rect( + NumGet(this.struct_ptr(), MonitorInfo.monitor_offset(), "Int"), + NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE(), "Int"), + NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE() * 2, "Int"), + NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE() * 3, "Int") + ) + } + work { + get => Rect( + NumGet(this.struct_ptr(), MonitorInfo.work_offset(), "Int"), + NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE(), "Int"), + NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE() * 2, "Int"), + NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE() * 3, "Int") + ) + } + flags { + get => NumGet(this.struct_ptr(), MonitorInfo.flags_offset(), "Int") + } +} ; MonitorInfo + +class MonitorInfoEx extends MonitorInfo { + __New() { + this.buff := Buffer(MonitorInfoEx.struct_size(), 0) + NumPut("Int", MonitorInfoEx.struct_size(), this.struct_ptr()) + } + + static device_offset := (*) => MonitorInfoEx.flags_offset() + INT_SIZE() + static struct_size := (*) => MonitorInfoEx.device_offset() + WCHAR_SIZE() * CCHDEVICENAME + + device { + get => StrGet(this.struct_ptr() + MonitorInfoEx.device_offset(), CCHDEVICENAME) + } +} ; MonitorInfoEx + +class MonitorManage extends Class { + static monitors := Map() + + static MonitorEnumProc(hMon, hDC, rect, data) { + MonitorManage.monitors[hMon] := MonitorManage.GetMonitorInfo(hMon) + return true + } + + static EnumDisplayMonitors() { + MonitorManage.monitors := Map() + return DllCall("EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", CallbackCreate(ObjBindMethod(MonitorManage, "MonitorEnumProc"), , 4), "Ptr", 0) + } + + static MonitorFromWindow(hWnd, flags := MONITOR_DEFAULTTONULL) { + return DllCall("MonitorFromWindow", "Ptr", hWnd, "UInt", flags) + } + + static MonitorFromPoint(point, flags := MONITOR_DEFAULTTONULL) { + return DllCall("MonitorFromPoint", "Int64", (point.x & 0xFFFFFFFF) | (point.y << 32), "UInt", flags) + } + + static MonitorFromRect(rect, flags := MONITOR_DEFAULTTONULL) { + return DllCall("MonitorFromRect", "Ptr", rect.struct_ptr(), "UInt", flags) + } + + static GetMonitorInfo(hMon) { + info := MonitorInfoEx() + res := DllCall("GetMonitorInfo", "Ptr", hMon, "Ptr", info.struct_ptr()) + return res ? info : 0 + } +} ; MonitorManage diff --git a/Lib/RabbitTrayMenu.ahk b/Lib/RabbitTrayMenu.ahk index 46ad9fb..06b3d44 100644 --- a/Lib/RabbitTrayMenu.ahk +++ b/Lib/RabbitTrayMenu.ahk @@ -18,10 +18,18 @@ TraySetIcon("Rabbit.ico") ; https://www.freepik.com/icon/rabbit_4905239 A_TrayMenu.Delete() -A_TrayMenu.add("打开用户文件夹", (*) => Run(A_ScriptDir . "\Rime")) -A_TrayMenu.add("打开脚本文件夹", (*) => Run(A_ScriptDir)) +; A_TrayMenu.add("输入法设定") +; A_TrayMenu.add("用户词典管理") +A_TrayMenu.add("用户资料同步", (*) => false) +A_TrayMenu.add() +A_TrayMenu.add("用户文件夹", (*) => Run(A_ScriptDir . "\Rime")) +A_TrayMenu.add("脚本文件夹", (*) => Run(A_ScriptDir)) A_TrayMenu.add() A_TrayMenu.add("仓库主页", (*) => Run("https://github.com/amorphobia/rabbit")) A_TrayMenu.add() A_TrayMenu.add("重新部署", (*) => Reload()) A_TrayMenu.add("退出玉兔毫", (*) => ExitApp()) + +; A_TrayMenu.Disable("输入法设定") +; A_TrayMenu.Disable("用户词典管理") +A_TrayMenu.Disable("用户资料同步") diff --git a/Rabbit.ahk b/Rabbit.ahk index b4c9f90..8d4151c 100644 --- a/Rabbit.ahk +++ b/Rabbit.ahk @@ -23,6 +23,7 @@ #Include #Include #Include +#Include global session_id := 0 global box := Gui() @@ -236,8 +237,6 @@ ProcessKey(key, mask, this_hotkey) { max_width := 150 if caret { - workspace_width := SysGet(16) ; SM_CXFULLSCREEN - workspace_height := SysGet(17) ; SM_CYFULLSCREEN box["Preedit"].Value := preedit_text box["Candidates"].Value := menu_text box["Preedit"].Move(, , max_width) @@ -246,10 +245,23 @@ ProcessKey(key, mask, this_hotkey) { box.GetPos(, , &box_width, &box_height) new_x := caret_x + caret_w new_y := caret_y + caret_h + 4 - if new_x + box_width > workspace_width - new_x := workspace_width - box_width - if new_y + box_height > workspace_height - new_y := caret_y - 4 - box_height + + hWnd := WinExist("A") + hMon := MonitorManage.MonitorFromWindow(hWnd) + info := MonitorManage.GetMonitorInfo(hMon) + if info { + if new_x + box_width > info.work.right + new_x := info.work.right - box_width + if new_y + box_height > info.work.bottom + new_y := caret_y - 4 - box_height + } else { + workspace_width := SysGet(16) ; SM_CXFULLSCREEN + workspace_height := SysGet(17) ; SM_CYFULLSCREEN + if new_x + box_width > workspace_width + new_x := workspace_width - box_width + if new_y + box_height > workspace_height + new_y := caret_y - 4 - box_height + } box.Show("AutoSize NA x" . new_x . " y" . new_y) WinSetAlwaysOnTop(1, box) } else { diff --git a/RabbitDeployer.ahk b/RabbitDeployer.ahk new file mode 100644 index 0000000..fdffc28 --- /dev/null +++ b/RabbitDeployer.ahk @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 Xuesong Peng + * + * 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 . + * + */ +#Requires AutoHotkey v2.0 32-bit +; #NoTrayIcon + +#Include +#Include + +global rime +global ERROR_ALREADY_EXISTS := 183 +global INVALID_FILE_ATTRIBUTES := -1 +global FILE_ATTRIBUTE_DIRECTORY := 0x00000010 + +arg := A_Args.Length > 0 ? A_Args[1] : "" +RunDeployer(arg) + +RunDeployer(command) { + conf := Configurator() + conf.Initialize() + deployment_scheduled := command == "deploy" + if deployment_scheduled + return conf.UpdateWorkspace() + dict_management := command == "dict" + if dict_management + return 0 ; return conf.DictManagement() + sync_user_dict := command == "sync" + if sync_user_dict + return conf.SyncUserData() + installing := command == "install" + return conf.Run(installing) +} + +CreateFileIfNotExist(filename) { + user_data_dir := A_ScriptDir . "\Rime\" + if not InStr(DirExist(user_data_dir), "D") + DirCreate(user_data_dir) + filepath := user_data_dir . filename + if not InStr(FileExist(filepath), "N") + FileAppend("", filepath) +} + +ConfigureSwitcher(levers, switcher_settings, reconfigured) { + if not levers.load_settings(switcher_settings) + return false + ; +} + +class Configurator extends Class { + __New() { + CreateFileIfNotExist("default.custom.yaml") + CreateFileIfNotExist("rabbit.custom.yaml") + } + + Initialize() { + rabbit_traits := CreateTraits() + rime.setup(rabbit_traits) + rime.deployer_initialize(0) + } + + Run(installing) { + levers := RimeLeversApi() + if not levers + return 1 + + switcher_settings := levers.switcher_settings_init() + skip_switcher_settings := installing && !levers.is_first_run(switcher_settings) + + if installing + this.UpdateWorkspace() + + return 0 + } + + UpdateWorkspace(report_errors := false) { + hMutex := DllCall("CreateMutex", "Ptr", 0, "Int", true, "Str", "RabbitDeployerMutex") + if not hMutex { + return 1 + } + if DllCall("GetLastError") == ERROR_ALREADY_EXISTS { + DllCall("CloseHandle", "Ptr", hMutex) + return 1 + } + + { + rime.deploy() + ; rime.deploy_config_file("rabbit.yaml", "config_version") + } + + DllCall("CloseHandle", "Ptr", hMutex) + + return 0 + } + + ; DictManagement() + + SyncUserData() { + hMutex := DllCall("CreateMutex", "Ptr", 0, "Int", true, "Str", "RabbitDeployerMutex") + if not hMutex { + return 1 + } + if DllCall("GetLastError") == ERROR_ALREADY_EXISTS { + DllCall("CloseHandle", "Ptr", hMutex) + return 1 + } + + { + if not rime.sync_user_data() { + DllCall("CloseHandle", "Ptr", hMutex) + return 1 + } + rime.join_maintenance_thread() + } + + DllCall("CloseHandle", "Ptr", hMutex) + + return 0 + } +}