├── src
├── vite-env.d.ts
├── assets
│ ├── icon.png
│ └── icon.svg
├── main.tsx
├── components
│ ├── frameComponents
│ │ ├── MonitorFrame.tsx
│ │ ├── WindowFrame.tsx
│ │ └── IntegratedFrame.tsx
│ ├── configComponents
│ │ ├── autoSummon
│ │ │ ├── SwitchConfElm.tsx
│ │ │ ├── AutoSummonConfElm.tsx
│ │ │ ├── ShortcutConfElm.tsx
│ │ │ ├── SizeConfElm.tsx
│ │ │ └── ThresholdConfElm.tsx
│ │ ├── DragTermMarginConfElm.tsx
│ │ ├── WinWinMapSummonConfElm.tsx
│ │ ├── CursorConfigElm.tsx
│ │ ├── SummonPosConfElm.tsx
│ │ └── ConfigElm.tsx
│ ├── listComponents
│ │ ├── ListComponent.tsx
│ │ └── FrameRowComponent.tsx
│ └── AppMain.tsx
├── winwin-type.ts
├── util.ts
├── App.tsx
├── App.css
├── app-store.ts
├── drag-util.ts
├── style.css
├── fetch-objects.ts
└── hooks
│ ├── frame-hook.ts
│ ├── app-hook.ts
│ └── config-hook.ts
├── src-tauri
├── build.rs
├── .gitignore
├── src
│ ├── lib.rs
│ ├── winapi
│ │ ├── mod.rs
│ │ ├── mouse.rs
│ │ ├── monitors.rs
│ │ ├── winprocess.rs
│ │ ├── virtual_desktop.rs
│ │ ├── structs.rs
│ │ ├── winevents.rs
│ │ └── winwins.rs
│ ├── log.rs
│ ├── backend.rs
│ ├── util.rs
│ └── main.rs
├── icons
│ ├── icon.ico
│ ├── icon.png
│ ├── 32x32.png
│ ├── icon.icns
│ ├── 128x128.png
│ ├── StoreLogo.png
│ ├── 128x128@2x.png
│ ├── Square30x30Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ └── Square310x310Logo.png
├── examples
│ ├── move_cursor.rs
│ ├── get_monitors.rs
│ ├── get_windows.rs
│ ├── move_windows.rs
│ └── get_events.rs
├── Cargo.toml
└── tauri.conf.json
├── .github
├── winwinmap1.png
├── winwinmap_demo.gif
├── winwinmap2_configopen.png
└── workflows
│ └── build.yaml
├── .vscode
└── extensions.json
├── tsconfig.node.json
├── .gitignore
├── index.html
├── tsconfig.json
├── package.json
├── LICENSE.md
├── vite.config.ts
├── public
├── vite.svg
└── tauri.svg
├── README.md
└── yarn.lock
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
--------------------------------------------------------------------------------
/src-tauri/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod backend;
2 | pub mod log;
3 | pub mod util;
4 | pub mod winapi;
5 |
--------------------------------------------------------------------------------
/src/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src/assets/icon.png
--------------------------------------------------------------------------------
/.github/winwinmap1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap1.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/.github/winwinmap_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap_demo.gif
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/.github/winwinmap2_configopen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap2_configopen.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/src-tauri/examples/move_cursor.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use win_win_map::winapi::mouse::set_cursor_pos;
3 |
4 | fn main() -> Result<()> {
5 | set_cursor_pos(10, 10)?;
6 |
7 | Ok(())
8 | }
9 |
--------------------------------------------------------------------------------
/src-tauri/examples/get_monitors.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use win_win_map::winapi::Canvas;
3 |
4 | fn main() -> Result<()> {
5 | let canvas = Canvas::new()?;
6 |
7 | println!("{:?}", canvas);
8 |
9 | Ok(())
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/src-tauri/src/winapi/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod monitors;
2 | pub mod mouse;
3 | pub mod structs;
4 | pub mod virtual_desktop;
5 | pub mod winevents;
6 | pub mod winprocess;
7 | pub mod winwins;
8 |
9 | pub use structs::Canvas;
10 | pub use structs::WinInfo;
11 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./style.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/frameComponents/MonitorFrame.tsx:
--------------------------------------------------------------------------------
1 | export interface MonitorFrameProps {
2 | width: number;
3 | height: number;
4 | left: number;
5 | top: number;
6 | }
7 |
8 | export const MonitorFrame = (props: MonitorFrameProps) => {
9 | return
;
10 | };
11 |
12 | export default MonitorFrame;
13 |
--------------------------------------------------------------------------------
/src-tauri/src/winapi/mouse.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
3 |
4 | pub fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
5 | let result = unsafe { SetCursorPos(x, y) };
6 | if result.as_bool() {
7 | Ok(())
8 | } else {
9 | Err(anyhow!("SetCursorPos failed"))
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tauri + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/winwin-type.ts:
--------------------------------------------------------------------------------
1 | export interface WinInfo {
2 | hwnd: number;
3 | title: string;
4 | exe_name?: string;
5 | left: number;
6 | top: number;
7 | width: number;
8 | height: number;
9 | is_foreground: boolean;
10 | }
11 |
12 | export interface Rect {
13 | left: number;
14 | top: number;
15 | right: number;
16 | bottom: number;
17 | }
18 |
19 | export interface Canvas {
20 | min_x: number;
21 | min_y: number;
22 | max_x: number;
23 | max_y: number;
24 | monitors: Rect[];
25 | }
26 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | export const constructShortcut = (
2 | e: React.KeyboardEvent
3 | ): string => {
4 | const shortcut_list = [];
5 | if (e.altKey) {
6 | shortcut_list.push("Alt");
7 | }
8 | if (e.ctrlKey) {
9 | shortcut_list.push("Control");
10 | }
11 | if (e.shiftKey) {
12 | shortcut_list.push("Shift");
13 | }
14 | /*
15 | if (e.metaKey) {
16 | shortcut_list.push("Meta");
17 | }
18 | */
19 |
20 | if (shortcut_list.indexOf(e.key) == -1) {
21 | shortcut_list.push(e.key);
22 | }
23 |
24 | const shortcut = shortcut_list.join("+");
25 |
26 | return shortcut;
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/frameComponents/WindowFrame.tsx:
--------------------------------------------------------------------------------
1 | export interface WindowFrameProps {
2 | width: number;
3 | height: number;
4 | left: number;
5 | top: number;
6 | is_target: boolean;
7 | is_active: boolean;
8 | onClick: (e: React.MouseEvent) => void;
9 | }
10 |
11 | const WindowFrame = (props: WindowFrameProps) => {
12 | return (
13 |
20 | );
21 | };
22 |
23 | export default WindowFrame;
24 |
--------------------------------------------------------------------------------
/src/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["src/*"]
21 | }
22 | },
23 | "include": ["src/**/*", "hooks/config_hook.ts"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/configComponents/autoSummon/SwitchConfElm.tsx:
--------------------------------------------------------------------------------
1 | import { Config, ConfigMethods } from "@/hooks/config-hook";
2 | import Switch from "@mui/material/Switch";
3 |
4 | export interface AutoSummonSwitchElmProps {
5 | config: Config;
6 | configMethods: ConfigMethods;
7 | }
8 |
9 | const AutoSummonSwitchElm = ({
10 | config,
11 | configMethods,
12 | }: AutoSummonSwitchElmProps) => {
13 | const handleChange = (event: React.ChangeEvent) => {
14 | configMethods.setAutoSummonEnabled(event.target.checked);
15 | };
16 |
17 | return (
18 | <>
19 | {"Auto Summon: "}
20 |
21 | >
22 | );
23 | };
24 |
25 | export default AutoSummonSwitchElm;
26 |
--------------------------------------------------------------------------------
/src/components/configComponents/DragTermMarginConfElm.tsx:
--------------------------------------------------------------------------------
1 | import TextField from "@mui/material/TextField";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 |
4 | interface DragTermMarginConfElmProps {
5 | config: Config;
6 | configMethods: ConfigMethods;
7 | }
8 |
9 | const DragTermMarginConfElm = ({
10 | config,
11 | configMethods,
12 | }: DragTermMarginConfElmProps) => {
13 | return (
14 | {
19 | configMethods.setDragTermMargin(Number(e.target.value));
20 | }}
21 | inputProps={{
22 | step: 1,
23 | type: "number",
24 | }}
25 | />
26 | );
27 | };
28 |
29 | export default DragTermMarginConfElm;
30 |
--------------------------------------------------------------------------------
/src-tauri/examples/get_windows.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use win_win_map::util::Com;
3 | use win_win_map::winapi::virtual_desktop::VirtualDesktopManager;
4 | use win_win_map::winapi::WinInfo;
5 |
6 | fn main() -> Result<()> {
7 | let _com = Com::new()?;
8 | let vdm = VirtualDesktopManager::new()?;
9 | let wininfo_vec = WinInfo::get_windows_info()?;
10 |
11 | for wininfo in wininfo_vec {
12 | let this_disp = vdm.is_window_on_current_desktop(wininfo.hwnd)?;
13 | let sign = if this_disp { " " } else { "~" };
14 | println!("{}{:?}", sign, wininfo);
15 | }
16 |
17 | println!("====================");
18 |
19 | let this_display_windows = WinInfo::get_windows_info_filtered()?;
20 |
21 | for wininfo in this_display_windows {
22 | println!(" {:?}", wininfo);
23 | }
24 |
25 | WinInfo::drop_vdmth()?;
26 |
27 | Ok(())
28 | }
29 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import AppMain from "@/components/AppMain";
3 | import { useMemo } from "react";
4 | import { createTheme, ThemeProvider } from "@mui/material/styles";
5 | import useMediaQuery from "@mui/material/useMediaQuery";
6 | import { Provider } from "react-redux";
7 | import store from "@/app-store";
8 |
9 | const App = () => {
10 | // https://amateur-engineer.com/react-mui-dark-mode/
11 | const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
12 | const theme = useMemo(
13 | () =>
14 | createTheme({
15 | palette: {
16 | mode: prefersDarkMode ? "dark" : "light",
17 | },
18 | }),
19 | [prefersDarkMode]
20 | );
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "win-win-map",
3 | "private": true,
4 | "version": "0.0.4",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview",
10 | "tauri": "tauri"
11 | },
12 | "dependencies": {
13 | "@emotion/react": "^11.10.5",
14 | "@emotion/styled": "^11.10.5",
15 | "@mui/icons-material": "^5.11.0",
16 | "@mui/material": "^5.11.8",
17 | "@reduxjs/toolkit": "^1.9.3",
18 | "@tauri-apps/api": "^1.2.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-draggable": "^4.4.5",
22 | "react-redux": "^8.0.5",
23 | "redux": "^4.2.1"
24 | },
25 | "devDependencies": {
26 | "@tauri-apps/cli": "^1.2.2",
27 | "@types/node": "^18.7.10",
28 | "@types/react": "^18.0.15",
29 | "@types/react-dom": "^18.0.6",
30 | "@vitejs/plugin-react": "^3.0.0",
31 | "typescript": "^4.6.4",
32 | "vite": "^4.0.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src-tauri/src/log.rs:
--------------------------------------------------------------------------------
1 | use env_logger::fmt::Color;
2 | use log::Level::*;
3 | use std::io::Write;
4 |
5 | pub fn env_logger_init() {
6 | env_logger::builder()
7 | .format(|buf, record| {
8 | let mut level_style = buf.style();
9 | let color = match record.level() {
10 | Error => Color::Red,
11 | Warn => Color::Yellow,
12 | Info => Color::Green,
13 | Debug => Color::Blue,
14 | Trace => Color::Magenta,
15 | };
16 | level_style.set_color(color).set_bold(true);
17 |
18 | let ts = buf.timestamp();
19 | writeln!(
20 | buf,
21 | "[{} {} {} @{}:{}] {}",
22 | ts,
23 | level_style.value(record.level()),
24 | record.target(),
25 | record.file().unwrap_or("*"),
26 | record.line().unwrap_or(0),
27 | record.args(),
28 | )
29 | })
30 | .init();
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2023 (c) namnium (anotherhollow1125) https://www.namnium.work/
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | alias: [{ find: "@", replacement: "/src" }],
9 | },
10 |
11 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
12 | // prevent vite from obscuring rust errors
13 | clearScreen: false,
14 | // tauri expects a fixed port, fail if that port is not available
15 | server: {
16 | port: 1420,
17 | strictPort: true,
18 | },
19 | // to make use of `TAURI_DEBUG` and other env variables
20 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
21 | envPrefix: ["VITE_", "TAURI_"],
22 | build: {
23 | // Tauri supports es2021
24 | target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
25 | // don't minify for debug builds
26 | minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
27 | // produce sourcemaps for debug builds
28 | sourcemap: !!process.env.TAURI_DEBUG,
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .mapZone {
2 | top: 0;
3 | z-index: 1;
4 | padding: 8px;
5 | background-color: #f6f6f6;
6 | }
7 |
8 | .object {
9 | background-color: transparent;
10 | box-sizing: border-box;
11 | }
12 |
13 | .object.child {
14 | position: absolute;
15 | }
16 |
17 | .object.base {
18 | position: relative;
19 | border: 1px solid red;
20 | }
21 |
22 | .disabled {
23 | opacity: 0.5;
24 | }
25 |
26 | .object.monitor {
27 | border: 1px solid blue;
28 | background-color: rgba(0, 0, 255, 0.1);
29 | }
30 |
31 | .object.window {
32 | border: 1px solid black;
33 | /* border-top: 5px solid black; */
34 | }
35 |
36 | .object.window.active {
37 | background-color: rgba(0, 0, 0, 0.1);
38 | }
39 |
40 | .object.window.target {
41 | border-top: 5px solid black;
42 | }
43 |
44 | @media (prefers-color-scheme: dark) {
45 | .mapZone {
46 | background-color: #2f2f2f;
47 | }
48 | .object.window {
49 | border-color: white;
50 | }
51 | .object.window.active {
52 | background-color: rgba(255, 255, 255, 0.1);
53 | }
54 | .object.window.target {
55 | border-top-color: white;
56 | }
57 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | release:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | platform: [windows-latest]
14 | runs-on: ${{ matrix.platform }}
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v2
18 |
19 | - name: Node.js setup
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: 18
23 |
24 | - name: Rust setup
25 | uses: actions-rs/toolchain@v1
26 | with:
27 | toolchain: stable
28 |
29 | - name: Install app dependencies and build web
30 | run: yarn && yarn build
31 |
32 | - name: Build the app
33 | uses: tauri-apps/tauri-action@v0.3
34 |
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 | with:
38 | tagName: v__VERSION__ # tauri-action replaces \_\_VERSION\_\_ with the app version
39 | releaseName: "v__VERSION__"
40 | releaseBody: "See the assets to download this version and install."
41 | releaseDraft: true
42 | prerelease: false
--------------------------------------------------------------------------------
/src-tauri/examples/move_windows.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use dialoguer::{console::Term, theme::ColorfulTheme, Select};
3 | use win_win_map::util::Com;
4 | use win_win_map::winapi::virtual_desktop::VirtualDesktopManager;
5 | use win_win_map::winapi::WinInfo;
6 |
7 | fn main() -> Result<()> {
8 | let _com = Com::new()?;
9 | let vdm = VirtualDesktopManager::new()?;
10 | let wininfo_vec = WinInfo::get_windows_info()?
11 | .into_iter()
12 | .filter(|wi| vdm.is_window_on_current_desktop(wi.hwnd).unwrap_or(false))
13 | .collect::>();
14 |
15 | let selection = Select::with_theme(&ColorfulTheme::default())
16 | .with_prompt("Select a window")
17 | .items(&wininfo_vec)
18 | .default(0)
19 | .interact_on_opt(&Term::stderr())
20 | .unwrap();
21 |
22 | match selection {
23 | Some(i) => {
24 | let wi = &wininfo_vec[i];
25 | println!("Move and Resize {} to (20, 20, 200, 100)", wi);
26 | wi.move_and_resize(20, 20, 200, 100)?;
27 | // wi.set_foreground()?;
28 | }
29 | None => {
30 | println!("No window selected");
31 | }
32 | }
33 |
34 | Ok(())
35 | }
36 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/configComponents/autoSummon/AutoSummonConfElm.tsx:
--------------------------------------------------------------------------------
1 | import SwitchConfElm from "@/components/configComponents/autoSummon/SwitchConfElm";
2 | import ThresholdConfElm from "@/components/configComponents/autoSummon/ThresholdConfElm";
3 | import SizeConfElm from "@/components/configComponents/autoSummon/SizeConfElm";
4 | import AutoSummonShortcutConfElm from "@/components/configComponents/autoSummon/ShortcutConfElm";
5 | import Grid from "@mui/material/Grid";
6 | import { Config, ConfigMethods } from "@/hooks/config-hook";
7 |
8 | export interface AutoSummonConfElmProps {
9 | config: Config;
10 | configMethods: ConfigMethods;
11 | }
12 |
13 | const AutoSummonConfElm = ({
14 | config,
15 | configMethods,
16 | }: AutoSummonConfElmProps) => {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default AutoSummonConfElm;
39 |
--------------------------------------------------------------------------------
/src/components/configComponents/WinWinMapSummonConfElm.tsx:
--------------------------------------------------------------------------------
1 | import TextField from "@mui/material/TextField";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 | import { IconButton } from "@mui/material";
4 | import ClearIcon from "@mui/icons-material/Clear";
5 | import { constructShortcut } from "@/util";
6 |
7 | interface ConfigElmProps {
8 | config: Config;
9 | configMethods: ConfigMethods;
10 | }
11 |
12 | const WinWinMapSummonConfElm = ({ config, configMethods }: ConfigElmProps) => {
13 | const handleShortcut = async (e: React.KeyboardEvent) => {
14 | const shortcut = constructShortcut(e);
15 | configMethods.setWinWinMapSummonShortcut(shortcut);
16 | };
17 |
18 | const resetShortcut = () => {
19 | configMethods.setWinWinMapSummonShortcut("");
20 | };
21 |
22 | return (
23 | <>
24 |
36 |
37 |
38 | ),
39 | }}
40 | />
41 | >
42 | );
43 | };
44 |
45 | export default WinWinMapSummonConfElm;
46 |
--------------------------------------------------------------------------------
/src/app-store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit";
2 |
3 | export type DragStateEnum = "dragging" | "idling";
4 |
5 | export const dragStateSlice = createSlice({
6 | name: "dragState",
7 | initialState: {
8 | state: "idling" as DragStateEnum,
9 | handle: (st: DragStateEnum) => {},
10 | },
11 | reducers: {
12 | setDragging: (state) => {
13 | state.state = "dragging";
14 | state.handle(state.state);
15 | },
16 | setIdling: (state) => {
17 | state.state = "idling";
18 | state.handle(state.state);
19 | },
20 | setHandle: (state, handle: PayloadAction<(st: DragStateEnum) => void>) => {
21 | state.handle = handle.payload;
22 | },
23 | },
24 | });
25 |
26 | // TODO: app-hookの内容をこちらに一部移動する
27 | // 正直したほうがいいかしないべきか迷ってる
28 |
29 | export const { setDragging, setIdling, setHandle } = dragStateSlice.actions;
30 | // export default dragStateSlice.reducer;
31 |
32 | const store = configureStore({
33 | reducer: {
34 | dragState: dragStateSlice.reducer,
35 | },
36 | middleware: (getDefaultMiddleware) =>
37 | getDefaultMiddleware({
38 | serializableCheck: {
39 | ignoredActions: ["dragState/setHandle"],
40 | // ignoredActionPaths: ["payload"],
41 | ignoredPaths: ["dragState.handle"],
42 | },
43 | }),
44 | });
45 |
46 | export default store;
47 | export type DragState = ReturnType;
48 | export type AppDispatch = typeof store.dispatch;
49 |
--------------------------------------------------------------------------------
/src/components/configComponents/autoSummon/ShortcutConfElm.tsx:
--------------------------------------------------------------------------------
1 | import TextField from "@mui/material/TextField";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 | import { IconButton } from "@mui/material";
4 | import ClearIcon from "@mui/icons-material/Clear";
5 | import { constructShortcut } from "@/util";
6 |
7 | interface AutoSummonShortcutConfElmProps {
8 | config: Config;
9 | configMethods: ConfigMethods;
10 | }
11 |
12 | const AutoSummonShortcutConfElm = ({
13 | config,
14 | configMethods,
15 | }: AutoSummonShortcutConfElmProps) => {
16 | const handleShortcut = async (e: React.KeyboardEvent) => {
17 | const shortcut = constructShortcut(e);
18 | configMethods.setAutoSummonShortcut(shortcut);
19 | };
20 |
21 | const resetShortcut = () => {
22 | configMethods.setAutoSummonShortcut("");
23 | };
24 |
25 | return (
26 | <>
27 |
39 |
40 |
41 | ),
42 | }}
43 | />
44 | >
45 | );
46 | };
47 |
48 | export default AutoSummonShortcutConfElm;
49 |
--------------------------------------------------------------------------------
/src/components/configComponents/CursorConfigElm.tsx:
--------------------------------------------------------------------------------
1 | import TextField from "@mui/material/TextField";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 | import { IconButton } from "@mui/material";
4 | import ClearIcon from "@mui/icons-material/Clear";
5 | import { constructShortcut } from "@/util";
6 | // import MouseIcon from "@mui/icons-material/Mouse";
7 |
8 | interface CursorConfigElmProps {
9 | config: Config;
10 | configMethods: ConfigMethods;
11 | }
12 |
13 | const CursorConfigElm = ({ config, configMethods }: CursorConfigElmProps) => {
14 | const handleShortcut = async (e: React.KeyboardEvent) => {
15 | const shortcut = constructShortcut(e);
16 | configMethods.setSummonMouseCursorShortcut(shortcut);
17 | };
18 |
19 | const resetShortcut = () => {
20 | configMethods.setSummonMouseCursorShortcut("");
21 | };
22 |
23 | return (
24 | <>
25 |
37 |
38 |
39 | ),
40 | }}
41 | />
42 | >
43 | );
44 | };
45 |
46 | export default CursorConfigElm;
47 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win-win-map"
3 | version = "0.0.4"
4 | description = "A Tauri App"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | edition = "2021"
9 | rust-version = "1.57"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [build-dependencies]
14 | tauri-build = { version = "1.2", features = [] }
15 |
16 | [dependencies]
17 | serde_json = "1.0"
18 | serde = { version = "1.0", features = ["derive"] }
19 | tauri = { version = "1.2", features = ["app-all", "fs-all", "global-shortcut-all", "path-all", "process-exit", "window-set-size"] }
20 | anyhow = "1.0.68"
21 | once_cell = "1.17.0"
22 | tokio = { version = "1.25.0", features = ["full"] }
23 | crossbeam = "0.8.2"
24 | env_logger = "0.10.0"
25 | log = "0.4.17"
26 | dotenv = "0.15.0"
27 |
28 | [dev-dependencies]
29 | ctrlc = "3.2.4"
30 | dialoguer = "0.10.3"
31 |
32 | [dependencies.windows]
33 | varsion = "0.44.0"
34 | features = [
35 | "Win32_System_Com",
36 | "Win32_UI_WindowsAndMessaging",
37 | "Win32_Foundation",
38 | "Win32_UI_Accessibility",
39 | "Win32_System_Threading",
40 | "Win32_System_Console",
41 | "Win32_Graphics_Gdi",
42 | "Win32_UI_Shell",
43 | # "interface",
44 | ]
45 |
46 | [features]
47 | # by default Tauri runs in production mode
48 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
49 | default = ["custom-protocol"]
50 | # this feature is used used for production builds where `devPath` points to the filesystem
51 | # DO NOT remove this
52 | custom-protocol = ["tauri/custom-protocol"]
53 |
--------------------------------------------------------------------------------
/src/components/configComponents/SummonPosConfElm.tsx:
--------------------------------------------------------------------------------
1 | import TextField from "@mui/material/TextField";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 | import Grid from "@mui/material/Grid";
4 |
5 | export interface SummonPosConfElmProps {
6 | config: Config;
7 | configMethods: ConfigMethods;
8 | }
9 |
10 | const SummonPosConfElm = ({ config, configMethods }: SummonPosConfElmProps) => {
11 | return (
12 |
13 |
14 | {"Summon Pos: "}
15 |
16 |
17 | {
22 | configMethods.setSummonPoint({
23 | x: Number(e.target.value),
24 | y: config.summon_point?.y ?? 0,
25 | });
26 | }}
27 | inputProps={{
28 | step: 1,
29 | type: "number",
30 | }}
31 | />
32 |
33 |
34 | {
39 | configMethods.setSummonPoint({
40 | x: config.summon_point?.x ?? 0,
41 | y: Number(e.target.value),
42 | });
43 | }}
44 | inputProps={{
45 | step: 1,
46 | type: "number",
47 | }}
48 | />
49 |
50 |
51 | );
52 | };
53 |
54 | export default SummonPosConfElm;
55 |
--------------------------------------------------------------------------------
/src/components/configComponents/autoSummon/SizeConfElm.tsx:
--------------------------------------------------------------------------------
1 | import Grid from "@mui/material/Grid";
2 | import { Config, ConfigMethods } from "@/hooks/config-hook";
3 | import TextField from "@mui/material/TextField";
4 |
5 | interface AutoSummonSizeConfElmProps {
6 | config: Config;
7 | configMethods: ConfigMethods;
8 | }
9 |
10 | const AutoSummonSizeConfElm = ({
11 | config,
12 | configMethods,
13 | }: AutoSummonSizeConfElmProps) => {
14 | return (
15 |
16 |
17 | {
22 | configMethods.setAutoSummonSize({
23 | width: Number(e.target.value),
24 | height: config.auto_summon_size?.height ?? 0,
25 | });
26 | }}
27 | inputProps={{
28 | step: 1,
29 | type: "number",
30 | }}
31 | />
32 |
33 |
34 | {
39 | configMethods.setAutoSummonSize({
40 | width: config.auto_summon_size?.width ?? 0,
41 | height: Number(e.target.value),
42 | });
43 | }}
44 | inputProps={{
45 | step: 1,
46 | type: "number",
47 | }}
48 | />
49 |
50 |
51 | );
52 | };
53 |
54 | export default AutoSummonSizeConfElm;
55 |
--------------------------------------------------------------------------------
/src/components/listComponents/ListComponent.tsx:
--------------------------------------------------------------------------------
1 | import List from "@mui/material/List";
2 | import ListItem from "@mui/material/ListItem";
3 | import { WinInfo } from "@/winwin-type";
4 | import FrameRowComponent from "@/components/listComponents/FrameRowComponent";
5 | import { Config, ConfigMethods } from "@/hooks/config-hook";
6 | import { WindowAttr } from "@/hooks/frame-hook";
7 |
8 | interface ListComponentProps {
9 | windows: WindowAttr[];
10 | config: Config;
11 | configMethods: ConfigMethods;
12 | target_hwnd: number | undefined;
13 | setTarget: (w: WindowAttr) => void;
14 | accessable_windows: WinInfo[];
15 | }
16 |
17 | const ListComponent = ({
18 | windows,
19 | config,
20 | configMethods,
21 | target_hwnd: target,
22 | setTarget,
23 | accessable_windows,
24 | }: ListComponentProps) => {
25 | windows.sort((a, b) => {
26 | if (a.original.hwnd === target) {
27 | return -1;
28 | } else if (b.original.hwnd === target) {
29 | return 1;
30 | }
31 |
32 | if (a.is_visible && !b.is_visible) {
33 | return -1;
34 | } else if (!a.is_visible && b.is_visible) {
35 | return 1;
36 | }
37 |
38 | return 0;
39 | });
40 |
41 | return (
42 |
43 | {windows.map((window) => (
44 |
45 |
53 |
54 | ))}
55 |
56 | );
57 | };
58 |
59 | export default ListComponent;
60 |
--------------------------------------------------------------------------------
/src-tauri/src/backend.rs:
--------------------------------------------------------------------------------
1 | use crate::winapi::winevents::{init_event_notifier, UpdateEvents};
2 | use anyhow::{anyhow, Result};
3 | use std::thread;
4 | use tauri::{App, Manager, Wry};
5 | use tokio::sync::mpsc::Receiver;
6 | use tokio::task;
7 |
8 | pub fn prepare() -> Result<(Receiver, thread::JoinHandle>)> {
9 | let Some((rx, th)) = init_event_notifier()? else {
10 | return Err(anyhow!("init_event_notifier already initialized"));
11 | };
12 |
13 | Ok((rx, th))
14 | }
15 |
16 | #[derive(serde::Serialize, Clone)]
17 | struct UpdateEventsPayload {
18 | kind: String,
19 | hwnd: u64,
20 | }
21 |
22 | pub fn tauri_setup(app: &mut App, mut rx: Receiver) -> task::JoinHandle<()> {
23 | let main_window = app.get_window("main").unwrap();
24 |
25 | let mw = main_window.clone();
26 | let notification_thread = tokio::spawn(async move {
27 | while let Some(event) = rx.recv().await {
28 | let payload = match event {
29 | UpdateEvents::Active(hwnd) => UpdateEventsPayload {
30 | kind: "active".to_string(),
31 | hwnd: hwnd.0 as _,
32 | },
33 | UpdateEvents::Move(hwnd) => UpdateEventsPayload {
34 | kind: "move".to_string(),
35 | hwnd: hwnd.0 as _,
36 | },
37 | UpdateEvents::Destroy(hwnd) => UpdateEventsPayload {
38 | kind: "destroy".to_string(),
39 | hwnd: hwnd.0 as _,
40 | },
41 | };
42 | if let Err(e) = mw.emit("update", payload) {
43 | log::error!("{:?}", e);
44 | }
45 | }
46 | });
47 |
48 | notification_thread
49 | }
50 |
--------------------------------------------------------------------------------
/src-tauri/src/winapi/monitors.rs:
--------------------------------------------------------------------------------
1 | use crate::winapi::structs::Canvas;
2 | use anyhow::Result;
3 | use windows::Win32::{
4 | Foundation::{BOOL, LPARAM, RECT},
5 | Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR},
6 | };
7 |
8 | unsafe extern "system" fn monitor_enum_proc(
9 | _p0: HMONITOR,
10 | _p1: HDC,
11 | rect: *mut RECT,
12 | v: LPARAM,
13 | ) -> BOOL {
14 | let rect_vec = &mut *(v.0 as *mut Vec);
15 | rect_vec.push(*rect);
16 |
17 | BOOL(1)
18 | }
19 |
20 | pub fn get_monitors_rect() -> Result> {
21 | let mut rect_vec = Vec::new();
22 | unsafe {
23 | EnumDisplayMonitors(
24 | None,
25 | None,
26 | Some(monitor_enum_proc),
27 | LPARAM(&mut rect_vec as *mut _ as _),
28 | );
29 | }
30 | Ok(rect_vec)
31 | }
32 |
33 | pub(crate) fn get_canvas() -> Result