@bobfrankston/winpos 2.0.39 → 2.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -1
- package/.editorconfig +0 -0
- package/.gitattributes +0 -0
- package/.vscode/launch.json +0 -0
- package/.vscode/settings.json +0 -0
- package/.vscode/spellright.dict +0 -0
- package/.vscode/tasks.json +0 -0
- package/DEVELOPMENT.md +0 -0
- package/README.md +13 -13
- package/ffi-wrapper.d.ts +0 -0
- package/ffi-wrapper.d.ts.map +0 -0
- package/ffi-wrapper.js +0 -0
- package/ffi-wrapper.js.map +0 -0
- package/ffi-wrapper.ts +0 -0
- package/ignores.txt +0 -0
- package/index.d.ts +16 -0
- package/index.d.ts.map +1 -1
- package/index.js +378 -54
- package/index.js.map +1 -1
- package/index.ts +440 -54
- package/layout.json +46 -0
- package/package.json +3 -4
- package/problem.txt +0 -0
- package/screens.d.ts +0 -0
- package/screens.d.ts.map +0 -0
- package/screens.js +0 -0
- package/screens.js.map +0 -0
- package/screens.ts +0 -0
- package/smposer.json +17 -0
- package/tabs.d.ts +0 -0
- package/tabs.d.ts.map +0 -0
- package/tabs.js +0 -0
- package/tabs.js.map +0 -0
- package/tabs.ts +0 -0
- package/tsconfig.json +0 -0
- package/windows.d.ts +0 -0
- package/windows.d.ts.map +0 -0
- package/windows.js +0 -0
- package/windows.js.map +0 -0
- package/windows.ts +0 -0
|
@@ -23,7 +23,14 @@
|
|
|
23
23
|
"Bash(git gc:*)",
|
|
24
24
|
"Bash(git rm:*)",
|
|
25
25
|
"Bash(git reset:*)",
|
|
26
|
-
"Bash(git reflog:*)"
|
|
26
|
+
"Bash(git reflog:*)",
|
|
27
|
+
"Bash(git fetch:*)",
|
|
28
|
+
"Bash(git clone:*)",
|
|
29
|
+
"Bash(cmd /c \"cd /d Y:\\dev\\utils && git clone https://github.com/BobFrankston/winpos.git winpos-clean\")",
|
|
30
|
+
"Bash(git -C \"Y:\\dev\\utils\\winpos-clean\" count-objects -vH)",
|
|
31
|
+
"Bash(xcopy:*)",
|
|
32
|
+
"Bash(dir:*)",
|
|
33
|
+
"Bash(node:*)"
|
|
27
34
|
],
|
|
28
35
|
"deny": [],
|
|
29
36
|
"ask": []
|
package/.editorconfig
CHANGED
|
File without changes
|
package/.gitattributes
CHANGED
|
File without changes
|
package/.vscode/launch.json
CHANGED
|
File without changes
|
package/.vscode/settings.json
CHANGED
|
File without changes
|
package/.vscode/spellright.dict
CHANGED
|
File without changes
|
package/.vscode/tasks.json
CHANGED
|
File without changes
|
package/DEVELOPMENT.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -15,13 +15,13 @@ TypeScript implementation of WinPos - A Windows window positioning utility for m
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install @bobfrankston/
|
|
18
|
+
npm install @bobfrankston/winpos
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Or with Bun:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
bun add @bobfrankston/
|
|
24
|
+
bun add @bobfrankston/winpos
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Usage
|
|
@@ -30,34 +30,34 @@ bun add @bobfrankston/tswinpos
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
# Move window to screen 0 at position (100, 200)
|
|
33
|
-
|
|
33
|
+
winpos "Notepad" 100 200 0
|
|
34
34
|
|
|
35
35
|
# Move window using percentages
|
|
36
|
-
|
|
36
|
+
winpos "Chrome*" 50% 50% 1
|
|
37
37
|
|
|
38
38
|
# Set window size as well
|
|
39
|
-
|
|
39
|
+
winpos "Visual Studio Code" 0 0 0 1920 1080
|
|
40
40
|
|
|
41
41
|
# List all windows
|
|
42
|
-
|
|
42
|
+
winpos *
|
|
43
43
|
|
|
44
44
|
# Get screen count
|
|
45
|
-
|
|
45
|
+
winpos -c
|
|
46
46
|
|
|
47
47
|
# Debug mode
|
|
48
|
-
|
|
48
|
+
winpos -d "Firefox" 0 0 1
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### Pattern Matching
|
|
52
52
|
|
|
53
|
-
- **Exact match**: `
|
|
54
|
-
- **Prefix match**: `
|
|
55
|
-
- **Regex match**: `
|
|
53
|
+
- **Exact match**: `winpos "Notepad" 0 0 0`
|
|
54
|
+
- **Prefix match**: `winpos "Chrome*" 0 0 0`
|
|
55
|
+
- **Regex match**: `winpos "/Visual.*Code/" 0 0 0`
|
|
56
56
|
|
|
57
57
|
### As a Library
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
|
-
import { run } from '@bobfrankston/
|
|
60
|
+
import { run } from '@bobfrankston/winpos';
|
|
61
61
|
|
|
62
62
|
// Move a window programmatically
|
|
63
63
|
run(['Chrome*', '0', '0', '1', '1920', '1080']);
|
|
@@ -89,7 +89,7 @@ Screens are numbered starting from 0, sorted by position:
|
|
|
89
89
|
## Parameters
|
|
90
90
|
|
|
91
91
|
```
|
|
92
|
-
|
|
92
|
+
winpos [-d -c *] <window title> <x> <y> <screen> [<width> <height>]
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
- `<window title>` - Window title (exact, prefix with `*`, or regex with `/.../ `)
|
package/ffi-wrapper.d.ts
CHANGED
|
File without changes
|
package/ffi-wrapper.d.ts.map
CHANGED
|
File without changes
|
package/ffi-wrapper.js
CHANGED
|
File without changes
|
package/ffi-wrapper.js.map
CHANGED
|
File without changes
|
package/ffi-wrapper.ts
CHANGED
|
File without changes
|
package/ignores.txt
CHANGED
|
File without changes
|
package/index.d.ts
CHANGED
|
@@ -7,6 +7,22 @@ import { user32, RECT } from './ffi-wrapper.js';
|
|
|
7
7
|
import { enumerateScreens, sortScreens, ScreenInfo } from './screens.js';
|
|
8
8
|
import { enumerateWindows, findWindowsByPattern, WindowInfo, WindowState, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow } from './windows.js';
|
|
9
9
|
import { enumerateTabs, TabInfo, mightHaveTabs, TAB_ENUMERATION_NOTE } from './tabs.js';
|
|
10
|
+
export interface WindowConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
regex?: boolean;
|
|
13
|
+
pos?: {
|
|
14
|
+
x: number | string;
|
|
15
|
+
y: number | string;
|
|
16
|
+
screen?: number;
|
|
17
|
+
};
|
|
18
|
+
size?: {
|
|
19
|
+
w: number | string;
|
|
20
|
+
h: number | string;
|
|
21
|
+
};
|
|
22
|
+
minimize?: boolean;
|
|
23
|
+
maximize?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export type ConfigFile = WindowConfig | WindowConfig[];
|
|
10
26
|
export { RECT, ScreenInfo, WindowInfo, WindowState, TabInfo, enumerateScreens, sortScreens, enumerateWindows, findWindowsByPattern, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow, enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE, user32 };
|
|
11
27
|
/**
|
|
12
28
|
* Convert screen-relative coordinates to global coordinates
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EAEpB,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EAEpB,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMxF,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,IAAI,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,YAAY,EAAE,CAAC;AAkBvD,OAAO,EACH,IAAI,EACJ,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,MAAM,EACT,CAAC;AA8RF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAyBlG;AAmND,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAuHjC"}
|
package/index.js
CHANGED
|
@@ -7,11 +7,269 @@ import { user32 } from './ffi-wrapper.js';
|
|
|
7
7
|
import { enumerateScreens, sortScreens } from './screens.js';
|
|
8
8
|
import { enumerateWindows, findWindowsByPattern, loadIgnorePatterns, WindowState, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow } from './windows.js';
|
|
9
9
|
import { enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE } from './tabs.js';
|
|
10
|
-
import { join } from 'path';
|
|
10
|
+
import { join, resolve, isAbsolute } from 'path';
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
11
12
|
import * as packageJson from './package.json' with { type: 'json' };
|
|
12
13
|
// Re-export public API for library usage
|
|
13
14
|
export { WindowState, enumerateScreens, sortScreens, enumerateWindows, findWindowsByPattern, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow, enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE, user32 };
|
|
15
|
+
// WindowConfig and ConfigFile types are exported from interface definitions above
|
|
14
16
|
// Note: screenPosToPos is exported separately below (after the function definition)
|
|
17
|
+
/**
|
|
18
|
+
* Load window config from JSON file
|
|
19
|
+
*/
|
|
20
|
+
function loadConfigFile(filePath) {
|
|
21
|
+
const absPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
|
22
|
+
if (!existsSync(absPath)) {
|
|
23
|
+
throw new Error(`Config file not found: ${absPath}`);
|
|
24
|
+
}
|
|
25
|
+
const content = readFileSync(absPath, 'utf-8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Save window config to JSON file
|
|
30
|
+
* If file exists and contains array, merge; otherwise create/overwrite
|
|
31
|
+
*/
|
|
32
|
+
function saveConfigFile(filePath, config, merge = false) {
|
|
33
|
+
const absPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
|
34
|
+
let finalConfig;
|
|
35
|
+
if (merge && existsSync(absPath)) {
|
|
36
|
+
const existing = loadConfigFile(absPath);
|
|
37
|
+
if (Array.isArray(existing)) {
|
|
38
|
+
// Find and update existing entry with same name, or append
|
|
39
|
+
const idx = existing.findIndex(e => e.name === config.name);
|
|
40
|
+
if (idx >= 0) {
|
|
41
|
+
existing[idx] = config;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
existing.push(config);
|
|
45
|
+
}
|
|
46
|
+
finalConfig = existing;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Convert single entry to array
|
|
50
|
+
if (existing.name === config.name) {
|
|
51
|
+
finalConfig = config; // Replace single entry
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
finalConfig = [existing, config];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
finalConfig = config;
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(absPath, JSON.stringify(finalConfig, null, 2), 'utf-8');
|
|
62
|
+
console.log(`Config saved to: ${absPath}`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build WindowConfig from CLI options
|
|
66
|
+
*/
|
|
67
|
+
function buildConfigFromOptions(opts) {
|
|
68
|
+
if (!opts.windowTitle)
|
|
69
|
+
return null;
|
|
70
|
+
const config = {
|
|
71
|
+
name: opts.windowTitle
|
|
72
|
+
};
|
|
73
|
+
// Check if name is a regex pattern (enclosed in /.../)
|
|
74
|
+
if (config.name.startsWith('/') && config.name.endsWith('/')) {
|
|
75
|
+
config.regex = true;
|
|
76
|
+
config.name = config.name.slice(1, -1); // Store without delimiters
|
|
77
|
+
}
|
|
78
|
+
if (opts.pos) {
|
|
79
|
+
config.pos = { x: opts.pos.x, y: opts.pos.y };
|
|
80
|
+
if (opts.pos.screen !== undefined) {
|
|
81
|
+
config.pos.screen = opts.pos.screen;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (opts.size) {
|
|
85
|
+
config.size = { w: opts.size.w, h: opts.size.h };
|
|
86
|
+
}
|
|
87
|
+
if (opts.minimize) {
|
|
88
|
+
config.minimize = true;
|
|
89
|
+
}
|
|
90
|
+
if (opts.maximize) {
|
|
91
|
+
config.maximize = true;
|
|
92
|
+
}
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse CLI arguments into structured options
|
|
97
|
+
*/
|
|
98
|
+
function parseArgs(args) {
|
|
99
|
+
const opts = {
|
|
100
|
+
debug: false,
|
|
101
|
+
debugVerbose: false,
|
|
102
|
+
showCount: false,
|
|
103
|
+
positionalArgs: []
|
|
104
|
+
};
|
|
105
|
+
let i = 0;
|
|
106
|
+
while (i < args.length) {
|
|
107
|
+
const arg = args[i];
|
|
108
|
+
// Check for .json file as first non-flag arg (auto-load)
|
|
109
|
+
if (!arg.startsWith('-') && arg.toLowerCase().endsWith('.json') && !opts.loadFile) {
|
|
110
|
+
opts.loadFile = arg;
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (arg.startsWith('-')) {
|
|
115
|
+
const flag = arg.substring(1).toLowerCase();
|
|
116
|
+
switch (flag) {
|
|
117
|
+
case 'd':
|
|
118
|
+
opts.debug = true;
|
|
119
|
+
break;
|
|
120
|
+
case 'dbg':
|
|
121
|
+
opts.debugVerbose = true;
|
|
122
|
+
break;
|
|
123
|
+
case 'c':
|
|
124
|
+
opts.showCount = true;
|
|
125
|
+
break;
|
|
126
|
+
case 'version':
|
|
127
|
+
case 'v':
|
|
128
|
+
console.log(`winpos version ${packageJson.default.version}`);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
break;
|
|
131
|
+
case 'pos':
|
|
132
|
+
i++;
|
|
133
|
+
if (i < args.length) {
|
|
134
|
+
const parts = args[i].split(',');
|
|
135
|
+
opts.pos = { x: parts[0], y: parts[1] };
|
|
136
|
+
if (parts.length > 2) {
|
|
137
|
+
opts.pos.screen = parseInt(parts[2], 10);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case 'size':
|
|
142
|
+
i++;
|
|
143
|
+
if (i < args.length) {
|
|
144
|
+
const parts = args[i].split(',');
|
|
145
|
+
opts.size = { w: parts[0], h: parts[1] };
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
case 'load':
|
|
149
|
+
i++;
|
|
150
|
+
if (i < args.length) {
|
|
151
|
+
opts.loadFile = args[i];
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case 'save':
|
|
155
|
+
i++;
|
|
156
|
+
if (i < args.length) {
|
|
157
|
+
opts.saveFile = args[i];
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
case 'title':
|
|
161
|
+
i++;
|
|
162
|
+
if (i < args.length) {
|
|
163
|
+
opts.windowTitle = args[i];
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
case 'min':
|
|
167
|
+
opts.minimize = true;
|
|
168
|
+
break;
|
|
169
|
+
case 'max':
|
|
170
|
+
opts.maximize = true;
|
|
171
|
+
break;
|
|
172
|
+
case 'help':
|
|
173
|
+
case '?':
|
|
174
|
+
return opts; // Will trigger usage display
|
|
175
|
+
default:
|
|
176
|
+
// Unknown flag - could be negative number for position
|
|
177
|
+
if (/^-?\d+/.test(arg)) {
|
|
178
|
+
opts.positionalArgs.push(arg);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(`Unknown option: ${arg}`);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
opts.positionalArgs.push(arg);
|
|
188
|
+
}
|
|
189
|
+
i++;
|
|
190
|
+
}
|
|
191
|
+
// First positional arg is window title (if using new flag-based syntax and -title not set)
|
|
192
|
+
const usingFlagSyntax = opts.pos || opts.size || opts.loadFile || opts.saveFile || opts.minimize || opts.maximize;
|
|
193
|
+
if (opts.positionalArgs.length > 0 && usingFlagSyntax) {
|
|
194
|
+
if (!opts.windowTitle) {
|
|
195
|
+
opts.windowTitle = opts.positionalArgs[0];
|
|
196
|
+
opts.positionalArgs = opts.positionalArgs.slice(1);
|
|
197
|
+
}
|
|
198
|
+
// Error if stray positional args remain when using flag-based syntax
|
|
199
|
+
if (opts.positionalArgs.length > 0) {
|
|
200
|
+
console.error(`Error: unexpected arguments: ${opts.positionalArgs.join(' ')}`);
|
|
201
|
+
console.error('When using flags, only window title should be positional');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return opts;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Apply a single window config
|
|
209
|
+
*/
|
|
210
|
+
function applyWindowConfig(config, screensInfo) {
|
|
211
|
+
// Build pattern for matching
|
|
212
|
+
let pattern;
|
|
213
|
+
if (config.regex) {
|
|
214
|
+
pattern = `/${config.name}/`;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
pattern = config.name;
|
|
218
|
+
}
|
|
219
|
+
let matchedWindows = findWindowsByPattern(pattern);
|
|
220
|
+
matchedWindows = matchedWindows.filter(w => !w.title.toLowerCase().includes('tswinpos'));
|
|
221
|
+
if (matchedWindows.length === 0) {
|
|
222
|
+
console.log(`No windows found matching '${config.name}'`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Handle minimize
|
|
226
|
+
if (config.minimize) {
|
|
227
|
+
for (const window of matchedWindows) {
|
|
228
|
+
if (dbgVerbose)
|
|
229
|
+
console.log(`[DBG] Minimizing: "${window.title}"`);
|
|
230
|
+
minimizeWindow(window.hwnd);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Handle maximize
|
|
235
|
+
if (config.maximize) {
|
|
236
|
+
for (const window of matchedWindows) {
|
|
237
|
+
if (dbgVerbose)
|
|
238
|
+
console.log(`[DBG] Maximizing: "${window.title}"`);
|
|
239
|
+
maximizeWindow(window.hwnd);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Handle positioning
|
|
244
|
+
if (!config.pos)
|
|
245
|
+
return;
|
|
246
|
+
const screenIndex = config.pos.screen ?? 0;
|
|
247
|
+
if (screenIndex < 0 || screenIndex >= screensInfo.length) {
|
|
248
|
+
console.log(`Invalid screen ${screenIndex} for window '${config.name}'`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const targetScreen = screensInfo[screenIndex];
|
|
252
|
+
const bounds = targetScreen.bounds;
|
|
253
|
+
const x = parseDimension(String(config.pos.x), bounds.Width) + bounds.Left;
|
|
254
|
+
const y = parseDimension(String(config.pos.y), bounds.Height) + bounds.Top;
|
|
255
|
+
let width = config.size ? parseDimension(String(config.size.w), bounds.Width) : 0;
|
|
256
|
+
let height = config.size ? parseDimension(String(config.size.h), bounds.Height) : 0;
|
|
257
|
+
for (const window of matchedWindows) {
|
|
258
|
+
let w = width, h = height;
|
|
259
|
+
if (w === 0)
|
|
260
|
+
w = window.rect.Right - window.rect.Left;
|
|
261
|
+
if (h === 0)
|
|
262
|
+
h = window.rect.Bottom - window.rect.Top;
|
|
263
|
+
if (dbgVerbose) {
|
|
264
|
+
console.log(`[DBG] Window: "${window.title}" -> Position: (${x}, ${y}) Size: ${w}x${h} Screen: ${screenIndex}`);
|
|
265
|
+
}
|
|
266
|
+
if (isMinimized(window.hwnd) || isMaximized(window.hwnd)) {
|
|
267
|
+
restoreWindow(window.hwnd);
|
|
268
|
+
}
|
|
269
|
+
user32.MoveWindow(window.hwnd, x, y, w, h, true);
|
|
270
|
+
user32.SetForegroundWindow(window.hwnd);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
15
273
|
let dbg = false;
|
|
16
274
|
let dbgVerbose = false; // -dbg flag for detailed output
|
|
17
275
|
let screens = [];
|
|
@@ -125,6 +383,27 @@ function getScreenForWindow(windowRect) {
|
|
|
125
383
|
return screens.findIndex(s => s.primary) || 0;
|
|
126
384
|
}
|
|
127
385
|
const padn = (n, p = 6) => n.toString().padStart(p);
|
|
386
|
+
/**
|
|
387
|
+
* Show position info for windows matching pattern
|
|
388
|
+
*/
|
|
389
|
+
function showWindowInfo(pattern) {
|
|
390
|
+
let matchedWindows = findWindowsByPattern(pattern);
|
|
391
|
+
matchedWindows = matchedWindows.filter(w => !w.title.toLowerCase().includes('tswinpos'));
|
|
392
|
+
if (matchedWindows.length === 0) {
|
|
393
|
+
console.log(`No windows found matching '${pattern}'`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
for (const window of matchedWindows) {
|
|
397
|
+
const screenIndex = getScreenForWindow(window.rect);
|
|
398
|
+
const screen = screens[screenIndex];
|
|
399
|
+
const x = window.rect.Left - screen.bounds.Left;
|
|
400
|
+
const y = window.rect.Top - screen.bounds.Top;
|
|
401
|
+
const width = window.rect.Right - window.rect.Left;
|
|
402
|
+
const height = window.rect.Bottom - window.rect.Top;
|
|
403
|
+
console.log(`${window.title}`);
|
|
404
|
+
console.log(` pos: ${x},${y},${screenIndex} size: ${width},${height}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
128
407
|
function listAllWindows(includeIgnored = false) {
|
|
129
408
|
console.log('====================================');
|
|
130
409
|
console.log('Screens');
|
|
@@ -157,23 +436,32 @@ function listAllWindows(includeIgnored = false) {
|
|
|
157
436
|
function usage(msg = '') {
|
|
158
437
|
if (msg)
|
|
159
438
|
console.log(msg);
|
|
160
|
-
console.log('Usage: winpos [
|
|
161
|
-
console.log(' winpos <
|
|
162
|
-
console.log('
|
|
163
|
-
console.log('
|
|
164
|
-
console.log('
|
|
165
|
-
console.log('
|
|
166
|
-
console.log(
|
|
167
|
-
console.log('
|
|
168
|
-
console.log('
|
|
169
|
-
console.log('
|
|
170
|
-
console.log('
|
|
439
|
+
console.log('Usage: winpos [options] <title> <x> <y> <screen> [<width> <height>]');
|
|
440
|
+
console.log(' winpos [options] <title> -pos x,y[,screen] [-size w,h]');
|
|
441
|
+
console.log(' winpos [options] -title <name> -pos x,y[,screen] [-size w,h]');
|
|
442
|
+
console.log(' winpos <title> min | -min');
|
|
443
|
+
console.log(' winpos <config.json>');
|
|
444
|
+
console.log(' winpos -load <config.json> [-save <output.json>]');
|
|
445
|
+
console.log();
|
|
446
|
+
console.log('Options:');
|
|
447
|
+
console.log(' -d Debug mode');
|
|
448
|
+
console.log(' -dbg Verbose debug output');
|
|
449
|
+
console.log(' -c Return screen count as exit code');
|
|
450
|
+
console.log(' -v/-version Show version');
|
|
451
|
+
console.log(' -title name Window title/pattern (alternative to positional)');
|
|
452
|
+
console.log(' -pos x,y[,s] Position (x,y with optional screen index)');
|
|
453
|
+
console.log(' -size w,h Window size (width,height)');
|
|
454
|
+
console.log(' -min Minimize window');
|
|
455
|
+
console.log(' -max Maximize window');
|
|
456
|
+
console.log(' -load file Load config from JSON file');
|
|
457
|
+
console.log(' -save file Save config to JSON file (merges if file exists)');
|
|
171
458
|
console.log();
|
|
172
|
-
console.log('
|
|
173
|
-
console.log('
|
|
174
|
-
console.log('
|
|
175
|
-
console.log(
|
|
176
|
-
console.log('
|
|
459
|
+
console.log('Position/size values can be pixels or percentages (e.g. 50%)');
|
|
460
|
+
console.log('Title can be regex (/pattern/) or prefix (title*)');
|
|
461
|
+
console.log('* = list windows, ** = list all including ignored');
|
|
462
|
+
console.log();
|
|
463
|
+
console.log('JSON format: {"name":"app","regex":false,"pos":{"x":0,"y":0,"screen":0},"size":{"w":800,"h":600}}');
|
|
464
|
+
console.log(' or array: [{...}, {...}]');
|
|
177
465
|
console.log();
|
|
178
466
|
console.log('Screen | X Y | W H ');
|
|
179
467
|
console.log('------------------------------------');
|
|
@@ -181,8 +469,6 @@ function usage(msg = '') {
|
|
|
181
469
|
const screen = screens[i];
|
|
182
470
|
console.log(`${i.toString().padStart(6)} | ${screen.bounds.Left.toString().padStart(5)} ${screen.bounds.Top.toString().padStart(5)} | ${screen.bounds.Width.toString().padStart(5)} ${screen.bounds.Height.toString().padStart(5)}`);
|
|
183
471
|
}
|
|
184
|
-
console.log();
|
|
185
|
-
console.log('0-Lower right, 1-Lower Left, 2-Upper left, 3-Upper right');
|
|
186
472
|
process.exit(1);
|
|
187
473
|
}
|
|
188
474
|
export function run(args) {
|
|
@@ -200,56 +486,94 @@ export function run(args) {
|
|
|
200
486
|
// Load ignore patterns from the same directory as the script
|
|
201
487
|
const ignoresPath = join(import.meta.dirname, 'ignores.txt');
|
|
202
488
|
loadIgnorePatterns(ignoresPath);
|
|
203
|
-
// Parse
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
489
|
+
// Parse arguments using new parser
|
|
490
|
+
const opts = parseArgs(args);
|
|
491
|
+
// Set debug flags
|
|
492
|
+
dbg = opts.debug;
|
|
493
|
+
dbgVerbose = opts.debugVerbose;
|
|
494
|
+
if (dbg)
|
|
495
|
+
console.log('Debug mode enabled.');
|
|
496
|
+
if (dbgVerbose)
|
|
497
|
+
console.log(`[DBG] winpos version ${packageJson.default.version}`);
|
|
498
|
+
// Handle -c (screen count)
|
|
499
|
+
if (opts.showCount) {
|
|
500
|
+
console.log(screens.length);
|
|
501
|
+
process.exit(screens.length);
|
|
502
|
+
}
|
|
503
|
+
// Handle load file (JSON config)
|
|
504
|
+
if (opts.loadFile) {
|
|
505
|
+
try {
|
|
506
|
+
const config = loadConfigFile(opts.loadFile);
|
|
507
|
+
const configs = Array.isArray(config) ? config : [config];
|
|
508
|
+
// If -save specified with CLI params, merge new config into loaded
|
|
509
|
+
if (opts.saveFile && opts.windowTitle) {
|
|
510
|
+
const newConfig = buildConfigFromOptions(opts);
|
|
511
|
+
if (newConfig) {
|
|
512
|
+
// Apply the CLI-specified window first
|
|
513
|
+
applyWindowConfig(newConfig, screens);
|
|
514
|
+
// Save merged config
|
|
515
|
+
saveConfigFile(opts.saveFile, newConfig, true);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// Apply all loaded configs
|
|
519
|
+
for (const cfg of configs) {
|
|
520
|
+
applyWindowConfig(cfg, screens);
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
console.error(`Error loading config: ${e.message}`);
|
|
526
|
+
process.exit(1);
|
|
227
527
|
}
|
|
228
|
-
args = args.slice(1);
|
|
229
528
|
}
|
|
230
|
-
// Handle
|
|
231
|
-
|
|
529
|
+
// Handle new flag-based syntax (-pos, -size, -min, -max)
|
|
530
|
+
if (opts.pos || opts.size || opts.minimize || opts.maximize) {
|
|
531
|
+
if (!opts.windowTitle) {
|
|
532
|
+
usage('Window title required when using -pos, -size, -min, or -max');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const config = buildConfigFromOptions(opts);
|
|
536
|
+
if (config) {
|
|
537
|
+
applyWindowConfig(config, screens);
|
|
538
|
+
// Save if requested
|
|
539
|
+
if (opts.saveFile) {
|
|
540
|
+
saveConfigFile(opts.saveFile, config, existsSync(opts.saveFile));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// If only -title provided (no -pos/-size/-min/-max), show window position info
|
|
546
|
+
if (opts.windowTitle && !opts.loadFile) {
|
|
547
|
+
showWindowInfo(opts.windowTitle);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
// Fall back to legacy positional argument handling
|
|
551
|
+
const posArgs = opts.positionalArgs;
|
|
232
552
|
if (dbg)
|
|
233
|
-
console.log(`DEBUG[${packageJson.default.version}]: ${
|
|
234
|
-
if (
|
|
553
|
+
console.log(`DEBUG[${packageJson.default.version}]: ${posArgs.join(' ')}`);
|
|
554
|
+
if (posArgs.length === 1 && posArgs[0] === '*') {
|
|
235
555
|
listAllWindows();
|
|
236
556
|
return;
|
|
237
557
|
}
|
|
238
|
-
if (
|
|
558
|
+
if (posArgs.length === 1 && posArgs[0] === '**') {
|
|
239
559
|
listAllWindows(true);
|
|
240
560
|
return;
|
|
241
561
|
}
|
|
242
562
|
// Check for minimize command (title + min/-min)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
563
|
+
if (posArgs.length === 2 && (posArgs[1].toLowerCase() === 'min' || posArgs[1].toLowerCase() === '-min')) {
|
|
564
|
+
adjustWindow(posArgs);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
// Single positional arg = show window info
|
|
568
|
+
if (posArgs.length === 1) {
|
|
569
|
+
showWindowInfo(posArgs[0]);
|
|
246
570
|
return;
|
|
247
571
|
}
|
|
248
|
-
if (
|
|
572
|
+
if (posArgs.length < 4) {
|
|
249
573
|
usage();
|
|
250
574
|
return;
|
|
251
575
|
}
|
|
252
|
-
adjustWindow(
|
|
576
|
+
adjustWindow(posArgs);
|
|
253
577
|
}
|
|
254
578
|
// CLI entry point using import.meta.main
|
|
255
579
|
if (import.meta.main) {
|