├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGES.md
├── LICENSE
├── README.md
├── checkstyle.xml
├── pom.xml
├── spotbugs-exclude.xml
└── src
└── main
├── java
└── com
│ └── fatico
│ └── winthing
│ ├── Application.java
│ ├── ApplicationModule.java
│ ├── Settings.java
│ ├── common
│ └── BaseController.java
│ ├── gui
│ └── WindowGui.java
│ ├── logging
│ ├── ConsoleLogger.java
│ └── FileLogEnabler.java
│ ├── messaging
│ ├── Engine.java
│ ├── Message.java
│ ├── MessagePublisher.java
│ ├── MessagingModule.java
│ ├── QualityOfService.java
│ ├── Registry.java
│ ├── RegistryConfigurator.java
│ └── SimpleRegistry.java
│ ├── systems
│ ├── desktop
│ │ ├── DesktopController.java
│ │ ├── DesktopService.java
│ │ └── Module.java
│ ├── keyboard
│ │ ├── KeyboardController.java
│ │ ├── KeyboardService.java
│ │ └── Module.java
│ ├── radeon
│ │ ├── AdlException.java
│ │ ├── Module.java
│ │ ├── RadeonController.java
│ │ ├── RadeonService.java
│ │ └── jna
│ │ │ └── AtiAdl.java
│ └── system
│ │ ├── Module.java
│ │ ├── SystemCommander.java
│ │ ├── SystemController.java
│ │ └── SystemService.java
│ └── windows
│ ├── SystemException.java
│ ├── WindowsModule.java
│ ├── input
│ ├── KeyboardKey.java
│ └── MouseButton.java
│ └── jna
│ ├── Advapi32.java
│ ├── Kernel32.java
│ └── User32.java
└── resources
├── application.properties
├── favicon-green.png
├── favicon-red.png
├── favicon.ico
├── favicon.png
└── logback.xml
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 | - uses: actions/setup-java@v1
9 | with:
10 | java-version: 8
11 | java-package: jdk
12 | - name: Run Checkstyle
13 | run: mvn checkstyle:check
14 | - name: Build
15 | run: mvn package
16 | - name: Run PMD
17 | run: mvn pmd:check
18 | - name: Run SpotBugs
19 | run: mvn spotbugs:check
20 | - name: Create artifacts directory
21 | run: mkdir -p target/artifacts
22 | - name: Prepare artifacts for packaging
23 | run: mv target/winthing-* target/artifacts
24 | - uses: actions/upload-artifact@v1
25 | with:
26 | name: artifacts
27 | path: target/artifacts
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 | .idea
4 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # Release notes
2 |
3 | ## 1.4.2
4 |
5 | - Updated build process and minor security fixes.
6 |
7 | ## 1.4.1
8 |
9 | - Fix winthing.ini whitelist file and command execution
10 | - Format log in the console and log file
11 |
12 | ## 1.4.0
13 |
14 | - Fix duplicate system/system in winthing/system/online topic
15 | - Fix Windows executable build for Java 11
16 | - Fix logging to file when -debug parameter is passed
17 | - Change how GUI is created
18 | - Fix check whether file logging should be enabled
19 |
20 | ## 1.3.0
21 |
22 | - Add tray icon and GUI console window
23 | - Read settings from file
24 | - Add configurable whitelist of allowed commands to execute
25 | - Create file logs only when enabled
26 | - Remove prefix from config parameters
27 | - Update dependencies
28 | - Update build for Java 11
29 |
30 | ## 1.2.0
31 |
32 | - Original version
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WinThing
2 |
3 | 
4 |
5 | A modular background service that makes Windows remotely controllable through MQTT. For home automation and Internet of Things.
6 |
7 | ## :warning: Deprecation Notice :warning:
8 |
9 | **WinThing is no longer actively maintained.** As an alternative, please consider [IOT Link](https://iotlink.gitlab.io/), which is actively maintained and has a lot more features, including most of WinThing's features.
10 |
11 | ## Requirements
12 |
13 | Java 8 or greater.
14 |
15 | ## Running
16 |
17 | Download either JAR or EXE file from [Releases page](https://github.com/msiedlarek/winthing/releases) and execute it:
18 |
19 | target/winthing-1.4.2.exe
20 | java -jar target/winthing-1.4.2.jar
21 |
22 | ## Configuration
23 |
24 | Configuration parameters can be passed from command line or they can be placed in configuration files in the working directory from where you launch WinThing.
25 |
26 |
27 | Property | Description | Default |
28 |
---|
broker | URL of the MQTT broker to use | 127.0.0.1:1883 |
29 | username | Username used when connecting to MQTT broker | mqtt |
30 | password | Password used when connecting to MQTT broker | mqtt |
31 | clientid | Client ID to present to the broker | WinThing |
32 | prefix | Prefix for all MQTT topics used by this WinThing instance | winthing |
33 | reconnect | Time interval between connection attempts in seconds | 5 |
34 |
35 |
36 | ### Command line parameters
37 |
38 | Example how to pass parameters from command line:
39 |
40 | java -Dbroker="127.0.0.1:1883" -jar winthing-1.2.0.jar
41 |
42 | ### winthing.conf
43 |
44 | WinThing will look for this file in the current working directory (directory from where you launched WinThing). Create this file and put desired parameters into it.
45 |
46 | Example file:
47 |
48 | broker = "127.0.0.1:1883"
49 | username = "mqtt"
50 | password = "somesecret"
51 |
52 | ### winthing.ini
53 |
54 | By default WinThing executes any command it receives in the system/commands/run topic. Create this file in the current working directory to whitelist only specific commands. The file contains an unique string identifier (used as payload in the MQTT message, see below) and path to executable.
55 |
56 | Example file:
57 |
58 | notepad = "c:/windows/system32/notepad.exe"
59 | adobe = "c:\\program files\\adobe\\reader.exe"
60 |
61 | *Note you can use slash* ' / ' *or double backslash* ' \\\\ ' *as path separator.*
62 |
63 | ## Logging
64 |
65 | You can open application log by clicking on the tray icon. To log into **winthing.log** file in the current working directory run WinThing with the **-debug** parameter.
66 |
67 | winthing.exe -debug
68 |
69 | ## Supported messages
70 |
71 | The payload of all messages is either empty or a valid JSON element (possibly a primitive, like a single integer). This means, specifically, that if an argument is supposed to be a single string, it should be sent in double quotes.
72 |
73 | Example valid message payloads:
74 |
75 | * `123`
76 | * `true`
77 | * `"notepad.exe"`
78 | * `[1024, 768]`
79 | * `["notepad.exe", "C:\\file.txt", "C:\\"]` (note that JSON string requires escaped backslash)
80 |
81 | ### Broadcast status
82 |
83 | #### System
84 |
85 | **Topic:** winthing/system/online
86 | **Payload:** state:boolean
87 | **QoS:** 2
88 | **Persistent:** yes
89 |
90 | True when WinThing is running, false otherwise. WinThing registers a "last will" message with the broker to notify clients when WinThing disconnects.
91 |
92 | ### Commands
93 |
94 | #### System
95 |
96 | **Topic:** winthing/system/commands/shutdown
97 | **Payload:** -
98 |
99 | Trigger immediate system shutdown.
100 |
101 | ---
102 |
103 | **Topic:** winthing/system/commands/reboot
104 | **Payload:** -
105 |
106 | Trigger immediate system reboot.
107 |
108 | ---
109 |
110 | **Topic:** winthing/system/commands/suspend
111 | **Payload:** -
112 |
113 | Trigger immediate system suspend.
114 |
115 | ---
116 |
117 | **Topic:** winthing/system/commands/hibernate
118 | **Payload:** -
119 |
120 | Trigger immediate system hibernate.
121 |
122 | ---
123 |
124 | **Topic:** winthing/system/commands/run
125 | **Payload:** [command:string, arguments:string, workingDirectory:string]
126 |
127 | Run a command. Arguments and working directory are optional (empty string and null by default).
128 | If whitelist is enabled, only the command as unique identifier is required. The identifier is checked against the whitelist file (see **whitelist.ini** above).
129 |
130 | ---
131 |
132 | **Topic:** winthing/system/commands/open
133 | **Payload:** uri:string
134 |
135 | Opens an URI, like a website in a browser or a disk location in a file browser.
136 |
137 | #### Desktop
138 |
139 | **Topic:** winthing/desktop/commands/close_active_window
140 | **Payload:** -
141 |
142 | Closes currently active window.
143 |
144 | ---
145 |
146 | **Topic:** winthing/desktop/commands/set_display_sleep
147 | **Payload:** displaySleep:boolean
148 |
149 | Puts the display to sleep (on true) or wakes it up (on false).
150 |
151 | #### Keyboard
152 |
153 | **Topic:** winthing/keyboard/commands/press_keys
154 | **Payload:** [key:string...]
155 |
156 | Simulates pressing of given set of keyboard keys. Keys are specified by name. List of available key names and aliases can be found [here](src/main/java/com/fatico/winthing/windows/input/KeyboardKey.java).
157 |
158 | #### ATI Radeon display driver
159 |
160 | **Topic:** winthing/radeon/commands/set_best_resolution
161 | **Payload:** -
162 |
163 | Sets the screen to the best available resolution.
164 |
165 | ---
166 |
167 | **Topic:** winthing/radeon/commands/set_resolution
168 | **Payload:** [widthInPixels:integer, heightInPixels:integer]
169 |
170 | Sets the screen to the given resolution.
171 |
172 | ## Building
173 |
174 | Maven is required to build the application. For convenience the Maven build file contains execution to produce a Windows executable.
175 |
176 | mvn clean package
177 |
178 | To run static analysis tools, use these commands:
179 |
180 | mvn checkstyle:check
181 | mvn pmd:check
182 | mvn spotbugs:check
183 |
184 | ## License
185 |
186 | Copyright 2015-2020 Mikołaj Siedlarek <mikolaj@siedlarek.pl>
187 |
188 | Licensed under the Apache License, Version 2.0 (the "License");
189 | you may not use this software except in compliance with the License.
190 | You may obtain a copy of the License at
191 |
192 | > http://www.apache.org/licenses/LICENSE-2.0
193 |
194 | Unless required by applicable law or agreed to in writing, software
195 | distributed under the License is distributed on an "AS IS" BASIS,
196 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
197 | See the License for the specific language governing permissions and
198 | limitations under the License.
199 |
--------------------------------------------------------------------------------
/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
76 |
77 |
78 |
80 |
81 |
82 |
88 |
89 |
90 |
91 |
94 |
95 |
96 |
97 |
98 |
101 |
102 |
103 |
104 |
105 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
122 |
124 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
171 |
172 |
173 |
174 |
176 |
177 |
178 |
179 |
181 |
182 |
183 |
184 |
186 |
187 |
188 |
189 |
191 |
192 |
193 |
194 |
196 |
197 |
198 |
199 |
201 |
202 |
203 |
204 |
206 |
207 |
208 |
209 |
211 |
212 |
213 |
214 |
216 |
217 |
218 |
219 |
221 |
223 |
225 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
255 |
256 |
257 |
259 |
260 |
261 |
262 |
267 |
268 |
269 |
270 |
273 |
274 |
275 |
276 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
290 |
291 |
292 |
293 |
294 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
322 |
323 |
324 |
325 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | com.fatico
7 | winthing
8 | 1.4.2
9 |
10 | WinThing
11 | Remotely control Windows through MQTT.
12 | https://github.com/msiedlarek/winthing
13 |
14 |
15 | https://github.com/msiedlarek/winthing/issues
16 | GitHub Issues
17 |
18 |
19 |
20 |
21 | Apache License, Version 2.0
22 | http://www.apache.org/licenses/LICENSE-2.0.txt
23 | repo
24 |
25 |
26 |
27 |
28 | https://github.com/msiedlarek/winthing
29 | scm:git:git://github.com/msiedlarek/winthing.git
30 | scm:git:git@github.com:msiedlarek/winthing.git
31 |
32 |
33 |
34 |
35 | mikolaj@siedlarek.pl
36 | Mikołaj Siedlarek
37 | msiedlarek
38 |
39 |
40 |
41 |
42 | 1.8
43 | 1.8
44 | UTF-8
45 | UTF-8
46 | ${project.basedir}/checkstyle.xml
47 | 1.7.30
48 | 1.2.3
49 | 28.2-jre
50 | 4.2.3
51 | 2.8.6
52 | 1.2.2
53 | 5.5.0
54 | 1.4.0
55 | 3.1.2
56 | 4.0.1
57 |
58 |
59 |
60 |
61 | org.slf4j
62 | slf4j-api
63 | ${version.slf4j}
64 |
65 |
66 | org.slf4j
67 | jcl-over-slf4j
68 | ${version.slf4j}
69 | runtime
70 |
71 |
72 | ch.qos.logback
73 | logback-classic
74 | ${version.logback}
75 |
76 |
77 | com.google.guava
78 | guava
79 | ${version.guava}
80 |
81 |
82 | com.google.inject
83 | guice
84 | ${version.guice}
85 |
86 |
87 | com.google.code.gson
88 | gson
89 | ${version.gson}
90 |
91 |
92 | org.eclipse.paho
93 | org.eclipse.paho.client.mqttv3
94 | ${version.paho}
95 |
96 |
97 | net.java.dev.jna
98 | jna
99 | ${version.jna}
100 |
101 |
102 | net.java.dev.jna
103 | jna-platform
104 | ${version.jna}
105 |
106 |
107 | com.typesafe
108 | config
109 | ${version.typesafe.config}
110 |
111 |
112 | org.codehaus.janino
113 | janino
114 | ${version.janino}
115 |
116 |
117 | com.github.spotbugs
118 | spotbugs
119 | ${version.spotbugs}
120 |
121 |
122 |
123 |
124 |
125 |
126 | org.apache.maven.plugins
127 | maven-checkstyle-plugin
128 | 3.1.1
129 |
130 | ${checkstyle.config}
131 | UTF-8
132 | true
133 | warning
134 | false
135 | true
136 |
137 |
138 |
139 | com.puppycrawl.tools
140 | checkstyle
141 | 8.30
142 |
143 |
144 |
145 |
146 | org.apache.maven.plugins
147 | maven-pmd-plugin
148 | 3.13.0
149 |
150 | true
151 | true
152 | false
153 |
154 |
155 |
156 | com.github.spotbugs
157 | spotbugs-maven-plugin
158 | 3.1.11
159 |
160 | Max
161 | Low
162 | true
163 | spotbugs-exclude.xml
164 |
165 |
166 | com.h3xstream.findsecbugs
167 | findsecbugs-plugin
168 | 1.10.1
169 |
170 |
171 |
172 |
173 |
174 | org.apache.maven.plugins
175 | maven-compiler-plugin
176 | 3.8.1
177 |
178 |
179 | -Xlint
180 | -Werror
181 |
182 |
183 |
184 |
185 | org.apache.maven.plugins
186 | maven-shade-plugin
187 | 3.2.2
188 |
189 |
190 | package
191 |
192 | shade
193 |
194 |
195 | ${project.artifactId}-${project.version}
196 | ${project.build.directory}/pom.xml
197 |
198 |
199 |
201 | com.fatico.winthing.Application
202 |
203 | ${project.name}
204 | ${project.version}
205 |
206 |
207 |
208 |
209 |
210 | *:*
211 |
212 | META-INF/*.SF
213 | META-INF/*.DSA
214 | META-INF/*.RSA
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | com.akathist.maven.plugins.launch4j
224 | launch4j-maven-plugin
225 | 1.7.25
226 |
227 |
228 | l4j-clui
229 | package
230 |
231 | launch4j
232 |
233 |
234 | gui
235 | ${project.build.directory}/${project.artifactId}-${project.version}.jar
236 | ${project.build.directory}/winthing-${project.version}.exe
237 | ${project.build.resources[0].directory}/favicon.ico
238 | http://java.com/download
239 |
240 | com.fatico.winthing.Application
241 |
242 |
243 | false
244 | false
245 | ${maven.compiler.target}
246 | preferJdk
247 | 64
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/spotbugs-exclude.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/Application.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing;
2 |
3 | import com.fatico.winthing.gui.WindowGui;
4 | import com.fatico.winthing.messaging.Engine;
5 | import com.google.inject.Guice;
6 | import com.google.inject.Injector;
7 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | public class Application {
12 | private static final Application app = new Application();
13 |
14 | private boolean debug = false;
15 | private WindowGui gui;
16 | private Logger logger;
17 |
18 | private void parseArgs(String[] args) {
19 | for (String arg : args) {
20 | if (arg.equals("-debug")) {
21 | debug = true;
22 | }
23 | }
24 | }
25 |
26 | public static boolean debug() {
27 | return app.debug;
28 | }
29 |
30 | public static WindowGui getApp() {
31 | return app.gui;
32 | }
33 |
34 | @SuppressFBWarnings("DM_EXIT")
35 | public static void quit() {
36 | app.logger.info("Application terminated.");
37 | System.exit(0);
38 | }
39 |
40 | public static void main(final String[] args) {
41 | try {
42 | app.parseArgs(args);
43 |
44 | app.logger = LoggerFactory.getLogger(Application.class);
45 |
46 | app.gui = new WindowGui();
47 | app.gui.initialize();
48 |
49 | final Injector injector = Guice.createInjector(new ApplicationModule());
50 | final Engine engine = injector.getInstance(Engine.class);
51 | engine.run();
52 |
53 | } catch (final Throwable throwable) {
54 | app.logger.error("Critical error.", throwable);
55 | System.exit(1);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/ApplicationModule.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing;
2 |
3 | import com.fatico.winthing.messaging.MessagingModule;
4 | import com.fatico.winthing.windows.WindowsModule;
5 | import com.google.gson.Gson;
6 | import com.google.inject.AbstractModule;
7 | import com.google.inject.Provides;
8 | import com.google.inject.Singleton;
9 | import com.typesafe.config.Config;
10 | import com.typesafe.config.ConfigFactory;
11 | import com.typesafe.config.ConfigParseOptions;
12 | import com.typesafe.config.ConfigSyntax;
13 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 | import java.io.File;
15 | import java.util.Locale;
16 |
17 | public class ApplicationModule extends AbstractModule {
18 | public static final String ConfigFile = "winthing.conf";
19 |
20 | @Override
21 | protected void configure() {
22 | bind(Gson.class).in(Singleton.class);
23 |
24 | install(new MessagingModule());
25 | if (System.getProperty("os.name").toLowerCase(Locale.getDefault()).contains("win")) {
26 | install(new WindowsModule());
27 | install(new com.fatico.winthing.systems.system.Module());
28 | install(new com.fatico.winthing.systems.keyboard.Module());
29 | install(new com.fatico.winthing.systems.desktop.Module());
30 | }
31 | }
32 |
33 | @Provides
34 | @Singleton
35 | @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
36 | Config config() {
37 | Config cfg = ConfigFactory.load();
38 | String path = System.getProperty("user.dir") + File.separator + ConfigFile;
39 |
40 | File fp = new File(path);
41 | if (fp.exists()) {
42 | ConfigParseOptions options = ConfigParseOptions.defaults();
43 | options.setSyntax(ConfigSyntax.CONF);
44 |
45 | cfg = ConfigFactory.parseFile(fp, options).withFallback(cfg);
46 | }
47 |
48 | return cfg;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/Settings.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing;
2 |
3 | public abstract class Settings {
4 |
5 | public static final String BROKER_URL = "broker";
6 | public static final String BROKER_USERNAME = "username";
7 | public static final String BROKER_PASSWORD = "password";
8 | public static final String CLIENT_ID = "clientid";
9 | public static final String TOPIC_PREFIX = "prefix";
10 | public static final String RECONNECT_INTERVAL = "reconnect";
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/common/BaseController.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.common;
2 |
3 | import com.fatico.winthing.messaging.Message;
4 | import com.fatico.winthing.messaging.QualityOfService;
5 | import com.google.gson.JsonElement;
6 | import java.util.Objects;
7 |
8 | public class BaseController {
9 |
10 | protected final String prefix;
11 |
12 | public BaseController(String prefix) {
13 | Objects.requireNonNull(prefix);
14 | prefix = prefix.replaceFirst("^/+", "");
15 | if (!prefix.isEmpty() && !prefix.endsWith("/")) {
16 | prefix += "/";
17 | }
18 | this.prefix = prefix;
19 | }
20 |
21 | protected Message makeMessage(final String topic) {
22 | return new Message(prefix + topic);
23 | }
24 |
25 | protected Message makeMessage(final String topic, final JsonElement payload) {
26 | return new Message(prefix + topic, payload);
27 | }
28 |
29 | protected Message makeMessage(final String topic, final JsonElement payload,
30 | final QualityOfService qos) {
31 | return new Message(prefix + topic, payload, qos);
32 | }
33 |
34 | protected Message makeMessage(final String topic, final JsonElement payload,
35 | final QualityOfService qos, boolean retained) {
36 | return new Message(prefix + topic, payload, qos, retained);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/gui/WindowGui.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.gui;
2 |
3 | import com.fatico.winthing.Application;
4 | import com.fatico.winthing.logging.ConsoleLogger;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 | import java.awt.AWTException;
7 | import java.awt.BorderLayout;
8 | import java.awt.Component;
9 | import java.awt.Dimension;
10 | import java.awt.FlowLayout;
11 | import java.awt.Image;
12 | import java.awt.MenuItem;
13 | import java.awt.PopupMenu;
14 | import java.awt.SystemTray;
15 | import java.awt.Toolkit;
16 | import java.awt.TrayIcon;
17 | import java.awt.event.ActionEvent;
18 | import java.awt.event.ActionListener;
19 | import java.awt.event.MouseEvent;
20 | import java.awt.event.MouseListener;
21 | import java.net.URL;
22 | import java.util.HashMap;
23 | import java.util.Map;
24 | import javax.swing.JButton;
25 | import javax.swing.JFrame;
26 | import javax.swing.JPanel;
27 | import javax.swing.JScrollPane;
28 | import javax.swing.JTextArea;
29 |
30 | @SuppressWarnings("serial")
31 | public class WindowGui extends JFrame {
32 | private final String appName;
33 | private final String appVersion;
34 | private Map components = new HashMap();
35 |
36 | public enum Gui {
37 | TEXTAREA(0),
38 | SCROLLBAR(1),
39 | TRAYICON(2);
40 |
41 | public final int key;
42 |
43 | Gui(int value) {
44 | key = value;
45 | }
46 | }
47 |
48 | public enum Actions {
49 | EXIT,
50 | EVENTS,
51 | CLOSE,
52 | QUIT
53 | }
54 |
55 | public WindowGui() {
56 | appName = Application.class.getPackage().getImplementationTitle();
57 | appVersion = Application.class.getPackage().getImplementationVersion();
58 | }
59 |
60 | public void initialize() throws AWTException {
61 | setTitle(appName + " " + appVersion);
62 | setDefaultCloseOperation(HIDE_ON_CLOSE);
63 | setVisible(false);
64 | setLayout(new BorderLayout());
65 | setResizable(false);
66 | setPreferredSize(new Dimension(800, 530));
67 | setSize(getPreferredSize());
68 |
69 | JTextArea logs = new JTextArea();
70 | logs.setEditable(false);
71 | logs.setRows(25);
72 | components.put(Gui.TEXTAREA.key, logs);
73 |
74 | JScrollPane scrolls = new JScrollPane(logs);
75 | scrolls.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
76 | getContentPane().add(scrolls, BorderLayout.NORTH);
77 | components.put(Gui.SCROLLBAR.key, scrolls);
78 |
79 | JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
80 | getContentPane().add(panel, BorderLayout.SOUTH);
81 |
82 | JButton btnClose = new JButton("Close");
83 | btnClose.setToolTipText("Close this window");
84 | btnClose.addMouseListener(new MouseEventListener(this, Actions.CLOSE));
85 | panel.add(btnClose);
86 |
87 | JButton btnQuit = new JButton("Quit");
88 | btnQuit.setToolTipText("Stop and terminate application");
89 | btnQuit.addMouseListener(new MouseEventListener(this, Actions.QUIT));
90 | panel.add(btnQuit);
91 |
92 | TrayIcon trayIcon = null;
93 | if (SystemTray.isSupported()) {
94 | PopupMenu popup = new PopupMenu();
95 |
96 | MenuItem menuTitle = new MenuItem(appName);
97 | menuTitle.setEnabled(false);
98 | popup.add(menuTitle);
99 |
100 | popup.addSeparator();
101 |
102 | MenuItem menuEvent = new MenuItem("Events");
103 | menuEvent.addActionListener(new MouseEventListener(this, Actions.EVENTS));
104 | popup.add(menuEvent);
105 |
106 | MenuItem menuExit = new MenuItem("Exit");
107 | menuExit.addActionListener(new MouseEventListener(this, Actions.EXIT));
108 | popup.add(menuExit);
109 |
110 | SystemTray tray = SystemTray.getSystemTray();
111 |
112 | URL url = getClass().getClassLoader().getResource("favicon-red.png");
113 | Image image = Toolkit.getDefaultToolkit().getImage(url);
114 |
115 | int trayWidth = tray.getTrayIconSize().width;
116 | int trayheight = tray.getTrayIconSize().height;
117 | Image scaled = image.getScaledInstance(trayWidth, trayheight, Image.SCALE_SMOOTH);
118 |
119 | trayIcon = new TrayIcon(scaled, appName + " " + appVersion, popup);
120 | trayIcon.addMouseListener(new MouseEventListener(this, Actions.EVENTS));
121 | tray.add(trayIcon);
122 | }
123 | }
124 |
125 | public void setIcon(boolean color) {
126 | SystemTray tray = SystemTray.getSystemTray();
127 | TrayIcon[] icons = tray.getTrayIcons();
128 | if (icons.length > 0) {
129 | String name = color ? "favicon-green.png" : "favicon-red.png";
130 |
131 | URL url = getClass().getClassLoader().getResource(name);
132 | Image image = Toolkit.getDefaultToolkit().getImage(url);
133 |
134 | int trayWidth = tray.getTrayIconSize().width;
135 | int trayHeight = tray.getTrayIconSize().height;
136 | Image scaled = image.getScaledInstance(trayWidth, trayHeight, Image.SCALE_SMOOTH);
137 | icons[0].setImage(scaled);
138 | }
139 | }
140 |
141 | public void reload() {
142 | if (isVisible()) {
143 | JTextArea area = (JTextArea)components.get(Gui.TEXTAREA.key);
144 | area.setText(ConsoleLogger.getEvents());
145 |
146 | JScrollPane scroll = (JScrollPane)components.get(Gui.SCROLLBAR.key);
147 | scroll.getVerticalScrollBar().setValue(scroll.getVerticalScrollBar().getMaximum());
148 | }
149 | }
150 |
151 | public void open() {
152 | pack();
153 | setLocationRelativeTo(null);
154 | setVisible(true);
155 |
156 | reload();
157 | }
158 |
159 | public void close() {
160 | setVisible(false);
161 | }
162 |
163 | public void quit() {
164 | Application.quit();
165 | }
166 |
167 | @SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC")
168 | public class MouseEventListener implements ActionListener, MouseListener {
169 | private Actions action;
170 | private WindowGui window;
171 |
172 | public MouseEventListener(WindowGui gui, Actions act) {
173 | window = gui;
174 | action = act;
175 | }
176 |
177 | @Override
178 | public void actionPerformed(ActionEvent event) {
179 | if (action == WindowGui.Actions.EXIT) {
180 | window.quit();
181 | }
182 |
183 | if (action == WindowGui.Actions.EVENTS) {
184 | window.open();
185 | }
186 | }
187 |
188 | @Override
189 | public void mouseClicked(MouseEvent event) {
190 | if (event.getButton() == MouseEvent.BUTTON1) {
191 | switch (action) {
192 | default:
193 | break;
194 |
195 | case EVENTS:
196 | window.open();
197 | break;
198 |
199 | case CLOSE:
200 | window.close();
201 | break;
202 |
203 | case QUIT:
204 | window.quit();
205 | break;
206 | }
207 | }
208 | }
209 |
210 | @Override
211 | public void mousePressed(MouseEvent event) {
212 | // no code here
213 | }
214 |
215 | @Override
216 | public void mouseReleased(MouseEvent event) {
217 | // no code here
218 | }
219 |
220 | @Override
221 | public void mouseEntered(MouseEvent event) {
222 | // no code here
223 | }
224 |
225 | @Override
226 | public void mouseExited(MouseEvent event) {
227 | // no code here
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/logging/ConsoleLogger.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.logging;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import ch.qos.logback.core.ConsoleAppender;
5 | import com.fatico.winthing.Application;
6 | import java.nio.charset.Charset;
7 | import java.util.Iterator;
8 | import java.util.StringJoiner;
9 | import java.util.concurrent.ConcurrentLinkedQueue;
10 |
11 | public class ConsoleLogger extends ConsoleAppender {
12 | private static final int LOG_SIZE = 50;
13 | private static final ConcurrentLinkedQueue events = new ConcurrentLinkedQueue();
14 |
15 | public static String getEvents() {
16 | if (events.size() == 0) {
17 | return "";
18 | }
19 |
20 | StringJoiner joiner = new StringJoiner("");
21 | Iterator iterator = events.iterator();
22 | while (iterator.hasNext()) {
23 | joiner.add(iterator.next());
24 | }
25 |
26 | String result = joiner.toString();
27 |
28 | return result;
29 | }
30 |
31 | protected void append(ILoggingEvent event) {
32 | super.append(event);
33 |
34 | if (events.size() > LOG_SIZE) {
35 | events.remove();
36 | }
37 |
38 | byte[] data = encoder.encode(event);
39 | events.add(new String(data, Charset.forName("UTF-8")));
40 |
41 | Application.getApp().reload();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/logging/FileLogEnabler.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.logging;
2 |
3 | import ch.qos.logback.core.PropertyDefinerBase;
4 | import com.fatico.winthing.Application;
5 |
6 | public class FileLogEnabler extends PropertyDefinerBase {
7 |
8 | @Override
9 | public String getPropertyValue() {
10 | if (Application.debug()) {
11 | return "true";
12 | }
13 |
14 | return "false";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/Engine.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import com.fatico.winthing.Application;
4 | import com.fatico.winthing.Settings;
5 | import com.google.common.base.Charsets;
6 | import com.google.gson.Gson;
7 | import com.google.gson.JsonElement;
8 | import com.google.gson.JsonSyntaxException;
9 | import com.google.inject.Inject;
10 | import com.typesafe.config.Config;
11 | import java.nio.charset.Charset;
12 | import java.time.Duration;
13 | import java.util.Collection;
14 | import java.util.Map;
15 | import java.util.Objects;
16 | import java.util.concurrent.locks.Condition;
17 | import java.util.concurrent.locks.Lock;
18 | import java.util.concurrent.locks.ReentrantLock;
19 | import java.util.function.Consumer;
20 | import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
21 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
22 | import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
23 | import org.eclipse.paho.client.mqttv3.MqttCallback;
24 | import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
25 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
26 | import org.eclipse.paho.client.mqttv3.MqttException;
27 | import org.eclipse.paho.client.mqttv3.MqttMessage;
28 | import org.slf4j.Logger;
29 | import org.slf4j.LoggerFactory;
30 |
31 | public class Engine implements MqttCallback, MessagePublisher {
32 |
33 | private static final Charset CHARSET = Charsets.UTF_8;
34 |
35 | private final Logger logger = LoggerFactory.getLogger(getClass());
36 |
37 | private final Gson gson;
38 | private final Registry registry;
39 | private final String topicPrefix;
40 | private final IMqttAsyncClient client;
41 | private final MqttConnectOptions options = new MqttConnectOptions();
42 | private final Duration reconnectInterval;
43 |
44 | private final Lock runnningLock = new ReentrantLock();
45 | private final Condition runningCondition = runnningLock.newCondition();
46 |
47 | @Inject
48 | public Engine(final Gson gson, final Registry registry, final Config config,
49 | final MqttClientPersistence persistence) throws MqttException {
50 | String topicPrefix = config.getString(Settings.TOPIC_PREFIX).replaceFirst("^/+", "");
51 | if (!topicPrefix.isEmpty() && !topicPrefix.endsWith("/")) {
52 | topicPrefix += "/";
53 | }
54 | this.topicPrefix = topicPrefix;
55 |
56 | this.reconnectInterval = Duration.ofSeconds(config.getLong(Settings.RECONNECT_INTERVAL));
57 |
58 | this.gson = Objects.requireNonNull(gson);
59 | this.registry = Objects.requireNonNull(registry);
60 |
61 | this.client = new MqttAsyncClient(
62 | "tcp://" + config.getString(Settings.BROKER_URL),
63 | config.getString(Settings.CLIENT_ID),
64 | persistence
65 | );
66 | this.client.setCallback(this);
67 |
68 | {
69 | final String username = config.getString(Settings.BROKER_USERNAME);
70 | if (username != null && !username.isEmpty()) {
71 | this.options.setUserName(username);
72 | }
73 | }
74 | {
75 | final String password = config.getString(Settings.BROKER_PASSWORD);
76 | if (password != null && !password.isEmpty()) {
77 | this.options.setPassword(password.toCharArray());
78 | }
79 | }
80 |
81 | this.options.setCleanSession(true);
82 | }
83 |
84 | public void run() {
85 | runnningLock.lock();
86 | try {
87 | while (true) {
88 | boolean connected = false;
89 | try {
90 | connect();
91 | connected = true;
92 | } catch (final MqttException exception) {
93 | logger.error("Could not connect: {}", exception.getMessage());
94 | }
95 | if (connected) {
96 | try {
97 | runningCondition.await();
98 | } catch (final InterruptedException exception) {
99 | try {
100 | disconnect();
101 | } catch (final MqttException disconnectException) {
102 | logger.error("Could not disconnect.", disconnectException);
103 | }
104 | return;
105 | }
106 | }
107 | logger.info(
108 | "Trying to reconnect in {} seconds...",
109 | reconnectInterval.getSeconds()
110 | );
111 | try {
112 | Thread.sleep(reconnectInterval.toMillis());
113 | } catch (final InterruptedException exception) {
114 | return;
115 | }
116 | }
117 | } finally {
118 | runnningLock.unlock();
119 | }
120 | }
121 |
122 | private void connect() throws MqttException {
123 | if (registry.getWill().isPresent()) {
124 | final Message will = registry.getWill().get();
125 | final MqttMessage mqttMessage = serialize(will);
126 | this.options.setWill(
127 | topicPrefix + will.getTopic(),
128 | mqttMessage.getPayload(),
129 | mqttMessage.getQos(),
130 | mqttMessage.isRetained()
131 | );
132 | }
133 |
134 | logger.info("Connecting to {} as {}...", client.getServerURI(), client.getClientId());
135 | client.connect(options).waitForCompletion();
136 | logger.info("Connected.");
137 |
138 | logger.info("Subscribing to topics...");
139 | for (final Map.Entry entry
140 | : registry.getSubscriptions().entrySet()) {
141 | logger.info(" - {}", topicPrefix + entry.getKey());
142 | client.subscribe(topicPrefix + entry.getKey(), entry.getValue().ordinal());
143 | }
144 | logger.info("Subscribed.");
145 |
146 | logger.info("Sending initial messages...");
147 | registry.getInitialMessages().stream().forEach(this::publish);
148 |
149 | logger.info("Engine started.");
150 |
151 | Application.getApp().setIcon(true);
152 | }
153 |
154 | private void disconnect() throws MqttException {
155 | Application.getApp().setIcon(false);
156 |
157 | client.disconnect();
158 | }
159 |
160 | @Override
161 | public void publish(final Message message) {
162 | final MqttMessage mqttMessage = serialize(message);
163 | try {
164 | client.publish(
165 | topicPrefix + message.getTopic(),
166 | mqttMessage
167 | );
168 | } catch (final MqttException exception) {
169 | logger.error("Error while publishing message.", exception);
170 | }
171 | }
172 |
173 | @Override
174 | public void connectionLost(final Throwable throwable) {
175 | Application.getApp().setIcon(false);
176 |
177 | logger.error("Connection lost.");
178 | runnningLock.lock();
179 | try {
180 | runningCondition.signal();
181 | } finally {
182 | runnningLock.unlock();
183 | }
184 | }
185 |
186 | @Override
187 | public void messageArrived(final String topic, final MqttMessage mqttMessage) throws Exception {
188 | try {
189 | handleMessage(topic, mqttMessage);
190 | } catch (final Throwable throwable) {
191 | logger.error(
192 | "Error while handling message " + topic
193 | + "(" + new String(mqttMessage.getPayload(), CHARSET) + "): "
194 | + throwable.getMessage(),
195 | throwable
196 | );
197 | }
198 | }
199 |
200 | @Override
201 | public void deliveryComplete(final IMqttDeliveryToken token) {
202 | // Do nothing.
203 | }
204 |
205 | private void handleMessage(String topic, final MqttMessage mqttMessage) throws Exception {
206 | if (!topic.startsWith(topicPrefix)) {
207 | return;
208 | }
209 | topic = topic.substring(topicPrefix.length());
210 |
211 | final Collection> consumers = registry.getConsumers(topic);
212 | if (consumers.isEmpty()) {
213 | return;
214 | }
215 |
216 | final byte[] payloadBytes = mqttMessage.getPayload();
217 | final JsonElement payload;
218 | if (payloadBytes.length == 0) {
219 | payload = null;
220 | } else {
221 | try {
222 | payload = gson.fromJson(new String(payloadBytes, CHARSET), JsonElement.class);
223 | } catch (final JsonSyntaxException exception) {
224 | logger.error("Invalid JSON received for: {}", topic);
225 | return;
226 | }
227 | }
228 |
229 | final Message message = new Message(
230 | topic,
231 | payload,
232 | QualityOfService.values()[mqttMessage.getQos()],
233 | mqttMessage.isRetained()
234 | );
235 |
236 | logger.debug(
237 | "Received: {}({})",
238 | message.getTopic(),
239 | message.getPayload().isPresent() ? message.getPayload().get().toString() : ""
240 | );
241 |
242 | for (final Consumer consumer : consumers) {
243 | try {
244 | consumer.accept(message);
245 | } catch (final Exception exception) {
246 | logger.error(
247 | "Error while processing {}({}): {}",
248 | message.getTopic(),
249 | message.getPayload().isPresent()
250 | ? message.getPayload().get().toString() : "",
251 | exception.getMessage()
252 | );
253 | }
254 | }
255 | }
256 |
257 | private MqttMessage serialize(final Message message) {
258 | final byte[] payload;
259 | if (message.getPayload().isPresent()) {
260 | payload = gson.toJson(message.getPayload().get()).getBytes(CHARSET);
261 | } else {
262 | payload = new byte[0];
263 | }
264 | final MqttMessage mqttMessage = new MqttMessage(payload);
265 | mqttMessage.setQos(message.getQualityOfService().ordinal());
266 | mqttMessage.setRetained(message.isRetained());
267 | return mqttMessage;
268 | }
269 |
270 | }
271 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/Message.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import com.google.gson.JsonElement;
4 | import java.util.Objects;
5 | import java.util.Optional;
6 |
7 | public class Message {
8 |
9 | private static final JsonElement DEFAULT_PAYLOAD = null;
10 | private static final QualityOfService DEFAULT_QOS = QualityOfService.AT_MOST_ONCE;
11 | private static final boolean DEFAULT_RETAINMENT = false;
12 |
13 | private final String topic;
14 | private final JsonElement payload;
15 | private final QualityOfService qos;
16 | private final boolean retained;
17 |
18 | public Message(final String topic) {
19 | this.topic = Objects.requireNonNull(topic);
20 | this.payload = DEFAULT_PAYLOAD;
21 | this.qos = DEFAULT_QOS;
22 | this.retained = DEFAULT_RETAINMENT;
23 | }
24 |
25 | public Message(final String topic, final JsonElement payload) {
26 | this.topic = Objects.requireNonNull(topic);
27 | this.payload = payload;
28 | this.qos = DEFAULT_QOS;
29 | this.retained = DEFAULT_RETAINMENT;
30 | }
31 |
32 | public Message(final String topic, final JsonElement payload, final QualityOfService qos) {
33 | this.topic = Objects.requireNonNull(topic);
34 | this.payload = payload;
35 | this.qos = Objects.requireNonNull(qos);
36 | this.retained = DEFAULT_RETAINMENT;
37 | }
38 |
39 | public Message(final String topic, final JsonElement payload, final QualityOfService qos,
40 | boolean retained) {
41 | this.topic = Objects.requireNonNull(topic);
42 | this.payload = payload;
43 | this.qos = Objects.requireNonNull(qos);
44 | this.retained = retained;
45 | }
46 |
47 | public String getTopic() {
48 | return topic;
49 | }
50 |
51 | public Optional getPayload() {
52 | return Optional.ofNullable(payload);
53 | }
54 |
55 | public QualityOfService getQualityOfService() {
56 | return qos;
57 | }
58 |
59 | public boolean isRetained() {
60 | return retained;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/MessagePublisher.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | public interface MessagePublisher {
4 |
5 | void publish(Message message);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/MessagingModule.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import com.google.inject.PrivateModule;
4 | import com.google.inject.Singleton;
5 | import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
6 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
7 |
8 | public class MessagingModule extends PrivateModule {
9 |
10 | @Override
11 | protected void configure() {
12 | bind(MqttClientPersistence.class).to(MemoryPersistence.class).in(Singleton.class);
13 |
14 | bind(Registry.class).to(SimpleRegistry.class).in(Singleton.class);
15 |
16 | bind(Engine.class).in(Singleton.class);
17 | bind(MessagePublisher.class).to(Engine.class);
18 |
19 | expose(Registry.class);
20 | expose(MessagePublisher.class);
21 | expose(Engine.class);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/QualityOfService.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | public enum QualityOfService {
4 |
5 | AT_MOST_ONCE,
6 | AT_LEAST_ONCE,
7 | EXACTLY_ONCE;
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/Registry.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import java.util.Collection;
4 | import java.util.Map;
5 | import java.util.Optional;
6 | import java.util.Queue;
7 | import java.util.function.Consumer;
8 |
9 | public interface Registry extends RegistryConfigurator {
10 |
11 | Map getSubscriptions();
12 |
13 | Collection> getConsumers(String topic);
14 |
15 | Queue getInitialMessages();
16 |
17 | Optional getWill();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/RegistryConfigurator.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import java.util.function.Consumer;
4 |
5 | public interface RegistryConfigurator {
6 |
7 | void subscribe(final String topic, final Consumer consumer);
8 |
9 | void subscribe(final String topic, final Consumer consumer,
10 | final QualityOfService qos);
11 |
12 | void queueInitialMessage(final Message message);
13 |
14 | void setWill(final Message message);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/messaging/SimpleRegistry.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.messaging;
2 |
3 | import com.google.common.collect.HashMultimap;
4 | import com.google.common.collect.ImmutableMap;
5 | import com.google.common.collect.Multimap;
6 | import java.util.Collection;
7 | import java.util.HashMap;
8 | import java.util.LinkedList;
9 | import java.util.Map;
10 | import java.util.Optional;
11 | import java.util.Queue;
12 | import java.util.function.Consumer;
13 |
14 | class SimpleRegistry implements Registry {
15 |
16 | private static final QualityOfService DEFAULT_QOS = QualityOfService.EXACTLY_ONCE;
17 |
18 | private final Map topicQos = new HashMap<>();
19 | private final Multimap> consumers = HashMultimap.create();
20 | private final Queue initialMessages = new LinkedList<>();
21 | private Message will = null;
22 |
23 | @Override
24 | public void subscribe(final String topic, final Consumer consumer) {
25 | subscribe(topic, consumer, DEFAULT_QOS);
26 | }
27 |
28 | @Override
29 | public void subscribe(final String topic, final Consumer consumer,
30 | final QualityOfService qos) {
31 | topicQos.compute(topic, (currentTopic, currentQos) -> {
32 | if (currentQos == null || qos.compareTo(currentQos) > 0) {
33 | return qos;
34 | } else {
35 | return currentQos;
36 | }
37 | });
38 | consumers.put(topic, consumer);
39 | }
40 |
41 | @Override
42 | public Map getSubscriptions() {
43 | return ImmutableMap.copyOf(topicQos);
44 | }
45 |
46 | @Override
47 | public Collection> getConsumers(final String topic) {
48 | return consumers.get(topic);
49 | }
50 |
51 | @Override
52 | public void queueInitialMessage(final Message message) {
53 | initialMessages.add(message);
54 | }
55 |
56 | @Override
57 | public Queue getInitialMessages() {
58 | return initialMessages;
59 | }
60 |
61 | @Override
62 | public void setWill(final Message message) {
63 | this.will = message;
64 | }
65 |
66 | @Override
67 | public Optional getWill() {
68 | return Optional.ofNullable(will);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/desktop/DesktopController.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.desktop;
2 |
3 | import com.fatico.winthing.common.BaseController;
4 | import com.fatico.winthing.messaging.Message;
5 | import com.fatico.winthing.messaging.Registry;
6 | import com.google.inject.Inject;
7 | import java.util.NoSuchElementException;
8 | import java.util.Objects;
9 |
10 | public class DesktopController extends BaseController {
11 |
12 | private final DesktopService desktopService;
13 |
14 | @Inject
15 | public DesktopController(final Registry registry, final DesktopService desktopService) {
16 | super("desktop");
17 | this.desktopService = Objects.requireNonNull(desktopService);
18 | registry.subscribe(prefix + "commands/close_active_window", this::closeActiveWindow);
19 | registry.subscribe(prefix + "commands/set_display_sleep", this::setDisplaySleep);
20 | }
21 |
22 | public void closeActiveWindow(final Message message) {
23 | desktopService.getForegroundWindow().ifPresent(desktopService::closeWindow);
24 | }
25 |
26 | public void setDisplaySleep(final Message message) {
27 | final boolean sleep;
28 | try {
29 | sleep = message.getPayload().get().getAsBoolean();
30 | } catch (final NoSuchElementException | IllegalStateException exception) {
31 | throw new IllegalArgumentException("Invalid arguments.");
32 | }
33 | desktopService.setDisplaySleep(sleep);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/desktop/DesktopService.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.desktop;
2 |
3 | import com.fatico.winthing.windows.jna.User32;
4 | import com.google.inject.Inject;
5 | import com.sun.jna.Pointer;
6 | import com.sun.jna.platform.win32.WinDef;
7 | import com.sun.jna.platform.win32.WinUser;
8 | import com.sun.jna.ptr.IntByReference;
9 | import java.util.Objects;
10 | import java.util.Optional;
11 |
12 | public class DesktopService {
13 |
14 | private static final long SC_MONITORPOWER = 0xF170;
15 |
16 | private final User32 user32;
17 |
18 | @Inject
19 | public DesktopService(final User32 user32) {
20 | this.user32 = Objects.requireNonNull(user32);
21 | }
22 |
23 | public Optional getForegroundWindow() {
24 | return Optional.ofNullable(user32.GetForegroundWindow());
25 | }
26 |
27 | public void setForegroundWindow(final WinDef.HWND window) {
28 | user32.SetForegroundWindow(window);
29 | }
30 |
31 | public void closeWindow(final WinDef.HWND window) {
32 | user32.SendMessage(window, WinUser.WM_CLOSE, null, null);
33 | }
34 |
35 | public Optional getMainWindow(final int process) {
36 | class Callback implements WinUser.WNDENUMPROC {
37 | public WinDef.HWND foundWindow = null;
38 |
39 | @Override
40 | public boolean callback(final WinDef.HWND hwnd, final Pointer pointer) {
41 | final IntByReference processIdReference = new IntByReference();
42 | user32.GetWindowThreadProcessId(hwnd, processIdReference);
43 | if (processIdReference.getValue() == process) {
44 | foundWindow = hwnd;
45 | return false;
46 | }
47 | return true;
48 | }
49 | }
50 |
51 | final Callback callback = new Callback();
52 | user32.EnumWindows(callback, null);
53 | return Optional.ofNullable(callback.foundWindow);
54 | }
55 |
56 | public void setDisplaySleep(final boolean sleep) {
57 | user32.DefWindowProc(
58 | getForegroundWindow().get(),
59 | WinUser.WM_SYSCOMMAND,
60 | new WinDef.WPARAM(SC_MONITORPOWER),
61 | new WinDef.LPARAM(sleep ? 2 : -1)
62 | );
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/desktop/Module.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.desktop;
2 |
3 | import com.google.inject.PrivateModule;
4 | import com.google.inject.Singleton;
5 |
6 | public class Module extends PrivateModule {
7 |
8 | @Override
9 | protected void configure() {
10 | bind(DesktopService.class).in(Singleton.class);
11 | bind(DesktopController.class).asEagerSingleton();
12 | expose(DesktopService.class);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/keyboard/KeyboardController.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.keyboard;
2 |
3 | import com.fatico.winthing.common.BaseController;
4 | import com.fatico.winthing.messaging.Message;
5 | import com.fatico.winthing.messaging.Registry;
6 | import com.fatico.winthing.windows.input.KeyboardKey;
7 | import com.google.gson.JsonArray;
8 | import com.google.gson.JsonElement;
9 | import com.google.inject.Inject;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.NoSuchElementException;
13 | import java.util.Objects;
14 |
15 | public class KeyboardController extends BaseController {
16 |
17 | private final KeyboardService keyboardService;
18 |
19 | @Inject
20 | public KeyboardController(final Registry registry, final KeyboardService keyboardService) {
21 | super("keyboard");
22 | this.keyboardService = Objects.requireNonNull(keyboardService);
23 | registry.subscribe(prefix + "commands/press_keys", this::pressKeys);
24 | }
25 |
26 | public void pressKeys(final Message message) {
27 | final List keys;
28 | try {
29 | final JsonArray arguments = message.getPayload().get().getAsJsonArray();
30 | keys = new ArrayList<>(arguments.size());
31 | for (final JsonElement element : arguments) {
32 | try {
33 | keys.add(KeyboardKey.getByCodename(element.getAsString()));
34 | } catch (final NoSuchElementException exception) {
35 | throw new IllegalArgumentException("Unknown key: " + element.getAsString());
36 | }
37 | }
38 | } catch (final NoSuchElementException | IllegalStateException exception) {
39 | throw new IllegalArgumentException("Invalid arguments.");
40 | }
41 | keyboardService.pressKeys(keys);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/keyboard/KeyboardService.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.keyboard;
2 |
3 | import com.fatico.winthing.windows.input.KeyboardKey;
4 | import com.fatico.winthing.windows.jna.User32;
5 | import com.google.inject.Inject;
6 | import com.sun.jna.platform.win32.WinDef;
7 | import com.sun.jna.platform.win32.WinUser;
8 | import java.util.List;
9 | import java.util.ListIterator;
10 | import java.util.Objects;
11 |
12 | public class KeyboardService {
13 |
14 | private final User32 user32;
15 |
16 | @Inject
17 | public KeyboardService(final User32 user32) {
18 | this.user32 = Objects.requireNonNull(user32);
19 | }
20 |
21 | public void pressKeys(final List keys) {
22 | if (keys.isEmpty()) {
23 | return;
24 | }
25 |
26 | final WinUser.INPUT input = new WinUser.INPUT();
27 | final WinUser.INPUT[] inputs = (WinUser.INPUT[]) input.toArray(keys.size() * 2);
28 |
29 | final ListIterator iterator = keys.listIterator();
30 | int index = 0;
31 | while (iterator.hasNext()) {
32 | setKeyDown(inputs[index], iterator.next());
33 | index++;
34 | }
35 | while (iterator.hasPrevious()) {
36 | setKeyUp(inputs[index], iterator.previous());
37 | index++;
38 | }
39 |
40 | user32.SendInput(new WinDef.DWORD(inputs.length), inputs, inputs[0].size());
41 | }
42 |
43 | private void setKeyDown(final WinUser.INPUT input, final KeyboardKey key) {
44 | input.type.setValue(WinUser.INPUT.INPUT_KEYBOARD);
45 | input.input.setType(WinUser.KEYBDINPUT.class);
46 | input.input.ki.wVk = key.getVirtualKeyCode();
47 | }
48 |
49 | private void setKeyUp(final WinUser.INPUT input, final KeyboardKey key) {
50 | input.type.setValue(WinUser.INPUT.INPUT_KEYBOARD);
51 | input.input.setType(WinUser.KEYBDINPUT.class);
52 | input.input.ki.dwFlags.setValue(WinUser.KEYBDINPUT.KEYEVENTF_KEYUP);
53 | input.input.ki.wVk = key.getVirtualKeyCode();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/keyboard/Module.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.keyboard;
2 |
3 | import com.google.inject.PrivateModule;
4 | import com.google.inject.Singleton;
5 |
6 | public class Module extends PrivateModule {
7 |
8 | @Override
9 | protected void configure() {
10 | bind(KeyboardService.class).in(Singleton.class);
11 | bind(KeyboardController.class).asEagerSingleton();
12 | expose(KeyboardService.class);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/radeon/AdlException.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.radeon;
2 |
3 | import com.fatico.winthing.windows.SystemException;
4 |
5 | @SuppressWarnings("serial")
6 | public class AdlException extends SystemException {
7 |
8 | public AdlException(final String function, final int code) {
9 | super(function + " returned error code " + code + ".");
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/radeon/Module.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.radeon;
2 |
3 | import com.fatico.winthing.systems.radeon.jna.AtiAdl;
4 | import com.google.inject.PrivateModule;
5 | import com.google.inject.Singleton;
6 |
7 | public class Module extends PrivateModule {
8 |
9 | @Override
10 | protected void configure() {
11 | bind(AtiAdl.class).toInstance(AtiAdl.INSTANCE);
12 | bind(RadeonService.class).in(Singleton.class);
13 | bind(RadeonController.class).asEagerSingleton();
14 | expose(RadeonService.class);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/radeon/RadeonController.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.radeon;
2 |
3 | import com.fatico.winthing.common.BaseController;
4 | import com.fatico.winthing.messaging.Message;
5 | import com.fatico.winthing.messaging.Registry;
6 | import com.google.gson.JsonArray;
7 | import com.google.inject.Inject;
8 | import java.util.NoSuchElementException;
9 | import java.util.Objects;
10 |
11 | public class RadeonController extends BaseController {
12 |
13 | private final RadeonService radeonService;
14 |
15 | @Inject
16 | public RadeonController(final Registry registry, final RadeonService radeonService) {
17 | super("radeon");
18 | this.radeonService = Objects.requireNonNull(radeonService);
19 | registry.subscribe(prefix + "commands/set_best_resolution", this::setBestResolution);
20 | registry.subscribe(prefix + "commands/set_resolution", this::setResolution);
21 | }
22 |
23 | public void setBestResolution(final Message message) {
24 | radeonService.setBestResolution(radeonService.getPrimaryAdapterIndex());
25 | }
26 |
27 | public void setResolution(final Message message) {
28 | final int width;
29 | final int height;
30 | try {
31 | final JsonArray arguments = message.getPayload().get().getAsJsonArray();
32 | width = arguments.get(0).getAsInt();
33 | height = arguments.get(1).getAsInt();
34 | } catch (final NoSuchElementException | IllegalStateException exception) {
35 | throw new IllegalArgumentException("Invalid arguments.");
36 | }
37 | radeonService.setResolution(radeonService.getPrimaryAdapterIndex(), width, height);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/radeon/RadeonService.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.radeon;
2 |
3 | import com.fatico.winthing.systems.radeon.jna.AtiAdl;
4 | import com.google.common.collect.ComparisonChain;
5 | import com.google.inject.Inject;
6 | import com.sun.jna.Memory;
7 | import com.sun.jna.Pointer;
8 | import com.sun.jna.ptr.IntByReference;
9 | import com.sun.jna.ptr.PointerByReference;
10 | import java.util.Arrays;
11 | import java.util.Collections;
12 | import java.util.NoSuchElementException;
13 | import java.util.Objects;
14 |
15 | @SuppressWarnings({"checkstyle:nofinalizer"})
16 | public class RadeonService {
17 |
18 | private final AtiAdl atiAdl;
19 | private final Pointer context;
20 |
21 | @Inject
22 | public RadeonService(final AtiAdl atiAdl) {
23 | this.atiAdl = Objects.requireNonNull(atiAdl);
24 | {
25 | final PointerByReference contextReference = new PointerByReference();
26 | final int result = atiAdl.ADL2_Main_Control_Create(
27 | new MallocCallback(),
28 | 1,
29 | contextReference
30 | );
31 | if (result != AtiAdl.ADL_OK) {
32 | throw new AdlException("ADL2_Main_Control_Create", result);
33 | }
34 | this.context = contextReference.getValue();
35 | }
36 | }
37 |
38 | @SuppressWarnings("deprecation")
39 | @Override
40 | protected void finalize() throws Throwable {
41 | atiAdl.ADL2_Main_Control_Destroy(context);
42 | super.finalize();
43 | }
44 |
45 | public int getPrimaryAdapterIndex() {
46 | final IntByReference adapterIndexReference = new IntByReference();
47 | final int result = atiAdl.ADL2_Adapter_Primary_Get(
48 | context,
49 | adapterIndexReference
50 | );
51 | if (result != AtiAdl.ADL_OK) {
52 | throw new AdlException("ADL2_Display_CustomizedModeListNum_Get", result);
53 | }
54 | return adapterIndexReference.getValue();
55 | }
56 |
57 | public void setBestResolution(final int adapterIndex) {
58 | final AtiAdl.ADLMode mode = getBestMode(adapterIndex);
59 | setMode(adapterIndex, mode);
60 | }
61 |
62 | public void setResolution(final int adapterIndex, final int width, final int height) {
63 | final AtiAdl.ADLMode mode = getBestMode(adapterIndex);
64 | mode.iXRes = width;
65 | mode.iYRes = height;
66 | setMode(adapterIndex, mode);
67 | }
68 |
69 | private void setMode(final int adapterIndex, final AtiAdl.ADLMode mode) {
70 | final int result = atiAdl.ADL2_Display_Modes_Set(
71 | context,
72 | adapterIndex,
73 | -1,
74 | 1,
75 | (AtiAdl.ADLMode[]) mode.toArray(1)
76 | );
77 | if (result != AtiAdl.ADL_OK) {
78 | throw new AdlException("ADL2_Display_Modes_Set", result);
79 | }
80 | }
81 |
82 | private AtiAdl.ADLMode getBestMode(final int adapterIndex) {
83 | final AtiAdl.ADLMode[] modes;
84 | {
85 | final IntByReference numberOfModesReference = new IntByReference();
86 | final PointerByReference pointer = new PointerByReference();
87 | final int result = atiAdl.ADL2_Display_PossibleMode_Get(
88 | context,
89 | adapterIndex,
90 | numberOfModesReference,
91 | pointer
92 | );
93 | if (result != AtiAdl.ADL_OK) {
94 | throw new AdlException("ADL2_Display_Modes_Get", result);
95 | }
96 | modes = (AtiAdl.ADLMode[]) new AtiAdl.ADLMode(pointer.getValue()).toArray(
97 | numberOfModesReference.getValue()
98 | );
99 | }
100 | if (modes.length == 0) {
101 | throw new NoSuchElementException();
102 | }
103 | return Collections.max(Arrays.asList(modes), (left, right) -> ComparisonChain.start()
104 | .compare(left.iColourDepth, right.iColourDepth)
105 | .compare(left.iXRes, right.iXRes)
106 | .compare(left.iYRes, right.iYRes)
107 | .compare(left.fRefreshRate, right.fRefreshRate)
108 | .result()
109 | );
110 | }
111 |
112 | private static class MallocCallback extends Memory implements AtiAdl.ADL_MAIN_MALLOC_CALLBACK {
113 | @Override
114 | public Pointer invoke(int size) {
115 | return new Pointer(Memory.malloc(size));
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/radeon/jna/AtiAdl.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.radeon.jna;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.sun.jna.Callback;
5 | import com.sun.jna.Library;
6 | import com.sun.jna.Native;
7 | import com.sun.jna.Platform;
8 | import com.sun.jna.Pointer;
9 | import com.sun.jna.Structure;
10 | import com.sun.jna.ptr.IntByReference;
11 | import com.sun.jna.ptr.PointerByReference;
12 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 | import java.util.List;
14 |
15 | @SuppressWarnings({
16 | "checkstyle:typename",
17 | "checkstyle:abbreviationaswordinname",
18 | "checkstyle:methodname",
19 | "checkstyle:membername",
20 | "checkstyle:parametername"})
21 | @SuppressFBWarnings({"UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD",
22 | "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"})
23 | public interface AtiAdl extends Library {
24 |
25 | AtiAdl INSTANCE = (AtiAdl) Native.load(
26 | Platform.is64Bit() ? "atiadlxx" : "atiadlxy",
27 | AtiAdl.class
28 | );
29 |
30 | int ADL_OK = 0;
31 |
32 | interface ADL_MAIN_MALLOC_CALLBACK extends Callback {
33 | Pointer invoke(int size);
34 | }
35 |
36 | class ADLDisplayID extends Structure {
37 | public int iDisplayLogicalIndex;
38 | public int iDisplayPhysicalIndex;
39 | public int iDisplayLogicalAdapterIndex;
40 | public int iDisplayPhysicalAdapterIndex;
41 |
42 | @Override
43 | protected List getFieldOrder() {
44 | return ImmutableList.of(
45 | "iDisplayLogicalIndex",
46 | "iDisplayPhysicalIndex",
47 | "iDisplayLogicalAdapterIndex",
48 | "iDisplayPhysicalAdapterIndex"
49 | );
50 | }
51 | }
52 |
53 | class ADLMode extends Structure {
54 | public int iAdapterIndex;
55 | public ADLDisplayID displayID;
56 | public int iXPos;
57 | public int iYPos;
58 | public int iXRes;
59 | public int iYRes;
60 | public int iColourDepth;
61 | public float fRefreshRate;
62 | public int iOrientation;
63 | public int iModeFlag;
64 | public int iModeMask;
65 | public int iModeValue;
66 |
67 | public ADLMode(final Pointer pointer) {
68 | super(pointer);
69 | }
70 |
71 | @Override
72 | protected List getFieldOrder() {
73 | return ImmutableList.of(
74 | "iAdapterIndex",
75 | "displayID",
76 | "iXPos",
77 | "iYPos",
78 | "iXRes",
79 | "iYRes",
80 | "iColourDepth",
81 | "fRefreshRate",
82 | "iOrientation",
83 | "iModeFlag",
84 | "iModeMask",
85 | "iModeValue"
86 | );
87 | }
88 | }
89 |
90 | int ADL2_Main_Control_Create(
91 | ADL_MAIN_MALLOC_CALLBACK callback,
92 | int iEnumConnectedAdapters,
93 | PointerByReference context
94 | );
95 |
96 | int ADL2_Main_Control_Destroy(Pointer context);
97 |
98 | int ADL2_Adapter_Primary_Get(
99 | Pointer context,
100 | IntByReference lpPrimaryAdapterIndex
101 | );
102 |
103 | int ADL2_Display_Modes_Set(
104 | Pointer context,
105 | int iAdapterIndex,
106 | int iDisplayIndex,
107 | int iNumModes,
108 | ADLMode[] lpModes
109 | );
110 |
111 | int ADL2_Display_PossibleMode_Get(
112 | Pointer context,
113 | int iAdapterIndex,
114 | IntByReference lpNumModes,
115 | PointerByReference lppModes
116 | );
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/system/Module.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.system;
2 |
3 | import com.google.inject.PrivateModule;
4 | import com.google.inject.Singleton;
5 |
6 | public class Module extends PrivateModule {
7 |
8 | @Override
9 | protected void configure() {
10 | bind(SystemService.class).in(Singleton.class);
11 | bind(SystemController.class).asEagerSingleton();
12 | expose(SystemService.class);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/system/SystemCommander.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.system;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 | import com.typesafe.config.ConfigParseOptions;
6 | import com.typesafe.config.ConfigSyntax;
7 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8 | import java.io.File;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.Set;
12 | import java.util.StringJoiner;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | public class SystemCommander {
17 | public static final String ConfigFile = "winthing.ini";
18 |
19 | private final Logger logger = LoggerFactory.getLogger(getClass());
20 | private boolean isEnabled = false;
21 | private Map whitelist = new HashMap();
22 |
23 | public boolean isEnabled() {
24 | return isEnabled;
25 | }
26 |
27 | public String getCommand(String key) {
28 | return whitelist.get(key);
29 | }
30 |
31 | @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
32 | public void parseConfig() {
33 | String path = System.getProperty("user.dir") + File.separator + ConfigFile;
34 | File fp = new File(path);
35 | if (!fp.exists()) {
36 | logger.warn("No whitelist found. Every command is allowed to execute on this device!");
37 | return;
38 | }
39 |
40 | try {
41 | StringJoiner joiner = new StringJoiner(", ");
42 |
43 | ConfigParseOptions options = ConfigParseOptions.defaults();
44 | options.setSyntax(ConfigSyntax.CONF);
45 |
46 | Config cfg = ConfigFactory.parseFile(fp, options);
47 | Set map = cfg.root().keySet();
48 | for (String key : map) {
49 | whitelist.put(key, cfg.getString(key));
50 | joiner.add(key);
51 | }
52 |
53 | logger.info("Found whitelist of allowed commands to execute, using it...");
54 | logger.info("Allowed commands: [" + joiner.toString() + "]");
55 |
56 | isEnabled = true;
57 | } catch (Exception e) {
58 | logger.error("Unable to process whitelist file", e);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/system/SystemController.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.system;
2 |
3 | import com.fatico.winthing.common.BaseController;
4 | import com.fatico.winthing.messaging.Message;
5 | import com.fatico.winthing.messaging.QualityOfService;
6 | import com.fatico.winthing.messaging.Registry;
7 | import com.fatico.winthing.windows.SystemException;
8 | import com.google.gson.JsonArray;
9 | import com.google.gson.JsonPrimitive;
10 | import com.google.inject.Inject;
11 | import java.util.NoSuchElementException;
12 | import java.util.Objects;
13 |
14 | public class SystemController extends BaseController {
15 |
16 | private final SystemService systemService;
17 | private final SystemCommander systemCommander;
18 |
19 | @Inject
20 | public SystemController(final Registry registry, final SystemService systemService)
21 | throws SystemException {
22 | super("system");
23 | this.systemService = Objects.requireNonNull(systemService);
24 | registry.queueInitialMessage(
25 | makeMessage(
26 | "online",
27 | new JsonPrimitive(true),
28 | QualityOfService.AT_LEAST_ONCE,
29 | true
30 | )
31 | );
32 | registry.setWill(
33 | makeMessage(
34 | "online",
35 | new JsonPrimitive(false),
36 | QualityOfService.AT_LEAST_ONCE,
37 | true
38 | )
39 | );
40 | registry.subscribe(prefix + "commands/shutdown", this::shutdown);
41 | registry.subscribe(prefix + "commands/suspend", this::suspend);
42 | registry.subscribe(prefix + "commands/hibernate", this::hibernate);
43 | registry.subscribe(prefix + "commands/reboot", this::reboot);
44 | registry.subscribe(prefix + "commands/open", this::open);
45 | registry.subscribe(prefix + "commands/run", this::run);
46 |
47 | systemCommander = new SystemCommander();
48 | systemCommander.parseConfig();
49 | }
50 |
51 | public void shutdown(final Message message) {
52 | systemService.shutdown();
53 | }
54 |
55 | void reboot(final Message message) {
56 | systemService.reboot();
57 | }
58 |
59 | public void suspend(final Message message) {
60 | systemService.suspend();
61 | }
62 |
63 | public void hibernate(final Message message) {
64 | systemService.hibernate();
65 | }
66 |
67 | public void run(final Message message) {
68 | String command;
69 | String parameters;
70 | String workingDirectory;
71 |
72 | try {
73 | final JsonArray arguments = message.getPayload().get().getAsJsonArray();
74 | command = arguments.get(0).getAsString();
75 | parameters = arguments.size() > 1 ? arguments.get(1).getAsString() : "";
76 | workingDirectory = arguments.size() > 2 ? arguments.get(2).getAsString() : null;
77 | } catch (final NoSuchElementException | IllegalStateException exception) {
78 | throw new IllegalArgumentException("Invalid arguments.");
79 | }
80 |
81 | if (systemCommander.isEnabled()) {
82 | String commander = systemCommander.getCommand(command);
83 | if (commander == null) {
84 | throw new SystemException("Invalid command.");
85 | }
86 | command = commander;
87 | }
88 |
89 | systemService.run(command, parameters, workingDirectory);
90 | }
91 |
92 | public void open(final Message message) {
93 | final String uri;
94 | try {
95 | uri = message.getPayload().get().getAsString();
96 | } catch (final NoSuchElementException | IllegalStateException exception) {
97 | throw new IllegalArgumentException("Invalid arguments.");
98 | }
99 | systemService.open(uri);
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/systems/system/SystemService.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.systems.system;
2 |
3 | import com.fatico.winthing.windows.SystemException;
4 | import com.fatico.winthing.windows.jna.Advapi32;
5 | import com.fatico.winthing.windows.jna.Kernel32;
6 | import com.google.common.collect.ImmutableList;
7 | import com.google.inject.Inject;
8 | import com.sun.jna.platform.win32.Kernel32Util;
9 | import com.sun.jna.platform.win32.Shell32;
10 | import com.sun.jna.platform.win32.Tlhelp32;
11 | import com.sun.jna.platform.win32.WinDef;
12 | import com.sun.jna.platform.win32.WinNT;
13 | import com.sun.jna.platform.win32.WinUser;
14 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.Objects;
19 |
20 | public class SystemService {
21 |
22 | private static final List REQUIRED_PRIVILEGES = ImmutableList.of(
23 | WinNT.SE_SHUTDOWN_NAME
24 | );
25 |
26 | private final Kernel32 kernel32;
27 | private final Advapi32 advapi32;
28 | private final Shell32 shell32;
29 |
30 | @Inject
31 | public SystemService(final Kernel32 kernel32, final Advapi32 advapi32,
32 | final Shell32 shell32) throws SystemException {
33 | this.kernel32 = Objects.requireNonNull(kernel32);
34 | this.advapi32 = Objects.requireNonNull(advapi32);
35 | this.shell32 = Objects.requireNonNull(shell32);
36 | escalatePrivileges(REQUIRED_PRIVILEGES);
37 | }
38 |
39 | public void shutdown() throws SystemException {
40 | final boolean success = advapi32.InitiateSystemShutdown(
41 | null,
42 | null,
43 | new WinDef.DWORD(0),
44 | true,
45 | false
46 | );
47 | if (!success) {
48 | throw new SystemException(Kernel32Util.formatMessage(kernel32.GetLastError()));
49 | }
50 | }
51 |
52 | public void reboot() throws SystemException {
53 | final boolean success = advapi32.InitiateSystemShutdown(
54 | null,
55 | null,
56 | new WinDef.DWORD(0),
57 | true,
58 | true
59 | );
60 | if (!success) {
61 | throw new SystemException(Kernel32Util.formatMessage(kernel32.GetLastError()));
62 | }
63 | }
64 |
65 | public void suspend() throws SystemException {
66 | final boolean success = kernel32.SetSystemPowerState(
67 | true,
68 | false
69 | );
70 | if (!success) {
71 | throw new SystemException(Kernel32Util.formatMessage(kernel32.GetLastError()));
72 | }
73 | }
74 |
75 | public void hibernate() throws SystemException {
76 | final boolean success = kernel32.SetSystemPowerState(
77 | false,
78 | false
79 | );
80 | if (!success) {
81 | throw new SystemException(Kernel32Util.formatMessage(kernel32.GetLastError()));
82 | }
83 | }
84 |
85 | public void run(final String command, final String parameters, final String workingDirectory)
86 | throws SystemException {
87 | final WinDef.INT_PTR result = shell32.ShellExecute(
88 | null,
89 | "open",
90 | Objects.requireNonNull(command),
91 | Objects.requireNonNull(parameters),
92 | workingDirectory,
93 | WinUser.SW_SHOWNORMAL
94 | );
95 | if (result.intValue() <= 32) {
96 | throw new SystemException("Could not run command: " + command + " " + parameters);
97 | }
98 | }
99 |
100 | public void open(final String uri) throws SystemException {
101 | final WinDef.INT_PTR result = shell32.ShellExecute(
102 | null,
103 | "open",
104 | Objects.requireNonNull(uri),
105 | null,
106 | null,
107 | WinUser.SW_SHOWNORMAL
108 | );
109 | if (result.intValue() <= 32) {
110 | throw new SystemException("Cannot open URI: " + uri);
111 | }
112 | }
113 |
114 | @SuppressFBWarnings("DM_CONVERT_CASE")
115 | public Map findProcesses(final String nameFragment) {
116 | Objects.requireNonNull(nameFragment);
117 |
118 | final String lowercaseNameFragment = nameFragment.toLowerCase();
119 | final Map processIds = new HashMap<>();
120 |
121 | final WinNT.HANDLE snapshot = kernel32.CreateToolhelp32Snapshot(
122 | Tlhelp32.TH32CS_SNAPPROCESS,
123 | null
124 | );
125 | try {
126 | final Tlhelp32.PROCESSENTRY32.ByReference entryReference =
127 | new Tlhelp32.PROCESSENTRY32.ByReference();
128 | if (kernel32.Process32First(snapshot, entryReference)) {
129 | while (kernel32.Process32Next(snapshot, entryReference)) {
130 | final String processName = new String(entryReference.szExeFile).trim();
131 | if (processName.toLowerCase().contains(lowercaseNameFragment)) {
132 | processIds.put(entryReference.th32ProcessID.intValue(), processName);
133 | }
134 | }
135 | }
136 | } finally {
137 | kernel32.CloseHandle(snapshot);
138 | }
139 |
140 | return processIds;
141 | }
142 |
143 | private void escalatePrivileges(final List requiredPrivilegeNames)
144 | throws SystemException {
145 | final WinNT.HANDLE accessToken;
146 | {
147 | final WinNT.HANDLEByReference tokenReference = new WinNT.HANDLEByReference();
148 | final boolean success = advapi32.OpenProcessToken(
149 | kernel32.GetCurrentProcess(),
150 | WinNT.TOKEN_ADJUST_PRIVILEGES | WinNT.TOKEN_QUERY,
151 | tokenReference
152 | );
153 | if (!success) {
154 | throw new SystemException("Cannot open access token");
155 | }
156 | accessToken = tokenReference.getValue();
157 | }
158 |
159 | final WinNT.TOKEN_PRIVILEGES privileges = new WinNT.TOKEN_PRIVILEGES(
160 | requiredPrivilegeNames.size()
161 | );
162 | {
163 | privileges.PrivilegeCount.setValue(requiredPrivilegeNames.size());
164 | int index = 0;
165 | for (final String privilegeName : requiredPrivilegeNames) {
166 | final WinNT.LUID luid = new WinNT.LUID();
167 | {
168 | final boolean success = advapi32.LookupPrivilegeValue(
169 | null,
170 | privilegeName,
171 | luid
172 | );
173 | if (!success) {
174 | throw new SystemException("Cannot find privilege " + privilegeName);
175 | }
176 | }
177 | privileges.Privileges[index] = new WinNT.LUID_AND_ATTRIBUTES();
178 | privileges.Privileges[index].Luid = luid;
179 | privileges.Privileges[index].Attributes.setValue(WinNT.SE_PRIVILEGE_ENABLED);
180 | index++;
181 | }
182 | }
183 |
184 | {
185 | final boolean success = advapi32.AdjustTokenPrivileges(
186 | accessToken,
187 | false,
188 | privileges,
189 | privileges.size(),
190 | null,
191 | null
192 | );
193 | if (!success) {
194 | throw new SystemException(
195 | "Cannot obtain required privileges: "
196 | + Kernel32Util.formatMessage(kernel32.GetLastError())
197 | );
198 | }
199 | }
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/SystemException.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows;
2 |
3 | @SuppressWarnings("serial")
4 | public class SystemException extends RuntimeException {
5 |
6 | public SystemException(final String message) {
7 | super(message);
8 | }
9 |
10 | public SystemException(final String message, final Throwable cause) {
11 | super(message, cause);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/WindowsModule.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows;
2 |
3 | import com.fatico.winthing.windows.jna.Advapi32;
4 | import com.fatico.winthing.windows.jna.Kernel32;
5 | import com.fatico.winthing.windows.jna.User32;
6 | import com.google.inject.PrivateModule;
7 | import com.sun.jna.platform.win32.Shell32;
8 |
9 | public class WindowsModule extends PrivateModule {
10 |
11 | @Override
12 | protected void configure() {
13 | bind(User32.class).toInstance(User32.INSTANCE);
14 | expose(User32.class);
15 |
16 | bind(Kernel32.class).toInstance(Kernel32.INSTANCE);
17 | expose(Kernel32.class);
18 |
19 | bind(Advapi32.class).toInstance(Advapi32.INSTANCE);
20 | expose(Advapi32.class);
21 |
22 | bind(Shell32.class).toInstance(Shell32.INSTANCE);
23 | expose(Shell32.class);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/input/KeyboardKey.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows.input;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import com.sun.jna.platform.win32.WinDef;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.NoSuchElementException;
9 |
10 | @SuppressFBWarnings("DM_CONVERT_CASE")
11 | public enum KeyboardKey {
12 |
13 | CANCEL(3),
14 | BACK(8, "BACKSPACE"),
15 | TAB(9),
16 | CLEAR(12),
17 | RETURN(13, "ENTER"),
18 | SHIFT(16),
19 | CONTROL(17, "CTRL"),
20 | MENU(18, "ALT"),
21 | PAUSE(19),
22 | CAPITAL(20, "CAPSLOCK"),
23 | KANA(0x15),
24 | HANGEUL(0x15),
25 | HANGUL(0x15),
26 | JUNJA(0x17),
27 | FINAL(0x18),
28 | HANJA(0x19),
29 | KANJI(0x19),
30 | ESCAPE(0x1B, "ESC"),
31 | CONVERT(0x1C),
32 | NONCONVERT(0x1D),
33 | ACCEPT(0x1E),
34 | MODECHANGE(0x1F),
35 | SPACE(32, " "),
36 | PRIOR(33),
37 | NEXT(34),
38 | END(35),
39 | HOME(36),
40 | LEFT(37),
41 | UP(38),
42 | RIGHT(39),
43 | DOWN(40),
44 | SELECT(41),
45 | PRINT(42),
46 | EXECUTE(43),
47 | SNAPSHOT(44),
48 | INSERT(45),
49 | DELETE(46),
50 | HELP(47),
51 |
52 | NUM0(0x30, "0"),
53 | NUM1(0x31, "1"),
54 | NUM2(0x32, "2"),
55 | NUM3(0x33, "3"),
56 | NUM4(0x34, "4"),
57 | NUM5(0x35, "5"),
58 | NUM6(0x36, "6"),
59 | NUM7(0x37, "7"),
60 | NUM8(0x38, "8"),
61 | NUM9(0x39, "9"),
62 |
63 | A(0x41),
64 | B(0x42),
65 | C(0x43),
66 | D(0x44),
67 | E(0x45),
68 | F(0x46),
69 | G(0x47),
70 | H(0x48),
71 | I(0x49),
72 | J(0x4a),
73 | K(0x4b),
74 | L(0x4c),
75 | M(0x4d),
76 | N(0x4e),
77 | O(0x4f),
78 | P(0x50),
79 | Q(0x51),
80 | R(0x52),
81 | S(0x53),
82 | T(0x54),
83 | U(0x55),
84 | V(0x56),
85 | W(0x57),
86 | X(0x58),
87 | Y(0x59),
88 | Z(0x5a),
89 |
90 | LWIN(0x5B, "LEFT_WIN", "LEFT_WINDOWS"),
91 | RWIN(0x5C, "RIGHT_WIN", "RIGHT_WINDOWS"),
92 | APPS(0x5D),
93 | SLEEP(0x5F),
94 | NUMPAD0(0x60),
95 | NUMPAD1(0x61),
96 | NUMPAD2(0x62),
97 | NUMPAD3(0x63),
98 | NUMPAD4(0x64),
99 | NUMPAD5(0x65),
100 | NUMPAD6(0x66),
101 | NUMPAD7(0x67),
102 | NUMPAD8(0x68),
103 | NUMPAD9(0x69),
104 | MULTIPLY(0x6A),
105 | ADD(0x6B),
106 | SEPARATOR(0x6C),
107 | SUBTRACT(0x6D),
108 | DECIMAL(0x6E),
109 | DIVIDE(0x6F),
110 | F1(0x70),
111 | F2(0x71),
112 | F3(0x72),
113 | F4(0x73),
114 | F5(0x74),
115 | F6(0x75),
116 | F7(0x76),
117 | F8(0x77),
118 | F9(0x78),
119 | F10(0x79),
120 | F11(0x7A),
121 | F12(0x7B),
122 | F13(0x7C),
123 | F14(0x7D),
124 | F15(0x7E),
125 | F16(0x7F),
126 | F17(0x80),
127 | F18(0x81),
128 | F19(0x82),
129 | F20(0x83),
130 | F21(0x84),
131 | F22(0x85),
132 | F23(0x86),
133 | F24(0x87),
134 | NUMLOCK(0x90),
135 | SCROLL(0x91),
136 | LSHIFT(0xA0),
137 | RSHIFT(0xA1),
138 | LCONTROL(0xA2),
139 | RCONTROL(0xA3),
140 | LMENU(0xA4, "LEFT_ALT"),
141 | RMENU(0xA5, "RIGHT_ALT"),
142 | BROWSER_BACK(0xA6),
143 | BROWSER_FORWARD(0xA7),
144 | BROWSER_REFRESH(0xA8),
145 | BROWSER_STOP(0xA9),
146 | BROWSER_SEARCH(0xAA),
147 | BROWSER_FAVORITES(0xAB),
148 | BROWSER_HOME(0xAC),
149 | VOLUME_MUTE(0xAD),
150 | VOLUME_DOWN(0xAE),
151 | VOLUME_UP(0xAF),
152 | MEDIA_NEXT_TRACK(0xB0),
153 | MEDIA_PREV_TRACK(0xB1),
154 | MEDIA_STOP(0xB2),
155 | MEDIA_PLAY_PAUSE(0xB3),
156 | LAUNCH_MAIL(0xB4),
157 | LAUNCH_MEDIA_SELECT(0xB5),
158 | LAUNCH_APP1(0xB6),
159 | LAUNCH_APP2(0xB7),
160 | OEM_1(0xBA),
161 | OEM_PLUS(0xBB),
162 | OEM_COMMA(0xBC),
163 | OEM_MINUS(0xBD),
164 | OEM_PERIOD(0xBE),
165 | OEM_2(0xBF),
166 | OEM_3(0xC0),
167 | OEM_4(0xDB),
168 | OEM_5(0xDC),
169 | OEM_6(0xDD),
170 | OEM_7(0xDE),
171 | OEM_8(0xDF),
172 | OEM_102(0xE2),
173 | PROCESSKEY(0xE5),
174 | PACKET(0xE7),
175 | ATTN(0xF6),
176 | CRSEL(0xF7),
177 | EXSEL(0xF8),
178 | EREOF(0xF9),
179 | PLAY(0xFA),
180 | ZOOM(0xFB),
181 | NONAME(0xFC),
182 | PA1(0xFD),
183 | OEM_CLEAR(0xFE);
184 |
185 | private static final Map index = new HashMap<>();
186 |
187 | static {
188 | for (final KeyboardKey key : KeyboardKey.values()) {
189 | index.put(key.name().toLowerCase(), key);
190 | for (final String alias : key.aliases) {
191 | index.put(alias.toLowerCase(), key);
192 | }
193 | }
194 | }
195 |
196 | private final WinDef.WORD virtualKeyCode;
197 | private final ImmutableSet aliases;
198 |
199 | KeyboardKey(final int virtualKeyCode, final String... aliases) {
200 | assert 0 < virtualKeyCode;
201 | assert virtualKeyCode < 0xFF;
202 | this.virtualKeyCode = new WinDef.WORD(virtualKeyCode);
203 | this.aliases = ImmutableSet.copyOf(aliases);
204 | }
205 |
206 | public WinDef.WORD getVirtualKeyCode() {
207 | return virtualKeyCode;
208 | }
209 |
210 | public static KeyboardKey getByCodename(final String codename) {
211 | final KeyboardKey key = index.get(codename.toLowerCase());
212 | if (key == null) {
213 | throw new NoSuchElementException("Unknown key: " + codename);
214 | }
215 | return key;
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/input/MouseButton.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows.input;
2 |
3 | import com.sun.jna.platform.win32.WinDef;
4 |
5 | public enum MouseButton {
6 |
7 | LEFT(1),
8 | RIGHT(2),
9 | MIDDLE(4),
10 | X1(5),
11 | X2(6);
12 |
13 | private final WinDef.WORD virtualKeyCode;
14 |
15 | MouseButton(final int virtualKeyCode) {
16 | assert 0 < virtualKeyCode;
17 | assert virtualKeyCode < 0xFF;
18 | this.virtualKeyCode = new WinDef.WORD(virtualKeyCode);
19 | }
20 |
21 | public WinDef.WORD getVirtualKeyCode() {
22 | return virtualKeyCode;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/jna/Advapi32.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows.jna;
2 |
3 | import com.sun.jna.Native;
4 | import com.sun.jna.platform.win32.WinDef;
5 | import com.sun.jna.win32.W32APIOptions;
6 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
7 |
8 | @SuppressWarnings({"checkstyle:methodname", "checkstyle:parametername"})
9 | @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
10 | public interface Advapi32 extends com.sun.jna.platform.win32.Advapi32 {
11 |
12 | Advapi32 INSTANCE = (Advapi32) Native.load(
13 | "advapi32",
14 | Advapi32.class,
15 | W32APIOptions.DEFAULT_OPTIONS
16 | );
17 |
18 | boolean InitiateSystemShutdown(
19 | String lpMachineName,
20 | String lpMessage,
21 | WinDef.DWORD dwTimeout,
22 | boolean bForceAppsClosed,
23 | boolean bRebootAfterShutdown
24 | );
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/jna/Kernel32.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows.jna;
2 |
3 | import com.sun.jna.Native;
4 | import com.sun.jna.win32.W32APIOptions;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 |
7 | @SuppressWarnings({"checkstyle:methodname", "checkstyle:parametername"})
8 | @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
9 | public interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {
10 |
11 | Kernel32 INSTANCE = (Kernel32) Native.load(
12 | "kernel32",
13 | Kernel32.class,
14 | W32APIOptions.DEFAULT_OPTIONS
15 | );
16 |
17 | boolean SetSystemPowerState(
18 | boolean fSuspend,
19 | boolean fForce
20 | );
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/fatico/winthing/windows/jna/User32.java:
--------------------------------------------------------------------------------
1 | package com.fatico.winthing.windows.jna;
2 |
3 | import com.sun.jna.Native;
4 | import com.sun.jna.win32.W32APIOptions;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 |
7 | @SuppressWarnings({"checkstyle:methodname", "checkstyle:parametername"})
8 | @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
9 | public interface User32 extends com.sun.jna.platform.win32.User32 {
10 |
11 | User32 INSTANCE = (User32) Native.load(
12 | "user32",
13 | User32.class,
14 | W32APIOptions.DEFAULT_OPTIONS
15 | );
16 |
17 | LRESULT SendMessage(HWND hWnd, int Msg, WPARAM wParam, LPARAM lParam);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | broker = "127.0.0.1:1883"
2 | username = mqtt
3 | password = mqtt
4 | clientid = WinThing
5 | prefix = winthing
6 | reconnect = 5
--------------------------------------------------------------------------------
/src/main/resources/favicon-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msiedlarek/winthing/7a565e58d5bec8c179bf139a9c9b93bf7079e94c/src/main/resources/favicon-green.png
--------------------------------------------------------------------------------
/src/main/resources/favicon-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msiedlarek/winthing/7a565e58d5bec8c179bf139a9c9b93bf7079e94c/src/main/resources/favicon-red.png
--------------------------------------------------------------------------------
/src/main/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msiedlarek/winthing/7a565e58d5bec8c179bf139a9c9b93bf7079e94c/src/main/resources/favicon.ico
--------------------------------------------------------------------------------
/src/main/resources/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msiedlarek/winthing/7a565e58d5bec8c179bf139a9c9b93bf7079e94c/src/main/resources/favicon.png
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss} %level [%thread/%logger{36}] %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 | winthing.log
14 | false
15 |
16 | %d{HH:mm:ss} %level [%thread/%logger{36}] %msg%n
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------