Skip to content

Commit c6198a1

Browse files
committed
chore: try to support multi-monitor
1 parent d37bd27 commit c6198a1

File tree

5 files changed

+337
-9
lines changed

5 files changed

+337
-9
lines changed

Lib/RabbitCommon.ahk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ OnMessage(context_object, session_id, message_type, message_value) {
5050
TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME)
5151
}
5252
} else {
53-
; TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME)
53+
TrayTip(msg_type . ": " . msg_value . " (" . session_id . ")", RABBIT_IME_NAME)
5454
}
5555
}

Lib/RabbitMonitors.ahk

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright (c) 2023 Xuesong Peng <pengxuesong.cn@gmail.com>
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
19+
WCHAR_SIZE() {
20+
return 2
21+
}
22+
23+
global CCHDEVICENAME := 32
24+
global MONITOR_DEFAULTTONULL := 0
25+
global MONITOR_DEFAULTTOPRIMARY := 1
26+
global MONITOR_DEFAULTTONEAREST := 2
27+
28+
class Point extends Class {
29+
__New(x := 0, y := 0) {
30+
this.buff := Buffer(Point.struct_size(), 0)
31+
this.x := x
32+
this.y := y
33+
}
34+
35+
static x_offset := (*) => 0
36+
static y_offset := (*) => Point.x_offset() + INT_SIZE()
37+
static struct_size := (*) => Point.y_offset() + INT_SIZE()
38+
39+
struct_ptr := (*) => this.buff.Ptr
40+
41+
x {
42+
get => NumGet(this.struct_ptr(), Point.x_offset(), "Int")
43+
set => NumPut("Int", Value, this.struct_ptr(), Point.x_offset())
44+
}
45+
y {
46+
get => NumGet(this.struct_ptr(), Point.y_offset(), "Int")
47+
set => NumPut("Int", Value, this.struct_ptr(), Point.y_offset())
48+
}
49+
}
50+
51+
class Rect extends Class {
52+
__New(left := 0, top := 0, right := 0, bottom := 0) {
53+
this.buff := Buffer(Rect.struct_size(), 0)
54+
this.left := left
55+
this.top := top
56+
this.right := right
57+
this.bottom := bottom
58+
}
59+
60+
static left_offset := (*) => 0
61+
static top_offset := (*) => Rect.left_offset() + INT_SIZE()
62+
static right_offset := (*) => Rect.top_offset() + INT_SIZE()
63+
static bottom_offset := (*) => Rect.right_offset() + INT_SIZE()
64+
static struct_size := (*) => Rect.bottom_offset() + INT_SIZE()
65+
66+
struct_ptr := (*) => this.buff.Ptr
67+
68+
left {
69+
get => NumGet(this.struct_ptr(), Rect.left_offset(), "Int")
70+
set => NumPut("Int", Value, this.struct_ptr(), Rect.left_offset())
71+
}
72+
top {
73+
get => NumGet(this.struct_ptr(), Rect.top_offset(), "Int")
74+
set => NumPut("Int", Value, this.struct_ptr(), Rect.top_offset())
75+
}
76+
right {
77+
get => NumGet(this.struct_ptr(), Rect.right_offset(), "Int")
78+
set => NumPut("Int", Value, this.struct_ptr(), Rect.right_offset())
79+
}
80+
bottom {
81+
get => NumGet(this.struct_ptr(), Rect.bottom_offset(), "Int")
82+
set => NumPut("Int", Value, this.struct_ptr(), Rect.bottom_offset())
83+
}
84+
85+
width() {
86+
return this.right - this.left
87+
}
88+
height() {
89+
return this.bottom - this.top
90+
}
91+
} ; Rect
92+
93+
class MonitorInfo extends Class {
94+
__New() {
95+
this.buff := Buffer(MonitorInfo.struct_size(), 0)
96+
NumPut("Int", MonitorInfo.struct_size(), this.struct_ptr())
97+
}
98+
99+
static size_offset := (*) => 0
100+
static monitor_offset := (*) => MonitorInfo.size_offset() + INT_SIZE()
101+
static work_offset := (*) => MonitorInfo.monitor_offset() + Rect.struct_size()
102+
static flags_offset := (*) => MonitorInfo.work_offset() + Rect.struct_size()
103+
static struct_size := (*) => MonitorInfo.flags_offset() + INT_SIZE()
104+
105+
struct_ptr := (*) => this.buff.Ptr
106+
107+
size {
108+
get => NumGet(this.struct_ptr(), MonitorInfo.size_offset(), "Int")
109+
}
110+
monitor {
111+
get => Rect(
112+
NumGet(this.struct_ptr(), MonitorInfo.monitor_offset(), "Int"),
113+
NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE(), "Int"),
114+
NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE() * 2, "Int"),
115+
NumGet(this.struct_ptr(), MonitorInfo.monitor_offset() + INT_SIZE() * 3, "Int")
116+
)
117+
}
118+
work {
119+
get => Rect(
120+
NumGet(this.struct_ptr(), MonitorInfo.work_offset(), "Int"),
121+
NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE(), "Int"),
122+
NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE() * 2, "Int"),
123+
NumGet(this.struct_ptr(), MonitorInfo.work_offset() + INT_SIZE() * 3, "Int")
124+
)
125+
}
126+
flags {
127+
get => NumGet(this.struct_ptr(), MonitorInfo.flags_offset(), "Int")
128+
}
129+
} ; MonitorInfo
130+
131+
class MonitorInfoEx extends MonitorInfo {
132+
__New() {
133+
this.buff := Buffer(MonitorInfoEx.struct_size(), 0)
134+
NumPut("Int", MonitorInfoEx.struct_size(), this.struct_ptr())
135+
}
136+
137+
static device_offset := (*) => MonitorInfoEx.flags_offset() + INT_SIZE()
138+
static struct_size := (*) => MonitorInfoEx.device_offset() + WCHAR_SIZE() * CCHDEVICENAME
139+
140+
device {
141+
get => StrGet(this.struct_ptr() + MonitorInfoEx.device_offset(), CCHDEVICENAME)
142+
}
143+
} ; MonitorInfoEx
144+
145+
class MonitorManage extends Class {
146+
static monitors := Map()
147+
148+
static MonitorEnumProc(hMon, hDC, rect, data) {
149+
MonitorManage.monitors[hMon] := MonitorManage.GetMonitorInfo(hMon)
150+
return true
151+
}
152+
153+
static EnumDisplayMonitors() {
154+
MonitorManage.monitors := Map()
155+
return DllCall("EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", CallbackCreate(ObjBindMethod(MonitorManage, "MonitorEnumProc"), , 4), "Ptr", 0)
156+
}
157+
158+
static MonitorFromWindow(hWnd, flags := MONITOR_DEFAULTTONULL) {
159+
return DllCall("MonitorFromWindow", "Ptr", hWnd, "UInt", flags)
160+
}
161+
162+
static MonitorFromPoint(point, flags := MONITOR_DEFAULTTONULL) {
163+
return DllCall("MonitorFromPoint", "Int64", (point.x & 0xFFFFFFFF) | (point.y << 32), "UInt", flags)
164+
}
165+
166+
static MonitorFromRect(rect, flags := MONITOR_DEFAULTTONULL) {
167+
return DllCall("MonitorFromRect", "Ptr", rect.struct_ptr(), "UInt", flags)
168+
}
169+
170+
static GetMonitorInfo(hMon) {
171+
info := MonitorInfoEx()
172+
res := DllCall("GetMonitorInfo", "Ptr", hMon, "Ptr", info.struct_ptr())
173+
return res ? info : 0
174+
}
175+
} ; MonitorManage

Lib/RabbitTrayMenu.ahk

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@
1818

1919
TraySetIcon("Rabbit.ico") ; https://www.freepik.com/icon/rabbit_4905239
2020
A_TrayMenu.Delete()
21-
A_TrayMenu.add("打开用户文件夹", (*) => Run(A_ScriptDir . "\Rime"))
22-
A_TrayMenu.add("打开脚本文件夹", (*) => Run(A_ScriptDir))
21+
; A_TrayMenu.add("输入法设定")
22+
; A_TrayMenu.add("用户词典管理")
23+
A_TrayMenu.add("用户资料同步", (*) => false)
24+
A_TrayMenu.add()
25+
A_TrayMenu.add("用户文件夹", (*) => Run(A_ScriptDir . "\Rime"))
26+
A_TrayMenu.add("脚本文件夹", (*) => Run(A_ScriptDir))
2327
A_TrayMenu.add()
2428
A_TrayMenu.add("仓库主页", (*) => Run("https://github.com/amorphobia/rabbit"))
2529
A_TrayMenu.add()
2630
A_TrayMenu.add("重新部署", (*) => Reload())
2731
A_TrayMenu.add("退出玉兔毫", (*) => ExitApp())
32+
33+
; A_TrayMenu.Disable("输入法设定")
34+
; A_TrayMenu.Disable("用户词典管理")
35+
A_TrayMenu.Disable("用户资料同步")

Rabbit.ahk

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#Include <RabbitCandidateBox>
2424
#Include <RabbitCaret>
2525
#Include <RabbitTrayMenu>
26+
#Include <RabbitMonitors>
2627

2728
global session_id := 0
2829
global box := Gui()
@@ -236,8 +237,6 @@ ProcessKey(key, mask, this_hotkey) {
236237
max_width := 150
237238

238239
if caret {
239-
workspace_width := SysGet(16) ; SM_CXFULLSCREEN
240-
workspace_height := SysGet(17) ; SM_CYFULLSCREEN
241240
box["Preedit"].Value := preedit_text
242241
box["Candidates"].Value := menu_text
243242
box["Preedit"].Move(, , max_width)
@@ -246,10 +245,23 @@ ProcessKey(key, mask, this_hotkey) {
246245
box.GetPos(, , &box_width, &box_height)
247246
new_x := caret_x + caret_w
248247
new_y := caret_y + caret_h + 4
249-
if new_x + box_width > workspace_width
250-
new_x := workspace_width - box_width
251-
if new_y + box_height > workspace_height
252-
new_y := caret_y - 4 - box_height
248+
249+
hWnd := WinExist("A")
250+
hMon := MonitorManage.MonitorFromWindow(hWnd)
251+
info := MonitorManage.GetMonitorInfo(hMon)
252+
if info {
253+
if new_x + box_width > info.work.right
254+
new_x := info.work.right - box_width
255+
if new_y + box_height > info.work.bottom
256+
new_y := caret_y - 4 - box_height
257+
} else {
258+
workspace_width := SysGet(16) ; SM_CXFULLSCREEN
259+
workspace_height := SysGet(17) ; SM_CYFULLSCREEN
260+
if new_x + box_width > workspace_width
261+
new_x := workspace_width - box_width
262+
if new_y + box_height > workspace_height
263+
new_y := caret_y - 4 - box_height
264+
}
253265
box.Show("AutoSize NA x" . new_x . " y" . new_y)
254266
WinSetAlwaysOnTop(1, box)
255267
} else {

RabbitDeployer.ahk

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright (c) 2023 Xuesong Peng <pengxuesong.cn@gmail.com>
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
#Requires AutoHotkey v2.0 32-bit
19+
; #NoTrayIcon
20+
21+
#Include <RabbitCommon>
22+
#Include <librime-ahk\rime_levers_api>
23+
24+
global rime
25+
global ERROR_ALREADY_EXISTS := 183
26+
global INVALID_FILE_ATTRIBUTES := -1
27+
global FILE_ATTRIBUTE_DIRECTORY := 0x00000010
28+
29+
arg := A_Args.Length > 0 ? A_Args[1] : ""
30+
RunDeployer(arg)
31+
32+
RunDeployer(command) {
33+
conf := Configurator()
34+
conf.Initialize()
35+
deployment_scheduled := command == "deploy"
36+
if deployment_scheduled
37+
return conf.UpdateWorkspace()
38+
dict_management := command == "dict"
39+
if dict_management
40+
return 0 ; return conf.DictManagement()
41+
sync_user_dict := command == "sync"
42+
if sync_user_dict
43+
return conf.SyncUserData()
44+
installing := command == "install"
45+
return conf.Run(installing)
46+
}
47+
48+
CreateFileIfNotExist(filename) {
49+
user_data_dir := A_ScriptDir . "\Rime\"
50+
if not InStr(DirExist(user_data_dir), "D")
51+
DirCreate(user_data_dir)
52+
filepath := user_data_dir . filename
53+
if not InStr(FileExist(filepath), "N")
54+
FileAppend("", filepath)
55+
}
56+
57+
ConfigureSwitcher(levers, switcher_settings, reconfigured) {
58+
if not levers.load_settings(switcher_settings)
59+
return false
60+
;
61+
}
62+
63+
class Configurator extends Class {
64+
__New() {
65+
CreateFileIfNotExist("default.custom.yaml")
66+
CreateFileIfNotExist("rabbit.custom.yaml")
67+
}
68+
69+
Initialize() {
70+
rabbit_traits := CreateTraits()
71+
rime.setup(rabbit_traits)
72+
rime.deployer_initialize(0)
73+
}
74+
75+
Run(installing) {
76+
levers := RimeLeversApi()
77+
if not levers
78+
return 1
79+
80+
switcher_settings := levers.switcher_settings_init()
81+
skip_switcher_settings := installing && !levers.is_first_run(switcher_settings)
82+
83+
if installing
84+
this.UpdateWorkspace()
85+
86+
return 0
87+
}
88+
89+
UpdateWorkspace(report_errors := false) {
90+
hMutex := DllCall("CreateMutex", "Ptr", 0, "Int", true, "Str", "RabbitDeployerMutex")
91+
if not hMutex {
92+
return 1
93+
}
94+
if DllCall("GetLastError") == ERROR_ALREADY_EXISTS {
95+
DllCall("CloseHandle", "Ptr", hMutex)
96+
return 1
97+
}
98+
99+
{
100+
rime.deploy()
101+
; rime.deploy_config_file("rabbit.yaml", "config_version")
102+
}
103+
104+
DllCall("CloseHandle", "Ptr", hMutex)
105+
106+
return 0
107+
}
108+
109+
; DictManagement()
110+
111+
SyncUserData() {
112+
hMutex := DllCall("CreateMutex", "Ptr", 0, "Int", true, "Str", "RabbitDeployerMutex")
113+
if not hMutex {
114+
return 1
115+
}
116+
if DllCall("GetLastError") == ERROR_ALREADY_EXISTS {
117+
DllCall("CloseHandle", "Ptr", hMutex)
118+
return 1
119+
}
120+
121+
{
122+
if not rime.sync_user_data() {
123+
DllCall("CloseHandle", "Ptr", hMutex)
124+
return 1
125+
}
126+
rime.join_maintenance_thread()
127+
}
128+
129+
DllCall("CloseHandle", "Ptr", hMutex)
130+
131+
return 0
132+
}
133+
}

0 commit comments

Comments
 (0)